diff --git a/interface/config-overrides.js b/interface/config-overrides.js index 562a63dbd..96e481183 100644 --- a/interface/config-overrides.js +++ b/interface/config-overrides.js @@ -1,42 +1,42 @@ -const ManifestPlugin = require('webpack-manifest-plugin') -const WorkboxWebpackPlugin = require('workbox-webpack-plugin') -const MiniCssExtractPlugin = require('mini-css-extract-plugin') -const CompressionPlugin = require('compression-webpack-plugin') -const ProgmemGenerator = require('./progmem-generator.js') +const ManifestPlugin = require('webpack-manifest-plugin'); +const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const CompressionPlugin = require('compression-webpack-plugin'); +const ProgmemGenerator = require('./progmem-generator.js'); module.exports = function override(config, env) { - const hosted = process.env.REACT_APP_HOSTED + const hosted = process.env.REACT_APP_HOSTED; if (env === 'production' && !hosted) { - console.log('Custom webpack...') + console.log('Custom webpack...'); // rename the output file, we need it's path to be short for LittleFS - config.output.filename = 'js/[id].[chunkhash:4].js' - config.output.chunkFilename = 'js/[id].[chunkhash:4].js' + config.output.filename = 'js/[id].[chunkhash:4].js'; + config.output.chunkFilename = 'js/[id].[chunkhash:4].js'; // take out the manifest and service worker plugins config.plugins = config.plugins.filter( - (plugin) => !(plugin instanceof ManifestPlugin), - ) + (plugin) => !(plugin instanceof ManifestPlugin) + ); config.plugins = config.plugins.filter( - (plugin) => !(plugin instanceof WorkboxWebpackPlugin.GenerateSW), - ) + (plugin) => !(plugin instanceof WorkboxWebpackPlugin.GenerateSW) + ); // shorten css filenames const miniCssExtractPlugin = config.plugins.find( - (plugin) => plugin instanceof MiniCssExtractPlugin, - ) - miniCssExtractPlugin.options.filename = 'css/[id].[contenthash:4].css' + (plugin) => plugin instanceof MiniCssExtractPlugin + ); + miniCssExtractPlugin.options.filename = 'css/[id].[contenthash:4].css'; miniCssExtractPlugin.options.chunkFilename = - 'css/[id].[contenthash:4].c.css' + 'css/[id].[contenthash:4].c.css'; // build progmem data files config.plugins.push( new ProgmemGenerator({ outputPath: '../lib/framework/WWWData.h', - bytesPerLine: 20, - }), - ) + bytesPerLine: 20 + }) + ); // add compression plugin, compress javascript config.plugins.push( @@ -44,9 +44,9 @@ module.exports = function override(config, env) { filename: '[path].gz[query]', algorithm: 'gzip', test: /\.(js)$/, - deleteOriginalAssets: true, - }), - ) + deleteOriginalAssets: true + }) + ); } - return config -} + return config; +}; diff --git a/interface/progmem-generator.js b/interface/progmem-generator.js index f06ed338b..ef8855962 100644 --- a/interface/progmem-generator.js +++ b/interface/progmem-generator.js @@ -1,37 +1,37 @@ -const { resolve, relative, sep } = require('path') +const { resolve, relative, sep } = require('path'); const { readdirSync, existsSync, unlinkSync, readFileSync, - createWriteStream, -} = require('fs') -var zlib = require('zlib') -var mime = require('mime-types') + createWriteStream +} = require('fs'); +var zlib = require('zlib'); +var mime = require('mime-types'); -const ARDUINO_INCLUDES = '#include \n\n' +const ARDUINO_INCLUDES = '#include \n\n'; function getFilesSync(dir, files = []) { readdirSync(dir, { withFileTypes: true }).forEach((entry) => { - const entryPath = resolve(dir, entry.name) + const entryPath = resolve(dir, entry.name); if (entry.isDirectory()) { - getFilesSync(entryPath, files) + getFilesSync(entryPath, files); } else { - files.push(entryPath) + files.push(entryPath); } - }) - return files + }); + return files; } function coherseToBuffer(input) { - return Buffer.isBuffer(input) ? input : Buffer.from(input) + return Buffer.isBuffer(input) ? input : Buffer.from(input); } function cleanAndOpen(path) { if (existsSync(path)) { - unlinkSync(path) + unlinkSync(path); } - return createWriteStream(path, { flags: 'w+' }) + return createWriteStream(path, { flags: 'w+' }); } class ProgmemGenerator { @@ -40,70 +40,70 @@ class ProgmemGenerator { outputPath, bytesPerLine = 20, indent = ' ', - includes = ARDUINO_INCLUDES, - } = options - this.options = { outputPath, bytesPerLine, indent, includes } + includes = ARDUINO_INCLUDES + } = options; + this.options = { outputPath, bytesPerLine, indent, includes }; } apply(compiler) { compiler.hooks.emit.tapAsync( { name: 'ProgmemGenerator' }, (compilation, callback) => { - const { outputPath, bytesPerLine, indent, includes } = this.options - const fileInfo = [] + const { outputPath, bytesPerLine, indent, includes } = this.options; + const fileInfo = []; const writeStream = cleanAndOpen( - resolve(compilation.options.context, outputPath), - ) + resolve(compilation.options.context, outputPath) + ); try { const writeIncludes = () => { - writeStream.write(includes) - } + writeStream.write(includes); + }; const writeFile = (relativeFilePath, buffer) => { - const variable = 'ESP_REACT_DATA_' + fileInfo.length - const mimeType = mime.lookup(relativeFilePath) - var size = 0 - writeStream.write('const uint8_t ' + variable + '[] PROGMEM = {') - const zipBuffer = zlib.gzipSync(buffer) + const variable = 'ESP_REACT_DATA_' + fileInfo.length; + const mimeType = mime.lookup(relativeFilePath); + var size = 0; + writeStream.write('const uint8_t ' + variable + '[] PROGMEM = {'); + const zipBuffer = zlib.gzipSync(buffer); zipBuffer.forEach((b) => { if (!(size % bytesPerLine)) { - writeStream.write('\n') - writeStream.write(indent) + writeStream.write('\n'); + writeStream.write(indent); } writeStream.write( - '0x' + ('00' + b.toString(16).toUpperCase()).substr(-2) + ',', - ) - size++ - }) + '0x' + ('00' + b.toString(16).toUpperCase()).substr(-2) + ',' + ); + size++; + }); if (size % bytesPerLine) { - writeStream.write('\n') + writeStream.write('\n'); } - writeStream.write('};\n\n') + writeStream.write('};\n\n'); fileInfo.push({ uri: '/' + relativeFilePath.replace(sep, '/'), mimeType, variable, - size, - }) - } + size + }); + }; const writeFiles = () => { // process static files - const buildPath = compilation.options.output.path + const buildPath = compilation.options.output.path; for (const filePath of getFilesSync(buildPath)) { - const readStream = readFileSync(filePath) - const relativeFilePath = relative(buildPath, filePath) - writeFile(relativeFilePath, readStream) + const readStream = readFileSync(filePath); + const relativeFilePath = relative(buildPath, filePath); + writeFile(relativeFilePath, readStream); } // process assets - const { assets } = compilation + const { assets } = compilation; Object.keys(assets).forEach((relativeFilePath) => { writeFile( relativeFilePath, - coherseToBuffer(assets[relativeFilePath].source()), - ) - }) - } + coherseToBuffer(assets[relativeFilePath].source()) + ); + }); + }; const generateWWWClass = () => { return `typedef std::function RouteRegistrationHandler; @@ -111,38 +111,38 @@ class ProgmemGenerator { class WWWData { ${indent}public: ${indent.repeat( - 2, + 2 )}static void registerRoutes(RouteRegistrationHandler handler) { ${fileInfo .map( (file) => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${ file.variable - }, ${file.size});`, + }, ${file.size});` ) .join('\n')} ${indent.repeat(2)}} }; -` - } +`; + }; const writeWWWClass = () => { - writeStream.write(generateWWWClass()) - } + writeStream.write(generateWWWClass()); + }; - writeIncludes() - writeFiles() - writeWWWClass() + writeIncludes(); + writeFiles(); + writeWWWClass(); writeStream.on('finish', () => { - callback() - }) + callback(); + }); } finally { - writeStream.end() + writeStream.end(); } - }, - ) + } + ); } } -module.exports = ProgmemGenerator +module.exports = ProgmemGenerator; diff --git a/interface/src/App.tsx b/interface/src/App.tsx index 32d3fb303..4a9d277f7 100644 --- a/interface/src/App.tsx +++ b/interface/src/App.tsx @@ -14,7 +14,6 @@ import FeaturesWrapper from './features/FeaturesWrapper'; const unauthorizedRedirect = () => ; class App extends Component { - notistackRef: RefObject = React.createRef(); componentDidMount() { @@ -23,21 +22,29 @@ class App extends Component { onClickDismiss = (key: string | number | undefined) => () => { this.notistackRef.current.closeSnackbar(key); - } + }; render() { return ( - ( - )}> + )} + > - + @@ -47,4 +54,4 @@ class App extends Component { } } -export default App +export default App; diff --git a/interface/src/AppRouting.tsx b/interface/src/AppRouting.tsx index 12c280bf3..c10881ab8 100644 --- a/interface/src/AppRouting.tsx +++ b/interface/src/AppRouting.tsx @@ -19,9 +19,9 @@ import Mqtt from './mqtt/Mqtt'; import { withFeatures, WithFeaturesProps } from './features/FeaturesContext'; import { Features } from './features/types'; -export const getDefaultRoute = (features: Features) => features.project ? `/${PROJECT_PATH}/` : "/network/"; +export const getDefaultRoute = (features: Features) => + features.project ? `/${PROJECT_PATH}/` : '/network/'; class AppRouting extends Component { - componentDidMount() { Authentication.clearLoginRedirect(); } @@ -35,9 +35,17 @@ class AppRouting extends Component { )} {features.project && ( - + )} - + {features.ntp && ( @@ -52,7 +60,7 @@ class AppRouting extends Component { - ) + ); } } diff --git a/interface/src/CustomMuiTheme.tsx b/interface/src/CustomMuiTheme.tsx index 98caf1315..d62965149 100644 --- a/interface/src/CustomMuiTheme.tsx +++ b/interface/src/CustomMuiTheme.tsx @@ -1,17 +1,21 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { CssBaseline } from '@material-ui/core'; -import { MuiThemeProvider, createMuiTheme, StylesProvider } from '@material-ui/core/styles'; +import { + MuiThemeProvider, + createMuiTheme, + StylesProvider +} from '@material-ui/core/styles'; import { blueGrey, orange, red, green } from '@material-ui/core/colors'; const theme = createMuiTheme({ palette: { - type: "dark", + type: 'dark', primary: { - main: '#33bfff', + main: '#33bfff' }, secondary: { - main: '#3d5afe', + main: '#3d5afe' }, info: { main: blueGrey[500] @@ -29,7 +33,6 @@ const theme = createMuiTheme({ }); export default class CustomMuiTheme extends Component { - render() { return ( @@ -40,5 +43,4 @@ export default class CustomMuiTheme extends Component { ); } - } diff --git a/interface/src/SignIn.tsx b/interface/src/SignIn.tsx index aad465df0..90cdb4a52 100644 --- a/interface/src/SignIn.tsx +++ b/interface/src/SignIn.tsx @@ -2,53 +2,63 @@ import React, { Component } from 'react'; import { withSnackbar, WithSnackbarProps } from 'notistack'; import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator'; -import { withStyles, createStyles, Theme, WithStyles } from '@material-ui/core/styles'; +import { + withStyles, + createStyles, + Theme, + WithStyles +} from '@material-ui/core/styles'; import { Paper, Typography, Fab } from '@material-ui/core'; import ForwardIcon from '@material-ui/icons/Forward'; -import { withAuthenticationContext, AuthenticationContextProps } from './authentication/AuthenticationContext'; +import { + withAuthenticationContext, + AuthenticationContextProps +} from './authentication/AuthenticationContext'; import { PasswordValidator } from './components'; import { PROJECT_NAME, SIGN_IN_ENDPOINT } from './api'; -const styles = (theme: Theme) => createStyles({ - signInPage: { - display: "flex", - height: "100vh", - margin: "auto", - padding: theme.spacing(2), - justifyContent: "center", - flexDirection: "column", - maxWidth: theme.breakpoints.values.sm - }, - signInPanel: { - textAlign: "center", - padding: theme.spacing(2), - paddingTop: "200px", - backgroundImage: 'url("/app/icon.png")', - backgroundRepeat: "no-repeat", - backgroundPosition: "50% " + theme.spacing(2) + "px", - backgroundSize: "auto 150px", - width: "100%" - }, - extendedIcon: { - marginRight: theme.spacing(0.5), - }, - button: { - marginRight: theme.spacing(2), - marginTop: theme.spacing(2), - } -}); +const styles = (theme: Theme) => + createStyles({ + signInPage: { + display: 'flex', + height: '100vh', + margin: 'auto', + padding: theme.spacing(2), + justifyContent: 'center', + flexDirection: 'column', + maxWidth: theme.breakpoints.values.sm + }, + signInPanel: { + textAlign: 'center', + padding: theme.spacing(2), + paddingTop: '200px', + backgroundImage: 'url("/app/icon.png")', + backgroundRepeat: 'no-repeat', + backgroundPosition: '50% ' + theme.spacing(2) + 'px', + backgroundSize: 'auto 150px', + width: '100%' + }, + extendedIcon: { + marginRight: theme.spacing(0.5) + }, + button: { + marginRight: theme.spacing(2), + marginTop: theme.spacing(2) + } + }); -type SignInProps = WithSnackbarProps & WithStyles & AuthenticationContextProps; +type SignInProps = WithSnackbarProps & + WithStyles & + AuthenticationContextProps; interface SignInState { - username: string, - password: string, - processing: boolean + username: string; + password: string; + processing: boolean; } class SignIn extends Component { - constructor(props: SignInProps) { super(props); this.state = { @@ -60,10 +70,10 @@ class SignIn extends Component { updateInputElement = (event: React.ChangeEvent): void => { const { name, value } = event.currentTarget; - this.setState(prevState => ({ + this.setState((prevState) => ({ ...prevState, - [name]: value, - })) + [name]: value + })); }; onSubmit = () => { @@ -77,20 +87,21 @@ class SignIn extends Component { 'Content-Type': 'application/json' }) }) - .then(response => { + .then((response) => { if (response.status === 200) { return response.json(); } else if (response.status === 401) { - throw Error("Invalid credentials."); + throw Error('Invalid credentials.'); } else { - throw Error("Invalid status code: " + response.status); + throw Error('Invalid status code: ' + response.status); } - }).then(json => { + }) + .then((json) => { authenticationContext.signIn(json.access_token); }) - .catch(error => { + .catch((error) => { this.props.enqueueSnackbar(error.message, { - variant: 'warning', + variant: 'warning' }); this.setState({ processing: false }); }); @@ -116,8 +127,8 @@ class SignIn extends Component { onChange={this.updateInputElement} margin="normal" inputProps={{ - autoCapitalize: "none", - autoCorrect: "off", + autoCapitalize: 'none', + autoCorrect: 'off' }} /> { onChange={this.updateInputElement} margin="normal" /> - + Sign In @@ -141,7 +158,8 @@ class SignIn extends Component { ); } - } -export default withAuthenticationContext(withSnackbar(withStyles(styles)(SignIn))); +export default withAuthenticationContext( + withSnackbar(withStyles(styles)(SignIn)) +); diff --git a/interface/src/ap/APModes.ts b/interface/src/ap/APModes.ts index 991463bde..e89818668 100644 --- a/interface/src/ap/APModes.ts +++ b/interface/src/ap/APModes.ts @@ -1,8 +1,8 @@ -import { APSettings, APProvisionMode } from './types' +import { APSettings, APProvisionMode } from './types'; export const isAPEnabled = ({ provision_mode }: APSettings) => { return ( provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED - ) -} + ); +}; diff --git a/interface/src/ap/APSettingsController.tsx b/interface/src/ap/APSettingsController.tsx index 7508f47c6..1a93050ba 100644 --- a/interface/src/ap/APSettingsController.tsx +++ b/interface/src/ap/APSettingsController.tsx @@ -1,7 +1,12 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { AP_SETTINGS_ENDPOINT } from '../api'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import APSettingsForm from './APSettingsForm'; import { APSettings } from './types'; @@ -9,7 +14,6 @@ import { APSettings } from './types'; type APSettingsControllerProps = RestControllerProps; class APSettingsController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,11 @@ class APSettingsController extends Component { } + render={(formProps) => } /> - ) + ); } - } export default restController(AP_SETTINGS_ENDPOINT, APSettingsController); diff --git a/interface/src/ap/APSettingsForm.tsx b/interface/src/ap/APSettingsForm.tsx index 0dd34560e..df871a291 100644 --- a/interface/src/ap/APSettingsForm.tsx +++ b/interface/src/ap/APSettingsForm.tsx @@ -1,10 +1,19 @@ import React, { Fragment } from 'react'; -import { TextValidator, ValidatorForm, SelectValidator } from 'react-material-ui-form-validator'; +import { + TextValidator, + ValidatorForm, + SelectValidator +} from 'react-material-ui-form-validator'; import MenuItem from '@material-ui/core/MenuItem'; import SaveIcon from '@material-ui/icons/Save'; -import { PasswordValidator, RestFormProps, FormActions, FormButton } from '../components'; +import { + PasswordValidator, + RestFormProps, + FormActions, + FormButton +} from '../components'; import { isAPEnabled } from './APModes'; import { APSettings, APProvisionMode } from './types'; @@ -13,7 +22,6 @@ import { isIP } from '../validators'; type APSettingsFormProps = RestFormProps; class APSettingsForm extends React.Component { - componentDidMount() { ValidatorForm.addValidationRule('isIP', isIP); } @@ -22,23 +30,29 @@ class APSettingsForm extends React.Component { const { data, handleValueChange, saveData } = this.props; return ( - + margin="normal" + > Always - When Network Disconnected + + When Network Disconnected + Never - { - isAPEnabled(data) && + {isAPEnabled(data) && ( { /> { /> { /> { margin="normal" /> - } + )} - } variant="contained" color="primary" type="submit"> + } + variant="contained" + color="primary" + type="submit" + > Save diff --git a/interface/src/ap/APStatus.ts b/interface/src/ap/APStatus.ts index 72064dfb8..5a0a13eec 100644 --- a/interface/src/ap/APStatus.ts +++ b/interface/src/ap/APStatus.ts @@ -1,28 +1,28 @@ -import { Theme } from '@material-ui/core' -import { APStatus, APNetworkStatus } from './types' +import { Theme } from '@material-ui/core'; +import { APStatus, APNetworkStatus } from './types'; export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => { switch (status) { case APNetworkStatus.ACTIVE: - return theme.palette.success.main + return theme.palette.success.main; case APNetworkStatus.INACTIVE: - return theme.palette.info.main + return theme.palette.info.main; case APNetworkStatus.LINGERING: - return theme.palette.warning.main + return theme.palette.warning.main; default: - return theme.palette.warning.main + return theme.palette.warning.main; } -} +}; export const apStatus = ({ status }: APStatus) => { switch (status) { case APNetworkStatus.ACTIVE: - return 'Active' + return 'Active'; case APNetworkStatus.INACTIVE: - return 'Inactive' + return 'Inactive'; case APNetworkStatus.LINGERING: - return 'Lingering until idle' + return 'Lingering until idle'; default: - return 'Unknown' + return 'Unknown'; } -} +}; diff --git a/interface/src/ap/APStatusController.tsx b/interface/src/ap/APStatusController.tsx index a9dad744c..5b817d4de 100644 --- a/interface/src/ap/APStatusController.tsx +++ b/interface/src/ap/APStatusController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { AP_STATUS_ENDPOINT } from '../api'; import APStatusForm from './APStatusForm'; @@ -9,7 +14,6 @@ import { APStatus } from './types'; type APStatusControllerProps = RestControllerProps; class APStatusController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,10 +23,10 @@ class APStatusController extends Component { } + render={(formProps) => } /> - ) + ); } } diff --git a/interface/src/ap/APStatusForm.tsx b/interface/src/ap/APStatusForm.tsx index 88c2135e2..39e6c914a 100644 --- a/interface/src/ap/APStatusForm.tsx +++ b/interface/src/ap/APStatusForm.tsx @@ -1,23 +1,34 @@ import React, { Component, Fragment } from 'react'; import { WithTheme, withTheme } from '@material-ui/core/styles'; -import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'; +import { + Avatar, + Divider, + List, + ListItem, + ListItemAvatar, + ListItemText +} from '@material-ui/core'; import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna'; import DeviceHubIcon from '@material-ui/icons/DeviceHub'; import ComputerIcon from '@material-ui/icons/Computer'; import RefreshIcon from '@material-ui/icons/Refresh'; -import { RestFormProps, FormActions, FormButton, HighlightAvatar } from '../components'; +import { + RestFormProps, + FormActions, + FormButton, + HighlightAvatar +} from '../components'; import { apStatusHighlight, apStatus } from './APStatus'; import { APStatus } from './types'; type APStatusFormProps = RestFormProps & WithTheme; class APStatusForm extends Component { - createListItems() { - const { data, theme } = this.props + const { data, theme } = this.props; return ( @@ -61,18 +72,20 @@ class APStatusForm extends Component { render() { return ( - - {this.createListItems()} - + {this.createListItems()} - } variant="contained" color="secondary" onClick={this.props.loadData}> + } + variant="contained" + color="secondary" + onClick={this.props.loadData} + > Refresh ); } - } export default withTheme(APStatusForm); diff --git a/interface/src/ap/AccessPoint.tsx b/interface/src/ap/AccessPoint.tsx index eba011e20..f1b3b0ab0 100644 --- a/interface/src/ap/AccessPoint.tsx +++ b/interface/src/ap/AccessPoint.tsx @@ -1,9 +1,13 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; -import { AuthenticatedContextProps, withAuthenticatedContext, AuthenticatedRoute } from '../authentication'; +import { + AuthenticatedContextProps, + withAuthenticatedContext, + AuthenticatedRoute +} from '../authentication'; import { MenuAppBar } from '../components'; import APSettingsController from './APSettingsController'; @@ -12,8 +16,7 @@ import APStatusController from './APStatusController'; type AccessPointProps = AuthenticatedContextProps & RouteComponentProps; class AccessPoint extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; @@ -21,17 +24,33 @@ class AccessPoint extends Component { const { authenticatedContext } = this.props; return ( - + this.handleTabChange(path)} + variant="fullWidth" + > - + - - + + - ) + ); } } diff --git a/interface/src/ap/types.ts b/interface/src/ap/types.ts index 1e143add2..437d0e63c 100644 --- a/interface/src/ap/types.ts +++ b/interface/src/ap/types.ts @@ -1,27 +1,27 @@ export enum APProvisionMode { AP_MODE_ALWAYS = 0, AP_MODE_DISCONNECTED = 1, - AP_NEVER = 2, + AP_NEVER = 2 } export enum APNetworkStatus { ACTIVE = 0, INACTIVE = 1, - LINGERING = 2, + LINGERING = 2 } export interface APStatus { - status: APNetworkStatus - ip_address: string - mac_address: string - station_num: number + status: APNetworkStatus; + ip_address: string; + mac_address: string; + station_num: number; } export interface APSettings { - provision_mode: APProvisionMode - ssid: string - password: string - local_ip: string - gateway_ip: string - subnet_mask: string + provision_mode: APProvisionMode; + ssid: string; + password: string; + local_ip: string; + gateway_ip: string; + subnet_mask: string; } diff --git a/interface/src/api/Endpoints.ts b/interface/src/api/Endpoints.ts index d19381d04..4264000d5 100644 --- a/interface/src/api/Endpoints.ts +++ b/interface/src/api/Endpoints.ts @@ -1,24 +1,24 @@ -import { ENDPOINT_ROOT } from './Env' +import { ENDPOINT_ROOT } from './Env'; -export const FEATURES_ENDPOINT = ENDPOINT_ROOT + 'features' -export const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'ntpStatus' -export const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'ntpSettings' -export const TIME_ENDPOINT = ENDPOINT_ROOT + 'time' -export const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'apSettings' -export const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'apStatus' -export const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + 'scanNetworks' -export const LIST_NETWORKS_ENDPOINT = ENDPOINT_ROOT + 'listNetworks' -export const NETWORK_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'networkSettings' -export const NETWORK_STATUS_ENDPOINT = ENDPOINT_ROOT + 'networkStatus' -export const OTA_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'otaSettings' -export const UPLOAD_FIRMWARE_ENDPOINT = ENDPOINT_ROOT + 'uploadFirmware' -export const MQTT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'mqttSettings' -export const MQTT_STATUS_ENDPOINT = ENDPOINT_ROOT + 'mqttStatus' -export const SYSTEM_STATUS_ENDPOINT = ENDPOINT_ROOT + 'systemStatus' -export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + 'signIn' +export const FEATURES_ENDPOINT = ENDPOINT_ROOT + 'features'; +export const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'ntpStatus'; +export const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'ntpSettings'; +export const TIME_ENDPOINT = ENDPOINT_ROOT + 'time'; +export const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'apSettings'; +export const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'apStatus'; +export const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + 'scanNetworks'; +export const LIST_NETWORKS_ENDPOINT = ENDPOINT_ROOT + 'listNetworks'; +export const NETWORK_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'networkSettings'; +export const NETWORK_STATUS_ENDPOINT = ENDPOINT_ROOT + 'networkStatus'; +export const OTA_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'otaSettings'; +export const UPLOAD_FIRMWARE_ENDPOINT = ENDPOINT_ROOT + 'uploadFirmware'; +export const MQTT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'mqttSettings'; +export const MQTT_STATUS_ENDPOINT = ENDPOINT_ROOT + 'mqttStatus'; +export const SYSTEM_STATUS_ENDPOINT = ENDPOINT_ROOT + 'systemStatus'; +export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + 'signIn'; export const VERIFY_AUTHORIZATION_ENDPOINT = - ENDPOINT_ROOT + 'verifyAuthorization' -export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'securitySettings' -export const GENERATE_TOKEN_ENDPOINT = ENDPOINT_ROOT + 'generateToken' -export const RESTART_ENDPOINT = ENDPOINT_ROOT + 'restart' -export const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + 'factoryReset' + ENDPOINT_ROOT + 'verifyAuthorization'; +export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'securitySettings'; +export const GENERATE_TOKEN_ENDPOINT = ENDPOINT_ROOT + 'generateToken'; +export const RESTART_ENDPOINT = ENDPOINT_ROOT + 'restart'; +export const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + 'factoryReset'; diff --git a/interface/src/api/Env.ts b/interface/src/api/Env.ts index edeeccae3..59a1263ae 100644 --- a/interface/src/api/Env.ts +++ b/interface/src/api/Env.ts @@ -1,24 +1,24 @@ -export const PROJECT_NAME = process.env.REACT_APP_PROJECT_NAME! -export const PROJECT_PATH = process.env.REACT_APP_PROJECT_PATH! +export const PROJECT_NAME = process.env.REACT_APP_PROJECT_NAME!; +export const PROJECT_PATH = process.env.REACT_APP_PROJECT_PATH!; -export const ENDPOINT_ROOT = calculateEndpointRoot('/rest/') -export const WEB_SOCKET_ROOT = calculateWebSocketRoot('/ws/') +export const ENDPOINT_ROOT = calculateEndpointRoot('/rest/'); +export const WEB_SOCKET_ROOT = calculateWebSocketRoot('/ws/'); function calculateEndpointRoot(endpointPath: string) { - const httpRoot = process.env.REACT_APP_HTTP_ROOT + const httpRoot = process.env.REACT_APP_HTTP_ROOT; if (httpRoot) { - return httpRoot + endpointPath + return httpRoot + endpointPath; } - const location = window.location - return location.protocol + '//' + location.host + endpointPath + const location = window.location; + return location.protocol + '//' + location.host + endpointPath; } function calculateWebSocketRoot(webSocketPath: string) { - const webSocketRoot = process.env.REACT_APP_WEB_SOCKET_ROOT + const webSocketRoot = process.env.REACT_APP_WEB_SOCKET_ROOT; if (webSocketRoot) { - return webSocketRoot + webSocketPath + return webSocketRoot + webSocketPath; } - const location = window.location - const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:' - return webProtocol + '//' + location.host + webSocketPath + const location = window.location; + const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; + return webProtocol + '//' + location.host + webSocketPath; } diff --git a/interface/src/api/index.ts b/interface/src/api/index.ts index 8ec579747..1e5b5ff82 100644 --- a/interface/src/api/index.ts +++ b/interface/src/api/index.ts @@ -1,2 +1,2 @@ -export * from './Env' -export * from './Endpoints' +export * from './Env'; +export * from './Endpoints'; diff --git a/interface/src/authentication/AuthenticatedRoute.tsx b/interface/src/authentication/AuthenticatedRoute.tsx index 3c71234a9..ff28427d0 100644 --- a/interface/src/authentication/AuthenticatedRoute.tsx +++ b/interface/src/authentication/AuthenticatedRoute.tsx @@ -1,40 +1,56 @@ import * as React from 'react'; -import { Redirect, Route, RouteProps, RouteComponentProps } from "react-router-dom"; +import { + Redirect, + Route, + RouteProps, + RouteComponentProps +} from 'react-router-dom'; import { withSnackbar, WithSnackbarProps } from 'notistack'; import * as Authentication from './Authentication'; -import { withAuthenticationContext, AuthenticationContextProps, AuthenticatedContext, AuthenticatedContextValue } from './AuthenticationContext'; +import { + withAuthenticationContext, + AuthenticationContextProps, + AuthenticatedContext, + AuthenticatedContextValue +} from './AuthenticationContext'; -interface AuthenticatedRouteProps extends RouteProps, WithSnackbarProps, AuthenticationContextProps { - component: React.ComponentType> | React.ComponentType; +interface AuthenticatedRouteProps + extends RouteProps, + WithSnackbarProps, + AuthenticationContextProps { + component: + | React.ComponentType> + | React.ComponentType; } type RenderComponent = (props: RouteComponentProps) => React.ReactNode; export class AuthenticatedRoute extends React.Component { - render() { - const { enqueueSnackbar, authenticationContext, component: Component, ...rest } = this.props; + const { + enqueueSnackbar, + authenticationContext, + component: Component, + ...rest + } = this.props; const { location } = this.props; const renderComponent: RenderComponent = (props) => { if (authenticationContext.me) { return ( - + ); } Authentication.storeLoginRedirect(location); - enqueueSnackbar("Please sign in to continue", { variant: 'info' }); - return ( - - ); - } - return ( - - ); + enqueueSnackbar('Please sign in to continue', { variant: 'info' }); + return ; + }; + return ; } - } export default withSnackbar(withAuthenticationContext(AuthenticatedRoute)); diff --git a/interface/src/authentication/Authentication.ts b/interface/src/authentication/Authentication.ts index 610791e56..d1ee1d4ab 100644 --- a/interface/src/authentication/Authentication.ts +++ b/interface/src/authentication/Authentication.ts @@ -1,42 +1,42 @@ -import * as H from 'history' +import * as H from 'history'; -import history from '../history' -import { Features } from '../features/types' -import { getDefaultRoute } from '../AppRouting' +import history from '../history'; +import { Features } from '../features/types'; +import { getDefaultRoute } from '../AppRouting'; -export const ACCESS_TOKEN = 'access_token' -export const SIGN_IN_PATHNAME = 'signInPathname' -export const SIGN_IN_SEARCH = 'signInSearch' +export const ACCESS_TOKEN = 'access_token'; +export const SIGN_IN_PATHNAME = 'signInPathname'; +export const SIGN_IN_SEARCH = 'signInSearch'; /** * Fallback to sessionStorage if localStorage is absent. WebView may not have local storage enabled. */ export function getStorage() { - return localStorage || sessionStorage + return localStorage || sessionStorage; } export function storeLoginRedirect(location?: H.Location) { if (location) { - getStorage().setItem(SIGN_IN_PATHNAME, location.pathname) - getStorage().setItem(SIGN_IN_SEARCH, location.search) + getStorage().setItem(SIGN_IN_PATHNAME, location.pathname); + getStorage().setItem(SIGN_IN_SEARCH, location.search); } } export function clearLoginRedirect() { - getStorage().removeItem(SIGN_IN_PATHNAME) - getStorage().removeItem(SIGN_IN_SEARCH) + getStorage().removeItem(SIGN_IN_PATHNAME); + getStorage().removeItem(SIGN_IN_SEARCH); } export function fetchLoginRedirect( - features: Features, + features: Features ): H.LocationDescriptorObject { - const signInPathname = getStorage().getItem(SIGN_IN_PATHNAME) - const signInSearch = getStorage().getItem(SIGN_IN_SEARCH) - clearLoginRedirect() + const signInPathname = getStorage().getItem(SIGN_IN_PATHNAME); + const signInSearch = getStorage().getItem(SIGN_IN_SEARCH); + clearLoginRedirect(); return { pathname: signInPathname || getDefaultRoute(features), - search: (signInPathname && signInSearch) || undefined, - } + search: (signInPathname && signInSearch) || undefined + }; } /** @@ -44,18 +44,18 @@ export function fetchLoginRedirect( */ export function authorizedFetch( url: RequestInfo, - params?: RequestInit, + params?: RequestInit ): Promise { - const accessToken = getStorage().getItem(ACCESS_TOKEN) + const accessToken = getStorage().getItem(ACCESS_TOKEN); if (accessToken) { - params = params || {} - params.credentials = 'include' + params = params || {}; + params.credentials = 'include'; params.headers = { ...params.headers, - Authorization: 'Bearer ' + accessToken, - } + Authorization: 'Bearer ' + accessToken + }; } - return fetch(url, params) + return fetch(url, params); } /** @@ -67,33 +67,33 @@ export function redirectingAuthorizedUpload( xhr: XMLHttpRequest, url: string, file: File, - onProgress: (event: ProgressEvent) => void, + onProgress: (event: ProgressEvent) => void ): Promise { return new Promise((resolve, reject) => { - xhr.open('POST', url, true) - const accessToken = getStorage().getItem(ACCESS_TOKEN) + xhr.open('POST', url, true); + const accessToken = getStorage().getItem(ACCESS_TOKEN); if (accessToken) { - xhr.withCredentials = true - xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken) + xhr.withCredentials = true; + xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken); } - xhr.upload.onprogress = onProgress + xhr.upload.onprogress = onProgress; xhr.onload = function () { if (xhr.status === 401 || xhr.status === 403) { - history.push('/unauthorized') + history.push('/unauthorized'); } else { - resolve() + resolve(); } - } - xhr.onerror = function (event: ProgressEvent) { - reject(new DOMException('Error', 'UploadError')) - } + }; + xhr.onerror = function () { + reject(new DOMException('Error', 'UploadError')); + }; xhr.onabort = function () { - reject(new DOMException('Aborted', 'AbortError')) - } - const formData = new FormData() - formData.append('file', file) - xhr.send(formData) - }) + reject(new DOMException('Aborted', 'AbortError')); + }; + const formData = new FormData(); + formData.append('file', file); + xhr.send(formData); + }); } /** @@ -101,29 +101,29 @@ export function redirectingAuthorizedUpload( */ export function redirectingAuthorizedFetch( url: RequestInfo, - params?: RequestInit, + params?: RequestInit ): Promise { return new Promise((resolve, reject) => { authorizedFetch(url, params) .then((response) => { if (response.status === 401 || response.status === 403) { - history.push('/unauthorized') + history.push('/unauthorized'); } else { - resolve(response) + resolve(response); } }) .catch((error) => { - reject(error) - }) - }) + reject(error); + }); + }); } export function addAccessTokenParameter(url: string) { - const accessToken = getStorage().getItem(ACCESS_TOKEN) + const accessToken = getStorage().getItem(ACCESS_TOKEN); if (!accessToken) { - return url + return url; } - const parsedUrl = new URL(url) - parsedUrl.searchParams.set(ACCESS_TOKEN, accessToken) - return parsedUrl.toString() + const parsedUrl = new URL(url); + parsedUrl.searchParams.set(ACCESS_TOKEN, accessToken); + return parsedUrl.toString(); } diff --git a/interface/src/authentication/AuthenticationContext.tsx b/interface/src/authentication/AuthenticationContext.tsx index 40b0ce177..7350c0d03 100644 --- a/interface/src/authentication/AuthenticationContext.tsx +++ b/interface/src/authentication/AuthenticationContext.tsx @@ -1,9 +1,8 @@ -import * as React from "react"; +import * as React from 'react'; export interface Me { username: string; admin: boolean; - version: string; // proddy added } export interface AuthenticationContextValue { @@ -13,7 +12,7 @@ export interface AuthenticationContextValue { me?: Me; } -const AuthenticationContextDefaultValue = {} as AuthenticationContextValue +const AuthenticationContextDefaultValue = {} as AuthenticationContextValue; export const AuthenticationContext = React.createContext( AuthenticationContextDefaultValue ); @@ -22,12 +21,21 @@ export interface AuthenticationContextProps { authenticationContext: AuthenticationContextValue; } -export function withAuthenticationContext(Component: React.ComponentType) { - return class extends React.Component> { +export function withAuthenticationContext( + Component: React.ComponentType +) { + return class extends React.Component< + Omit + > { render() { return ( - {authenticationContext => } + {(authenticationContext) => ( + + )} ); } @@ -38,7 +46,7 @@ export interface AuthenticatedContextValue extends AuthenticationContextValue { me: Me; } -const AuthenticatedContextDefaultValue = {} as AuthenticatedContextValue +const AuthenticatedContextDefaultValue = {} as AuthenticatedContextValue; export const AuthenticatedContext = React.createContext( AuthenticatedContextDefaultValue ); @@ -47,14 +55,23 @@ export interface AuthenticatedContextProps { authenticatedContext: AuthenticatedContextValue; } -export function withAuthenticatedContext(Component: React.ComponentType) { - return class extends React.Component> { +export function withAuthenticatedContext( + Component: React.ComponentType +) { + return class extends React.Component< + Omit + > { render() { return ( - {authenticatedContext => } + {(authenticatedContext) => ( + + )} ); } }; -} \ No newline at end of file +} diff --git a/interface/src/authentication/AuthenticationWrapper.tsx b/interface/src/authentication/AuthenticationWrapper.tsx index c91522c8b..3437ff719 100644 --- a/interface/src/authentication/AuthenticationWrapper.tsx +++ b/interface/src/authentication/AuthenticationWrapper.tsx @@ -2,14 +2,19 @@ import * as React from 'react'; import { withSnackbar, WithSnackbarProps } from 'notistack'; import jwtDecode from 'jwt-decode'; -import history from '../history' +import history from '../history'; import { VERIFY_AUTHORIZATION_ENDPOINT } from '../api'; import { ACCESS_TOKEN, authorizedFetch, getStorage } from './Authentication'; -import { AuthenticationContext, AuthenticationContextValue, Me } from './AuthenticationContext'; +import { + AuthenticationContext, + AuthenticationContextValue, + Me +} from './AuthenticationContext'; import FullScreenLoading from '../components/FullScreenLoading'; import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext'; -export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken) as Me; +export const decodeMeJWT = (accessToken: string): Me => + jwtDecode(accessToken) as Me; interface AuthenticationWrapperState { context: AuthenticationContextValue; @@ -18,15 +23,17 @@ interface AuthenticationWrapperState { type AuthenticationWrapperProps = WithSnackbarProps & WithFeaturesProps; -class AuthenticationWrapper extends React.Component { - +class AuthenticationWrapper extends React.Component< + AuthenticationWrapperProps, + AuthenticationWrapperState +> { constructor(props: AuthenticationWrapperProps) { super(props); this.state = { context: { refresh: this.refresh, signIn: this.signIn, - signOut: this.signOut, + signOut: this.signOut }, initialized: false }; @@ -39,7 +46,9 @@ class AuthenticationWrapper extends React.Component - {this.state.initialized ? this.renderContent() : this.renderContentLoading()} + {this.state.initialized + ? this.renderContent() + : this.renderContentLoading()} ); } @@ -53,9 +62,7 @@ class AuthenticationWrapper extends React.Component - ); + return ; } refresh = () => { @@ -64,34 +71,53 @@ class AuthenticationWrapper extends React.Component { - const me = response.status === 200 ? decodeMeJWT(accessToken) : undefined; - this.setState({ initialized: true, context: { ...this.state.context, me } }); - }).catch(error => { - this.setState({ initialized: true, context: { ...this.state.context, me: undefined } }); - this.props.enqueueSnackbar("Error verifying authorization: " + error.message, { - variant: 'error', + .then((response) => { + const me = + response.status === 200 ? decodeMeJWT(accessToken) : undefined; + this.setState({ + initialized: true, + context: { ...this.state.context, me } }); + }) + .catch((error) => { + this.setState({ + initialized: true, + context: { ...this.state.context, me: undefined } + }); + this.props.enqueueSnackbar( + 'Error verifying authorization: ' + error.message, + { + variant: 'error' + } + ); }); } else { - this.setState({ initialized: true, context: { ...this.state.context, me: undefined } }); + this.setState({ + initialized: true, + context: { ...this.state.context, me: undefined } + }); } - } + }; signIn = (accessToken: string) => { try { getStorage().setItem(ACCESS_TOKEN, accessToken); const me: Me = decodeMeJWT(accessToken); this.setState({ context: { ...this.state.context, me } }); - this.props.enqueueSnackbar(`Logged in as ${me.username}`, { variant: 'success' }); + this.props.enqueueSnackbar(`Logged in as ${me.username}`, { + variant: 'success' + }); } catch (err) { - this.setState({ initialized: true, context: { ...this.state.context, me: undefined } }); - throw new Error("Failed to parse JWT " + err.message); + this.setState({ + initialized: true, + context: { ...this.state.context, me: undefined } + }); + throw new Error('Failed to parse JWT ' + err.message); } - } + }; signOut = () => { getStorage().removeItem(ACCESS_TOKEN); @@ -101,10 +127,9 @@ class AuthenticationWrapper extends React.Component> | React.ComponentType; +interface UnauthenticatedRouteProps + extends RouteProps, + AuthenticationContextProps, + WithFeaturesProps { + component: + | React.ComponentType> + | React.ComponentType; } type RenderComponent = (props: RouteComponentProps) => React.ReactNode; class UnauthenticatedRoute extends Route { - public render() { - const { authenticationContext, component: Component, features, ...rest } = this.props; + const { + authenticationContext, + component: Component, + features, + ...rest + } = this.props; const renderComponent: RenderComponent = (props) => { if (authenticationContext.me) { - return (); + return ; } if (Component) { - return (); + return ; } - } - return ( - - ); + }; + return ; } } diff --git a/interface/src/authentication/index.ts b/interface/src/authentication/index.ts index aff17b6c4..d93294c89 100644 --- a/interface/src/authentication/index.ts +++ b/interface/src/authentication/index.ts @@ -1,6 +1,6 @@ -export { default as AuthenticatedRoute } from './AuthenticatedRoute' -export { default as AuthenticationWrapper } from './AuthenticationWrapper' -export { default as UnauthenticatedRoute } from './UnauthenticatedRoute' +export { default as AuthenticatedRoute } from './AuthenticatedRoute'; +export { default as AuthenticationWrapper } from './AuthenticationWrapper'; +export { default as UnauthenticatedRoute } from './UnauthenticatedRoute'; -export * from './Authentication' -export * from './AuthenticationContext' +export * from './Authentication'; +export * from './AuthenticationContext'; diff --git a/interface/src/components/ApplicationError.tsx b/interface/src/components/ApplicationError.tsx index 7fbe0fc4c..46cbae129 100644 --- a/interface/src/components/ApplicationError.tsx +++ b/interface/src/components/ApplicationError.tsx @@ -1,27 +1,25 @@ import React, { FC } from 'react'; import { makeStyles } from '@material-ui/styles'; -import { Paper, Typography, Box, CssBaseline } from "@material-ui/core"; -import WarningIcon from "@material-ui/icons/Warning" +import { Paper, Typography, Box, CssBaseline } from '@material-ui/core'; +import WarningIcon from '@material-ui/icons/Warning'; -const styles = makeStyles( - { - siteErrorPage: { - display: "flex", - height: "100vh", - justifyContent: "center", - flexDirection: "column" - }, - siteErrorPagePanel: { - textAlign: "center", - padding: "280px 0 40px 0", - backgroundImage: 'url("/app/icon.png")', - backgroundRepeat: "no-repeat", - backgroundPosition: "50% 40px", - backgroundSize: "200px auto", - width: "100%", - } +const styles = makeStyles({ + siteErrorPage: { + display: 'flex', + height: '100vh', + justifyContent: 'center', + flexDirection: 'column' + }, + siteErrorPagePanel: { + textAlign: 'center', + padding: '280px 0 40px 0', + backgroundImage: 'url("/app/icon.png")', + backgroundRepeat: 'no-repeat', + backgroundPosition: '50% 40px', + backgroundSize: '200px auto', + width: '100%' } -); +}); interface ApplicationErrorProps { error?: string; @@ -33,27 +31,29 @@ const ApplicationError: FC = ({ error }) => {
- + - - Application error - + Application error Failed to configure the application, please refresh to try again. - {error && - ( - - Error: {error} - - ) - } + {error && ( + + Error: {error} + + )}
); -} +}; export default ApplicationError; diff --git a/interface/src/components/BlockFormControlLabel.tsx b/interface/src/components/BlockFormControlLabel.tsx index 2ea2ebab2..3067c961f 100644 --- a/interface/src/components/BlockFormControlLabel.tsx +++ b/interface/src/components/BlockFormControlLabel.tsx @@ -1,10 +1,10 @@ -import React, { FC } from "react"; -import { FormControlLabel, FormControlLabelProps } from "@material-ui/core"; +import { FC } from 'react'; +import { FormControlLabel, FormControlLabelProps } from '@material-ui/core'; const BlockFormControlLabel: FC = (props) => (
-) +); export default BlockFormControlLabel; diff --git a/interface/src/components/ErrorButton.tsx b/interface/src/components/ErrorButton.tsx index c93cddd66..667b6eb6c 100644 --- a/interface/src/components/ErrorButton.tsx +++ b/interface/src/components/ErrorButton.tsx @@ -1,10 +1,10 @@ -import { Button, styled } from "@material-ui/core"; +import { Button, styled } from '@material-ui/core'; const ErrorButton = styled(Button)(({ theme }) => ({ color: theme.palette.getContrastText(theme.palette.error.main), backgroundColor: theme.palette.error.main, '&:hover': { - backgroundColor: theme.palette.error.dark, + backgroundColor: theme.palette.error.dark } })); diff --git a/interface/src/components/FormActions.tsx b/interface/src/components/FormActions.tsx index 8c6eb73fe..8e47e9ad9 100644 --- a/interface/src/components/FormActions.tsx +++ b/interface/src/components/FormActions.tsx @@ -1,4 +1,4 @@ -import { styled, Box } from "@material-ui/core"; +import { styled, Box } from '@material-ui/core'; const FormActions = styled(Box)(({ theme }) => ({ marginTop: theme.spacing(1) diff --git a/interface/src/components/FormButton.tsx b/interface/src/components/FormButton.tsx index f14ef0653..3254c29f4 100644 --- a/interface/src/components/FormButton.tsx +++ b/interface/src/components/FormButton.tsx @@ -1,12 +1,12 @@ -import { Button, styled } from "@material-ui/core"; +import { Button, styled } from '@material-ui/core'; const FormButton = styled(Button)(({ theme }) => ({ margin: theme.spacing(0, 1), '&:last-child': { - marginRight: 0, + marginRight: 0 }, '&:first-child': { - marginLeft: 0, + marginLeft: 0 } })); diff --git a/interface/src/components/FullScreenLoading.tsx b/interface/src/components/FullScreenLoading.tsx index d08d90466..4e6c02086 100644 --- a/interface/src/components/FullScreenLoading.tsx +++ b/interface/src/components/FullScreenLoading.tsx @@ -3,30 +3,30 @@ import CircularProgress from '@material-ui/core/CircularProgress'; import { Typography, Theme } from '@material-ui/core'; import { makeStyles, createStyles } from '@material-ui/styles'; -const useStyles = makeStyles((theme: Theme) => createStyles({ - fullScreenLoading: { - padding: theme.spacing(2), - display: "flex", - alignItems: "center", - justifyContent: "center", - height: "100vh", - flexDirection: "column" - }, - progress: { - margin: theme.spacing(4), - } -})); +const useStyles = makeStyles((theme: Theme) => + createStyles({ + fullScreenLoading: { + padding: theme.spacing(2), + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: '100vh', + flexDirection: 'column' + }, + progress: { + margin: theme.spacing(4) + } + }) +); const FullScreenLoading = () => { const classes = useStyles(); return (
- - Loading… - + Loading…
- ) -} + ); +}; export default FullScreenLoading; diff --git a/interface/src/components/HighlightAvatar.tsx b/interface/src/components/HighlightAvatar.tsx index f7ce8c752..cfc640948 100644 --- a/interface/src/components/HighlightAvatar.tsx +++ b/interface/src/components/HighlightAvatar.tsx @@ -1,5 +1,5 @@ -import { Avatar, makeStyles } from "@material-ui/core"; -import React, { FC } from "react"; +import { Avatar, makeStyles } from '@material-ui/core'; +import { FC } from 'react'; interface HighlightAvatarProps { color: string; @@ -13,11 +13,7 @@ const useStyles = makeStyles({ const HighlightAvatar: FC = (props) => { const classes = useStyles(props); - return ( - - {props.children} - - ); -} + return {props.children}; +}; export default HighlightAvatar; diff --git a/interface/src/components/PasswordValidator.tsx b/interface/src/components/PasswordValidator.tsx index 875d215fb..6cdc99a20 100644 --- a/interface/src/components/PasswordValidator.tsx +++ b/interface/src/components/PasswordValidator.tsx @@ -1,5 +1,8 @@ import React from 'react'; -import { TextValidator, ValidatorComponentProps } from 'react-material-ui-form-validator'; +import { + TextValidator, + ValidatorComponentProps +} from 'react-material-ui-form-validator'; import { withStyles, WithStyles, createStyles } from '@material-ui/core/styles'; import { InputAdornment, IconButton } from '@material-ui/core'; @@ -7,20 +10,23 @@ import { Visibility, VisibilityOff } from '@material-ui/icons'; const styles = createStyles({ input: { - "&::-ms-reveal": { - display: "none" + '&::-ms-reveal': { + display: 'none' } } }); -type PasswordValidatorProps = WithStyles & Exclude; +type PasswordValidatorProps = WithStyles & + Exclude; interface PasswordValidatorState { showPassword: boolean; } -class PasswordValidator extends React.Component { - +class PasswordValidator extends React.Component< + PasswordValidatorProps, + PasswordValidatorState +> { state = { showPassword: false }; @@ -29,7 +35,7 @@ class PasswordValidator extends React.Component : } + ) }} /> ); } - } export default withStyles(styles)(PasswordValidator); diff --git a/interface/src/components/RestController.tsx b/interface/src/components/RestController.tsx index c9751c6a1..0e3019aa4 100644 --- a/interface/src/components/RestController.tsx +++ b/interface/src/components/RestController.tsx @@ -4,7 +4,9 @@ import { withSnackbar, WithSnackbarProps } from 'notistack'; import { redirectingAuthorizedFetch } from '../authentication'; export interface RestControllerProps extends WithSnackbarProps { - handleValueChange: (name: keyof D) => (event: React.ChangeEvent) => void; + handleValueChange: ( + name: keyof D + ) => (event: React.ChangeEvent) => void; setData: (data: D, callback?: () => void) => void; saveData: () => void; @@ -15,16 +17,18 @@ export interface RestControllerProps extends WithSnackbarProps { errorMessage?: string; } -export const extractEventValue = (event: React.ChangeEvent) => { +export const extractEventValue = ( + event: React.ChangeEvent +) => { switch (event.target.type) { - case "number": + case 'number': return event.target.valueAsNumber; - case "checkbox": + case 'checkbox': return event.target.checked; default: - return event.target.value + return event.target.value; } -} +}; interface RestControllerState { data?: D; @@ -32,10 +36,15 @@ interface RestControllerState { errorMessage?: string; } -export function restController>(endpointUrl: string, RestController: React.ComponentType

>) { +export function restController>( + endpointUrl: string, + RestController: React.ComponentType

> +) { return withSnackbar( - class extends React.Component> & WithSnackbarProps, RestControllerState> { - + class extends React.Component< + Omit> & WithSnackbarProps, + RestControllerState + > { state: RestControllerState = { data: undefined, loading: false, @@ -43,12 +52,15 @@ export function restController>(endpointUrl: }; setData = (data: D, callback?: () => void) => { - this.setState({ - data, - loading: false, - errorMessage: undefined - }, callback); - } + this.setState( + { + data, + loading: false, + errorMessage: undefined + }, + callback + ); + }; loadData = () => { this.setState({ @@ -56,19 +68,24 @@ export function restController>(endpointUrl: loading: true, errorMessage: undefined }); - redirectingAuthorizedFetch(endpointUrl).then(response => { - if (response.status === 200) { - return response.json(); - } - throw Error("Invalid status code: " + response.status); - }).then(json => { - this.setState({ data: json, loading: false }) - }).catch(error => { - const errorMessage = error.message || "Unknown error"; - this.props.enqueueSnackbar("Problem fetching: " + errorMessage, { variant: 'error' }); - this.setState({ data: undefined, loading: false, errorMessage }); - }); - } + redirectingAuthorizedFetch(endpointUrl) + .then((response) => { + if (response.status === 200) { + return response.json(); + } + throw Error('Invalid status code: ' + response.status); + }) + .then((json) => { + this.setState({ data: json, loading: false }); + }) + .catch((error) => { + const errorMessage = error.message || 'Unknown error'; + this.props.enqueueSnackbar('Problem fetching: ' + errorMessage, { + variant: 'error' + }); + this.setState({ data: undefined, loading: false, errorMessage }); + }); + }; saveData = () => { this.setState({ loading: true }); @@ -78,36 +95,47 @@ export function restController>(endpointUrl: headers: { 'Content-Type': 'application/json' } - }).then(response => { - if (response.status === 200) { - return response.json(); - } - throw Error("Invalid status code: " + response.status); - }).then(json => { - this.props.enqueueSnackbar("Update successful.", { variant: 'success' }); - this.setState({ data: json, loading: false }); - }).catch(error => { - const errorMessage = error.message || "Unknown error"; - this.props.enqueueSnackbar("Problem updating: " + errorMessage, { variant: 'error' }); - this.setState({ data: undefined, loading: false, errorMessage }); - }); - } + }) + .then((response) => { + if (response.status === 200) { + return response.json(); + } + throw Error('Invalid status code: ' + response.status); + }) + .then((json) => { + this.props.enqueueSnackbar('Update successful.', { + variant: 'success' + }); + this.setState({ data: json, loading: false }); + }) + .catch((error) => { + const errorMessage = error.message || 'Unknown error'; + this.props.enqueueSnackbar('Problem updating: ' + errorMessage, { + variant: 'error' + }); + this.setState({ data: undefined, loading: false, errorMessage }); + }); + }; - handleValueChange = (name: keyof D) => (event: React.ChangeEvent) => { + handleValueChange = (name: keyof D) => ( + event: React.ChangeEvent + ) => { const data = { ...this.state.data!, [name]: extractEventValue(event) }; this.setState({ data }); - } + }; render() { - return ; + return ( + + ); } - - }); + } + ); } diff --git a/interface/src/components/RestFormLoader.tsx b/interface/src/components/RestFormLoader.tsx index 29fb304af..ca65805fd 100644 --- a/interface/src/components/RestFormLoader.tsx +++ b/interface/src/components/RestFormLoader.tsx @@ -8,20 +8,23 @@ import { RestControllerProps } from '.'; const useStyles = makeStyles((theme: Theme) => createStyles({ loadingSettings: { - margin: theme.spacing(0.5), + margin: theme.spacing(0.5) }, loadingSettingsDetails: { margin: theme.spacing(4), - textAlign: "center" + textAlign: 'center' }, button: { marginRight: theme.spacing(2), - marginTop: theme.spacing(2), + marginTop: theme.spacing(2) } }) ); -export type RestFormProps = Omit, "loading" | "errorMessage"> & { data: D }; +export type RestFormProps = Omit< + RestControllerProps, + 'loading' | 'errorMessage' +> & { data: D }; interface RestFormLoaderProps extends RestControllerProps { render: (props: RestFormProps) => JSX.Element; @@ -46,7 +49,12 @@ export default function RestFormLoader(props: RestFormLoaderProps) { {errorMessage} - diff --git a/interface/src/components/SectionContent.tsx b/interface/src/components/SectionContent.tsx index 457014f69..3d200b66a 100644 --- a/interface/src/components/SectionContent.tsx +++ b/interface/src/components/SectionContent.tsx @@ -7,7 +7,7 @@ const useStyles = makeStyles((theme: Theme) => createStyles({ content: { padding: theme.spacing(2), - margin: theme.spacing(3), + margin: theme.spacing(3) } }) ); diff --git a/interface/src/components/SingleUpload.tsx b/interface/src/components/SingleUpload.tsx index 003c28653..fe422354a 100644 --- a/interface/src/components/SingleUpload.tsx +++ b/interface/src/components/SingleUpload.tsx @@ -4,13 +4,20 @@ import { useDropzone, DropzoneState } from 'react-dropzone'; import { makeStyles, createStyles } from '@material-ui/styles'; import CloudUploadIcon from '@material-ui/icons/CloudUpload'; import CancelIcon from '@material-ui/icons/Cancel'; -import { Theme, Box, Typography, LinearProgress, Button } from '@material-ui/core'; +import { + Theme, + Box, + Typography, + LinearProgress, + Button +} from '@material-ui/core'; interface SingleUploadStyleProps extends DropzoneState { uploading: boolean; } -const progressPercentage = (progress: ProgressEvent) => Math.round((progress.loaded * 100) / progress.total); +const progressPercentage = (progress: ProgressEvent) => + Math.round((progress.loaded * 100) / progress.total); const getBorderColor = (theme: Theme, props: SingleUploadStyleProps) => { if (props.isDragAccept) { @@ -23,21 +30,25 @@ const getBorderColor = (theme: Theme, props: SingleUploadStyleProps) => { return theme.palette.info.main; } return theme.palette.grey[700]; -} +}; -const useStyles = makeStyles((theme: Theme) => createStyles({ - dropzone: { - padding: theme.spacing(8, 2), - borderWidth: 2, - borderRadius: 2, - borderStyle: 'dashed', - color: theme.palette.grey[700], - transition: 'border .24s ease-in-out', - cursor: (props: SingleUploadStyleProps) => props.uploading ? 'default' : 'pointer', - width: '100%', - borderColor: (props: SingleUploadStyleProps) => getBorderColor(theme, props) - } -})); +const useStyles = makeStyles((theme: Theme) => + createStyles({ + dropzone: { + padding: theme.spacing(8, 2), + borderWidth: 2, + borderRadius: 2, + borderStyle: 'dashed', + color: theme.palette.grey[700], + transition: 'border .24s ease-in-out', + cursor: (props: SingleUploadStyleProps) => + props.uploading ? 'default' : 'pointer', + width: '100%', + borderColor: (props: SingleUploadStyleProps) => + getBorderColor(theme, props) + } + }) +); export interface SingleUploadProps { onDrop: (acceptedFiles: File[]) => void; @@ -47,26 +58,44 @@ export interface SingleUploadProps { progress?: ProgressEvent; } -const SingleUpload: FC = ({ onDrop, onCancel, accept, uploading, progress }) => { - const dropzoneState = useDropzone({ onDrop, accept, disabled: uploading, multiple: false }); +const SingleUpload: FC = ({ + onDrop, + onCancel, + accept, + uploading, + progress +}) => { + const dropzoneState = useDropzone({ + onDrop, + accept, + disabled: uploading, + multiple: false + }); const { getRootProps, getInputProps } = dropzoneState; const classes = useStyles({ ...dropzoneState, uploading }); - const renderProgressText = () => { if (uploading) { if (progress?.lengthComputable) { return `Uploading: ${progressPercentage(progress)}%`; } - return "Uploading\u2026"; + return 'Uploading\u2026'; } - return "Drop file or click here"; - } + return 'Drop file or click here'; + }; const renderProgress = (progress?: ProgressEvent) => ( ); @@ -74,16 +103,19 @@ const SingleUpload: FC = ({ onDrop, onCancel, accept, uploadi

- - - {renderProgressText()} - + + {renderProgressText()} {uploading && ( {renderProgress(progress)} - @@ -91,6 +123,6 @@ const SingleUpload: FC = ({ onDrop, onCancel, accept, uploadi
); -} +}; export default SingleUpload; diff --git a/interface/src/components/WebSocketController.tsx b/interface/src/components/WebSocketController.tsx index 5fe9fa33c..fda9bf591 100644 --- a/interface/src/components/WebSocketController.tsx +++ b/interface/src/components/WebSocketController.tsx @@ -7,7 +7,9 @@ import { addAccessTokenParameter } from '../authentication'; import { extractEventValue } from '.'; export interface WebSocketControllerProps extends WithSnackbarProps { - handleValueChange: (name: keyof D) => (event: React.ChangeEvent) => void; + handleValueChange: ( + name: keyof D + ) => (event: React.ChangeEvent) => void; setData: (data: D, callback?: () => void) => void; saveData: () => void; @@ -25,8 +27,8 @@ interface WebSocketControllerState { } enum WebSocketMessageType { - ID = "id", - PAYLOAD = "payload" + ID = 'id', + PAYLOAD = 'payload' } interface WebSocketIdMessage { @@ -40,21 +42,32 @@ interface WebSocketPayloadMessage { payload: D; } -export type WebSocketMessage = WebSocketIdMessage | WebSocketPayloadMessage; +export type WebSocketMessage = + | WebSocketIdMessage + | WebSocketPayloadMessage; -export function webSocketController>(wsUrl: string, wsThrottle: number, WebSocketController: React.ComponentType

>) { +export function webSocketController>( + wsUrl: string, + wsThrottle: number, + WebSocketController: React.ComponentType

> +) { return withSnackbar( - class extends React.Component> & WithSnackbarProps, WebSocketControllerState> { - constructor(props: Omit> & WithSnackbarProps) { + class extends React.Component< + Omit> & WithSnackbarProps, + WebSocketControllerState + > { + constructor( + props: Omit> & WithSnackbarProps + ) { super(props); this.state = { ws: new Sockette(addAccessTokenParameter(wsUrl), { onmessage: this.onMessage, onopen: this.onOpen, - onclose: this.onClose, + onclose: this.onClose }), connected: false - } + }; } componentWillUnmount() { @@ -64,37 +77,42 @@ export function webSocketController>(ws onMessage = (event: MessageEvent) => { const rawData = event.data; if (typeof rawData === 'string' || rawData instanceof String) { - this.handleMessage(JSON.parse(rawData as string) as WebSocketMessage); + this.handleMessage( + JSON.parse(rawData as string) as WebSocketMessage + ); } - } + }; handleMessage = (message: WebSocketMessage) => { + const { clientId, data } = this.state; + switch (message.type) { case WebSocketMessageType.ID: this.setState({ clientId: message.id }); break; case WebSocketMessageType.PAYLOAD: - const { clientId, data } = this.state; if (clientId && (!data || clientId !== message.origin_id)) { - this.setState( - { data: message.payload } - ); + this.setState({ data: message.payload }); } break; } - } + }; onOpen = () => { this.setState({ connected: true }); - } + }; onClose = () => { - this.setState({ connected: false, clientId: undefined, data: undefined }); - } + this.setState({ + connected: false, + clientId: undefined, + data: undefined + }); + }; setData = (data: D, callback?: () => void) => { this.setState({ data }, callback); - } + }; saveData = throttle(() => { const { ws, connected, data } = this.state; @@ -106,28 +124,35 @@ export function webSocketController>(ws saveDataAndClear = throttle(() => { const { ws, connected, data } = this.state; if (connected) { - this.setState({ - data: undefined - }, () => ws.json(data)); + this.setState( + { + data: undefined + }, + () => ws.json(data) + ); } }, wsThrottle); - handleValueChange = (name: keyof D) => (event: React.ChangeEvent) => { + handleValueChange = (name: keyof D) => ( + event: React.ChangeEvent + ) => { const data = { ...this.state.data!, [name]: extractEventValue(event) }; this.setState({ data }); - } + }; render() { - return ; + return ( + + ); } - - }); + } + ); } diff --git a/interface/src/components/WebSocketFormLoader.tsx b/interface/src/components/WebSocketFormLoader.tsx index ee5f335a0..eb4c2e582 100644 --- a/interface/src/components/WebSocketFormLoader.tsx +++ b/interface/src/components/WebSocketFormLoader.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; import { LinearProgress, Typography } from '@material-ui/core'; @@ -8,22 +6,27 @@ import { WebSocketControllerProps } from '.'; const useStyles = makeStyles((theme: Theme) => createStyles({ loadingSettings: { - margin: theme.spacing(0.5), + margin: theme.spacing(0.5) }, loadingSettingsDetails: { margin: theme.spacing(4), - textAlign: "center" + textAlign: 'center' } }) ); -export type WebSocketFormProps = Omit, "connected"> & { data: D }; +export type WebSocketFormProps = Omit< + WebSocketControllerProps, + 'connected' +> & { data: D }; interface WebSocketFormLoaderProps extends WebSocketControllerProps { render: (props: WebSocketFormProps) => JSX.Element; } -export default function WebSocketFormLoader(props: WebSocketFormLoaderProps) { +export default function WebSocketFormLoader( + props: WebSocketFormLoaderProps +) { const { connected, render, data, ...rest } = props; const classes = useStyles(); if (!connected || !data) { diff --git a/interface/src/components/index.ts b/interface/src/components/index.ts index a4284c347..4a6cc6a7b 100644 --- a/interface/src/components/index.ts +++ b/interface/src/components/index.ts @@ -1,17 +1,17 @@ -export { default as BlockFormControlLabel } from './BlockFormControlLabel' -export { default as FormActions } from './FormActions' -export { default as FormButton } from './FormButton' -export { default as HighlightAvatar } from './HighlightAvatar' -export { default as MenuAppBar } from './MenuAppBar' -export { default as PasswordValidator } from './PasswordValidator' -export { default as RestFormLoader } from './RestFormLoader' -export { default as SectionContent } from './SectionContent' -export { default as WebSocketFormLoader } from './WebSocketFormLoader' -export { default as ErrorButton } from './ErrorButton' -export { default as SingleUpload } from './SingleUpload' +export { default as BlockFormControlLabel } from './BlockFormControlLabel'; +export { default as FormActions } from './FormActions'; +export { default as FormButton } from './FormButton'; +export { default as HighlightAvatar } from './HighlightAvatar'; +export { default as MenuAppBar } from './MenuAppBar'; +export { default as PasswordValidator } from './PasswordValidator'; +export { default as RestFormLoader } from './RestFormLoader'; +export { default as SectionContent } from './SectionContent'; +export { default as WebSocketFormLoader } from './WebSocketFormLoader'; +export { default as ErrorButton } from './ErrorButton'; +export { default as SingleUpload } from './SingleUpload'; -export * from './RestFormLoader' -export * from './RestController' +export * from './RestFormLoader'; +export * from './RestController'; -export * from './WebSocketFormLoader' -export * from './WebSocketController' +export * from './WebSocketFormLoader'; +export * from './WebSocketController'; diff --git a/interface/src/features/FeaturesContext.tsx b/interface/src/features/FeaturesContext.tsx index 7986072c6..15296e085 100644 --- a/interface/src/features/FeaturesContext.tsx +++ b/interface/src/features/FeaturesContext.tsx @@ -5,21 +5,26 @@ export interface FeaturesContextValue { features: Features; } -const FeaturesContextDefaultValue = {} as FeaturesContextValue -export const FeaturesContext = React.createContext( - FeaturesContextDefaultValue -); +const FeaturesContextDefaultValue = {} as FeaturesContextValue; +export const FeaturesContext = React.createContext(FeaturesContextDefaultValue); export interface WithFeaturesProps { features: Features; } -export function withFeatures(Component: React.ComponentType) { +export function withFeatures( + Component: React.ComponentType +) { return class extends React.Component> { render() { return ( - {featuresContext => } + {(featuresContext) => ( + + )} ); } diff --git a/interface/src/features/FeaturesWrapper.tsx b/interface/src/features/FeaturesWrapper.tsx index aac353328..4742a4bcc 100644 --- a/interface/src/features/FeaturesWrapper.tsx +++ b/interface/src/features/FeaturesWrapper.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { Features } from './types'; import { FeaturesContext } from './FeaturesContext'; @@ -9,10 +9,9 @@ import { FEATURES_ENDPOINT } from '../api'; interface FeaturesWrapperState { features?: Features; error?: string; -}; +} class FeaturesWrapper extends Component<{}, FeaturesWrapperState> { - state: FeaturesWrapperState = {}; componentDidMount() { @@ -21,41 +20,39 @@ class FeaturesWrapper extends Component<{}, FeaturesWrapperState> { fetchFeaturesDetails = () => { fetch(FEATURES_ENDPOINT) - .then(response => { + .then((response) => { if (response.status === 200) { return response.json(); } else { - throw Error("Unexpected status code: " + response.status); + throw Error('Unexpected status code: ' + response.status); } - }).then(features => { + }) + .then((features) => { this.setState({ features }); }) - .catch(error => { + .catch((error) => { this.setState({ error: error.message }); }); - } + }; render() { const { features, error } = this.state; if (features) { return ( - + {this.props.children} ); } if (error) { - return ( - - ); + return ; } - return ( - - ); + return ; } - } export default FeaturesWrapper; diff --git a/interface/src/features/types.ts b/interface/src/features/types.ts index 0ec4e3e5d..1753d9abf 100644 --- a/interface/src/features/types.ts +++ b/interface/src/features/types.ts @@ -1,8 +1,8 @@ export interface Features { - project: boolean - security: boolean - mqtt: boolean - ntp: boolean - ota: boolean - upload_firmware: boolean + project: boolean; + security: boolean; + mqtt: boolean; + ntp: boolean; + ota: boolean; + upload_firmware: boolean; } diff --git a/interface/src/history.ts b/interface/src/history.ts index 3caf18284..2d6284a38 100644 --- a/interface/src/history.ts +++ b/interface/src/history.ts @@ -1,5 +1,5 @@ -import { createBrowserHistory } from 'history' +import { createBrowserHistory } from 'history'; export default createBrowserHistory({ /* pass a configuration object here if needed */ -}) +}); diff --git a/interface/src/index.tsx b/interface/src/index.tsx index 537631eaa..2abd63e9f 100644 --- a/interface/src/index.tsx +++ b/interface/src/index.tsx @@ -6,8 +6,9 @@ import { Router } from 'react-router'; import App from './App'; -render(( +render( - -), document.getElementById("root")) + , + document.getElementById('root') +); diff --git a/interface/src/mqtt/Mqtt.tsx b/interface/src/mqtt/Mqtt.tsx index 8daca772a..6db8dd5e0 100644 --- a/interface/src/mqtt/Mqtt.tsx +++ b/interface/src/mqtt/Mqtt.tsx @@ -1,9 +1,13 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; -import { AuthenticatedContextProps, withAuthenticatedContext, AuthenticatedRoute } from '../authentication'; +import { + AuthenticatedContextProps, + withAuthenticatedContext, + AuthenticatedRoute +} from '../authentication'; import { MenuAppBar } from '../components'; import MqttStatusController from './MqttStatusController'; import MqttSettingsController from './MqttSettingsController'; @@ -11,8 +15,7 @@ import MqttSettingsController from './MqttSettingsController'; type MqttProps = AuthenticatedContextProps & RouteComponentProps; class Mqtt extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; @@ -20,17 +23,33 @@ class Mqtt extends Component { const { authenticatedContext } = this.props; return ( - + this.handleTabChange(path)} + variant="fullWidth" + > - + - - + + - ) + ); } } diff --git a/interface/src/mqtt/MqttSettingsController.tsx b/interface/src/mqtt/MqttSettingsController.tsx index 63257bd91..eeea08d7c 100644 --- a/interface/src/mqtt/MqttSettingsController.tsx +++ b/interface/src/mqtt/MqttSettingsController.tsx @@ -1,6 +1,11 @@ import React, { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { MQTT_SETTINGS_ENDPOINT } from '../api'; import MqttSettingsForm from './MqttSettingsForm'; @@ -9,7 +14,6 @@ import { MqttSettings } from './types'; type MqttSettingsControllerProps = RestControllerProps; class MqttSettingsController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,11 @@ class MqttSettingsController extends Component { } + render={(formProps) => } /> - ) + ); } - } export default restController(MQTT_SETTINGS_ENDPOINT, MqttSettingsController); diff --git a/interface/src/mqtt/MqttSettingsForm.tsx b/interface/src/mqtt/MqttSettingsForm.tsx index 492183abf..d4ecc3b0f 100644 --- a/interface/src/mqtt/MqttSettingsForm.tsx +++ b/interface/src/mqtt/MqttSettingsForm.tsx @@ -1,31 +1,31 @@ -import React from "react"; +import React from 'react'; import { TextValidator, ValidatorForm, - SelectValidator, -} from "react-material-ui-form-validator"; + SelectValidator +} from 'react-material-ui-form-validator'; -import { Checkbox, TextField, Typography } from "@material-ui/core"; -import SaveIcon from "@material-ui/icons/Save"; -import MenuItem from "@material-ui/core/MenuItem"; +import { Checkbox, TextField, Typography } from '@material-ui/core'; +import SaveIcon from '@material-ui/icons/Save'; +import MenuItem from '@material-ui/core/MenuItem'; import { RestFormProps, FormActions, FormButton, BlockFormControlLabel, - PasswordValidator, -} from "../components"; -import { isIP, isHostname, or, isPath } from "../validators"; + PasswordValidator +} from '../components'; +import { isIP, isHostname, or, isPath } from '../validators'; -import { MqttSettings } from "./types"; +import { MqttSettings } from './types'; type MqttSettingsFormProps = RestFormProps; class MqttSettingsForm extends React.Component { componentDidMount() { - ValidatorForm.addValidationRule("isIPOrHostname", or(isIP, isHostname)); - ValidatorForm.addValidationRule("isPath", isPath); + ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname)); + ValidatorForm.addValidationRule('isPath', isPath); } render() { @@ -36,38 +36,38 @@ class MqttSettingsForm extends React.Component { control={ } label="Enable MQTT" /> { variant="outlined" value={data.port} type="number" - onChange={handleValueChange("port")} + onChange={handleValueChange('port')} margin="normal" /> { fullWidth variant="outlined" value={data.username} - onChange={handleValueChange("username")} + onChange={handleValueChange('username')} margin="normal" /> { fullWidth variant="outlined" value={data.password} - onChange={handleValueChange("password")} + onChange={handleValueChange('password')} margin="normal" /> { fullWidth variant="outlined" value={data.client_id} - onChange={handleValueChange("client_id")} + onChange={handleValueChange('client_id')} margin="normal" /> { variant="outlined" value={data.keep_alive} type="number" - onChange={handleValueChange("keep_alive")} + onChange={handleValueChange('keep_alive')} margin="normal" /> { value={data.mqtt_qos} fullWidth variant="outlined" - onChange={handleValueChange("mqtt_qos")} + onChange={handleValueChange('mqtt_qos')} margin="normal" > 0 (default) @@ -155,7 +155,7 @@ class MqttSettingsForm extends React.Component { control={ } @@ -165,7 +165,7 @@ class MqttSettingsForm extends React.Component { control={ } @@ -181,7 +181,7 @@ class MqttSettingsForm extends React.Component { value={data.nested_format} fullWidth variant="outlined" - onChange={handleValueChange("nested_format")} + onChange={handleValueChange('nested_format')} margin="normal" > nested on a single topic @@ -193,7 +193,7 @@ class MqttSettingsForm extends React.Component { value={data.dallas_format} fullWidth variant="outlined" - onChange={handleValueChange("dallas_format")} + onChange={handleValueChange('dallas_format')} margin="normal" > by Sensor ID @@ -205,7 +205,7 @@ class MqttSettingsForm extends React.Component { value={data.bool_format} fullWidth variant="outlined" - onChange={handleValueChange("bool_format")} + onChange={handleValueChange('bool_format')} margin="normal" > "on"/"off" @@ -219,7 +219,7 @@ class MqttSettingsForm extends React.Component { value={data.subscribe_format} fullWidth variant="outlined" - onChange={handleValueChange("subscribe_format")} + onChange={handleValueChange('subscribe_format')} margin="normal" > general device topic @@ -230,7 +230,7 @@ class MqttSettingsForm extends React.Component { control={ } @@ -243,7 +243,7 @@ class MqttSettingsForm extends React.Component { value={data.ha_climate_format} fullWidth variant="outlined" - onChange={handleValueChange("ha_climate_format")} + onChange={handleValueChange('ha_climate_format')} margin="normal" > use Current temperature (default) @@ -257,16 +257,16 @@ class MqttSettingsForm extends React.Component { { variant="outlined" value={data.publish_time_boiler} type="number" - onChange={handleValueChange("publish_time_boiler")} + onChange={handleValueChange('publish_time_boiler')} margin="normal" /> { variant="outlined" value={data.publish_time_thermostat} type="number" - onChange={handleValueChange("publish_time_thermostat")} + onChange={handleValueChange('publish_time_thermostat')} margin="normal" /> { variant="outlined" value={data.publish_time_solar} type="number" - onChange={handleValueChange("publish_time_solar")} + onChange={handleValueChange('publish_time_solar')} margin="normal" /> { variant="outlined" value={data.publish_time_mixer} type="number" - onChange={handleValueChange("publish_time_mixer")} + onChange={handleValueChange('publish_time_mixer')} margin="normal" /> { variant="outlined" value={data.publish_time_sensor} type="number" - onChange={handleValueChange("publish_time_sensor")} + onChange={handleValueChange('publish_time_sensor')} margin="normal" /> { variant="outlined" value={data.publish_time_other} type="number" - onChange={handleValueChange("publish_time_other")} + onChange={handleValueChange('publish_time_other')} margin="normal" /> diff --git a/interface/src/mqtt/MqttStatus.ts b/interface/src/mqtt/MqttStatus.ts index 8b75743ce..c898fddbd 100644 --- a/interface/src/mqtt/MqttStatus.ts +++ b/interface/src/mqtt/MqttStatus.ts @@ -1,59 +1,59 @@ -import { Theme } from '@material-ui/core' -import { MqttStatus, MqttDisconnectReason } from './types' +import { Theme } from '@material-ui/core'; +import { MqttStatus, MqttDisconnectReason } from './types'; export const mqttStatusHighlight = ( { enabled, connected }: MqttStatus, - theme: Theme, + theme: Theme ) => { if (!enabled) { - return theme.palette.info.main + return theme.palette.info.main; } if (connected) { - return theme.palette.success.main + return theme.palette.success.main; } - return theme.palette.error.main -} + return theme.palette.error.main; +}; export const mqttStatus = ({ enabled, connected }: MqttStatus) => { if (!enabled) { - return 'Not enabled' + return 'Not enabled'; } if (connected) { - return 'Connected' + return 'Connected'; } - return 'Disconnected' -} + return 'Disconnected'; +}; export const disconnectReason = ({ disconnect_reason }: MqttStatus) => { switch (disconnect_reason) { case MqttDisconnectReason.TCP_DISCONNECTED: - return 'TCP disconnected' + return 'TCP disconnected'; case MqttDisconnectReason.MQTT_UNACCEPTABLE_PROTOCOL_VERSION: - return 'Unacceptable protocol version' + return 'Unacceptable protocol version'; case MqttDisconnectReason.MQTT_IDENTIFIER_REJECTED: - return 'Client ID rejected' + return 'Client ID rejected'; case MqttDisconnectReason.MQTT_SERVER_UNAVAILABLE: - return 'Server unavailable' + return 'Server unavailable'; case MqttDisconnectReason.MQTT_MALFORMED_CREDENTIALS: - return 'Malformed credentials' + return 'Malformed credentials'; case MqttDisconnectReason.MQTT_NOT_AUTHORIZED: - return 'Not authorized' + return 'Not authorized'; case MqttDisconnectReason.ESP8266_NOT_ENOUGH_SPACE: - return 'Device out of memory' + return 'Device out of memory'; case MqttDisconnectReason.TLS_BAD_FINGERPRINT: - return 'Server fingerprint invalid' + return 'Server fingerprint invalid'; default: - return 'Unknown' + return 'Unknown'; } -} +}; export const mqttPublishHighlight = ( { mqtt_fails }: MqttStatus, - theme: Theme, + theme: Theme ) => { - if (mqtt_fails === 0) return theme.palette.success.main + if (mqtt_fails === 0) return theme.palette.success.main; - if (mqtt_fails < 10) return theme.palette.warning.main + if (mqtt_fails < 10) return theme.palette.warning.main; - return theme.palette.error.main -} + return theme.palette.error.main; +}; diff --git a/interface/src/mqtt/MqttStatusController.tsx b/interface/src/mqtt/MqttStatusController.tsx index 663fc9013..80eb646c3 100644 --- a/interface/src/mqtt/MqttStatusController.tsx +++ b/interface/src/mqtt/MqttStatusController.tsx @@ -1,6 +1,11 @@ import React, { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { MQTT_STATUS_ENDPOINT } from '../api'; import MqttStatusForm from './MqttStatusForm'; @@ -9,7 +14,6 @@ import { MqttStatus } from './types'; type MqttStatusControllerProps = RestControllerProps; class MqttStatusController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,10 +23,10 @@ class MqttStatusController extends Component { } + render={(formProps) => } /> - ) + ); } } diff --git a/interface/src/mqtt/MqttStatusForm.tsx b/interface/src/mqtt/MqttStatusForm.tsx index c7b7ee161..5d3eefd2e 100644 --- a/interface/src/mqtt/MqttStatusForm.tsx +++ b/interface/src/mqtt/MqttStatusForm.tsx @@ -1,23 +1,39 @@ -import React, { Component, Fragment } from 'react'; +import { Component, Fragment } from 'react'; import { WithTheme, withTheme } from '@material-ui/core/styles'; -import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'; +import { + Avatar, + Divider, + List, + ListItem, + ListItemAvatar, + ListItemText +} from '@material-ui/core'; import DeviceHubIcon from '@material-ui/icons/DeviceHub'; import RefreshIcon from '@material-ui/icons/Refresh'; import ReportIcon from '@material-ui/icons/Report'; -import SpeakerNotesOffIcon from "@material-ui/icons/SpeakerNotesOff"; +import SpeakerNotesOffIcon from '@material-ui/icons/SpeakerNotesOff'; -import { RestFormProps, FormActions, FormButton, HighlightAvatar } from '../components'; -import { mqttStatusHighlight, mqttStatus, mqttPublishHighlight, disconnectReason } from './MqttStatus'; +import { + RestFormProps, + FormActions, + FormButton, + HighlightAvatar +} from '../components'; +import { + mqttStatusHighlight, + mqttStatus, + mqttPublishHighlight, + disconnectReason +} from './MqttStatus'; import { MqttStatus } from './types'; type MqttStatusFormProps = RestFormProps & WithTheme; class MqttStatusForm extends Component { - renderConnectionStatus() { - const { data, theme } = this.props + const { data, theme } = this.props; if (data.connected) { return ( @@ -50,7 +66,10 @@ class MqttStatusForm extends Component { - + @@ -58,7 +77,7 @@ class MqttStatusForm extends Component { } createListItems() { - const { data, theme } = this.props + const { data, theme } = this.props; return ( @@ -78,18 +97,20 @@ class MqttStatusForm extends Component { render() { return ( - - {this.createListItems()} - + {this.createListItems()} - } variant="contained" color="secondary" onClick={this.props.loadData}> + } + variant="contained" + color="secondary" + onClick={this.props.loadData} + > Refresh ); } - } export default withTheme(MqttStatusForm); diff --git a/interface/src/mqtt/types.ts b/interface/src/mqtt/types.ts index c5310c9f8..b5cbdadeb 100644 --- a/interface/src/mqtt/types.ts +++ b/interface/src/mqtt/types.ts @@ -6,40 +6,40 @@ export enum MqttDisconnectReason { MQTT_MALFORMED_CREDENTIALS = 4, MQTT_NOT_AUTHORIZED = 5, ESP8266_NOT_ENOUGH_SPACE = 6, - TLS_BAD_FINGERPRINT = 7, + TLS_BAD_FINGERPRINT = 7 } export interface MqttStatus { - enabled: boolean - connected: boolean - client_id: string - disconnect_reason: MqttDisconnectReason - mqtt_fails: number + enabled: boolean; + connected: boolean; + client_id: string; + disconnect_reason: MqttDisconnectReason; + mqtt_fails: number; } export interface MqttSettings { - enabled: boolean - host: string - port: number - base: string - username: string - password: string - client_id: string - keep_alive: number - clean_session: boolean - max_topic_length: number - publish_time_boiler: number - publish_time_thermostat: number - publish_time_solar: number - publish_time_mixer: number - publish_time_other: number - publish_time_sensor: number - dallas_format: number - bool_format: number - mqtt_qos: number - mqtt_retain: boolean - ha_enabled: boolean - ha_climate_format: number - nested_format: number - subscribe_format: number + enabled: boolean; + host: string; + port: number; + base: string; + username: string; + password: string; + client_id: string; + keep_alive: number; + clean_session: boolean; + max_topic_length: number; + publish_time_boiler: number; + publish_time_thermostat: number; + publish_time_solar: number; + publish_time_mixer: number; + publish_time_other: number; + publish_time_sensor: number; + dallas_format: number; + bool_format: number; + mqtt_qos: number; + mqtt_retain: boolean; + ha_enabled: boolean; + ha_climate_format: number; + nested_format: number; + subscribe_format: number; } diff --git a/interface/src/network/NetworkConnection.tsx b/interface/src/network/NetworkConnection.tsx index 86c289249..e8433e2af 100644 --- a/interface/src/network/NetworkConnection.tsx +++ b/interface/src/network/NetworkConnection.tsx @@ -1,22 +1,31 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; -import { withAuthenticatedContext, AuthenticatedContextProps, AuthenticatedRoute } from '../authentication'; +import { + withAuthenticatedContext, + AuthenticatedContextProps, + AuthenticatedRoute +} from '../authentication'; import { MenuAppBar } from '../components'; import NetworkStatusController from './NetworkStatusController'; import NetworkSettingsController from './NetworkSettingsController'; import WiFiNetworkScanner from './WiFiNetworkScanner'; -import { NetworkConnectionContext, NetworkConnectionContextValue } from './NetworkConnectionContext'; +import { + NetworkConnectionContext, + NetworkConnectionContextValue +} from './NetworkConnectionContext'; import { WiFiNetwork } from './types'; type NetworkConnectionProps = AuthenticatedContextProps & RouteComponentProps; -class NetworkConnection extends Component { - +class NetworkConnection extends Component< + NetworkConnectionProps, + NetworkConnectionContextValue +> { constructor(props: NetworkConnectionProps) { super(props); this.state = { @@ -28,13 +37,13 @@ class NetworkConnection extends Component { this.setState({ selectedNetwork: network }); this.props.history.push('/network/settings'); - } + }; deselectNetwork = () => { this.setState({ selectedNetwork: undefined }); - } + }; - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; @@ -43,20 +52,44 @@ class NetworkConnection extends Component - + this.handleTabChange(path)} + variant="fullWidth" + > - - + + - - - + + + - ) + ); } } diff --git a/interface/src/network/NetworkConnectionContext.tsx b/interface/src/network/NetworkConnectionContext.tsx index e2077ab30..600331199 100644 --- a/interface/src/network/NetworkConnectionContext.tsx +++ b/interface/src/network/NetworkConnectionContext.tsx @@ -7,7 +7,7 @@ export interface NetworkConnectionContextValue { deselectNetwork: () => void; } -const NetworkConnectionContextDefaultValue = {} as NetworkConnectionContextValue +const NetworkConnectionContextDefaultValue = {} as NetworkConnectionContextValue; export const NetworkConnectionContext = React.createContext( NetworkConnectionContextDefaultValue ); diff --git a/interface/src/network/NetworkSettingsController.tsx b/interface/src/network/NetworkSettingsController.tsx index 109e18d8a..5a96bc25b 100644 --- a/interface/src/network/NetworkSettingsController.tsx +++ b/interface/src/network/NetworkSettingsController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import NetworkSettingsForm from './NetworkSettingsForm'; import { NETWORK_SETTINGS_ENDPOINT } from '../api'; import { NetworkSettings } from './types'; @@ -8,7 +13,6 @@ import { NetworkSettings } from './types'; type NetworkSettingsControllerProps = RestControllerProps; class NetworkSettingsController extends Component { - componentDidMount() { this.props.loadData(); } @@ -18,12 +22,14 @@ class NetworkSettingsController extends Component } + render={(formProps) => } /> ); } - } -export default restController(NETWORK_SETTINGS_ENDPOINT, NetworkSettingsController); \ No newline at end of file +export default restController( + NETWORK_SETTINGS_ENDPOINT, + NetworkSettingsController +); diff --git a/interface/src/network/NetworkSettingsForm.tsx b/interface/src/network/NetworkSettingsForm.tsx index 1347d1d0e..5928688a5 100644 --- a/interface/src/network/NetworkSettingsForm.tsx +++ b/interface/src/network/NetworkSettingsForm.tsx @@ -1,7 +1,14 @@ import React, { Fragment } from 'react'; import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator'; -import { Checkbox, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction } from '@material-ui/core'; +import { + Checkbox, + List, + ListItem, + ListItemText, + ListItemAvatar, + ListItemSecondaryAction +} from '@material-ui/core'; import Avatar from '@material-ui/core/Avatar'; import IconButton from '@material-ui/core/IconButton'; @@ -10,31 +17,42 @@ import LockOpenIcon from '@material-ui/icons/LockOpen'; import DeleteIcon from '@material-ui/icons/Delete'; import SaveIcon from '@material-ui/icons/Save'; -import { RestFormProps, PasswordValidator, BlockFormControlLabel, FormActions, FormButton } from '../components'; +import { + RestFormProps, + PasswordValidator, + BlockFormControlLabel, + FormActions, + FormButton +} from '../components'; import { isIP, isHostname, optional } from '../validators'; -import { NetworkConnectionContext, NetworkConnectionContextValue } from './NetworkConnectionContext'; +import { + NetworkConnectionContext, + NetworkConnectionContextValue +} from './NetworkConnectionContext'; import { isNetworkOpen, networkSecurityMode } from './WiFiSecurityModes'; import { NetworkSettings } from './types'; type NetworkStatusFormProps = RestFormProps; class NetworkSettingsForm extends React.Component { - static contextType = NetworkConnectionContext; context!: React.ContextType; - constructor(props: NetworkStatusFormProps, context: NetworkConnectionContextValue) { + constructor( + props: NetworkStatusFormProps, + context: NetworkConnectionContextValue + ) { super(props); const { selectedNetwork } = context; if (selectedNetwork) { const networkSettings: NetworkSettings = { ssid: selectedNetwork.ssid, - password: "", + password: '', hostname: props.data.hostname, - static_ip_config: false, - } + static_ip_config: false + }; props.setData(networkSettings); } } @@ -48,7 +66,7 @@ class NetworkSettingsForm extends React.Component { deselectNetworkAndLoadData = () => { this.context.deselectNetwork(); this.props.loadData(); - } + }; componentWillUnmount() { this.context.deselectNetwork(); @@ -59,41 +77,51 @@ class NetworkSettingsForm extends React.Component { const { data, handleValueChange, saveData } = this.props; return ( - { - selectedNetwork ? - - - - - {isNetworkOpen(selectedNetwork) ? : } - - - - - - - - - - - : - - } - { - (!selectedNetwork || !isNetworkOpen(selectedNetwork)) && + {selectedNetwork ? ( + + + + + {isNetworkOpen(selectedNetwork) ? ( + + ) : ( + + )} + + + + + + + + + + + ) : ( + + )} + {(!selectedNetwork || !isNetworkOpen(selectedNetwork)) && ( { onChange={handleValueChange('password')} margin="normal" /> - } + )} { } label="Static IP Config" /> - { - data.static_ip_config && + {data.static_ip_config && ( { /> { margin="normal" /> - } + )} - } variant="contained" color="primary" type="submit"> + } + variant="contained" + color="primary" + type="submit" + > Save @@ -197,4 +232,4 @@ class NetworkSettingsForm extends React.Component { } } -export default NetworkSettingsForm; \ No newline at end of file +export default NetworkSettingsForm; diff --git a/interface/src/network/NetworkStatus.ts b/interface/src/network/NetworkStatus.ts index b3988605b..c75b14531 100644 --- a/interface/src/network/NetworkStatus.ts +++ b/interface/src/network/NetworkStatus.ts @@ -1,57 +1,57 @@ -import { Theme } from '@material-ui/core' -import { NetworkStatus, NetworkConnectionStatus } from './types' +import { Theme } from '@material-ui/core'; +import { NetworkStatus, NetworkConnectionStatus } from './types'; export const isConnected = ({ status }: NetworkStatus) => { return ( status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED || status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED - ) -} + ); +}; export const isWiFi = ({ status }: NetworkStatus) => - status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED + status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED; export const isEthernet = ({ status }: NetworkStatus) => - status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED + status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED; export const networkStatusHighlight = ( { status }: NetworkStatus, - theme: Theme, + theme: Theme ) => { switch (status) { case NetworkConnectionStatus.WIFI_STATUS_IDLE: case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED: case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD: - return theme.palette.info.main + return theme.palette.info.main; case NetworkConnectionStatus.WIFI_STATUS_CONNECTED: case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED: - return theme.palette.success.main + return theme.palette.success.main; case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED: case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST: - return theme.palette.error.main + return theme.palette.error.main; default: - return theme.palette.warning.main + return theme.palette.warning.main; } -} +}; export const networkStatus = ({ status }: NetworkStatus) => { switch (status) { case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD: - return 'Inactive' + return 'Inactive'; case NetworkConnectionStatus.WIFI_STATUS_IDLE: - return 'Idle' + return 'Idle'; case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL: - return 'No SSID Available' + return 'No SSID Available'; case NetworkConnectionStatus.WIFI_STATUS_CONNECTED: - return 'Connected (WiFi)' + return 'Connected (WiFi)'; case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED: - return 'Connected (Ethernet)' + return 'Connected (Ethernet)'; case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED: - return 'Connection Failed' + return 'Connection Failed'; case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST: - return 'Connection Lost' + return 'Connection Lost'; case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED: - return 'Disconnected' + return 'Disconnected'; default: - return 'Unknown' + return 'Unknown'; } -} +}; diff --git a/interface/src/network/NetworkStatusController.tsx b/interface/src/network/NetworkStatusController.tsx index c647a1992..0c813fcff 100644 --- a/interface/src/network/NetworkStatusController.tsx +++ b/interface/src/network/NetworkStatusController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import NetworkStatusForm from './NetworkStatusForm'; import { NETWORK_STATUS_ENDPOINT } from '../api'; import { NetworkStatus } from './types'; @@ -8,7 +13,6 @@ import { NetworkStatus } from './types'; type NetworkStatusControllerProps = RestControllerProps; class NetworkStatusController extends Component { - componentDidMount() { this.props.loadData(); } @@ -18,12 +22,11 @@ class NetworkStatusController extends Component { } + render={(formProps) => } /> ); } - } export default restController(NETWORK_STATUS_ENDPOINT, NetworkStatusController); diff --git a/interface/src/network/NetworkStatusForm.tsx b/interface/src/network/NetworkStatusForm.tsx index 889ddf3cb..ed4c3ca8e 100644 --- a/interface/src/network/NetworkStatusForm.tsx +++ b/interface/src/network/NetworkStatusForm.tsx @@ -1,46 +1,46 @@ -import React, { Component, Fragment } from "react"; +import { Component, Fragment } from 'react'; -import { WithTheme, withTheme } from "@material-ui/core/styles"; +import { WithTheme, withTheme } from '@material-ui/core/styles'; import { Avatar, Divider, List, ListItem, ListItemAvatar, - ListItemText, -} from "@material-ui/core"; + ListItemText +} from '@material-ui/core'; -import DNSIcon from "@material-ui/icons/Dns"; -import WifiIcon from "@material-ui/icons/Wifi"; -import RouterIcon from "@material-ui/icons/Router"; -import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent"; -import SettingsInputAntennaIcon from "@material-ui/icons/SettingsInputAntenna"; -import DeviceHubIcon from "@material-ui/icons/DeviceHub"; -import RefreshIcon from "@material-ui/icons/Refresh"; +import DNSIcon from '@material-ui/icons/Dns'; +import WifiIcon from '@material-ui/icons/Wifi'; +import RouterIcon from '@material-ui/icons/Router'; +import SettingsInputComponentIcon from '@material-ui/icons/SettingsInputComponent'; +import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna'; +import DeviceHubIcon from '@material-ui/icons/DeviceHub'; +import RefreshIcon from '@material-ui/icons/Refresh'; import { RestFormProps, FormActions, FormButton, - HighlightAvatar, -} from "../components"; + HighlightAvatar +} from '../components'; import { networkStatus, networkStatusHighlight, isConnected, isWiFi, - isEthernet, -} from "./NetworkStatus"; -import { NetworkStatus } from "./types"; + isEthernet +} from './NetworkStatus'; +import { NetworkStatus } from './types'; type NetworkStatusFormProps = RestFormProps & WithTheme; class NetworkStatusForm extends Component { dnsServers(status: NetworkStatus) { if (!status.dns_ip_1) { - return "none"; + return 'none'; } - return status.dns_ip_1 + (status.dns_ip_2 ? "," + status.dns_ip_2 : ""); + return status.dns_ip_1 + (status.dns_ip_2 ? ',' + status.dns_ip_2 : ''); } createListItems() { @@ -110,7 +110,7 @@ class NetworkStatusForm extends Component { diff --git a/interface/src/network/WiFiNetworkScanner.tsx b/interface/src/network/WiFiNetworkScanner.tsx index 744f5154f..ecd470701 100644 --- a/interface/src/network/WiFiNetworkScanner.tsx +++ b/interface/src/network/WiFiNetworkScanner.tsx @@ -1,7 +1,14 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { withSnackbar, WithSnackbarProps } from 'notistack'; -import { createStyles, WithStyles, Theme, withStyles, Typography, LinearProgress } from '@material-ui/core'; +import { + createStyles, + WithStyles, + Theme, + withStyles, + Typography, + LinearProgress +} from '@material-ui/core'; import PermScanWifiIcon from '@material-ui/icons/PermScanWifi'; import { FormActions, FormButton, SectionContent } from '../components'; @@ -11,9 +18,9 @@ import { SCAN_NETWORKS_ENDPOINT, LIST_NETWORKS_ENDPOINT } from '../api'; import WiFiNetworkSelector from './WiFiNetworkSelector'; import { WiFiNetworkList, WiFiNetwork } from './types'; -const NUM_POLLS = 10 -const POLLING_FREQUENCY = 500 -const RETRY_EXCEPTION_TYPE = "retry" +const NUM_POLLS = 10; +const POLLING_FREQUENCY = 500; +const RETRY_EXCEPTION_TYPE = 'retry'; interface WiFiNetworkScannerState { scanningForNetworks: boolean; @@ -21,28 +28,31 @@ interface WiFiNetworkScannerState { networkList?: WiFiNetworkList; } -const styles = (theme: Theme) => createStyles({ - scanningSettings: { - margin: theme.spacing(0.5), - }, - scanningSettingsDetails: { - margin: theme.spacing(4), - textAlign: "center" - }, - scanningProgress: { - margin: theme.spacing(4), - textAlign: "center" - } -}); +const styles = (theme: Theme) => + createStyles({ + scanningSettings: { + margin: theme.spacing(0.5) + }, + scanningSettingsDetails: { + margin: theme.spacing(4), + textAlign: 'center' + }, + scanningProgress: { + margin: theme.spacing(4), + textAlign: 'center' + } + }); type WiFiNetworkScannerProps = WithSnackbarProps & WithStyles; -class WiFiNetworkScanner extends Component { - - pollCount: number = 0; +class WiFiNetworkScanner extends Component< + WiFiNetworkScannerProps, + WiFiNetworkScannerState +> { + pollCount = 0; state: WiFiNetworkScannerState = { - scanningForNetworks: false, + scanningForNetworks: false }; componentDidMount() { @@ -54,23 +64,36 @@ class WiFiNetworkScanner extends Component { - if (response.status === 202) { - this.schedulePollTimeout(); - return; - } - throw Error("Scanning for networks returned unexpected response code: " + response.status); - }).catch(error => { - this.props.enqueueSnackbar("Problem scanning: " + error.message, { - variant: 'error', - }); - this.setState({ scanningForNetworks: false, networkList: undefined, errorMessage: error.message }); + this.setState({ + scanningForNetworks: true, + networkList: undefined, + errorMessage: undefined }); + redirectingAuthorizedFetch(SCAN_NETWORKS_ENDPOINT) + .then((response) => { + if (response.status === 202) { + this.schedulePollTimeout(); + return; + } + throw Error( + 'Scanning for networks returned unexpected response code: ' + + response.status + ); + }) + .catch((error) => { + this.props.enqueueSnackbar('Problem scanning: ' + error.message, { + variant: 'error' + }); + this.setState({ + scanningForNetworks: false, + networkList: undefined, + errorMessage: error.message + }); + }); } schedulePollTimeout() { @@ -80,21 +103,20 @@ class WiFiNetworkScanner extends Component network2.rssi) - return -1; + if (network1.rssi < network2.rssi) return 1; + if (network1.rssi > network2.rssi) return -1; return 0; } pollNetworkList = () => { redirectingAuthorizedFetch(LIST_NETWORKS_ENDPOINT) - .then(response => { + .then((response) => { if (response.status === 200) { return response.json(); } @@ -103,24 +125,34 @@ class WiFiNetworkScanner extends Component { - json.networks.sort(this.compareNetworks) - this.setState({ scanningForNetworks: false, networkList: json, errorMessage: undefined }) + .then((json) => { + json.networks.sort(this.compareNetworks); + this.setState({ + scanningForNetworks: false, + networkList: json, + errorMessage: undefined + }); }) - .catch(error => { + .catch((error) => { if (error.name !== RETRY_EXCEPTION_TYPE) { - this.props.enqueueSnackbar("Problem scanning: " + error.message, { - variant: 'error', + this.props.enqueueSnackbar('Problem scanning: ' + error.message, { + variant: 'error' + }); + this.setState({ + scanningForNetworks: false, + networkList: undefined, + errorMessage: error.message }); - this.setState({ scanningForNetworks: false, networkList: undefined, errorMessage: error.message }); } }); - } + }; renderNetworkScanner() { const { classes } = this.props; @@ -144,9 +176,7 @@ class WiFiNetworkScanner extends Component ); } - return ( - - ); + return ; } render() { @@ -155,14 +185,19 @@ class WiFiNetworkScanner extends Component {this.renderNetworkScanner()} - } variant="contained" color="secondary" onClick={this.requestNetworkScan} disabled={scanningForNetworks}> + } + variant="contained" + color="secondary" + onClick={this.requestNetworkScan} + disabled={scanningForNetworks} + > Scan again… ); } - } export default withSnackbar(withStyles(styles)(WiFiNetworkScanner)); diff --git a/interface/src/network/WiFiNetworkSelector.tsx b/interface/src/network/WiFiNetworkSelector.tsx index 04aa7d660..f63347162 100644 --- a/interface/src/network/WiFiNetworkSelector.tsx +++ b/interface/src/network/WiFiNetworkSelector.tsx @@ -1,7 +1,13 @@ import React, { Component } from 'react'; import { Avatar, Badge } from '@material-ui/core'; -import { List, ListItem, ListItemIcon, ListItemText, ListItemAvatar } from '@material-ui/core'; +import { + List, + ListItem, + ListItemIcon, + ListItemText, + ListItemAvatar +} from '@material-ui/core'; import WifiIcon from '@material-ui/icons/Wifi'; import LockIcon from '@material-ui/icons/Lock'; @@ -16,13 +22,16 @@ interface WiFiNetworkSelectorProps { } class WiFiNetworkSelector extends Component { - static contextType = NetworkConnectionContext; context!: React.ContextType; renderNetwork = (network: WiFiNetwork) => { return ( - this.context.selectNetwork(network)}> + this.context.selectNetwork(network)} + > {isNetworkOpen(network) ? : } @@ -30,25 +39,27 @@ class WiFiNetworkSelector extends Component { - + ); - } + }; render() { return ( - - {this.props.networkList.networks.map(this.renderNetwork)} - + {this.props.networkList.networks.map(this.renderNetwork)} ); } - } export default WiFiNetworkSelector; diff --git a/interface/src/network/WiFiSecurityModes.ts b/interface/src/network/WiFiSecurityModes.ts index 577dc5103..40a055f9d 100644 --- a/interface/src/network/WiFiSecurityModes.ts +++ b/interface/src/network/WiFiSecurityModes.ts @@ -1,23 +1,23 @@ -import { WiFiNetwork, WiFiEncryptionType } from './types' +import { WiFiNetwork, WiFiEncryptionType } from './types'; export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) => - encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN + encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN; export const networkSecurityMode = ({ encryption_type }: WiFiNetwork) => { switch (encryption_type) { case WiFiEncryptionType.WIFI_AUTH_WEP: - return 'WEP' + return 'WEP'; case WiFiEncryptionType.WIFI_AUTH_WPA_PSK: - return 'WPA' + return 'WPA'; case WiFiEncryptionType.WIFI_AUTH_WPA2_PSK: - return 'WPA2' + return 'WPA2'; case WiFiEncryptionType.WIFI_AUTH_WPA_WPA2_PSK: - return 'WPA/WPA2' + return 'WPA/WPA2'; case WiFiEncryptionType.WIFI_AUTH_WPA2_ENTERPRISE: - return 'WPA2 Enterprise' + return 'WPA2 Enterprise'; case WiFiEncryptionType.WIFI_AUTH_OPEN: - return 'None' + return 'None'; default: - return 'Unknown' + return 'Unknown'; } -} +}; diff --git a/interface/src/network/types.ts b/interface/src/network/types.ts index bbd74528a..f5e3cfb2e 100644 --- a/interface/src/network/types.ts +++ b/interface/src/network/types.ts @@ -6,7 +6,7 @@ export enum NetworkConnectionStatus { WIFI_STATUS_CONNECTION_LOST = 5, WIFI_STATUS_DISCONNECTED = 6, ETHERNET_STATUS_CONNECTED = 10, - WIFI_STATUS_NO_SHIELD = 255, + WIFI_STATUS_NO_SHIELD = 255 } export enum WiFiEncryptionType { @@ -15,43 +15,43 @@ export enum WiFiEncryptionType { WIFI_AUTH_WPA_PSK = 2, WIFI_AUTH_WPA2_PSK = 3, WIFI_AUTH_WPA_WPA2_PSK = 4, - WIFI_AUTH_WPA2_ENTERPRISE = 5, + WIFI_AUTH_WPA2_ENTERPRISE = 5 } export interface NetworkStatus { - status: NetworkConnectionStatus - local_ip: string - mac_address: string - rssi: number - ssid: string - bssid: string - channel: number - subnet_mask: string - gateway_ip: string - dns_ip_1: string - dns_ip_2: string + status: NetworkConnectionStatus; + local_ip: string; + mac_address: string; + rssi: number; + ssid: string; + bssid: string; + channel: number; + subnet_mask: string; + gateway_ip: string; + dns_ip_1: string; + dns_ip_2: string; } export interface NetworkSettings { - ssid: string - password: string - hostname: string - static_ip_config: boolean - local_ip?: string - gateway_ip?: string - subnet_mask?: string - dns_ip_1?: string - dns_ip_2?: string + ssid: string; + password: string; + hostname: string; + static_ip_config: boolean; + local_ip?: string; + gateway_ip?: string; + subnet_mask?: string; + dns_ip_1?: string; + dns_ip_2?: string; } export interface WiFiNetworkList { - networks: WiFiNetwork[] + networks: WiFiNetwork[]; } export interface WiFiNetwork { - rssi: number - ssid: string - bssid: string - channel: number - encryption_type: WiFiEncryptionType + rssi: number; + ssid: string; + bssid: string; + channel: number; + encryption_type: WiFiEncryptionType; } diff --git a/interface/src/ntp/NTPSettingsController.tsx b/interface/src/ntp/NTPSettingsController.tsx index 2daa9b6cd..fa7e3cb2f 100644 --- a/interface/src/ntp/NTPSettingsController.tsx +++ b/interface/src/ntp/NTPSettingsController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { NTP_SETTINGS_ENDPOINT } from '../api'; import NTPSettingsForm from './NTPSettingsForm'; @@ -9,7 +14,6 @@ import { NTPSettings } from './types'; type NTPSettingsControllerProps = RestControllerProps; class NTPSettingsController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,11 @@ class NTPSettingsController extends Component { } + render={(formProps) => } /> - ) + ); } - } export default restController(NTP_SETTINGS_ENDPOINT, NTPSettingsController); diff --git a/interface/src/ntp/NTPSettingsForm.tsx b/interface/src/ntp/NTPSettingsForm.tsx index d43700380..aff27a87c 100644 --- a/interface/src/ntp/NTPSettingsForm.tsx +++ b/interface/src/ntp/NTPSettingsForm.tsx @@ -1,10 +1,19 @@ import React from 'react'; -import { TextValidator, ValidatorForm, SelectValidator } from 'react-material-ui-form-validator'; +import { + TextValidator, + ValidatorForm, + SelectValidator +} from 'react-material-ui-form-validator'; import { Checkbox, MenuItem } from '@material-ui/core'; import SaveIcon from '@material-ui/icons/Save'; -import { RestFormProps, FormActions, FormButton, BlockFormControlLabel } from '../components'; +import { + RestFormProps, + FormActions, + FormButton, + BlockFormControlLabel +} from '../components'; import { isIP, isHostname, or } from '../validators'; import { TIME_ZONES, timeZoneSelectItems, selectedTimeZone } from './TZ'; @@ -13,7 +22,6 @@ import { NTPSettings } from './types'; type NTPSettingsFormProps = RestFormProps; class NTPSettingsForm extends React.Component { - componentDidMount() { ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname)); } @@ -25,7 +33,7 @@ class NTPSettingsForm extends React.Component { tz_label: event.target.value, tz_format: TIME_ZONES[event.target.value] }); - } + }; render() { const { data, handleValueChange, saveData } = this.props; @@ -43,7 +51,10 @@ class NTPSettingsForm extends React.Component { /> { {timeZoneSelectItems()} - } variant="contained" color="primary" type="submit"> + } + variant="contained" + color="primary" + type="submit" + > Save diff --git a/interface/src/ntp/NTPStatus.ts b/interface/src/ntp/NTPStatus.ts index d979d5470..4e490bbe4 100644 --- a/interface/src/ntp/NTPStatus.ts +++ b/interface/src/ntp/NTPStatus.ts @@ -1,27 +1,27 @@ -import { Theme } from '@material-ui/core' -import { NTPStatus, NTPSyncStatus } from './types' +import { Theme } from '@material-ui/core'; +import { NTPStatus, NTPSyncStatus } from './types'; export const isNtpActive = ({ status }: NTPStatus) => - status === NTPSyncStatus.NTP_ACTIVE + status === NTPSyncStatus.NTP_ACTIVE; export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => { switch (status) { case NTPSyncStatus.NTP_INACTIVE: - return theme.palette.info.main + return theme.palette.info.main; case NTPSyncStatus.NTP_ACTIVE: - return theme.palette.success.main + return theme.palette.success.main; default: - return theme.palette.error.main + return theme.palette.error.main; } -} +}; export const ntpStatus = ({ status }: NTPStatus) => { switch (status) { case NTPSyncStatus.NTP_INACTIVE: - return 'Inactive' + return 'Inactive'; case NTPSyncStatus.NTP_ACTIVE: - return 'Active' + return 'Active'; default: - return 'Unknown' + return 'Unknown'; } -} +}; diff --git a/interface/src/ntp/NTPStatusController.tsx b/interface/src/ntp/NTPStatusController.tsx index 25ea4dee0..56c8e970e 100644 --- a/interface/src/ntp/NTPStatusController.tsx +++ b/interface/src/ntp/NTPStatusController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { NTP_STATUS_ENDPOINT } from '../api'; import NTPStatusForm from './NTPStatusForm'; @@ -9,7 +14,6 @@ import { NTPStatus } from './types'; type NTPStatusControllerProps = RestControllerProps; class NTPStatusController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,11 @@ class NTPStatusController extends Component { } + render={(formProps) => } /> ); } - } export default restController(NTP_STATUS_ENDPOINT, NTPStatusController); diff --git a/interface/src/ntp/NTPStatusForm.tsx b/interface/src/ntp/NTPStatusForm.tsx index cc0996783..bda5ce295 100644 --- a/interface/src/ntp/NTPStatusForm.tsx +++ b/interface/src/ntp/NTPStatusForm.tsx @@ -1,8 +1,23 @@ import React, { Component, Fragment } from 'react'; import { WithTheme, withTheme } from '@material-ui/core/styles'; -import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText, Button } from '@material-ui/core'; -import { Dialog, DialogTitle, DialogContent, DialogActions, Box, TextField } from '@material-ui/core'; +import { + Avatar, + Divider, + List, + ListItem, + ListItemAvatar, + ListItemText, + Button +} from '@material-ui/core'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Box, + TextField +} from '@material-ui/core'; import SwapVerticalCircleIcon from '@material-ui/icons/SwapVerticalCircle'; import AccessTimeIcon from '@material-ui/icons/AccessTime'; @@ -13,12 +28,22 @@ import RefreshIcon from '@material-ui/icons/Refresh'; import { RestFormProps, FormButton, HighlightAvatar } from '../components'; import { isNtpActive, ntpStatusHighlight, ntpStatus } from './NTPStatus'; -import { formatDuration, formatDateTime, formatLocalDateTime } from './TimeFormat'; +import { + formatDuration, + formatDateTime, + formatLocalDateTime +} from './TimeFormat'; import { NTPStatus, Time } from './types'; -import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; +import { + redirectingAuthorizedFetch, + withAuthenticatedContext, + AuthenticatedContextProps +} from '../authentication'; import { TIME_ENDPOINT } from '../api'; -type NTPStatusFormProps = RestFormProps & WithTheme & AuthenticatedContextProps; +type NTPStatusFormProps = RestFormProps & + WithTheme & + AuthenticatedContextProps; interface NTPStatusFormState { settingTime: boolean; @@ -27,7 +52,6 @@ interface NTPStatusFormState { } class NTPStatusForm extends Component { - constructor(props: NTPStatusFormProps) { super(props); this.state = { @@ -41,20 +65,20 @@ class NTPStatusForm extends Component { this.setState({ localTime: event.target.value }); - } + }; openSetTime = () => { this.setState({ localTime: formatLocalDateTime(new Date()), settingTime: true }); - } + }; closeSetTime = () => { this.setState({ settingTime: false }); - } + }; createTime = (): Time => ({ local_time: formatLocalDateTime(new Date(this.state.localTime)) @@ -62,27 +86,34 @@ class NTPStatusForm extends Component { configureTime = () => { this.setState({ processing: true }); - redirectingAuthorizedFetch(TIME_ENDPOINT, - { - method: 'POST', - body: JSON.stringify(this.createTime()), - headers: { - 'Content-Type': 'application/json' - } - }) - .then(response => { + redirectingAuthorizedFetch(TIME_ENDPOINT, { + method: 'POST', + body: JSON.stringify(this.createTime()), + headers: { + 'Content-Type': 'application/json' + } + }) + .then((response) => { if (response.status === 200) { - this.props.enqueueSnackbar("Time set successfully", { variant: 'success' }); - this.setState({ processing: false, settingTime: false }, this.props.loadData); + this.props.enqueueSnackbar('Time set successfully', { + variant: 'success' + }); + this.setState( + { processing: false, settingTime: false }, + this.props.loadData + ); } else { - throw Error("Error setting time, status code: " + response.status); + throw Error('Error setting time, status code: ' + response.status); } }) - .catch(error => { - this.props.enqueueSnackbar(error.message || "Problem setting the time", { variant: 'error' }); + .catch((error) => { + this.props.enqueueSnackbar( + error.message || 'Problem setting the time', + { variant: 'error' } + ); this.setState({ processing: false, settingTime: false }); }); - } + }; renderSetTimeDialog() { return ( @@ -94,7 +125,9 @@ class NTPStatusForm extends Component { > Set Time - Enter local date and time below to set the device's time. + + Enter local date and time below to set the device's time. + { variant="outlined" fullWidth InputLabelProps={{ - shrink: true, + shrink: true }} /> - - - ) + ); } render() { - const { data, theme } = this.props + const { data, theme } = this.props; const me = this.props.authenticatedContext.me; return ( @@ -154,7 +198,10 @@ class NTPStatusForm extends Component { - + @@ -163,7 +210,10 @@ class NTPStatusForm extends Component { - + @@ -172,19 +222,32 @@ class NTPStatusForm extends Component { - + - } variant="contained" color="secondary" onClick={this.props.loadData}> + } + variant="contained" + color="secondary" + onClick={this.props.loadData} + > Refresh {me.admin && !isNtpActive(data) && ( - diff --git a/interface/src/ntp/NetworkTime.tsx b/interface/src/ntp/NetworkTime.tsx index ebefb6e40..aa7f3af88 100644 --- a/interface/src/ntp/NetworkTime.tsx +++ b/interface/src/ntp/NetworkTime.tsx @@ -1,9 +1,13 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; -import { withAuthenticatedContext, AuthenticatedContextProps, AuthenticatedRoute } from '../authentication'; +import { + withAuthenticatedContext, + AuthenticatedContextProps, + AuthenticatedRoute +} from '../authentication'; import { MenuAppBar } from '../components'; import NTPStatusController from './NTPStatusController'; @@ -12,8 +16,7 @@ import NTPSettingsController from './NTPSettingsController'; type NetworkTimeProps = AuthenticatedContextProps & RouteComponentProps; class NetworkTime extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; @@ -21,19 +24,34 @@ class NetworkTime extends Component { const { authenticatedContext } = this.props; return ( - + this.handleTabChange(path)} + variant="fullWidth" + > - + - - + + - ) + ); } - } -export default withAuthenticatedContext(NetworkTime) +export default withAuthenticatedContext(NetworkTime); diff --git a/interface/src/ntp/TZ.tsx b/interface/src/ntp/TZ.tsx index 0c7ebf809..099300d66 100644 --- a/interface/src/ntp/TZ.tsx +++ b/interface/src/ntp/TZ.tsx @@ -1,479 +1,480 @@ -import React from 'react'; import MenuItem from '@material-ui/core/MenuItem'; type TimeZones = { - [name: string]: string + [name: string]: string; }; export const TIME_ZONES: TimeZones = { - "Africa/Abidjan": "GMT0", - "Africa/Accra": "GMT0", - "Africa/Addis_Ababa": "EAT-3", - "Africa/Algiers": "CET-1", - "Africa/Asmara": "EAT-3", - "Africa/Bamako": "GMT0", - "Africa/Bangui": "WAT-1", - "Africa/Banjul": "GMT0", - "Africa/Bissau": "GMT0", - "Africa/Blantyre": "CAT-2", - "Africa/Brazzaville": "WAT-1", - "Africa/Bujumbura": "CAT-2", - "Africa/Cairo": "EET-2", - "Africa/Casablanca": "UNK-1", - "Africa/Ceuta": "CET-1CEST,M3.5.0,M10.5.0/3", - "Africa/Conakry": "GMT0", - "Africa/Dakar": "GMT0", - "Africa/Dar_es_Salaam": "EAT-3", - "Africa/Djibouti": "EAT-3", - "Africa/Douala": "WAT-1", - "Africa/El_Aaiun": "UNK-1", - "Africa/Freetown": "GMT0", - "Africa/Gaborone": "CAT-2", - "Africa/Harare": "CAT-2", - "Africa/Johannesburg": "SAST-2", - "Africa/Juba": "EAT-3", - "Africa/Kampala": "EAT-3", - "Africa/Khartoum": "CAT-2", - "Africa/Kigali": "CAT-2", - "Africa/Kinshasa": "WAT-1", - "Africa/Lagos": "WAT-1", - "Africa/Libreville": "WAT-1", - "Africa/Lome": "GMT0", - "Africa/Luanda": "WAT-1", - "Africa/Lubumbashi": "CAT-2", - "Africa/Lusaka": "CAT-2", - "Africa/Malabo": "WAT-1", - "Africa/Maputo": "CAT-2", - "Africa/Maseru": "SAST-2", - "Africa/Mbabane": "SAST-2", - "Africa/Mogadishu": "EAT-3", - "Africa/Monrovia": "GMT0", - "Africa/Nairobi": "EAT-3", - "Africa/Ndjamena": "WAT-1", - "Africa/Niamey": "WAT-1", - "Africa/Nouakchott": "GMT0", - "Africa/Ouagadougou": "GMT0", - "Africa/Porto-Novo": "WAT-1", - "Africa/Sao_Tome": "GMT0", - "Africa/Tripoli": "EET-2", - "Africa/Tunis": "CET-1", - "Africa/Windhoek": "CAT-2", - "America/Adak": "HST10HDT,M3.2.0,M11.1.0", - "America/Anchorage": "AKST9AKDT,M3.2.0,M11.1.0", - "America/Anguilla": "AST4", - "America/Antigua": "AST4", - "America/Araguaina": "UNK3", - "America/Argentina/Buenos_Aires": "UNK3", - "America/Argentina/Catamarca": "UNK3", - "America/Argentina/Cordoba": "UNK3", - "America/Argentina/Jujuy": "UNK3", - "America/Argentina/La_Rioja": "UNK3", - "America/Argentina/Mendoza": "UNK3", - "America/Argentina/Rio_Gallegos": "UNK3", - "America/Argentina/Salta": "UNK3", - "America/Argentina/San_Juan": "UNK3", - "America/Argentina/San_Luis": "UNK3", - "America/Argentina/Tucuman": "UNK3", - "America/Argentina/Ushuaia": "UNK3", - "America/Aruba": "AST4", - "America/Asuncion": "UNK4UNK,M10.1.0/0,M3.4.0/0", - "America/Atikokan": "EST5", - "America/Bahia": "UNK3", - "America/Bahia_Banderas": "CST6CDT,M4.1.0,M10.5.0", - "America/Barbados": "AST4", - "America/Belem": "UNK3", - "America/Belize": "CST6", - "America/Blanc-Sablon": "AST4", - "America/Boa_Vista": "UNK4", - "America/Bogota": "UNK5", - "America/Boise": "MST7MDT,M3.2.0,M11.1.0", - "America/Cambridge_Bay": "MST7MDT,M3.2.0,M11.1.0", - "America/Campo_Grande": "UNK4", - "America/Cancun": "EST5", - "America/Caracas": "UNK4", - "America/Cayenne": "UNK3", - "America/Cayman": "EST5", - "America/Chicago": "CST6CDT,M3.2.0,M11.1.0", - "America/Chihuahua": "MST7MDT,M4.1.0,M10.5.0", - "America/Costa_Rica": "CST6", - "America/Creston": "MST7", - "America/Cuiaba": "UNK4", - "America/Curacao": "AST4", - "America/Danmarkshavn": "GMT0", - "America/Dawson": "MST7", - "America/Dawson_Creek": "MST7", - "America/Denver": "MST7MDT,M3.2.0,M11.1.0", - "America/Detroit": "EST5EDT,M3.2.0,M11.1.0", - "America/Dominica": "AST4", - "America/Edmonton": "MST7MDT,M3.2.0,M11.1.0", - "America/Eirunepe": "UNK5", - "America/El_Salvador": "CST6", - "America/Fort_Nelson": "MST7", - "America/Fortaleza": "UNK3", - "America/Glace_Bay": "AST4ADT,M3.2.0,M11.1.0", - "America/Godthab": "UNK3UNK,M3.5.0/-2,M10.5.0/-1", - "America/Goose_Bay": "AST4ADT,M3.2.0,M11.1.0", - "America/Grand_Turk": "EST5EDT,M3.2.0,M11.1.0", - "America/Grenada": "AST4", - "America/Guadeloupe": "AST4", - "America/Guatemala": "CST6", - "America/Guayaquil": "UNK5", - "America/Guyana": "UNK4", - "America/Halifax": "AST4ADT,M3.2.0,M11.1.0", - "America/Havana": "CST5CDT,M3.2.0/0,M11.1.0/1", - "America/Hermosillo": "MST7", - "America/Indiana/Indianapolis": "EST5EDT,M3.2.0,M11.1.0", - "America/Indiana/Knox": "CST6CDT,M3.2.0,M11.1.0", - "America/Indiana/Marengo": "EST5EDT,M3.2.0,M11.1.0", - "America/Indiana/Petersburg": "EST5EDT,M3.2.0,M11.1.0", - "America/Indiana/Tell_City": "CST6CDT,M3.2.0,M11.1.0", - "America/Indiana/Vevay": "EST5EDT,M3.2.0,M11.1.0", - "America/Indiana/Vincennes": "EST5EDT,M3.2.0,M11.1.0", - "America/Indiana/Winamac": "EST5EDT,M3.2.0,M11.1.0", - "America/Inuvik": "MST7MDT,M3.2.0,M11.1.0", - "America/Iqaluit": "EST5EDT,M3.2.0,M11.1.0", - "America/Jamaica": "EST5", - "America/Juneau": "AKST9AKDT,M3.2.0,M11.1.0", - "America/Kentucky/Louisville": "EST5EDT,M3.2.0,M11.1.0", - "America/Kentucky/Monticello": "EST5EDT,M3.2.0,M11.1.0", - "America/Kralendijk": "AST4", - "America/La_Paz": "UNK4", - "America/Lima": "UNK5", - "America/Los_Angeles": "PST8PDT,M3.2.0,M11.1.0", - "America/Lower_Princes": "AST4", - "America/Maceio": "UNK3", - "America/Managua": "CST6", - "America/Manaus": "UNK4", - "America/Marigot": "AST4", - "America/Martinique": "AST4", - "America/Matamoros": "CST6CDT,M3.2.0,M11.1.0", - "America/Mazatlan": "MST7MDT,M4.1.0,M10.5.0", - "America/Menominee": "CST6CDT,M3.2.0,M11.1.0", - "America/Merida": "CST6CDT,M4.1.0,M10.5.0", - "America/Metlakatla": "AKST9AKDT,M3.2.0,M11.1.0", - "America/Mexico_City": "CST6CDT,M4.1.0,M10.5.0", - "America/Miquelon": "UNK3UNK,M3.2.0,M11.1.0", - "America/Moncton": "AST4ADT,M3.2.0,M11.1.0", - "America/Monterrey": "CST6CDT,M4.1.0,M10.5.0", - "America/Montevideo": "UNK3", - "America/Montreal": "EST5EDT,M3.2.0,M11.1.0", - "America/Montserrat": "AST4", - "America/Nassau": "EST5EDT,M3.2.0,M11.1.0", - "America/New_York": "EST5EDT,M3.2.0,M11.1.0", - "America/Nipigon": "EST5EDT,M3.2.0,M11.1.0", - "America/Nome": "AKST9AKDT,M3.2.0,M11.1.0", - "America/Noronha": "UNK2", - "America/North_Dakota/Beulah": "CST6CDT,M3.2.0,M11.1.0", - "America/North_Dakota/Center": "CST6CDT,M3.2.0,M11.1.0", - "America/North_Dakota/New_Salem": "CST6CDT,M3.2.0,M11.1.0", - "America/Ojinaga": "MST7MDT,M3.2.0,M11.1.0", - "America/Panama": "EST5", - "America/Pangnirtung": "EST5EDT,M3.2.0,M11.1.0", - "America/Paramaribo": "UNK3", - "America/Phoenix": "MST7", - "America/Port-au-Prince": "EST5EDT,M3.2.0,M11.1.0", - "America/Port_of_Spain": "AST4", - "America/Porto_Velho": "UNK4", - "America/Puerto_Rico": "AST4", - "America/Punta_Arenas": "UNK3", - "America/Rainy_River": "CST6CDT,M3.2.0,M11.1.0", - "America/Rankin_Inlet": "CST6CDT,M3.2.0,M11.1.0", - "America/Recife": "UNK3", - "America/Regina": "CST6", - "America/Resolute": "CST6CDT,M3.2.0,M11.1.0", - "America/Rio_Branco": "UNK5", - "America/Santarem": "UNK3", - "America/Santiago": "UNK4UNK,M9.1.6/24,M4.1.6/24", - "America/Santo_Domingo": "AST4", - "America/Sao_Paulo": "UNK3", - "America/Scoresbysund": "UNK1UNK,M3.5.0/0,M10.5.0/1", - "America/Sitka": "AKST9AKDT,M3.2.0,M11.1.0", - "America/St_Barthelemy": "AST4", - "America/St_Johns": "NST3:30NDT,M3.2.0,M11.1.0", - "America/St_Kitts": "AST4", - "America/St_Lucia": "AST4", - "America/St_Thomas": "AST4", - "America/St_Vincent": "AST4", - "America/Swift_Current": "CST6", - "America/Tegucigalpa": "CST6", - "America/Thule": "AST4ADT,M3.2.0,M11.1.0", - "America/Thunder_Bay": "EST5EDT,M3.2.0,M11.1.0", - "America/Tijuana": "PST8PDT,M3.2.0,M11.1.0", - "America/Toronto": "EST5EDT,M3.2.0,M11.1.0", - "America/Tortola": "AST4", - "America/Vancouver": "PST8PDT,M3.2.0,M11.1.0", - "America/Whitehorse": "MST7", - "America/Winnipeg": "CST6CDT,M3.2.0,M11.1.0", - "America/Yakutat": "AKST9AKDT,M3.2.0,M11.1.0", - "America/Yellowknife": "MST7MDT,M3.2.0,M11.1.0", - "Antarctica/Casey": "UNK-8", - "Antarctica/Davis": "UNK-7", - "Antarctica/DumontDUrville": "UNK-10", - "Antarctica/Macquarie": "UNK-11", - "Antarctica/Mawson": "UNK-5", - "Antarctica/McMurdo": "NZST-12NZDT,M9.5.0,M4.1.0/3", - "Antarctica/Palmer": "UNK3", - "Antarctica/Rothera": "UNK3", - "Antarctica/Syowa": "UNK-3", - "Antarctica/Troll": "UNK0UNK-2,M3.5.0/1,M10.5.0/3", - "Antarctica/Vostok": "UNK-6", - "Arctic/Longyearbyen": "CET-1CEST,M3.5.0,M10.5.0/3", - "Asia/Aden": "UNK-3", - "Asia/Almaty": "UNK-6", - "Asia/Amman": "EET-2EEST,M3.5.4/24,M10.5.5/1", - "Asia/Anadyr": "UNK-12", - "Asia/Aqtau": "UNK-5", - "Asia/Aqtobe": "UNK-5", - "Asia/Ashgabat": "UNK-5", - "Asia/Atyrau": "UNK-5", - "Asia/Baghdad": "UNK-3", - "Asia/Bahrain": "UNK-3", - "Asia/Baku": "UNK-4", - "Asia/Bangkok": "UNK-7", - "Asia/Barnaul": "UNK-7", - "Asia/Beirut": "EET-2EEST,M3.5.0/0,M10.5.0/0", - "Asia/Bishkek": "UNK-6", - "Asia/Brunei": "UNK-8", - "Asia/Chita": "UNK-9", - "Asia/Choibalsan": "UNK-8", - "Asia/Colombo": "UNK-5:30", - "Asia/Damascus": "EET-2EEST,M3.5.5/0,M10.5.5/0", - "Asia/Dhaka": "UNK-6", - "Asia/Dili": "UNK-9", - "Asia/Dubai": "UNK-4", - "Asia/Dushanbe": "UNK-5", - "Asia/Famagusta": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Asia/Gaza": "EET-2EEST,M3.5.5/0,M10.5.6/1", - "Asia/Hebron": "EET-2EEST,M3.5.5/0,M10.5.6/1", - "Asia/Ho_Chi_Minh": "UNK-7", - "Asia/Hong_Kong": "HKT-8", - "Asia/Hovd": "UNK-7", - "Asia/Irkutsk": "UNK-8", - "Asia/Jakarta": "WIB-7", - "Asia/Jayapura": "WIT-9", - "Asia/Jerusalem": "IST-2IDT,M3.4.4/26,M10.5.0", - "Asia/Kabul": "UNK-4:30", - "Asia/Kamchatka": "UNK-12", - "Asia/Karachi": "PKT-5", - "Asia/Kathmandu": "UNK-5:45", - "Asia/Khandyga": "UNK-9", - "Asia/Kolkata": "IST-5:30", - "Asia/Krasnoyarsk": "UNK-7", - "Asia/Kuala_Lumpur": "UNK-8", - "Asia/Kuching": "UNK-8", - "Asia/Kuwait": "UNK-3", - "Asia/Macau": "CST-8", - "Asia/Magadan": "UNK-11", - "Asia/Makassar": "WITA-8", - "Asia/Manila": "PST-8", - "Asia/Muscat": "UNK-4", - "Asia/Nicosia": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Asia/Novokuznetsk": "UNK-7", - "Asia/Novosibirsk": "UNK-7", - "Asia/Omsk": "UNK-6", - "Asia/Oral": "UNK-5", - "Asia/Phnom_Penh": "UNK-7", - "Asia/Pontianak": "WIB-7", - "Asia/Pyongyang": "KST-9", - "Asia/Qatar": "UNK-3", - "Asia/Qyzylorda": "UNK-5", - "Asia/Riyadh": "UNK-3", - "Asia/Sakhalin": "UNK-11", - "Asia/Samarkand": "UNK-5", - "Asia/Seoul": "KST-9", - "Asia/Shanghai": "CST-8", - "Asia/Singapore": "UNK-8", - "Asia/Srednekolymsk": "UNK-11", - "Asia/Taipei": "CST-8", - "Asia/Tashkent": "UNK-5", - "Asia/Tbilisi": "UNK-4", - "Asia/Tehran": "UNK-3:30UNK,J79/24,J263/24", - "Asia/Thimphu": "UNK-6", - "Asia/Tokyo": "JST-9", - "Asia/Tomsk": "UNK-7", - "Asia/Ulaanbaatar": "UNK-8", - "Asia/Urumqi": "UNK-6", - "Asia/Ust-Nera": "UNK-10", - "Asia/Vientiane": "UNK-7", - "Asia/Vladivostok": "UNK-10", - "Asia/Yakutsk": "UNK-9", - "Asia/Yangon": "UNK-6:30", - "Asia/Yekaterinburg": "UNK-5", - "Asia/Yerevan": "UNK-4", - "Atlantic/Azores": "UNK1UNK,M3.5.0/0,M10.5.0/1", - "Atlantic/Bermuda": "AST4ADT,M3.2.0,M11.1.0", - "Atlantic/Canary": "WET0WEST,M3.5.0/1,M10.5.0", - "Atlantic/Cape_Verde": "UNK1", - "Atlantic/Faroe": "WET0WEST,M3.5.0/1,M10.5.0", - "Atlantic/Madeira": "WET0WEST,M3.5.0/1,M10.5.0", - "Atlantic/Reykjavik": "GMT0", - "Atlantic/South_Georgia": "UNK2", - "Atlantic/St_Helena": "GMT0", - "Atlantic/Stanley": "UNK3", - "Australia/Adelaide": "ACST-9:30ACDT,M10.1.0,M4.1.0/3", - "Australia/Brisbane": "AEST-10", - "Australia/Broken_Hill": "ACST-9:30ACDT,M10.1.0,M4.1.0/3", - "Australia/Currie": "AEST-10AEDT,M10.1.0,M4.1.0/3", - "Australia/Darwin": "ACST-9:30", - "Australia/Eucla": "UNK-8:45", - "Australia/Hobart": "AEST-10AEDT,M10.1.0,M4.1.0/3", - "Australia/Lindeman": "AEST-10", - "Australia/Lord_Howe": "UNK-10:30UNK-11,M10.1.0,M4.1.0", - "Australia/Melbourne": "AEST-10AEDT,M10.1.0,M4.1.0/3", - "Australia/Perth": "AWST-8", - "Australia/Sydney": "AEST-10AEDT,M10.1.0,M4.1.0/3", - "Etc/GMT": "GMT0", - "Etc/GMT+0": "GMT0", - "Etc/GMT+1": "UNK1", - "Etc/GMT+10": "UNK10", - "Etc/GMT+11": "UNK11", - "Etc/GMT+12": "UNK12", - "Etc/GMT+2": "UNK2", - "Etc/GMT+3": "UNK3", - "Etc/GMT+4": "UNK4", - "Etc/GMT+5": "UNK5", - "Etc/GMT+6": "UNK6", - "Etc/GMT+7": "UNK7", - "Etc/GMT+8": "UNK8", - "Etc/GMT+9": "UNK9", - "Etc/GMT-0": "GMT0", - "Etc/GMT-1": "UNK-1", - "Etc/GMT-10": "UNK-10", - "Etc/GMT-11": "UNK-11", - "Etc/GMT-12": "UNK-12", - "Etc/GMT-13": "UNK-13", - "Etc/GMT-14": "UNK-14", - "Etc/GMT-2": "UNK-2", - "Etc/GMT-3": "UNK-3", - "Etc/GMT-4": "UNK-4", - "Etc/GMT-5": "UNK-5", - "Etc/GMT-6": "UNK-6", - "Etc/GMT-7": "UNK-7", - "Etc/GMT-8": "UNK-8", - "Etc/GMT-9": "UNK-9", - "Etc/GMT0": "GMT0", - "Etc/Greenwich": "GMT0", - "Etc/UCT": "UTC0", - "Etc/UTC": "UTC0", - "Etc/Universal": "UTC0", - "Etc/Zulu": "UTC0", - "Europe/Amsterdam": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Andorra": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Astrakhan": "UNK-4", - "Europe/Athens": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Belgrade": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Berlin": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Bratislava": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Brussels": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Bucharest": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Budapest": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Busingen": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Chisinau": "EET-2EEST,M3.5.0,M10.5.0/3", - "Europe/Copenhagen": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Dublin": "IST-1GMT0,M10.5.0,M3.5.0/1", - "Europe/Gibraltar": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Guernsey": "GMT0BST,M3.5.0/1,M10.5.0", - "Europe/Helsinki": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Isle_of_Man": "GMT0BST,M3.5.0/1,M10.5.0", - "Europe/Istanbul": "UNK-3", - "Europe/Jersey": "GMT0BST,M3.5.0/1,M10.5.0", - "Europe/Kaliningrad": "EET-2", - "Europe/Kiev": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Kirov": "UNK-3", - "Europe/Lisbon": "WET0WEST,M3.5.0/1,M10.5.0", - "Europe/Ljubljana": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/London": "GMT0BST,M3.5.0/1,M10.5.0", - "Europe/Luxembourg": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Madrid": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Malta": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Mariehamn": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Minsk": "UNK-3", - "Europe/Monaco": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Moscow": "MSK-3", - "Europe/Oslo": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Paris": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Podgorica": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Prague": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Riga": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Rome": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Samara": "UNK-4", - "Europe/San_Marino": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Sarajevo": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Saratov": "UNK-4", - "Europe/Simferopol": "MSK-3", - "Europe/Skopje": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Sofia": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Stockholm": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Tallinn": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Tirane": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Ulyanovsk": "UNK-4", - "Europe/Uzhgorod": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Vaduz": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Vatican": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Vienna": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Vilnius": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Volgograd": "UNK-4", - "Europe/Warsaw": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Zagreb": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Zaporozhye": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Zurich": "CET-1CEST,M3.5.0,M10.5.0/3", - "Indian/Antananarivo": "EAT-3", - "Indian/Chagos": "UNK-6", - "Indian/Christmas": "UNK-7", - "Indian/Cocos": "UNK-6:30", - "Indian/Comoro": "EAT-3", - "Indian/Kerguelen": "UNK-5", - "Indian/Mahe": "UNK-4", - "Indian/Maldives": "UNK-5", - "Indian/Mauritius": "UNK-4", - "Indian/Mayotte": "EAT-3", - "Indian/Reunion": "UNK-4", - "Pacific/Apia": "UNK-13UNK,M9.5.0/3,M4.1.0/4", - "Pacific/Auckland": "NZST-12NZDT,M9.5.0,M4.1.0/3", - "Pacific/Bougainville": "UNK-11", - "Pacific/Chatham": "UNK-12:45UNK,M9.5.0/2:45,M4.1.0/3:45", - "Pacific/Chuuk": "UNK-10", - "Pacific/Easter": "UNK6UNK,M9.1.6/22,M4.1.6/22", - "Pacific/Efate": "UNK-11", - "Pacific/Enderbury": "UNK-13", - "Pacific/Fakaofo": "UNK-13", - "Pacific/Fiji": "UNK-12UNK,M11.2.0,M1.2.3/99", - "Pacific/Funafuti": "UNK-12", - "Pacific/Galapagos": "UNK6", - "Pacific/Gambier": "UNK9", - "Pacific/Guadalcanal": "UNK-11", - "Pacific/Guam": "ChST-10", - "Pacific/Honolulu": "HST10", - "Pacific/Kiritimati": "UNK-14", - "Pacific/Kosrae": "UNK-11", - "Pacific/Kwajalein": "UNK-12", - "Pacific/Majuro": "UNK-12", - "Pacific/Marquesas": "UNK9:30", - "Pacific/Midway": "SST11", - "Pacific/Nauru": "UNK-12", - "Pacific/Niue": "UNK11", - "Pacific/Norfolk": "UNK-11UNK,M10.1.0,M4.1.0/3", - "Pacific/Noumea": "UNK-11", - "Pacific/Pago_Pago": "SST11", - "Pacific/Palau": "UNK-9", - "Pacific/Pitcairn": "UNK8", - "Pacific/Pohnpei": "UNK-11", - "Pacific/Port_Moresby": "UNK-10", - "Pacific/Rarotonga": "UNK10", - "Pacific/Saipan": "ChST-10", - "Pacific/Tahiti": "UNK10", - "Pacific/Tarawa": "UNK-12", - "Pacific/Tongatapu": "UNK-13", - "Pacific/Wake": "UNK-12", - "Pacific/Wallis": "UNK-12" -} + 'Africa/Abidjan': 'GMT0', + 'Africa/Accra': 'GMT0', + 'Africa/Addis_Ababa': 'EAT-3', + 'Africa/Algiers': 'CET-1', + 'Africa/Asmara': 'EAT-3', + 'Africa/Bamako': 'GMT0', + 'Africa/Bangui': 'WAT-1', + 'Africa/Banjul': 'GMT0', + 'Africa/Bissau': 'GMT0', + 'Africa/Blantyre': 'CAT-2', + 'Africa/Brazzaville': 'WAT-1', + 'Africa/Bujumbura': 'CAT-2', + 'Africa/Cairo': 'EET-2', + 'Africa/Casablanca': 'UNK-1', + 'Africa/Ceuta': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Africa/Conakry': 'GMT0', + 'Africa/Dakar': 'GMT0', + 'Africa/Dar_es_Salaam': 'EAT-3', + 'Africa/Djibouti': 'EAT-3', + 'Africa/Douala': 'WAT-1', + 'Africa/El_Aaiun': 'UNK-1', + 'Africa/Freetown': 'GMT0', + 'Africa/Gaborone': 'CAT-2', + 'Africa/Harare': 'CAT-2', + 'Africa/Johannesburg': 'SAST-2', + 'Africa/Juba': 'EAT-3', + 'Africa/Kampala': 'EAT-3', + 'Africa/Khartoum': 'CAT-2', + 'Africa/Kigali': 'CAT-2', + 'Africa/Kinshasa': 'WAT-1', + 'Africa/Lagos': 'WAT-1', + 'Africa/Libreville': 'WAT-1', + 'Africa/Lome': 'GMT0', + 'Africa/Luanda': 'WAT-1', + 'Africa/Lubumbashi': 'CAT-2', + 'Africa/Lusaka': 'CAT-2', + 'Africa/Malabo': 'WAT-1', + 'Africa/Maputo': 'CAT-2', + 'Africa/Maseru': 'SAST-2', + 'Africa/Mbabane': 'SAST-2', + 'Africa/Mogadishu': 'EAT-3', + 'Africa/Monrovia': 'GMT0', + 'Africa/Nairobi': 'EAT-3', + 'Africa/Ndjamena': 'WAT-1', + 'Africa/Niamey': 'WAT-1', + 'Africa/Nouakchott': 'GMT0', + 'Africa/Ouagadougou': 'GMT0', + 'Africa/Porto-Novo': 'WAT-1', + 'Africa/Sao_Tome': 'GMT0', + 'Africa/Tripoli': 'EET-2', + 'Africa/Tunis': 'CET-1', + 'Africa/Windhoek': 'CAT-2', + 'America/Adak': 'HST10HDT,M3.2.0,M11.1.0', + 'America/Anchorage': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Anguilla': 'AST4', + 'America/Antigua': 'AST4', + 'America/Araguaina': 'UNK3', + 'America/Argentina/Buenos_Aires': 'UNK3', + 'America/Argentina/Catamarca': 'UNK3', + 'America/Argentina/Cordoba': 'UNK3', + 'America/Argentina/Jujuy': 'UNK3', + 'America/Argentina/La_Rioja': 'UNK3', + 'America/Argentina/Mendoza': 'UNK3', + 'America/Argentina/Rio_Gallegos': 'UNK3', + 'America/Argentina/Salta': 'UNK3', + 'America/Argentina/San_Juan': 'UNK3', + 'America/Argentina/San_Luis': 'UNK3', + 'America/Argentina/Tucuman': 'UNK3', + 'America/Argentina/Ushuaia': 'UNK3', + 'America/Aruba': 'AST4', + 'America/Asuncion': 'UNK4UNK,M10.1.0/0,M3.4.0/0', + 'America/Atikokan': 'EST5', + 'America/Bahia': 'UNK3', + 'America/Bahia_Banderas': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Barbados': 'AST4', + 'America/Belem': 'UNK3', + 'America/Belize': 'CST6', + 'America/Blanc-Sablon': 'AST4', + 'America/Boa_Vista': 'UNK4', + 'America/Bogota': 'UNK5', + 'America/Boise': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Cambridge_Bay': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Campo_Grande': 'UNK4', + 'America/Cancun': 'EST5', + 'America/Caracas': 'UNK4', + 'America/Cayenne': 'UNK3', + 'America/Cayman': 'EST5', + 'America/Chicago': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Chihuahua': 'MST7MDT,M4.1.0,M10.5.0', + 'America/Costa_Rica': 'CST6', + 'America/Creston': 'MST7', + 'America/Cuiaba': 'UNK4', + 'America/Curacao': 'AST4', + 'America/Danmarkshavn': 'GMT0', + 'America/Dawson': 'MST7', + 'America/Dawson_Creek': 'MST7', + 'America/Denver': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Detroit': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Dominica': 'AST4', + 'America/Edmonton': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Eirunepe': 'UNK5', + 'America/El_Salvador': 'CST6', + 'America/Fort_Nelson': 'MST7', + 'America/Fortaleza': 'UNK3', + 'America/Glace_Bay': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Godthab': 'UNK3UNK,M3.5.0/-2,M10.5.0/-1', + 'America/Goose_Bay': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Grand_Turk': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Grenada': 'AST4', + 'America/Guadeloupe': 'AST4', + 'America/Guatemala': 'CST6', + 'America/Guayaquil': 'UNK5', + 'America/Guyana': 'UNK4', + 'America/Halifax': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Havana': 'CST5CDT,M3.2.0/0,M11.1.0/1', + 'America/Hermosillo': 'MST7', + 'America/Indiana/Indianapolis': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Knox': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Indiana/Marengo': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Petersburg': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Tell_City': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Indiana/Vevay': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Vincennes': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Winamac': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Inuvik': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Iqaluit': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Jamaica': 'EST5', + 'America/Juneau': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Kentucky/Louisville': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Kentucky/Monticello': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Kralendijk': 'AST4', + 'America/La_Paz': 'UNK4', + 'America/Lima': 'UNK5', + 'America/Los_Angeles': 'PST8PDT,M3.2.0,M11.1.0', + 'America/Lower_Princes': 'AST4', + 'America/Maceio': 'UNK3', + 'America/Managua': 'CST6', + 'America/Manaus': 'UNK4', + 'America/Marigot': 'AST4', + 'America/Martinique': 'AST4', + 'America/Matamoros': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Mazatlan': 'MST7MDT,M4.1.0,M10.5.0', + 'America/Menominee': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Merida': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Metlakatla': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Mexico_City': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Miquelon': 'UNK3UNK,M3.2.0,M11.1.0', + 'America/Moncton': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Monterrey': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Montevideo': 'UNK3', + 'America/Montreal': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Montserrat': 'AST4', + 'America/Nassau': 'EST5EDT,M3.2.0,M11.1.0', + 'America/New_York': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Nipigon': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Nome': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Noronha': 'UNK2', + 'America/North_Dakota/Beulah': 'CST6CDT,M3.2.0,M11.1.0', + 'America/North_Dakota/Center': 'CST6CDT,M3.2.0,M11.1.0', + 'America/North_Dakota/New_Salem': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Ojinaga': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Panama': 'EST5', + 'America/Pangnirtung': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Paramaribo': 'UNK3', + 'America/Phoenix': 'MST7', + 'America/Port-au-Prince': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Port_of_Spain': 'AST4', + 'America/Porto_Velho': 'UNK4', + 'America/Puerto_Rico': 'AST4', + 'America/Punta_Arenas': 'UNK3', + 'America/Rainy_River': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Rankin_Inlet': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Recife': 'UNK3', + 'America/Regina': 'CST6', + 'America/Resolute': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Rio_Branco': 'UNK5', + 'America/Santarem': 'UNK3', + 'America/Santiago': 'UNK4UNK,M9.1.6/24,M4.1.6/24', + 'America/Santo_Domingo': 'AST4', + 'America/Sao_Paulo': 'UNK3', + 'America/Scoresbysund': 'UNK1UNK,M3.5.0/0,M10.5.0/1', + 'America/Sitka': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/St_Barthelemy': 'AST4', + 'America/St_Johns': 'NST3:30NDT,M3.2.0,M11.1.0', + 'America/St_Kitts': 'AST4', + 'America/St_Lucia': 'AST4', + 'America/St_Thomas': 'AST4', + 'America/St_Vincent': 'AST4', + 'America/Swift_Current': 'CST6', + 'America/Tegucigalpa': 'CST6', + 'America/Thule': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Thunder_Bay': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Tijuana': 'PST8PDT,M3.2.0,M11.1.0', + 'America/Toronto': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Tortola': 'AST4', + 'America/Vancouver': 'PST8PDT,M3.2.0,M11.1.0', + 'America/Whitehorse': 'MST7', + 'America/Winnipeg': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Yakutat': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Yellowknife': 'MST7MDT,M3.2.0,M11.1.0', + 'Antarctica/Casey': 'UNK-8', + 'Antarctica/Davis': 'UNK-7', + 'Antarctica/DumontDUrville': 'UNK-10', + 'Antarctica/Macquarie': 'UNK-11', + 'Antarctica/Mawson': 'UNK-5', + 'Antarctica/McMurdo': 'NZST-12NZDT,M9.5.0,M4.1.0/3', + 'Antarctica/Palmer': 'UNK3', + 'Antarctica/Rothera': 'UNK3', + 'Antarctica/Syowa': 'UNK-3', + 'Antarctica/Troll': 'UNK0UNK-2,M3.5.0/1,M10.5.0/3', + 'Antarctica/Vostok': 'UNK-6', + 'Arctic/Longyearbyen': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Asia/Aden': 'UNK-3', + 'Asia/Almaty': 'UNK-6', + 'Asia/Amman': 'EET-2EEST,M3.5.4/24,M10.5.5/1', + 'Asia/Anadyr': 'UNK-12', + 'Asia/Aqtau': 'UNK-5', + 'Asia/Aqtobe': 'UNK-5', + 'Asia/Ashgabat': 'UNK-5', + 'Asia/Atyrau': 'UNK-5', + 'Asia/Baghdad': 'UNK-3', + 'Asia/Bahrain': 'UNK-3', + 'Asia/Baku': 'UNK-4', + 'Asia/Bangkok': 'UNK-7', + 'Asia/Barnaul': 'UNK-7', + 'Asia/Beirut': 'EET-2EEST,M3.5.0/0,M10.5.0/0', + 'Asia/Bishkek': 'UNK-6', + 'Asia/Brunei': 'UNK-8', + 'Asia/Chita': 'UNK-9', + 'Asia/Choibalsan': 'UNK-8', + 'Asia/Colombo': 'UNK-5:30', + 'Asia/Damascus': 'EET-2EEST,M3.5.5/0,M10.5.5/0', + 'Asia/Dhaka': 'UNK-6', + 'Asia/Dili': 'UNK-9', + 'Asia/Dubai': 'UNK-4', + 'Asia/Dushanbe': 'UNK-5', + 'Asia/Famagusta': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Asia/Gaza': 'EET-2EEST,M3.5.5/0,M10.5.6/1', + 'Asia/Hebron': 'EET-2EEST,M3.5.5/0,M10.5.6/1', + 'Asia/Ho_Chi_Minh': 'UNK-7', + 'Asia/Hong_Kong': 'HKT-8', + 'Asia/Hovd': 'UNK-7', + 'Asia/Irkutsk': 'UNK-8', + 'Asia/Jakarta': 'WIB-7', + 'Asia/Jayapura': 'WIT-9', + 'Asia/Jerusalem': 'IST-2IDT,M3.4.4/26,M10.5.0', + 'Asia/Kabul': 'UNK-4:30', + 'Asia/Kamchatka': 'UNK-12', + 'Asia/Karachi': 'PKT-5', + 'Asia/Kathmandu': 'UNK-5:45', + 'Asia/Khandyga': 'UNK-9', + 'Asia/Kolkata': 'IST-5:30', + 'Asia/Krasnoyarsk': 'UNK-7', + 'Asia/Kuala_Lumpur': 'UNK-8', + 'Asia/Kuching': 'UNK-8', + 'Asia/Kuwait': 'UNK-3', + 'Asia/Macau': 'CST-8', + 'Asia/Magadan': 'UNK-11', + 'Asia/Makassar': 'WITA-8', + 'Asia/Manila': 'PST-8', + 'Asia/Muscat': 'UNK-4', + 'Asia/Nicosia': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Asia/Novokuznetsk': 'UNK-7', + 'Asia/Novosibirsk': 'UNK-7', + 'Asia/Omsk': 'UNK-6', + 'Asia/Oral': 'UNK-5', + 'Asia/Phnom_Penh': 'UNK-7', + 'Asia/Pontianak': 'WIB-7', + 'Asia/Pyongyang': 'KST-9', + 'Asia/Qatar': 'UNK-3', + 'Asia/Qyzylorda': 'UNK-5', + 'Asia/Riyadh': 'UNK-3', + 'Asia/Sakhalin': 'UNK-11', + 'Asia/Samarkand': 'UNK-5', + 'Asia/Seoul': 'KST-9', + 'Asia/Shanghai': 'CST-8', + 'Asia/Singapore': 'UNK-8', + 'Asia/Srednekolymsk': 'UNK-11', + 'Asia/Taipei': 'CST-8', + 'Asia/Tashkent': 'UNK-5', + 'Asia/Tbilisi': 'UNK-4', + 'Asia/Tehran': 'UNK-3:30UNK,J79/24,J263/24', + 'Asia/Thimphu': 'UNK-6', + 'Asia/Tokyo': 'JST-9', + 'Asia/Tomsk': 'UNK-7', + 'Asia/Ulaanbaatar': 'UNK-8', + 'Asia/Urumqi': 'UNK-6', + 'Asia/Ust-Nera': 'UNK-10', + 'Asia/Vientiane': 'UNK-7', + 'Asia/Vladivostok': 'UNK-10', + 'Asia/Yakutsk': 'UNK-9', + 'Asia/Yangon': 'UNK-6:30', + 'Asia/Yekaterinburg': 'UNK-5', + 'Asia/Yerevan': 'UNK-4', + 'Atlantic/Azores': 'UNK1UNK,M3.5.0/0,M10.5.0/1', + 'Atlantic/Bermuda': 'AST4ADT,M3.2.0,M11.1.0', + 'Atlantic/Canary': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Atlantic/Cape_Verde': 'UNK1', + 'Atlantic/Faroe': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Atlantic/Madeira': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Atlantic/Reykjavik': 'GMT0', + 'Atlantic/South_Georgia': 'UNK2', + 'Atlantic/St_Helena': 'GMT0', + 'Atlantic/Stanley': 'UNK3', + 'Australia/Adelaide': 'ACST-9:30ACDT,M10.1.0,M4.1.0/3', + 'Australia/Brisbane': 'AEST-10', + 'Australia/Broken_Hill': 'ACST-9:30ACDT,M10.1.0,M4.1.0/3', + 'Australia/Currie': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Australia/Darwin': 'ACST-9:30', + 'Australia/Eucla': 'UNK-8:45', + 'Australia/Hobart': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Australia/Lindeman': 'AEST-10', + 'Australia/Lord_Howe': 'UNK-10:30UNK-11,M10.1.0,M4.1.0', + 'Australia/Melbourne': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Australia/Perth': 'AWST-8', + 'Australia/Sydney': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Etc/GMT': 'GMT0', + 'Etc/GMT+0': 'GMT0', + 'Etc/GMT+1': 'UNK1', + 'Etc/GMT+10': 'UNK10', + 'Etc/GMT+11': 'UNK11', + 'Etc/GMT+12': 'UNK12', + 'Etc/GMT+2': 'UNK2', + 'Etc/GMT+3': 'UNK3', + 'Etc/GMT+4': 'UNK4', + 'Etc/GMT+5': 'UNK5', + 'Etc/GMT+6': 'UNK6', + 'Etc/GMT+7': 'UNK7', + 'Etc/GMT+8': 'UNK8', + 'Etc/GMT+9': 'UNK9', + 'Etc/GMT-0': 'GMT0', + 'Etc/GMT-1': 'UNK-1', + 'Etc/GMT-10': 'UNK-10', + 'Etc/GMT-11': 'UNK-11', + 'Etc/GMT-12': 'UNK-12', + 'Etc/GMT-13': 'UNK-13', + 'Etc/GMT-14': 'UNK-14', + 'Etc/GMT-2': 'UNK-2', + 'Etc/GMT-3': 'UNK-3', + 'Etc/GMT-4': 'UNK-4', + 'Etc/GMT-5': 'UNK-5', + 'Etc/GMT-6': 'UNK-6', + 'Etc/GMT-7': 'UNK-7', + 'Etc/GMT-8': 'UNK-8', + 'Etc/GMT-9': 'UNK-9', + 'Etc/GMT0': 'GMT0', + 'Etc/Greenwich': 'GMT0', + 'Etc/UCT': 'UTC0', + 'Etc/UTC': 'UTC0', + 'Etc/Universal': 'UTC0', + 'Etc/Zulu': 'UTC0', + 'Europe/Amsterdam': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Andorra': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Astrakhan': 'UNK-4', + 'Europe/Athens': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Belgrade': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Berlin': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Bratislava': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Brussels': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Bucharest': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Budapest': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Busingen': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Chisinau': 'EET-2EEST,M3.5.0,M10.5.0/3', + 'Europe/Copenhagen': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Dublin': 'IST-1GMT0,M10.5.0,M3.5.0/1', + 'Europe/Gibraltar': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Guernsey': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Helsinki': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Isle_of_Man': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Istanbul': 'UNK-3', + 'Europe/Jersey': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Kaliningrad': 'EET-2', + 'Europe/Kiev': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Kirov': 'UNK-3', + 'Europe/Lisbon': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Europe/Ljubljana': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/London': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Luxembourg': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Madrid': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Malta': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Mariehamn': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Minsk': 'UNK-3', + 'Europe/Monaco': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Moscow': 'MSK-3', + 'Europe/Oslo': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Paris': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Podgorica': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Prague': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Riga': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Rome': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Samara': 'UNK-4', + 'Europe/San_Marino': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Sarajevo': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Saratov': 'UNK-4', + 'Europe/Simferopol': 'MSK-3', + 'Europe/Skopje': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Sofia': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Stockholm': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Tallinn': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Tirane': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Ulyanovsk': 'UNK-4', + 'Europe/Uzhgorod': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Vaduz': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Vatican': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Vienna': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Vilnius': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Volgograd': 'UNK-4', + 'Europe/Warsaw': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Zagreb': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Zaporozhye': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Zurich': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Indian/Antananarivo': 'EAT-3', + 'Indian/Chagos': 'UNK-6', + 'Indian/Christmas': 'UNK-7', + 'Indian/Cocos': 'UNK-6:30', + 'Indian/Comoro': 'EAT-3', + 'Indian/Kerguelen': 'UNK-5', + 'Indian/Mahe': 'UNK-4', + 'Indian/Maldives': 'UNK-5', + 'Indian/Mauritius': 'UNK-4', + 'Indian/Mayotte': 'EAT-3', + 'Indian/Reunion': 'UNK-4', + 'Pacific/Apia': 'UNK-13UNK,M9.5.0/3,M4.1.0/4', + 'Pacific/Auckland': 'NZST-12NZDT,M9.5.0,M4.1.0/3', + 'Pacific/Bougainville': 'UNK-11', + 'Pacific/Chatham': 'UNK-12:45UNK,M9.5.0/2:45,M4.1.0/3:45', + 'Pacific/Chuuk': 'UNK-10', + 'Pacific/Easter': 'UNK6UNK,M9.1.6/22,M4.1.6/22', + 'Pacific/Efate': 'UNK-11', + 'Pacific/Enderbury': 'UNK-13', + 'Pacific/Fakaofo': 'UNK-13', + 'Pacific/Fiji': 'UNK-12UNK,M11.2.0,M1.2.3/99', + 'Pacific/Funafuti': 'UNK-12', + 'Pacific/Galapagos': 'UNK6', + 'Pacific/Gambier': 'UNK9', + 'Pacific/Guadalcanal': 'UNK-11', + 'Pacific/Guam': 'ChST-10', + 'Pacific/Honolulu': 'HST10', + 'Pacific/Kiritimati': 'UNK-14', + 'Pacific/Kosrae': 'UNK-11', + 'Pacific/Kwajalein': 'UNK-12', + 'Pacific/Majuro': 'UNK-12', + 'Pacific/Marquesas': 'UNK9:30', + 'Pacific/Midway': 'SST11', + 'Pacific/Nauru': 'UNK-12', + 'Pacific/Niue': 'UNK11', + 'Pacific/Norfolk': 'UNK-11UNK,M10.1.0,M4.1.0/3', + 'Pacific/Noumea': 'UNK-11', + 'Pacific/Pago_Pago': 'SST11', + 'Pacific/Palau': 'UNK-9', + 'Pacific/Pitcairn': 'UNK8', + 'Pacific/Pohnpei': 'UNK-11', + 'Pacific/Port_Moresby': 'UNK-10', + 'Pacific/Rarotonga': 'UNK10', + 'Pacific/Saipan': 'ChST-10', + 'Pacific/Tahiti': 'UNK10', + 'Pacific/Tarawa': 'UNK-12', + 'Pacific/Tongatapu': 'UNK-13', + 'Pacific/Wake': 'UNK-12', + 'Pacific/Wallis': 'UNK-12' +}; export function selectedTimeZone(label: string, format: string) { return TIME_ZONES[label] === format ? label : undefined; } export function timeZoneSelectItems() { - return Object.keys(TIME_ZONES).map(label => ( - {label} + return Object.keys(TIME_ZONES).map((label) => ( + + {label} + )); -} \ No newline at end of file +} diff --git a/interface/src/ntp/TimeFormat.ts b/interface/src/ntp/TimeFormat.ts index a8e986ea1..273c5df14 100644 --- a/interface/src/ntp/TimeFormat.ts +++ b/interface/src/ntp/TimeFormat.ts @@ -1,4 +1,4 @@ -import parseMilliseconds from 'parse-ms' +import parseMilliseconds from 'parse-ms'; const LOCALE_FORMAT = new Intl.DateTimeFormat([...window.navigator.languages], { day: 'numeric', @@ -7,37 +7,37 @@ const LOCALE_FORMAT = new Intl.DateTimeFormat([...window.navigator.languages], { hour: 'numeric', minute: 'numeric', second: 'numeric', - hour12: false, -}) + hour12: false +}); export const formatDateTime = (dateTime: string) => { - return LOCALE_FORMAT.format(new Date(dateTime.substr(0, 19))) -} + return LOCALE_FORMAT.format(new Date(dateTime.substr(0, 19))); +}; export const formatLocalDateTime = (date: Date) => { return new Date(date.getTime() - date.getTimezoneOffset() * 60000) .toISOString() .slice(0, -1) - .substr(0, 19) -} + .substr(0, 19); +}; export const formatDuration = (duration: number) => { - const { days, hours, minutes, seconds } = parseMilliseconds(duration * 1000) - var formatted = '' + const { days, hours, minutes, seconds } = parseMilliseconds(duration * 1000); + let formatted = ''; if (days) { - formatted += pluralize(days, 'day') + formatted += pluralize(days, 'day'); } if (formatted || hours) { - formatted += pluralize(hours, 'hour') + formatted += pluralize(hours, 'hour'); } if (formatted || minutes) { - formatted += pluralize(minutes, 'minute') + formatted += pluralize(minutes, 'minute'); } if (formatted || seconds) { - formatted += pluralize(seconds, 'second') + formatted += pluralize(seconds, 'second'); } - return formatted -} + return formatted; +}; -const pluralize = (count: number, noun: string, suffix: string = 's') => - ` ${count} ${noun}${count !== 1 ? suffix : ''} ` +const pluralize = (count: number, noun: string, suffix = 's') => + ` ${count} ${noun}${count !== 1 ? suffix : ''} `; diff --git a/interface/src/ntp/types.ts b/interface/src/ntp/types.ts index 8311b8743..06c87ef06 100644 --- a/interface/src/ntp/types.ts +++ b/interface/src/ntp/types.ts @@ -1,23 +1,23 @@ export enum NTPSyncStatus { NTP_INACTIVE = 0, - NTP_ACTIVE = 1, + NTP_ACTIVE = 1 } export interface NTPStatus { - status: NTPSyncStatus - utc_time: string - local_time: string - server: string - uptime: number + status: NTPSyncStatus; + utc_time: string; + local_time: string; + server: string; + uptime: number; } export interface NTPSettings { - enabled: boolean - server: string - tz_label: string - tz_format: string + enabled: boolean; + server: string; + tz_label: string; + tz_format: string; } export interface Time { - local_time: string + local_time: string; } diff --git a/interface/src/project/EMSESPBoardProfiles.tsx b/interface/src/project/EMSESPBoardProfiles.tsx index b13eced75..05336ae70 100644 --- a/interface/src/project/EMSESPBoardProfiles.tsx +++ b/interface/src/project/EMSESPBoardProfiles.tsx @@ -1,23 +1,24 @@ -import React from 'react'; import MenuItem from '@material-ui/core/MenuItem'; type BoardProfiles = { - [name: string]: string + [name: string]: string; }; export const BOARD_PROFILES: BoardProfiles = { - "S32": "BBQKees Gateway S32", - "E32": "BBQKees Gateway E32", - "NODEMCU": "NodeMCU 32S", - "MH-ET": "MH-ET Live D1 Mini", - "LOLIN": "Lolin D32", - "OLIMEX": "Olimex ESP32-EVB", - "TLK110": "Generic Ethernet (TLK110)", - "LAN8720": "Generic Ethernet (LAN8720)" -} + S32: 'BBQKees Gateway S32', + E32: 'BBQKees Gateway E32', + NODEMCU: 'NodeMCU 32S', + 'MH-ET': 'MH-ET Live D1 Mini', + LOLIN: 'Lolin D32', + OLIMEX: 'Olimex ESP32-EVB', + TLK110: 'Generic Ethernet (TLK110)', + LAN8720: 'Generic Ethernet (LAN8720)' +}; export function boardProfileSelectItems() { - return Object.keys(BOARD_PROFILES).map(code => ( - {BOARD_PROFILES[code]} - )); + return Object.keys(BOARD_PROFILES).map((code) => ( + + {BOARD_PROFILES[code]} + + )); } diff --git a/interface/src/project/EMSESPDashboard.tsx b/interface/src/project/EMSESPDashboard.tsx index 626821f7c..55a396085 100644 --- a/interface/src/project/EMSESPDashboard.tsx +++ b/interface/src/project/EMSESPDashboard.tsx @@ -1,5 +1,5 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; @@ -12,30 +12,43 @@ import EMSESPDevicesController from './EMSESPDevicesController'; import EMSESPHelp from './EMSESPHelp'; class EMSESP extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; render() { return ( - + this.handleTabChange(path)} + variant="fullWidth" + > - - - + + + - - ) + ); } - } export default EMSESP; diff --git a/interface/src/project/EMSESPDevicesController.tsx b/interface/src/project/EMSESPDevicesController.tsx index bd035f933..66a52bd3a 100644 --- a/interface/src/project/EMSESPDevicesController.tsx +++ b/interface/src/project/EMSESPDevicesController.tsx @@ -1,30 +1,34 @@ import React, { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { ENDPOINT_ROOT } from '../api'; import EMSESPDevicesForm from './EMSESPDevicesForm'; import { EMSESPDevices } from './EMSESPtypes'; -export const EMSESP_DEVICES_ENDPOINT = ENDPOINT_ROOT + "allDevices"; +export const EMSESP_DEVICES_ENDPOINT = ENDPOINT_ROOT + 'allDevices'; type EMSESPDevicesControllerProps = RestControllerProps; class EMSESPDevicesController extends Component { + componentDidMount() { + this.props.loadData(); + } - componentDidMount() { - this.props.loadData(); - } - - render() { - return ( - - } - /> - - ) - } + render() { + return ( + + } + /> + + ); + } } export default restController(EMSESP_DEVICES_ENDPOINT, EMSESPDevicesController); diff --git a/interface/src/project/EMSESPDevicesForm.tsx b/interface/src/project/EMSESPDevicesForm.tsx index e6f422c3b..394e29ffc 100644 --- a/interface/src/project/EMSESPDevicesForm.tsx +++ b/interface/src/project/EMSESPDevicesForm.tsx @@ -1,38 +1,62 @@ -import React, { Component, Fragment } from "react"; -import { withStyles, Theme, createStyles } from "@material-ui/core/styles"; +import React, { Component, Fragment } from 'react'; +import { withStyles, Theme, createStyles } from '@material-ui/core/styles'; import { - Table, TableBody, TableCell, TableHead, TableRow, TableContainer, withWidth, WithWidthProps, isWidthDown, - Button, Tooltip, DialogTitle, DialogContent, DialogActions, Box, Dialog, Typography -} from "@material-ui/core"; + Table, + TableBody, + TableCell, + TableHead, + TableRow, + TableContainer, + withWidth, + WithWidthProps, + isWidthDown, + Button, + Tooltip, + DialogTitle, + DialogContent, + DialogActions, + Box, + Dialog, + Typography +} from '@material-ui/core'; -import RefreshIcon from "@material-ui/icons/Refresh"; -import ListIcon from "@material-ui/icons/List"; +import RefreshIcon from '@material-ui/icons/Refresh'; +import ListIcon from '@material-ui/icons/List'; import IconButton from '@material-ui/core/IconButton'; import EditIcon from '@material-ui/icons/Edit'; -import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from "../authentication"; -import { RestFormProps, FormButton, extractEventValue } from "../components"; +import { + redirectingAuthorizedFetch, + withAuthenticatedContext, + AuthenticatedContextProps +} from '../authentication'; +import { RestFormProps, FormButton, extractEventValue } from '../components'; -import { EMSESPDevices, EMSESPDeviceData, Device, DeviceValue } from "./EMSESPtypes"; +import { + EMSESPDevices, + EMSESPDeviceData, + Device, + DeviceValue +} from './EMSESPtypes'; import ValueForm from './ValueForm'; -import { ENDPOINT_ROOT } from "../api"; +import { ENDPOINT_ROOT } from '../api'; -export const SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + "scanDevices"; -export const DEVICE_DATA_ENDPOINT = ENDPOINT_ROOT + "deviceData"; -export const WRITE_VALUE_ENDPOINT = ENDPOINT_ROOT + "writeValue"; +export const SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + 'scanDevices'; +export const DEVICE_DATA_ENDPOINT = ENDPOINT_ROOT + 'deviceData'; +export const WRITE_VALUE_ENDPOINT = ENDPOINT_ROOT + 'writeValue'; const StyledTableCell = withStyles((theme: Theme) => createStyles({ head: { backgroundColor: theme.palette.common.black, - color: theme.palette.common.white, + color: theme.palette.common.white }, body: { - fontSize: 14, - }, + fontSize: 14 + } }) )(TableCell); @@ -42,8 +66,8 @@ const CustomTooltip = withStyles((theme: Theme) => ({ color: 'white', boxShadow: theme.shadows[1], fontSize: 11, - border: '1px solid #dadde9', - }, + border: '1px solid #dadde9' + } }))(Tooltip); function compareDevices(a: Device, b: Device) { @@ -64,63 +88,81 @@ interface EMSESPDevicesFormState { devicevalue?: DeviceValue; } -type EMSESPDevicesFormProps = RestFormProps & AuthenticatedContextProps & WithWidthProps; +type EMSESPDevicesFormProps = RestFormProps & + AuthenticatedContextProps & + WithWidthProps; function formatTemp(t: string) { if (t == null) { - return "n/a"; + return 'n/a'; } - return t + " °C"; + return t + ' °C'; } function formatUnit(u: string) { if (u == null) { return u; } - return " " + u; + return ' ' + u; } -class EMSESPDevicesForm extends Component { +class EMSESPDevicesForm extends Component< + EMSESPDevicesFormProps, + EMSESPDevicesFormState +> { state: EMSESPDevicesFormState = { confirmScanDevices: false, processing: false }; - handleValueChange = (name: keyof DeviceValue) => (event: React.ChangeEvent) => { - this.setState({ devicevalue: { ...this.state.devicevalue!, [name]: extractEventValue(event) } }); + handleValueChange = (name: keyof DeviceValue) => ( + event: React.ChangeEvent + ) => { + this.setState({ + devicevalue: { + ...this.state.devicevalue!, + [name]: extractEventValue(event) + } + }); }; cancelEditingValue = () => { this.setState({ devicevalue: undefined }); - } + }; doneEditingValue = () => { const { devicevalue } = this.state; redirectingAuthorizedFetch(WRITE_VALUE_ENDPOINT, { - method: "POST", + method: 'POST', body: JSON.stringify({ devicevalue: devicevalue }), headers: { - "Content-Type": "application/json", - }, + 'Content-Type': 'application/json' + } }) .then((response) => { if (response.status === 200) { - this.props.enqueueSnackbar("Write command sent to device", { variant: "success" }); + this.props.enqueueSnackbar('Write command sent to device', { + variant: 'success' + }); } else if (response.status === 204) { - this.props.enqueueSnackbar("Write command failed", { variant: "error" }); + this.props.enqueueSnackbar('Write command failed', { + variant: 'error' + }); } else if (response.status === 403) { - this.props.enqueueSnackbar("Write access denied", { variant: "error" }); + this.props.enqueueSnackbar('Write access denied', { + variant: 'error' + }); } else { - throw Error("Unexpected response code: " + response.status); + throw Error('Unexpected response code: ' + response.status); } }) .catch((error) => { - this.props.enqueueSnackbar( - error.message || "Problem writing value", { variant: "error" } - ); + this.props.enqueueSnackbar(error.message || 'Problem writing value', { + variant: 'error' + }); }); if (devicevalue) { @@ -128,20 +170,19 @@ class EMSESPDevicesForm extends Component { + sendCommand = (i: number) => { this.setState({ devicevalue: { id: this.state.selectedDevice!, data: this.state.deviceData?.data[i]!, uom: this.state.deviceData?.data[i + 1]!, name: this.state.deviceData?.data[i + 2]!, - cmd: this.state.deviceData?.data[i + 3]!, + cmd: this.state.deviceData?.data[i + 3]! } }); - } + }; noDevices = () => { return this.props.data.devices.length === 0; @@ -166,22 +207,41 @@ class EMSESPDevicesForm extends Component {data.devices.sort(compareDevices).map((device) => ( - this.handleRowClick(device)}> + this.handleRowClick(device)} + > - - {device.brand + " " + device.name} + + {device.brand + ' ' + device.name}{' '} + ))} @@ -191,10 +251,13 @@ class EMSESPDevicesForm extends Component - No EMS devices found. Check the connections and for possible Tx errors. + No EMS devices found. Check the connections and for possible Tx + errors. )} @@ -255,14 +318,25 @@ class EMSESPDevicesForm extends Component Confirm Scan Devices - Are you sure you want to initiate a scan on the EMS bus for all new devices? + Are you sure you want to initiate a scan on the EMS bus for all new + devices? - @@ -283,17 +357,17 @@ class EMSESPDevicesForm extends Component { if (response.status === 200) { - this.props.enqueueSnackbar("Device scan is starting...", { - variant: "info", + this.props.enqueueSnackbar('Device scan is starting...', { + variant: 'info' }); this.setState({ processing: false, confirmScanDevices: false }); } else { - throw Error("Invalid status code: " + response.status); + throw Error('Invalid status code: ' + response.status); } }) .catch((error) => { - this.props.enqueueSnackbar(error.message || "Problem with scan", { - variant: "error", + this.props.enqueueSnackbar(error.message || 'Problem with scan', { + variant: 'error' }); this.setState({ processing: false, confirmScanDevices: false }); }); @@ -302,25 +376,25 @@ class EMSESPDevicesForm extends Component { this.setState({ selectedDevice: device.id, deviceData: undefined }); redirectingAuthorizedFetch(DEVICE_DATA_ENDPOINT, { - method: "POST", + method: 'POST', body: JSON.stringify({ id: device.id }), headers: { - "Content-Type": "application/json", - }, + 'Content-Type': 'application/json' + } }) .then((response) => { if (response.status === 200) { return response.json(); } - throw Error("Unexpected response code: " + response.status); + throw Error('Unexpected response code: ' + response.status); }) .then((json) => { this.setState({ deviceData: json }); }) .catch((error) => { this.props.enqueueSnackbar( - error.message || "Problem getting device data", - { variant: "error" } + error.message || 'Problem getting device data', + { variant: 'error' } ); this.setState({ deviceData: undefined }); }); @@ -351,10 +425,9 @@ class EMSESPDevicesForm extends Component - - + {deviceData.data.map((item, i) => { if (i % 4) { @@ -362,19 +435,30 @@ class EMSESPDevicesForm extends Component - + {deviceData.data[i + 3] && me.admin && ( - - this.sendCommand(i)}> + this.sendCommand(i)} + > )} - {deviceData.data[i + 2]} - {deviceData.data[i]}{formatUnit(deviceData.data[i + 1])} + + {deviceData.data[i + 2]} + + + {deviceData.data[i]} + {formatUnit(deviceData.data[i + 1])} + ); } @@ -390,7 +474,7 @@ class EMSESPDevicesForm extends Component )} - + ); } @@ -405,26 +489,34 @@ class EMSESPDevicesForm extends Component
- } variant="contained" color="secondary" onClick={this.props.loadData} > + } + variant="contained" + color="secondary" + onClick={this.props.loadData} + > Refresh - } variant="contained" onClick={this.onScanDevices} > + } + variant="contained" + onClick={this.onScanDevices} + > Scan Devices {this.renderScanDevicesDialog()} - { - devicevalue && + {devicevalue && ( - } + )} ); } diff --git a/interface/src/project/EMSESPHelp.tsx b/interface/src/project/EMSESPHelp.tsx index 278c1a12f..46905d542 100644 --- a/interface/src/project/EMSESPHelp.tsx +++ b/interface/src/project/EMSESPHelp.tsx @@ -1,85 +1,110 @@ import React, { Component } from 'react'; -import { Typography, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@material-ui/core'; +import { + Typography, + Box, + List, + ListItem, + ListItemText, + Link, + ListItemAvatar +} from '@material-ui/core'; import { SectionContent } from '../components'; -import CommentIcon from "@material-ui/icons/CommentTwoTone"; -import MenuBookIcon from "@material-ui/icons/MenuBookTwoTone"; -import GitHubIcon from "@material-ui/icons/GitHub"; -import StarIcon from "@material-ui/icons/Star"; -import ImportExportIcon from "@material-ui/icons/ImportExport"; -import BugReportIcon from "@material-ui/icons/BugReportTwoTone"; +import CommentIcon from '@material-ui/icons/CommentTwoTone'; +import MenuBookIcon from '@material-ui/icons/MenuBookTwoTone'; +import GitHubIcon from '@material-ui/icons/GitHub'; +import StarIcon from '@material-ui/icons/Star'; +import ImportExportIcon from '@material-ui/icons/ImportExport'; +import BugReportIcon from '@material-ui/icons/BugReportTwoTone'; -export const WebAPISystemSettings = window.location.origin + "/api/system/settings"; -export const WebAPISystemInfo = window.location.origin + "/api/system/info"; +export const WebAPISystemSettings = + window.location.origin + '/api/system/settings'; +export const WebAPISystemInfo = window.location.origin + '/api/system/info'; class EMSESPHelp extends Component { + render() { + return ( + + + + + + + + For the latest news and updates go to the{' '} + + {'official documentation'} website + + + - render() { - return ( - + + + + + + For live community chat join our{' '} + + {'Discord'} server + + + - + + + + + + To report an issue or feature request go to{' '} + + {'click here'} + + + - - - - - - For the latest news and updates go to the {'official documentation'} website - - + + + + + + To export your system settings{' '} + + {'click here'} + + + - - - - - - For live community chat join our {'Discord'} server - - - - - - - - - To report an issue or feature request go to {'click here'} - - - - - - - - - To export your system settings {'click here'} - - - - - - - - - - To export the current status of EMS-ESP {'click here'} - - - - - - - - EMS-ESP is free and open-source. -

Please consider supporting this project by giving it a on our {'GitHub page'}. -
-
-

- -
- ) - } + + + + + + To export the current status of EMS-ESP{' '} + + {'click here'} + + + +
+ + + EMS-ESP is free and open-source. +

Please consider supporting this project by giving it a{' '} + on our{' '} + + {'GitHub page'} + + . +
+
+

+
+ ); + } } export default EMSESPHelp; diff --git a/interface/src/project/EMSESPSettings.tsx b/interface/src/project/EMSESPSettings.tsx index f61fa9b71..826130a2e 100644 --- a/interface/src/project/EMSESPSettings.tsx +++ b/interface/src/project/EMSESPSettings.tsx @@ -1,5 +1,5 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; @@ -10,26 +10,31 @@ import { AuthenticatedRoute } from '../authentication'; import EMSESPSettingsController from './EMSESPSettingsController'; class EMSESP extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; render() { return ( - + this.handleTabChange(path)} + variant="fullWidth" + > - + - - ) + ); } - } export default EMSESP; diff --git a/interface/src/project/EMSESPSettingsController.tsx b/interface/src/project/EMSESPSettingsController.tsx index 286afd576..c173e74e6 100644 --- a/interface/src/project/EMSESPSettingsController.tsx +++ b/interface/src/project/EMSESPSettingsController.tsx @@ -1,38 +1,41 @@ -import React, { Component } from 'react'; -// import { Container } from '@material-ui/core'; +import { Component } from 'react'; import { ENDPOINT_ROOT } from '../api'; import EMSESPSettingsForm from './EMSESPSettingsForm'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { EMSESPSettings } from './EMSESPtypes'; -export const EMSESP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "emsespSettings"; +export const EMSESP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'emsespSettings'; type EMSESPSettingsControllerProps = RestControllerProps; class EMSESPSettingsController extends Component { + componentDidMount() { + this.props.loadData(); + } - componentDidMount() { - this.props.loadData(); - } - - render() { - return ( - // - - ( - - )} - /> - - // - ) - } - + render() { + return ( + // + + } + /> + + // + ); + } } -export default restController(EMSESP_SETTINGS_ENDPOINT, EMSESPSettingsController); +export default restController( + EMSESP_SETTINGS_ENDPOINT, + EMSESPSettingsController +); diff --git a/interface/src/project/EMSESPSettingsForm.tsx b/interface/src/project/EMSESPSettingsForm.tsx index d5306e1d3..16c075243 100644 --- a/interface/src/project/EMSESPSettingsForm.tsx +++ b/interface/src/project/EMSESPSettingsForm.tsx @@ -1,9 +1,9 @@ -import React from "react"; +import React from 'react'; import { ValidatorForm, TextValidator, - SelectValidator, -} from "react-material-ui-form-validator"; + SelectValidator +} from 'react-material-ui-form-validator'; import { Checkbox, @@ -11,34 +11,34 @@ import { Box, Link, withWidth, - WithWidthProps, -} from "@material-ui/core"; -import SaveIcon from "@material-ui/icons/Save"; -import MenuItem from "@material-ui/core/MenuItem"; + WithWidthProps +} from '@material-ui/core'; +import SaveIcon from '@material-ui/icons/Save'; +import MenuItem from '@material-ui/core/MenuItem'; -import Grid from "@material-ui/core/Grid"; +import Grid from '@material-ui/core/Grid'; import { redirectingAuthorizedFetch, withAuthenticatedContext, - AuthenticatedContextProps, -} from "../authentication"; + AuthenticatedContextProps +} from '../authentication'; import { RestFormProps, FormActions, FormButton, - BlockFormControlLabel, -} from "../components"; + BlockFormControlLabel +} from '../components'; -import { isIP, optional } from "../validators"; +import { isIP, optional } from '../validators'; -import { EMSESPSettings } from "./EMSESPtypes"; +import { EMSESPSettings } from './EMSESPtypes'; -import { boardProfileSelectItems } from "./EMSESPBoardProfiles"; +import { boardProfileSelectItems } from './EMSESPBoardProfiles'; -import { ENDPOINT_ROOT } from "../api"; -export const BOARD_PROFILE_ENDPOINT = ENDPOINT_ROOT + "boardProfile"; +import { ENDPOINT_ROOT } from '../api'; +export const BOARD_PROFILE_ENDPOINT = ENDPOINT_ROOT + 'boardProfile'; type EMSESPSettingsFormProps = RestFormProps & AuthenticatedContextProps & @@ -50,38 +50,38 @@ interface EMSESPSettingsFormState { class EMSESPSettingsForm extends React.Component { state: EMSESPSettingsFormState = { - processing: false, + processing: false }; componentDidMount() { - ValidatorForm.addValidationRule("isOptionalIP", optional(isIP)); + ValidatorForm.addValidationRule('isOptionalIP', optional(isIP)); } changeBoardProfile = (event: React.ChangeEvent) => { const { data, setData } = this.props; setData({ ...data, - board_profile: event.target.value, + board_profile: event.target.value }); - if (event.target.value === "CUSTOM") return; + if (event.target.value === 'CUSTOM') return; this.setState({ processing: true }); redirectingAuthorizedFetch(BOARD_PROFILE_ENDPOINT, { - method: "POST", + method: 'POST', body: JSON.stringify({ code: event.target.value }), headers: { - "Content-Type": "application/json", - }, + 'Content-Type': 'application/json' + } }) .then((response) => { if (response.status === 200) { return response.json(); } - throw Error("Unexpected response code: " + response.status); + throw Error('Unexpected response code: ' + response.status); }) .then((json) => { - this.props.enqueueSnackbar("Profile loaded", { variant: "success" }); + this.props.enqueueSnackbar('Profile loaded', { variant: 'success' }); setData({ ...data, led_gpio: json.led_gpio, @@ -89,14 +89,14 @@ class EMSESPSettingsForm extends React.Component { rx_gpio: json.rx_gpio, tx_gpio: json.tx_gpio, pbutton_gpio: json.pbutton_gpio, - board_profile: event.target.value, + board_profile: event.target.value }); this.setState({ processing: false }); }) .catch((error) => { this.props.enqueueSnackbar( - error.message || "Problem fetching board profile", - { variant: "warning" } + error.message || 'Problem fetching board profile', + { variant: 'warning' } ); this.setState({ processing: false }); }); @@ -108,13 +108,13 @@ class EMSESPSettingsForm extends React.Component { - Adjust any of the EMS-ESP settings here. For help refer to the{" "} + Adjust any of the EMS-ESP settings here. For help refer to the{' '} - {"online documentation"} + {'online documentation'} . @@ -139,7 +139,7 @@ class EMSESPSettingsForm extends React.Component { value={data.tx_mode} fullWidth variant="outlined" - onChange={handleValueChange("tx_mode")} + onChange={handleValueChange('tx_mode')} margin="normal" > Off @@ -156,7 +156,7 @@ class EMSESPSettingsForm extends React.Component { value={data.ems_bus_id} fullWidth variant="outlined" - onChange={handleValueChange("ems_bus_id")} + onChange={handleValueChange('ems_bus_id')} margin="normal" > Service Key (0x0B) @@ -169,16 +169,16 @@ class EMSESPSettingsForm extends React.Component { { variant="outlined" value={data.tx_delay} type="number" - onChange={handleValueChange("tx_delay")} + onChange={handleValueChange('tx_delay')} margin="normal" /> @@ -216,12 +216,12 @@ class EMSESPSettingsForm extends React.Component { margin="normal" > {boardProfileSelectItems()} - + Custom... - {data.board_profile === "CUSTOM" && ( + {data.board_profile === 'CUSTOM' && ( { { variant="outlined" value={data.rx_gpio} type="number" - onChange={handleValueChange("rx_gpio")} + onChange={handleValueChange('rx_gpio')} margin="normal" /> { variant="outlined" value={data.tx_gpio} type="number" - onChange={handleValueChange("tx_gpio")} + onChange={handleValueChange('tx_gpio')} margin="normal" /> { variant="outlined" value={data.pbutton_gpio} type="number" - onChange={handleValueChange("pbutton_gpio")} + onChange={handleValueChange('pbutton_gpio')} margin="normal" /> { variant="outlined" value={data.dallas_gpio} type="number" - onChange={handleValueChange("dallas_gpio")} + onChange={handleValueChange('dallas_gpio')} margin="normal" /> { variant="outlined" value={data.led_gpio} type="number" - onChange={handleValueChange("led_gpio")} + onChange={handleValueChange('led_gpio')} margin="normal" /> @@ -372,7 +372,7 @@ class EMSESPSettingsForm extends React.Component { control={ } @@ -385,7 +385,7 @@ class EMSESPSettingsForm extends React.Component { control={ } @@ -397,7 +397,7 @@ class EMSESPSettingsForm extends React.Component { control={ } @@ -407,7 +407,7 @@ class EMSESPSettingsForm extends React.Component { control={ } @@ -424,7 +424,7 @@ class EMSESPSettingsForm extends React.Component { control={ } @@ -434,7 +434,7 @@ class EMSESPSettingsForm extends React.Component { control={ } @@ -451,7 +451,7 @@ class EMSESPSettingsForm extends React.Component { control={ } @@ -468,30 +468,30 @@ class EMSESPSettingsForm extends React.Component { > { variant="outlined" value={data.syslog_port} type="number" - onChange={handleValueChange("syslog_port")} + onChange={handleValueChange('syslog_port')} margin="normal" /> @@ -510,7 +510,7 @@ class EMSESPSettingsForm extends React.Component { value={data.syslog_level} fullWidth variant="outlined" - onChange={handleValueChange("syslog_level")} + onChange={handleValueChange('syslog_level')} margin="normal" > OFF @@ -524,16 +524,16 @@ class EMSESPSettingsForm extends React.Component { { variant="outlined" value={data.syslog_mark_interval} type="number" - onChange={handleValueChange("syslog_mark_interval")} + onChange={handleValueChange('syslog_mark_interval')} margin="normal" /> @@ -549,7 +549,7 @@ class EMSESPSettingsForm extends React.Component { control={ } diff --git a/interface/src/project/EMSESPStatus.ts b/interface/src/project/EMSESPStatus.ts index 71c256f85..b018ff6cc 100644 --- a/interface/src/project/EMSESPStatus.ts +++ b/interface/src/project/EMSESPStatus.ts @@ -1,39 +1,39 @@ -import { Theme } from '@material-ui/core' -import { EMSESPStatus, busConnectionStatus } from './EMSESPtypes' +import { Theme } from '@material-ui/core'; +import { EMSESPStatus, busConnectionStatus } from './EMSESPtypes'; export const isConnected = ({ status }: EMSESPStatus) => - status !== busConnectionStatus.BUS_STATUS_OFFLINE + status !== busConnectionStatus.BUS_STATUS_OFFLINE; export const busStatusHighlight = ({ status }: EMSESPStatus, theme: Theme) => { switch (status) { case busConnectionStatus.BUS_STATUS_TX_ERRORS: - return theme.palette.warning.main + return theme.palette.warning.main; case busConnectionStatus.BUS_STATUS_CONNECTED: - return theme.palette.success.main + return theme.palette.success.main; case busConnectionStatus.BUS_STATUS_OFFLINE: - return theme.palette.error.main + return theme.palette.error.main; default: - return theme.palette.warning.main + return theme.palette.warning.main; } -} +}; export const busStatus = ({ status }: EMSESPStatus) => { switch (status) { case busConnectionStatus.BUS_STATUS_CONNECTED: - return 'Connected' + return 'Connected'; case busConnectionStatus.BUS_STATUS_TX_ERRORS: - return 'Tx Errors' + return 'Tx Errors'; case busConnectionStatus.BUS_STATUS_OFFLINE: - return 'Disconnected' + return 'Disconnected'; default: - return 'Unknown' + return 'Unknown'; } -} +}; export const qualityHighlight = (value: number, theme: Theme) => { if (value >= 95) { - return theme.palette.success.main + return theme.palette.success.main; } - return theme.palette.error.main -} + return theme.palette.error.main; +}; diff --git a/interface/src/project/EMSESPStatusController.tsx b/interface/src/project/EMSESPStatusController.tsx index 0b8293b6f..83ba70b50 100644 --- a/interface/src/project/EMSESPStatusController.tsx +++ b/interface/src/project/EMSESPStatusController.tsx @@ -1,30 +1,34 @@ import React, { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { ENDPOINT_ROOT } from '../api'; import EMSESPStatusForm from './EMSESPStatusForm'; import { EMSESPStatus } from './EMSESPtypes'; -export const EMSESP_STATUS_ENDPOINT = ENDPOINT_ROOT + "emsespStatus"; +export const EMSESP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'emsespStatus'; type EMSESPStatusControllerProps = RestControllerProps; class EMSESPStatusController extends Component { + componentDidMount() { + this.props.loadData(); + } - componentDidMount() { - this.props.loadData(); - } - - render() { - return ( - - } - /> - - ) - } + render() { + return ( + + } + /> + + ); + } } export default restController(EMSESP_STATUS_ENDPOINT, EMSESPStatusController); diff --git a/interface/src/project/EMSESPStatusForm.tsx b/interface/src/project/EMSESPStatusForm.tsx index a080e3e63..909024bcb 100644 --- a/interface/src/project/EMSESPStatusForm.tsx +++ b/interface/src/project/EMSESPStatusForm.tsx @@ -1,6 +1,6 @@ -import React, { Component, Fragment } from "react"; +import React, { Component, Fragment } from 'react'; -import { WithTheme, withTheme } from "@material-ui/core/styles"; +import { WithTheme, withTheme } from '@material-ui/core/styles'; import { TableContainer, Table, @@ -13,35 +13,32 @@ import { ListItemText, withWidth, WithWidthProps, - isWidthDown, -} from "@material-ui/core"; + isWidthDown +} from '@material-ui/core'; -import RefreshIcon from "@material-ui/icons/Refresh"; -import DeviceHubIcon from "@material-ui/icons/DeviceHub"; +import RefreshIcon from '@material-ui/icons/Refresh'; +import DeviceHubIcon from '@material-ui/icons/DeviceHub'; import { RestFormProps, FormActions, FormButton, - HighlightAvatar, -} from "../components"; + HighlightAvatar +} from '../components'; -import { - busStatus, - busStatusHighlight, - isConnected, -} from "./EMSESPStatus"; +import { busStatus, busStatusHighlight, isConnected } from './EMSESPStatus'; -import { EMSESPStatus } from "./EMSESPtypes"; +import { EMSESPStatus } from './EMSESPtypes'; function formatNumber(num: number) { return new Intl.NumberFormat().format(num); } -type EMSESPStatusFormProps = RestFormProps & WithTheme & WithWidthProps; +type EMSESPStatusFormProps = RestFormProps & + WithTheme & + WithWidthProps; class EMSESPStatusForm extends Component { - createListItems() { const { data, theme, width } = this.props; return ( @@ -52,24 +49,30 @@ class EMSESPStatusForm extends Component { - + {isConnected(data) && ( -
+
- - # Telegrams Received - - {formatNumber(data.rx_received)} (quality {data.rx_quality}%) + # Telegrams Received + + {formatNumber(data.rx_received)} (quality{' '} + {data.rx_quality}%) - - # Telegrams Sent - - {formatNumber(data.tx_sent)} (quality {data.tx_quality}%) + # Telegrams Sent + + {formatNumber(data.tx_sent)} (quality {data.tx_quality} + %) @@ -86,7 +89,11 @@ class EMSESPStatusForm extends Component { {this.createListItems()} } variant="contained" color="secondary" onClick={this.props.loadData}> + startIcon={} + variant="contained" + color="secondary" + onClick={this.props.loadData} + > Refresh diff --git a/interface/src/project/EMSESPtypes.ts b/interface/src/project/EMSESPtypes.ts index 4dd440a70..0fbe1fdd2 100644 --- a/interface/src/project/EMSESPtypes.ts +++ b/interface/src/project/EMSESPtypes.ts @@ -1,72 +1,72 @@ export interface EMSESPSettings { - tx_mode: number - tx_delay: number - ems_bus_id: number - syslog_enabled: boolean - syslog_level: number - syslog_mark_interval: number - syslog_host: string - syslog_port: number - master_thermostat: number - shower_timer: boolean - shower_alert: boolean - rx_gpio: number - tx_gpio: number - dallas_gpio: number - dallas_parasite: boolean - led_gpio: number - hide_led: boolean - notoken_api: boolean - analog_enabled: boolean - pbutton_gpio: number - trace_raw: boolean - board_profile: string + tx_mode: number; + tx_delay: number; + ems_bus_id: number; + syslog_enabled: boolean; + syslog_level: number; + syslog_mark_interval: number; + syslog_host: string; + syslog_port: number; + master_thermostat: number; + shower_timer: boolean; + shower_alert: boolean; + rx_gpio: number; + tx_gpio: number; + dallas_gpio: number; + dallas_parasite: boolean; + led_gpio: number; + hide_led: boolean; + notoken_api: boolean; + analog_enabled: boolean; + pbutton_gpio: number; + trace_raw: boolean; + board_profile: string; } export enum busConnectionStatus { BUS_STATUS_CONNECTED = 0, BUS_STATUS_TX_ERRORS = 1, - BUS_STATUS_OFFLINE = 2, + BUS_STATUS_OFFLINE = 2 } export interface EMSESPStatus { - status: busConnectionStatus - rx_received: number - tx_sent: number - rx_quality: number - tx_quality: number + status: busConnectionStatus; + rx_received: number; + tx_sent: number; + rx_quality: number; + tx_quality: number; } export interface Device { - id: number - type: string - brand: string - name: string - deviceid: number - productid: number - version: string + id: number; + type: string; + brand: string; + name: string; + deviceid: number; + productid: number; + version: string; } export interface Sensor { - no: number - id: string - temp: string + no: number; + id: string; + temp: string; } export interface EMSESPDevices { - devices: Device[] - sensors: Sensor[] + devices: Device[]; + sensors: Sensor[]; } export interface EMSESPDeviceData { - name: string - data: string[] + name: string; + data: string[]; } export interface DeviceValue { - id: number - data: string - uom: string - name: string - cmd: string + id: number; + data: string; + uom: string; + name: string; + cmd: string; } diff --git a/interface/src/project/ProjectMenu.tsx b/interface/src/project/ProjectMenu.tsx index ae35aa145..844c60664 100644 --- a/interface/src/project/ProjectMenu.tsx +++ b/interface/src/project/ProjectMenu.tsx @@ -1,12 +1,15 @@ -import React, { Component } from "react"; -import { Link, withRouter, RouteComponentProps } from "react-router-dom"; +import { Component } from 'react'; +import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; -import { List, ListItem, ListItemIcon, ListItemText } from "@material-ui/core"; +import { List, ListItem, ListItemIcon, ListItemText } from '@material-ui/core'; import TuneIcon from '@material-ui/icons/Tune'; -import DashboardIcon from "@material-ui/icons/Dashboard"; +import DashboardIcon from '@material-ui/icons/Dashboard'; -import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; +import { + withAuthenticatedContext, + AuthenticatedContextProps +} from '../authentication'; type ProjectProps = AuthenticatedContextProps & RouteComponentProps; @@ -16,13 +19,28 @@ class ProjectMenu extends Component { const path = this.props.match.url; return ( - + - + diff --git a/interface/src/project/ProjectRouting.tsx b/interface/src/project/ProjectRouting.tsx index 489642dad..4ab78ad2c 100644 --- a/interface/src/project/ProjectRouting.tsx +++ b/interface/src/project/ProjectRouting.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { Redirect, Switch } from 'react-router'; import { AuthenticatedRoute } from '../authentication'; @@ -7,24 +7,32 @@ import EMSESPDashboard from './EMSESPDashboard'; import EMSESPSettings from './EMSESPSettings'; class ProjectRouting extends Component { - render() { return ( - - - - { - /* - * The redirect below caters for the default project route and redirecting invalid paths. - * The "to" property must match one of the routes above for this to work correctly. - */ - } + + + + {/* + * The redirect below caters for the default project route and redirecting invalid paths. + * The "to" property must match one of the routes above for this to work correctly. + */} - ) + ); } - } export default ProjectRouting; diff --git a/interface/src/project/ValueForm.tsx b/interface/src/project/ValueForm.tsx index fabfb568a..bddc32ac2 100644 --- a/interface/src/project/ValueForm.tsx +++ b/interface/src/project/ValueForm.tsx @@ -1,64 +1,100 @@ import React, { RefObject } from 'react'; import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator'; -import { Dialog, DialogTitle, DialogContent, DialogActions, Box, Typography } from '@material-ui/core'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Box, + Typography +} from '@material-ui/core'; import { FormButton } from '../components'; import { DeviceValue } from './EMSESPtypes'; interface ValueFormProps { - devicevalue: DeviceValue; - onDoneEditing: () => void; - onCancelEditing: () => void; - handleValueChange: (data: keyof DeviceValue) => (event: React.ChangeEvent) => void; + devicevalue: DeviceValue; + onDoneEditing: () => void; + onCancelEditing: () => void; + handleValueChange: ( + data: keyof DeviceValue + ) => (event: React.ChangeEvent) => void; } class ValueForm extends React.Component { + formRef: RefObject = React.createRef(); - formRef: RefObject = React.createRef(); + submit = () => { + this.formRef.current.submit(); + }; - submit = () => { - this.formRef.current.submit(); + buildLabel = (devicevalue: DeviceValue) => { + if (devicevalue.uom === '' || !devicevalue.uom) { + return 'New value'; } + return 'New value (' + devicevalue.uom + ')'; + }; - buildLabel = (devicevalue: DeviceValue) => { - if ((devicevalue.uom === "") || (!devicevalue.uom)) { - return "New value"; - } - return "New value (" + devicevalue.uom + ")"; - } + render() { + const { + devicevalue, + handleValueChange, + onDoneEditing, + onCancelEditing + } = this.props; + return ( + + + + Change the {devicevalue.name} + + + + + + + Note: it may take a few seconds before the change is visible. + If nothing happens check the logs. + + + + - render() { - const { devicevalue, handleValueChange, onDoneEditing, onCancelEditing } = this.props; - return ( - - - Change the {devicevalue.name} - - - - - Note: it may take a few seconds before the change is visible. If nothing happens check the logs. - - - - - - Cancel - Done - - - - ); - } + + + Cancel + + + Done + + + + + ); + } } export default ValueForm; diff --git a/interface/src/security/GenerateToken.tsx b/interface/src/security/GenerateToken.tsx index c492d88d7..775ef92a6 100644 --- a/interface/src/security/GenerateToken.tsx +++ b/interface/src/security/GenerateToken.tsx @@ -1,5 +1,14 @@ import React, { Fragment } from 'react'; -import { Dialog, DialogTitle, DialogContent, DialogActions, Box, LinearProgress, Typography, TextField } from '@material-ui/core'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Box, + LinearProgress, + Typography, + TextField +} from '@material-ui/core'; import { FormButton } from '../components'; import { redirectingAuthorizedFetch } from '../authentication'; @@ -7,71 +16,105 @@ import { GENERATE_TOKEN_ENDPOINT } from '../api'; import { withSnackbar, WithSnackbarProps } from 'notistack'; interface GenerateTokenProps extends WithSnackbarProps { - username: string; - onClose: () => void; + username: string; + onClose: () => void; } interface GenerateTokenState { - token?: string; + token?: string; } -class GenerateToken extends React.Component { +class GenerateToken extends React.Component< + GenerateTokenProps, + GenerateTokenState +> { + state: GenerateTokenState = {}; - state: GenerateTokenState = {}; - - componentDidMount() { - const { username } = this.props; - redirectingAuthorizedFetch(GENERATE_TOKEN_ENDPOINT + "?" + new URLSearchParams({ username }), { method: 'GET' }) - .then(response => { - if (response.status === 200) { - return response.json(); - } else { - throw Error("Error generating token: " + response.status); - } - }).then(generatedToken => { - console.log(generatedToken); - this.setState({ token: generatedToken.token }); - }) - .catch(error => { - this.props.enqueueSnackbar(error.message || "Problem generating token", { variant: 'error' }); - }); - } - - render() { - const { onClose, username } = this.props; - const { token } = this.state; - return ( - - Token for: {username} - - {token ? - - - - The token below may be used to access the secured APIs, either as a Bearer authentication in the "Authorization" header or using the "access_token" query parameter. - - - - - - - : - - - - Generating token… - - - } - - - - Close - - - + componentDidMount() { + const { username } = this.props; + redirectingAuthorizedFetch( + GENERATE_TOKEN_ENDPOINT + '?' + new URLSearchParams({ username }), + { method: 'GET' } + ) + .then((response) => { + if (response.status === 200) { + return response.json(); + } else { + throw Error('Error generating token: ' + response.status); + } + }) + .then((generatedToken) => { + console.log(generatedToken); + this.setState({ token: generatedToken.token }); + }) + .catch((error) => { + this.props.enqueueSnackbar( + error.message || 'Problem generating token', + { variant: 'error' } ); - } + }); + } + + render() { + const { onClose, username } = this.props; + const { token } = this.state; + return ( + + + Token for: {username} + + + {token ? ( + + + + The token below may be used to access the secured APIs, either + as a Bearer authentication in the "Authorization" header or + using the "access_token" query parameter. + + + + + + + ) : ( + + + Generating token… + + )} + + + + Close + + + + ); + } } export default withSnackbar(GenerateToken); diff --git a/interface/src/security/ManageUsersController.tsx b/interface/src/security/ManageUsersController.tsx index 227c2bb18..02ed43da6 100644 --- a/interface/src/security/ManageUsersController.tsx +++ b/interface/src/security/ManageUsersController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { SECURITY_SETTINGS_ENDPOINT } from '../api'; import ManageUsersForm from './ManageUsersForm'; @@ -9,7 +14,6 @@ import { SecuritySettings } from './types'; type ManageUsersControllerProps = RestControllerProps; class ManageUsersController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,14 @@ class ManageUsersController extends Component { } + render={(formProps) => } /> - ) + ); } - } -export default restController(SECURITY_SETTINGS_ENDPOINT, ManageUsersController); +export default restController( + SECURITY_SETTINGS_ENDPOINT, + ManageUsersController +); diff --git a/interface/src/security/ManageUsersForm.tsx b/interface/src/security/ManageUsersForm.tsx index e861f7136..4f0ff084b 100644 --- a/interface/src/security/ManageUsersForm.tsx +++ b/interface/src/security/ManageUsersForm.tsx @@ -1,8 +1,18 @@ import React, { Fragment } from 'react'; import { ValidatorForm } from 'react-material-ui-form-validator'; -import { Table, TableBody, TableCell, TableHead, TableFooter, TableRow, withWidth, WithWidthProps, isWidthDown } from '@material-ui/core'; -import { Box, Button, Typography, } from '@material-ui/core'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableFooter, + TableRow, + withWidth, + WithWidthProps, + isWidthDown +} from '@material-ui/core'; +import { Box, Button, Typography } from '@material-ui/core'; import EditIcon from '@material-ui/icons/Edit'; import DeleteIcon from '@material-ui/icons/Delete'; @@ -13,8 +23,16 @@ import SaveIcon from '@material-ui/icons/Save'; import PersonAddIcon from '@material-ui/icons/PersonAdd'; import VpnKeyIcon from '@material-ui/icons/VpnKey'; -import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; -import { RestFormProps, FormActions, FormButton, extractEventValue } from '../components'; +import { + withAuthenticatedContext, + AuthenticatedContextProps +} from '../authentication'; +import { + RestFormProps, + FormActions, + FormButton, + extractEventValue +} from '../components'; import UserForm from './UserForm'; import { SecuritySettings, User } from './types'; @@ -30,16 +48,20 @@ function compareUsers(a: User, b: User) { return 0; } -type ManageUsersFormProps = RestFormProps & AuthenticatedContextProps & WithWidthProps; +type ManageUsersFormProps = RestFormProps & + AuthenticatedContextProps & + WithWidthProps; type ManageUsersFormState = { creating: boolean; user?: User; generateTokenFor?: string; -} - -class ManageUsersForm extends React.Component { +}; +class ManageUsersForm extends React.Component< + ManageUsersFormProps, + ManageUsersFormState +> { state: ManageUsersFormState = { creating: false }; @@ -48,38 +70,38 @@ class ManageUsersForm extends React.Component { - return !this.props.data.users.find(u => u.username === username); - } + return !this.props.data.users.find((u) => u.username === username); + }; noAdminConfigured = () => { - return !this.props.data.users.find(u => u.admin); - } + return !this.props.data.users.find((u) => u.admin); + }; removeUser = (user: User) => { const { data } = this.props; - const users = data.users.filter(u => u.username !== user.username); + const users = data.users.filter((u) => u.username !== user.username); this.props.setData({ ...data, users }); - } + }; closeGenerateToken = () => { this.setState({ generateTokenFor: undefined }); - } + }; generateToken = (user: User) => { this.setState({ generateTokenFor: user.username }); - } + }; startEditingUser = (user: User) => { this.setState({ @@ -92,13 +114,13 @@ class ManageUsersForm extends React.Component { const { user } = this.state; if (user) { const { data } = this.props; - const users = data.users.filter(u => u.username !== user.username); + const users = data.users.filter((u) => u.username !== user.username); users.push(user); this.props.setData({ ...data, users }); this.setState({ @@ -107,14 +129,18 @@ class ManageUsersForm extends React.Component (event: React.ChangeEvent) => { - this.setState({ user: { ...this.state.user!, [name]: extractEventValue(event) } }); + handleUserValueChange = (name: keyof User) => ( + event: React.ChangeEvent + ) => { + this.setState({ + user: { ...this.state.user!, [name]: extractEventValue(event) } + }); }; onSubmit = () => { this.props.saveData(); this.props.authenticatedContext.refresh(); - } + }; render() { const { width, data } = this.props; @@ -122,7 +148,10 @@ class ManageUsersForm extends React.Component -
+
Username @@ -131,7 +160,7 @@ class ManageUsersForm extends React.Component - {data.users.sort(compareUsers).map(user => ( + {data.users.sort(compareUsers).map((user) => ( {user.username} @@ -140,51 +169,79 @@ class ManageUsersForm extends React.Component : } - this.generateToken(user)}> + this.generateToken(user)} + > - this.removeUser(user)}> + this.removeUser(user)} + > - this.startEditingUser(user)}> + this.startEditingUser(user)} + > ))} - + -
- { - this.noAdminConfigured() && - ( - - - You must have at least one admin user configured. - - - ) - } + {this.noAdminConfigured() && ( + + + You must have at least one admin user configured. + + + )} - } variant="contained" color="primary" type="submit" disabled={this.noAdminConfigured()}> + } + variant="contained" + color="primary" + type="submit" + disabled={this.noAdminConfigured()} + > Save - { - generateTokenFor && - } - { - user && + {generateTokenFor && ( + + )} + {user && ( - } + )}
); } - } export default withAuthenticatedContext(withWidth()(ManageUsersForm)); diff --git a/interface/src/security/Security.tsx b/interface/src/security/Security.tsx index 4e99769b6..cab47a709 100644 --- a/interface/src/security/Security.tsx +++ b/interface/src/security/Security.tsx @@ -1,9 +1,12 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; -import { AuthenticatedContextProps, AuthenticatedRoute } from '../authentication'; +import { + AuthenticatedContextProps, + AuthenticatedRoute +} from '../authentication'; import { MenuAppBar } from '../components'; import ManageUsersController from './ManageUsersController'; @@ -12,25 +15,36 @@ import SecuritySettingsController from './SecuritySettingsController'; type SecurityProps = AuthenticatedContextProps & RouteComponentProps; class Security extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; render() { return ( - + this.handleTabChange(path)} + variant="fullWidth" + > - - + + - ) + ); } } diff --git a/interface/src/security/SecuritySettingsController.tsx b/interface/src/security/SecuritySettingsController.tsx index 30e52f1a3..e98885011 100644 --- a/interface/src/security/SecuritySettingsController.tsx +++ b/interface/src/security/SecuritySettingsController.tsx @@ -1,6 +1,11 @@ import React, { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { SECURITY_SETTINGS_ENDPOINT } from '../api'; import SecuritySettingsForm from './SecuritySettingsForm'; @@ -9,7 +14,6 @@ import { SecuritySettings } from './types'; type SecuritySettingsControllerProps = RestControllerProps; class SecuritySettingsController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,14 @@ class SecuritySettingsController extends Component } + render={(formProps) => } /> ); } - } -export default restController(SECURITY_SETTINGS_ENDPOINT, SecuritySettingsController); +export default restController( + SECURITY_SETTINGS_ENDPOINT, + SecuritySettingsController +); diff --git a/interface/src/security/SecuritySettingsForm.tsx b/interface/src/security/SecuritySettingsForm.tsx index 2c9d12779..fa770f8ce 100644 --- a/interface/src/security/SecuritySettingsForm.tsx +++ b/interface/src/security/SecuritySettingsForm.tsx @@ -4,19 +4,27 @@ import { ValidatorForm } from 'react-material-ui-form-validator'; import { Box, Typography } from '@material-ui/core'; import SaveIcon from '@material-ui/icons/Save'; -import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; -import { RestFormProps, PasswordValidator, FormActions, FormButton } from '../components'; +import { + withAuthenticatedContext, + AuthenticatedContextProps +} from '../authentication'; +import { + RestFormProps, + PasswordValidator, + FormActions, + FormButton +} from '../components'; import { SecuritySettings } from './types'; -type SecuritySettingsFormProps = RestFormProps & AuthenticatedContextProps; +type SecuritySettingsFormProps = RestFormProps & + AuthenticatedContextProps; class SecuritySettingsForm extends React.Component { - onSubmit = () => { this.props.saveData(); this.props.authenticatedContext.refresh(); - } + }; render() { const { data, handleValueChange } = this.props; @@ -24,7 +32,10 @@ class SecuritySettingsForm extends React.Component { { onChange={handleValueChange('jwt_secret')} margin="normal" /> - + - The Super User password is used to sign authentication tokens and is also the Console's `su` password. If you modify this all users will be signed out. + The Super User password is used to sign authentication tokens and is + also the Console's `su` password. If you modify this all users will + be signed out. - } variant="contained" color="primary" type="submit"> + } + variant="contained" + color="primary" + type="submit" + > Save ); } - } export default withAuthenticatedContext(SecuritySettingsForm); diff --git a/interface/src/security/UserForm.tsx b/interface/src/security/UserForm.tsx index dd16674de..db7d140a5 100644 --- a/interface/src/security/UserForm.tsx +++ b/interface/src/security/UserForm.tsx @@ -1,9 +1,19 @@ import React, { RefObject } from 'react'; import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator'; -import { Dialog, DialogTitle, DialogContent, DialogActions, Checkbox } from '@material-ui/core'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Checkbox +} from '@material-ui/core'; -import { PasswordValidator, BlockFormControlLabel, FormButton } from '../components'; +import { + PasswordValidator, + BlockFormControlLabel, + FormButton +} from '../components'; import { User } from './types'; @@ -11,33 +21,67 @@ interface UserFormProps { creating: boolean; user: User; uniqueUsername: (value: any) => boolean; - handleValueChange: (name: keyof User) => (event: React.ChangeEvent) => void; + handleValueChange: ( + name: keyof User + ) => (event: React.ChangeEvent) => void; onDoneEditing: () => void; onCancelEditing: () => void; } class UserForm extends React.Component { - formRef: RefObject = React.createRef(); componentDidMount() { - ValidatorForm.addValidationRule('uniqueUsername', this.props.uniqueUsername); + ValidatorForm.addValidationRule( + 'uniqueUsername', + this.props.uniqueUsername + ); } submit = () => { this.formRef.current.submit(); - } + }; render() { - const { user, creating, handleValueChange, onDoneEditing, onCancelEditing } = this.props; + const { + user, + creating, + handleValueChange, + onDoneEditing, + onCancelEditing + } = this.props; return ( -

