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 ManifestPlugin = require('webpack-manifest-plugin')
const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CompressionPlugin = require('compression-webpack-plugin'); const CompressionPlugin = require('compression-webpack-plugin')
const ProgmemGenerator = require('./progmem-generator.js'); const ProgmemGenerator = require('./progmem-generator.js')
const path = require('path'); const path = require('path')
const fs = require('fs'); const fs = require('fs')
module.exports = function override(config, env) { 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 // rename the output file, we need it's path to be short for LittleFS
config.output.filename = 'js/[id].[chunkhash:4].js'; config.output.filename = 'js/[id].[chunkhash:4].js'
config.output.chunkFilename = 'js/[id].[chunkhash:4].js'; config.output.chunkFilename = 'js/[id].[chunkhash:4].js'
// take out the manifest and service worker plugins // take out the manifest and service worker plugins
config.plugins = config.plugins.filter(plugin => !(plugin instanceof ManifestPlugin)); config.plugins = config.plugins.filter(
config.plugins = config.plugins.filter(plugin => !(plugin instanceof WorkboxWebpackPlugin.GenerateSW)); (plugin) => !(plugin instanceof ManifestPlugin),
)
config.plugins = config.plugins.filter(
(plugin) => !(plugin instanceof WorkboxWebpackPlugin.GenerateSW),
)
// shorten css filenames // shorten css filenames
const miniCssExtractPlugin = config.plugins.find((plugin) => plugin instanceof MiniCssExtractPlugin); const miniCssExtractPlugin = config.plugins.find(
miniCssExtractPlugin.options.filename = "css/[id].[contenthash:4].css"; (plugin) => plugin instanceof MiniCssExtractPlugin,
miniCssExtractPlugin.options.chunkFilename = "css/[id].[contenthash:4].c.css"; )
miniCssExtractPlugin.options.filename = 'css/[id].[contenthash:4].css'
miniCssExtractPlugin.options.chunkFilename =
'css/[id].[contenthash:4].c.css'
// build progmem data files // 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 // add compression plugin, compress javascript
config.plugins.push(new CompressionPlugin({ config.plugins.push(
filename: "[path].gz[query]", new CompressionPlugin({
algorithm: "gzip", filename: '[path].gz[query]',
test: /\.(js)$/, algorithm: 'gzip',
deleteOriginalAssets: true test: /\.(js)$/,
})); deleteOriginalAssets: true,
}),
)
} }
return config; return config
} }

View File

