auto formatting

This commit is contained in:
proddy
2021-05-07 10:15:29 +02:00
parent d15aa79d18
commit c6a40d2125
35 changed files with 633 additions and 570 deletions

View File

@@ -1,37 +1,51 @@
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')
const path = require('path');
const fs = require('fs');
const path = require('path')
const fs = require('fs')
module.exports = function override(config, env) {
if (env === "production") {
if (env === 'production') {
// 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));
config.plugins = config.plugins.filter(plugin => !(plugin instanceof WorkboxWebpackPlugin.GenerateSW));
config.plugins = config.plugins.filter(
(plugin) => !(plugin instanceof ManifestPlugin),
)
config.plugins = config.plugins.filter(
(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";
miniCssExtractPlugin.options.chunkFilename = "css/[id].[contenthash:4].c.css";
const miniCssExtractPlugin = config.plugins.find(
(plugin) => plugin instanceof MiniCssExtractPlugin,
)
miniCssExtractPlugin.options.filename = 'css/[id].[contenthash:4].css'
miniCssExtractPlugin.options.chunkFilename =
'css/[id].[contenthash:4].c.css'
// build progmem data files
config.plugins.push(new ProgmemGenerator({ outputPath: "../lib/framework/WWWData.h", bytesPerLine: 20 }));
config.plugins.push(
new ProgmemGenerator({
outputPath: '../lib/framework/WWWData.h',
bytesPerLine: 20,
}),
)
// add compression plugin, compress javascript
config.plugins.push(new CompressionPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: /\.(js)$/,
deleteOriginalAssets: true
}));
config.plugins.push(
new CompressionPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: /\.(js)$/,
deleteOriginalAssets: true,
}),
)
}
return config;
return config
}

View File

@@ -1,91 +1,108 @@
const { resolve, relative, sep } = require('path');
const { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } = require('fs');
var zlib = require('zlib');
var mime = require('mime-types');
const { resolve, relative, sep } = require('path')
const {
readdirSync,
existsSync,
unlinkSync,
readFileSync,
createWriteStream,
} = require('fs')
var zlib = require('zlib')
var mime = require('mime-types')
const ARDUINO_INCLUDES = "#include <Arduino.h>\n\n";
const ARDUINO_INCLUDES = '#include <Arduino.h>\n\n'
function getFilesSync(dir, files = []) {
readdirSync(dir, { withFileTypes: true }).forEach(entry => {
const entryPath = resolve(dir, entry.name);
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
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 {
constructor(options = {}) {
const { outputPath, bytesPerLine = 20, indent = " ", includes = ARDUINO_INCLUDES } = options;
this.options = { outputPath, bytesPerLine, indent, includes };
const {
outputPath,
bytesPerLine = 20,
indent = ' ',
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 writeStream = cleanAndOpen(resolve(compilation.options.context, outputPath));
const { outputPath, bytesPerLine, indent, includes } = this.options
const fileInfo = []
const writeStream = cleanAndOpen(
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++;
});
writeStream.write(
'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()));
});
writeFile(
relativeFilePath,
coherseToBuffer(assets[relativeFilePath].source()),
)
})
}
const generateWWWClass = () => {
@@ -93,30 +110,39 @@ class ProgmemGenerator {
class WWWData {
${indent}public:
${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
${fileInfo.map(file => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size});`).join('\n')}
${indent.repeat(
2,
)}static void registerRoutes(RouteRegistrationHandler handler) {
${fileInfo
.map(
(file) =>
`${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${
file.variable
}, ${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

View File

@@ -9,4 +9,4 @@
"start_url": "/",
"display": "fullscreen",
"orientation": "any"
}
}

View File

@@ -3,20 +3,26 @@
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/li.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215;
src: local('Roboto Light'), local('Roboto-Light'),
url(../fonts/li.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215;
src: local('Roboto'), local('Roboto-Regular'),
url(../fonts/re.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/me.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215;
}
src: local('Roboto Medium'), local('Roboto-Medium'),
url(../fonts/me.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215;
}

View File

@@ -1,5 +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;
return (
provision_mode === APProvisionMode.AP_MODE_ALWAYS ||
provision_mode === APProvisionMode.AP_MODE_DISCONNECTED
)
}

View File

@@ -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'
}
};
}

View File

@@ -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
}

View File

@@ -1,23 +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 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";
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'

View File

@@ -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;
if (httpRoot) {
return httpRoot + endpointPath;
}
const location = window.location;
return location.protocol + "//" + location.host + endpointPath;
const httpRoot = process.env.REACT_APP_HTTP_ROOT
if (httpRoot) {
return httpRoot + endpointPath
}
const location = window.location
return location.protocol + '//' + location.host + endpointPath
}
function calculateWebSocketRoot(webSocketPath: string) {
const webSocketRoot = process.env.REACT_APP_WEB_SOCKET_ROOT;
if (webSocketRoot) {
return webSocketRoot + webSocketPath;
}
const location = window.location;
const webProtocol = location.protocol === "https:" ? "wss:" : "ws:";
return webProtocol + "//" + location.host + webSocketPath;
const webSocketRoot = process.env.REACT_APP_WEB_SOCKET_ROOT
if (webSocketRoot) {
return webSocketRoot + webSocketPath
}
const location = window.location
const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
return webProtocol + '//' + location.host + webSocketPath
}