- {creating ? 'Add' : 'Modify'} User + + + {creating ? 'Add' : 'Modify'} User + { /> { /> - + Cancel - + Done diff --git a/interface/src/security/types.ts b/interface/src/security/types.ts index 6f7562217..87fb64418 100644 --- a/interface/src/security/types.ts +++ b/interface/src/security/types.ts @@ -1,14 +1,14 @@ export interface User { - username: string - password: string - admin: boolean + username: string; + password: string; + admin: boolean; } export interface SecuritySettings { - users: User[] - jwt_secret: string + users: User[]; + jwt_secret: string; } export interface GeneratedToken { - token: string + token: string; } diff --git a/interface/src/serviceWorker.ts b/interface/src/serviceWorker.ts index 8a502cb66..d9941b314 100644 --- a/interface/src/serviceWorker.ts +++ b/interface/src/serviceWorker.ts @@ -16,46 +16,46 @@ const isLocalhost = Boolean( window.location.hostname === '[::1]' || // 127.0.0.0/8 are considered localhost for IPv4. window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/, - ), -) + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); type Config = { - onSuccess?: (registration: ServiceWorkerRegistration) => void - onUpdate?: (registration: ServiceWorkerRegistration) => void -} + onSuccess?: (registration: ServiceWorkerRegistration) => void; + onUpdate?: (registration: ServiceWorkerRegistration) => void; +}; export function register(config?: Config) { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href) + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); if (publicUrl.origin !== window.location.origin) { // Our service worker won't work if PUBLIC_URL is on a different origin // from what our page is served on. This might happen if a CDN is used to // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return + return; } window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js` + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; if (isLocalhost) { // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config) + checkValidServiceWorker(swUrl, config); // Add some additional logging to localhost, pointing developers to the // service worker/PWA documentation. navigator.serviceWorker.ready.then(() => { console.log( 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA', - ) - }) + 'worker. To learn more, visit https://bit.ly/CRA-PWA' + ); + }); } else { // Is not localhost. Just register service worker - registerValidSW(swUrl, config) + registerValidSW(swUrl, config); } - }) + }); } } @@ -64,9 +64,9 @@ function registerValidSW(swUrl: string, config?: Config) { .register(swUrl) .then((registration) => { registration.onupdatefound = () => { - const installingWorker = registration.installing + const installingWorker = registration.installing; if (installingWorker == null) { - return + return; } installingWorker.onstatechange = () => { if (installingWorker.state === 'installed') { @@ -76,41 +76,41 @@ function registerValidSW(swUrl: string, config?: Config) { // content until all client tabs are closed. console.log( 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.', - ) + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + ); // Execute callback if (config && config.onUpdate) { - config.onUpdate(registration) + config.onUpdate(registration); } } else { // At this point, everything has been precached. // It's the perfect time to display a // "Content is cached for offline use." message. - console.log('Content is cached for offline use.') + console.log('Content is cached for offline use.'); // Execute callback if (config && config.onSuccess) { - config.onSuccess(registration) + config.onSuccess(registration); } } } - } - } + }; + }; }) .catch((error) => { - console.error('Error during service worker registration:', error) - }) + console.error('Error during service worker registration:', error); + }); } function checkValidServiceWorker(swUrl: string, config?: Config) { // Check if the service worker can be found. If it can't reload the page. fetch(swUrl, { - headers: { 'Service-Worker': 'script' }, + headers: { 'Service-Worker': 'script' } }) .then((response) => { // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type') + const contentType = response.headers.get('content-type'); if ( response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1) @@ -118,25 +118,25 @@ function checkValidServiceWorker(swUrl: string, config?: Config) { // No service worker found. Probably a different app. Reload the page. navigator.serviceWorker.ready.then((registration) => { registration.unregister().then(() => { - window.location.reload() - }) - }) + window.location.reload(); + }); + }); } else { // Service worker found. Proceed as normal. - registerValidSW(swUrl, config) + registerValidSW(swUrl, config); } }) .catch(() => { console.log( - 'No internet connection found. App is running in offline mode.', - ) - }) + 'No internet connection found. App is running in offline mode.' + ); + }); } export function unregister() { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready.then((registration) => { - registration.unregister() - }) + registration.unregister(); + }); } } diff --git a/interface/src/setupProxy.js b/interface/src/setupProxy.js index 78ac89244..7c34fb4c6 100644 --- a/interface/src/setupProxy.js +++ b/interface/src/setupProxy.js @@ -1,4 +1,4 @@ -const { createProxyMiddleware } = require('http-proxy-middleware') +const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = function (app) { app.use( @@ -6,7 +6,7 @@ module.exports = function (app) { createProxyMiddleware({ target: 'http://localhost:3080', secure: false, - changeOrigin: true, - }), - ) -} + changeOrigin: true + }) + ); +}; diff --git a/interface/src/system/OTASettingsController.tsx b/interface/src/system/OTASettingsController.tsx index 3fb0788bf..0ad07d6ce 100644 --- a/interface/src/system/OTASettingsController.tsx +++ b/interface/src/system/OTASettingsController.tsx @@ -1,6 +1,11 @@ import React, { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { OTA_SETTINGS_ENDPOINT } from '../api'; import OTASettingsForm from './OTASettingsForm'; @@ -9,7 +14,6 @@ import { OTASettings } from './types'; type OTASettingsControllerProps = RestControllerProps; class OTASettingsController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,11 @@ class OTASettingsController extends Component { } + render={(formProps) => } /> ); } - } export default restController(OTA_SETTINGS_ENDPOINT, OTASettingsController); diff --git a/interface/src/system/OTASettingsForm.tsx b/interface/src/system/OTASettingsForm.tsx index 1e88ad6df..c511e9db9 100644 --- a/interface/src/system/OTASettingsForm.tsx +++ b/interface/src/system/OTASettingsForm.tsx @@ -4,7 +4,13 @@ import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator'; import { Checkbox } from '@material-ui/core'; import SaveIcon from '@material-ui/icons/Save'; -import { RestFormProps, BlockFormControlLabel, PasswordValidator, FormButton, FormActions } from '../components'; +import { + RestFormProps, + BlockFormControlLabel, + PasswordValidator, + FormButton, + FormActions +} from '../components'; import { isIP, isHostname, or } from '../validators'; import { OTASettings } from './types'; @@ -12,7 +18,6 @@ import { OTASettings } from './types'; type OTASettingsFormProps = RestFormProps; class OTASettingsForm extends React.Component { - componentDidMount() { ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname)); } @@ -25,14 +30,24 @@ class OTASettingsForm extends React.Component { control={ } label="Enable OTA Updates" /> { /> { margin="normal" /> - } variant="contained" color="primary" type="submit"> + } + variant="contained" + color="primary" + type="submit" + > Save diff --git a/interface/src/system/System.tsx b/interface/src/system/System.tsx index 671d5e073..8b41c5deb 100644 --- a/interface/src/system/System.tsx +++ b/interface/src/system/System.tsx @@ -1,22 +1,27 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; import { WithFeaturesProps, withFeatures } from '../features/FeaturesContext'; -import { withAuthenticatedContext, AuthenticatedContextProps, AuthenticatedRoute } from '../authentication'; +import { + withAuthenticatedContext, + AuthenticatedContextProps, + AuthenticatedRoute +} from '../authentication'; import { MenuAppBar } from '../components'; import SystemStatusController from './SystemStatusController'; import OTASettingsController from './OTASettingsController'; import UploadFirmwareController from './UploadFirmwareController'; -type SystemProps = AuthenticatedContextProps & RouteComponentProps & WithFeaturesProps; +type SystemProps = AuthenticatedContextProps & + RouteComponentProps & + WithFeaturesProps; class System extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; @@ -24,27 +29,51 @@ class System extends Component { const { authenticatedContext, features } = this.props; return ( - + this.handleTabChange(path)} + variant="fullWidth" + > {features.ota && ( - + )} {features.upload_firmware && ( - + )} - + {features.ota && ( - + )} {features.upload_firmware && ( - + )} - ) + ); } } diff --git a/interface/src/system/SystemStatusController.tsx b/interface/src/system/SystemStatusController.tsx index c0537c43b..4d77ef1ab 100644 --- a/interface/src/system/SystemStatusController.tsx +++ b/interface/src/system/SystemStatusController.tsx @@ -1,6 +1,11 @@ import React, { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { SYSTEM_STATUS_ENDPOINT } from '../api'; import SystemStatusForm from './SystemStatusForm'; @@ -9,7 +14,6 @@ import { SystemStatus } from './types'; type SystemStatusControllerProps = RestControllerProps; class SystemStatusController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,11 @@ class SystemStatusController extends Component { } + render={(formProps) => } /> ); } - } export default restController(SYSTEM_STATUS_ENDPOINT, SystemStatusController); diff --git a/interface/src/system/UploadFirmwareController.tsx b/interface/src/system/UploadFirmwareController.tsx index 16d21ffb4..931ae9eaf 100644 --- a/interface/src/system/UploadFirmwareController.tsx +++ b/interface/src/system/UploadFirmwareController.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { SectionContent } from '../components'; import { UPLOAD_FIRMWARE_ENDPOINT } from '../api'; @@ -12,8 +12,10 @@ interface UploadFirmwareControllerState { progress?: ProgressEvent; } -class UploadFirmwareController extends Component { - +class UploadFirmwareController extends Component< + WithSnackbarProps, + UploadFirmwareControllerState +> { state: UploadFirmwareControllerState = { xhr: undefined, progress: undefined @@ -25,47 +27,67 @@ class UploadFirmwareController extends Component { this.setState({ progress }); - } + }; uploadFile = (file: File) => { if (this.state.xhr) { return; } - var xhr = new XMLHttpRequest(); + const xhr = new XMLHttpRequest(); this.setState({ xhr }); - redirectingAuthorizedUpload(xhr, UPLOAD_FIRMWARE_ENDPOINT, file, this.updateProgress).then(() => { - if (xhr.status !== 200) { - throw Error("Invalid status code: " + xhr.status); - } - this.props.enqueueSnackbar("Activating new firmware", { variant: 'success' }); - this.setState({ xhr: undefined, progress: undefined }); - }).catch((error: Error) => { - if (error.name === 'AbortError') { - this.props.enqueueSnackbar("Upload cancelled by user", { variant: 'warning' }); - } else { - const errorMessage = error.name === 'UploadError' ? "Error during upload" : (error.message || "Unknown error"); - this.props.enqueueSnackbar("Problem uploading: " + errorMessage, { variant: 'error' }); + redirectingAuthorizedUpload( + xhr, + UPLOAD_FIRMWARE_ENDPOINT, + file, + this.updateProgress + ) + .then(() => { + if (xhr.status !== 200) { + throw Error('Invalid status code: ' + xhr.status); + } + this.props.enqueueSnackbar('Activating new firmware', { + variant: 'success' + }); this.setState({ xhr: undefined, progress: undefined }); - } - }); - } + }) + .catch((error: Error) => { + if (error.name === 'AbortError') { + this.props.enqueueSnackbar('Upload cancelled by user', { + variant: 'warning' + }); + } else { + const errorMessage = + error.name === 'UploadError' + ? 'Error during upload' + : error.message || 'Unknown error'; + this.props.enqueueSnackbar('Problem uploading: ' + errorMessage, { + variant: 'error' + }); + this.setState({ xhr: undefined, progress: undefined }); + } + }); + }; cancelUpload = () => { if (this.state.xhr) { this.state.xhr.abort(); this.setState({ xhr: undefined, progress: undefined }); } - } + }; render() { const { xhr, progress } = this.state; return ( - + ); } - } export default withSnackbar(UploadFirmwareController); diff --git a/interface/src/system/UploadFirmwareForm.tsx b/interface/src/system/UploadFirmwareForm.tsx index 0b9fade75..7fef79691 100644 --- a/interface/src/system/UploadFirmwareForm.tsx +++ b/interface/src/system/UploadFirmwareForm.tsx @@ -1,6 +1,6 @@ -import React, { Fragment } from "react"; -import { SingleUpload } from "../components"; -import { Box } from "@material-ui/core"; +import React, { Fragment } from 'react'; +import { SingleUpload } from '../components'; +import { Box } from '@material-ui/core'; interface UploadFirmwareFormProps { uploading: boolean; @@ -22,8 +22,10 @@ class UploadFirmwareForm extends React.Component { return ( - Upload a new firmware file (.bin or .bin.gz) below to replace the existing firmware. -

This can take up to a minute. Wait until you see "Activating new Firmware" and EMS-ESP will then automatically restart. + Upload a new firmware file (.bin or .bin.gz) below to replace the + existing firmware. +

This can take up to a minute. Wait until you see "Activating + new Firmware" and EMS-ESP will then automatically restart.
boolean) => (value: any) => - !value || validator(value) + !value || validator(value); -export default OPTIONAL +export default OPTIONAL; diff --git a/interface/src/validators/or.ts b/interface/src/validators/or.ts index 3f345fbe5..eb189e65b 100644 --- a/interface/src/validators/or.ts +++ b/interface/src/validators/or.ts @@ -1,8 +1,8 @@ const OR = ( validator1: (value: any) => boolean, - validator2: (value: any) => boolean, + validator2: (value: any) => boolean ) => { - return (value: any) => validator1(value) || validator2(value) -} + return (value: any) => validator1(value) || validator2(value); +}; -export default OR +export default OR;