@@ -1,91 +1,108 @@
const { resolve, relative, sep } = require('path'); const { resolve, relative, sep } = require('path')
const { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } = require('fs'); const {
var zlib = require('zlib'); readdirSync,
var mime = require('mime-types'); 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 = []) { function getFilesSync(dir, files = []) {
readdirSync(dir, { withFileTypes: true }).forEach(entry => { readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
const entryPath = resolve(dir, entry.name); const entryPath = resolve(dir, entry.name)
if (entry.isDirectory()) { if (entry.isDirectory()) {
getFilesSync(entryPath, files); getFilesSync(entryPath, files)
} else { } else {
files.push(entryPath); files.push(entryPath)
} }
}) })
return files; return files
} }
function coherseToBuffer(input) { function coherseToBuffer(input) {
return Buffer.isBuffer(input) ? input : Buffer.from(input); return Buffer.isBuffer(input) ? input : Buffer.from(input)
} }
function cleanAndOpen(path) { function cleanAndOpen(path) {
if (existsSync(path)) { if (existsSync(path)) {
unlinkSync(path); unlinkSync(path)
} }
return createWriteStream(path, { flags: "w+" }); return createWriteStream(path, { flags: 'w+' })
} }
class ProgmemGenerator { class ProgmemGenerator {
constructor(options = {}) { constructor(options = {}) {
const { outputPath, bytesPerLine = 20, indent = " ", includes = ARDUINO_INCLUDES } = options; const {
this.options = { outputPath, bytesPerLine, indent, includes }; outputPath,
bytesPerLine = 20,
indent = ' ',
includes = ARDUINO_INCLUDES,
} = options
this.options = { outputPath, bytesPerLine, indent, includes }
} }
apply(compiler) { apply(compiler) {
compiler.hooks.emit.tapAsync( compiler.hooks.emit.tapAsync(
{ name: 'ProgmemGenerator' }, { name: 'ProgmemGenerator' },
(compilation, callback) => { (compilation, callback) => {
const { outputPath, bytesPerLine, indent, includes } = this.options; const { outputPath, bytesPerLine, indent, includes } = this.options
const fileInfo = []; const fileInfo = []
const writeStream = cleanAndOpen(resolve(compilation.options.context, outputPath)); const writeStream = cleanAndOpen(
resolve(compilation.options.context, outputPath),
)
try { try {
const writeIncludes = () => { const writeIncludes = () => {
writeStream.write(includes); writeStream.write(includes)
} }
const writeFile = (relativeFilePath, buffer) => { const writeFile = (relativeFilePath, buffer) => {
const variable = "ESP_REACT_DATA_" + fileInfo.length; const variable = 'ESP_REACT_DATA_' + fileInfo.length
const mimeType = mime.lookup(relativeFilePath); const mimeType = mime.lookup(relativeFilePath)
var size = 0; var size = 0
writeStream.write("const uint8_t " + variable + "[] PROGMEM = {"); writeStream.write('const uint8_t ' + variable + '[] PROGMEM = {')
const zipBuffer = zlib.gzipSync(buffer); const zipBuffer = zlib.gzipSync(buffer)
zipBuffer.forEach((b) => { zipBuffer.forEach((b) => {
if (!(size % bytesPerLine)) { if (!(size % bytesPerLine)) {
writeStream.write("\n"); writeStream.write('\n')
writeStream.write(indent); writeStream.write(indent)
} }
writeStream.write("0x" + ("00" + b.toString(16).toUpperCase()).substr(-2) + ","); writeStream.write(
size++; '0x' + ('00' + b.toString(16).toUpperCase()).substr(-2) + ',',
}); )
size++
})
if (size % bytesPerLine) { if (size % bytesPerLine) {
writeStream.write("\n"); writeStream.write('\n')
} }
writeStream.write("};\n\n"); writeStream.write('};\n\n')
fileInfo.push({ fileInfo.push({
uri: '/' + relativeFilePath.replace(sep, '/'), uri: '/' + relativeFilePath.replace(sep, '/'),
mimeType, mimeType,
variable, variable,
size size,
}); })
}; }
const writeFiles = () => { const writeFiles = () => {
// process static files // process static files
const buildPath = compilation.options.output.path; const buildPath = compilation.options.output.path
for (const filePath of getFilesSync(buildPath)) { for (const filePath of getFilesSync(buildPath)) {
const readStream = readFileSync(filePath); const readStream = readFileSync(filePath)
const relativeFilePath = relative(buildPath, filePath); const relativeFilePath = relative(buildPath, filePath)
writeFile(relativeFilePath, readStream); writeFile(relativeFilePath, readStream)
} }
// process assets // process assets
const { assets } = compilation; const { assets } = compilation
Object.keys(assets).forEach((relativeFilePath) => { Object.keys(assets).forEach((relativeFilePath) => {
writeFile(relativeFilePath, coherseToBuffer(assets[relativeFilePath].source())); writeFile(
}); relativeFilePath,
coherseToBuffer(assets[relativeFilePath].source()),
)
})
} }
const generateWWWClass = () => { const generateWWWClass = () => {
@@ -93,30 +110,39 @@ class ProgmemGenerator {
class WWWData { class WWWData {
${indent}public: ${indent}public:
${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) { ${indent.repeat(
${fileInfo.map(file => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size});`).join('\n')} 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)}} ${indent.repeat(2)}}
}; };
`; `
} }
const writeWWWClass = () => { const writeWWWClass = () => {
writeStream.write(generateWWWClass()); writeStream.write(generateWWWClass())
} }
writeIncludes(); writeIncludes()
writeFiles(); writeFiles()
writeWWWClass(); writeWWWClass()
writeStream.on('finish', () => { writeStream.on('finish', () => {
callback(); callback()
}); })
} finally { } finally {
writeStream.end(); writeStream.end()
} }
} },
); )
} }
} }
module.exports = ProgmemGenerator; module.exports = ProgmemGenerator

View File

@@ -3,20 +3,26 @@
font-family: 'Roboto'; font-family: 'Roboto';
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/li.woff2) format('woff2'); src: local('Roboto Light'), local('Roboto-Light'),
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; 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-face {
font-family: 'Roboto'; font-family: 'Roboto';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2'); src: local('Roboto'), local('Roboto-Regular'),
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; 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-face {
font-family: 'Roboto'; font-family: 'Roboto';
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/me.woff2) format('woff2'); src: local('Roboto Medium'), local('Roboto-Medium'),
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; 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) => { 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 { Theme } from '@material-ui/core'
import { APStatus, APNetworkStatus } from "./types"; import { APStatus, APNetworkStatus } from './types'
export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => { export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
switch (status) { switch (status) {
case APNetworkStatus.ACTIVE: case APNetworkStatus.ACTIVE:
return theme.palette.success.main; return theme.palette.success.main
case APNetworkStatus.INACTIVE: case APNetworkStatus.INACTIVE:
return theme.palette.info.main; return theme.palette.info.main
case APNetworkStatus.LINGERING: case APNetworkStatus.LINGERING:
return theme.palette.warning.main; return theme.palette.warning.main
default: default:
return theme.palette.warning.main; return theme.palette.warning.main
} }
} }
export const apStatus = ({ status }: APStatus) => { export const apStatus = ({ status }: APStatus) => {
switch (status) { switch (status) {
case APNetworkStatus.ACTIVE: case APNetworkStatus.ACTIVE:
return "Active"; return 'Active'
case APNetworkStatus.INACTIVE: case APNetworkStatus.INACTIVE:
return "Inactive"; return 'Inactive'
case APNetworkStatus.LINGERING: case APNetworkStatus.LINGERING:
return "Lingering until idle"; return 'Lingering until idle'
default: default:
return "Unknown"; return 'Unknown'
} }
}; }

View File

@@ -1,27 +1,27 @@
export enum APProvisionMode { export enum APProvisionMode {
AP_MODE_ALWAYS = 0, AP_MODE_ALWAYS = 0,
AP_MODE_DISCONNECTED = 1, AP_MODE_DISCONNECTED = 1,
AP_NEVER = 2 AP_NEVER = 2,
} }
export enum APNetworkStatus { export enum APNetworkStatus {
ACTIVE = 0, ACTIVE = 0,
INACTIVE = 1, INACTIVE = 1,
LINGERING = 2 LINGERING = 2,
} }
export interface APStatus { export interface APStatus {
status: APNetworkStatus; status: APNetworkStatus
ip_address: string; ip_address: string
mac_address: string; mac_address: string
station_num: number; station_num: number
} }
export interface APSettings { export interface APSettings {
provision_mode: APProvisionMode; provision_mode: APProvisionMode
ssid: string; ssid: string
password: string; password: string
local_ip: string; local_ip: string
gateway_ip: string; gateway_ip: string
subnet_mask: 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 FEATURES_ENDPOINT = ENDPOINT_ROOT + 'features'
export const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + "ntpStatus"; export const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'ntpStatus'
export const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "ntpSettings"; export const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'ntpSettings'
export const TIME_ENDPOINT = ENDPOINT_ROOT + "time"; export const TIME_ENDPOINT = ENDPOINT_ROOT + 'time'
export const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "apSettings"; export const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'apSettings'
export const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + "apStatus"; export const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'apStatus'
export const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + "scanNetworks"; export const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + 'scanNetworks'
export const LIST_NETWORKS_ENDPOINT = ENDPOINT_ROOT + "listNetworks"; export const LIST_NETWORKS_ENDPOINT = ENDPOINT_ROOT + 'listNetworks'
export const NETWORK_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "networkSettings"; export const NETWORK_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'networkSettings'
export const NETWORK_STATUS_ENDPOINT = ENDPOINT_ROOT + "networkStatus"; export const NETWORK_STATUS_ENDPOINT = ENDPOINT_ROOT + 'networkStatus'
export const OTA_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "otaSettings"; export const OTA_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'otaSettings'
export const UPLOAD_FIRMWARE_ENDPOINT = ENDPOINT_ROOT + "uploadFirmware"; export const UPLOAD_FIRMWARE_ENDPOINT = ENDPOINT_ROOT + 'uploadFirmware'
export const MQTT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "mqttSettings"; export const MQTT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'mqttSettings'
export const MQTT_STATUS_ENDPOINT = ENDPOINT_ROOT + "mqttStatus"; export const MQTT_STATUS_ENDPOINT = ENDPOINT_ROOT + 'mqttStatus'
export const SYSTEM_STATUS_ENDPOINT = ENDPOINT_ROOT + "systemStatus"; export const SYSTEM_STATUS_ENDPOINT = ENDPOINT_ROOT + 'systemStatus'
export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + "signIn"; export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + 'signIn'
export const VERIFY_AUTHORIZATION_ENDPOINT = ENDPOINT_ROOT + "verifyAuthorization"; export const VERIFY_AUTHORIZATION_ENDPOINT =
export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "securitySettings"; ENDPOINT_ROOT + 'verifyAuthorization'
export const GENERATE_TOKEN_ENDPOINT = ENDPOINT_ROOT + "generateToken"; export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'securitySettings'
export const RESTART_ENDPOINT = ENDPOINT_ROOT + "restart"; export const GENERATE_TOKEN_ENDPOINT = ENDPOINT_ROOT + 'generateToken'
export const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + "factoryReset"; 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_NAME = process.env.REACT_APP_PROJECT_NAME!
export const PROJECT_PATH = process.env.REACT_APP_PROJECT_PATH!; export const PROJECT_PATH = process.env.REACT_APP_PROJECT_PATH!
export const ENDPOINT_ROOT = calculateEndpointRoot("/rest/"); export const ENDPOINT_ROOT = calculateEndpointRoot('/rest/')
export const WEB_SOCKET_ROOT = calculateWebSocketRoot("/ws/"); export const WEB_SOCKET_ROOT = calculateWebSocketRoot('/ws/')
function calculateEndpointRoot(endpointPath: string) { function calculateEndpointRoot(endpointPath: string) {
const httpRoot = process.env.REACT_APP_HTTP_ROOT; const httpRoot = process.env.REACT_APP_HTTP_ROOT
if (httpRoot) { if (httpRoot) {
return httpRoot + endpointPath; return httpRoot + endpointPath
} }
const location = window.location; const location = window.location
return location.protocol + "//" + location.host + endpointPath; return location.protocol + '//' + location.host + endpointPath
} }
function calculateWebSocketRoot(webSocketPath: string) { function calculateWebSocketRoot(webSocketPath: string) {
const webSocketRoot = process.env.REACT_APP_WEB_SOCKET_ROOT; const webSocketRoot = process.env.REACT_APP_WEB_SOCKET_ROOT
if (webSocketRoot) { if (webSocketRoot) {
return webSocketRoot + webSocketPath; return webSocketRoot + webSocketPath
} }
const location = window.location; const location = window.location
const webProtocol = location.protocol === "https:" ? "wss:" : "ws:"; const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
return webProtocol + "//" + location.host + webSocketPath; return webProtocol + '//' + location.host + webSocketPath
} }

View File

@@ -1,56 +1,61 @@
import * as H from 'history'; import * as H from 'history'
import history from '../history'; import history from '../history'
import { Features } from '../features/types'; import { Features } from '../features/types'
import { getDefaultRoute } from '../AppRouting'; import { getDefaultRoute } from '../AppRouting'
export const ACCESS_TOKEN = 'access_token'; export const ACCESS_TOKEN = 'access_token'
export const SIGN_IN_PATHNAME = 'signInPathname'; export const SIGN_IN_PATHNAME = 'signInPathname'
export const SIGN_IN_SEARCH = 'signInSearch'; export const SIGN_IN_SEARCH = 'signInSearch'
/** /**
* Fallback to sessionStorage if localStorage is absent. WebView may not have local storage enabled. * Fallback to sessionStorage if localStorage is absent. WebView may not have local storage enabled.
*/ */
export function getStorage() { export function getStorage() {
return localStorage || sessionStorage; return localStorage || sessionStorage
} }
export function storeLoginRedirect(location?: H.Location) { export function storeLoginRedirect(location?: H.Location) {
if (location) { if (location) {
getStorage().setItem(SIGN_IN_PATHNAME, location.pathname); getStorage().setItem(SIGN_IN_PATHNAME, location.pathname)
getStorage().setItem(SIGN_IN_SEARCH, location.search); getStorage().setItem(SIGN_IN_SEARCH, location.search)
} }
} }
export function clearLoginRedirect() { export function clearLoginRedirect() {
getStorage().removeItem(SIGN_IN_PATHNAME); getStorage().removeItem(SIGN_IN_PATHNAME)
getStorage().removeItem(SIGN_IN_SEARCH); getStorage().removeItem(SIGN_IN_SEARCH)
} }
export function fetchLoginRedirect(features: Features): H.LocationDescriptorObject { export function fetchLoginRedirect(
const signInPathname = getStorage().getItem(SIGN_IN_PATHNAME); features: Features,
const signInSearch = getStorage().getItem(SIGN_IN_SEARCH); ): H.LocationDescriptorObject {
clearLoginRedirect(); const signInPathname = getStorage().getItem(SIGN_IN_PATHNAME)
const signInSearch = getStorage().getItem(SIGN_IN_SEARCH)
clearLoginRedirect()
return { return {
pathname: signInPathname || getDefaultRoute(features), 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. * Wraps the normal fetch routine with one with provides the access token if present.
*/ */
export function authorizedFetch(url: RequestInfo, params?: RequestInit): Promise<Response> { export function authorizedFetch(
const accessToken = getStorage().getItem(ACCESS_TOKEN); url: RequestInfo,
params?: RequestInit,
): Promise<Response> {
const accessToken = getStorage().getItem(ACCESS_TOKEN)
if (accessToken) { if (accessToken) {
params = params || {}; params = params || {}
params.credentials = 'include'; params.credentials = 'include'
params.headers = { params.headers = {
...params.headers, ...params.headers,
"Authorization": 'Bearer ' + accessToken Authorization: 'Bearer ' + accessToken,
}; }
} }
return fetch(url, params); return fetch(url, params)
} }
/** /**
@@ -58,57 +63,67 @@ export function authorizedFetch(url: RequestInfo, params?: RequestInit): Promise
* for a single file upload and takes care of adding the Authorization header and redirecting on * 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. * 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) => { return new Promise((resolve, reject) => {
xhr.open("POST", url, true); xhr.open('POST', url, true)
const accessToken = getStorage().getItem(ACCESS_TOKEN); const accessToken = getStorage().getItem(ACCESS_TOKEN)
if (accessToken) { if (accessToken) {
xhr.withCredentials = true; xhr.withCredentials = true
xhr.setRequestHeader("Authorization", 'Bearer ' + accessToken); xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken)
} }
xhr.upload.onprogress = onProgress; xhr.upload.onprogress = onProgress
xhr.onload = function () { xhr.onload = function () {
if (xhr.status === 401 || xhr.status === 403) { if (xhr.status === 401 || xhr.status === 403) {
history.push("/unauthorized"); history.push('/unauthorized')
} else { } else {
resolve(); resolve()
} }
}; }
xhr.onerror = function (event: ProgressEvent<EventTarget>) { xhr.onerror = function (event: ProgressEvent<EventTarget>) {
reject(new DOMException('Error', 'UploadError')); reject(new DOMException('Error', 'UploadError'))
}; }
xhr.onabort = function () { xhr.onabort = function () {
reject(new DOMException('Aborted', 'AbortError')); reject(new DOMException('Aborted', 'AbortError'))
}; }
const formData = new FormData(); const formData = new FormData()
formData.append('file', file); formData.append('file', file)
xhr.send(formData); xhr.send(formData)
}); })
} }
/** /**
* Wraps the normal fetch routene which redirects on 401 response. * 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) => { return new Promise<Response>((resolve, reject) => {
authorizedFetch(url, params).then(response => { authorizedFetch(url, params)
if (response.status === 401 || response.status === 403) { .then((response) => {
history.push("/unauthorized"); if (response.status === 401 || response.status === 403) {
} else { history.push('/unauthorized')
resolve(response); } else {
} resolve(response)
}).catch(error => { }
reject(error); })
}); .catch((error) => {
}); reject(error)
})
})
} }
export function addAccessTokenParameter(url: string) { export function addAccessTokenParameter(url: string) {
const accessToken = getStorage().getItem(ACCESS_TOKEN); const accessToken = getStorage().getItem(ACCESS_TOKEN)
if (!accessToken) { if (!accessToken) {
return url; return url
} }
const parsedUrl = new URL(url); const parsedUrl = new URL(url)
parsedUrl.searchParams.set(ACCESS_TOKEN, accessToken); parsedUrl.searchParams.set(ACCESS_TOKEN, accessToken)
return parsedUrl.toString(); return parsedUrl.toString()
} }

View File

@@ -1,6 +1,6 @@
export { default as AuthenticatedRoute } from './AuthenticatedRoute'; export { default as AuthenticatedRoute } from './AuthenticatedRoute'
export { default as AuthenticationWrapper } from './AuthenticationWrapper'; export { default as AuthenticationWrapper } from './AuthenticationWrapper'
export { default as UnauthenticatedRoute } from './UnauthenticatedRoute'; export { default as UnauthenticatedRoute } from './UnauthenticatedRoute'
export * from './Authentication'; export * from './Authentication'
export * from './AuthenticationContext'; export * from './AuthenticationContext'

View File

@@ -1,17 +1,17 @@
export { default as BlockFormControlLabel } from './BlockFormControlLabel'; export { default as BlockFormControlLabel } from './BlockFormControlLabel'
export { default as FormActions } from './FormActions'; export { default as FormActions } from './FormActions'
export { default as FormButton } from './FormButton'; export { default as FormButton } from './FormButton'
export { default as HighlightAvatar } from './HighlightAvatar'; export { default as HighlightAvatar } from './HighlightAvatar'
export { default as MenuAppBar } from './MenuAppBar'; export { default as MenuAppBar } from './MenuAppBar'
export { default as PasswordValidator } from './PasswordValidator'; export { default as PasswordValidator } from './PasswordValidator'
export { default as RestFormLoader } from './RestFormLoader'; export { default as RestFormLoader } from './RestFormLoader'
export { default as SectionContent } from './SectionContent'; export { default as SectionContent } from './SectionContent'
export { default as WebSocketFormLoader } from './WebSocketFormLoader'; export { default as WebSocketFormLoader } from './WebSocketFormLoader'
export { default as ErrorButton } from './ErrorButton'; export { default as ErrorButton } from './ErrorButton'
export { default as SingleUpload } from './SingleUpload'; export { default as SingleUpload } from './SingleUpload'
export * from './RestFormLoader'; export * from './RestFormLoader'
export * from './RestController'; export * from './RestController'
export * from './WebSocketFormLoader'; export * from './WebSocketFormLoader'
export * from './WebSocketController'; export * from './WebSocketController'

View File

@@ -1,8 +1,8 @@
export interface Features { export interface Features {
project: boolean; project: boolean
security: boolean; security: boolean
mqtt: boolean; mqtt: boolean
ntp: boolean; ntp: boolean
ota: boolean; ota: boolean
upload_firmware: boolean; upload_firmware: boolean
} }

View File

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

View File

@@ -1,56 +1,59 @@
import { Theme } from "@material-ui/core"; import { Theme } from '@material-ui/core'
import { MqttStatus, MqttDisconnectReason } from "./types"; import { MqttStatus, MqttDisconnectReason } from './types'
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => { export const mqttStatusHighlight = (
{ enabled, connected }: MqttStatus,
theme: Theme,
) => {
if (!enabled) { if (!enabled) {
return theme.palette.info.main; return theme.palette.info.main
} }
if (connected) { 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) => { export const mqttStatus = ({ enabled, connected }: MqttStatus) => {
if (!enabled) { if (!enabled) {
return "Not enabled"; return 'Not enabled'
} }
if (connected) { if (connected) {
return "Connected"; return 'Connected'
} }
return "Disconnected"; return 'Disconnected'
} }
export const disconnectReason = ({ disconnect_reason }: MqttStatus) => { export const disconnectReason = ({ disconnect_reason }: MqttStatus) => {
switch (disconnect_reason) { switch (disconnect_reason) {
case MqttDisconnectReason.TCP_DISCONNECTED: case MqttDisconnectReason.TCP_DISCONNECTED:
return "TCP disconnected"; return 'TCP disconnected'
case MqttDisconnectReason.MQTT_UNACCEPTABLE_PROTOCOL_VERSION: case MqttDisconnectReason.MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
return "Unacceptable protocol version"; return 'Unacceptable protocol version'
case MqttDisconnectReason.MQTT_IDENTIFIER_REJECTED: case MqttDisconnectReason.MQTT_IDENTIFIER_REJECTED:
return "Client ID rejected"; return 'Client ID rejected'
case MqttDisconnectReason.MQTT_SERVER_UNAVAILABLE: case MqttDisconnectReason.MQTT_SERVER_UNAVAILABLE:
return "Server unavailable"; return 'Server unavailable'
case MqttDisconnectReason.MQTT_MALFORMED_CREDENTIALS: case MqttDisconnectReason.MQTT_MALFORMED_CREDENTIALS:
return "Malformed credentials"; return 'Malformed credentials'
case MqttDisconnectReason.MQTT_NOT_AUTHORIZED: case MqttDisconnectReason.MQTT_NOT_AUTHORIZED:
return "Not authorized"; return 'Not authorized'
case MqttDisconnectReason.ESP8266_NOT_ENOUGH_SPACE: case MqttDisconnectReason.ESP8266_NOT_ENOUGH_SPACE:
return "Device out of memory"; return 'Device out of memory'
case MqttDisconnectReason.TLS_BAD_FINGERPRINT: case MqttDisconnectReason.TLS_BAD_FINGERPRINT:
return "Server fingerprint invalid"; return 'Server fingerprint invalid'
default: 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) if (mqtt_fails < 10) return theme.palette.warning.main
return theme.palette.success.main;
if (mqtt_fails < 10) return theme.palette.error.main
return theme.palette.warning.main;
return theme.palette.error.main;
} }

View File

@@ -6,40 +6,40 @@ export enum MqttDisconnectReason {
MQTT_MALFORMED_CREDENTIALS = 4, MQTT_MALFORMED_CREDENTIALS = 4,
MQTT_NOT_AUTHORIZED = 5, MQTT_NOT_AUTHORIZED = 5,
ESP8266_NOT_ENOUGH_SPACE = 6, ESP8266_NOT_ENOUGH_SPACE = 6,
TLS_BAD_FINGERPRINT = 7 TLS_BAD_FINGERPRINT = 7,
} }
export interface MqttStatus { export interface MqttStatus {
enabled: boolean; enabled: boolean
connected: boolean; connected: boolean
client_id: string; client_id: string
disconnect_reason: MqttDisconnectReason; disconnect_reason: MqttDisconnectReason
mqtt_fails: number; mqtt_fails: number
} }
export interface MqttSettings { export interface MqttSettings {
enabled: boolean; enabled: boolean
host: string; host: string
port: number; port: number
base: string; base: string
username: string; username: string
password: string; password: string
client_id: string; client_id: string
keep_alive: number; keep_alive: number
clean_session: boolean; clean_session: boolean
max_topic_length: number; max_topic_length: number
publish_time_boiler: number; publish_time_boiler: number
publish_time_thermostat: number; publish_time_thermostat: number
publish_time_solar: number; publish_time_solar: number
publish_time_mixer: number; publish_time_mixer: number
publish_time_other: number; publish_time_other: number
publish_time_sensor: number; publish_time_sensor: number
dallas_format: number; dallas_format: number
bool_format: number; bool_format: number
mqtt_qos: number; mqtt_qos: number
mqtt_retain: boolean; mqtt_retain: boolean
ha_enabled: boolean; ha_enabled: boolean
ha_climate_format: number; ha_climate_format: number
nested_format: number; nested_format: number
subscribe_format: number; subscribe_format: number
} }

View File

@@ -1,57 +1,57 @@
import { Theme } from "@material-ui/core"; import { Theme } from '@material-ui/core'
import { NetworkStatus, NetworkConnectionStatus } from "./types"; import { NetworkStatus, NetworkConnectionStatus } from './types'
export const isConnected = ({ status }: NetworkStatus) => { export const isConnected = ({ status }: NetworkStatus) => {
return ( return (
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED || status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED
); )
}; }
export const isWiFi = ({ status }: NetworkStatus) => export const isWiFi = ({ status }: NetworkStatus) =>
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED; status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED
export const isEthernet = ({ status }: NetworkStatus) => export const isEthernet = ({ status }: NetworkStatus) =>
status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED; status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED
export const networkStatusHighlight = ( export const networkStatusHighlight = (
{ status }: NetworkStatus, { status }: NetworkStatus,
theme: Theme theme: Theme,
) => { ) => {
switch (status) { switch (status) {
case NetworkConnectionStatus.WIFI_STATUS_IDLE: case NetworkConnectionStatus.WIFI_STATUS_IDLE:
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED: case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD: case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
return theme.palette.info.main; return theme.palette.info.main
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED: case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
case NetworkConnectionStatus.ETHERNET_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_CONNECT_FAILED:
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST: case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
return theme.palette.error.main; return theme.palette.error.main
default: default:
return theme.palette.warning.main; return theme.palette.warning.main
} }
}; }
export const networkStatus = ({ status }: NetworkStatus) => { export const networkStatus = ({ status }: NetworkStatus) => {
switch (status) { switch (status) {
case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD: case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD:
return "Inactive"; return 'Inactive'
case NetworkConnectionStatus.WIFI_STATUS_IDLE: case NetworkConnectionStatus.WIFI_STATUS_IDLE:
return "Idle"; return 'Idle'
case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL: case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL:
return "No SSID Available"; return 'No SSID Available'
case NetworkConnectionStatus.WIFI_STATUS_CONNECTED: case NetworkConnectionStatus.WIFI_STATUS_CONNECTED:
return "Connected (WiFi)"; return 'Connected (WiFi)'
case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED: case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED:
return "Connected (Ethernet)"; return 'Connected (Ethernet)'
case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED: case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED:
return "Connection Failed"; return 'Connection Failed'
case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST: case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST:
return "Connection Lost"; return 'Connection Lost'
case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED: case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED:
return "Disconnected"; return 'Disconnected'
default: 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) => { export const networkSecurityMode = ({ encryption_type }: WiFiNetwork) => {
switch (encryption_type) { switch (encryption_type) {
case WiFiEncryptionType.WIFI_AUTH_WEP: case WiFiEncryptionType.WIFI_AUTH_WEP:
return "WEP"; return 'WEP'
case WiFiEncryptionType.WIFI_AUTH_WPA_PSK: case WiFiEncryptionType.WIFI_AUTH_WPA_PSK:
return "WPA"; return 'WPA'
case WiFiEncryptionType.WIFI_AUTH_WPA2_PSK: case WiFiEncryptionType.WIFI_AUTH_WPA2_PSK:
return "WPA2"; return 'WPA2'
case WiFiEncryptionType.WIFI_AUTH_WPA_WPA2_PSK: case WiFiEncryptionType.WIFI_AUTH_WPA_WPA2_PSK:
return "WPA/WPA2"; return 'WPA/WPA2'
case WiFiEncryptionType.WIFI_AUTH_WPA2_ENTERPRISE: case WiFiEncryptionType.WIFI_AUTH_WPA2_ENTERPRISE:
return "WPA2 Enterprise"; return 'WPA2 Enterprise'
case WiFiEncryptionType.WIFI_AUTH_OPEN: case WiFiEncryptionType.WIFI_AUTH_OPEN:
return "None"; return 'None'
default: default:
return "Unknown"; return 'Unknown'
} }
} }

View File

@@ -6,7 +6,7 @@ export enum NetworkConnectionStatus {
WIFI_STATUS_CONNECTION_LOST = 5, WIFI_STATUS_CONNECTION_LOST = 5,
WIFI_STATUS_DISCONNECTED = 6, WIFI_STATUS_DISCONNECTED = 6,
ETHERNET_STATUS_CONNECTED = 10, ETHERNET_STATUS_CONNECTED = 10,
WIFI_STATUS_NO_SHIELD = 255 WIFI_STATUS_NO_SHIELD = 255,
} }
export enum WiFiEncryptionType { export enum WiFiEncryptionType {
@@ -15,43 +15,43 @@ export enum WiFiEncryptionType {
WIFI_AUTH_WPA_PSK = 2, WIFI_AUTH_WPA_PSK = 2,
WIFI_AUTH_WPA2_PSK = 3, WIFI_AUTH_WPA2_PSK = 3,
WIFI_AUTH_WPA_WPA2_PSK = 4, WIFI_AUTH_WPA_WPA2_PSK = 4,
WIFI_AUTH_WPA2_ENTERPRISE = 5 WIFI_AUTH_WPA2_ENTERPRISE = 5,
} }
export interface NetworkStatus { export interface NetworkStatus {
status: NetworkConnectionStatus; status: NetworkConnectionStatus
local_ip: string; local_ip: string
mac_address: string; mac_address: string
rssi: number; rssi: number
ssid: string; ssid: string
bssid: string; bssid: string
channel: number; channel: number
subnet_mask: string; subnet_mask: string
gateway_ip: string; gateway_ip: string
dns_ip_1: string; dns_ip_1: string
dns_ip_2: string; dns_ip_2: string
} }
export interface NetworkSettings { export interface NetworkSettings {
ssid: string; ssid: string
password: string; password: string
hostname: string; hostname: string
static_ip_config: boolean; static_ip_config: boolean
local_ip?: string; local_ip?: string
gateway_ip?: string; gateway_ip?: string
subnet_mask?: string; subnet_mask?: string
dns_ip_1?: string; dns_ip_1?: string
dns_ip_2?: string; dns_ip_2?: string
} }
export interface WiFiNetworkList { export interface WiFiNetworkList {
networks: WiFiNetwork[]; networks: WiFiNetwork[]
} }
export interface WiFiNetwork { export interface WiFiNetwork {
rssi: number; rssi: number
ssid: string; ssid: string
bssid: string; bssid: string
channel: number; channel: number
encryption_type: WiFiEncryptionType; encryption_type: WiFiEncryptionType
} }

View File

@@ -1,26 +1,27 @@
import { Theme } from "@material-ui/core"; import { Theme } from '@material-ui/core'
import { NTPStatus, NTPSyncStatus } from "./types"; 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) => { export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => {
switch (status) { switch (status) {
case NTPSyncStatus.NTP_INACTIVE: case NTPSyncStatus.NTP_INACTIVE:
return theme.palette.info.main; return theme.palette.info.main
case NTPSyncStatus.NTP_ACTIVE: case NTPSyncStatus.NTP_ACTIVE:
return theme.palette.success.main; return theme.palette.success.main
default: default:
return theme.palette.error.main; return theme.palette.error.main
} }
} }
export const ntpStatus = ({ status }: NTPStatus) => { export const ntpStatus = ({ status }: NTPStatus) => {
switch (status) { switch (status) {
case NTPSyncStatus.NTP_INACTIVE: case NTPSyncStatus.NTP_INACTIVE:
return "Inactive"; return 'Inactive'
case NTPSyncStatus.NTP_ACTIVE: case NTPSyncStatus.NTP_ACTIVE:
return "Active"; return 'Active'
default: 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( const LOCALE_FORMAT = new Intl.DateTimeFormat([...window.navigator.languages], {
[...window.navigator.languages], day: 'numeric',
{ month: 'short',
day: 'numeric', year: 'numeric',
month: 'short', hour: 'numeric',
year: 'numeric', minute: 'numeric',
hour: 'numeric', second: 'numeric',
minute: 'numeric', hour12: false,
second: 'numeric', })
hour12: false
}
);
export const formatDateTime = (dateTime: string) => { 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) => { export const formatLocalDateTime = (date: Date) => {
return new Date(date.getTime() - date.getTimezoneOffset() * 60000) return new Date(date.getTime() - date.getTimezoneOffset() * 60000)
.toISOString() .toISOString()
.slice(0, -1) .slice(0, -1)
.substr(0, 19); .substr(0, 19)
} }
export const formatDuration = (duration: number) => { export const formatDuration = (duration: number) => {
const { days, hours, minutes, seconds } = parseMilliseconds(duration * 1000); const { days, hours, minutes, seconds } = parseMilliseconds(duration * 1000)
var formatted = ''; var formatted = ''
if (days) { if (days) {
formatted += pluralize(days, 'day'); formatted += pluralize(days, 'day')
} }
if (formatted || hours) { if (formatted || hours) {
formatted += pluralize(hours, 'hour'); formatted += pluralize(hours, 'hour')
} }
if (formatted || minutes) { if (formatted || minutes) {
formatted += pluralize(minutes, 'minute'); formatted += pluralize(minutes, 'minute')
} }
if (formatted || seconds) { 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 { export enum NTPSyncStatus {
NTP_INACTIVE = 0, NTP_INACTIVE = 0,
NTP_ACTIVE = 1 NTP_ACTIVE = 1,
} }
export interface NTPStatus { export interface NTPStatus {
status: NTPSyncStatus; status: NTPSyncStatus
utc_time: string; utc_time: string
local_time: string; local_time: string
server: string; server: string
uptime: number; uptime: number
} }
export interface NTPSettings { export interface NTPSettings {
enabled: boolean; enabled: boolean
server: string; server: string
tz_label: string; tz_label: string
tz_format: string; tz_format: string
} }
export interface Time { export interface Time {
local_time: string; local_time: string
} }

View File

@@ -1,40 +1,39 @@
import { Theme } from '@material-ui/core'; import { Theme } from '@material-ui/core'
import { EMSESPStatus, busConnectionStatus } from './EMSESPtypes'; 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) => { export const busStatusHighlight = ({ status }: EMSESPStatus, theme: Theme) => {
switch (status) { switch (status) {
case busConnectionStatus.BUS_STATUS_TX_ERRORS: case busConnectionStatus.BUS_STATUS_TX_ERRORS:
return theme.palette.warning.main; return theme.palette.warning.main
case busConnectionStatus.BUS_STATUS_CONNECTED: case busConnectionStatus.BUS_STATUS_CONNECTED:
return theme.palette.success.main; return theme.palette.success.main
case busConnectionStatus.BUS_STATUS_OFFLINE: case busConnectionStatus.BUS_STATUS_OFFLINE:
return theme.palette.error.main; return theme.palette.error.main
default: default:
return theme.palette.warning.main; return theme.palette.warning.main
} }
} }
export const busStatus = ({ status }: EMSESPStatus) => { export const busStatus = ({ status }: EMSESPStatus) => {
switch (status) { switch (status) {
case busConnectionStatus.BUS_STATUS_CONNECTED: case busConnectionStatus.BUS_STATUS_CONNECTED:
return "Connected"; return 'Connected'
case busConnectionStatus.BUS_STATUS_TX_ERRORS: case busConnectionStatus.BUS_STATUS_TX_ERRORS:
return "Tx Errors"; return 'Tx Errors'
case busConnectionStatus.BUS_STATUS_OFFLINE: case busConnectionStatus.BUS_STATUS_OFFLINE:
return "Disconnected"; return 'Disconnected'
default: default:
return "Unknown"; return 'Unknown'
} }
} }
export const qualityHighlight = (value: number, theme: Theme) => { export const qualityHighlight = (value: number, theme: Theme) => {
if (value >= 95) { 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 { export interface EMSESPSettings {
tx_mode: number; tx_mode: number
tx_delay: number; tx_delay: number
ems_bus_id: number; ems_bus_id: number
syslog_enabled: boolean; syslog_enabled: boolean
syslog_level: number; syslog_level: number
syslog_mark_interval: number; syslog_mark_interval: number
syslog_host: string; syslog_host: string
syslog_port: number; syslog_port: number
master_thermostat: number; master_thermostat: number
shower_timer: boolean; shower_timer: boolean
shower_alert: boolean; shower_alert: boolean
rx_gpio: number; rx_gpio: number
tx_gpio: number; tx_gpio: number
dallas_gpio: number; dallas_gpio: number
dallas_parasite: boolean; dallas_parasite: boolean
led_gpio: number; led_gpio: number
hide_led: boolean; hide_led: boolean
notoken_api: boolean; notoken_api: boolean
analog_enabled: boolean; analog_enabled: boolean
pbutton_gpio: number; pbutton_gpio: number
trace_raw: boolean; trace_raw: boolean
board_profile: string; board_profile: string
} }
export enum busConnectionStatus { export enum busConnectionStatus {
BUS_STATUS_CONNECTED = 0, BUS_STATUS_CONNECTED = 0,
BUS_STATUS_TX_ERRORS = 1, BUS_STATUS_TX_ERRORS = 1,
BUS_STATUS_OFFLINE = 2 BUS_STATUS_OFFLINE = 2,
} }
export interface EMSESPStatus { export interface EMSESPStatus {
status: busConnectionStatus; status: busConnectionStatus
rx_received: number; rx_received: number
tx_sent: number; tx_sent: number
rx_quality: number; rx_quality: number
tx_quality: number; tx_quality: number
} }
export interface Device { export interface Device {
id: number; id: number
type: string; type: string
brand: string; brand: string
name: string; name: string
deviceid: number; deviceid: number
productid: number; productid: number
version: string; version: string
} }
export interface Sensor { export interface Sensor {
no: number; no: number
id: string; id: string
temp: string; temp: string
} }
export interface EMSESPDevices { export interface EMSESPDevices {
devices: Device[]; devices: Device[]
sensors: Sensor[]; sensors: Sensor[]
} }
export interface EMSESPDeviceData { export interface EMSESPDeviceData {
name: string; name: string
data: string[]; data: string[]
} }
export interface DeviceValue { export interface DeviceValue {
id: number; id: number
data: string, data: string
uom: string, uom: string
name: string, name: string
cmd: string cmd: string
} }

View File

@@ -1,14 +1,14 @@
export interface User { export interface User {
username: string; username: string
password: string; password: string
admin: boolean; admin: boolean
} }
export interface SecuritySettings { export interface SecuritySettings {
users: User[]; users: User[]
jwt_secret: string; jwt_secret: string
} }
export interface GeneratedToken { export interface GeneratedToken {
token: string; token: string
} }

View File

@@ -12,64 +12,61 @@
const isLocalhost = Boolean( const isLocalhost = Boolean(
window.location.hostname === 'localhost' || window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address. // [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' || window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4. // 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match( 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 = { type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void; onSuccess?: (registration: ServiceWorkerRegistration) => void
onUpdate?: (registration: ServiceWorkerRegistration) => void; onUpdate?: (registration: ServiceWorkerRegistration) => void
}; }
export function register(config?: Config) { export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW. // The URL constructor is available in all browsers that support SW.
const publicUrl = new URL( const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
process.env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) { if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different 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 // 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 // serve assets; see https://github.com/facebook/create-react-app/issues/2374
return; return
} }
window.addEventListener('load', () => { window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
if (isLocalhost) { if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not. // 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 // Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation. // service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => { navigator.serviceWorker.ready.then(() => {
console.log( console.log(
'This web app is being served cache-first by a service ' + '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 { } else {
// Is not localhost. Just register service worker // Is not localhost. Just register service worker
registerValidSW(swUrl, config); registerValidSW(swUrl, config)
} }
}); })
} }
} }
function registerValidSW(swUrl: string, config?: Config) { function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker navigator.serviceWorker
.register(swUrl) .register(swUrl)
.then(registration => { .then((registration) => {
registration.onupdatefound = () => { registration.onupdatefound = () => {
const installingWorker = registration.installing; const installingWorker = registration.installing
if (installingWorker == null) { if (installingWorker == null) {
return; return
} }
installingWorker.onstatechange = () => { installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') { if (installingWorker.state === 'installed') {
@@ -79,67 +76,67 @@ function registerValidSW(swUrl: string, config?: Config) {
// content until all client tabs are closed. // content until all client tabs are closed.
console.log( console.log(
'New content is available and will be used when all ' + '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 // Execute callback
if (config && config.onUpdate) { if (config && config.onUpdate) {
config.onUpdate(registration); config.onUpdate(registration)
} }
} else { } else {
// At this point, everything has been precached. // At this point, everything has been precached.
// It's the perfect time to display a // It's the perfect time to display a
// "Content is cached for offline use." message. // "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 // Execute callback
if (config && config.onSuccess) { 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) { function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page. // Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, { 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. // 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 ( if (
response.status === 404 || response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1) (contentType != null && contentType.indexOf('javascript') === -1)
) { ) {
// No service worker found. Probably a different app. Reload the page. // No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => { registration.unregister().then(() => {
window.location.reload(); window.location.reload()
}); })
}); })
} else { } else {
// Service worker found. Proceed as normal. // Service worker found. Proceed as normal.
registerValidSW(swUrl, config); registerValidSW(swUrl, config)
} }
}) })
.catch(() => { .catch(() => {
console.log( 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() { export function unregister() {
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then((registration) => {
registration.unregister(); registration.unregister()
}); })
} }
} }

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
export { default as isHostname } from './isHostname'; export { default as isHostname } from './isHostname'
export { default as isIP } from './isIP'; export { default as isIP } from './isIP'
export { default as optional } from './optional'; export { default as optional } from './optional'
export { default as or } from './or'; export { default as or } from './or'
export { default as isPath } from './isPath'; 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])$/ 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) { 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]?)$/ 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) { 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_])$/ const pathPatternRegex = /^([a-zA-Z0-9_][a-zA-Z0-9/_-]*[a-zA-Z0-9_])$/
export default function isPath(path: string) { 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) => { const OR = (
return (value: any) => validator1(value) || validator2(value); validator1: (value: any) => boolean,
}; validator2: (value: any) => boolean,
) => {
export default OR; return (value: any) => validator1(value) || validator2(value)
}
export default OR

View File

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