From ac39a4644292e9863df8fcd75221eeca3f7df744 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 21 Apr 2024 15:10:22 +0200 Subject: [PATCH] formatting --- .prettierrc | 2 +- interface/progmem-generator.js | 15 +- interface/public/css/roboto.css | 5 +- interface/src/AppRouting.tsx | 10 +- interface/src/AuthenticatedRouting.tsx | 5 +- interface/src/CustomTheme.tsx | 6 +- interface/src/SignIn.tsx | 30 +- interface/src/api/ap.ts | 6 +- interface/src/api/authentication.ts | 6 +- interface/src/api/endpoints.ts | 3 +- interface/src/api/mqtt.ts | 6 +- interface/src/api/network.ts | 7 +- interface/src/api/ntp.ts | 6 +- interface/src/api/security.ts | 3 +- interface/src/api/system.ts | 20 +- interface/src/api/unpack.ts | 191 +++++++++---- interface/src/components/MessageBox.tsx | 25 +- interface/src/components/SectionContent.tsx | 11 +- .../inputs/ValidatedPasswordField.tsx | 5 +- .../components/inputs/ValidatedTextField.tsx | 9 +- .../src/components/layout/LayoutAppBar.tsx | 7 +- .../src/components/layout/LayoutMenu.tsx | 34 ++- .../src/components/layout/LayoutMenuItem.tsx | 11 +- .../src/components/layout/ListMenuItem.tsx | 34 ++- .../components/loading/ApplicationError.tsx | 8 +- .../src/components/loading/FormLoader.tsx | 13 +- .../src/components/loading/LoadingSpinner.tsx | 9 +- .../components/routing/BlockNavigation.tsx | 20 +- .../src/components/routing/RequireAdmin.tsx | 6 +- .../routing/RequireAuthenticated.tsx | 9 +- .../routing/RequireUnauthenticated.tsx | 6 +- .../src/components/routing/RouterTabs.tsx | 6 +- .../src/components/upload/SingleUpload.tsx | 24 +- .../authentication/Authentication.tsx | 9 +- .../src/contexts/authentication/context.ts | 4 +- interface/src/framework/Settings.tsx | 49 +++- interface/src/framework/ap/APSettings.tsx | 30 +- interface/src/framework/ap/APStatus.tsx | 28 +- interface/src/framework/mqtt/MqttSettings.tsx | 139 ++++++++-- interface/src/framework/mqtt/MqttStatus.tsx | 38 ++- .../src/framework/network/NetworkSettings.tsx | 115 +++++--- .../src/framework/network/NetworkStatus.tsx | 57 +++- .../network/WiFiConnectionContext.tsx | 4 +- .../framework/network/WiFiNetworkScanner.tsx | 8 +- .../framework/network/WiFiNetworkSelector.tsx | 23 +- interface/src/framework/ntp/NTPSettings.tsx | 15 +- interface/src/framework/ntp/NTPStatus.tsx | 55 +++- interface/src/framework/ota/OTASettings.tsx | 15 +- .../src/framework/security/GenerateToken.tsx | 32 ++- .../src/framework/security/ManageUsers.tsx | 56 +++- .../framework/security/SecuritySettings.tsx | 16 +- interface/src/framework/security/User.tsx | 47 +++- .../src/framework/system/ESPSystemStatus.tsx | 62 ++++- .../src/framework/system/RestartMonitor.tsx | 7 +- interface/src/framework/system/System.tsx | 6 +- interface/src/framework/system/SystemLog.tsx | 81 +++++- .../src/framework/system/SystemStatus.tsx | 36 ++- .../src/framework/system/UploadDownload.tsx | 106 +++++-- interface/src/i18n/de/index.ts | 5 +- interface/src/i18n/en/index.ts | 13 +- interface/src/i18n/fr/index.ts | 59 ++-- interface/src/i18n/it/index.ts | 14 +- interface/src/i18n/nl/index.ts | 5 +- interface/src/i18n/no/index.ts | 5 +- interface/src/i18n/pl/index.ts | 6 +- interface/src/i18n/sk/index.ts | 7 +- interface/src/i18n/sv/index.ts | 5 +- interface/src/i18n/tr/index.ts | 5 +- interface/src/index.tsx | 11 +- interface/src/project/ApplicationSettings.tsx | 170 ++++++++++-- interface/src/project/CustomEntities.tsx | 69 ++++- .../src/project/CustomEntitiesDialog.tsx | 121 +++++--- interface/src/project/Customization.tsx | 144 ++++++++-- interface/src/project/CustomizationDialog.tsx | 32 ++- interface/src/project/DeviceIcon.tsx | 13 +- interface/src/project/Devices.tsx | 260 +++++++++++++----- interface/src/project/DevicesDialog.tsx | 32 ++- interface/src/project/EntityMaskToggle.tsx | 31 ++- interface/src/project/Help.tsx | 26 +- interface/src/project/OptionIcon.tsx | 14 +- interface/src/project/Scheduler.tsx | 80 +++++- interface/src/project/SchedulerDialog.tsx | 85 ++++-- interface/src/project/Sensors.tsx | 93 +++++-- interface/src/project/SensorsAnalogDialog.tsx | 169 +++++++----- .../src/project/SensorsTemperatureDialog.tsx | 14 +- interface/src/project/SystemActivity.tsx | 23 +- interface/src/project/api.ts | 21 +- interface/src/project/deviceValue.ts | 6 +- interface/src/project/validators.ts | 241 +++++++++++++--- interface/src/utils/route.ts | 3 +- interface/src/utils/time.ts | 8 +- interface/src/utils/useRest.ts | 7 +- interface/src/validators/ap.ts | 40 ++- interface/src/validators/mqtt.ts | 19 +- interface/src/validators/network.ts | 42 ++- interface/src/validators/ntp.ts | 5 +- interface/src/validators/security.ts | 20 +- interface/src/validators/shared.ts | 37 ++- interface/src/validators/system.ts | 14 +- interface/vite.config.ts | 6 +- 100 files changed, 2778 insertions(+), 798 deletions(-) diff --git a/.prettierrc b/.prettierrc index 7bed89b7c..c2f70575b 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,7 +4,7 @@ "tabWidth": 2, "semi": true, "singleQuote": true, - "printWidth": 120, + "printWidth": 85, "bracketSpacing": true, "importOrder": ["^react", "^@mui/(.*)$", "^api*/(.*)$", "", "^[./]"], "importOrderSeparation": true, diff --git a/interface/progmem-generator.js b/interface/progmem-generator.js index f8661706d..aca4bddf8 100644 --- a/interface/progmem-generator.js +++ b/interface/progmem-generator.js @@ -1,5 +1,11 @@ import crypto from 'crypto'; -import { createWriteStream, existsSync, readFileSync, readdirSync, unlinkSync } from 'fs'; +import { + createWriteStream, + existsSync, + readFileSync, + readdirSync, + unlinkSync +} from 'fs'; import mime from 'mime-types'; import { relative, resolve, sep } from 'path'; import zlib from 'zlib'; @@ -18,12 +24,7 @@ const generateWWWClass = () => 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}, "${file.hash}");` - ) - .join('\n')} +${fileInfo.map((file) => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size}, "${file.hash}");`).join('\n')} ${indent.repeat(2)}} }; `; diff --git a/interface/public/css/roboto.css b/interface/public/css/roboto.css index 36f599b8a..639165cb6 100644 --- a/interface/public/css/roboto.css +++ b/interface/public/css/roboto.css @@ -12,7 +12,8 @@ local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131, U+0141-0144, U+0152-0153, U+015A-015B, - U+015E-015F, U+0179-017C, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, + unicode-range: U+0000-00FF, U+0104-0107, U+0118-0119, U+011E-011F, U+0130-0131, + U+0141-0144, U+0152-0153, U+015A-015B, U+015E-015F, U+0179-017C, U+02BB-02BC, + U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } diff --git a/interface/src/AppRouting.tsx b/interface/src/AppRouting.tsx index 6f917408d..e54f8023f 100644 --- a/interface/src/AppRouting.tsx +++ b/interface/src/AppRouting.tsx @@ -44,8 +44,14 @@ const AppRouting: FC = () => { - } /> - } /> + } + /> + } + /> { } /> } /> } /> - } /> + } + /> } /> )} diff --git a/interface/src/CustomTheme.tsx b/interface/src/CustomTheme.tsx index 743935f3d..03c03c6a3 100644 --- a/interface/src/CustomTheme.tsx +++ b/interface/src/CustomTheme.tsx @@ -1,7 +1,11 @@ import type { FC } from 'react'; import { CssBaseline } from '@mui/material'; -import { ThemeProvider, createTheme, responsiveFontSizes } from '@mui/material/styles'; +import { + ThemeProvider, + createTheme, + responsiveFontSizes +} from '@mui/material/styles'; import type { RequiredChildrenProps } from 'utils'; diff --git a/interface/src/SignIn.tsx b/interface/src/SignIn.tsx index c95b444f7..984cc8af8 100644 --- a/interface/src/SignIn.tsx +++ b/interface/src/SignIn.tsx @@ -41,9 +41,12 @@ const SignIn: FC = () => { const [processing, setProcessing] = useState(false); const [fieldErrors, setFieldErrors] = useState(); - const { send: callSignIn, onSuccess } = useRequest((request: SignInRequest) => AuthenticationApi.signIn(request), { - immediate: false - }); + const { send: callSignIn, onSuccess } = useRequest( + (request: SignInRequest) => AuthenticationApi.signIn(request), + { + immediate: false + } + ); onSuccess((response) => { if (response.data) { @@ -80,7 +83,9 @@ const SignIn: FC = () => { const submitOnEnter = onEnterCallback(signIn); - const onLocaleSelected: ChangeEventHandler = async ({ target }) => { + const onLocaleSelected: ChangeEventHandler = async ({ + target + }) => { const loc = target.value as Locales; localStorage.setItem('lang', loc); await loadLocaleAsync(loc); @@ -110,7 +115,14 @@ const SignIn: FC = () => { > {PROJECT_NAME} - +  DE @@ -182,7 +194,13 @@ const SignIn: FC = () => { /> - diff --git a/interface/src/api/ap.ts b/interface/src/api/ap.ts index 8545dc3f0..6106c05f5 100644 --- a/interface/src/api/ap.ts +++ b/interface/src/api/ap.ts @@ -3,5 +3,7 @@ import type { APSettingsType, APStatusType } from 'types'; import { alovaInstance } from './endpoints'; export const readAPStatus = () => alovaInstance.Get('/rest/apStatus'); -export const readAPSettings = () => alovaInstance.Get('/rest/apSettings'); -export const updateAPSettings = (data: APSettingsType) => alovaInstance.Post('/rest/apSettings', data); +export const readAPSettings = () => + alovaInstance.Get('/rest/apSettings'); +export const updateAPSettings = (data: APSettingsType) => + alovaInstance.Post('/rest/apSettings', data); diff --git a/interface/src/api/authentication.ts b/interface/src/api/authentication.ts index 41b7579f8..076b69bd5 100644 --- a/interface/src/api/authentication.ts +++ b/interface/src/api/authentication.ts @@ -9,8 +9,10 @@ import { ACCESS_TOKEN, alovaInstance } from './endpoints'; export const SIGN_IN_PATHNAME = 'loginPathname'; export const SIGN_IN_SEARCH = 'loginSearch'; -export const verifyAuthorization = () => alovaInstance.Get('/rest/verifyAuthorization'); -export const signIn = (request: SignInRequest) => alovaInstance.Post('/rest/signIn', request); +export const verifyAuthorization = () => + alovaInstance.Get('/rest/verifyAuthorization'); +export const signIn = (request: SignInRequest) => + alovaInstance.Post('/rest/signIn', request); export function getStorage() { return localStorage || sessionStorage; diff --git a/interface/src/api/endpoints.ts b/interface/src/api/endpoints.ts index 5d6974da8..e2f351f8f 100644 --- a/interface/src/api/endpoints.ts +++ b/interface/src/api/endpoints.ts @@ -19,7 +19,8 @@ export const alovaInstance = createAlova({ requestAdapter: xhrRequestAdapter(), beforeRequest(method) { if (localStorage.getItem(ACCESS_TOKEN)) { - method.config.headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN); + method.config.headers.Authorization = + 'Bearer ' + localStorage.getItem(ACCESS_TOKEN); } }, diff --git a/interface/src/api/mqtt.ts b/interface/src/api/mqtt.ts index dd9d03842..7ccc31d10 100644 --- a/interface/src/api/mqtt.ts +++ b/interface/src/api/mqtt.ts @@ -2,7 +2,9 @@ import type { MqttSettingsType, MqttStatusType } from 'types'; import { alovaInstance } from './endpoints'; -export const readMqttStatus = () => alovaInstance.Get('/rest/mqttStatus'); -export const readMqttSettings = () => alovaInstance.Get('/rest/mqttSettings'); +export const readMqttStatus = () => + alovaInstance.Get('/rest/mqttStatus'); +export const readMqttSettings = () => + alovaInstance.Get('/rest/mqttSettings'); export const updateMqttSettings = (data: MqttSettingsType) => alovaInstance.Post('/rest/mqttSettings', data); diff --git a/interface/src/api/network.ts b/interface/src/api/network.ts index 3337f36e9..ebbec684a 100644 --- a/interface/src/api/network.ts +++ b/interface/src/api/network.ts @@ -2,7 +2,8 @@ import type { NetworkSettingsType, NetworkStatusType, WiFiNetworkList } from 'ty import { alovaInstance } from './endpoints'; -export const readNetworkStatus = () => alovaInstance.Get('/rest/networkStatus'); +export const readNetworkStatus = () => + alovaInstance.Get('/rest/networkStatus'); export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks'); export const listNetworks = () => alovaInstance.Get('/rest/listNetworks', { @@ -10,6 +11,8 @@ export const listNetworks = () => timeout: 20000 // timeout 20 seconds }); export const readNetworkSettings = () => - alovaInstance.Get('/rest/networkSettings', { name: 'networkSettings' }); + alovaInstance.Get('/rest/networkSettings', { + name: 'networkSettings' + }); export const updateNetworkSettings = (wifiSettings: NetworkSettingsType) => alovaInstance.Post('/rest/networkSettings', wifiSettings); diff --git a/interface/src/api/ntp.ts b/interface/src/api/ntp.ts index 6400b5f23..69e7471b4 100644 --- a/interface/src/api/ntp.ts +++ b/interface/src/api/ntp.ts @@ -2,7 +2,8 @@ import type { NTPSettingsType, NTPStatusType, Time } from 'types'; import { alovaInstance } from './endpoints'; -export const readNTPStatus = () => alovaInstance.Get('/rest/ntpStatus'); +export const readNTPStatus = () => + alovaInstance.Get('/rest/ntpStatus'); export const readNTPSettings = () => alovaInstance.Get('/rest/ntpSettings', { name: 'ntpSettings' @@ -10,4 +11,5 @@ export const readNTPSettings = () => export const updateNTPSettings = (data: NTPSettingsType) => alovaInstance.Post('/rest/ntpSettings', data); -export const updateTime = (data: Time) => alovaInstance.Post } + control={ + + } label={LL.MQTT_RESPONSE()} /> {!data.ha_enabled && ( @@ -229,7 +280,13 @@ const MqttSettings: FC = () => { > } + control={ + + } label={LL.MQTT_PUBLISH_TEXT_1()} /> @@ -237,7 +294,11 @@ const MqttSettings: FC = () => { + } label={LL.MQTT_PUBLISH_TEXT_2()} /> @@ -246,10 +307,22 @@ const MqttSettings: FC = () => { )} {!data.publish_single && ( - + } + control={ + + } label={LL.MQTT_PUBLISH_TEXT_3()} /> @@ -312,14 +385,22 @@ const MqttSettings: FC = () => { {LL.MQTT_PUBLISH_INTERVALS()} (0=auto) - + {LL.SECONDS()} + endAdornment: ( + {LL.SECONDS()} + ) }} fullWidth variant="outlined" @@ -334,7 +415,9 @@ const MqttSettings: FC = () => { name="publish_time_boiler" label={LL.MQTT_INT_BOILER()} InputProps={{ - endAdornment: {LL.SECONDS()} + endAdornment: ( + {LL.SECONDS()} + ) }} fullWidth variant="outlined" @@ -349,7 +432,9 @@ const MqttSettings: FC = () => { name="publish_time_thermostat" label={LL.MQTT_INT_THERMOSTATS()} InputProps={{ - endAdornment: {LL.SECONDS()} + endAdornment: ( + {LL.SECONDS()} + ) }} fullWidth variant="outlined" @@ -364,7 +449,9 @@ const MqttSettings: FC = () => { name="publish_time_solar" label={LL.MQTT_INT_SOLAR()} InputProps={{ - endAdornment: {LL.SECONDS()} + endAdornment: ( + {LL.SECONDS()} + ) }} fullWidth variant="outlined" @@ -379,7 +466,9 @@ const MqttSettings: FC = () => { name="publish_time_mixer" label={LL.MQTT_INT_MIXER()} InputProps={{ - endAdornment: {LL.SECONDS()} + endAdornment: ( + {LL.SECONDS()} + ) }} fullWidth variant="outlined" @@ -394,7 +483,9 @@ const MqttSettings: FC = () => { name="publish_time_water" label={LL.MQTT_INT_WATER()} InputProps={{ - endAdornment: {LL.SECONDS()} + endAdornment: ( + {LL.SECONDS()} + ) }} fullWidth variant="outlined" @@ -409,7 +500,9 @@ const MqttSettings: FC = () => { name="publish_time_sensor" label={LL.TEMP_SENSORS()} InputProps={{ - endAdornment: {LL.SECONDS()} + endAdornment: ( + {LL.SECONDS()} + ) }} fullWidth variant="outlined" @@ -423,7 +516,9 @@ const MqttSettings: FC = () => { {LL.SECONDS()} + endAdornment: ( + {LL.SECONDS()} + ) }} label={LL.DEFAULT(0)} fullWidth diff --git a/interface/src/framework/mqtt/MqttStatus.tsx b/interface/src/framework/mqtt/MqttStatus.tsx index 9807f75d3..f171b5cee 100644 --- a/interface/src/framework/mqtt/MqttStatus.tsx +++ b/interface/src/framework/mqtt/MqttStatus.tsx @@ -5,7 +5,16 @@ import DeviceHubIcon from '@mui/icons-material/DeviceHub'; import RefreshIcon from '@mui/icons-material/Refresh'; import ReportIcon from '@mui/icons-material/Report'; import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff'; -import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material'; +import { + Avatar, + Button, + Divider, + List, + ListItem, + ListItemAvatar, + ListItemText, + useTheme +} from '@mui/material'; import type { Theme } from '@mui/material'; import * as MqttApi from 'api/mqtt'; @@ -16,7 +25,10 @@ import { useI18nContext } from 'i18n/i18n-react'; import type { MqttStatusType } from 'types'; import { MqttDisconnectReason } from 'types'; -export const mqttStatusHighlight = ({ enabled, connected }: MqttStatusType, theme: Theme) => { +export const mqttStatusHighlight = ( + { enabled, connected }: MqttStatusType, + theme: Theme +) => { if (!enabled) { return theme.palette.info.main; } @@ -26,14 +38,20 @@ export const mqttStatusHighlight = ({ enabled, connected }: MqttStatusType, them return theme.palette.error.main; }; -export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatusType, theme: Theme) => { +export const mqttPublishHighlight = ( + { mqtt_fails }: MqttStatusType, + theme: Theme +) => { if (mqtt_fails === 0) return theme.palette.success.main; if (mqtt_fails < 10) return theme.palette.warning.main; return theme.palette.error.main; }; -export const mqttQueueHighlight = ({ mqtt_queued }: MqttStatusType, theme: Theme) => { +export const mqttQueueHighlight = ( + { mqtt_queued }: MqttStatusType, + theme: Theme +) => { if (mqtt_queued <= 1) return theme.palette.success.main; return theme.palette.warning.main; @@ -92,7 +110,10 @@ const MqttStatus: FC = () => { - + @@ -140,7 +161,12 @@ const MqttStatus: FC = () => { {data.enabled && renderConnectionStatus()} - diff --git a/interface/src/framework/network/NetworkSettings.tsx b/interface/src/framework/network/NetworkSettings.tsx index f3d8bd04e..ea2318f94 100644 --- a/interface/src/framework/network/NetworkSettings.tsx +++ b/interface/src/framework/network/NetworkSettings.tsx @@ -99,7 +99,12 @@ const NetworkSettings: FC = () => { } }, [initialized, setInitialized, data, selectedNetwork]); - const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue); + const updateFormValue = updateValueDirty( + origData, + dirtyFlags, + setDirtyFlags, + updateDataValue + ); const [fieldErrors, setFieldErrors] = useState(); @@ -142,7 +147,9 @@ const NetworkSettings: FC = () => { - {isNetworkOpen(selectedNetwork) ? : } + + {isNetworkOpen(selectedNetwork) ? : } + { 2 dBm } + control={ + + } label={LL.NETWORK_DISABLE_SLEEP()} /> } + control={ + + } label={LL.NETWORK_LOW_BAND()} /> @@ -241,11 +260,23 @@ const NetworkSettings: FC = () => { margin="normal" /> } + control={ + + } label={LL.NETWORK_USE_DNS()} /> } + control={ + + } label={LL.NETWORK_ENABLE_CORS()} /> {data.enableCORS && ( @@ -261,12 +292,24 @@ const NetworkSettings: FC = () => { )} {data.enableIPv6 !== undefined && ( } + control={ + + } label={LL.NETWORK_ENABLE_IPV6()} /> )} } + control={ + + } label={LL.NETWORK_FIXED_IP()} /> {data.static_ip_config && ( @@ -325,36 +368,42 @@ const NetworkSettings: FC = () => { )} {restartNeeded && ( - )} - {!restartNeeded && (selectedNetwork || (dirtyFlags && dirtyFlags.length !== 0)) && ( - - - - - )} + {!restartNeeded && + (selectedNetwork || (dirtyFlags && dirtyFlags.length !== 0)) && ( + + + + + )} ); }; diff --git a/interface/src/framework/network/NetworkStatus.tsx b/interface/src/framework/network/NetworkStatus.tsx index 4d2322f49..782f5df75 100644 --- a/interface/src/framework/network/NetworkStatus.tsx +++ b/interface/src/framework/network/NetworkStatus.tsx @@ -8,7 +8,16 @@ import RouterIcon from '@mui/icons-material/Router'; import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna'; import SettingsInputComponentIcon from '@mui/icons-material/SettingsInputComponent'; import WifiIcon from '@mui/icons-material/Wifi'; -import { Avatar, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, useTheme } from '@mui/material'; +import { + Avatar, + Button, + Divider, + List, + ListItem, + ListItemAvatar, + ListItemText, + useTheme +} from '@mui/material'; import type { Theme } from '@mui/material'; import * as NetworkApi from 'api/network'; @@ -49,7 +58,8 @@ const networkQualityHighlight = ({ rssi }: NetworkStatusType, theme: Theme) => { return theme.palette.success.main; }; -export const isWiFi = ({ status }: NetworkStatusType) => status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED; +export const isWiFi = ({ status }: NetworkStatusType) => + status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED; export const isEthernet = ({ status }: NetworkStatusType) => status === NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED; @@ -61,7 +71,10 @@ const dnsServers = ({ dns_ip_1, dns_ip_2 }: NetworkStatusType) => { }; const IPs = (status: NetworkStatusType) => { - if (!status.local_ipv6 || status.local_ipv6 === '0000:0000:0000:0000:0000:0000:0000:0000') { + if ( + !status.local_ipv6 || + status.local_ipv6 === '0000:0000:0000:0000:0000:0000:0000:0000' + ) { return status.local_ip; } if (!status.local_ip || status.local_ip === '0.0.0.0') { @@ -71,7 +84,11 @@ const IPs = (status: NetworkStatusType) => { }; const NetworkStatus: FC = () => { - const { data: data, send: loadData, error } = useRequest(NetworkApi.readNetworkStatus); + const { + data: data, + send: loadData, + error + } = useRequest(NetworkApi.readNetworkStatus); const { LL } = useI18nContext(); @@ -135,7 +152,10 @@ const NetworkStatus: FC = () => { - + @@ -155,14 +175,20 @@ const NetworkStatus: FC = () => { - + # - + @@ -171,7 +197,10 @@ const NetworkStatus: FC = () => { - + @@ -180,14 +209,22 @@ const NetworkStatus: FC = () => { - + )} - diff --git a/interface/src/framework/network/WiFiConnectionContext.tsx b/interface/src/framework/network/WiFiConnectionContext.tsx index 9ced0a96c..4ae9feb36 100644 --- a/interface/src/framework/network/WiFiConnectionContext.tsx +++ b/interface/src/framework/network/WiFiConnectionContext.tsx @@ -9,4 +9,6 @@ export interface WiFiConnectionContextValue { } const WiFiConnectionContextDefaultValue = {} as WiFiConnectionContextValue; -export const WiFiConnectionContext = createContext(WiFiConnectionContextDefaultValue); +export const WiFiConnectionContext = createContext( + WiFiConnectionContextDefaultValue +); diff --git a/interface/src/framework/network/WiFiNetworkScanner.tsx b/interface/src/framework/network/WiFiNetworkScanner.tsx index 57c7cdfb2..3ffd32fb9 100644 --- a/interface/src/framework/network/WiFiNetworkScanner.tsx +++ b/interface/src/framework/network/WiFiNetworkScanner.tsx @@ -20,7 +20,9 @@ const WiFiNetworkScanner: FC = () => { const { LL } = useI18nContext(); const [errorMessage, setErrorMessage] = useState(); - const { send: scanNetworks, onComplete: onCompleteScanNetworks } = useRequest(NetworkApi.scanNetworks); // is called on page load to start network scan + const { send: scanNetworks, onComplete: onCompleteScanNetworks } = useRequest( + NetworkApi.scanNetworks + ); // is called on page load to start network scan const { data: networkList, send: getNetworkList, @@ -51,7 +53,9 @@ const WiFiNetworkScanner: FC = () => { const renderNetworkScanner = () => { if (!networkList) { - return ; + return ( + + ); } return ; }; diff --git a/interface/src/framework/network/WiFiNetworkSelector.tsx b/interface/src/framework/network/WiFiNetworkSelector.tsx index cadf5022e..9124a4678 100644 --- a/interface/src/framework/network/WiFiNetworkSelector.tsx +++ b/interface/src/framework/network/WiFiNetworkSelector.tsx @@ -4,7 +4,16 @@ import type { FC } from 'react'; import LockIcon from '@mui/icons-material/Lock'; import LockOpenIcon from '@mui/icons-material/LockOpen'; import WifiIcon from '@mui/icons-material/Wifi'; -import { Avatar, Badge, List, ListItem, ListItemAvatar, ListItemIcon, ListItemText, useTheme } from '@mui/material'; +import { + Avatar, + Badge, + List, + ListItem, + ListItemAvatar, + ListItemIcon, + ListItemText, + useTheme +} from '@mui/material'; import type { Theme } from '@mui/material'; import { MessageBox } from 'components'; @@ -60,14 +69,22 @@ const WiFiNetworkSelector: FC = ({ networkList }) => { const wifiConnectionContext = useContext(WiFiConnectionContext); const renderNetwork = (network: WiFiNetwork) => ( - wifiConnectionContext.selectNetwork(network)}> + wifiConnectionContext.selectNetwork(network)} + > {isNetworkOpen(network) ? : } diff --git a/interface/src/framework/ntp/NTPSettings.tsx b/interface/src/framework/ntp/NTPSettings.tsx index 51982c5d4..b24f22633 100644 --- a/interface/src/framework/ntp/NTPSettings.tsx +++ b/interface/src/framework/ntp/NTPSettings.tsx @@ -44,7 +44,12 @@ const NTPSettings: FC = () => { const { LL } = useI18nContext(); - const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue); + const updateFormValue = updateValueDirty( + origData, + dirtyFlags, + setDirtyFlags, + updateDataValue + ); const [fieldErrors, setFieldErrors] = useState(); @@ -76,7 +81,13 @@ const NTPSettings: FC = () => { return ( <> } + control={ + + } label={LL.ENABLE_NTP()} /> { const { LL } = useI18nContext(); - const { send: updateTime } = useRequest((local_time: Time) => NTPApi.updateTime(local_time), { - immediate: false - }); + const { send: updateTime } = useRequest( + (local_time: Time) => NTPApi.updateTime(local_time), + { + immediate: false + } + ); NTPApi.updateTime; - const isNtpActive = ({ status }: NTPStatusType) => status === NTPSyncStatus.NTP_ACTIVE; - const isNtpEnabled = ({ status }: NTPStatusType) => status !== NTPSyncStatus.NTP_DISABLED; + const isNtpActive = ({ status }: NTPStatusType) => + status === NTPSyncStatus.NTP_ACTIVE; + const isNtpEnabled = ({ status }: NTPStatusType) => + status !== NTPSyncStatus.NTP_DISABLED; const ntpStatusHighlight = ({ status }: NTPStatusType, theme: Theme) => { switch (status) { @@ -68,7 +73,8 @@ const NTPStatus: FC = () => { } }; - const updateLocalTime = (event: React.ChangeEvent) => setLocalTime(event.target.value); + const updateLocalTime = (event: React.ChangeEvent) => + setLocalTime(event.target.value); const openSetTime = () => { setLocalTime(formatLocalDateTime(new Date())); @@ -108,7 +114,11 @@ const NTPStatus: FC = () => { }; const renderSetTimeDialog = () => ( - setSettingTime(false)}> + setSettingTime(false)} + > {LL.SET_TIME(1)} @@ -127,7 +137,12 @@ const NTPStatus: FC = () => { /> - @@ -203,7 +229,12 @@ const NTPStatus: FC = () => { {data && !isNtpActive(data) && ( - diff --git a/interface/src/framework/ota/OTASettings.tsx b/interface/src/framework/ota/OTASettings.tsx index ef0d348d8..f6770a646 100644 --- a/interface/src/framework/ota/OTASettings.tsx +++ b/interface/src/framework/ota/OTASettings.tsx @@ -43,7 +43,12 @@ const OTASettings: FC = () => { const { LL } = useI18nContext(); - const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue); + const updateFormValue = updateValueDirty( + origData, + dirtyFlags, + setDirtyFlags, + updateDataValue + ); const [fieldErrors, setFieldErrors] = useState(); @@ -67,7 +72,13 @@ const OTASettings: FC = () => { return ( <> } + control={ + + } label={LL.ENABLE_OTA()} /> = ({ username, onClose }) => { const { LL } = useI18nContext(); const open = !!username; - const { data: token, send: generateToken } = useRequest(SecurityApi.generateToken(username), { - immediate: false - }); + const { data: token, send: generateToken } = useRequest( + SecurityApi.generateToken(username), + { + immediate: false + } + ); useEffect(() => { if (open) { @@ -41,14 +44,26 @@ const GenerateToken: FC = ({ username, onClose }) => { }, [open]); return ( - + {LL.ACCESS_TOKEN_FOR() + ' ' + username} {token ? ( <> - + ) : ( @@ -59,7 +74,12 @@ const GenerateToken: FC = ({ username, onClose }) => { )} - diff --git a/interface/src/framework/security/ManageUsers.tsx b/interface/src/framework/security/ManageUsers.tsx index 209a9dfab..c49c20bc5 100644 --- a/interface/src/framework/security/ManageUsers.tsx +++ b/interface/src/framework/security/ManageUsers.tsx @@ -14,9 +14,23 @@ import { Box, Button, IconButton } from '@mui/material'; import * as SecurityApi from 'api/security'; -import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table'; +import { + Body, + Cell, + Header, + HeaderCell, + HeaderRow, + Row, + Table +} from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; -import { BlockNavigation, ButtonRow, FormLoader, MessageBox, SectionContent } from 'components'; +import { + BlockNavigation, + ButtonRow, + FormLoader, + MessageBox, + SectionContent +} from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; import type { SecuritySettingsType, UserType } from 'types'; @@ -27,10 +41,11 @@ import GenerateToken from './GenerateToken'; import User from './User'; const ManageUsers: FC = () => { - const { loadData, saveData, saving, data, updateDataValue, errorMessage } = useRest({ - read: SecurityApi.readSecuritySettings, - update: SecurityApi.updateSecuritySettings - }); + const { loadData, saveData, saving, data, updateDataValue, errorMessage } = + useRest({ + read: SecurityApi.readSecuritySettings, + update: SecurityApi.updateSecuritySettings + }); const [user, setUser] = useState(); const [creating, setCreating] = useState(false); @@ -114,7 +129,12 @@ const ManageUsers: FC = () => { const doneEditingUser = () => { if (user) { - const users = [...data.users.filter((u: { username: string }) => u.username !== user.username), user]; + const users = [ + ...data.users.filter( + (u: { username: string }) => u.username !== user.username + ), + user + ]; updateDataValue({ ...data, users }); setUser(undefined); setChanged(changed + 1); @@ -148,11 +168,18 @@ const ManageUsers: FC = () => { } // add id to the type, needed for the table - const user_table = data.users.map((u) => ({ ...u, id: u.username })) as UserType2[]; + const user_table = data.users.map((u) => ({ + ...u, + id: u.username + })) as UserType2[]; return ( <> - +
{(tableList: UserType2[]) => ( <>
@@ -189,7 +216,9 @@ const ManageUsers: FC = () => { )}
- {noAdminConfigured() && } + {noAdminConfigured() && ( + + )} @@ -221,7 +250,12 @@ const ManageUsers: FC = () => { - diff --git a/interface/src/framework/security/SecuritySettings.tsx b/interface/src/framework/security/SecuritySettings.tsx index 49bee2aa3..2b49e17db 100644 --- a/interface/src/framework/security/SecuritySettings.tsx +++ b/interface/src/framework/security/SecuritySettings.tsx @@ -8,7 +8,14 @@ import { Button } from '@mui/material'; import * as SecurityApi from 'api/security'; import type { ValidateFieldsError } from 'async-validator'; -import { BlockNavigation, ButtonRow, FormLoader, MessageBox, SectionContent, ValidatedPasswordField } from 'components'; +import { + BlockNavigation, + ButtonRow, + FormLoader, + MessageBox, + SectionContent, + ValidatedPasswordField +} from 'components'; import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; import type { SecuritySettingsType } from 'types'; @@ -37,7 +44,12 @@ const SecuritySettings: FC = () => { const authenticatedContext = useContext(AuthenticatedContext); - const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue); + const updateFormValue = updateValueDirty( + origData, + dirtyFlags, + setDirtyFlags, + updateDataValue + ); const content = () => { if (!data) { diff --git a/interface/src/framework/security/User.tsx b/interface/src/framework/security/User.tsx index 464d86a9f..9d234abda 100644 --- a/interface/src/framework/security/User.tsx +++ b/interface/src/framework/security/User.tsx @@ -4,12 +4,23 @@ import type { FC } from 'react'; import CancelIcon from '@mui/icons-material/Cancel'; import PersonAddIcon from '@mui/icons-material/PersonAdd'; import SaveIcon from '@mui/icons-material/Save'; -import { Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material'; +import { + Button, + Checkbox, + Dialog, + DialogActions, + DialogContent, + DialogTitle +} from '@mui/material'; import { dialogStyle } from 'CustomTheme'; import type Schema from 'async-validator'; import type { ValidateFieldsError } from 'async-validator'; -import { BlockFormControlLabel, ValidatedPasswordField, ValidatedTextField } from 'components'; +import { + BlockFormControlLabel, + ValidatedPasswordField, + ValidatedTextField +} from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import type { UserType } from 'types'; import { updateValue } from 'utils'; @@ -26,7 +37,14 @@ interface UserFormProps { onCancelEditing: () => void; } -const User: FC = ({ creating, validator, user, setUser, onDoneEditing, onCancelEditing }) => { +const User: FC = ({ + creating, + validator, + user, + setUser, + onDoneEditing, + onCancelEditing +}) => { const { LL } = useI18nContext(); const updateFormValue = updateValue(setUser); @@ -52,7 +70,13 @@ const User: FC = ({ creating, validator, user, setUser, onDoneEdi }; return ( - + {user && ( <> @@ -81,12 +105,23 @@ const User: FC = ({ creating, validator, user, setUser, onDoneEdi margin="normal" /> } + control={ + + } label={LL.IS_ADMIN(1)} /> - diff --git a/interface/src/framework/system/RestartMonitor.tsx b/interface/src/framework/system/RestartMonitor.tsx index f8870d180..9abe17a3a 100644 --- a/interface/src/framework/system/RestartMonitor.tsx +++ b/interface/src/framework/system/RestartMonitor.tsx @@ -36,7 +36,12 @@ const RestartMonitor: FC = () => { useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]); - return ; + return ( + + ); }; export default RestartMonitor; diff --git a/interface/src/framework/system/System.tsx b/interface/src/framework/system/System.tsx index f0023a682..0cc0c28d6 100644 --- a/interface/src/framework/system/System.tsx +++ b/interface/src/framework/system/System.tsx @@ -24,7 +24,11 @@ const System: FC = () => { - + } /> diff --git a/interface/src/framework/system/SystemLog.tsx b/interface/src/framework/system/SystemLog.tsx index a61ded90f..855fb1a74 100644 --- a/interface/src/framework/system/SystemLog.tsx +++ b/interface/src/framework/system/SystemLog.tsx @@ -4,14 +4,28 @@ import { toast } from 'react-toastify'; import DownloadIcon from '@mui/icons-material/GetApp'; import WarningIcon from '@mui/icons-material/Warning'; -import { Box, Button, Checkbox, Grid, MenuItem, TextField, styled } from '@mui/material'; +import { + Box, + Button, + Checkbox, + Grid, + MenuItem, + TextField, + styled +} from '@mui/material'; import * as SystemApi from 'api/system'; import { fetchLogES } from 'api/system'; import { useSSE } from '@alova/scene-react'; import { useRequest } from 'alova'; -import { BlockFormControlLabel, BlockNavigation, FormLoader, SectionContent, useLayoutTitle } from 'components'; +import { + BlockFormControlLabel, + BlockNavigation, + FormLoader, + SectionContent, + useLayoutTitle +} from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import type { LogEntry, LogSettings } from 'types'; import { LogLevel } from 'types'; @@ -25,8 +39,10 @@ const LogEntryLine = styled('div')(() => ({ whiteSpace: 'nowrap' })); -const topOffset = () => document.getElementById('log-window')?.getBoundingClientRect().bottom || 0; -const leftOffset = () => document.getElementById('log-window')?.getBoundingClientRect().left || 0; +const topOffset = () => + document.getElementById('log-window')?.getBoundingClientRect().bottom || 0; +const leftOffset = () => + document.getElementById('log-window')?.getBoundingClientRect().left || 0; const levelLabel = (level: LogLevel) => { switch (level) { @@ -50,16 +66,30 @@ const SystemLog: FC = () => { useLayoutTitle(LL.LOG_OF('')); - const { loadData, data, updateDataValue, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } = - useRest({ - read: SystemApi.readLogSettings, - update: SystemApi.updateLogSettings - }); + const { + loadData, + data, + updateDataValue, + origData, + dirtyFlags, + setDirtyFlags, + blocker, + saveData, + errorMessage + } = useRest({ + read: SystemApi.readLogSettings, + update: SystemApi.updateLogSettings + }); const [logEntries, setLogEntries] = useState([]); const [lastIndex, setLastIndex] = useState(0); - const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue); + const updateFormValue = updateValueDirty( + origData, + dirtyFlags, + setDirtyFlags, + updateDataValue + ); // eslint-disable-next-line @typescript-eslint/unbound-method const { onMessage, onError } = useSSE(fetchLogES, { @@ -102,10 +132,14 @@ const SystemLog: FC = () => { const onDownload = () => { let result = ''; for (const i of logEntries) { - result += i.t + ' ' + levelLabel(i.l) + ' ' + i.i + ': [' + i.n + '] ' + i.m + '\n'; + result += + i.t + ' ' + levelLabel(i.l) + ' ' + i.i + ': [' + i.n + '] ' + i.m + '\n'; } const a = document.createElement('a'); - a.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result)); + a.setAttribute( + 'href', + 'data:text/plain;charset=utf-8,' + encodeURIComponent(result) + ); a.setAttribute('download', 'log.txt'); document.body.appendChild(a); a.click(); @@ -134,7 +168,13 @@ const SystemLog: FC = () => { return ( <> - + { } + control={ + + } label={LL.COMPACT()} /> @@ -185,7 +231,12 @@ const SystemLog: FC = () => { } }} > - {dirtyFlags && dirtyFlags.length !== 0 && ( diff --git a/interface/src/framework/system/SystemStatus.tsx b/interface/src/framework/system/SystemStatus.tsx index 30ac6b0e6..8a83ef884 100644 --- a/interface/src/framework/system/SystemStatus.tsx +++ b/interface/src/framework/system/SystemStatus.tsx @@ -49,7 +49,11 @@ const SystemStatus: FC = () => { const [confirmScan, setConfirmScan] = useState(false); - const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus, { force: true }); + const { + data: data, + send: loadData, + error + } = useRequest(SystemApi.readSystemStatus, { force: true }); const { send: scanDevices } = useRequest(EMSESP.scanDevices, { immediate: false @@ -134,7 +138,8 @@ const SystemStatus: FC = () => { } }; - const activeHighlight = (value: boolean) => (value ? theme.palette.success.main : theme.palette.info.main); + const activeHighlight = (value: boolean) => + value ? theme.palette.success.main : theme.palette.info.main; const scan = async () => { await scanDevices() @@ -148,14 +153,28 @@ const SystemStatus: FC = () => { }; const renderScanDialog = () => ( - setConfirmScan(false)}> + setConfirmScan(false)} + > {LL.SCAN_DEVICES()} {LL.EMS_SCAN()} - - @@ -282,7 +301,12 @@ const SystemStatus: FC = () => { {renderScanDialog()} - diff --git a/interface/src/framework/system/UploadDownload.tsx b/interface/src/framework/system/UploadDownload.tsx index 0f7e46a44..4e5bac899 100644 --- a/interface/src/framework/system/UploadDownload.tsx +++ b/interface/src/framework/system/UploadDownload.tsx @@ -8,7 +8,12 @@ import * as SystemApi from 'api/system'; import * as EMSESP from 'project/api'; import { useRequest } from 'alova'; -import { FormLoader, SectionContent, SingleUpload, useLayoutTitle } from 'components'; +import { + FormLoader, + SectionContent, + SingleUpload, + useLayoutTitle +} from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import type { APIcall } from 'project/types'; @@ -19,23 +24,40 @@ const UploadDownload: FC = () => { const [restarting, setRestarting] = useState(); const [md5, setMd5] = useState(); - const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(EMSESP.getSettings(), { - immediate: false - }); - const { send: getCustomizations, onSuccess: onSuccessGetCustomizations } = useRequest(EMSESP.getCustomizations(), { - immediate: false - }); - const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest(EMSESP.getEntities(), { - immediate: false - }); - const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), { - immediate: false - }); - const { send: getAPI, onSuccess: onGetAPI } = useRequest((data: APIcall) => EMSESP.API(data), { - immediate: false - }); + const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest( + EMSESP.getSettings(), + { + immediate: false + } + ); + const { send: getCustomizations, onSuccess: onSuccessGetCustomizations } = + useRequest(EMSESP.getCustomizations(), { + immediate: false + }); + const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest( + EMSESP.getEntities(), + { + immediate: false + } + ); + const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest( + EMSESP.getSchedule(), + { + immediate: false + } + ); + const { send: getAPI, onSuccess: onGetAPI } = useRequest( + (data: APIcall) => EMSESP.API(data), + { + immediate: false + } + ); - const { data: data, send: loadData, error } = useRequest(SystemApi.readESPSystemStatus, { force: true }); + const { + data: data, + send: loadData, + error + } = useRequest(SystemApi.readESPSystemStatus, { force: true }); const { data: latestVersion } = useRequest(SystemApi.getStableVersion, { immediate: true, @@ -50,11 +72,17 @@ const UploadDownload: FC = () => { const STABLE_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/'; const DEV_URL = 'https://github.com/emsesp/EMS-ESP32/releases/download/latest/'; - const STABLE_RELNOTES_URL = 'https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md'; - const DEV_RELNOTES_URL = 'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md'; + const STABLE_RELNOTES_URL = + 'https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md'; + const DEV_RELNOTES_URL = + 'https://github.com/emsesp/EMS-ESP32/blob/dev/CHANGELOG_LATEST.md'; const getBinURL = (v: string) => - 'EMS-ESP-' + v.replaceAll('.', '_') + '-' + data.esp_platform.replaceAll('-', '_') + '.bin'; + 'EMS-ESP-' + + v.replaceAll('.', '_') + + '-' + + data.esp_platform.replaceAll('-', '_') + + '.bin'; const { loading: isUploading, @@ -115,8 +143,11 @@ const UploadDownload: FC = () => { saveFile(event.data, 'schedule.json'); }); onGetAPI((event) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - saveFile(event.data, event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt'); + saveFile( + event.data, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt' + ); }); const downloadSettings = async () => { @@ -170,7 +201,8 @@ const UploadDownload: FC = () => { {data.emsesp_version} ({data.esp_platform}) {latestVersion && ( - {LL.THE_LATEST()} {LL.OFFICIAL()} {LL.RELEASE_IS()} {latestVersion} + {LL.THE_LATEST()} {LL.OFFICIAL()} {LL.RELEASE_IS()} +  {latestVersion}  ( {LL.RELEASE_NOTES()} @@ -178,7 +210,13 @@ const UploadDownload: FC = () => { ) ( {LL.DOWNLOAD(1)} @@ -188,14 +226,19 @@ const UploadDownload: FC = () => { )} {latestDevVersion && ( - {LL.THE_LATEST()} {LL.DEVELOPMENT()} {LL.RELEASE_IS()}  + {LL.THE_LATEST()} {LL.DEVELOPMENT()} {LL.RELEASE_IS()} +   {latestDevVersion}  ( {LL.RELEASE_NOTES()} ) ( - + {LL.DOWNLOAD(1)} ) @@ -219,7 +262,12 @@ const UploadDownload: FC = () => { {'MD5: ' + md5} )} - + {!isUploading && ( <> @@ -307,7 +355,9 @@ const UploadDownload: FC = () => { ); }; - return {restarting ? : content()}; + return ( + {restarting ? : content()} + ); }; export default UploadDownload; diff --git a/interface/src/i18n/de/index.ts b/interface/src/i18n/de/index.ts index 981b79d08..91bbd9ab4 100644 --- a/interface/src/i18n/de/index.ts +++ b/interface/src/i18n/de/index.ts @@ -1,7 +1,5 @@ import type { Translation } from '../i18n-types'; -/* prettier-ignore */ - const de: Translation = { LANGUAGE: 'Sprache', RETRY: 'Neuer Versuch', @@ -208,7 +206,8 @@ const de: Translation = { USER_WARNING: 'Sie müssen mindestens einen Admin-Nutzer konfigurieren', ADD: 'Hinzufügen', ACCESS_TOKEN_FOR: 'Zugangs-Token für', - ACCESS_TOKEN_TEXT: 'Dieses Token ist für REST API Aufrufe bestimmt, die eine Authentifizierung benötigen. Es kann entweder als Bearer Token im `Authorization-Header` oder in der Access_Token URL verwendet werden.', + ACCESS_TOKEN_TEXT: + 'Dieses Token ist für REST API Aufrufe bestimmt, die eine Authentifizierung benötigen. Es kann entweder als Bearer Token im `Authorization-Header` oder in der Access_Token URL verwendet werden.', GENERATING_TOKEN: 'Erzeuge Token', USER: 'Nutzer', MODIFY: 'Ändern', diff --git a/interface/src/i18n/en/index.ts b/interface/src/i18n/en/index.ts index 4f110d52f..fe65104ef 100644 --- a/interface/src/i18n/en/index.ts +++ b/interface/src/i18n/en/index.ts @@ -1,7 +1,5 @@ import type { Translation } from '../i18n-types'; -/* prettier-ignore */ - const en: Translation = { LANGUAGE: 'Language', RETRY: 'Retry', @@ -208,7 +206,8 @@ const en: Translation = { USER_WARNING: 'You must have at least one admin user configured', ADD: 'Add', ACCESS_TOKEN_FOR: 'Access Token for', - ACCESS_TOKEN_TEXT: 'The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.', + ACCESS_TOKEN_TEXT: + 'The token below is used with REST API calls that require authorization. It can be passed either as a Bearer token in the Authorization header or in the access_token URL query parameter.', GENERATING_TOKEN: 'Generating token', USER: 'User', MODIFY: 'Modify', @@ -325,10 +324,10 @@ const en: Translation = { ALWAYS: 'Always', ACTIVITY: 'Activity', CONFIGURE: 'Configure {0}', - SYSTEM_MEMORY: 'System Memory', - APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', - SECURITY_1: 'Add or remove users', - UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', + SYSTEM_MEMORY: 'System Memory', + APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', + SECURITY_1: 'Add or remove users', + UPLOAD_DOWNLOAD_1: 'Upload/Download Settings and Firmware', MODULE: 'Module' // TODO translate }; diff --git a/interface/src/i18n/fr/index.ts b/interface/src/i18n/fr/index.ts index c8da394db..316f44122 100644 --- a/interface/src/i18n/fr/index.ts +++ b/interface/src/i18n/fr/index.ts @@ -1,7 +1,5 @@ import type { Translation } from '../i18n-types'; -/* prettier-ignore */ - const fr: Translation = { LANGUAGE: 'Langue', RETRY: 'Réessayer', @@ -9,7 +7,7 @@ const fr: Translation = { IS_REQUIRED: '{0} est requis', SIGN_IN: 'Se connecter', SIGN_OUT: 'Se déconnecter', - USERNAME: 'Nom d\'utilisateur', + USERNAME: "Nom d'utilisateur", PASSWORD: 'Mot de passe', SU_PASSWORD: 'Mot de passe su', SETTINGS_OF: 'Paramètres {0}', @@ -28,13 +26,13 @@ const fr: Translation = { ENTITIES: 'Entités', REFRESH: 'Rafraîchir', EXPORT: 'Exporter', - DEVICE_DETAILS: 'Détails de l\'appareil', + DEVICE_DETAILS: "Détails de l'appareil", ID_OF: 'ID {0}', DEVICE: 'Appareil', PRODUCT: 'Produit', VERSION: 'Version', BRAND: 'Marque', - ENTITY_NAME: 'Nom de l\'entité', + ENTITY_NAME: "Nom de l'entité", VALUE: 'Valeur', DEVICES: 'Appareils', SENSORS: 'Capteurs', @@ -88,7 +86,7 @@ const fr: Translation = { 'Lectures capteurs de température', 'Lectures capteurs analogiques', 'Publications MQTT', - 'Appels à l\'API', + "Appels à l'API", 'Messages Syslog' ], NUM_DEVICES: '{num} Appareil{{s}}', @@ -98,11 +96,11 @@ const fr: Translation = { NUM_SECONDS: '{num} seconde{{s}}', NUM_HOURS: '{num} heure{{s}}', NUM_MINUTES: '{num} minute{{s}}', - APPLICATION_SETTINGS: 'Paramètres de l\'application', + APPLICATION_SETTINGS: "Paramètres de l'application", CUSTOMIZATIONS: 'Personnalisation', APPLICATION_RESTARTING: 'EMS-ESP redémarre', - INTERFACE_BOARD_PROFILE: 'Profile de carte d\'interface', - BOARD_PROFILE_TEXT: 'Sélectionnez un profil de carte d\'interface préconfiguré dans la liste ci-dessous ou choisissez Personnalisé pour configurer vos propres paramètres matériels', + INTERFACE_BOARD_PROFILE: "Profile de carte d'interface", + BOARD_PROFILE_TEXT: "Sélectionnez un profil de carte d'interface préconfiguré dans la liste ci-dessous ou choisissez Personnalisé pour configurer vos propres paramètres matériels", BOARD_PROFILE: 'Profil de carte', CUSTOM: 'Personnalisé', GPIO_OF: 'GPIO {0}', @@ -119,14 +117,14 @@ const fr: Translation = { ENABLE_TELNET: 'Activer la console Telnet', ENABLE_ANALOG: 'Activer les capteurs analogiques', CONVERT_FAHRENHEIT: 'Convertir les températures en Fahrenheit', - BYPASS_TOKEN: 'Contourner l\'autorisation du jeton d\'accès sur les appels API', + BYPASS_TOKEN: "Contourner l'autorisation du jeton d'accès sur les appels API", READONLY: 'Activer le mode lecture uniquement (bloque toutes les commandes EMS sortantes en écriture Tx)', UNDERCLOCK_CPU: 'Underclock du CPU', HEATINGOFF: 'Start boiler with forced heating off', // TODO translate ENABLE_SHOWER_TIMER: 'Activer la minuterie de la douche', ENABLE_SHOWER_ALERT: 'Activer les alertes de durée de douche', TRIGGER_TIME: 'Durée avant déclenchement', - COLD_SHOT_DURATION: 'Durée du coup d\'eau froide', + COLD_SHOT_DURATION: "Durée du coup d'eau froide", FORMATTING_OPTIONS: 'Options de mise en forme', BOOLEAN_FORMAT_DASHBOARD: 'Tableau de bord du format booléen', BOOLEAN_FORMAT_API: 'Format booléen API/MQTT', @@ -150,8 +148,8 @@ const fr: Translation = { CUSTOMIZATIONS_SAVED: 'Personnalisations enregistrées', CUSTOMIZATIONS_HELP_1: 'Sélectionnez un appareil et personnalisez les options des entités ou cliquez pour renommer', CUSTOMIZATIONS_HELP_2: 'marquer comme favori', - CUSTOMIZATIONS_HELP_3: 'désactiver l\'action d\'écriture', - CUSTOMIZATIONS_HELP_4: 'exclure de MQTT et de l\'API', + CUSTOMIZATIONS_HELP_3: "désactiver l'action d'écriture", + CUSTOMIZATIONS_HELP_4: "exclure de MQTT et de l'API", CUSTOMIZATIONS_HELP_5: 'cacher du Tableau de bord', CUSTOMIZATIONS_HELP_6: 'remove from memory', // TODO translate SELECT_DEVICE: 'Sélectionnez un appareil', @@ -163,7 +161,7 @@ const fr: Translation = { HELP_INFORMATION_1: 'Visitez le wiki en ligne pour obtenir des instructions sur la façon de configurer EMS-ESP.', HELP_INFORMATION_2: 'Pour une discussion en direct avec la communauté, rejoignez notre serveur Discord', HELP_INFORMATION_3: 'Pour demander une fonctionnalité ou signaler un problème', - HELP_INFORMATION_4: 'N\'oubliez pas de télécharger et de joindre les informations relatives à votre système pour obtenir une réponse plus rapide lorsque vous signalez un problème', + HELP_INFORMATION_4: "N'oubliez pas de télécharger et de joindre les informations relatives à votre système pour obtenir une réponse plus rapide lorsque vous signalez un problème", HELP_INFORMATION_5: 'EMS-ESP est un projet libre et open-source. Merci de soutenir son développement futur en lui donnant une étoile sur Github !', UPLOAD: 'Upload', DOWNLOAD: '{{D|d|d}}ownload', @@ -178,8 +176,8 @@ const fr: Translation = { CLOSE: 'Fermer', USE: 'Utiliser', FACTORY_RESET: 'Réinitialisation', - SYSTEM_FACTORY_TEXT: 'L\'appareil a été réinitialisé et va maintenant redémarrer', - SYSTEM_FACTORY_TEXT_DIALOG: 'Êtes-vous sûr de vouloir réinitialiser l\'appareil à ses paramètres d\'usine ?', + SYSTEM_FACTORY_TEXT: "L'appareil a été réinitialisé et va maintenant redémarrer", + SYSTEM_FACTORY_TEXT_DIALOG: "Êtes-vous sûr de vouloir réinitialiser l'appareil à ses paramètres d'usine ?", THE_LATEST: 'La dernière', OFFICIAL: 'officielle', DEVELOPMENT: 'développement', @@ -195,10 +193,12 @@ const fr: Translation = { BUFFER_SIZE: 'Max taille du buffer', COMPACT: 'Compact', ENABLE_OTA: 'Activer les updates OTA', - DOWNLOAD_CUSTOMIZATION_TEXT: 'Télécharger les personnalisations d\'entités', + DOWNLOAD_CUSTOMIZATION_TEXT: "Télécharger les personnalisations d'entités", DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate - DOWNLOAD_SETTINGS_TEXT: 'Téléchargez les paramètres de l\'application. Soyez prudent lorsque vous partagez vos paramètres car ce fichier contient des mots de passe et d\'autres informations système sensibles.', - UPLOAD_TEXT: 'Téléchargez un nouveau fichier de firmware (.bin), un fichier de paramètres ou de personnalisations (.json) ci-dessous, pour une validation optionnelle téléchargez d\'abord un fichier (.md5)', + DOWNLOAD_SETTINGS_TEXT: + "Téléchargez les paramètres de l'application. Soyez prudent lorsque vous partagez vos paramètres car ce fichier contient des mots de passe et d'autres informations système sensibles.", + UPLOAD_TEXT: + "Téléchargez un nouveau fichier de firmware (.bin), un fichier de paramètres ou de personnalisations (.json) ci-dessous, pour une validation optionnelle téléchargez d'abord un fichier (.md5)", UPLOADING: 'Téléchargement', UPLOAD_DROP_TEXT: 'Déposer le fichier ou cliquer ici', ERROR: 'Erreur inattendue, veuillez réessayer', @@ -207,12 +207,13 @@ const fr: Translation = { IS_ADMIN: 'admin', USER_WARNING: 'Vous devez avoir au moins un utilisateur admin configuré', ADD: 'Ajouter', - ACCESS_TOKEN_FOR: 'Jeton d\'accès pour', - ACCESS_TOKEN_TEXT: 'Le jeton ci-dessous est utilisé avec les appels d\'API REST qui nécessitent une autorisation. Il peut être passé soit en tant que jeton Bearer dans l\'en-tête Authorization, soit dans le paramètre de requête URL access_token.', + ACCESS_TOKEN_FOR: "Jeton d'accès pour", + ACCESS_TOKEN_TEXT: + "Le jeton ci-dessous est utilisé avec les appels d'API REST qui nécessitent une autorisation. Il peut être passé soit en tant que jeton Bearer dans l'en-tête Authorization, soit dans le paramètre de requête URL access_token.", GENERATING_TOKEN: 'Génération de jeton', USER: 'Utilisateur', MODIFY: 'Modifier', - SU_TEXT: 'Le mot de passe su (super utilisateur) est utilisé pour signer les jetons d\'authentification et activer les privilèges d\'administrateur dans la console.', + SU_TEXT: "Le mot de passe su (super utilisateur) est utilisé pour signer les jetons d'authentification et activer les privilèges d'administrateur dans la console.", NOT_ENABLED: 'Non activé', ERRORS_OF: 'Erreurs {0}', DISCONNECT_REASON: 'Raison de la déconnexion', @@ -240,7 +241,7 @@ const fr: Translation = { MQTT_QUEUE: 'Queue MQTT', DEFAULT: 'Défaut', MQTT_ENTITY_FORMAT: 'Entity ID format', // TODO translate - MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)',// TODO translate + MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)', // TODO translate MQTT_ENTITY_FORMAT_1: 'Single instance, short name', // TODO translate MQTT_ENTITY_FORMAT_2: 'Multiple instances, short name', // TODO translate MQTT_CLEAN_SESSION: 'Flag Clean Session', @@ -248,15 +249,15 @@ const fr: Translation = { INACTIVE: 'Inactif', ACTIVE: 'Actif', UNKNOWN: 'Inconnu', - SET_TIME: 'Définir l\'heure', - SET_TIME_TEXT: 'Entrer la date et l\'heure locale ci-dessous pour régler l\'heure', + SET_TIME: "Définir l'heure", + SET_TIME_TEXT: "Entrer la date et l'heure locale ci-dessous pour régler l'heure", LOCAL_TIME: 'Heure locale', UTC_TIME: 'Heure UTC', ENABLE_NTP: 'Activer le NTP', NTP_SERVER: 'Serveur NTP', TIME_ZONE: 'Fuseau horaire', - ACCESS_POINT: 'Point d\'accès', - AP_PROVIDE: 'Activer le Point d\'Accès', + ACCESS_POINT: "Point d'accès", + AP_PROVIDE: "Activer le Point d'Accès", AP_PROVIDE_TEXT_1: 'toujours', AP_PROVIDE_TEXT_2: 'quand le WiFi est déconnecté', AP_PROVIDE_TEXT_3: 'jamais', @@ -275,13 +276,13 @@ const fr: Translation = { NETWORK_BLANK_SSID: 'laisser vide pour désactiver le WiFi', // and enable ETH // TODO translate NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate TX_POWER: 'Puissance Tx', - HOSTNAME: 'Nom d\'hôte', + HOSTNAME: "Nom d'hôte", NETWORK_DISABLE_SLEEP: 'Désactiver le mode veille du WiFi', NETWORK_LOW_BAND: 'Utiliser une bande passante WiFi plus faible', NETWORK_USE_DNS: 'Activer le service mDNS', NETWORK_ENABLE_CORS: 'Activer CORS', NETWORK_CORS_ORIGIN: 'Origine CORS', - NETWORK_ENABLE_IPV6: 'Activer le support de l\'IPv6', + NETWORK_ENABLE_IPV6: "Activer le support de l'IPv6", NETWORK_FIXED_IP: 'Utiliser une adresse IP fixe', NETWORK_GATEWAY: 'Passerelle', NETWORK_SUBNET: 'Masque de sous-réseau', diff --git a/interface/src/i18n/it/index.ts b/interface/src/i18n/it/index.ts index 8e94b2fe3..46d3348fb 100644 --- a/interface/src/i18n/it/index.ts +++ b/interface/src/i18n/it/index.ts @@ -1,7 +1,5 @@ import type { Translation } from '../i18n-types'; -/* prettier-ignore */ - const it: Translation = { LANGUAGE: 'Lingua', RETRY: 'Riprovare', @@ -102,7 +100,8 @@ const it: Translation = { CUSTOMIZATIONS: 'Personalizzazione', APPLICATION_RESTARTING: 'EMS-ESP sta riavviando', INTERFACE_BOARD_PROFILE: 'Profilo scheda di interfaccia', - BOARD_PROFILE_TEXT: 'Selezionare un profilo di interfaccia pre-configurato dalla lista sottostante o scegliere un profilo personalizzato per configurare le impostazioni del tuo hardware', + BOARD_PROFILE_TEXT: + 'Selezionare un profilo di interfaccia pre-configurato dalla lista sottostante o scegliere un profilo personalizzato per configurare le impostazioni del tuo hardware', BOARD_PROFILE: 'Profilo Scheda', CUSTOM: 'Personalizzazione', GPIO_OF: 'GPIO {0}', @@ -197,8 +196,10 @@ const it: Translation = { ENABLE_OTA: 'Abilita aggiornamenti OTA', DOWNLOAD_CUSTOMIZATION_TEXT: 'Scarica personalizzazioni entità', DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', - DOWNLOAD_SETTINGS_TEXT: 'Scarica le impostazioni dell applicazione. Fai attenzione quando condividi le tue impostazioni poiché questo file contiene password e altre informazioni di sistema riservate', - UPLOAD_TEXT: 'Carica un nuovo file firmware (.bin) , file delle impostazioni o delle personalizzazioni (.json) di seguito, per un opzione di convalida scaricare dapprima un file "*.MD5" ', + DOWNLOAD_SETTINGS_TEXT: + 'Scarica le impostazioni dell applicazione. Fai attenzione quando condividi le tue impostazioni poiché questo file contiene password e altre informazioni di sistema riservate', + UPLOAD_TEXT: + 'Carica un nuovo file firmware (.bin) , file delle impostazioni o delle personalizzazioni (.json) di seguito, per un opzione di convalida scaricare dapprima un file "*.MD5" ', UPLOADING: 'Caricamento', UPLOAD_DROP_TEXT: 'Trascina il file o clicca qui', ERROR: 'Errore Inaspettato, prego tenta ancora', @@ -208,7 +209,8 @@ const it: Translation = { USER_WARNING: 'Devi avere configurato almeno un utente amministratore', ADD: 'Aggiungi', ACCESS_TOKEN_FOR: 'Token di accesso per', - ACCESS_TOKEN_TEXT: 'Il token seguente viene utilizzato con le chiamate API REST che richiedono l autorizzazione. Può essere passato come token Bearer nell intestazione di autorizzazione o nel parametro di query URL access_token.', + ACCESS_TOKEN_TEXT: + 'Il token seguente viene utilizzato con le chiamate API REST che richiedono l autorizzazione. Può essere passato come token Bearer nell intestazione di autorizzazione o nel parametro di query URL access_token.', GENERATING_TOKEN: 'Generazione token', USER: 'Utente', MODIFY: 'Modifica', diff --git a/interface/src/i18n/nl/index.ts b/interface/src/i18n/nl/index.ts index 7084f6e00..7cb00cf06 100644 --- a/interface/src/i18n/nl/index.ts +++ b/interface/src/i18n/nl/index.ts @@ -1,7 +1,5 @@ import type { Translation } from '../i18n-types'; -/* prettier-ignore */ - const nl: Translation = { LANGUAGE: 'Taal', RETRY: 'Opnieuw proberen', @@ -208,7 +206,8 @@ const nl: Translation = { USER_WARNING: 'U dient tenminste 1 admin gebruiker te configureren', ADD: 'Toevoegen', ACCESS_TOKEN_FOR: 'Access Token voor', - ACCESS_TOKEN_TEXT: 'Het token hieronder wordt gebruikt voor de REST API calls die authorisatie nodig hebben. Het kan zowel als Bearer token in de Authorization header of in acccess_token URL query parameter gebruikt worden', + ACCESS_TOKEN_TEXT: + 'Het token hieronder wordt gebruikt voor de REST API calls die authorisatie nodig hebben. Het kan zowel als Bearer token in de Authorization header of in acccess_token URL query parameter gebruikt worden', GENERATING_TOKEN: 'Token aan het genereren', USER: 'Gebruiker', MODIFY: 'Aanpassen', diff --git a/interface/src/i18n/no/index.ts b/interface/src/i18n/no/index.ts index 250c107e9..24ba9e52b 100644 --- a/interface/src/i18n/no/index.ts +++ b/interface/src/i18n/no/index.ts @@ -1,7 +1,5 @@ import type { Translation } from '../i18n-types'; -/* prettier-ignore */ - const no: Translation = { LANGUAGE: 'Språk', RETRY: 'Forsøk igjen', @@ -208,7 +206,8 @@ const no: Translation = { USER_WARNING: 'Du må ha minst en admin bruker konfigurert', ADD: 'Legg til', ACCESS_TOKEN_FOR: 'Aksess Token for', - ACCESS_TOKEN_TEXT: 'Token nedenfor benyttes med REST API-kall som krever autorisering. Den kan sendes med enten som en Bearer token i Authorization-headern eller i access_token URL query-parameter.', + ACCESS_TOKEN_TEXT: + 'Token nedenfor benyttes med REST API-kall som krever autorisering. Den kan sendes med enten som en Bearer token i Authorization-headern eller i access_token URL query-parameter.', GENERATING_TOKEN: 'Generer token', USER: 'Bruker', MODIFY: 'Endre', diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index e930ebb8b..0b4980212 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -1,7 +1,5 @@ import type { BaseTranslation } from '../i18n-types'; -/* prettier-ignore */ - const pl: BaseTranslation = { LANGUAGE: 'Język', RETRY: 'Ponów', @@ -122,7 +120,7 @@ const pl: BaseTranslation = { BYPASS_TOKEN: 'Pomiń autoryzację tokenem w wywołaniach API', READONLY: 'Tryb pracy "tylko do odczytu" (blokuje wszystkie komendy zapisu na magistralę EMS)', UNDERCLOCK_CPU: 'Obniż taktowanie CPU', - HEATINGOFF: 'Uruchom kocioł z wymuszonym wyłączonym grzaniem', + HEATINGOFF: 'Uruchom kocioł z wymuszonym wyłączonym grzaniem', ENABLE_SHOWER_TIMER: 'Aktywuj minutnik prysznica', ENABLE_SHOWER_ALERT: 'Aktywuj alarm prysznica', TRIGGER_TIME: 'Wyzwalaj po czasie', @@ -158,7 +156,7 @@ const pl: BaseTranslation = { SET_ALL: 'Ustaw wszystko jako', OPTIONS: 'Opcje', NAME: '{{Nazwa|nazwa|}}', - CUSTOMIZATIONS_RESET: 'Na pewno chcesz usunąć wszystkie personalizacje łącznie z ustawieniami dla czujników temperatury 1-Wire® i urządzeń podłączonych do EMS-ESP?', + CUSTOMIZATIONS_RESET: 'Na pewno chcesz usunąć wszystkie personalizacje łącznie z ustawieniami dla czujników temperatury 1-Wire® i urządzeń podłączonych do EMS-ESP?', SUPPORT_INFORMATION: '{{I|i|}}nformacj{{e|i|}} o systemie', HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP, skorzystaj z wiki w internecie', HELP_INFORMATION_2: 'Aby dołączyć do naszego serwera Discord i komunikować się na żywo ze społecznością', diff --git a/interface/src/i18n/sk/index.ts b/interface/src/i18n/sk/index.ts index 539918382..bdeb61d7c 100644 --- a/interface/src/i18n/sk/index.ts +++ b/interface/src/i18n/sk/index.ts @@ -1,7 +1,5 @@ import type { Translation } from '../i18n-types'; -/* prettier-ignore */ - const sk: Translation = { LANGUAGE: 'Jazyk', RETRY: 'Opakovať', @@ -209,7 +207,8 @@ const sk: Translation = { USER_WARNING: 'Musíte mať nakonfigurovaného aspoň jedného používateľa administrátora', ADD: 'Pridať', ACCESS_TOKEN_FOR: 'Prístupový token pre', - ACCESS_TOKEN_TEXT: 'Nižšie uvedený token sa používa pri volaniach REST API, ktoré vyžadujú autorizáciu. Môže byť odovzdaný buď ako token Bearer v hlavičke Authorization (Autorizácia), alebo v parametri dotazu URL access_token.', + ACCESS_TOKEN_TEXT: + 'Nižšie uvedený token sa používa pri volaniach REST API, ktoré vyžadujú autorizáciu. Môže byť odovzdaný buď ako token Bearer v hlavičke Authorization (Autorizácia), alebo v parametri dotazu URL access_token.', GENERATING_TOKEN: 'Generovanie tokenu', USER: 'Užívateľ', MODIFY: 'Upraviť', @@ -324,7 +323,7 @@ const sk: Translation = { ACTIVELOW: 'Aktívny Nízky', UNCHANGED: 'Nezmenené', ALWAYS: 'Vždy', - ACTIVITY: 'Aktivita', + ACTIVITY: 'Aktivita', CONFIGURE: 'Konfiguracia {0}', SYSTEM_MEMORY: 'System Memory', // TODO translate APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate diff --git a/interface/src/i18n/sv/index.ts b/interface/src/i18n/sv/index.ts index a2d675f07..2b8750ee1 100644 --- a/interface/src/i18n/sv/index.ts +++ b/interface/src/i18n/sv/index.ts @@ -1,7 +1,5 @@ import type { Translation } from '../i18n-types'; -/* prettier-ignore */ - const sv: Translation = { LANGUAGE: 'Språk', RETRY: 'Försök igen', @@ -208,7 +206,8 @@ const sv: Translation = { USER_WARNING: 'Du måste ha minst en admin konfigurerad', ADD: 'Lägg till', ACCESS_TOKEN_FOR: 'Access Token för', - ACCESS_TOKEN_TEXT: 'Nedan Token används med REST API-anrop som kräver auktorisering. Den kan skickas med antingen som en Bearer token i Authorization-headern eller i access_token URL query-parametern.', + ACCESS_TOKEN_TEXT: + 'Nedan Token används med REST API-anrop som kräver auktorisering. Den kan skickas med antingen som en Bearer token i Authorization-headern eller i access_token URL query-parametern.', GENERATING_TOKEN: 'Genererar token', USER: 'Användare', MODIFY: 'Ändra', diff --git a/interface/src/i18n/tr/index.ts b/interface/src/i18n/tr/index.ts index 43f4ceee1..b53e411e6 100644 --- a/interface/src/i18n/tr/index.ts +++ b/interface/src/i18n/tr/index.ts @@ -1,7 +1,5 @@ import type { Translation } from '../i18n-types'; -/* prettier-ignore */ - const tr: Translation = { LANGUAGE: 'Dil', RETRY: 'Tekrar Dene', @@ -208,7 +206,8 @@ const tr: Translation = { USER_WARNING: 'En az bir yönetici kullanıcısı ayarlamanız gerekmektedir', ADD: 'Ekle', ACCESS_TOKEN_FOR: 'Erişim Jetonunun sahibi', - ACCESS_TOKEN_TEXT: 'Aşağıdaki Jeton yetki gerektiren REST API çağrıları ile kullanılmaktadır. Taşıyıcı Jeton olarak yetkilendirme başlığında yada erişim jetonu olarak URL sorgu parametresinde kullanılabilir.', + ACCESS_TOKEN_TEXT: + 'Aşağıdaki Jeton yetki gerektiren REST API çağrıları ile kullanılmaktadır. Taşıyıcı Jeton olarak yetkilendirme başlığında yada erişim jetonu olarak URL sorgu parametresinde kullanılabilir.', GENERATING_TOKEN: 'Jeton oluşturuluyor', USER: 'Kullanıcı', MODIFY: 'Düzenle', diff --git a/interface/src/index.tsx b/interface/src/index.tsx index 7d2d70aad..bc6d09288 100644 --- a/interface/src/index.tsx +++ b/interface/src/index.tsx @@ -1,10 +1,17 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; -import { Route, RouterProvider, createBrowserRouter, createRoutesFromElements } from 'react-router-dom'; +import { + Route, + RouterProvider, + createBrowserRouter, + createRoutesFromElements +} from 'react-router-dom'; import App from 'App'; -const router = createBrowserRouter(createRoutesFromElements(} />)); +const router = createBrowserRouter( + createRoutesFromElements(} />) +); createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/interface/src/project/ApplicationSettings.tsx b/interface/src/project/ApplicationSettings.tsx index 7f21ca869..651983655 100644 --- a/interface/src/project/ApplicationSettings.tsx +++ b/interface/src/project/ApplicationSettings.tsx @@ -5,7 +5,17 @@ import { toast } from 'react-toastify'; import CancelIcon from '@mui/icons-material/Cancel'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import WarningIcon from '@mui/icons-material/Warning'; -import { Box, Button, Checkbox, Divider, Grid, InputAdornment, MenuItem, TextField, Typography } from '@mui/material'; +import { + Box, + Button, + Checkbox, + Divider, + Grid, + InputAdornment, + MenuItem, + TextField, + Typography +} from '@mui/material'; import * as SystemApi from 'api/system'; @@ -61,7 +71,12 @@ const ApplicationSettings: FC = () => { const { LL } = useI18nContext(); - const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue); + const updateFormValue = updateValueDirty( + origData, + dirtyFlags, + setDirtyFlags, + updateDataValue + ); const [fieldErrors, setFieldErrors] = useState(); @@ -220,7 +235,9 @@ const ApplicationSettings: FC = () => { { {LL.SETTINGS_OF(LL.EMS_BUS(0))} - + { {data.led_gpio !== 0 && ( } + control={ + + } label={LL.HIDE_LED()} disabled={saving} /> )} } + control={ + + } label={LL.ENABLE_TELNET()} disabled={saving} /> } + control={ + + } label={LL.ENABLE_ANALOG()} disabled={saving} /> } + control={ + + } label={LL.CONVERT_FAHRENHEIT()} disabled={saving} /> } + control={ + + } label={LL.BYPASS_TOKEN()} disabled={saving} /> } + control={ + + } label={LL.READONLY()} disabled={saving} /> } + control={ + + } label={LL.UNDERCLOCK_CPU()} disabled={saving} /> } + control={ + + } label={LL.HEATINGOFF()} disabled={saving} /> - + } + control={ + + } label={LL.ENABLE_SHOWER_TIMER()} disabled={saving} /> } + control={ + + } label={LL.ENABLE_SHOWER_ALERT()} disabled={!data.shower_timer} /> @@ -465,7 +554,9 @@ const ApplicationSettings: FC = () => { name="shower_alert_trigger" label={LL.TRIGGER_TIME()} InputProps={{ - endAdornment: {LL.MINUTES()} + endAdornment: ( + {LL.MINUTES()} + ) }} variant="outlined" value={numberValue(data.shower_alert_trigger)} @@ -481,7 +572,9 @@ const ApplicationSettings: FC = () => { name="shower_alert_coldshot" label={LL.COLD_SHOT_DURATION()} InputProps={{ - endAdornment: {LL.SECONDS()} + endAdornment: ( + {LL.SECONDS()} + ) }} variant="outlined" value={numberValue(data.shower_alert_coldshot)} @@ -497,7 +590,13 @@ const ApplicationSettings: FC = () => { {LL.FORMATTING_OPTIONS()} - + { {LL.TEMP_SENSORS()} } + control={ + + } label={LL.ENABLE_PARASITE()} disabled={saving} /> @@ -566,7 +671,13 @@ const ApplicationSettings: FC = () => { {LL.LOGGING()}
} + control={ + + } label={LL.LOG_HEX()} disabled={saving} /> @@ -582,7 +693,13 @@ const ApplicationSettings: FC = () => { label={LL.ENABLE_SYSLOG()} /> {data.syslog_enabled && ( - + { name="syslog_mark_interval" label={LL.MARK_INTERVAL()} InputProps={{ - endAdornment: {LL.SECONDS()} + endAdornment: ( + {LL.SECONDS()} + ) }} fullWidth variant="outlined" @@ -651,7 +770,12 @@ const ApplicationSettings: FC = () => { )} {restartNeeded && ( - diff --git a/interface/src/project/CustomEntities.tsx b/interface/src/project/CustomEntities.tsx index 610c76e3f..16d5c2b09 100644 --- a/interface/src/project/CustomEntities.tsx +++ b/interface/src/project/CustomEntities.tsx @@ -10,10 +10,24 @@ import RefreshIcon from '@mui/icons-material/Refresh'; import WarningIcon from '@mui/icons-material/Warning'; import { Box, Button, Typography } from '@mui/material'; -import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table'; +import { + Body, + Cell, + Header, + HeaderCell, + HeaderRow, + Row, + Table +} from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; import { updateState, useRequest } from 'alova'; -import { BlockNavigation, ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components'; +import { + BlockNavigation, + ButtonRow, + FormLoader, + SectionContent, + useLayoutTitle +} from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import * as EMSESP from './api'; @@ -171,8 +185,13 @@ const CustomEntities: FC = () => { updateState('entities', (data: EntityItem[]) => { const new_data = creating - ? [...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id), updatedItem] - : data.map((ei) => (ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei)); + ? [ + ...data.filter((ei) => creating || ei.o_id !== updatedItem.o_id), + updatedItem + ] + : data.map((ei) => + ei.id === updatedItem.id ? { ...ei, ...updatedItem } : ei + ); setNumChanges(new_data.filter((ei) => hasEntityChanged(ei)).length); return new_data; }); @@ -201,7 +220,8 @@ const CustomEntities: FC = () => { return value === undefined ? '' : typeof value === 'number' - ? new Intl.NumberFormat().format(value) + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom]) + ? new Intl.NumberFormat().format(value) + + (uom === 0 ? '' : ' ' + DeviceValueUOM_s[uom]) : (value as string); } @@ -215,7 +235,11 @@ const CustomEntities: FC = () => { } return ( - !ei.deleted) }} theme={entity_theme} layout={{ custom: true }}> +
!ei.deleted) }} + theme={entity_theme} + layout={{ custom: true }} + > {(tableList: EntityItem[]) => ( <>
@@ -233,12 +257,18 @@ const CustomEntities: FC = () => { editEntityItem(ei)}> {ei.name}  - {ei.writeable && } + {ei.writeable && ( + + )} + + + {ei.ram === 1 ? '' : showHex(ei.device_id as number, 2)} - {ei.ram === 1 ? '' : showHex(ei.device_id as number, 2)} {ei.ram === 1 ? '' : showHex(ei.type_id as number, 3)} {ei.ram === 1 ? '' : ei.offset} - {ei.ram === 1 ? 'RAM' : DeviceValueTypeNames[ei.value_type]} + + {ei.ram === 1 ? 'RAM' : DeviceValueTypeNames[ei.value_type]} + {formatValue(ei.value, ei.uom)} ))} @@ -273,7 +303,12 @@ const CustomEntities: FC = () => { {numChanges > 0 && ( - - diff --git a/interface/src/project/CustomEntitiesDialog.tsx b/interface/src/project/CustomEntitiesDialog.tsx index e41cf90ed..5650d9056 100644 --- a/interface/src/project/CustomEntitiesDialog.tsx +++ b/interface/src/project/CustomEntitiesDialog.tsx @@ -142,7 +142,13 @@ const CustomEntitiesDialog = ({ <> } + control={ + + } label={LL.WRITEABLE()} /> @@ -157,7 +163,11 @@ const CustomEntitiesDialog = ({ value={editItem.device_id as string} onChange={updateFormValue} inputProps={{ style: { textTransform: 'uppercase' } }} - InputProps={{ startAdornment: 0x }} + InputProps={{ + startAdornment: ( + 0x + ) + }} /> @@ -170,7 +180,11 @@ const CustomEntitiesDialog = ({ value={editItem.type_id} onChange={updateFormValue} inputProps={{ style: { textTransform: 'uppercase' } }} - InputProps={{ startAdornment: 0x }} + InputProps={{ + startAdornment: ( + 0x + ) + }} /> @@ -207,55 +221,57 @@ const CustomEntitiesDialog = ({ - {editItem.value_type !== DeviceValueType.BOOL && editItem.value_type !== DeviceValueType.STRING && ( - <> + {editItem.value_type !== DeviceValueType.BOOL && + editItem.value_type !== DeviceValueType.STRING && ( + <> + + + + + + {DeviceValueUOM_s.map((val, i) => ( + + {val} + + ))} + + + + )} + {editItem.value_type === DeviceValueType.STRING && + editItem.device_id !== '0' && ( - - - {DeviceValueUOM_s.map((val, i) => ( - - {val} - - ))} - - - - )} - {editItem.value_type === DeviceValueType.STRING && editItem.device_id !== '0' && ( - - - - )} + )} )} @@ -264,15 +280,30 @@ const CustomEntitiesDialog = ({ {!creating && ( - )} - - diff --git a/interface/src/project/Customization.tsx b/interface/src/project/Customization.tsx index a3e1e0d95..c7a5a3e11 100644 --- a/interface/src/project/Customization.tsx +++ b/interface/src/project/Customization.tsx @@ -27,11 +27,25 @@ import { import * as SystemApi from 'api/system'; -import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table'; +import { + Body, + Cell, + Header, + HeaderCell, + HeaderRow, + Row, + Table +} from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; import { dialogStyle } from 'CustomTheme'; import { useRequest } from 'alova'; -import { BlockNavigation, ButtonRow, MessageBox, SectionContent, useLayoutTitle } from 'components'; +import { + BlockNavigation, + ButtonRow, + MessageBox, + SectionContent, + useLayoutTitle +} from 'components'; import RestartMonitor from 'framework/system/RestartMonitor'; import { useI18nContext } from 'i18n/i18n-react'; @@ -63,7 +77,9 @@ const Customization: FC = () => { // fetch devices first const { data: devices } = useRequest(EMSESP.readDevices); - const [selectedDevice, setSelectedDevice] = useState(Number(useLocation().state) || -1); + const [selectedDevice, setSelectedDevice] = useState( + Number(useLocation().state) || -1 + ); const [selectedDeviceName, setSelectedDeviceName] = useState(''); const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), { @@ -71,7 +87,8 @@ const Customization: FC = () => { }); const { send: writeCustomizationEntities } = useRequest( - (data: { id: number; entity_ids: string[] }) => EMSESP.writeCustomizationEntities(data), + (data: { id: number; entity_ids: string[] }) => + EMSESP.writeCustomizationEntities(data), { immediate: false } @@ -86,7 +103,15 @@ const Customization: FC = () => { ); const setOriginalSettings = (data: DeviceEntity[]) => { - setDeviceEntities(data.map((de) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma }))); + setDeviceEntities( + data.map((de) => ({ + ...de, + o_m: de.m, + o_cn: de.cn, + o_mi: de.mi, + o_ma: de.ma + })) + ); }; onSuccess((event) => { @@ -166,7 +191,12 @@ const Customization: FC = () => { }); function hasEntityChanged(de: DeviceEntity) { - return (de?.cn || '') !== (de?.o_cn || '') || de.m !== de.o_m || de.ma !== de.o_ma || de.mi !== de.o_mi; + return ( + (de?.cn || '') !== (de?.o_cn || '') || + de.m !== de.o_m || + de.ma !== de.o_ma || + de.mi !== de.o_mi + ); } useEffect(() => { @@ -221,8 +251,11 @@ const Customization: FC = () => { } const formatName = (de: DeviceEntity, withShortname: boolean) => - (de.n && de.n[0] === '!' ? LL.COMMAND(1) + ': ' + de.n.slice(1) : de.cn && de.cn !== '' ? de.cn : de.n) + - (withShortname ? ' ' + de.id : ''); + (de.n && de.n[0] === '!' + ? LL.COMMAND(1) + ': ' + de.n.slice(1) + : de.cn && de.cn !== '' + ? de.cn + : de.n) + (withShortname ? ' ' + de.id : ''); const getMaskNumber = (newMask: string[]) => { let new_mask = 0; @@ -253,7 +286,8 @@ const Customization: FC = () => { }; const filter_entity = (de: DeviceEntity) => - (de.m & selectedFilters || !selectedFilters) && formatName(de, true).includes(search.toLocaleLowerCase()); + (de.m & selectedFilters || !selectedFilters) && + formatName(de, true).includes(search.toLocaleLowerCase()); const maskDisabled = (set: boolean) => { setDeviceEntities( @@ -262,8 +296,14 @@ const Customization: FC = () => { return { ...de, m: set - ? de.m | (DeviceEntityMask.DV_API_MQTT_EXCLUDE | DeviceEntityMask.DV_WEB_EXCLUDE) - : de.m & ~(DeviceEntityMask.DV_API_MQTT_EXCLUDE | DeviceEntityMask.DV_WEB_EXCLUDE) + ? de.m | + (DeviceEntityMask.DV_API_MQTT_EXCLUDE | + DeviceEntityMask.DV_WEB_EXCLUDE) + : de.m & + ~( + DeviceEntityMask.DV_API_MQTT_EXCLUDE | + DeviceEntityMask.DV_WEB_EXCLUDE + ) }; } else { return de; @@ -288,7 +328,11 @@ const Customization: FC = () => { }; const updateDeviceEntity = (updatedItem: DeviceEntity) => { - setDeviceEntities(deviceEntities?.map((de) => (de.id === updatedItem.id ? { ...de, ...updatedItem } : de))); + setDeviceEntities( + deviceEntities?.map((de) => + de.id === updatedItem.id ? { ...de, ...updatedItem } : de + ) + ); }; const onDialogSave = (updatedItem: DeviceEntity) => { @@ -330,7 +374,10 @@ const Customization: FC = () => { return; } - await writeCustomizationEntities({ id: selectedDevice, entity_ids: masked_entities }).catch((error: Error) => { + await writeCustomizationEntities({ + id: selectedDevice, + entity_ids: masked_entities + }).catch((error: Error) => { if (error.message === 'Reboot required') { setRestartNeeded(true); } else { @@ -376,14 +423,26 @@ const Customization: FC = () => { <> - ={LL.CUSTOMIZATIONS_HELP_2()}   - ={LL.CUSTOMIZATIONS_HELP_3()}   - ={LL.CUSTOMIZATIONS_HELP_4()}   - ={LL.CUSTOMIZATIONS_HELP_5()}   + ={LL.CUSTOMIZATIONS_HELP_2()} +    + ={LL.CUSTOMIZATIONS_HELP_3()} +    + = + {LL.CUSTOMIZATIONS_HELP_4()}   + = + {LL.CUSTOMIZATIONS_HELP_5()}   ={LL.CUSTOMIZATIONS_HELP_6()} - + { - {LL.SHOWING()} {shown_data.length}/{deviceEntities.length} {LL.ENTITIES(deviceEntities.length)} + {LL.SHOWING()} {shown_data.length}/{deviceEntities.length} +  {LL.ENTITIES(deviceEntities.length)} -
+
{(tableList: DeviceEntity[]) => ( <>
@@ -479,13 +543,20 @@ const Customization: FC = () => { {formatName(de, false)} ( - + {de.id} ) - {!(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.mi)} - {!(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.ma)} + + {!(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.mi)} + + + {!(de.m & DeviceEntityMask.DV_READONLY) && formatValue(de.ma)} + {formatValue(de.v)} ))} @@ -498,14 +569,28 @@ const Customization: FC = () => { }; const renderResetDialog = () => ( - setConfirmReset(false)}> + setConfirmReset(false)} + > {LL.RESET(1)} {LL.CUSTOMIZATIONS_RESET()} - - @@ -518,7 +603,12 @@ const Customization: FC = () => { {deviceEntities && renderDeviceData()} {restartNeeded && ( - diff --git a/interface/src/project/CustomizationDialog.tsx b/interface/src/project/CustomizationDialog.tsx index 088f114b5..6d60ac5ac 100644 --- a/interface/src/project/CustomizationDialog.tsx +++ b/interface/src/project/CustomizationDialog.tsx @@ -30,7 +30,12 @@ interface SettingsCustomizationDialogProps { selectedItem: DeviceEntity; } -const CustomizationDialog = ({ open, onClose, onSave, selectedItem }: SettingsCustomizationDialogProps) => { +const CustomizationDialog = ({ + open, + onClose, + onSave, + selectedItem +}: SettingsCustomizationDialogProps) => { const { LL } = useI18nContext(); const [editItem, setEditItem] = useState(selectedItem); const [error, setError] = useState(false); @@ -38,7 +43,9 @@ const CustomizationDialog = ({ open, onClose, onSave, selectedItem }: SettingsCu const updateFormValue = updateValue(setEditItem); const isWriteableNumber = - typeof editItem.v === 'number' && editItem.w && !(editItem.m & DeviceEntityMask.DV_READONLY); + typeof editItem.v === 'number' && + editItem.w && + !(editItem.m & DeviceEntityMask.DV_READONLY); useEffect(() => { if (open) { @@ -52,7 +59,12 @@ const CustomizationDialog = ({ open, onClose, onSave, selectedItem }: SettingsCu }; const save = () => { - if (isWriteableNumber && editItem.mi && editItem.ma && editItem.mi > editItem?.ma) { + if ( + isWriteableNumber && + editItem.mi && + editItem.ma && + editItem.mi > editItem?.ma + ) { setError(true); } else { onSave(editItem); @@ -140,10 +152,20 @@ const CustomizationDialog = ({ open, onClose, onSave, selectedItem }: SettingsCu )} - - diff --git a/interface/src/project/DeviceIcon.tsx b/interface/src/project/DeviceIcon.tsx index b83605a01..968d5ec6f 100644 --- a/interface/src/project/DeviceIcon.tsx +++ b/interface/src/project/DeviceIcon.tsx @@ -3,7 +3,12 @@ import { AiOutlineAlert, AiOutlineControl, AiOutlineGateway } from 'react-icons/ import { CgSmartHomeBoiler } from 'react-icons/cg'; import { FaSolarPanel } from 'react-icons/fa'; import { GiHeatHaze, GiTap } from 'react-icons/gi'; -import { MdOutlineDevices, MdOutlinePool, MdOutlineSensors, MdThermostatAuto } from 'react-icons/md'; +import { + MdOutlineDevices, + MdOutlinePool, + MdOutlineSensors, + MdThermostatAuto +} from 'react-icons/md'; import { TiFlowSwitch } from 'react-icons/ti'; import { VscVmConnect } from 'react-icons/vsc'; @@ -47,7 +52,11 @@ const DeviceIcon: FC = ({ type_id }) => { case DeviceType.POOL: return ; case DeviceType.CUSTOM: - return ; + return ( + + ); default: return null; } diff --git a/interface/src/project/Devices.tsx b/interface/src/project/Devices.tsx index 126c1f510..5ec355518 100644 --- a/interface/src/project/Devices.tsx +++ b/interface/src/project/Devices.tsx @@ -1,4 +1,10 @@ -import { useCallback, useContext, useEffect, useLayoutEffect, useState } from 'react'; +import { + useCallback, + useContext, + useEffect, + useLayoutEffect, + useState +} from 'react'; import type { FC } from 'react'; import { IconContext } from 'react-icons'; import { useNavigate } from 'react-router-dom'; @@ -35,7 +41,15 @@ import { import { useRowSelect } from '@table-library/react-table-library/select'; import { SortToggleType, useSort } from '@table-library/react-table-library/sort'; -import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table'; +import { + Body, + Cell, + Header, + HeaderCell, + HeaderRow, + Row, + Table +} from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; import type { Action, State } from '@table-library/react-table-library/types/common'; import { dialogStyle } from 'CustomTheme'; @@ -67,19 +81,25 @@ const Devices: FC = () => { useLayoutTitle(LL.DEVICES()); - const { data: coreData, send: readCoreData } = useRequest(() => EMSESP.readCoreData(), { - initialData: { - connected: true, - devices: [] + const { data: coreData, send: readCoreData } = useRequest( + () => EMSESP.readCoreData(), + { + initialData: { + connected: true, + devices: [] + } } - }); + ); - const { data: deviceData, send: readDeviceData } = useRequest((id: number) => EMSESP.readDeviceData(id), { - initialData: { - data: [] - }, - immediate: false - }); + const { data: deviceData, send: readDeviceData } = useRequest( + (id: number) => EMSESP.readDeviceData(id), + { + initialData: { + data: [] + }, + immediate: false + } + ); const { loading: submitting, send: writeDeviceValue } = useRequest( (data: { id: number; c: string; v: unknown }) => EMSESP.writeDeviceValue(data), @@ -235,9 +255,14 @@ const Devices: FC = () => { }, sortToggleType: SortToggleType.AlternateWithReset, sortFns: { - NAME: (array) => array.sort((a, b) => a.id.toString().slice(2).localeCompare(b.id.toString().slice(2))), - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - VALUE: (array) => array.sort((a, b) => a.v.toString().localeCompare(b.v.toString())) + NAME: (array) => + array.sort((a, b) => + a.id.toString().slice(2).localeCompare(b.id.toString().slice(2)) + ), + + VALUE: (array) => + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access + array.sort((a, b) => a.v.toString().localeCompare(b.v.toString())) } } ); @@ -300,35 +325,59 @@ const Devices: FC = () => { if (sc === '' || sc === '""') { return sc; } - if (sc.includes('"') || sc.includes(';') || sc.includes('\n') || sc.includes('\r')) { + if ( + sc.includes('"') || + sc.includes(';') || + sc.includes('\n') || + sc.includes('\r') + ) { return '"' + sc.replace(/"/g, '""') + '"'; } return sc; }; - const hasMask = (id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask; + const hasMask = (id: string, mask: number) => + (parseInt(id.slice(0, 2), 16) & mask) === mask; const handleDownloadCsv = () => { - const deviceIndex = coreData.devices.findIndex((d) => d.id === device_select.state.id); + const deviceIndex = coreData.devices.findIndex( + (d) => d.id === device_select.state.id + ); if (deviceIndex === -1) { return; } - const filename = coreData.devices[deviceIndex].tn + '_' + coreData.devices[deviceIndex].n; + const filename = + coreData.devices[deviceIndex].tn + '_' + coreData.devices[deviceIndex].n; const columns = [ - { accessor: (dv: DeviceValue) => dv.id.slice(2), name: LL.ENTITY_NAME(0) }, { - accessor: (dv: DeviceValue) => (typeof dv.v === 'number' ? new Intl.NumberFormat().format(dv.v) : dv.v), + accessor: (dv: DeviceValue) => dv.id.slice(2), + name: LL.ENTITY_NAME(0) + }, + { + accessor: (dv: DeviceValue) => + typeof dv.v === 'number' ? new Intl.NumberFormat().format(dv.v) : dv.v, name: LL.VALUE(1) }, - { accessor: (dv: DeviceValue) => DeviceValueUOM_s[dv.u].replace(/[^a-zA-Z0-9]/g, ''), name: 'UoM' }, { - accessor: (dv: DeviceValue) => (dv.c && !hasMask(dv.id, DeviceEntityMask.DV_READONLY) ? 'yes' : 'no'), + accessor: (dv: DeviceValue) => + DeviceValueUOM_s[dv.u].replace(/[^a-zA-Z0-9]/g, ''), + name: 'UoM' + }, + { + accessor: (dv: DeviceValue) => + dv.c && !hasMask(dv.id, DeviceEntityMask.DV_READONLY) ? 'yes' : 'no', name: LL.WRITEABLE() }, { accessor: (dv: DeviceValue) => - dv.h ? dv.h : dv.l ? dv.l.join(' | ') : dv.m !== undefined && dv.x !== undefined ? dv.m + ', ' + dv.x : '', + dv.h + ? dv.h + : dv.l + ? dv.l.join(' | ') + : dv.m !== undefined && dv.x !== undefined + ? dv.m + ', ' + dv.x + : '', name: 'Range' } ]; @@ -341,10 +390,13 @@ const Devices: FC = () => { (csvString: string, rowItem: DeviceValue) => csvString + columns - .map(({ accessor }: { accessor: (dv: DeviceValue) => unknown }) => escapeCsvCell(accessor(rowItem) as string)) + .map(({ accessor }: { accessor: (dv: DeviceValue) => unknown }) => + escapeCsvCell(accessor(rowItem) as string) + ) .join(';') + '\r\n', - columns.map(({ name }: { name: string }) => escapeCsvCell(name)).join(';') + '\r\n' + columns.map(({ name }: { name: string }) => escapeCsvCell(name)).join(';') + + '\r\n' ); const csvFile = new Blob([csvData], { type: 'text/csv;charset:utf-8' }); @@ -381,45 +433,76 @@ const Devices: FC = () => { const renderDeviceDetails = () => { if (showDeviceInfo) { - const deviceIndex = coreData.devices.findIndex((d) => d.id === device_select.state.id); + const deviceIndex = coreData.devices.findIndex( + (d) => d.id === device_select.state.id + ); if (deviceIndex === -1) { return; } return ( - setShowDeviceInfo(false)}> + setShowDeviceInfo(false)} + > {LL.DEVICE_DETAILS()} - + - + {coreData.devices[deviceIndex].t !== DeviceType.CUSTOM && ( <> - + - + - + )} - @@ -429,11 +512,24 @@ const Devices: FC = () => { }; const renderCoreData = () => ( - - {!coreData.connected && } + + {!coreData.connected && ( + + )} {coreData.connected && ( -
+
{(tableList: Device[]) => ( <>
@@ -451,7 +547,9 @@ const Devices: FC = () => { {device.n} -   ({device.e}) + +   ({device.e}) + {device.tn} @@ -481,8 +579,12 @@ const Devices: FC = () => { const renderNameCell = (dv: DeviceValue) => ( <> {dv.id.slice(2)}  - {hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) && } - {hasMask(dv.id, DeviceEntityMask.DV_READONLY) && } + {hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) && ( + + )} + {hasMask(dv.id, DeviceEntityMask.DV_READONLY) && ( + + )} {hasMask(dv.id, DeviceEntityMask.DV_API_MQTT_EXCLUDE) && ( )} @@ -493,7 +595,9 @@ const Devices: FC = () => { ? deviceData.data.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE)) : deviceData.data; - const deviceIndex = coreData.devices.findIndex((d) => d.id === device_select.state.id); + const deviceIndex = coreData.devices.findIndex( + (d) => d.id === device_select.state.id + ); if (deviceIndex === -1) { return; } @@ -514,7 +618,8 @@ const Devices: FC = () => { > - {coreData.devices[deviceIndex].tn} | {coreData.devices[deviceIndex].n} + {coreData.devices[deviceIndex].tn} |  + {coreData.devices[deviceIndex].n} @@ -527,30 +632,50 @@ const Devices: FC = () => { ' ' + LL.ENTITIES(shown_data.length)} setShowDeviceInfo(true)}> - + {me.admin && ( - + )} - + setOnlyFav(!onlyFav)}> {onlyFav ? ( - + ) : ( - + )} - + - + @@ -595,15 +720,20 @@ const Devices: FC = () => { {renderNameCell(dv)} {formatValue(LL, dv.v, dv.u)} - {me.admin && dv.c && !hasMask(dv.id, DeviceEntityMask.DV_READONLY) && ( - showDeviceValue(dv)}> - {dv.v === '' && dv.c ? ( - - ) : ( - - )} - - )} + {me.admin && + dv.c && + !hasMask(dv.id, DeviceEntityMask.DV_READONLY) && ( + showDeviceValue(dv)} + > + {dv.v === '' && dv.c ? ( + + ) : ( + + )} + + )} ))} @@ -627,14 +757,20 @@ const Devices: FC = () => { onSave={deviceValueDialogSave} selectedItem={selectedDeviceValue} writeable={ - selectedDeviceValue.c !== undefined && !hasMask(selectedDeviceValue.id, DeviceEntityMask.DV_READONLY) + selectedDeviceValue.c !== undefined && + !hasMask(selectedDeviceValue.id, DeviceEntityMask.DV_READONLY) } validator={deviceValueItemValidation(selectedDeviceValue)} progress={submitting} /> )} - diff --git a/interface/src/project/DevicesDialog.tsx b/interface/src/project/DevicesDialog.tsx index 500a01993..f01bab60b 100644 --- a/interface/src/project/DevicesDialog.tsx +++ b/interface/src/project/DevicesDialog.tsx @@ -102,7 +102,11 @@ const DevicesDialog = ({ return ( - {selectedItem.v === '' && selectedItem.c ? LL.RUN_COMMAND() : writeable ? LL.CHANGE_VALUE() : LL.VALUE(1)} + {selectedItem.v === '' && selectedItem.c + ? LL.RUN_COMMAND() + : writeable + ? LL.CHANGE_VALUE() + : LL.VALUE(1)} @@ -138,9 +142,17 @@ const DevicesDialog = ({ type="number" sx={{ width: '30ch' }} onChange={updateFormValue} - inputProps={editItem.s ? { min: editItem.m, max: editItem.x, step: editItem.s } : {}} + inputProps={ + editItem.s + ? { min: editItem.m, max: editItem.x, step: editItem.s } + : {} + } InputProps={{ - startAdornment: {setUom(editItem.u)} + startAdornment: ( + + {setUom(editItem.u)} + + ) }} /> ) : ( @@ -175,10 +187,20 @@ const DevicesDialog = ({ position: 'relative' }} > - - {progress && ( diff --git a/interface/src/project/EntityMaskToggle.tsx b/interface/src/project/EntityMaskToggle.tsx index 30df0e2ba..66232d286 100644 --- a/interface/src/project/EntityMaskToggle.tsx +++ b/interface/src/project/EntityMaskToggle.tsx @@ -55,25 +55,46 @@ const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => { }} > - + = 3}> - + - + ); diff --git a/interface/src/project/Help.tsx b/interface/src/project/Help.tsx index 0f5e32d94..d724d3a2b 100644 --- a/interface/src/project/Help.tsx +++ b/interface/src/project/Help.tsx @@ -29,9 +29,12 @@ const Help: FC = () => { const { LL } = useI18nContext(); useLayoutTitle(LL.HELP_OF('')); - const { send: getAPI, onSuccess: onGetAPI } = useRequest((data: APIcall) => EMSESP.API(data), { - immediate: false - }); + const { send: getAPI, onSuccess: onGetAPI } = useRequest( + (data: APIcall) => EMSESP.API(data), + { + immediate: false + } + ); onGetAPI((event) => { const anchor = document.createElement('a'); @@ -40,8 +43,10 @@ const Help: FC = () => { type: 'text/plain' }) ); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - anchor.download = 'emsesp_' + event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt'; + + anchor.download = + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + 'emsesp_' + event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt'; anchor.click(); URL.revokeObjectURL(anchor.href); toast.info(LL.DOWNLOAD_SUCCESSFUL()); @@ -79,7 +84,10 @@ const Help: FC = () => { - + @@ -119,7 +127,11 @@ const Help: FC = () => { {LL.HELP_INFORMATION_5()} - + {'github.com/emsesp/EMS-ESP32'} diff --git a/interface/src/project/OptionIcon.tsx b/interface/src/project/OptionIcon.tsx index 477c9e2f5..c1d7b241d 100644 --- a/interface/src/project/OptionIcon.tsx +++ b/interface/src/project/OptionIcon.tsx @@ -12,9 +12,19 @@ import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined'; import type { SvgIconProps } from '@mui/material'; -type OptionType = 'deleted' | 'readonly' | 'web_exclude' | 'api_mqtt_exclude' | 'favorite'; +type OptionType = + | 'deleted' + | 'readonly' + | 'web_exclude' + | 'api_mqtt_exclude' + | 'favorite'; -const OPTION_ICONS: { [type in OptionType]: [React.ComponentType, React.ComponentType] } = { +const OPTION_ICONS: { + [type in OptionType]: [ + React.ComponentType, + React.ComponentType + ]; +} = { deleted: [DeleteForeverIcon, DeleteOutlineIcon], readonly: [EditOffOutlinedIcon, EditOutlinedIcon], web_exclude: [VisibilityOffOutlinedIcon, VisibilityOutlinedIcon], diff --git a/interface/src/project/Scheduler.tsx b/interface/src/project/Scheduler.tsx index a566123d3..a3176763f 100644 --- a/interface/src/project/Scheduler.tsx +++ b/interface/src/project/Scheduler.tsx @@ -9,10 +9,24 @@ import CircleIcon from '@mui/icons-material/Circle'; import WarningIcon from '@mui/icons-material/Warning'; import { Box, Button, Divider, Stack, Typography } from '@mui/material'; -import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table'; +import { + Body, + Cell, + Header, + HeaderCell, + HeaderRow, + Row, + Table +} from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; import { updateState, useRequest } from 'alova'; -import { BlockNavigation, ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components'; +import { + BlockNavigation, + ButtonRow, + FormLoader, + SectionContent, + useLayoutTitle +} from 'components'; import { useI18nContext } from 'i18n/i18n-react'; import * as EMSESP from './api'; @@ -39,9 +53,12 @@ const Scheduler: FC = () => { force: true }); - const { send: writeSchedule } = useRequest((data: Schedule) => EMSESP.writeSchedule(data), { - immediate: false - }); + const { send: writeSchedule } = useRequest( + (data: Schedule) => EMSESP.writeSchedule(data), + { + immediate: false + } + ); function hasScheduleChanged(si: ScheduleItem) { return ( @@ -57,7 +74,10 @@ const Scheduler: FC = () => { } useEffect(() => { - const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }); + const formatter = new Intl.DateTimeFormat(locale, { + weekday: 'short', + timeZone: 'UTC' + }); const days = [1, 2, 3, 4, 5, 6, 7].map((day) => { const dd = day < 10 ? `0${day}` : day; return new Date(`2017-01-${dd}T00:00:00+00:00`); @@ -157,8 +177,13 @@ const Scheduler: FC = () => { updateState('schedule', (data: ScheduleItem[]) => { const new_data = creating - ? [...data.filter((si) => creating || si.o_id !== updatedItem.o_id), updatedItem] - : data.map((si) => (si.id === updatedItem.id ? { ...si, ...updatedItem } : si)); + ? [ + ...data.filter((si) => creating || si.o_id !== updatedItem.o_id), + updatedItem + ] + : data.map((si) => + si.id === updatedItem.id ? { ...si, ...updatedItem } : si + ); setNumChanges(new_data.filter((si) => hasScheduleChanged(si)).length); @@ -189,8 +214,13 @@ const Scheduler: FC = () => { const dayBox = (si: ScheduleItem, flag: number) => ( <> - - {flag === ScheduleFlag.SCHEDULE_TIMER ? LL.TIMER(0) : dow[Math.log(flag) / Math.log(2)]} + + {flag === ScheduleFlag.SCHEDULE_TIMER + ? LL.TIMER(0) + : dow[Math.log(flag) / Math.log(2)]} @@ -201,7 +231,11 @@ const Scheduler: FC = () => { return (
!si.deleted).sort((a, b) => a.time.localeCompare(b.time)) }} + data={{ + nodes: schedule + .filter((si) => !si.deleted) + .sort((a, b) => a.time.localeCompare(b.time)) + }} theme={schedule_theme} layout={{ custom: true }} > @@ -222,9 +256,15 @@ const Scheduler: FC = () => { editScheduleItem(si)}> {si.active ? ( - + ) : ( - + )} @@ -277,7 +317,12 @@ const Scheduler: FC = () => { {numChanges !== 0 && ( - diff --git a/interface/src/project/SchedulerDialog.tsx b/interface/src/project/SchedulerDialog.tsx index 95d3e3240..04b19b154 100644 --- a/interface/src/project/SchedulerDialog.tsx +++ b/interface/src/project/SchedulerDialog.tsx @@ -40,7 +40,15 @@ interface SchedulerDialogProps { dow: string[]; } -const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, validator, dow }: SchedulerDialogProps) => { +const SchedulerDialog = ({ + open, + creating, + onClose, + onSave, + selectedItem, + validator, + dow +}: SchedulerDialogProps) => { const { LL } = useI18nContext(); const [editItem, setEditItem] = useState(selectedItem); const [fieldErrors, setFieldErrors] = useState(); @@ -111,8 +119,14 @@ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, valida }; const showFlag = (si: ScheduleItem, flag: number) => ( - - {flag === ScheduleFlag.SCHEDULE_TIMER ? LL.TIMER(0) : dow[Math.log(flag) / Math.log(2)]} + + {flag === ScheduleFlag.SCHEDULE_TIMER + ? LL.TIMER(0) + : dow[Math.log(flag) / Math.log(2)]} ); @@ -121,7 +135,8 @@ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, valida return ( - {creating ? LL.ADD(1) + ' ' + LL.NEW(0) : LL.EDIT()} {LL.SCHEDULE(1)} + {creating ? LL.ADD(1) + ' ' + LL.NEW(0) : LL.EDIT()}  + {LL.SCHEDULE(1)} @@ -134,13 +149,27 @@ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, valida setEditItem({ ...editItem, flags: getFlagNumber(flag) & 127 }); }} > - {showFlag(editItem, ScheduleFlag.SCHEDULE_MON)} - {showFlag(editItem, ScheduleFlag.SCHEDULE_TUE)} - {showFlag(editItem, ScheduleFlag.SCHEDULE_WED)} - {showFlag(editItem, ScheduleFlag.SCHEDULE_THU)} - {showFlag(editItem, ScheduleFlag.SCHEDULE_FRI)} - {showFlag(editItem, ScheduleFlag.SCHEDULE_SAT)} - {showFlag(editItem, ScheduleFlag.SCHEDULE_SUN)} + + {showFlag(editItem, ScheduleFlag.SCHEDULE_MON)} + + + {showFlag(editItem, ScheduleFlag.SCHEDULE_TUE)} + + + {showFlag(editItem, ScheduleFlag.SCHEDULE_WED)} + + + {showFlag(editItem, ScheduleFlag.SCHEDULE_THU)} + + + {showFlag(editItem, ScheduleFlag.SCHEDULE_FRI)} + + + {showFlag(editItem, ScheduleFlag.SCHEDULE_SAT)} + + + {showFlag(editItem, ScheduleFlag.SCHEDULE_SUN)} + @@ -160,7 +189,10 @@ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, valida size="large" variant="outlined" onClick={() => { - setEditItem({ ...editItem, flags: ScheduleFlag.SCHEDULE_TIMER }); + setEditItem({ + ...editItem, + flags: ScheduleFlag.SCHEDULE_TIMER + }); }} > {showFlag(editItem, ScheduleFlag.SCHEDULE_TIMER)} @@ -170,7 +202,13 @@ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, valida } + control={ + + } label={LL.ACTIVE()} /> @@ -220,15 +258,30 @@ const SchedulerDialog = ({ open, creating, onClose, onSave, selectedItem, valida {!creating && ( - )} - - diff --git a/interface/src/project/Sensors.tsx b/interface/src/project/Sensors.tsx index c56d41fb5..d600c3bfc 100644 --- a/interface/src/project/Sensors.tsx +++ b/interface/src/project/Sensors.tsx @@ -10,7 +10,15 @@ import UnfoldMoreOutlinedIcon from '@mui/icons-material/UnfoldMoreOutlined'; import { Box, Button, Typography } from '@mui/material'; import { SortToggleType, useSort } from '@table-library/react-table-library/sort'; -import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table'; +import { + Body, + Cell, + Header, + HeaderCell, + HeaderRow, + Row, + Table +} from '@table-library/react-table-library/table'; import { useTheme } from '@table-library/react-table-library/theme'; import type { State } from '@table-library/react-table-library/types/common'; import { useRequest } from 'alova'; @@ -21,28 +29,45 @@ import { useI18nContext } from 'i18n/i18n-react'; import * as EMSESP from './api'; import DashboardSensorsAnalogDialog from './SensorsAnalogDialog'; import DashboardSensorsTemperatureDialog from './SensorsTemperatureDialog'; -import { AnalogType, AnalogTypeNames, DeviceValueUOM, DeviceValueUOM_s } from './types'; -import type { AnalogSensor, TemperatureSensor, WriteAnalogSensor, WriteTemperatureSensor } from './types'; -import { analogSensorItemValidation, temperatureSensorItemValidation } from './validators'; +import { + AnalogType, + AnalogTypeNames, + DeviceValueUOM, + DeviceValueUOM_s +} from './types'; +import type { + AnalogSensor, + TemperatureSensor, + WriteAnalogSensor, + WriteTemperatureSensor +} from './types'; +import { + analogSensorItemValidation, + temperatureSensorItemValidation +} from './validators'; const Sensors: FC = () => { const { LL } = useI18nContext(); const { me } = useContext(AuthenticatedContext); - const [selectedTemperatureSensor, setSelectedTemperatureSensor] = useState(); + const [selectedTemperatureSensor, setSelectedTemperatureSensor] = + useState(); const [selectedAnalogSensor, setSelectedAnalogSensor] = useState(); const [temperatureDialogOpen, setTemperatureDialogOpen] = useState(false); const [analogDialogOpen, setAnalogDialogOpen] = useState(false); const [creating, setCreating] = useState(false); - const { data: sensorData, send: fetchSensorData } = useRequest(() => EMSESP.readSensorData(), { - initialData: { - ts: [], - as: [], - analog_enabled: false, - platform: 'ESP32' + const { data: sensorData, send: fetchSensorData } = useRequest( + () => EMSESP.readSensorData(), + { + initialData: { + ts: [], + as: [], + analog_enabled: false, + platform: 'ESP32' + } } - }); + ); const { send: writeTemperatureSensor } = useRequest( (data: WriteTemperatureSensor) => EMSESP.writeTemperatureSensor(data), @@ -51,9 +76,12 @@ const Sensors: FC = () => { } ); - const { send: writeAnalogSensor } = useRequest((data: WriteAnalogSensor) => EMSESP.writeAnalogSensor(data), { - immediate: false - }); + const { send: writeAnalogSensor } = useRequest( + (data: WriteAnalogSensor) => EMSESP.writeAnalogSensor(data), + { + immediate: false + } + ); const common_theme = useTheme({ BaseRow: ` @@ -304,7 +332,12 @@ const Sensors: FC = () => { }; const RenderTemperatureSensors = () => ( -
+
{(tableList: TemperatureSensor[]) => ( <>
@@ -314,7 +347,9 @@ const Sensors: FC = () => { fullWidth style={{ fontSize: '14px', justifyContent: 'flex-start' }} endIcon={getSortIcon(temperature_sort.state, 'NAME')} - onClick={() => temperature_sort.fns.onToggleSort({ sortKey: 'NAME' })} + onClick={() => + temperature_sort.fns.onToggleSort({ sortKey: 'NAME' }) + } > {LL.NAME(0)} @@ -324,7 +359,9 @@ const Sensors: FC = () => { fullWidth style={{ fontSize: '14px', justifyContent: 'flex-end' }} endIcon={getSortIcon(temperature_sort.state, 'VALUE')} - onClick={() => temperature_sort.fns.onToggleSort({ sortKey: 'VALUE' })} + onClick={() => + temperature_sort.fns.onToggleSort({ sortKey: 'VALUE' }) + } > {LL.VALUE(0)} @@ -345,7 +382,12 @@ const Sensors: FC = () => { ); const RenderAnalogSensors = () => ( -
+
{(tableList: AnalogSensor[]) => ( <>
@@ -439,7 +481,11 @@ const Sensors: FC = () => { onSave={onAnalogDialogSave} creating={creating} selectedItem={selectedAnalogSensor} - validator={analogSensorItemValidation(sensorData.as, creating, sensorData.platform)} + validator={analogSensorItemValidation( + sensorData.as, + creating, + sensorData.platform + )} /> )} @@ -447,7 +493,12 @@ const Sensors: FC = () => { - diff --git a/interface/src/project/SensorsAnalogDialog.tsx b/interface/src/project/SensorsAnalogDialog.tsx index d47fdb00b..8ef784d77 100644 --- a/interface/src/project/SensorsAnalogDialog.tsx +++ b/interface/src/project/SensorsAnalogDialog.tsx @@ -79,7 +79,8 @@ const SensorsAnalogDialog = ({ return ( - {creating ? LL.ADD(1) + ' ' + LL.NEW(0) : LL.EDIT()} {LL.ANALOG_SENSOR(0)} + {creating ? LL.ADD(1) + ' ' + LL.NEW(0) : LL.EDIT()}  + {LL.ANALOG_SENSOR(0)} @@ -113,7 +114,14 @@ const SensorsAnalogDialog = ({ /> - + {AnalogTypeNames.map((val, i) => ( {val} @@ -123,7 +131,14 @@ const SensorsAnalogDialog = ({ {editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && ( - + {DeviceValueUOM_s.map((val, i) => ( {val} @@ -144,7 +159,9 @@ const SensorsAnalogDialog = ({ onChange={updateFormValue} inputProps={{ min: '0', max: '3300', step: '1' }} InputProps={{ - startAdornment: mV + startAdornment: ( + mV + ) }} /> @@ -177,70 +194,75 @@ const SensorsAnalogDialog = ({ /> )} - {editItem.t === AnalogType.DIGITAL_OUT && (editItem.g === 25 || editItem.g === 26) && ( - - - - )} - {editItem.t === AnalogType.DIGITAL_OUT && editItem.g !== 25 && editItem.g !== 26 && ( - <> + {editItem.t === AnalogType.DIGITAL_OUT && + (editItem.g === 25 || editItem.g === 26) && ( - {LL.OFF()} - {LL.ON()} - + inputProps={{ min: '0', max: '255', step: '1' }} + /> - - - {LL.ACTIVEHIGH()} - {LL.ACTIVELOW()} - - - - - {LL.UNCHANGED()} - - {LL.ALWAYS()} {LL.OFF()} - - - {LL.ALWAYS()} {LL.ON()} - - - - - )} - {(editItem.t === AnalogType.PWM_0 || editItem.t === AnalogType.PWM_1 || editItem.t === AnalogType.PWM_2) && ( + )} + {editItem.t === AnalogType.DIGITAL_OUT && + editItem.g !== 25 && + editItem.g !== 26 && ( + <> + + + {LL.OFF()} + {LL.ON()} + + + + + {LL.ACTIVEHIGH()} + {LL.ACTIVELOW()} + + + + + {LL.UNCHANGED()} + + {LL.ALWAYS()} {LL.OFF()} + + + {LL.ALWAYS()} {LL.ON()} + + + + + )} + {(editItem.t === AnalogType.PWM_0 || + editItem.t === AnalogType.PWM_1 || + editItem.t === AnalogType.PWM_2) && ( <> Hz + startAdornment: ( + Hz + ) }} /> @@ -268,7 +292,9 @@ const SensorsAnalogDialog = ({ onChange={updateFormValue} inputProps={{ min: '0', max: '100', step: '0.1' }} InputProps={{ - startAdornment: % + startAdornment: ( + % + ) }} /> @@ -279,15 +305,30 @@ const SensorsAnalogDialog = ({ {!creating && ( - )} - - diff --git a/interface/src/project/SensorsTemperatureDialog.tsx b/interface/src/project/SensorsTemperatureDialog.tsx index 8db0b7ea7..b45cf76d0 100644 --- a/interface/src/project/SensorsTemperatureDialog.tsx +++ b/interface/src/project/SensorsTemperatureDialog.tsx @@ -107,10 +107,20 @@ const SensorsTemperatureDialog = ({ - - diff --git a/interface/src/project/SystemActivity.tsx b/interface/src/project/SystemActivity.tsx index 958333d50..4e868e846 100644 --- a/interface/src/project/SystemActivity.tsx +++ b/interface/src/project/SystemActivity.tsx @@ -4,7 +4,15 @@ import type { FC } from 'react'; import RefreshIcon from '@mui/icons-material/Refresh'; import { Button } from '@mui/material'; -import { Body, Cell, Header, HeaderCell, HeaderRow, Row, Table } from '@table-library/react-table-library/table'; +import { + Body, + Cell, + Header, + HeaderCell, + HeaderRow, + Row, + Table +} from '@table-library/react-table-library/table'; import { useTheme as tableTheme } from '@table-library/react-table-library/theme'; import { useRequest } from 'alova'; import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components'; @@ -93,7 +101,11 @@ const SystemActivity: FC = () => { return ( <> -
+
{(tableList: Stat[]) => ( <>
@@ -118,7 +130,12 @@ const SystemActivity: FC = () => { )}
- diff --git a/interface/src/project/api.ts b/interface/src/project/api.ts index 8d430e750..42f69a687 100644 --- a/interface/src/project/api.ts +++ b/interface/src/project/api.ts @@ -30,17 +30,20 @@ export const writeDeviceValue = (data: { id: number; c: string; v: unknown }) => // Application Settings export const readSettings = () => alovaInstance.Get('/rest/settings'); -export const writeSettings = (data: Settings) => alovaInstance.Post('/rest/settings', data); +export const writeSettings = (data: Settings) => + alovaInstance.Post('/rest/settings', data); export const getBoardProfile = (boardProfile: string) => alovaInstance.Get('/rest/boardProfile', { params: { boardProfile } }); // Sensors -export const readSensorData = () => alovaInstance.Get('/rest/sensorData'); +export const readSensorData = () => + alovaInstance.Get('/rest/sensorData'); export const writeTemperatureSensor = (ts: WriteTemperatureSensor) => alovaInstance.Post('/rest/writeTemperatureSensor', ts); -export const writeAnalogSensor = (as: WriteAnalogSensor) => alovaInstance.Post('/rest/writeAnalogSensor', as); +export const writeAnalogSensor = (as: WriteAnalogSensor) => + alovaInstance.Post('/rest/writeAnalogSensor', as); // Activity export const readActivity = () => alovaInstance.Get('/rest/activity'); @@ -73,9 +76,12 @@ export const readDeviceEntities = (id: number) => } }); export const readDevices = () => alovaInstance.Get('/rest/devices'); -export const resetCustomizations = () => alovaInstance.Post('/rest/resetCustomizations'); -export const writeCustomizationEntities = (data: { id: number; entity_ids: string[] }) => - alovaInstance.Post('/rest/customizationEntities', data); +export const resetCustomizations = () => + alovaInstance.Post('/rest/resetCustomizations'); +export const writeCustomizationEntities = (data: { + id: number; + entity_ids: string[]; +}) => alovaInstance.Post('/rest/customizationEntities', data); // SettingsScheduler export const readSchedule = () => @@ -95,7 +101,8 @@ export const readSchedule = () => })); } }); -export const writeSchedule = (data: Schedule) => alovaInstance.Post('/rest/schedule', data); +export const writeSchedule = (data: Schedule) => + alovaInstance.Post('/rest/schedule', data); // SettingsEntities export const readCustomEntities = () => diff --git a/interface/src/project/deviceValue.ts b/interface/src/project/deviceValue.ts index 4af02ef3b..eb27a6f71 100644 --- a/interface/src/project/deviceValue.ts +++ b/interface/src/project/deviceValue.ts @@ -25,7 +25,11 @@ const formatDurationMin = (LL: TranslationFunctions, duration_min: number) => { return formatted; }; -export function formatValue(LL: TranslationFunctions, value: unknown, uom: DeviceValueUOM) { +export function formatValue( + LL: TranslationFunctions, + value: unknown, + uom: DeviceValueUOM +) { if (typeof value !== 'number') { return ''; } diff --git a/interface/src/project/validators.ts b/interface/src/project/validators.ts index c3116e94e..ab86314a0 100644 --- a/interface/src/project/validators.ts +++ b/interface/src/project/validators.ts @@ -5,7 +5,11 @@ import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared'; import type { AnalogSensor, DeviceValue, ScheduleItem, Settings } from './types'; export const GPIO_VALIDATOR = { - validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) { + validator( + rule: InternalRuleItem, + value: number, + callback: (error?: string) => void + ) { if ( value && (value === 1 || @@ -24,7 +28,11 @@ export const GPIO_VALIDATOR = { }; export const GPIO_VALIDATORR = { - validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) { + validator( + rule: InternalRuleItem, + value: number, + callback: (error?: string) => void + ) { if ( value && (value === 1 || @@ -44,7 +52,11 @@ export const GPIO_VALIDATORR = { }; export const GPIO_VALIDATORC3 = { - validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) { + validator( + rule: InternalRuleItem, + value: number, + callback: (error?: string) => void + ) { if (value && ((value >= 11 && value <= 19) || value > 21 || value < 0)) { callback('Must be an valid GPIO port'); } else { @@ -54,8 +66,18 @@ export const GPIO_VALIDATORC3 = { }; export const GPIO_VALIDATORS2 = { - validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) { - if (value && ((value >= 19 && value <= 20) || (value >= 22 && value <= 32) || value > 40 || value < 0)) { + validator( + rule: InternalRuleItem, + value: number, + callback: (error?: string) => void + ) { + if ( + value && + ((value >= 19 && value <= 20) || + (value >= 22 && value <= 32) || + value > 40 || + value < 0) + ) { callback('Must be an valid GPIO port'); } else { callback(); @@ -64,7 +86,11 @@ export const GPIO_VALIDATORS2 = { }; export const GPIO_VALIDATORS3 = { - validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) { + validator( + rule: InternalRuleItem, + value: number, + callback: (error?: string) => void + ) { if ( value && ((value >= 19 && value <= 20) || @@ -84,46 +110,121 @@ export const createSettingsValidator = (settings: Settings) => new Schema({ ...(settings.board_profile === 'CUSTOM' && settings.platform === 'ESP32' && { - led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATOR], - dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATOR], - pbutton_gpio: [{ required: true, message: 'Button GPIO is required' }, GPIO_VALIDATOR], - tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATOR], + led_gpio: [ + { required: true, message: 'LED GPIO is required' }, + GPIO_VALIDATOR + ], + dallas_gpio: [ + { required: true, message: 'GPIO is required' }, + GPIO_VALIDATOR + ], + pbutton_gpio: [ + { required: true, message: 'Button GPIO is required' }, + GPIO_VALIDATOR + ], + tx_gpio: [ + { required: true, message: 'Tx GPIO is required' }, + GPIO_VALIDATOR + ], rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATOR] }), ...(settings.board_profile === 'CUSTOM' && settings.platform === 'ESP32R' && { - led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATORR], - dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATORR], - pbutton_gpio: [{ required: true, message: 'Button GPIO is required' }, GPIO_VALIDATORR], - tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATORR], - rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATORR] + led_gpio: [ + { required: true, message: 'LED GPIO is required' }, + GPIO_VALIDATORR + ], + dallas_gpio: [ + { required: true, message: 'GPIO is required' }, + GPIO_VALIDATORR + ], + pbutton_gpio: [ + { required: true, message: 'Button GPIO is required' }, + GPIO_VALIDATORR + ], + tx_gpio: [ + { required: true, message: 'Tx GPIO is required' }, + GPIO_VALIDATORR + ], + rx_gpio: [ + { required: true, message: 'Rx GPIO is required' }, + GPIO_VALIDATORR + ] }), ...(settings.board_profile === 'CUSTOM' && settings.platform === 'ESP32-C3' && { - led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATORC3], - dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATORC3], - pbutton_gpio: [{ required: true, message: 'Button GPIO is required' }, GPIO_VALIDATORC3], - tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATORC3], - rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATORC3] + led_gpio: [ + { required: true, message: 'LED GPIO is required' }, + GPIO_VALIDATORC3 + ], + dallas_gpio: [ + { required: true, message: 'GPIO is required' }, + GPIO_VALIDATORC3 + ], + pbutton_gpio: [ + { required: true, message: 'Button GPIO is required' }, + GPIO_VALIDATORC3 + ], + tx_gpio: [ + { required: true, message: 'Tx GPIO is required' }, + GPIO_VALIDATORC3 + ], + rx_gpio: [ + { required: true, message: 'Rx GPIO is required' }, + GPIO_VALIDATORC3 + ] }), ...(settings.board_profile === 'CUSTOM' && settings.platform === 'ESP32-S2' && { - led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATORS2], - dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATORS2], - pbutton_gpio: [{ required: true, message: 'Button GPIO is required' }, GPIO_VALIDATORS2], - tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATORS2], - rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATORS2] + led_gpio: [ + { required: true, message: 'LED GPIO is required' }, + GPIO_VALIDATORS2 + ], + dallas_gpio: [ + { required: true, message: 'GPIO is required' }, + GPIO_VALIDATORS2 + ], + pbutton_gpio: [ + { required: true, message: 'Button GPIO is required' }, + GPIO_VALIDATORS2 + ], + tx_gpio: [ + { required: true, message: 'Tx GPIO is required' }, + GPIO_VALIDATORS2 + ], + rx_gpio: [ + { required: true, message: 'Rx GPIO is required' }, + GPIO_VALIDATORS2 + ] }), ...(settings.board_profile === 'CUSTOM' && settings.platform === 'ESP32-S3' && { - led_gpio: [{ required: true, message: 'LED GPIO is required' }, GPIO_VALIDATORS3], - dallas_gpio: [{ required: true, message: 'GPIO is required' }, GPIO_VALIDATORS3], - pbutton_gpio: [{ required: true, message: 'Button GPIO is required' }, GPIO_VALIDATORS3], - tx_gpio: [{ required: true, message: 'Tx GPIO is required' }, GPIO_VALIDATORS3], - rx_gpio: [{ required: true, message: 'Rx GPIO is required' }, GPIO_VALIDATORS3] + led_gpio: [ + { required: true, message: 'LED GPIO is required' }, + GPIO_VALIDATORS3 + ], + dallas_gpio: [ + { required: true, message: 'GPIO is required' }, + GPIO_VALIDATORS3 + ], + pbutton_gpio: [ + { required: true, message: 'Button GPIO is required' }, + GPIO_VALIDATORS3 + ], + tx_gpio: [ + { required: true, message: 'Tx GPIO is required' }, + GPIO_VALIDATORS3 + ], + rx_gpio: [ + { required: true, message: 'Rx GPIO is required' }, + GPIO_VALIDATORS3 + ] }), ...(settings.syslog_enabled && { - syslog_host: [{ required: true, message: 'Host is required' }, IP_OR_HOSTNAME_VALIDATOR], + syslog_host: [ + { required: true, message: 'Host is required' }, + IP_OR_HOSTNAME_VALIDATOR + ], syslog_port: [ { required: true, message: 'Port is required' }, { type: 'number', min: 0, max: 65535, message: 'Invalid Port' } @@ -134,14 +235,35 @@ export const createSettingsValidator = (settings: Settings) => ] }), ...(settings.shower_alert && { - shower_alert_trigger: [{ type: 'number', min: 1, max: 20, message: 'Time must be between 1 and 20 minutes' }], - shower_alert_coldshot: [{ type: 'number', min: 1, max: 10, message: 'Time must be between 1 and 10 seconds' }] + shower_alert_trigger: [ + { + type: 'number', + min: 1, + max: 20, + message: 'Time must be between 1 and 20 minutes' + } + ], + shower_alert_coldshot: [ + { + type: 'number', + min: 1, + max: 10, + message: 'Time must be between 1 and 10 seconds' + } + ] }) }); export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) => ({ - validator(rule: InternalRuleItem, name: string, callback: (error?: string) => void) { - if ((o_name === undefined || o_name !== name) && schedule.find((si) => si.name === name)) { + validator( + rule: InternalRuleItem, + name: string, + callback: (error?: string) => void + ) { + if ( + (o_name === undefined || o_name !== name) && + schedule.find((si) => si.name === name) + ) { callback('Name already in use'); } else { callback(); @@ -149,7 +271,10 @@ export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) = } }); -export const schedulerItemValidation = (schedule: ScheduleItem[], scheduleItem: ScheduleItem) => +export const schedulerItemValidation = ( + schedule: ScheduleItem[], + scheduleItem: ScheduleItem +) => new Schema({ name: [ { @@ -162,7 +287,12 @@ export const schedulerItemValidation = (schedule: ScheduleItem[], scheduleItem: ], cmd: [ { required: true, message: 'Command is required' }, - { type: 'string', min: 1, max: 64, message: 'Command must be 1-64 characters' } + { + type: 'string', + min: 1, + max: 64, + message: 'Command must be 1-64 characters' + } ] }); @@ -178,7 +308,11 @@ export const entityItemValidation = () => ], device_id: [ { - validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { + validator( + rule: InternalRuleItem, + value: string, + callback: (error?: string) => void + ) { if (isNaN(parseInt(value, 16))) { callback('Is required and must be in hex format'); } @@ -188,7 +322,11 @@ export const entityItemValidation = () => ], type_id: [ { - validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { + validator( + rule: InternalRuleItem, + value: string, + callback: (error?: string) => void + ) { if (isNaN(parseInt(value, 16))) { callback('Is required and must be in hex format'); } @@ -208,7 +346,11 @@ export const temperatureSensorItemValidation = () => }); export const isGPIOUniqueValidator = (sensors: AnalogSensor[]) => ({ - validator(rule: InternalRuleItem, gpio: number, callback: (error?: string) => void) { + validator( + rule: InternalRuleItem, + gpio: number, + callback: (error?: string) => void + ) { if (sensors.find((as) => as.g === gpio)) { callback('GPIO already in use'); } else { @@ -217,7 +359,11 @@ export const isGPIOUniqueValidator = (sensors: AnalogSensor[]) => ({ } }); -export const analogSensorItemValidation = (sensors: AnalogSensor[], creating: boolean, platform: string) => +export const analogSensorItemValidation = ( + sensors: AnalogSensor[], + creating: boolean, + platform: string +) => new Schema({ n: [{ required: true, message: 'Name is required' }], g: [ @@ -240,8 +386,17 @@ export const deviceValueItemValidation = (dv: DeviceValue) => v: [ { required: true, message: 'Value is required' }, { - validator(rule: InternalRuleItem, value: unknown, callback: (error?: string) => void) { - if (typeof value === 'number' && dv.m && dv.x && (value < dv.m || value > dv.x)) { + validator( + rule: InternalRuleItem, + value: unknown, + callback: (error?: string) => void + ) { + if ( + typeof value === 'number' && + dv.m && + dv.x && + (value < dv.m || value > dv.x) + ) { callback('Value out of range'); } callback(); diff --git a/interface/src/utils/route.ts b/interface/src/utils/route.ts index 21773adfb..62c0395e9 100644 --- a/interface/src/utils/route.ts +++ b/interface/src/utils/route.ts @@ -1 +1,2 @@ -export const routeMatches = (route: string, pathname: string) => pathname.startsWith(route + '/') || pathname === route; +export const routeMatches = (route: string, pathname: string) => + pathname.startsWith(route + '/') || pathname === route; diff --git a/interface/src/utils/time.ts b/interface/src/utils/time.ts index 3fefb063c..8189fef84 100644 --- a/interface/src/utils/time.ts +++ b/interface/src/utils/time.ts @@ -8,7 +8,11 @@ const LOCALE_FORMAT = new Intl.DateTimeFormat([...window.navigator.languages], { hour12: false }); -export const formatDateTime = (dateTime: string) => LOCALE_FORMAT.format(new Date(dateTime.substring(0, 19))); +export const formatDateTime = (dateTime: string) => + LOCALE_FORMAT.format(new Date(dateTime.substring(0, 19))); export const formatLocalDateTime = (date: Date) => - new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, -1).substring(0, 19); + new Date(date.getTime() - date.getTimezoneOffset() * 60000) + .toISOString() + .slice(0, -1) + .substring(0, 19); diff --git a/interface/src/utils/useRest.ts b/interface/src/utils/useRest.ts index aaa4c232a..dc4d00078 100644 --- a/interface/src/utils/useRest.ts +++ b/interface/src/utils/useRest.ts @@ -22,7 +22,12 @@ export const useRest = ({ read, update }: RestRequestOptions) => { const [dirtyFlags, setDirtyFlags] = useState([]); const blocker = useBlocker(dirtyFlags.length !== 0); - const { data, send: readData, update: updateData, onComplete: onReadComplete } = useRequest(read()); + const { + data, + send: readData, + update: updateData, + onComplete: onReadComplete + } = useRequest(read()); const { loading: saving, diff --git a/interface/src/validators/ap.ts b/interface/src/validators/ap.ts index 0bcbc9264..f67c830e4 100644 --- a/interface/src/validators/ap.ts +++ b/interface/src/validators/ap.ts @@ -6,15 +6,27 @@ import { IP_ADDRESS_VALIDATOR } from './shared'; export const createAPSettingsValidator = (apSettings: APSettingsType) => new Schema({ - provision_mode: { required: true, message: 'Please provide a provision mode' }, + provision_mode: { + required: true, + message: 'Please provide a provision mode' + }, ...(isAPEnabled(apSettings) && { ssid: [ { required: true, message: 'Please provide an SSID' }, - { type: 'string', max: 32, message: 'SSID must be 32 characters or less' } + { + type: 'string', + max: 32, + message: 'SSID must be 32 characters or less' + } ], password: [ { required: true, message: 'Please provide an access point password' }, - { type: 'string', min: 8, max: 64, message: 'Password must be 8-64 characters' } + { + type: 'string', + min: 8, + max: 64, + message: 'Password must be 8-64 characters' + } ], channel: [ { required: true, message: 'Please provide a network channel' }, @@ -22,10 +34,24 @@ export const createAPSettingsValidator = (apSettings: APSettingsType) => ], max_clients: [ { required: true, message: 'Please specify a value for max clients' }, - { type: 'number', min: 1, max: 9, message: 'Max clients must be between 1 and 9' } + { + type: 'number', + min: 1, + max: 9, + message: 'Max clients must be between 1 and 9' + } ], - local_ip: [{ required: true, message: 'Local IP address is required' }, IP_ADDRESS_VALIDATOR], - gateway_ip: [{ required: true, message: 'Gateway IP address is required' }, IP_ADDRESS_VALIDATOR], - subnet_mask: [{ required: true, message: 'Subnet mask is required' }, IP_ADDRESS_VALIDATOR] + local_ip: [ + { required: true, message: 'Local IP address is required' }, + IP_ADDRESS_VALIDATOR + ], + gateway_ip: [ + { required: true, message: 'Gateway IP address is required' }, + IP_ADDRESS_VALIDATOR + ], + subnet_mask: [ + { required: true, message: 'Subnet mask is required' }, + IP_ADDRESS_VALIDATOR + ] }) }); diff --git a/interface/src/validators/mqtt.ts b/interface/src/validators/mqtt.ts index 23454ed7c..c90d3c23a 100644 --- a/interface/src/validators/mqtt.ts +++ b/interface/src/validators/mqtt.ts @@ -6,7 +6,10 @@ import { IP_OR_HOSTNAME_VALIDATOR } from './shared'; export const createMqttSettingsValidator = (mqttSettings: MqttSettingsType) => new Schema({ ...(mqttSettings.enabled && { - host: [{ required: true, message: 'Host is required' }, IP_OR_HOSTNAME_VALIDATOR], + host: [ + { required: true, message: 'Host is required' }, + IP_OR_HOSTNAME_VALIDATOR + ], base: { required: true, message: 'Base is required' }, port: [ { required: true, message: 'Port is required' }, @@ -14,11 +17,21 @@ export const createMqttSettingsValidator = (mqttSettings: MqttSettingsType) => ], keep_alive: [ { required: true, message: 'Keep alive is required' }, - { type: 'number', min: 1, max: 86400, message: 'Keep alive must be between 1 and 86400' } + { + type: 'number', + min: 1, + max: 86400, + message: 'Keep alive must be between 1 and 86400' + } ], publish_time_heartbeat: [ { required: true, message: 'Heartbeat is required' }, - { type: 'number', min: 10, max: 86400, message: 'Heartbeat must be between 10 and 86400' } + { + type: 'number', + min: 10, + max: 86400, + message: 'Heartbeat must be between 10 and 86400' + } ] }) }); diff --git a/interface/src/validators/network.ts b/interface/src/validators/network.ts index d329c5bf8..4f5f8e866 100644 --- a/interface/src/validators/network.ts +++ b/interface/src/validators/network.ts @@ -3,16 +3,42 @@ import type { NetworkSettingsType } from 'types'; import { HOSTNAME_VALIDATOR, IP_ADDRESS_VALIDATOR } from './shared'; -export const createNetworkSettingsValidator = (networkSettings: NetworkSettingsType) => +export const createNetworkSettingsValidator = ( + networkSettings: NetworkSettingsType +) => new Schema({ - ssid: [{ type: 'string', max: 32, message: 'SSID must be 32 characters or less' }], - bssid: [{ type: 'string', max: 17, message: 'BSSID must be 17 characters or empty' }], - password: { type: 'string', max: 64, message: 'Password must be 64 characters or less' }, - hostname: [{ required: true, message: 'Hostname is required' }, HOSTNAME_VALIDATOR], + ssid: [ + { type: 'string', max: 32, message: 'SSID must be 32 characters or less' } + ], + bssid: [ + { + type: 'string', + max: 17, + message: 'BSSID must be 17 characters or empty' + } + ], + password: { + type: 'string', + max: 64, + message: 'Password must be 64 characters or less' + }, + hostname: [ + { required: true, message: 'Hostname is required' }, + HOSTNAME_VALIDATOR + ], ...(networkSettings.static_ip_config && { - local_ip: [{ required: true, message: 'Local IP is required' }, IP_ADDRESS_VALIDATOR], - gateway_ip: [{ required: true, message: 'Gateway IP is required' }, IP_ADDRESS_VALIDATOR], - subnet_mask: [{ required: true, message: 'Subnet mask is required' }, IP_ADDRESS_VALIDATOR], + local_ip: [ + { required: true, message: 'Local IP is required' }, + IP_ADDRESS_VALIDATOR + ], + gateway_ip: [ + { required: true, message: 'Gateway IP is required' }, + IP_ADDRESS_VALIDATOR + ], + subnet_mask: [ + { required: true, message: 'Subnet mask is required' }, + IP_ADDRESS_VALIDATOR + ], dns_ip_1: IP_ADDRESS_VALIDATOR, dns_ip_2: IP_ADDRESS_VALIDATOR }) diff --git a/interface/src/validators/ntp.ts b/interface/src/validators/ntp.ts index 4ca83833e..81aad6932 100644 --- a/interface/src/validators/ntp.ts +++ b/interface/src/validators/ntp.ts @@ -3,7 +3,10 @@ import Schema from 'async-validator'; import { IP_OR_HOSTNAME_VALIDATOR } from './shared'; export const NTP_SETTINGS_VALIDATOR = new Schema({ - server: [{ required: true, message: 'Server is required' }, IP_OR_HOSTNAME_VALIDATOR], + server: [ + { required: true, message: 'Server is required' }, + IP_OR_HOSTNAME_VALIDATOR + ], tz_label: { required: true, message: 'Time zone is required' diff --git a/interface/src/validators/security.ts b/interface/src/validators/security.ts index b5a59f540..6b9fa8005 100644 --- a/interface/src/validators/security.ts +++ b/interface/src/validators/security.ts @@ -5,12 +5,21 @@ import type { UserType } from 'types'; export const SECURITY_SETTINGS_VALIDATOR = new Schema({ jwt_secret: [ { required: true, message: 'JWT secret is required' }, - { type: 'string', min: 1, max: 64, message: 'JWT secret must be between 1 and 64 characters' } + { + type: 'string', + min: 1, + max: 64, + message: 'JWT secret must be between 1 and 64 characters' + } ] }); export const createUniqueUsernameValidator = (users: UserType[]) => ({ - validator(rule: InternalRuleItem, username: string, callback: (error?: string) => void) { + validator( + rule: InternalRuleItem, + username: string, + callback: (error?: string) => void + ) { if (username && users.find((u) => u.username === username)) { callback('Username already in use'); } else { @@ -32,6 +41,11 @@ export const createUserValidator = (users: UserType[], creating: boolean) => ], password: [ { required: true, message: 'Please provide a password' }, - { type: 'string', min: 1, max: 64, message: 'Password must be 1-64 characters' } + { + type: 'string', + min: 1, + max: 64, + message: 'Password must be 1-64 characters' + } ] }); diff --git a/interface/src/validators/shared.ts b/interface/src/validators/shared.ts index 72ea586e3..4c9d5c23b 100644 --- a/interface/src/validators/shared.ts +++ b/interface/src/validators/shared.ts @@ -7,13 +7,17 @@ export const validate = ( options?: ValidateOption ): Promise => new Promise((resolve, reject) => { - void validator.validate(source, options ? options : {}, (errors, fieldErrors) => { - if (errors) { - reject(fieldErrors); - } else { - resolve(source as T); + void validator.validate( + source, + options ? options : {}, + (errors, fieldErrors) => { + if (errors) { + reject(fieldErrors); + } else { + resolve(source as T); + } } - }); + ); }); // updated to support both IPv4 and IPv6 @@ -23,7 +27,11 @@ const IP_ADDRESS_REGEXP = const isValidIpAddress = (value: string) => IP_ADDRESS_REGEXP.test(value); export const IP_ADDRESS_VALIDATOR = { - validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { + validator( + rule: InternalRuleItem, + value: string, + callback: (error?: string) => void + ) { if (value && !isValidIpAddress(value)) { callback('Must be an IP address'); } else { @@ -36,10 +44,15 @@ const HOSTNAME_LENGTH_REGEXP = /^.{0,200}$/; const HOSTNAME_PATTERN_REGEXP = /^(([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 isValidHostname = (value: string) => HOSTNAME_LENGTH_REGEXP.test(value) && HOSTNAME_PATTERN_REGEXP.test(value); +const isValidHostname = (value: string) => + HOSTNAME_LENGTH_REGEXP.test(value) && HOSTNAME_PATTERN_REGEXP.test(value); export const HOSTNAME_VALIDATOR = { - validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { + validator( + rule: InternalRuleItem, + value: string, + callback: (error?: string) => void + ) { if (value && !isValidHostname(value)) { callback('Must be a valid hostname'); } else { @@ -49,7 +62,11 @@ export const HOSTNAME_VALIDATOR = { }; export const IP_OR_HOSTNAME_VALIDATOR = { - validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) { + validator( + rule: InternalRuleItem, + value: string, + callback: (error?: string) => void + ) { if (value && !(isValidIpAddress(value) || isValidHostname(value))) { callback('Must be a valid IP address or hostname'); } else { diff --git a/interface/src/validators/system.ts b/interface/src/validators/system.ts index 6bf3bc8fe..32e6865c9 100644 --- a/interface/src/validators/system.ts +++ b/interface/src/validators/system.ts @@ -3,10 +3,20 @@ import Schema from 'async-validator'; export const OTA_SETTINGS_VALIDATOR = new Schema({ port: [ { required: true, message: 'Port is required' }, - { type: 'number', min: 1025, max: 65535, message: 'Port must be between 1025 and 65535' } + { + type: 'number', + min: 1025, + max: 65535, + message: 'Port must be between 1025 and 65535' + } ], password: [ { required: true, message: 'Password is required' }, - { type: 'string', min: 1, max: 64, message: 'Password must be between 1 and 64 characters' } + { + type: 'string', + min: 1, + max: 64, + message: 'Password must be between 1 and 64 characters' + } ] }); diff --git a/interface/vite.config.ts b/interface/vite.config.ts index dd0bbdf39..e6af6f13b 100644 --- a/interface/vite.config.ts +++ b/interface/vite.config.ts @@ -121,7 +121,11 @@ export default defineConfig(({ command, mode }) => { manualChunks(id: string) { if (id.includes('node_modules')) { // creating a chunk to react routes deps. Reducing the vendor chunk size - if (id.includes('react-router-dom') || id.includes('@remix-run') || id.includes('react-router')) { + if ( + id.includes('react-router-dom') || + id.includes('@remix-run') || + id.includes('react-router') + ) { return '@react-router'; } return 'vendor';