View File

@@ -1,114 +1,129 @@
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): H.LocationDescriptorObject {
const signInPathname = getStorage().getItem(SIGN_IN_PATHNAME);
const signInSearch = getStorage().getItem(SIGN_IN_SEARCH);
clearLoginRedirect();
export function fetchLoginRedirect(
features: Features,
): H.LocationDescriptorObject {
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,
}
}
/**
* Wraps the normal fetch routine with one with provides the access token if present.
*/
export function authorizedFetch(url: RequestInfo, params?: RequestInit): Promise<Response> {
const accessToken = getStorage().getItem(ACCESS_TOKEN);
export function authorizedFetch(
url: RequestInfo,
params?: RequestInit,
): Promise<Response> {
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)
}
/**
* fetch() does not yet support upload progress, this wrapper allows us to configure the xhr request
* for a single file upload and takes care of adding the Authorization header and redirecting on
* fetch() does not yet support upload progress, this wrapper allows us to configure the xhr request
* for a single file upload and takes care of adding the Authorization header and redirecting on
* authorization errors as we do for normal fetch operations.
*/
export function redirectingAuthorizedUpload(xhr: XMLHttpRequest, url: string, file: File, onProgress: (event: ProgressEvent<EventTarget>) => void): Promise<void> {
export function redirectingAuthorizedUpload(
xhr: XMLHttpRequest,
url: string,
file: File,
onProgress: (event: ProgressEvent<EventTarget>) => void,
): Promise<void> {
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<EventTarget>) {
reject(new DOMException('Error', 'UploadError'));
};
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)
})
}
/**
* Wraps the normal fetch routene which redirects on 401 response.
*/
export function redirectingAuthorizedFetch(url: RequestInfo, params?: RequestInit): Promise<Response> {
export function redirectingAuthorizedFetch(
url: RequestInfo,
params?: RequestInit,
): Promise<Response> {
return new Promise<Response>((resolve, reject) => {
authorizedFetch(url, params).then(response => {
if (response.status === 401 || response.status === 403) {
history.push("/unauthorized");
} else {
resolve(response);
}
}).catch(error => {
reject(error);
});
});
authorizedFetch(url, params)
.then((response) => {
if (response.status === 401 || response.status === 403) {
history.push('/unauthorized')
} else {
resolve(response)
}
})
.catch((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()
}

View File

@@ -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'

View File

@@ -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'

View File

@@ -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
}

View File

@@ -1,4 +1,4 @@
import { createBrowserHistory } from 'history';
import { createBrowserHistory } from 'history'
export default createBrowserHistory({
/* pass a configuration object here if needed */

View File

@@ -1,56 +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) => {
export const mqttStatusHighlight = (
{ enabled, connected }: MqttStatus,
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) => {
export const mqttPublishHighlight = (
{ mqtt_fails }: MqttStatus,
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
}

View File

@@ -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
}

View File

@@ -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'
}
};
}

View File

@@ -1,22 +1,23 @@
import { WiFiNetwork, WiFiEncryptionType } from "./types";
import { WiFiNetwork, WiFiEncryptionType } from './types'
export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) => encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN;
export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) =>
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'
}
}

View File

@@ -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
}

View File

@@ -1,26 +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;
export const isNtpActive = ({ status }: NTPStatus) =>
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'
}
}

View File

@@ -1,45 +1,43 @@
import parseMilliseconds from 'parse-ms';
import parseMilliseconds from 'parse-ms'
const LOCALE_FORMAT = new Intl.DateTimeFormat(
[...window.navigator.languages],
{
day: 'numeric',
month: 'short',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hour12: false
}
);
const LOCALE_FORMAT = new Intl.DateTimeFormat([...window.navigator.languages], {
day: 'numeric',
month: 'short',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
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)
var 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: string = 's') =>
` ${count} ${noun}${count !== 1 ? suffix : ''} `

View File

@@ -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
}

View File

@@ -1,40 +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;
export const isConnected = ({ status }: EMSESPStatus) =>
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
}

View File

@@ -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,
id: number
data: string
uom: string
name: string
cmd: string
}

View File

@@ -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
}

View File

