Allow features to be disabled at build time (#143)

* Add framework for built-time feature selection
* Allow MQTT, NTP, OTA features to be disabled at build time
* Allow Project screens to be disabled at build time
* Allow security features to be disabled at build time
* Switch to std::function for StatefulService function aliases for greater flexibility
* Bump various UI lib versions
* Update docs
This commit is contained in:
rjwats
2020-06-09 21:57:44 +01:00
committed by GitHub
parent 88748ac30d
commit 449d3c91ce
36 changed files with 800 additions and 193 deletions

View File

@ -1,7 +1,8 @@
import * as H from 'history';
import history from '../history';
import { PROJECT_PATH } from '../api';
import { Features } from '../features/types';
import { getDefaultRoute } from '../AppRouting';
export const ACCESS_TOKEN = 'access_token';
export const LOGIN_PATHNAME = 'loginPathname';
@ -26,12 +27,12 @@ export function clearLoginRedirect() {
getStorage().removeItem(LOGIN_SEARCH);
}
export function fetchLoginRedirect(): H.LocationDescriptorObject {
export function fetchLoginRedirect(features: Features): H.LocationDescriptorObject {
const loginPathname = getStorage().getItem(LOGIN_PATHNAME);
const loginSearch = getStorage().getItem(LOGIN_SEARCH);
clearLoginRedirect();
return {
pathname: loginPathname || `/${PROJECT_PATH}/`,
pathname: loginPathname || getDefaultRoute(features),
search: (loginPathname && loginSearch) || undefined
};
}

View File

@ -2,37 +2,21 @@ import * as React from 'react';
import { withSnackbar, WithSnackbarProps } from 'notistack';
import jwtDecode from 'jwt-decode';
import CircularProgress from '@material-ui/core/CircularProgress';
import Typography from '@material-ui/core/Typography';
import { withStyles, Theme, createStyles, WithStyles } from '@material-ui/core/styles';
import history from '../history'
import { VERIFY_AUTHORIZATION_ENDPOINT } from '../api';
import { ACCESS_TOKEN, authorizedFetch, getStorage } from './Authentication';
import { AuthenticationContext, Me } from './AuthenticationContext';
import FullScreenLoading from '../components/FullScreenLoading';
import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext';
export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken);
const styles = (theme: Theme) => createStyles({
loadingPanel: {
padding: theme.spacing(2),
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100vh",
flexDirection: "column"
},
progress: {
margin: theme.spacing(4),
}
});
interface AuthenticationWrapperState {
context: AuthenticationContext;
initialized: boolean;
}
type AuthenticationWrapperProps = WithSnackbarProps & WithStyles<typeof styles>;
type AuthenticationWrapperProps = WithSnackbarProps & WithFeaturesProps;
class AuthenticationWrapper extends React.Component<AuthenticationWrapperProps, AuthenticationWrapperState> {
@ -69,18 +53,16 @@ class AuthenticationWrapper extends React.Component<AuthenticationWrapperProps,
}
renderContentLoading() {
const { classes } = this.props;
return (
<div className={classes.loadingPanel}>
<CircularProgress className={classes.progress} size={100} />
<Typography variant="h4" >
Loading...
</Typography>
</div>
<FullScreenLoading />
);
}
refresh = () => {
if (!this.props.features.security) {
this.setState({ initialized: true, context: { ...this.state.context, me: { admin: true, username: "admin" } } });
return;
}
const accessToken = getStorage().getItem(ACCESS_TOKEN)
if (accessToken) {
authorizedFetch(VERIFY_AUTHORIZATION_ENDPOINT)
@ -124,4 +106,4 @@ class AuthenticationWrapper extends React.Component<AuthenticationWrapperProps,
}
export default withStyles(styles)(withSnackbar(AuthenticationWrapper))
export default withFeatures(withSnackbar(AuthenticationWrapper))

View File

@ -3,19 +3,21 @@ import { Redirect, Route, RouteProps, RouteComponentProps } from "react-router-d
import { withAuthenticationContext, AuthenticationContextProps } from './AuthenticationContext';
import * as Authentication from './Authentication';
import { WithFeaturesProps, withFeatures } from '../features/FeaturesContext';
interface UnauthenticatedRouteProps extends RouteProps {
interface UnauthenticatedRouteProps extends RouteProps, AuthenticationContextProps, WithFeaturesProps {
component: React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>;
}
type RenderComponent = (props: RouteComponentProps<any>) => React.ReactNode;
class UnauthenticatedRoute extends Route<UnauthenticatedRouteProps & AuthenticationContextProps> {
class UnauthenticatedRoute extends Route<UnauthenticatedRouteProps> {
public render() {
const { authenticationContext, component:Component, ...rest } = this.props;
const { authenticationContext, component: Component, features, ...rest } = this.props;
const renderComponent: RenderComponent = (props) => {
if (authenticationContext.me) {
return (<Redirect to={Authentication.fetchLoginRedirect()} />);
return (<Redirect to={Authentication.fetchLoginRedirect(features)} />);
}
return (<Component {...props} />);
}
@ -25,4 +27,4 @@ class UnauthenticatedRoute extends Route<UnauthenticatedRouteProps & Authenticat
}
}
export default withAuthenticationContext(UnauthenticatedRoute);
export default withFeatures(withAuthenticationContext(UnauthenticatedRoute));