@@ -12,64 +12,61 @@
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
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}$/
)
);
// [::1] is the IPv6 localhost address.
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}$/,
),
)
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)
}
});
})
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
const installingWorker = registration.installing
if (installingWorker == null) {
return;
return
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
@@ -79,67 +76,67 @@ 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)
})
.catch(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 => {
.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)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
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();
});
navigator.serviceWorker.ready.then((registration) => {
registration.unregister()
})
}
}

View File

@@ -1,12 +1,12 @@
const { createProxyMiddleware } = require('http-proxy-middleware');
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function (app) {
app.use(
'/rest/*',
createProxyMiddleware({
target: 'http://localhost:3080',
secure: false,
changeOrigin: true
})
);
};
app.use(
'/rest/*',
createProxyMiddleware({
target: 'http://localhost:3080',
secure: false,
changeOrigin: true,
}),
)
}

View File

@@ -1,37 +1,37 @@
export enum EspPlatform {
ESP8266 = "esp8266",
ESP32 = "esp32"
ESP8266 = 'esp8266',
ESP32 = 'esp32',
}
interface ESPSystemStatus {
esp_platform: EspPlatform;
max_alloc_heap: number;
cpu_freq_mhz: number;
free_heap: number;
sdk_version: string;
flash_chip_size: number;
flash_chip_speed: number;
fs_used: number;
fs_total: number;
uptime: string;
free_mem: number;
esp_platform: EspPlatform
max_alloc_heap: number
cpu_freq_mhz: number
free_heap: number
sdk_version: string
flash_chip_size: number
flash_chip_speed: number
fs_used: number
fs_total: number
uptime: string
free_mem: number
}
export interface ESP32SystemStatus extends ESPSystemStatus {
esp_platform: EspPlatform.ESP32;
psram_size: number;
free_psram: number;
esp_platform: EspPlatform.ESP32
psram_size: number
free_psram: number
}
export interface ESP8266SystemStatus extends ESPSystemStatus {
esp_platform: EspPlatform.ESP8266;
heap_fragmentation: number;
esp_platform: EspPlatform.ESP8266
heap_fragmentation: number
}
export type SystemStatus = ESP8266SystemStatus | ESP32SystemStatus;
export type SystemStatus = ESP8266SystemStatus | ESP32SystemStatus
export interface OTASettings {
enabled: boolean;
port: number;
password: string;
enabled: boolean
port: number
password: string
}

View File

@@ -1,5 +1,5 @@
export { default as isHostname } from './isHostname';
export { default as isIP } from './isIP';
export { default as optional } from './optional';
export { default as or } from './or';
export { default as isPath } from './isPath';
export { default as isHostname } from './isHostname'
export { default as isIP } from './isIP'
export { default as optional } from './optional'
export { default as or } from './or'
export { default as isPath } from './isPath'

View File

@@ -2,5 +2,7 @@ const hostnameLengthRegex = /^.{0,48}$/
const hostnamePatternRegex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/
export default function isHostname(hostname: string) {
return hostnameLengthRegex.test(hostname) && hostnamePatternRegex.test(hostname);
return (
hostnameLengthRegex.test(hostname) && hostnamePatternRegex.test(hostname)
)
}

View File

@@ -1,5 +1,5 @@
const ipAddressRegexp = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
export default function isIp(ipAddress: string) {
return ipAddressRegexp.test(ipAddress);
}
return ipAddressRegexp.test(ipAddress)
}

View File

@@ -2,5 +2,5 @@ const pathLengthRegex = /^[^.]{0,108}$/
const pathPatternRegex = /^([a-zA-Z0-9_][a-zA-Z0-9/_-]*[a-zA-Z0-9_])$/
export default function isPath(path: string) {
return pathLengthRegex.test(path) && pathPatternRegex.test(path);
}
return pathLengthRegex.test(path) && pathPatternRegex.test(path)
}

View File

@@ -1,3 +1,4 @@
const OPTIONAL = (validator: (value: any) => boolean) => (value: any) => !value || validator(value);
const OPTIONAL = (validator: (value: any) => boolean) => (value: any) =>
!value || validator(value)
export default OPTIONAL;
export default OPTIONAL

View File

@@ -1,6 +1,8 @@
const OR = (validator1: (value: any) => boolean, validator2: (value: any) => boolean) => {
return (value: any) => validator1(value) || validator2(value);
};
export default OR;
const OR = (
validator1: (value: any) => boolean,
validator2: (value: any) => boolean,
) => {
return (value: any) => validator1(value) || validator2(value)
}
export default OR

View File

@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
@@ -20,7 +16,5 @@
"jsx": "react-jsx",
"noFallthroughCasesInSwitch": true
},
"include": [
"src"
]
}
"include": ["src"]
}