mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-08 08:49:52 +03:00
58
.github/workflows/test_release.yml
vendored
Normal file
58
.github/workflows/test_release.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
name: 'test-release'
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'dev2'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pre-release:
|
||||||
|
name: 'Automatic test-release build'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
|
||||||
|
- name: Get EMS-ESP source code and version
|
||||||
|
id: build_info
|
||||||
|
run: |
|
||||||
|
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/version.h | awk -F'"' '{print $2}'`
|
||||||
|
echo "VERSION=$version" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Install PlatformIO
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -U platformio
|
||||||
|
|
||||||
|
- name: Build WebUI
|
||||||
|
run: |
|
||||||
|
cd interface
|
||||||
|
yarn install
|
||||||
|
yarn run typesafe-i18n --no-watch
|
||||||
|
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
|
||||||
|
yarn run build
|
||||||
|
|
||||||
|
- name: Build firmware
|
||||||
|
run: |
|
||||||
|
platformio run -e ci
|
||||||
|
|
||||||
|
- name: Build S3 firmware
|
||||||
|
run: |
|
||||||
|
platformio run -e ci_s3
|
||||||
|
|
||||||
|
- name: Create a GH Release
|
||||||
|
id: 'automatic_releases'
|
||||||
|
uses: 'marvinpinto/action-automatic-releases@latest'
|
||||||
|
with:
|
||||||
|
repo_token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
title: Test Build v${{steps.build_info.outputs.VERSION}}
|
||||||
|
automatic_release_tag: 'test'
|
||||||
|
prerelease: true
|
||||||
|
files: |
|
||||||
|
CHANGELOG_LATEST.md
|
||||||
|
./build/firmware/*.*
|
||||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -29,6 +29,13 @@ node_modules
|
|||||||
stats.html
|
stats.html
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
# scripts
|
# scripts
|
||||||
test.sh
|
test.sh
|
||||||
@@ -49,5 +56,5 @@ sonar/
|
|||||||
build_wrapper_output_directory/
|
build_wrapper_output_directory/
|
||||||
|
|
||||||
# entity dump results
|
# entity dump results
|
||||||
dump_entities.csv
|
# dump_entities.csv
|
||||||
dump_entities.xls*
|
# dump_entities.xls*
|
||||||
|
|||||||
17
.vscode/settings.json
vendored
17
.vscode/settings.json
vendored
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"eslint.nodePath": "interface/.yarn/sdks",
|
"eslint.nodePath": "interface/.yarn/sdks",
|
||||||
"eslint.workingDirectories": ["interface"],
|
"eslint.workingDirectories": ["interface"],
|
||||||
"prettier.prettierPath": "interface/.yarn/sdks/prettier/index.js",
|
"prettier.prettierPath": "",
|
||||||
"typescript.tsdk": "interface/.yarn/sdks/typescript/lib",
|
"typescript.tsdk": "interface/.yarn/sdks/typescript/lib",
|
||||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
@@ -28,5 +28,18 @@
|
|||||||
"utility": "cpp",
|
"utility": "cpp",
|
||||||
"string": "cpp",
|
"string": "cpp",
|
||||||
"string_view": "cpp"
|
"string_view": "cpp"
|
||||||
}
|
},
|
||||||
|
"todo-tree.filtering.excludeGlobs": [
|
||||||
|
"**/vendor/**",
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"**/bower_components/**",
|
||||||
|
"**/build/**",
|
||||||
|
"**/.vscode/**",
|
||||||
|
"**/.github/**",
|
||||||
|
"**/_output/**",
|
||||||
|
"**/*.min.*",
|
||||||
|
"**/*.map",
|
||||||
|
"**/ArduinoJson/**"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
21
.vscode/tasks.json
vendored
21
.vscode/tasks.json
vendored
@@ -4,20 +4,15 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "PlatformIO: Execute EMS-ESP (standalone)",
|
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "./.pio/build/standalone/program",
|
"label": "build standalone emsesp",
|
||||||
"linux": {
|
"command": "make",
|
||||||
"options": {
|
"args": [],
|
||||||
"env": {
|
"problemMatcher": ["$gcc"],
|
||||||
// Workaround for sdl2 `-m32` crash
|
"group": {
|
||||||
// https://bugs.launchpad.net/ubuntu/+source/libsdl2/+bug/1775067/comments/7
|
"kind": "build",
|
||||||
"DBUS_FATAL_WARNINGS": "0"
|
"isDefault": true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependsOn": ["PlatformIO: Build EMS-ESP (standalone)"],
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,30 +4,52 @@
|
|||||||
|
|
||||||
## **IMPORTANT! BREAKING CHANGES**
|
## **IMPORTANT! BREAKING CHANGES**
|
||||||
|
|
||||||
There are breaking changes in 3.6.0. Please read carefully before applying the update.
|
There are breaking changes between 3.5.x and earlier versions of 3.6.0. Please read carefully before applying the update.
|
||||||
|
|
||||||
- The sensors have been renamed. `dallassensor` is now `temperaturesensor` in MQTT and `ts` in the Customizations file. Also `analogs` is now `analogsensor` in MQTT and `as` in the Customizations file. If you have customizations, make backup first using the Download option and rename the JSON arrays to `as` and `ts` respectively. Also removed any MQTT topics that start with `dallassensor` using something like MQTTExplorer.
|
- The sensors have been renamed. `dallassensor` is now `temperaturesensor` in the MQTT topic and named `ts` in the Customizations file. Likewise `analogs` is now `analogsensor` in MQTT and called `as` in the Customizations file. If you have previous customizations you will need to manually update by downloading, changing the JSON file and uploading. It's also recommended cleaning up any old MQTT topics from your broker using an application like MQTTExplorer.
|
||||||
- The format of the Custom Entities has changed, so you will need to manually re-create them.
|
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
- Workaround for better Domoticz MQTT intergration? [#904](https://github.com/emsesp/EMS-ESP32/issues/904)
|
- Workaround for better Domoticz MQTT intergration? [#904](https://github.com/emsesp/EMS-ESP32/issues/904)
|
||||||
|
- Show MAC address without connecting to network enhancement [#933](https://github.com/emsesp/EMS-ESP32/issues/933)
|
||||||
- Warn user in WebUI of unsaved changes [#911](https://github.com/emsesp/EMS-ESP32/issues/911)
|
- Warn user in WebUI of unsaved changes [#911](https://github.com/emsesp/EMS-ESP32/issues/911)
|
||||||
- Detect old Tado thermostat, device-id 0x19, no entities
|
- Detect old Tado thermostat, device-id 0x19, no entities
|
||||||
- Some more HM200 entities [#500](https://github.com/emsesp/EMS-ESP32/issues/500)
|
- Some more HM200 entities [#500](https://github.com/emsesp/EMS-ESP32/issues/500)
|
||||||
- Custom Scheduler [#701](https://github.com/emsesp/EMS-ESP32/issues/701)
|
- Added Scheduler [#701](https://github.com/emsesp/EMS-ESP32/issues/701)
|
||||||
- Custom Entities read from EMS bus
|
- Added Custom Entities read/write from EMS bus
|
||||||
- Build S3 binary with github actions
|
- Build S3 binary with github actions
|
||||||
- Greenstar HIU [#1158](https://github.com/emsesp/EMS-ESP32/issues/1158)
|
- Greenstar HIU [#1158](https://github.com/emsesp/EMS-ESP32/issues/1158)
|
||||||
- AM200 code 10 [#1161](https://github.com/emsesp/EMS-ESP32/issues/1161)
|
- AM200 code 10 [#1161](https://github.com/emsesp/EMS-ESP32/issues/1161)
|
||||||
- Ventilation device [#1172](https://github.com/emsesp/EMS-ESP32/issues/1172)
|
- Ventilation device (Logavent HRV176) [#1172](https://github.com/emsesp/EMS-ESP32/issues/1172)
|
||||||
- Turn ETH off on wifi connect [#1167](https://github.com/emsesp/EMS-ESP32/issues/1167)
|
- Turn ETH off on wifi connect [#1167](https://github.com/emsesp/EMS-ESP32/issues/1167)
|
||||||
|
- Support for multiple EMS-ESPs with HA [#1196](https://github.com/emsesp/EMS-ESP32/issues/1196)
|
||||||
|
- Italian translation [#1199](https://github.com/emsesp/EMS-ESP32/issues/1199)
|
||||||
|
- Turkish language support [#1076](https://github.com/emsesp/EMS-ESP32/issues/1076)
|
||||||
|
- Buderus GB182 - HC1 mode change not work bug [#1193](https://github.com/emsesp/EMS-ESP32/issues/1193)
|
||||||
|
- Minimal flow temperature enhancement [#1192](https://github.com/emsesp/EMS-ESP32/issues/1192)
|
||||||
|
- Roomtemperature Switching Difference enhancement [#1191](https://github.com/emsesp/EMS-ESP32/issues/1191)
|
||||||
|
- Dew Point Temperature Difference enhancement [#1190](https://github.com/emsesp/EMS-ESP32/issues/1190)
|
||||||
|
- Control of heating circuit mode enhancement [#1187](https://github.com/emsesp/EMS-ESP32/issues/1187)
|
||||||
|
- Warn user in WebUI of unsaved changes enhancement [#911](https://github.com/emsesp/EMS-ESP32/issues/911)
|
||||||
|
- Create safebuild app to fit into factory partition to give ESP32 more flash memory enhancement [#608](https://github.com/emsesp/EMS-ESP32/issues/608)
|
||||||
|
- Support ESP32 S2, C3 mini and S3 [#605](https://github.com/emsesp/EMS-ESP32/issues/605)
|
||||||
|
- Support Buderus AM200 [#1161](https://github.com/emsesp/EMS-ESP32/issues/1161)
|
||||||
|
- Custom telegram handler [#1155](https://github.com/emsesp/EMS-ESP32/issues/1155)
|
||||||
|
- Added support for TLS in MQTT (ESP32-S3 only) [#1178](https://github.com/emsesp/EMS-ESP32/issues/1178)
|
||||||
|
- Boardprofile BBQKees Gateway S3
|
||||||
|
- Custom entity type RAW [#1212](https://github.com/emsesp/EMS-ESP32/discussions/1212)
|
||||||
|
- API command response [#1212](https://github.com/emsesp/EMS-ESP32/discussions/1212)
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
- HA-discovery for analog sensor commands [#1035](https://github.com/emsesp/EMS-ESP32/issues/1035)
|
- HA-discovery for analog sensor commands [#1035](https://github.com/emsesp/EMS-ESP32/issues/1035)
|
||||||
- Enum order of RC3x nofrost mode
|
- Enum order of RC3x nofrost mode
|
||||||
- Heartbeat interval
|
- Heartbeat interval
|
||||||
|
- Exhaust temperature always zero on GB125/MC110/RC310 bug [#1147](https://github.com/emsesp/EMS-ESP32/issues/1147)
|
||||||
|
- thermostat modetype is not changing when mode changes (e.g. to night) bugSomething isn't working [#1098](https://github.com/emsesp/EMS-ESP32/issues/1098)
|
||||||
|
- NTP: cant apply changed timezone [#1182](https://github.com/emsesp/EMS-ESP32/issues/1182)
|
||||||
|
- Missing Status of VS1 for Buderus SM200 enhancement [#1034](https://github.com/emsesp/EMS-ESP32/issues/1034)
|
||||||
|
- Allowed gpios for S3
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|
||||||
@@ -36,8 +58,13 @@ There are breaking changes in 3.6.0. Please read carefully before applying the u
|
|||||||
- Write repeated selflowtemp if tx-queue is empty without verify [#954](https://github.com/emsesp/EMS-ESP32/issues/954)
|
- Write repeated selflowtemp if tx-queue is empty without verify [#954](https://github.com/emsesp/EMS-ESP32/issues/954)
|
||||||
- HA discovery recreate after disconnect by device [#1067](https://github.com/emsesp/EMS-ESP32/issues/1067)
|
- HA discovery recreate after disconnect by device [#1067](https://github.com/emsesp/EMS-ESP32/issues/1067)
|
||||||
- File upload: check flash size (overflow) instead of filesize
|
- File upload: check flash size (overflow) instead of filesize
|
||||||
- Improved HA Discovery so previous configs no longer need to be removed when starting [#1077](https://github.com/emsesp/EMS-ESP32/pull/1077) (thanks @pswid!)
|
- Improved HA Discovery so previous configs no longer need to be removed when starting [#1077](https://github.com/emsesp/EMS-ESP32/pull/1077) (thanks @pswid)
|
||||||
- Enlarge UART-Stack to 2,5k
|
- Enlarge UART-Stack to 2,5k
|
||||||
- Retry timeout for Mqtt-QOS1/2 10seconds
|
- Retry timeout for Mqtt-QOS1/2 10seconds
|
||||||
- Optimize WebUI rendering when using Dialog Boxes [#1116](https://github.com/emsesp/EMS-ESP32/issues/1116)
|
- Optimize WebUI rendering when using Dialog Boxes [#1116](https://github.com/emsesp/EMS-ESP32/issues/1116)
|
||||||
- Optimize Web libraries to reduce bundle size (3.6.x) [#1112](https://github.com/emsesp/EMS-ESP32/issues/1112)
|
- Optimize Web libraries to reduce bundle size (3.6.x) [#1112](https://github.com/emsesp/EMS-ESP32/issues/1112)
|
||||||
|
- Use [espMqttClient](https://github.com/bertmelis/espMqttClient) with integrated queue [#1178](https://github.com/emsesp/EMS-ESP32/issues/1178)
|
||||||
|
- Move Sensors from Web dashboard to it's own tab enhancement [#1170](https://github.com/emsesp/EMS-ESP32/issues/1170)
|
||||||
|
- Optimize WebUI dashboard data [#1169](https://github.com/emsesp/EMS-ESP32/issues/1169)
|
||||||
|
- Replace React core library with Preact to save on memory footprint
|
||||||
|
- Response to `system/send` raw reads gives combined data for telegrams with more parts
|
||||||
|
|||||||
22
Makefile
22
Makefile
@@ -17,9 +17,9 @@ MAKEFLAGS+="j "
|
|||||||
#TARGET := $(notdir $(CURDIR))
|
#TARGET := $(notdir $(CURDIR))
|
||||||
TARGET := emsesp
|
TARGET := emsesp
|
||||||
BUILD := build
|
BUILD := build
|
||||||
SOURCES := src src/* lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src src/devices lib/ArduinoJson/src lib/PButton lib/semver
|
SOURCES := src src/* lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src src/devices lib/ArduinoJson/src lib/PButton lib/semver lib/espMqttClient/src lib/espMqttClient/src/*
|
||||||
INCLUDES := src lib_standalone lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src lib/semver lib/* src/devices
|
INCLUDES := src lib_standalone lib/espMqttClient/src lib/espMqttClient/src/Transport lib/ArduinoJson/src lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src lib/semver lib/* src/devices
|
||||||
LIBRARIES :=
|
LIBRARIES :=
|
||||||
|
|
||||||
CPPCHECK = cppcheck
|
CPPCHECK = cppcheck
|
||||||
# CHECKFLAGS = -q --force --std=c++17
|
# CHECKFLAGS = -q --force --std=c++17
|
||||||
@@ -28,16 +28,18 @@ CHECKFLAGS = -q --force --std=c++11
|
|||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# Languages Standard
|
# Languages Standard
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# C_STANDARD := -std=c17
|
C_STANDARD := -std=c17
|
||||||
# CXX_STANDARD := -std=c++17
|
# CXX_STANDARD := -std=c++17
|
||||||
C_STANDARD := -std=c11
|
CXX_STANDARD := -std=gnu++11
|
||||||
CXX_STANDARD := -std=c++11
|
|
||||||
|
# C_STANDARD := -std=c11
|
||||||
|
# CXX_STANDARD := -std=c++11
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
# Defined Symbols
|
# Defined Symbols
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0
|
DEFINES += -DARDUINOJSON_ENABLE_STD_STRING=1 -DARDUINOJSON_ENABLE_PROGMEM=1 -DARDUINOJSON_ENABLE_ARDUINO_STRING -DARDUINOJSON_USE_DOUBLE=0
|
||||||
DEFINES += -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST
|
DEFINES += -DEMSESP_DEBUG -DEMSESP_STANDALONE -DEMSESP_TEST -D__linux__ -DEMC_RX_BUFFER_SIZE=1500
|
||||||
DEFINES += $(ARGS)
|
DEFINES += $(ARGS)
|
||||||
|
|
||||||
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.0-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
|
DEFAULTS = -DEMSESP_DEFAULT_LOCALE=\"en\" -DEMSESP_DEFAULT_TX_MODE=8 -DEMSESP_DEFAULT_VERSION=\"3.6.0-dev\" -DEMSESP_DEFAULT_BOARD_PROFILE=\"S32\"
|
||||||
@@ -52,7 +54,7 @@ CSOURCES := $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.c))
|
|||||||
CXXSOURCES := $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.cpp))
|
CXXSOURCES := $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.cpp))
|
||||||
|
|
||||||
OBJS := $(patsubst %,$(BUILD)/%.o,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)) )
|
OBJS := $(patsubst %,$(BUILD)/%.o,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)) )
|
||||||
DEPS := $(patsubst %,$(BUILD)/%.d,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)) )
|
DEPS := $(patsubst %,$(BUILD)/%.d,$(basename $(CSOURCES)) $(basename $(CXXSOURCES)) )
|
||||||
|
|
||||||
INCLUDE += $(addprefix -I,$(foreach dir,$(INCLUDES), $(wildcard $(dir))))
|
INCLUDE += $(addprefix -I,$(foreach dir,$(INCLUDES), $(wildcard $(dir))))
|
||||||
INCLUDE += $(addprefix -I,$(foreach dir,$(LIBRARIES),$(wildcard $(dir)/include)))
|
INCLUDE += $(addprefix -I,$(foreach dir,$(LIBRARIES),$(wildcard $(dir)/include)))
|
||||||
@@ -79,7 +81,7 @@ CPPFLAGS += -g3
|
|||||||
CPPFLAGS += -Os
|
CPPFLAGS += -Os
|
||||||
|
|
||||||
CFLAGS += $(CPPFLAGS)
|
CFLAGS += $(CPPFLAGS)
|
||||||
CFLAGS += -Wall -Wextra -Werror -Wswitch-enum -Wno-unused-parameter -Wno-inconsistent-missing-override -Wno-unused-lambda-capture
|
CFLAGS += -Wall -Wextra -Werror -Wswitch-enum -Wno-unused-parameter -Wno-inconsistent-missing-override -Wno-missing-braces -Wno-unused-lambda-capture
|
||||||
|
|
||||||
CXXFLAGS += $(CFLAGS) -MMD
|
CXXFLAGS += $(CFLAGS) -MMD
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ EMS-ESP is a project owned and maintained by [proddy](https://github.com/proddy)
|
|||||||
- [esp8266-react](https://github.com/rjwats/esp8266-react) by @rjwats for the framework that provides the core of the Web UI
|
- [esp8266-react](https://github.com/rjwats/esp8266-react) by @rjwats for the framework that provides the core of the Web UI
|
||||||
- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these open source libraries
|
- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these open source libraries
|
||||||
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for all the JSON
|
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for all the JSON
|
||||||
- [AsyncMqttClient](https://github.com/marvinroger/async-mqtt-client) for the MQTT client, with custom modifications from @bertmelis and @proddy
|
- [espMqttClient](https://github.com/bertmelis/espMqttClient) for the MQTT client, with custom modifications from @MichaelDvP and @proddy
|
||||||
- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance
|
- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance
|
||||||
|
|
||||||
## **License**
|
## **License**
|
||||||
|
|||||||
4005
dump_entities.csv
Normal file
4005
dump_entities.csv
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
# Name, Type, SubType, Offset, Size, Flags
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
nvs, data, nvs, 0x9000, 0x5000,
|
nvs, data, nvs, 0x9000, 0x5000,
|
||||||
otadata, data, ota, , 0x2000,
|
otadata, data, ota, , 0x2000,
|
||||||
app1, app, ota_1, , 0x140000,
|
|
||||||
app0, app, ota_0, , 0x2A0000,
|
app0, app, ota_0, , 0x2A0000,
|
||||||
|
app1, app, ota_1, , 0x140000,
|
||||||
spiffs, data, spiffs, , 64K,
|
spiffs, data, spiffs, , 64K,
|
||||||
|
2
interface/.env.development
Normal file
2
interface/.env.development
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_ALOVA_TIPS=0
|
||||||
|
REACT_APP_ALOVA_TIPS=0
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
"@typescript-eslint/consistent-type-definitions": ["off", "type"],
|
"@typescript-eslint/consistent-type-definitions": ["off", "type"],
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"@typescript-eslint/no-unsafe-call": "off",
|
"@typescript-eslint/no-unsafe-call": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-enum-comparison": "off",
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
"@typescript-eslint/no-unsafe-return": "off",
|
"@typescript-eslint/no-unsafe-return": "off",
|
||||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"adapter": "react",
|
"adapter": "react",
|
||||||
"baseLocale": "pl",
|
"baseLocale": "pl",
|
||||||
"$schema": "https://unpkg.com/typesafe-i18n@5.24.3/schema/typesafe-i18n.json"
|
"$schema": "https://unpkg.com/typesafe-i18n@5.25.1/schema/typesafe-i18n.json"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "EMS-ESP",
|
"name": "EMS-ESP",
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"description": "build EMS-ESP TypeScript WebUI",
|
"description": "build EMS-ESP WebUI",
|
||||||
"homepage": "https://emsesp.github.io/docs",
|
"homepage": "https://emsesp.github.io/docs",
|
||||||
"author": "proddy",
|
"author": "proddy",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -19,52 +19,57 @@
|
|||||||
"lint": "eslint . --cache --fix"
|
"lint": "eslint . --cache --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.11.0",
|
"@alova/adapter-xhr": "^1.0.1",
|
||||||
|
"@emotion/react": "^11.11.1",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/icons-material": "^5.11.16",
|
"@mui/icons-material": "^5.14.1",
|
||||||
"@mui/material": "^5.13.3",
|
"@mui/material": "^5.14.2",
|
||||||
|
"@preact/compat": "^17.1.2",
|
||||||
"@table-library/react-table-library": "4.1.4",
|
"@table-library/react-table-library": "4.1.4",
|
||||||
"@types/lodash-es": "^4.17.7",
|
"@types/lodash-es": "^4.17.8",
|
||||||
"@types/node": "^20.2.5",
|
"@types/node": "^20.4.5",
|
||||||
"@types/react": "^18.2.8",
|
"@types/react": "^18.2.17",
|
||||||
"@types/react-dom": "^18.2.4",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
|
"alova": "^2.9.3",
|
||||||
"async-validator": "^4.2.5",
|
"async-validator": "^4.2.5",
|
||||||
"axios": "^1.4.0",
|
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
|
"preact": "^10.16.0",
|
||||||
"react": "latest",
|
"react": "latest",
|
||||||
"react-dom": "latest",
|
"react-dom": "latest",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-icons": "^4.9.0",
|
"react-icons": "^4.10.1",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.14.2",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"sockette": "^2.0.6",
|
"sockette": "^2.0.6",
|
||||||
"typesafe-i18n": "^5.24.3",
|
"typesafe-i18n": "^5.25.1",
|
||||||
"typescript": "^5.1.3"
|
"typescript": "^5.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.8",
|
"@preact/preset-vite": "^2.5.0",
|
||||||
"@typescript-eslint/parser": "^5.59.8",
|
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||||
"@vitejs/plugin-react-swc": "^3.3.1",
|
"@typescript-eslint/parser": "^6.2.0",
|
||||||
"eslint": "^8.42.0",
|
"cspell": "^6.31.2",
|
||||||
|
"eslint": "^8.46.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^8.9.0",
|
||||||
"eslint-import-resolver-typescript": "^3.5.5",
|
"eslint-import-resolver-typescript": "^3.5.5",
|
||||||
"eslint-plugin-autofix": "^1.1.0",
|
"eslint-plugin-autofix": "^1.1.0",
|
||||||
"eslint-plugin-import": "^2.27.5",
|
"eslint-plugin-import": "^2.28.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "alpha",
|
||||||
"eslint-plugin-react": "^7.32.2",
|
"eslint-plugin-react": "^7.33.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^3.0.1",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^3.0.0",
|
||||||
"rollup-plugin-visualizer": "^5.9.0",
|
"rollup-plugin-visualizer": "^5.9.2",
|
||||||
"terser": "^5.17.7",
|
"terser": "^5.19.2",
|
||||||
"vite": "^4.3.9",
|
"vite": "^4.4.7",
|
||||||
"vite-plugin-svgr": "^3.2.0",
|
"vite-plugin-svgr": "^3.2.0",
|
||||||
"vite-tsconfig-paths": "^4.2.0"
|
"vite-tsconfig-paths": "^4.2.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Uses font-size 400 (normal) only and Latin (plus extra unicode chars) to keep flash memory to a minimun
|
* Uses font-size 400 (normal) only and Latin (plus extra unicode chars) to keep flash memory to a minimum
|
||||||
* View fonts on https://fonts.google.com/
|
* View fonts on https://fonts.google.com/
|
||||||
* Download woff2 using e.g. https://fonts.googleapis.com/css2?family=Lato or https://fonts.googleapis.com/css2?family=Roboto
|
* Download woff2 using e.g. https://fonts.googleapis.com/css2?family=Lato or https://fonts.googleapis.com/css2?family=Roboto
|
||||||
*/
|
*/
|
||||||
@@ -8,7 +8,10 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
/* src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2) format('woff2'); */
|
/* src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2) format('woff2'); */
|
||||||
src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2');
|
src:
|
||||||
|
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,
|
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+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;
|
U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ToastContainer, Slide } from 'react-toastify';
|
|||||||
import 'react-toastify/dist/ReactToastify.min.css';
|
import 'react-toastify/dist/ReactToastify.min.css';
|
||||||
|
|
||||||
import { localStorageDetector } from 'typesafe-i18n/detectors';
|
import { localStorageDetector } from 'typesafe-i18n/detectors';
|
||||||
|
import { FeaturesLoader } from './contexts/features';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import AppRouting from 'AppRouting';
|
import AppRouting from 'AppRouting';
|
||||||
import CustomTheme from 'CustomTheme';
|
import CustomTheme from 'CustomTheme';
|
||||||
@@ -26,7 +27,9 @@ const App: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<TypesafeI18n locale={detectedLocale}>
|
<TypesafeI18n locale={detectedLocale}>
|
||||||
<CustomTheme>
|
<CustomTheme>
|
||||||
<AppRouting />
|
<FeaturesLoader>
|
||||||
|
<AppRouting />
|
||||||
|
</FeaturesLoader>
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
position="bottom-left"
|
position="bottom-left"
|
||||||
autoClose={3000}
|
autoClose={3000}
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { Navigate, Routes, Route } from 'react-router-dom';
|
||||||
import { Navigate, Routes, Route, useNavigate, useLocation } from 'react-router-dom';
|
|
||||||
import Dashboard from './project/Dashboard';
|
import Dashboard from './project/Dashboard';
|
||||||
import Help from './project/Help';
|
import Help from './project/Help';
|
||||||
import Settings from './project/Settings';
|
import Settings from './project/Settings';
|
||||||
import type { AxiosError } from 'axios';
|
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import * as AuthenticationApi from 'api/authentication';
|
|
||||||
import { AXIOS } from 'api/endpoints';
|
|
||||||
import { Layout, RequireAdmin } from 'components';
|
import { Layout, RequireAdmin } from 'components';
|
||||||
|
|
||||||
import AccessPoint from 'framework/ap/AccessPoint';
|
import AccessPoint from 'framework/ap/AccessPoint';
|
||||||
import Mqtt from 'framework/mqtt/Mqtt';
|
import Mqtt from 'framework/mqtt/Mqtt';
|
||||||
import NetworkConnection from 'framework/network/NetworkConnection';
|
import NetworkConnection from 'framework/network/NetworkConnection';
|
||||||
@@ -17,57 +12,53 @@ import NetworkTime from 'framework/ntp/NetworkTime';
|
|||||||
import Security from 'framework/security/Security';
|
import Security from 'framework/security/Security';
|
||||||
import System from 'framework/system/System';
|
import System from 'framework/system/System';
|
||||||
|
|
||||||
const AuthenticatedRouting: FC = () => {
|
const AuthenticatedRouting: FC = () => (
|
||||||
const location = useLocation();
|
// const location = useLocation();
|
||||||
const navigate = useNavigate();
|
// const navigate = useNavigate();
|
||||||
|
// const handleApiResponseError = useCallback(
|
||||||
|
// (error: AxiosError) => {
|
||||||
|
// if (error.response && error.response.status === 401) {
|
||||||
|
// AuthenticationApi.storeLoginRedirect(location);
|
||||||
|
// navigate('/unauthorized');
|
||||||
|
// }
|
||||||
|
// return Promise.reject(error);
|
||||||
|
// },
|
||||||
|
// [location, navigate]
|
||||||
|
// );
|
||||||
|
// useEffect(() => {
|
||||||
|
// const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError);
|
||||||
|
// return () => AXIOS.interceptors.response.eject(axiosHandlerId);
|
||||||
|
// }, [handleApiResponseError]);
|
||||||
|
|
||||||
const handleApiResponseError = useCallback(
|
<Layout>
|
||||||
(error: AxiosError) => {
|
<Routes>
|
||||||
if (error.response && error.response.status === 401) {
|
<Route path="/dashboard/*" element={<Dashboard />} />
|
||||||
AuthenticationApi.storeLoginRedirect(location);
|
<Route
|
||||||
navigate('/unauthorized');
|
path="/settings/*"
|
||||||
}
|
element={
|
||||||
return Promise.reject(error);
|
<RequireAdmin>
|
||||||
},
|
<Settings />
|
||||||
[location, navigate]
|
</RequireAdmin>
|
||||||
);
|
}
|
||||||
|
/>
|
||||||
|
<Route path="/help/*" element={<Help />} />
|
||||||
|
|
||||||
useEffect(() => {
|
<Route path="/network/*" element={<NetworkConnection />} />
|
||||||
const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError);
|
<Route path="/ap/*" element={<AccessPoint />} />
|
||||||
return () => AXIOS.interceptors.response.eject(axiosHandlerId);
|
<Route path="/ntp/*" element={<NetworkTime />} />
|
||||||
}, [handleApiResponseError]);
|
<Route path="/mqtt/*" element={<Mqtt />} />
|
||||||
|
<Route
|
||||||
return (
|
path="/security/*"
|
||||||
<Layout>
|
element={
|
||||||
<Routes>
|
<RequireAdmin>
|
||||||
<Route path="/dashboard/*" element={<Dashboard />} />
|
<Security />
|
||||||
<Route
|
</RequireAdmin>
|
||||||
path="/settings/*"
|
}
|
||||||
element={
|
/>
|
||||||
<RequireAdmin>
|
<Route path="/system/*" element={<System />} />
|
||||||
<Settings />
|
<Route path="/*" element={<Navigate to="/" />} />
|
||||||
</RequireAdmin>
|
</Routes>
|
||||||
}
|
</Layout>
|
||||||
/>
|
);
|
||||||
<Route path="/help/*" element={<Help />} />
|
|
||||||
|
|
||||||
<Route path="/network/*" element={<NetworkConnection />} />
|
|
||||||
<Route path="/ap/*" element={<AccessPoint />} />
|
|
||||||
<Route path="/ntp/*" element={<NetworkTime />} />
|
|
||||||
<Route path="/mqtt/*" element={<Mqtt />} />
|
|
||||||
<Route
|
|
||||||
path="/security/*"
|
|
||||||
element={
|
|
||||||
<RequireAdmin>
|
|
||||||
<Security />
|
|
||||||
</RequireAdmin>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route path="/system/*" element={<System />} />
|
|
||||||
<Route path="/*" element={<Navigate to="/" />} />
|
|
||||||
</Routes>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthenticatedRouting;
|
export default AuthenticatedRouting;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import ForwardIcon from '@mui/icons-material/Forward';
|
import ForwardIcon from '@mui/icons-material/Forward';
|
||||||
import { Box, Fab, Paper, Typography, Button } from '@mui/material';
|
import { Box, Fab, Paper, Typography, Button } from '@mui/material';
|
||||||
|
import { useRequest } from 'alova';
|
||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
import { FeaturesContext } from './contexts/features';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
|
|
||||||
import type { Locales } from 'i18n/i18n-types';
|
import type { Locales } from 'i18n/i18n-types';
|
||||||
@@ -16,6 +18,7 @@ import { AuthenticationContext } from 'contexts/authentication';
|
|||||||
import { ReactComponent as DEflag } from 'i18n/DE.svg';
|
import { ReactComponent as DEflag } from 'i18n/DE.svg';
|
||||||
import { ReactComponent as FRflag } from 'i18n/FR.svg';
|
import { ReactComponent as FRflag } from 'i18n/FR.svg';
|
||||||
import { ReactComponent as GBflag } from 'i18n/GB.svg';
|
import { ReactComponent as GBflag } from 'i18n/GB.svg';
|
||||||
|
import { ReactComponent as ITflag } from 'i18n/IT.svg';
|
||||||
import { ReactComponent as NLflag } from 'i18n/NL.svg';
|
import { ReactComponent as NLflag } from 'i18n/NL.svg';
|
||||||
import { ReactComponent as NOflag } from 'i18n/NO.svg';
|
import { ReactComponent as NOflag } from 'i18n/NO.svg';
|
||||||
import { ReactComponent as PLflag } from 'i18n/PL.svg';
|
import { ReactComponent as PLflag } from 'i18n/PL.svg';
|
||||||
@@ -23,7 +26,7 @@ import { ReactComponent as SVflag } from 'i18n/SV.svg';
|
|||||||
import { ReactComponent as TRflag } from 'i18n/TR.svg';
|
import { ReactComponent as TRflag } from 'i18n/TR.svg';
|
||||||
import { I18nContext } from 'i18n/i18n-react';
|
import { I18nContext } from 'i18n/i18n-react';
|
||||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||||
import { extractErrorMessage, onEnterCallback, updateValue } from 'utils';
|
import { onEnterCallback, updateValue } from 'utils';
|
||||||
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
|
import { SIGN_IN_REQUEST_VALIDATOR, validate } from 'validators';
|
||||||
|
|
||||||
const SignIn: FC = () => {
|
const SignIn: FC = () => {
|
||||||
@@ -31,6 +34,8 @@ const SignIn: FC = () => {
|
|||||||
|
|
||||||
const { LL, setLocale, locale } = useContext(I18nContext);
|
const { LL, setLocale, locale } = useContext(I18nContext);
|
||||||
|
|
||||||
|
const { features } = useContext(FeaturesContext);
|
||||||
|
|
||||||
const [signInRequest, setSignInRequest] = useState<SignInRequest>({
|
const [signInRequest, setSignInRequest] = useState<SignInRequest>({
|
||||||
username: '',
|
username: '',
|
||||||
password: ''
|
password: ''
|
||||||
@@ -38,22 +43,27 @@ const SignIn: FC = () => {
|
|||||||
const [processing, setProcessing] = useState<boolean>(false);
|
const [processing, setProcessing] = useState<boolean>(false);
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
|
const { send: callSignIn, onSuccess } = useRequest((request: SignInRequest) => AuthenticationApi.signIn(request), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
onSuccess((response) => {
|
||||||
|
if (response.data) {
|
||||||
|
authenticationContext.signIn(response.data.access_token);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const updateLoginRequestValue = updateValue(setSignInRequest);
|
const updateLoginRequestValue = updateValue(setSignInRequest);
|
||||||
|
|
||||||
const signIn = async () => {
|
const signIn = async () => {
|
||||||
try {
|
await callSignIn(signInRequest).catch((event) => {
|
||||||
const { data: loginResponse } = await AuthenticationApi.signIn(signInRequest);
|
if (event.message === 'Unauthorized') {
|
||||||
authenticationContext.signIn(loginResponse.access_token);
|
toast.warning(LL.INVALID_LOGIN());
|
||||||
} catch (error) {
|
|
||||||
if (error.response) {
|
|
||||||
if (error.response?.status === 401) {
|
|
||||||
toast.warn(LL.INVALID_LOGIN());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
toast.error(extractErrorMessage(error, LL.ERROR()));
|
toast.error(LL.ERROR() + ' ' + event.message);
|
||||||
}
|
}
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
}
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateAndSignIn = async () => {
|
const validateAndSignIn = async () => {
|
||||||
@@ -100,6 +110,7 @@ const SignIn: FC = () => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Typography variant="h4">{PROJECT_NAME}</Typography>
|
<Typography variant="h4">{PROJECT_NAME}</Typography>
|
||||||
|
<Typography variant="subtitle2">{features.version}</Typography>
|
||||||
<Box
|
<Box
|
||||||
mt={2}
|
mt={2}
|
||||||
mb={2}
|
mb={2}
|
||||||
@@ -110,18 +121,22 @@ const SignIn: FC = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button size="small" variant={locale === 'en' ? 'contained' : 'outlined'} onClick={() => selectLocale('en')}>
|
|
||||||
<GBflag style={{ width: 24 }} />
|
|
||||||
EN
|
|
||||||
</Button>
|
|
||||||
<Button size="small" variant={locale === 'de' ? 'contained' : 'outlined'} onClick={() => selectLocale('de')}>
|
<Button size="small" variant={locale === 'de' ? 'contained' : 'outlined'} onClick={() => selectLocale('de')}>
|
||||||
<DEflag style={{ width: 24 }} />
|
<DEflag style={{ width: 24 }} />
|
||||||
DE
|
DE
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button size="small" variant={locale === 'en' ? 'contained' : 'outlined'} onClick={() => selectLocale('en')}>
|
||||||
|
<GBflag style={{ width: 24 }} />
|
||||||
|
EN
|
||||||
|
</Button>
|
||||||
<Button size="small" variant={locale === 'fr' ? 'contained' : 'outlined'} onClick={() => selectLocale('fr')}>
|
<Button size="small" variant={locale === 'fr' ? 'contained' : 'outlined'} onClick={() => selectLocale('fr')}>
|
||||||
<FRflag style={{ width: 24 }} />
|
<FRflag style={{ width: 24 }} />
|
||||||
FR
|
FR
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button size="small" variant={locale === 'it' ? 'contained' : 'outlined'} onClick={() => selectLocale('it')}>
|
||||||
|
<ITflag style={{ width: 24 }} />
|
||||||
|
IT
|
||||||
|
</Button>
|
||||||
<Button size="small" variant={locale === 'nl' ? 'contained' : 'outlined'} onClick={() => selectLocale('nl')}>
|
<Button size="small" variant={locale === 'nl' ? 'contained' : 'outlined'} onClick={() => selectLocale('nl')}>
|
||||||
<NLflag style={{ width: 24 }} />
|
<NLflag style={{ width: 24 }} />
|
||||||
NL
|
NL
|
||||||
|
|||||||
@@ -1,16 +1,7 @@
|
|||||||
import { AXIOS } from './endpoints';
|
import { alovaInstance } from './endpoints';
|
||||||
import type { AxiosPromise } from 'axios';
|
|
||||||
|
|
||||||
import type { APSettings, APStatus } from 'types';
|
import type { APSettings, APStatus } from 'types';
|
||||||
|
|
||||||
export function readAPStatus(): AxiosPromise<APStatus> {
|
export const readAPStatus = () => alovaInstance.Get<APStatus>('/rest/apStatus');
|
||||||
return AXIOS.get('/apStatus');
|
export const readAPSettings = () => alovaInstance.Get<APSettings>('/rest/apSettings');
|
||||||
}
|
export const updateAPSettings = (data: APSettings) => alovaInstance.Post<APSettings>('/rest/apSettings', data);
|
||||||
|
|
||||||
export function readAPSettings(): AxiosPromise<APSettings> {
|
|
||||||
return AXIOS.get('/apSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateAPSettings(apSettings: APSettings): AxiosPromise<APSettings> {
|
|
||||||
return AXIOS.post('/apSettings', apSettings);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import jwtDecode from 'jwt-decode';
|
import jwtDecode from 'jwt-decode';
|
||||||
import { ACCESS_TOKEN, AXIOS } from './endpoints';
|
import { ACCESS_TOKEN, alovaInstance } from './endpoints';
|
||||||
import type { AxiosPromise } from 'axios';
|
|
||||||
import type * as H from 'history';
|
import type * as H from 'history';
|
||||||
import type { Path } from 'react-router-dom';
|
import type { Path } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -9,13 +8,8 @@ import type { Me, SignInRequest, SignInResponse } from 'types';
|
|||||||
export const SIGN_IN_PATHNAME = 'loginPathname';
|
export const SIGN_IN_PATHNAME = 'loginPathname';
|
||||||
export const SIGN_IN_SEARCH = 'loginSearch';
|
export const SIGN_IN_SEARCH = 'loginSearch';
|
||||||
|
|
||||||
export function verifyAuthorization(): AxiosPromise<void> {
|
export const verifyAuthorization = () => alovaInstance.Get('/rest/verifyAuthorization');
|
||||||
return AXIOS.get('/verifyAuthorization');
|
export const signIn = (request: SignInRequest) => alovaInstance.Post<SignInResponse>('/rest/signIn', request);
|
||||||
}
|
|
||||||
|
|
||||||
export function signIn(request: SignInRequest): AxiosPromise<SignInResponse> {
|
|
||||||
return AXIOS.post('/signIn', request);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStorage() {
|
export function getStorage() {
|
||||||
return localStorage || sessionStorage;
|
return localStorage || sessionStorage;
|
||||||
|
|||||||
@@ -1,95 +1,60 @@
|
|||||||
import axios from 'axios';
|
import { xhrRequestAdapter } from '@alova/adapter-xhr';
|
||||||
import { unpack } from './unpack';
|
import { createAlova } from 'alova';
|
||||||
|
import ReactHook from 'alova/react';
|
||||||
|
import { unpack } from '../api/unpack';
|
||||||
|
|
||||||
import type { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios';
|
|
||||||
|
|
||||||
export const WS_BASE_URL = '/ws/';
|
|
||||||
export const API_BASE_URL = '/rest/';
|
|
||||||
export const ES_BASE_URL = '/es/';
|
|
||||||
export const EMSESP_API_BASE_URL = '/api/';
|
|
||||||
export const ACCESS_TOKEN = 'access_token';
|
export const ACCESS_TOKEN = 'access_token';
|
||||||
|
|
||||||
const location = window.location;
|
const host = window.location.host;
|
||||||
const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
export const WEB_SOCKET_ROOT = 'ws://' + host + '/ws/';
|
||||||
export const WEB_SOCKET_ROOT = webProtocol + '//' + location.host + WS_BASE_URL;
|
export const EVENT_SOURCE_ROOT = 'http://' + host + '/es/';
|
||||||
export const EVENT_SOURCE_ROOT = location.protocol + '//' + location.host + ES_BASE_URL;
|
|
||||||
|
|
||||||
export const AXIOS = axios.create({
|
export const alovaInstance = createAlova({
|
||||||
baseURL: API_BASE_URL,
|
statesHook: ReactHook,
|
||||||
headers: {
|
timeout: 3000, // 3 seconds but throwing a timeout error
|
||||||
'Content-Type': 'application/json'
|
localCache: null,
|
||||||
},
|
// localCache: {
|
||||||
transformRequest: [
|
// GET: {
|
||||||
(data, headers) => {
|
// mode: 'placeholder', // see https://alova.js.org/learning/response-cache/#cache-replaceholder-mode
|
||||||
if (headers) {
|
// expire: 2000
|
||||||
if (localStorage.getItem(ACCESS_TOKEN)) {
|
// }
|
||||||
headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
|
// },
|
||||||
}
|
requestAdapter: xhrRequestAdapter(),
|
||||||
if (headers['Content-Type'] !== 'application/json') {
|
beforeRequest(method) {
|
||||||
return data;
|
if (localStorage.getItem(ACCESS_TOKEN)) {
|
||||||
}
|
method.config.headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
|
||||||
}
|
|
||||||
return JSON.stringify(data);
|
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
|
|
||||||
|
responded: {
|
||||||
|
onSuccess: async (response) => {
|
||||||
|
// if (response.status === 202) {
|
||||||
|
// throw new Error('Wait'); // wifi scan in progress
|
||||||
|
// } else
|
||||||
|
if (response.status === 205) {
|
||||||
|
throw new Error('Reboot required');
|
||||||
|
} else if (response.status === 400) {
|
||||||
|
throw new Error('Request Failed');
|
||||||
|
} else if (response.status >= 400) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
const data = await response.data;
|
||||||
|
if (response.data instanceof ArrayBuffer) {
|
||||||
|
return unpack(data);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interceptor for request failure. This interceptor will be entered when the request is wrong.
|
||||||
|
// http errors like 401 (unauthorized) are handled either in the methods or AuthenticatedRouting()
|
||||||
|
// onError: (error, method) => {
|
||||||
|
// alert(error.message);
|
||||||
|
// }
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AXIOS_API = axios.create({
|
export const alovaInstanceGH = createAlova({
|
||||||
baseURL: EMSESP_API_BASE_URL,
|
baseURL: 'https://api.github.com/repos/emsesp/EMS-ESP32',
|
||||||
headers: {
|
statesHook: ReactHook,
|
||||||
'Content-Type': 'application/json'
|
requestAdapter: xhrRequestAdapter()
|
||||||
},
|
|
||||||
transformRequest: [
|
|
||||||
(data, headers) => {
|
|
||||||
if (headers) {
|
|
||||||
if (localStorage.getItem(ACCESS_TOKEN)) {
|
|
||||||
headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
|
|
||||||
}
|
|
||||||
if (headers['Content-Type'] !== 'application/json') {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JSON.stringify(data);
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AXIOS_BIN = axios.create({
|
|
||||||
baseURL: API_BASE_URL,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
responseType: 'arraybuffer',
|
|
||||||
transformRequest: [
|
|
||||||
(data, headers) => {
|
|
||||||
if (headers) {
|
|
||||||
if (localStorage.getItem(ACCESS_TOKEN)) {
|
|
||||||
headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
|
|
||||||
}
|
|
||||||
if (headers['Content-Type'] !== 'application/json') {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JSON.stringify(data);
|
|
||||||
}
|
|
||||||
],
|
|
||||||
// transformResponse: [(data) => decode(data)]
|
|
||||||
transformResponse: [(data) => unpack(data)] // new using msgpackr
|
|
||||||
});
|
|
||||||
|
|
||||||
export interface FileUploadConfig {
|
|
||||||
cancelToken?: CancelToken;
|
|
||||||
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const startUploadFile = (url: string, file: File, config?: FileUploadConfig): AxiosPromise<void> => {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', file);
|
|
||||||
|
|
||||||
return AXIOS.post(url, formData, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
},
|
|
||||||
...(config || {})
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { AXIOS } from './endpoints';
|
import { alovaInstance } from './endpoints';
|
||||||
import type { AxiosPromise } from 'axios';
|
|
||||||
|
|
||||||
import type { Features } from 'types';
|
import type { Features } from 'types';
|
||||||
|
|
||||||
export function readFeatures(): AxiosPromise<Features> {
|
export const readFeatures = () => alovaInstance.Get<Features>('/rest/features');
|
||||||
return AXIOS.get('/features');
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
import { AXIOS } from './endpoints';
|
import { alovaInstance } from './endpoints';
|
||||||
import type { AxiosPromise } from 'axios';
|
|
||||||
import type { MqttSettings, MqttStatus } from 'types';
|
import type { MqttSettings, MqttStatus } from 'types';
|
||||||
|
|
||||||
export function readMqttStatus(): AxiosPromise<MqttStatus> {
|
export const readMqttStatus = () => alovaInstance.Get<MqttStatus>('/rest/mqttStatus');
|
||||||
return AXIOS.get('/mqttStatus');
|
export const readMqttSettings = () => alovaInstance.Get<MqttSettings>('/rest/mqttSettings');
|
||||||
}
|
export const updateMqttSettings = (data: MqttSettings) => alovaInstance.Post<MqttSettings>('/rest/mqttSettings', data);
|
||||||
|
|
||||||
export function readMqttSettings(): AxiosPromise<MqttSettings> {
|
|
||||||
return AXIOS.get('/mqttSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateMqttSettings(mqttSettings: MqttSettings): AxiosPromise<MqttSettings> {
|
|
||||||
return AXIOS.post('/mqttSettings', mqttSettings);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,24 +1,15 @@
|
|||||||
import { AXIOS } from './endpoints';
|
import { alovaInstance } from './endpoints';
|
||||||
import type { AxiosPromise } from 'axios';
|
|
||||||
|
|
||||||
import type { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types';
|
import type { WiFiNetworkList, NetworkSettings, NetworkStatus } from 'types';
|
||||||
|
|
||||||
export function readNetworkStatus(): AxiosPromise<NetworkStatus> {
|
export const readNetworkStatus = () => alovaInstance.Get<NetworkStatus>('/rest/networkStatus');
|
||||||
return AXIOS.get('/networkStatus');
|
export const scanNetworks = () => alovaInstance.Get('/rest/scanNetworks');
|
||||||
}
|
export const listNetworks = () =>
|
||||||
|
alovaInstance.Get<WiFiNetworkList>('/rest/listNetworks', {
|
||||||
export function scanNetworks(): AxiosPromise<void> {
|
name: 'listNetworks',
|
||||||
return AXIOS.get('/scanNetworks');
|
timeout: 20000 // timeout 20 seconds
|
||||||
}
|
});
|
||||||
|
export const readNetworkSettings = () =>
|
||||||
export function listNetworks(): AxiosPromise<WiFiNetworkList> {
|
alovaInstance.Get<NetworkSettings>('/rest/networkSettings', { name: 'networkSettings' });
|
||||||
return AXIOS.get('/listNetworks');
|
export const updateNetworkSettings = (wifiSettings: NetworkSettings) =>
|
||||||
}
|
alovaInstance.Post<NetworkSettings>('/rest/networkSettings', wifiSettings);
|
||||||
|
|
||||||
export function readNetworkSettings(): AxiosPromise<NetworkSettings> {
|
|
||||||
return AXIOS.get('/networkSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateNetworkSettings(wifiSettings: NetworkSettings): AxiosPromise<NetworkSettings> {
|
|
||||||
return AXIOS.post('/networkSettings', wifiSettings);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
import { AXIOS } from './endpoints';
|
import { alovaInstance } from './endpoints';
|
||||||
import type { AxiosPromise } from 'axios';
|
|
||||||
import type { NTPSettings, NTPStatus, Time } from 'types';
|
import type { NTPSettings, NTPStatus, Time } from 'types';
|
||||||
|
|
||||||
export function readNTPStatus(): AxiosPromise<NTPStatus> {
|
export const readNTPStatus = () => alovaInstance.Get<NTPStatus>('/rest/ntpStatus');
|
||||||
return AXIOS.get('/ntpStatus');
|
export const readNTPSettings = () =>
|
||||||
}
|
alovaInstance.Get<NTPSettings>('/rest/ntpSettings', {
|
||||||
|
name: 'ntpSettings'
|
||||||
|
});
|
||||||
|
export const updateNTPSettings = (data: NTPSettings) => alovaInstance.Post<NTPSettings>('/rest/ntpSettings', data);
|
||||||
|
|
||||||
export function readNTPSettings(): AxiosPromise<NTPSettings> {
|
export const updateTime = (data: Time) => alovaInstance.Post<Time>('/rest/time', data);
|
||||||
return AXIOS.get('/ntpSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateNTPSettings(ntpSettings: NTPSettings): AxiosPromise<NTPSettings> {
|
|
||||||
return AXIOS.post('/ntpSettings', ntpSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTime(time: Time): AxiosPromise<Time> {
|
|
||||||
return AXIOS.post('/time', time);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { AXIOS } from './endpoints';
|
import { alovaInstance } from './endpoints';
|
||||||
import type { AxiosPromise } from 'axios';
|
|
||||||
|
|
||||||
import type { SecuritySettings, Token } from 'types';
|
import type { SecuritySettings, Token } from 'types';
|
||||||
|
|
||||||
export function readSecuritySettings(): AxiosPromise<SecuritySettings> {
|
export const readSecuritySettings = () => alovaInstance.Get<SecuritySettings>('/rest/securitySettings');
|
||||||
return AXIOS.get('/securitySettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateSecuritySettings(securitySettings: SecuritySettings): AxiosPromise<SecuritySettings> {
|
export const updateSecuritySettings = (securitySettings: SecuritySettings) =>
|
||||||
return AXIOS.post('/securitySettings', securitySettings);
|
alovaInstance.Post('/rest/securitySettings', securitySettings);
|
||||||
}
|
|
||||||
|
|
||||||
export function generateToken(username?: string): AxiosPromise<Token> {
|
export const generateToken = (username?: string) =>
|
||||||
return AXIOS.get('/generateToken', { params: { username } });
|
alovaInstance.Get<Token>('/rest/generateToken', {
|
||||||
}
|
params: { username }
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,44 +1,51 @@
|
|||||||
import { AXIOS, AXIOS_BIN, startUploadFile } from './endpoints';
|
import { alovaInstance, alovaInstanceGH } from './endpoints';
|
||||||
import type { FileUploadConfig } from './endpoints';
|
import type { OTASettings, SystemStatus, LogSettings, Version } from 'types';
|
||||||
import type { AxiosPromise } from 'axios';
|
|
||||||
|
|
||||||
import type { OTASettings, SystemStatus, LogSettings, LogEntries } from 'types';
|
// SystemStatus - also used to ping in Restart monitor for pinging
|
||||||
|
export const readSystemStatus = () => alovaInstance.Get<SystemStatus>('/rest/systemStatus');
|
||||||
|
|
||||||
export function readSystemStatus(timeout?: number): AxiosPromise<SystemStatus> {
|
// commands
|
||||||
return AXIOS.get('/systemStatus', { timeout });
|
export const restart = () => alovaInstance.Post('/rest/restart');
|
||||||
}
|
export const partition = () => alovaInstance.Post('/rest/partition');
|
||||||
|
export const factoryReset = () => alovaInstance.Post('/rest/factoryReset');
|
||||||
|
|
||||||
export function restart(): AxiosPromise<void> {
|
// OTA
|
||||||
return AXIOS.post('/restart');
|
export const readOTASettings = () => alovaInstance.Get<OTASettings>(`/rest/otaSettings`);
|
||||||
}
|
export const updateOTASettings = (data: any) => alovaInstance.Post('/rest/otaSettings', data);
|
||||||
|
|
||||||
export function partition(): AxiosPromise<void> {
|
// SystemLog
|
||||||
return AXIOS.post('/partition');
|
export const readLogSettings = () => alovaInstance.Get<LogSettings>(`/rest/logSettings`);
|
||||||
}
|
export const updateLogSettings = (data: any) => alovaInstance.Post('/rest/logSettings', data);
|
||||||
|
export const fetchLog = () => alovaInstance.Post('/rest/fetchLog');
|
||||||
|
|
||||||
export function factoryReset(): AxiosPromise<void> {
|
// Get versions from github
|
||||||
return AXIOS.post('/factoryReset');
|
export const getStableVersion = () =>
|
||||||
}
|
alovaInstanceGH.Get<Version>('releases/latest', {
|
||||||
|
transformData(response: any) {
|
||||||
|
return {
|
||||||
|
version: response.data.name,
|
||||||
|
url: response.data.assets[1].browser_download_url,
|
||||||
|
changelog: response.data.assets[0].browser_download_url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export function readOTASettings(): AxiosPromise<OTASettings> {
|
export const getDevVersion = () =>
|
||||||
return AXIOS.get('/otaSettings');
|
alovaInstanceGH.Get<Version>('releases/tags/latest', {
|
||||||
}
|
transformData(response: any) {
|
||||||
|
return {
|
||||||
|
version: response.data.name.split(/\s+/).splice(-1),
|
||||||
|
url: response.data.assets[1].browser_download_url,
|
||||||
|
changelog: response.data.assets[0].browser_download_url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export function updateOTASettings(otaSettings: OTASettings): AxiosPromise<OTASettings> {
|
export const uploadFile = (file: File) => {
|
||||||
return AXIOS.post('/otaSettings', otaSettings);
|
const formData = new FormData();
|
||||||
}
|
formData.append('file', file);
|
||||||
|
return alovaInstance.Post('/rest/uploadFile', formData, {
|
||||||
export const uploadFile = (file: File, config?: FileUploadConfig): AxiosPromise<void> =>
|
timeout: 60000, // override timeout for uploading firmware - 1 minute
|
||||||
startUploadFile('/uploadFile', file, config);
|
enableUpload: true
|
||||||
|
});
|
||||||
export function readLogSettings(): AxiosPromise<LogSettings> {
|
};
|
||||||
return AXIOS.get('/logSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateLogSettings(logSettings: LogSettings): AxiosPromise<LogSettings> {
|
|
||||||
return AXIOS.post('/logSettings', logSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readLogEntries(): AxiosPromise<LogEntries> {
|
|
||||||
return AXIOS_BIN.get('/fetchLog');
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -968,7 +968,6 @@ currentExtensions[0x69] = (data) => {
|
|||||||
if (!referenceMap) referenceMap = new Map();
|
if (!referenceMap) referenceMap = new Map();
|
||||||
const token = src[position];
|
const token = src[position];
|
||||||
let target;
|
let target;
|
||||||
// TODO: handle Maps, Sets, and other types that can cycle; this is complicated, because you potentially need to read
|
|
||||||
// ahead past references to record structure definitions
|
// ahead past references to record structure definitions
|
||||||
if ((token >= 0x90 && token < 0xa0) || token == 0xdc || token == 0xdd) target = [];
|
if ((token >= 0x90 && token < 0xa0) || token == 0xdc || token == 0xdd) target = [];
|
||||||
else target = {};
|
else target = {};
|
||||||
@@ -1041,7 +1040,6 @@ currentExtensions[0xff] = (data) => {
|
|||||||
((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000
|
((data[3] & 0x3) * 0x100000000 + data[4] * 0x1000000 + (data[5] << 16) + (data[6] << 8) + data[7]) * 1000
|
||||||
);
|
);
|
||||||
else if (data.length == 12)
|
else if (data.length == 12)
|
||||||
// TODO: Implement support for negative
|
|
||||||
return new Date(
|
return new Date(
|
||||||
((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
|
((data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3]) / 1000000 +
|
||||||
((data[4] & 0x80 ? -0x1000000000000 : 0) +
|
((data[4] & 0x80 ? -0x1000000000000 : 0) +
|
||||||
@@ -1070,7 +1068,6 @@ function saveState(callback) {
|
|||||||
const savedReferenceMap = referenceMap;
|
const savedReferenceMap = referenceMap;
|
||||||
const savedBundledStrings = bundledStrings;
|
const savedBundledStrings = bundledStrings;
|
||||||
|
|
||||||
// TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
|
|
||||||
const savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
|
const savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
|
||||||
const savedStructures = currentStructures;
|
const savedStructures = currentStructures;
|
||||||
const savedStructuresContents = currentStructures.slice(0, currentStructures.length);
|
const savedStructuresContents = currentStructures.slice(0, currentStructures.length);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { AuthenticatedContext } from 'contexts/authentication';
|
|||||||
import { ReactComponent as DEflag } from 'i18n/DE.svg';
|
import { ReactComponent as DEflag } from 'i18n/DE.svg';
|
||||||
import { ReactComponent as FRflag } from 'i18n/FR.svg';
|
import { ReactComponent as FRflag } from 'i18n/FR.svg';
|
||||||
import { ReactComponent as GBflag } from 'i18n/GB.svg';
|
import { ReactComponent as GBflag } from 'i18n/GB.svg';
|
||||||
|
import { ReactComponent as ITflag } from 'i18n/IT.svg';
|
||||||
import { ReactComponent as NLflag } from 'i18n/NL.svg';
|
import { ReactComponent as NLflag } from 'i18n/NL.svg';
|
||||||
import { ReactComponent as NOflag } from 'i18n/NO.svg';
|
import { ReactComponent as NOflag } from 'i18n/NO.svg';
|
||||||
import { ReactComponent as PLflag } from 'i18n/PL.svg';
|
import { ReactComponent as PLflag } from 'i18n/PL.svg';
|
||||||
@@ -73,19 +74,22 @@ const LayoutAuthMenu: FC = () => {
|
|||||||
size="small"
|
size="small"
|
||||||
select
|
select
|
||||||
>
|
>
|
||||||
<MenuItem key="en" value="en">
|
|
||||||
<GBflag style={{ width: 16, verticalAlign: 'middle' }} />
|
|
||||||
EN
|
|
||||||
</MenuItem>
|
|
||||||
<Divider />
|
|
||||||
<MenuItem key="de" value="de">
|
<MenuItem key="de" value="de">
|
||||||
<DEflag style={{ width: 16, verticalAlign: 'middle' }} />
|
<DEflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
DE
|
DE
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem key="en" value="en">
|
||||||
|
<GBflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
EN
|
||||||
|
</MenuItem>
|
||||||
<MenuItem key="fr" value="fr">
|
<MenuItem key="fr" value="fr">
|
||||||
<FRflag style={{ width: 16, verticalAlign: 'middle' }} />
|
<FRflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
FR
|
FR
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem key="it" value="it">
|
||||||
|
<ITflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
IT
|
||||||
|
</MenuItem>
|
||||||
<MenuItem key="nl" value="nl">
|
<MenuItem key="nl" value="nl">
|
||||||
<NLflag style={{ width: 16, verticalAlign: 'middle' }} />
|
<NLflag style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
NL
|
NL
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Box, Button, LinearProgress, Typography, useTheme } from '@mui/material
|
|||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
import type { Theme } from '@mui/material';
|
import type { Theme } from '@mui/material';
|
||||||
import type { AxiosProgressEvent } from 'axios';
|
import type { Progress } from 'alova';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import type { DropzoneState } from 'react-dropzone';
|
import type { DropzoneState } from 'react-dropzone';
|
||||||
|
|
||||||
@@ -26,11 +26,13 @@ const getBorderColor = (theme: Theme, props: DropzoneState) => {
|
|||||||
export interface SingleUploadProps {
|
export interface SingleUploadProps {
|
||||||
onDrop: (acceptedFiles: File[]) => void;
|
onDrop: (acceptedFiles: File[]) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
uploading: boolean;
|
isUploading: boolean;
|
||||||
progress?: AxiosProgressEvent;
|
progress: Progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, progress }) => {
|
const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, isUploading, progress }) => {
|
||||||
|
const uploading = isUploading && progress.total > 0;
|
||||||
|
|
||||||
const dropzoneState = useDropzone({
|
const dropzoneState = useDropzone({
|
||||||
onDrop,
|
onDrop,
|
||||||
accept: {
|
accept: {
|
||||||
@@ -38,20 +40,19 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
|||||||
'application/json': ['.json'],
|
'application/json': ['.json'],
|
||||||
'text/plain': ['.md5']
|
'text/plain': ['.md5']
|
||||||
},
|
},
|
||||||
disabled: uploading,
|
disabled: isUploading,
|
||||||
multiple: false
|
multiple: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = dropzoneState;
|
const { getRootProps, getInputProps } = dropzoneState;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const progressText = () => {
|
const progressText = () => {
|
||||||
if (uploading) {
|
if (uploading) {
|
||||||
if (progress?.total) {
|
if (progress.total) {
|
||||||
return LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%';
|
return LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%';
|
||||||
}
|
}
|
||||||
return LL.UPLOADING() + `\u2026`;
|
|
||||||
}
|
}
|
||||||
return LL.UPLOAD_DROP_TEXT();
|
return LL.UPLOAD_DROP_TEXT();
|
||||||
};
|
};
|
||||||
@@ -81,8 +82,8 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<Box width="100%" p={2}>
|
<Box width="100%" p={2}>
|
||||||
<LinearProgress
|
<LinearProgress
|
||||||
variant={!progress || progress.total ? 'determinate' : 'indeterminate'}
|
variant="determinate"
|
||||||
value={!progress ? 0 : progress.total ? Math.round((progress.loaded * 100) / progress.total) : 0}
|
value={progress.total === 0 ? 0 : Math.round((progress.loaded * 100) / progress.total)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>
|
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
export { default as SingleUpload } from './SingleUpload';
|
export { default as SingleUpload } from './SingleUpload';
|
||||||
export { default as useFileUpload } from './useFileUpload';
|
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
import axios from 'axios';
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import type { FileUploadConfig } from 'api/endpoints';
|
|
||||||
import type { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
interface MediaUploadOptions {
|
|
||||||
upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useFileUpload = ({ upload }: MediaUploadOptions) => {
|
|
||||||
const { LL } = useI18nContext();
|
|
||||||
|
|
||||||
const [uploading, setUploading] = useState<boolean>(false);
|
|
||||||
const [md5, setMd5] = useState<string>('');
|
|
||||||
const [uploadProgress, setUploadProgress] = useState<AxiosProgressEvent>();
|
|
||||||
const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>();
|
|
||||||
|
|
||||||
const resetUploadingStates = () => {
|
|
||||||
setUploading(false);
|
|
||||||
setUploadProgress(undefined);
|
|
||||||
setUploadCancelToken(undefined);
|
|
||||||
setMd5('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelUpload = useCallback(() => {
|
|
||||||
uploadCancelToken?.cancel();
|
|
||||||
resetUploadingStates();
|
|
||||||
}, [uploadCancelToken]);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => () => {
|
|
||||||
uploadCancelToken?.cancel();
|
|
||||||
},
|
|
||||||
[uploadCancelToken]
|
|
||||||
);
|
|
||||||
|
|
||||||
const uploadFile = async (images: File[]) => {
|
|
||||||
try {
|
|
||||||
const cancelToken = axios.CancelToken.source();
|
|
||||||
setUploadCancelToken(cancelToken);
|
|
||||||
setUploading(true);
|
|
||||||
const response = await upload(images[0], {
|
|
||||||
onUploadProgress: setUploadProgress,
|
|
||||||
cancelToken: cancelToken.token
|
|
||||||
});
|
|
||||||
resetUploadingStates();
|
|
||||||
if (response.status === 200) {
|
|
||||||
toast.success(LL.UPLOAD() + ' ' + LL.SUCCESSFUL());
|
|
||||||
} else if (response.status === 201) {
|
|
||||||
setMd5(String(response.data));
|
|
||||||
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (axios.isCancel(error)) {
|
|
||||||
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
|
|
||||||
} else {
|
|
||||||
resetUploadingStates();
|
|
||||||
toast.error(extractErrorMessage(error, LL.UPLOAD() + ' ' + LL.FAILED(0)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return [uploadFile, cancelUpload, uploading, uploadProgress, md5] as const;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useFileUpload;
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useRequest } from 'alova';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -19,6 +20,10 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
|||||||
const [initialized, setInitialized] = useState<boolean>(false);
|
const [initialized, setInitialized] = useState<boolean>(false);
|
||||||
const [me, setMe] = useState<Me>();
|
const [me, setMe] = useState<Me>();
|
||||||
|
|
||||||
|
const { send: verifyAuthorization } = useRequest(AuthenticationApi.verifyAuthorization(), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
const signIn = (accessToken: string) => {
|
const signIn = (accessToken: string) => {
|
||||||
try {
|
try {
|
||||||
AuthenticationApi.getStorage().setItem(ACCESS_TOKEN, accessToken);
|
AuthenticationApi.getStorage().setItem(ACCESS_TOKEN, accessToken);
|
||||||
@@ -42,18 +47,20 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
|||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
const accessToken = AuthenticationApi.getStorage().getItem(ACCESS_TOKEN);
|
const accessToken = AuthenticationApi.getStorage().getItem(ACCESS_TOKEN);
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
try {
|
await verifyAuthorization()
|
||||||
await AuthenticationApi.verifyAuthorization();
|
.then(() => {
|
||||||
setMe(AuthenticationApi.decodeMeJWT(accessToken));
|
setMe(AuthenticationApi.decodeMeJWT(accessToken));
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
} catch (error) {
|
})
|
||||||
setMe(undefined);
|
.catch(() => {
|
||||||
setInitialized(true);
|
setMe(undefined);
|
||||||
}
|
setInitialized(true);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setMe(undefined);
|
setMe(undefined);
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,30 +1,13 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useRequest } from 'alova';
|
||||||
|
|
||||||
import { FeaturesContext } from '.';
|
import { FeaturesContext } from '.';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import type { Features } from 'types';
|
|
||||||
import type { RequiredChildrenProps } from 'utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
import * as FeaturesApi from 'api/features';
|
import * as FeaturesApi from 'api/features';
|
||||||
import { ApplicationError, LoadingSpinner } from 'components';
|
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
|
const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
const { data: features } = useRequest(FeaturesApi.readFeatures);
|
||||||
const [features, setFeatures] = useState<Features>();
|
|
||||||
|
|
||||||
const loadFeatures = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const response = await FeaturesApi.readFeatures();
|
|
||||||
setFeatures(response.data);
|
|
||||||
} catch (error) {
|
|
||||||
setErrorMessage(extractErrorMessage(error, 'Failed to fetch application details.'));
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void loadFeatures();
|
|
||||||
}, [loadFeatures]);
|
|
||||||
|
|
||||||
if (features) {
|
if (features) {
|
||||||
return (
|
return (
|
||||||
@@ -37,12 +20,6 @@ const FeaturesLoader: FC<RequiredChildrenProps> = (props) => {
|
|||||||
</FeaturesContext.Provider>
|
</FeaturesContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMessage) {
|
|
||||||
return <ApplicationError message={errorMessage} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <LoadingSpinner height="100vh" />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FeaturesLoader;
|
export default FeaturesLoader;
|
||||||
|
|||||||
@@ -28,17 +28,27 @@ export const isAPEnabled = ({ provision_mode }: APSettings) =>
|
|||||||
provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
|
provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
|
||||||
|
|
||||||
const APSettingsForm: FC = () => {
|
const APSettingsForm: FC = () => {
|
||||||
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
const {
|
||||||
useRest<APSettings>({
|
loadData,
|
||||||
read: APApi.readAPSettings,
|
saving,
|
||||||
update: APApi.updateAPSettings
|
data,
|
||||||
});
|
updateDataValue,
|
||||||
|
origData,
|
||||||
|
dirtyFlags,
|
||||||
|
setDirtyFlags,
|
||||||
|
blocker,
|
||||||
|
saveData,
|
||||||
|
errorMessage
|
||||||
|
} = useRest<APSettings>({
|
||||||
|
read: APApi.readAPSettings,
|
||||||
|
update: APApi.updateAPSettings
|
||||||
|
});
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
|
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
|
||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import DeviceHubIcon from '@mui/icons-material/DeviceHub';
|
|||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
||||||
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 { useRequest } from 'alova';
|
||||||
import type { Theme } from '@mui/material';
|
import type { Theme } from '@mui/material';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
@@ -12,7 +13,6 @@ import { ButtonRow, FormLoader, SectionContent } from 'components';
|
|||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { APNetworkStatus } from 'types';
|
import { APNetworkStatus } from 'types';
|
||||||
import { useRest } from 'utils';
|
|
||||||
|
|
||||||
export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
|
export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@@ -28,7 +28,7 @@ export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const APStatusForm: FC = () => {
|
const APStatusForm: FC = () => {
|
||||||
const { loadData, data, errorMessage } = useRest<APStatus>({ read: APApi.readAPStatus });
|
const { data: data, send: loadData, error } = useRequest(APApi.readAPStatus);
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ const APStatusForm: FC = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -22,17 +22,27 @@ import { numberValue, updateValueDirty, useRest } from 'utils';
|
|||||||
import { createMqttSettingsValidator, validate } from 'validators';
|
import { createMqttSettingsValidator, validate } from 'validators';
|
||||||
|
|
||||||
const MqttSettingsForm: FC = () => {
|
const MqttSettingsForm: FC = () => {
|
||||||
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
const {
|
||||||
useRest<MqttSettings>({
|
loadData,
|
||||||
read: MqttApi.readMqttSettings,
|
saving,
|
||||||
update: MqttApi.updateMqttSettings
|
data,
|
||||||
});
|
updateDataValue,
|
||||||
|
origData,
|
||||||
|
dirtyFlags,
|
||||||
|
setDirtyFlags,
|
||||||
|
blocker,
|
||||||
|
saveData,
|
||||||
|
errorMessage
|
||||||
|
} = useRest<MqttSettings>({
|
||||||
|
read: MqttApi.readMqttSettings,
|
||||||
|
update: MqttApi.updateMqttSettings
|
||||||
|
});
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
|
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
|
||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@@ -158,7 +168,21 @@ const MqttSettingsForm: FC = () => {
|
|||||||
<MenuItem value={2}>2</MenuItem>
|
<MenuItem value={2}>2</MenuItem>
|
||||||
</TextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{data.rootCA !== undefined && (
|
||||||
|
<Grid item xs={12} sm={6}>
|
||||||
|
<ValidatedPasswordField
|
||||||
|
name="rootCA"
|
||||||
|
label={LL.CERT()}
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={data.rootCA}
|
||||||
|
onChange={updateFormValue}
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<BlockFormControlLabel
|
<BlockFormControlLabel
|
||||||
control={<Checkbox name="clean_session" checked={data.clean_session} onChange={updateFormValue} />}
|
control={<Checkbox name="clean_session" checked={data.clean_session} onChange={updateFormValue} />}
|
||||||
label={LL.MQTT_CLEAN_SESSION()}
|
label={LL.MQTT_CLEAN_SESSION()}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import RefreshIcon from '@mui/icons-material/Refresh';
|
|||||||
import ReportIcon from '@mui/icons-material/Report';
|
import ReportIcon from '@mui/icons-material/Report';
|
||||||
import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff';
|
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 { useRequest } from 'alova';
|
||||||
import type { Theme } from '@mui/material';
|
import type { Theme } from '@mui/material';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
@@ -12,7 +13,6 @@ import * as MqttApi from 'api/mqtt';
|
|||||||
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { MqttDisconnectReason } from 'types';
|
import { MqttDisconnectReason } from 'types';
|
||||||
import { useRest } from 'utils';
|
|
||||||
|
|
||||||
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => {
|
export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => {
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
@@ -26,7 +26,6 @@ export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: T
|
|||||||
|
|
||||||
export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatus, theme: Theme) => {
|
export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatus, theme: Theme) => {
|
||||||
if (mqtt_fails === 0) return theme.palette.success.main;
|
if (mqtt_fails === 0) return theme.palette.success.main;
|
||||||
|
|
||||||
if (mqtt_fails < 10) return theme.palette.warning.main;
|
if (mqtt_fails < 10) return theme.palette.warning.main;
|
||||||
|
|
||||||
return theme.palette.error.main;
|
return theme.palette.error.main;
|
||||||
@@ -39,7 +38,7 @@ export const mqttQueueHighlight = ({ mqtt_queued }: MqttStatus, theme: Theme) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
const MqttStatusForm: FC = () => {
|
const MqttStatusForm: FC = () => {
|
||||||
const { loadData, data, errorMessage } = useRest<MqttStatus>({ read: MqttApi.readMqttStatus });
|
const { data: data, send: loadData, error } = useRequest(MqttApi.readMqttStatus);
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
@@ -69,10 +68,8 @@ const MqttStatusForm: FC = () => {
|
|||||||
return 'Malformed credentials';
|
return 'Malformed credentials';
|
||||||
case MqttDisconnectReason.MQTT_NOT_AUTHORIZED:
|
case MqttDisconnectReason.MQTT_NOT_AUTHORIZED:
|
||||||
return 'Not authorized';
|
return 'Not authorized';
|
||||||
case MqttDisconnectReason.ESP8266_NOT_ENOUGH_SPACE:
|
|
||||||
return 'Device out of memory';
|
|
||||||
case MqttDisconnectReason.TLS_BAD_FINGERPRINT:
|
case MqttDisconnectReason.TLS_BAD_FINGERPRINT:
|
||||||
return 'Server fingerprint invalid';
|
return 'TSL fingerprint invalid';
|
||||||
default:
|
default:
|
||||||
return 'Unknown';
|
return 'Unknown';
|
||||||
}
|
}
|
||||||
@@ -80,7 +77,7 @@ const MqttStatusForm: FC = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderConnectionStatus = () => (
|
const renderConnectionStatus = () => (
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import {
|
|||||||
InputAdornment,
|
InputAdornment,
|
||||||
TextField
|
TextField
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
// eslint-disable-next-line import/named
|
||||||
|
import { updateState, useRequest } from 'alova';
|
||||||
import { useContext, useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import RestartMonitor from '../system/RestartMonitor';
|
import RestartMonitor from '../system/RestartMonitor';
|
||||||
@@ -28,6 +30,7 @@ import type { FC } from 'react';
|
|||||||
|
|
||||||
import type { NetworkSettings } from 'types';
|
import type { NetworkSettings } from 'types';
|
||||||
import * as NetworkApi from 'api/network';
|
import * as NetworkApi from 'api/network';
|
||||||
|
import * as SystemApi from 'api/system';
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
ButtonRow,
|
ButtonRow,
|
||||||
@@ -39,7 +42,7 @@ import {
|
|||||||
BlockNavigation
|
BlockNavigation
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import * as EMSESP from 'project/api';
|
|
||||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
|
|
||||||
import { validate } from 'validators';
|
import { validate } from 'validators';
|
||||||
@@ -52,11 +55,12 @@ const WiFiSettingsForm: FC = () => {
|
|||||||
|
|
||||||
const [initialized, setInitialized] = useState(false);
|
const [initialized, setInitialized] = useState(false);
|
||||||
const [restarting, setRestarting] = useState(false);
|
const [restarting, setRestarting] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loadData,
|
loadData,
|
||||||
saving,
|
saving,
|
||||||
data,
|
data,
|
||||||
setData,
|
updateDataValue,
|
||||||
origData,
|
origData,
|
||||||
dirtyFlags,
|
dirtyFlags,
|
||||||
setDirtyFlags,
|
setDirtyFlags,
|
||||||
@@ -69,13 +73,17 @@ const WiFiSettingsForm: FC = () => {
|
|||||||
update: NetworkApi.updateNetworkSettings
|
update: NetworkApi.updateNetworkSettings
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!initialized && data) {
|
if (!initialized && data) {
|
||||||
if (selectedNetwork) {
|
if (selectedNetwork) {
|
||||||
setData({
|
updateState('networkSettings', (current_data) => ({
|
||||||
ssid: selectedNetwork.ssid,
|
ssid: selectedNetwork.ssid,
|
||||||
password: '',
|
password: '',
|
||||||
hostname: data?.hostname,
|
hostname: current_data?.hostname,
|
||||||
static_ip_config: false,
|
static_ip_config: false,
|
||||||
enableIPv6: false,
|
enableIPv6: false,
|
||||||
bandwidth20: false,
|
bandwidth20: false,
|
||||||
@@ -84,13 +92,13 @@ const WiFiSettingsForm: FC = () => {
|
|||||||
enableMDNS: true,
|
enableMDNS: true,
|
||||||
enableCORS: false,
|
enableCORS: false,
|
||||||
CORSOrigin: '*'
|
CORSOrigin: '*'
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
}
|
}
|
||||||
}, [initialized, setInitialized, data, setData, selectedNetwork]);
|
}, [initialized, setInitialized, data, selectedNetwork]);
|
||||||
|
|
||||||
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
|
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
@@ -112,12 +120,10 @@ const WiFiSettingsForm: FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const restart = async () => {
|
const restart = async () => {
|
||||||
try {
|
await restartCommand().catch((error) => {
|
||||||
await EMSESP.restart();
|
toast.error(error.message);
|
||||||
setRestarting(true);
|
});
|
||||||
} catch (error) {
|
setRestarting(true);
|
||||||
toast.error(LL.PROBLEM_UPDATING());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import SettingsInputAntennaIcon from '@mui/icons-material/SettingsInputAntenna';
|
|||||||
import SettingsInputComponentIcon from '@mui/icons-material/SettingsInputComponent';
|
import SettingsInputComponentIcon from '@mui/icons-material/SettingsInputComponent';
|
||||||
import WifiIcon from '@mui/icons-material/Wifi';
|
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 { useRequest } from 'alova';
|
||||||
import type { Theme } from '@mui/material';
|
import type { Theme } from '@mui/material';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
@@ -15,7 +16,6 @@ import { ButtonRow, FormLoader, SectionContent } from 'components';
|
|||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { NetworkConnectionStatus } from 'types';
|
import { NetworkConnectionStatus } from 'types';
|
||||||
import { useRest } from 'utils';
|
|
||||||
|
|
||||||
const isConnected = ({ status }: NetworkStatus) =>
|
const isConnected = ({ status }: NetworkStatus) =>
|
||||||
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
|
status === NetworkConnectionStatus.WIFI_STATUS_CONNECTED ||
|
||||||
@@ -59,7 +59,7 @@ const IPs = (status: NetworkStatus) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const NetworkStatusForm: FC = () => {
|
const NetworkStatusForm: FC = () => {
|
||||||
const { loadData, data, errorMessage } = useRest<NetworkStatus>({ read: NetworkApi.readNetworkStatus });
|
const { data: data, send: loadData, error } = useRequest(NetworkApi.readNetworkStatus);
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ const NetworkStatusForm: FC = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,82 +1,52 @@
|
|||||||
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
|
import PermScanWifiIcon from '@mui/icons-material/PermScanWifi';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import { useEffect, useState, useCallback, useRef } from 'react';
|
// eslint-disable-next-line import/named
|
||||||
import { toast } from 'react-toastify';
|
import { updateState, useRequest } from 'alova';
|
||||||
|
import { useState, useRef } from 'react';
|
||||||
|
|
||||||
import WiFiNetworkSelector from './WiFiNetworkSelector';
|
import WiFiNetworkSelector from './WiFiNetworkSelector';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import type { WiFiNetwork, WiFiNetworkList } from 'types';
|
|
||||||
import * as NetworkApi from 'api/network';
|
import * as NetworkApi from 'api/network';
|
||||||
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const NUM_POLLS = 10;
|
const NUM_POLLS = 10;
|
||||||
const POLLING_FREQUENCY = 500;
|
const POLLING_FREQUENCY = 1000;
|
||||||
|
|
||||||
const compareNetworks = (network1: WiFiNetwork, network2: WiFiNetwork) => {
|
|
||||||
if (network1.rssi < network2.rssi) return 1;
|
|
||||||
if (network1.rssi > network2.rssi) return -1;
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const WiFiNetworkScanner: FC = () => {
|
const WiFiNetworkScanner: FC = () => {
|
||||||
const { LL } = useI18nContext();
|
|
||||||
|
|
||||||
const pollCount = useRef(0);
|
const pollCount = useRef(0);
|
||||||
const [networkList, setNetworkList] = useState<WiFiNetworkList>();
|
const { LL } = useI18nContext();
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
const [errorMessage, setErrorMessage] = useState<string>();
|
||||||
|
|
||||||
const finishedWithError = useCallback((message: string) => {
|
const { send: scanNetworks, onComplete: onCompleteScanNetworks } = useRequest(NetworkApi.scanNetworks); // is called on page load to start network scan
|
||||||
toast.error(message);
|
const {
|
||||||
setNetworkList(undefined);
|
data: networkList,
|
||||||
setErrorMessage(message);
|
send: getNetworkList,
|
||||||
}, []);
|
onSuccess: onSuccessNetworkList
|
||||||
|
} = useRequest(NetworkApi.listNetworks, {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
const pollNetworkList = useCallback(async () => {
|
onSuccessNetworkList((event) => {
|
||||||
try {
|
if (!event.data) {
|
||||||
const response = await NetworkApi.listNetworks();
|
const completedPollCount = pollCount.current + 1;
|
||||||
if (response.status === 202) {
|
if (completedPollCount < NUM_POLLS) {
|
||||||
const completedPollCount = pollCount.current + 1;
|
pollCount.current = completedPollCount;
|
||||||
if (completedPollCount < NUM_POLLS) {
|
setTimeout(getNetworkList, POLLING_FREQUENCY);
|
||||||
pollCount.current = completedPollCount;
|
|
||||||
setTimeout(pollNetworkList, POLLING_FREQUENCY);
|
|
||||||
} else {
|
|
||||||
finishedWithError(LL.PROBLEM_LOADING());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const newNetworkList = response.data;
|
setErrorMessage(LL.PROBLEM_LOADING());
|
||||||
newNetworkList.networks.sort(compareNetworks);
|
pollCount.current = 0;
|
||||||
setNetworkList(newNetworkList);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.response) {
|
|
||||||
finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
|
|
||||||
} else {
|
|
||||||
finishedWithError(LL.PROBLEM_LOADING());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [finishedWithError, LL]);
|
});
|
||||||
|
|
||||||
const startNetworkScan = useCallback(async () => {
|
onCompleteScanNetworks(() => {
|
||||||
pollCount.current = 0;
|
pollCount.current = 0;
|
||||||
setNetworkList(undefined);
|
|
||||||
setErrorMessage(undefined);
|
setErrorMessage(undefined);
|
||||||
try {
|
updateState('listNetworks', () => undefined);
|
||||||
await NetworkApi.scanNetworks();
|
void getNetworkList();
|
||||||
setTimeout(pollNetworkList, POLLING_FREQUENCY);
|
});
|
||||||
} catch (error) {
|
|
||||||
if (error.response) {
|
|
||||||
finishedWithError(LL.PROBLEM_LOADING() + ' ' + error.response?.data.message);
|
|
||||||
} else {
|
|
||||||
finishedWithError(LL.PROBLEM_LOADING());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [finishedWithError, pollNetworkList, LL]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void startNetworkScan();
|
|
||||||
}, [startNetworkScan]);
|
|
||||||
|
|
||||||
const renderNetworkScanner = () => {
|
const renderNetworkScanner = () => {
|
||||||
if (!networkList) {
|
if (!networkList) {
|
||||||
@@ -93,7 +63,7 @@ const WiFiNetworkScanner: FC = () => {
|
|||||||
startIcon={<PermScanWifiIcon />}
|
startIcon={<PermScanWifiIcon />}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onClick={startNetworkScan}
|
onClick={scanNetworks}
|
||||||
disabled={!errorMessage && !networkList}
|
disabled={!errorMessage && !networkList}
|
||||||
>
|
>
|
||||||
{LL.SCAN_AGAIN()}…
|
{LL.SCAN_AGAIN()}…
|
||||||
|
|||||||
@@ -33,8 +33,12 @@ export const networkSecurityMode = ({ encryption_type }: WiFiNetwork) => {
|
|||||||
return 'WPA2 Enterprise';
|
return 'WPA2 Enterprise';
|
||||||
case WiFiEncryptionType.WIFI_AUTH_OPEN:
|
case WiFiEncryptionType.WIFI_AUTH_OPEN:
|
||||||
return 'None';
|
return 'None';
|
||||||
|
case WiFiEncryptionType.WIFI_AUTH_WPA3_PSK:
|
||||||
|
return 'WPA3';
|
||||||
|
case WiFiEncryptionType.WIFI_AUTH_WPA2_WPA3_PSK:
|
||||||
|
return 'WPA2/WPA3';
|
||||||
default:
|
default:
|
||||||
return 'Unknown';
|
return 'Unknown: ' + encryption_type;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
import { Button, Checkbox, MenuItem } from '@mui/material';
|
import { Button, Checkbox, MenuItem } from '@mui/material';
|
||||||
|
// eslint-disable-next-line import/named
|
||||||
|
import { updateState } from 'alova';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';
|
import { selectedTimeZone, timeZoneSelectItems, TIME_ZONES } from './TZ';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
@@ -22,15 +24,25 @@ import { validate } from 'validators';
|
|||||||
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
||||||
|
|
||||||
const NTPSettingsForm: FC = () => {
|
const NTPSettingsForm: FC = () => {
|
||||||
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
const {
|
||||||
useRest<NTPSettings>({
|
loadData,
|
||||||
read: NTPApi.readNTPSettings,
|
saving,
|
||||||
update: NTPApi.updateNTPSettings
|
data,
|
||||||
});
|
updateDataValue,
|
||||||
|
origData,
|
||||||
|
dirtyFlags,
|
||||||
|
setDirtyFlags,
|
||||||
|
blocker,
|
||||||
|
saveData,
|
||||||
|
errorMessage
|
||||||
|
} = useRest<NTPSettings>({
|
||||||
|
read: NTPApi.readNTPSettings,
|
||||||
|
update: NTPApi.updateNTPSettings
|
||||||
|
});
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
|
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
@@ -51,11 +63,12 @@ const NTPSettingsForm: FC = () => {
|
|||||||
|
|
||||||
const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const changeTimeZone = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
updateFormValue(event);
|
updateFormValue(event);
|
||||||
setData({
|
|
||||||
...data,
|
updateState('ntpSettings', (settings) => ({
|
||||||
|
...settings,
|
||||||
tz_label: event.target.value,
|
tz_label: event.target.value,
|
||||||
tz_format: TIME_ZONES[event.target.value]
|
tz_format: TIME_ZONES[event.target.value]
|
||||||
});
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
import { useRequest } from 'alova';
|
||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import type { Theme } from '@mui/material';
|
import type { Theme } from '@mui/material';
|
||||||
@@ -33,7 +34,7 @@ import { AuthenticatedContext } from 'contexts/authentication';
|
|||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { NTPSyncStatus } from 'types';
|
import { NTPSyncStatus } from 'types';
|
||||||
import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } from 'utils';
|
import { formatDateTime, formatLocalDateTime } from 'utils';
|
||||||
|
|
||||||
export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE;
|
export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE;
|
||||||
export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED;
|
export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED;
|
||||||
@@ -52,7 +53,8 @@ export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const NTPStatusForm: FC = () => {
|
const NTPStatusForm: FC = () => {
|
||||||
const { loadData, data, errorMessage } = useRest<NTPStatus>({ read: NTPApi.readNTPStatus });
|
const { data: data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
|
||||||
|
|
||||||
const [localTime, setLocalTime] = useState<string>('');
|
const [localTime, setLocalTime] = useState<string>('');
|
||||||
const [settingTime, setSettingTime] = useState<boolean>(false);
|
const [settingTime, setSettingTime] = useState<boolean>(false);
|
||||||
const [processing, setProcessing] = useState<boolean>(false);
|
const [processing, setProcessing] = useState<boolean>(false);
|
||||||
@@ -60,6 +62,12 @@ const NTPStatusForm: FC = () => {
|
|||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
|
const { send: updateTime } = useRequest((local_time) => NTPApi.updateTime(local_time), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
NTPApi.updateTime;
|
||||||
|
|
||||||
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => setLocalTime(event.target.value);
|
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => setLocalTime(event.target.value);
|
||||||
|
|
||||||
const openSetTime = () => {
|
const openSetTime = () => {
|
||||||
@@ -84,18 +92,19 @@ const NTPStatusForm: FC = () => {
|
|||||||
|
|
||||||
const configureTime = async () => {
|
const configureTime = async () => {
|
||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
try {
|
|
||||||
await NTPApi.updateTime({
|
await updateTime({ local_time: formatLocalDateTime(new Date(localTime)) })
|
||||||
local_time: formatLocalDateTime(new Date(localTime))
|
.then(async () => {
|
||||||
|
toast.success(LL.TIME_SET());
|
||||||
|
setSettingTime(false);
|
||||||
|
await loadData();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error(LL.PROBLEM_UPDATING());
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setProcessing(false);
|
||||||
});
|
});
|
||||||
toast.success(LL.TIME_SET());
|
|
||||||
setSettingTime(false);
|
|
||||||
await loadData();
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
|
||||||
} finally {
|
|
||||||
setProcessing(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderSetTimeDialog = () => (
|
const renderSetTimeDialog = () => (
|
||||||
@@ -136,7 +145,7 @@ const NTPStatusForm: FC = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -10,16 +10,14 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
Button
|
Button
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useCallback, useState, useEffect } from 'react';
|
import { useRequest } from 'alova';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import type { Token } from 'types';
|
|
||||||
import * as SecurityApi from 'api/security';
|
import * as SecurityApi from 'api/security';
|
||||||
import { MessageBox } from 'components';
|
import { MessageBox } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
interface GenerateTokenProps {
|
interface GenerateTokenProps {
|
||||||
username?: string;
|
username?: string;
|
||||||
@@ -27,24 +25,18 @@ interface GenerateTokenProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
const GenerateToken: FC<GenerateTokenProps> = ({ username, onClose }) => {
|
||||||
const [token, setToken] = useState<Token>();
|
const { LL } = useI18nContext();
|
||||||
const open = !!username;
|
const open = !!username;
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { data: token, send: generateToken } = useRequest(SecurityApi.generateToken(username), {
|
||||||
|
immediate: false
|
||||||
const getToken = useCallback(async () => {
|
});
|
||||||
try {
|
|
||||||
setToken((await SecurityApi.generateToken(username)).data);
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
|
||||||
}
|
|
||||||
}, [username, LL]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
void getToken();
|
void generateToken();
|
||||||
}
|
}
|
||||||
}, [open, getToken]);
|
}, [open, generateToken]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog onClose={onClose} aria-labelledby="generate-token-dialog-title" open={!!username} fullWidth maxWidth="sm">
|
<Dialog onClose={onClose} aria-labelledby="generate-token-dialog-title" open={!!username} fullWidth maxWidth="sm">
|
||||||
|
|||||||
@@ -1,38 +1,42 @@
|
|||||||
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
||||||
import SaveIcon from '@mui/icons-material/Save';
|
|
||||||
import VpnKeyIcon from '@mui/icons-material/VpnKey';
|
import VpnKeyIcon from '@mui/icons-material/VpnKey';
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
import { Button, IconButton, Box } from '@mui/material';
|
import { Button, IconButton, Box } from '@mui/material';
|
||||||
|
|
||||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||||
import { useTheme } from '@table-library/react-table-library/theme';
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
|
|
||||||
|
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
||||||
import GenerateToken from './GenerateToken';
|
import GenerateToken from './GenerateToken';
|
||||||
import UserForm from './UserForm';
|
import UserForm from './UserForm';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import type { SecuritySettings, User } from 'types';
|
import type { SecuritySettings, User } from 'types';
|
||||||
import * as SecurityApi from 'api/security';
|
import * as SecurityApi from 'api/security';
|
||||||
import { ButtonRow, FormLoader, MessageBox, SectionContent } from 'components';
|
import { ButtonRow, FormLoader, MessageBox, SectionContent, BlockNavigation } from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { useRest } from 'utils';
|
import { useRest } from 'utils';
|
||||||
import { createUserValidator } from 'validators';
|
import { createUserValidator } from 'validators';
|
||||||
|
|
||||||
const ManageUsersForm: FC = () => {
|
const ManageUsersForm: FC = () => {
|
||||||
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<SecuritySettings>({
|
const { loadData, saveData, saving, data, updateDataValue, errorMessage } = useRest<SecuritySettings>({
|
||||||
read: SecurityApi.readSecuritySettings,
|
read: SecurityApi.readSecuritySettings,
|
||||||
update: SecurityApi.updateSecuritySettings
|
update: SecurityApi.updateSecuritySettings
|
||||||
});
|
});
|
||||||
|
|
||||||
const [user, setUser] = useState<User>();
|
const [user, setUser] = useState<User>();
|
||||||
const [creating, setCreating] = useState<boolean>(false);
|
const [creating, setCreating] = useState<boolean>(false);
|
||||||
|
const [changed, setChanged] = useState<number>(0);
|
||||||
const [generatingToken, setGeneratingToken] = useState<string>();
|
const [generatingToken, setGeneratingToken] = useState<string>();
|
||||||
const authenticatedContext = useContext(AuthenticatedContext);
|
const authenticatedContext = useContext(AuthenticatedContext);
|
||||||
|
|
||||||
|
const blocker = useBlocker(changed !== 0);
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const table_theme = useTheme({
|
const table_theme = useTheme({
|
||||||
@@ -84,7 +88,8 @@ const ManageUsersForm: FC = () => {
|
|||||||
|
|
||||||
const removeUser = (toRemove: User) => {
|
const removeUser = (toRemove: User) => {
|
||||||
const users = data.users.filter((u) => u.username !== toRemove.username);
|
const users = data.users.filter((u) => u.username !== toRemove.username);
|
||||||
setData({ ...data, users });
|
updateDataValue({ ...data, users });
|
||||||
|
setChanged(changed + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createUser = () => {
|
const createUser = () => {
|
||||||
@@ -107,9 +112,10 @@ const ManageUsersForm: FC = () => {
|
|||||||
|
|
||||||
const doneEditingUser = () => {
|
const doneEditingUser = () => {
|
||||||
if (user) {
|
if (user) {
|
||||||
const users = [...data.users.filter((u) => u.username !== user.username), user];
|
const users = [...data.users.filter((u: { username: string }) => u.username !== user.username), user];
|
||||||
setData({ ...data, users });
|
updateDataValue({ ...data, users });
|
||||||
setUser(undefined);
|
setUser(undefined);
|
||||||
|
setChanged(changed + 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,6 +130,12 @@ const ManageUsersForm: FC = () => {
|
|||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
await saveData();
|
await saveData();
|
||||||
await authenticatedContext.refresh();
|
await authenticatedContext.refresh();
|
||||||
|
setChanged(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancelSubmit = async () => {
|
||||||
|
await loadData();
|
||||||
|
setChanged(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const user_table = data.users.map((u) => ({ ...u, id: u.username }));
|
const user_table = data.users.map((u) => ({ ...u, id: u.username }));
|
||||||
@@ -172,16 +184,30 @@ const ManageUsersForm: FC = () => {
|
|||||||
|
|
||||||
<Box display="flex" flexWrap="wrap">
|
<Box display="flex" flexWrap="wrap">
|
||||||
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
|
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
|
||||||
<Button
|
{changed !== 0 && (
|
||||||
startIcon={<SaveIcon />}
|
<ButtonRow>
|
||||||
disabled={saving || noAdminConfigured()}
|
<Button
|
||||||
variant="outlined"
|
startIcon={<CancelIcon />}
|
||||||
color="primary"
|
disabled={saving}
|
||||||
type="submit"
|
variant="outlined"
|
||||||
onClick={onSubmit}
|
color="primary"
|
||||||
>
|
type="submit"
|
||||||
{LL.UPDATE()}
|
onClick={onCancelSubmit}
|
||||||
</Button>
|
>
|
||||||
|
{LL.CANCEL()}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
startIcon={<WarningIcon color="warning" />}
|
||||||
|
disabled={saving || noAdminConfigured()}
|
||||||
|
variant="contained"
|
||||||
|
color="info"
|
||||||
|
type="submit"
|
||||||
|
onClick={onSubmit}
|
||||||
|
>
|
||||||
|
{LL.APPLY_CHANGES(changed)}
|
||||||
|
</Button>
|
||||||
|
</ButtonRow>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
<Box flexWrap="nowrap" whiteSpace="nowrap">
|
||||||
@@ -208,6 +234,7 @@ const ManageUsersForm: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionContent title={LL.MANAGE_USERS()} titleGutter>
|
<SectionContent title={LL.MANAGE_USERS()} titleGutter>
|
||||||
|
{blocker ? <BlockNavigation blocker={blocker} /> : null}
|
||||||
{content()}
|
{content()}
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,15 +18,25 @@ const SecuritySettingsForm: FC = () => {
|
|||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
const { loadData, saving, data, setData, origData, dirtyFlags, blocker, setDirtyFlags, saveData, errorMessage } =
|
const {
|
||||||
useRest<SecuritySettings>({
|
loadData,
|
||||||
read: SecurityApi.readSecuritySettings,
|
saving,
|
||||||
update: SecurityApi.updateSecuritySettings
|
data,
|
||||||
});
|
updateDataValue,
|
||||||
|
origData,
|
||||||
|
dirtyFlags,
|
||||||
|
setDirtyFlags,
|
||||||
|
blocker,
|
||||||
|
saveData,
|
||||||
|
errorMessage
|
||||||
|
} = useRest<SecuritySettings>({
|
||||||
|
read: SecurityApi.readSecuritySettings,
|
||||||
|
update: SecurityApi.updateSecuritySettings
|
||||||
|
});
|
||||||
|
|
||||||
const authenticatedContext = useContext(AuthenticatedContext);
|
const authenticatedContext = useContext(AuthenticatedContext);
|
||||||
|
|
||||||
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
|
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
|
||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
|||||||
@@ -1,153 +0,0 @@
|
|||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
|
||||||
import { Typography, Button, Box } from '@mui/material';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import type { FileUploadConfig } from 'api/endpoints';
|
|
||||||
import type { AxiosPromise } from 'axios';
|
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import { SingleUpload, useFileUpload } from 'components';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
|
||||||
import * as EMSESP from 'project/api';
|
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
interface UploadFileProps {
|
|
||||||
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
|
||||||
const [uploadFile, cancelUpload, uploading, uploadProgress, md5] = useFileUpload({ upload: uploadGeneralFile });
|
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
|
||||||
|
|
||||||
const saveFile = (json: any, endpoint: string) => {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
const filename = 'emsesp_' + endpoint + '.json';
|
|
||||||
a.href = URL.createObjectURL(
|
|
||||||
new Blob([JSON.stringify(json, null, 2)], {
|
|
||||||
type: 'text/plain'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
a.setAttribute('download', filename);
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadSettings = async () => {
|
|
||||||
try {
|
|
||||||
const response = await EMSESP.getSettings();
|
|
||||||
if (response.status !== 200) {
|
|
||||||
toast.error(LL.PROBLEM_LOADING());
|
|
||||||
} else {
|
|
||||||
saveFile(response.data, 'settings');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadCustomizations = async () => {
|
|
||||||
try {
|
|
||||||
const response = await EMSESP.getCustomizations();
|
|
||||||
if (response.status !== 200) {
|
|
||||||
toast.error(LL.PROBLEM_LOADING());
|
|
||||||
} else {
|
|
||||||
saveFile(response.data, 'customizations');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadEntities = async () => {
|
|
||||||
try {
|
|
||||||
const response = await EMSESP.getEntities();
|
|
||||||
if (response.status !== 200) {
|
|
||||||
toast.error(LL.PROBLEM_LOADING());
|
|
||||||
} else {
|
|
||||||
saveFile(response.data, 'entities');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadSchedule = async () => {
|
|
||||||
try {
|
|
||||||
const response = await EMSESP.getSchedule();
|
|
||||||
if (response.status !== 200) {
|
|
||||||
toast.error(LL.PROBLEM_LOADING());
|
|
||||||
} else {
|
|
||||||
saveFile(response.data, 'schedule');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!uploading && (
|
|
||||||
<>
|
|
||||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
|
||||||
{LL.UPLOAD()}
|
|
||||||
</Typography>
|
|
||||||
<Box mb={2} color="warning.main">
|
|
||||||
<Typography variant="body2">{LL.UPLOAD_TEXT()} </Typography>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{md5 !== '' && (
|
|
||||||
<Box mb={2}>
|
|
||||||
<Typography variant="body2">{'MD5: ' + md5}</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<SingleUpload onDrop={uploadFile} onCancel={cancelUpload} uploading={uploading} progress={uploadProgress} />
|
|
||||||
|
|
||||||
{!uploading && (
|
|
||||||
<>
|
|
||||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
|
||||||
{LL.DOWNLOAD(0)}
|
|
||||||
</Typography>
|
|
||||||
<Box color="warning.main">
|
|
||||||
<Typography mb={1} variant="body2">
|
|
||||||
{LL.DOWNLOAD_SETTINGS_TEXT()}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSettings}>
|
|
||||||
{LL.SETTINGS_OF('')}
|
|
||||||
</Button>
|
|
||||||
<Box color="warning.main">
|
|
||||||
<Typography mt={2} mb={1} variant="body2">
|
|
||||||
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadCustomizations}>
|
|
||||||
{LL.CUSTOMIZATIONS()}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
sx={{ ml: 2 }}
|
|
||||||
startIcon={<DownloadIcon />}
|
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
onClick={downloadEntities}
|
|
||||||
>
|
|
||||||
{LL.CUSTOM_ENTITIES(0)}
|
|
||||||
</Button>
|
|
||||||
<Box color="warning.main">
|
|
||||||
<Typography mt={2} mb={1} variant="body2">
|
|
||||||
{LL.DOWNLOAD_SCHEDULE_TEXT()}{' '}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSchedule}>
|
|
||||||
{LL.SCHEDULE(0)}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GeneralFileUpload;
|
|
||||||
@@ -24,15 +24,25 @@ import { validate } from 'validators';
|
|||||||
import { OTA_SETTINGS_VALIDATOR } from 'validators/system';
|
import { OTA_SETTINGS_VALIDATOR } from 'validators/system';
|
||||||
|
|
||||||
const OTASettingsForm: FC = () => {
|
const OTASettingsForm: FC = () => {
|
||||||
const { loadData, saving, data, setData, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
const {
|
||||||
useRest<OTASettings>({
|
loadData,
|
||||||
read: SystemApi.readOTASettings,
|
saveData,
|
||||||
update: SystemApi.updateOTASettings
|
saving,
|
||||||
});
|
updateDataValue,
|
||||||
|
data,
|
||||||
|
origData,
|
||||||
|
dirtyFlags,
|
||||||
|
setDirtyFlags,
|
||||||
|
blocker,
|
||||||
|
errorMessage
|
||||||
|
} = useRest<OTASettings>({
|
||||||
|
read: SystemApi.readOTASettings,
|
||||||
|
update: SystemApi.updateOTASettings
|
||||||
|
});
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
|
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useRequest } from 'alova';
|
||||||
import { useRef, useState, useEffect } from 'react';
|
import { useRef, useState, useEffect } from 'react';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
@@ -7,20 +8,19 @@ import { FormLoader } from 'components';
|
|||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const RESTART_TIMEOUT = 2 * 60 * 1000;
|
const RESTART_TIMEOUT = 2 * 60 * 1000;
|
||||||
const POLL_TIMEOUT = 2000;
|
const POLL_INTERVAL = 3000;
|
||||||
const POLL_INTERVAL = 5000;
|
|
||||||
|
|
||||||
const RestartMonitor: FC = () => {
|
const RestartMonitor: FC = () => {
|
||||||
const [failed, setFailed] = useState<boolean>(false);
|
const [failed, setFailed] = useState<boolean>(false);
|
||||||
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
|
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
const { send } = useRequest(SystemApi.readSystemStatus, { force: true });
|
||||||
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
|
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
|
||||||
|
|
||||||
const poll = useRef(async () => {
|
const poll = useRef(async () => {
|
||||||
try {
|
try {
|
||||||
await SystemApi.readSystemStatus(POLL_TIMEOUT);
|
await send();
|
||||||
document.location.href = '/fileUpdated';
|
document.location.href = '/';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (new Date().getTime() < timeoutAt.current) {
|
if (new Date().getTime() < timeoutAt.current) {
|
||||||
setTimeoutId(setTimeout(poll.current, POLL_INTERVAL));
|
setTimeoutId(setTimeout(poll.current, POLL_INTERVAL));
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
import { Box, styled, Button, Checkbox, MenuItem, Grid, TextField } from '@mui/material';
|
import { Box, styled, Button, Checkbox, MenuItem, Grid, TextField } from '@mui/material';
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useRequest } from 'alova';
|
||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import type { LogSettings, LogEntry, LogEntries } from 'types';
|
import type { LogSettings, LogEntry } from 'types';
|
||||||
import { addAccessTokenParameter } from 'api/authentication';
|
import { addAccessTokenParameter } from 'api/authentication';
|
||||||
import { EVENT_SOURCE_ROOT } from 'api/endpoints';
|
import { EVENT_SOURCE_ROOT } from 'api/endpoints';
|
||||||
import * as SystemApi from 'api/system';
|
import * as SystemApi from 'api/system';
|
||||||
@@ -14,7 +15,7 @@ import { SectionContent, FormLoader, BlockFormControlLabel, BlockNavigation } fr
|
|||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { LogLevel } from 'types';
|
import { LogLevel } from 'types';
|
||||||
import { useRest, updateValueDirty, extractErrorMessage } from 'utils';
|
import { updateValueDirty, useRest } from 'utils';
|
||||||
|
|
||||||
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
|
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
|
||||||
|
|
||||||
@@ -49,14 +50,19 @@ const levelLabel = (level: LogLevel) => {
|
|||||||
const SystemLog: FC = () => {
|
const SystemLog: FC = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const { loadData, data, setData, origData, dirtyFlags, blocker, setDirtyFlags, setOrigData } = useRest<LogSettings>({
|
const { loadData, data, updateDataValue, origData, dirtyFlags, setDirtyFlags, blocker, saveData, errorMessage } =
|
||||||
read: SystemApi.readLogSettings
|
useRest<LogSettings>({
|
||||||
});
|
read: SystemApi.readLogSettings,
|
||||||
|
update: SystemApi.updateLogSettings
|
||||||
|
});
|
||||||
|
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
// called on page load to reset pointer and fetch all log entries
|
||||||
const [logEntries, setLogEntries] = useState<LogEntries>({ events: [] });
|
useRequest(SystemApi.fetchLog());
|
||||||
|
const [logEntries, setLogEntries] = useState<LogEntry[]>([]);
|
||||||
const [lastIndex, setLastIndex] = useState<number>(0);
|
const [lastIndex, setLastIndex] = useState<number>(0);
|
||||||
|
|
||||||
|
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
|
||||||
|
|
||||||
const paddedLevelLabel = (level: LogLevel) => {
|
const paddedLevelLabel = (level: LogLevel) => {
|
||||||
const label = levelLabel(level);
|
const label = levelLabel(level);
|
||||||
return data?.compact ? ' ' + label[0] : label.padStart(8, '\xa0');
|
return data?.compact ? ' ' + label[0] : label.padStart(8, '\xa0');
|
||||||
@@ -72,11 +78,9 @@ const SystemLog: FC = () => {
|
|||||||
return data?.compact ? label : label.padEnd(7, '\xa0');
|
return data?.compact ? label : label.padEnd(7, '\xa0');
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
|
|
||||||
|
|
||||||
const onDownload = () => {
|
const onDownload = () => {
|
||||||
let result = '';
|
let result = '';
|
||||||
for (const i of logEntries.events) {
|
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');
|
const a = document.createElement('a');
|
||||||
@@ -93,29 +97,32 @@ const SystemLog: FC = () => {
|
|||||||
const logentry = JSON.parse(rawData as string) as LogEntry;
|
const logentry = JSON.parse(rawData as string) as LogEntry;
|
||||||
if (logentry.i > lastIndex) {
|
if (logentry.i > lastIndex) {
|
||||||
setLastIndex(logentry.i);
|
setLastIndex(logentry.i);
|
||||||
setLogEntries((old) => ({ events: [...old.events, logentry] }));
|
setLogEntries((log) => [...log, logentry]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchLog = useCallback(async () => {
|
const saveSettings = async () => {
|
||||||
try {
|
await saveData();
|
||||||
await SystemApi.readLogEntries();
|
};
|
||||||
} catch (error) {
|
|
||||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
}
|
|
||||||
}, [LL]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void fetchLog();
|
if (logEntries.length) {
|
||||||
}, [fetchLog]);
|
ref.current?.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'end'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [logEntries.length]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const es = new EventSource(addAccessTokenParameter(LOG_EVENTSOURCE_URL));
|
const es = new EventSource(addAccessTokenParameter(LOG_EVENTSOURCE_URL));
|
||||||
es.onmessage = onMessage;
|
es.onmessage = onMessage;
|
||||||
es.onerror = () => {
|
es.onerror = () => {
|
||||||
es.close();
|
es.close();
|
||||||
window.location.reload();
|
toast.error('EventSource failed');
|
||||||
};
|
};
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -123,28 +130,6 @@ const SystemLog: FC = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const saveSettings = async () => {
|
|
||||||
if (data) {
|
|
||||||
try {
|
|
||||||
const response = await SystemApi.updateLogSettings({
|
|
||||||
level: data.level,
|
|
||||||
max_messages: data.max_messages,
|
|
||||||
compact: data.compact
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status !== 200) {
|
|
||||||
toast.error(LL.PROBLEM_UPDATING());
|
|
||||||
} else {
|
|
||||||
setOrigData(response.data);
|
|
||||||
setDirtyFlags([]);
|
|
||||||
toast.success(LL.UPDATED_OF(LL.SETTINGS()));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
||||||
@@ -230,17 +215,17 @@ const SystemLog: FC = () => {
|
|||||||
p: 1
|
p: 1
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{logEntries &&
|
{logEntries.map((e) => (
|
||||||
logEntries.events.map((e) => (
|
<LogEntryLine key={e.i}>
|
||||||
<LogEntryLine key={e.i}>
|
<span>{e.t}</span>
|
||||||
<span>{e.t}</span>
|
{data.compact && <span>{paddedLevelLabel(e.l)} </span>}
|
||||||
{data.compact && <span>{paddedLevelLabel(e.l)} </span>}
|
{!data.compact && <span>{paddedLevelLabel(e.l)} </span>}
|
||||||
{!data.compact && <span>{paddedLevelLabel(e.l)} </span>}
|
<span>{paddedIDLabel(e.i)} </span>
|
||||||
<span>{paddedIDLabel(e.i)} </span>
|
<span>{paddedNameLabel(e.n)} </span>
|
||||||
<span>{paddedNameLabel(e.n)} </span>
|
<span>{e.m}</span>
|
||||||
<span>{e.m}</span>
|
</LogEntryLine>
|
||||||
</LogEntryLine>
|
))}
|
||||||
))}
|
<div ref={ref} />
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,18 +28,16 @@ import {
|
|||||||
Typography
|
Typography
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import axios from 'axios';
|
import { useRequest } from 'alova';
|
||||||
import { useContext, useState, useEffect } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import RestartMonitor from './RestartMonitor';
|
import RestartMonitor from './RestartMonitor';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import type { SystemStatus, Version } from 'types';
|
|
||||||
import * as SystemApi from 'api/system';
|
import * as SystemApi from 'api/system';
|
||||||
import { ButtonRow, FormLoader, SectionContent, MessageBox } from 'components';
|
import { ButtonRow, FormLoader, SectionContent, MessageBox } from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { extractErrorMessage, useRest } from 'utils';
|
|
||||||
|
|
||||||
export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest';
|
export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest';
|
||||||
export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest';
|
export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest';
|
||||||
@@ -51,61 +49,75 @@ function formatNumber(num: number) {
|
|||||||
|
|
||||||
const SystemStatusForm: FC = () => {
|
const SystemStatusForm: FC = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const [restarting, setRestarting] = useState<boolean>();
|
|
||||||
|
|
||||||
const { loadData, data, errorMessage } = useRest<SystemStatus>({ read: SystemApi.readSystemStatus });
|
|
||||||
|
|
||||||
const { me } = useContext(AuthenticatedContext);
|
const { me } = useContext(AuthenticatedContext);
|
||||||
const [confirmRestart, setConfirmRestart] = useState<boolean>(false);
|
const [confirmRestart, setConfirmRestart] = useState<boolean>(false);
|
||||||
const [confirmFactoryReset, setConfirmFactoryReset] = useState<boolean>(false);
|
const [confirmFactoryReset, setConfirmFactoryReset] = useState<boolean>(false);
|
||||||
const [processing, setProcessing] = useState<boolean>(false);
|
const [processing, setProcessing] = useState<boolean>(false);
|
||||||
const [showingVersion, setShowingVersion] = useState<boolean>(false);
|
const [showingVersion, setShowingVersion] = useState<boolean>(false);
|
||||||
const [latestVersion, setLatestVersion] = useState<Version>();
|
const [restarting, setRestarting] = useState<boolean>();
|
||||||
const [latestDevVersion, setLatestDevVersion] = useState<Version>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||||
void axios.get(VERSIONCHECK_ENDPOINT).then((response) => {
|
immediate: false
|
||||||
setLatestVersion({
|
});
|
||||||
version: response.data.name,
|
|
||||||
url: response.data.assets[1].browser_download_url,
|
const { send: factoryResetCommand } = useRequest(SystemApi.factoryReset(), {
|
||||||
changelog: response.data.assets[0].browser_download_url
|
immediate: false
|
||||||
});
|
});
|
||||||
});
|
|
||||||
void axios.get(VERSIONCHECK_DEV_ENDPOINT).then((response) => {
|
const { send: partitionCommand } = useRequest(SystemApi.partition(), {
|
||||||
setLatestDevVersion({
|
immediate: false
|
||||||
version: response.data.name.split(/\s+/).splice(-1),
|
});
|
||||||
url: response.data.assets[1].browser_download_url,
|
|
||||||
changelog: response.data.assets[0].browser_download_url
|
// fetch versions from GH on load
|
||||||
});
|
const { data: latestVersion } = useRequest(SystemApi.getStableVersion);
|
||||||
});
|
const { data: latestDevVersion } = useRequest(SystemApi.getDevVersion);
|
||||||
}, []);
|
|
||||||
|
const { data: data, send: loadData, error } = useRequest(SystemApi.readSystemStatus, { force: true });
|
||||||
|
|
||||||
const restart = async () => {
|
const restart = async () => {
|
||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
try {
|
await restartCommand()
|
||||||
const response = await SystemApi.restart();
|
.then(() => {
|
||||||
if (response.status === 200) {
|
|
||||||
setRestarting(true);
|
setRestarting(true);
|
||||||
}
|
})
|
||||||
} catch (error) {
|
.catch((err) => {
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
toast.error(err.message);
|
||||||
} finally {
|
})
|
||||||
setConfirmRestart(false);
|
.finally(() => {
|
||||||
setProcessing(false);
|
setConfirmRestart(false);
|
||||||
}
|
setProcessing(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const factoryReset = async () => {
|
||||||
|
setProcessing(true);
|
||||||
|
await factoryResetCommand()
|
||||||
|
.then(() => {
|
||||||
|
setRestarting(true);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error(err.message);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setConfirmFactoryReset(false);
|
||||||
|
setProcessing(false);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const partition = async () => {
|
const partition = async () => {
|
||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
try {
|
await partitionCommand()
|
||||||
await SystemApi.partition();
|
.then(() => {
|
||||||
setRestarting(true);
|
setRestarting(true);
|
||||||
} catch (error) {
|
})
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
.catch((err) => {
|
||||||
} finally {
|
toast.error(err.message);
|
||||||
setConfirmRestart(false);
|
})
|
||||||
setProcessing(false);
|
.finally(() => {
|
||||||
}
|
setConfirmRestart(false);
|
||||||
|
setProcessing(false);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderRestartDialog = () => (
|
const renderRestartDialog = () => (
|
||||||
@@ -200,19 +212,6 @@ const SystemStatusForm: FC = () => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|
||||||
const factoryReset = async () => {
|
|
||||||
setProcessing(true);
|
|
||||||
try {
|
|
||||||
await SystemApi.factoryReset();
|
|
||||||
setRestarting(true);
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
|
||||||
} finally {
|
|
||||||
setConfirmFactoryReset(false);
|
|
||||||
setProcessing(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderFactoryResetDialog = () => (
|
const renderFactoryResetDialog = () => (
|
||||||
<Dialog open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}>
|
<Dialog open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}>
|
||||||
<DialogTitle>{LL.FACTORY_RESET()}</DialogTitle>
|
<DialogTitle>{LL.FACTORY_RESET()}</DialogTitle>
|
||||||
@@ -242,7 +241,7 @@ const SystemStatusForm: FC = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,30 +1,175 @@
|
|||||||
import { useRef, useState } from 'react';
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
import GeneralFileUpload from './GeneralFileUpload';
|
import { Typography, Button, Box } from '@mui/material';
|
||||||
|
import { useRequest } from 'alova';
|
||||||
|
import { useState, type FC } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
import RestartMonitor from './RestartMonitor';
|
import RestartMonitor from './RestartMonitor';
|
||||||
import type { FileUploadConfig } from 'api/endpoints';
|
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import * as SystemApi from 'api/system';
|
import * as SystemApi from 'api/system';
|
||||||
import { SectionContent } from 'components';
|
import { SectionContent, SingleUpload } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
import * as EMSESP from 'project/api';
|
||||||
|
|
||||||
const UploadFileForm: FC = () => {
|
const UploadFileForm: FC = () => {
|
||||||
const [restarting, setRestarting] = useState<boolean>();
|
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
const [restarting, setRestarting] = useState<boolean>(false);
|
||||||
|
const [md5, setMd5] = useState<string>();
|
||||||
|
|
||||||
const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => {
|
const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(EMSESP.getSettings(), {
|
||||||
const response = await SystemApi.uploadFile(file, config);
|
immediate: false
|
||||||
if (response.status === 200) {
|
});
|
||||||
setRestarting(true);
|
const { send: getCustomizations, onSuccess: onSuccessgetCustomizations } = useRequest(EMSESP.getCustomizations(), {
|
||||||
}
|
immediate: false
|
||||||
return response;
|
});
|
||||||
|
const { send: getEntities, onSuccess: onSuccessGetEntities } = useRequest(EMSESP.getEntities(), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), {
|
||||||
|
immediate: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
loading: isUploading,
|
||||||
|
uploading: progress,
|
||||||
|
send: sendUpload,
|
||||||
|
onSuccess: onSuccessUpload,
|
||||||
|
abort: cancelUpload
|
||||||
|
} = useRequest(SystemApi.uploadFile, {
|
||||||
|
immediate: false,
|
||||||
|
force: true
|
||||||
|
});
|
||||||
|
|
||||||
|
onSuccessUpload(({ data }: any) => {
|
||||||
|
if (data) {
|
||||||
|
setMd5(data.md5);
|
||||||
|
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
|
||||||
|
} else {
|
||||||
|
setRestarting(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const startUpload = async (files: File[]) => {
|
||||||
|
await sendUpload(files[0]).catch((err) => {
|
||||||
|
if (err.message === 'The user aborted a request') {
|
||||||
|
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
|
||||||
|
} else if (err.message === 'Network Error') {
|
||||||
|
toast.warning('Invalid file extension or incompatible bin file');
|
||||||
|
} else {
|
||||||
|
toast.error(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveFile = (json: any, endpoint: string) => {
|
||||||
|
const anchor = document.createElement('a');
|
||||||
|
anchor.href = URL.createObjectURL(
|
||||||
|
new Blob([JSON.stringify(json, null, 2)], {
|
||||||
|
type: 'text/plain'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
anchor.download = 'emsesp_' + endpoint + '.json';
|
||||||
|
anchor.click();
|
||||||
|
URL.revokeObjectURL(anchor.href);
|
||||||
|
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||||
|
};
|
||||||
|
|
||||||
|
onSuccessGetSettings((event) => {
|
||||||
|
saveFile(event.data, 'settings');
|
||||||
|
});
|
||||||
|
onSuccessgetCustomizations((event) => {
|
||||||
|
saveFile(event.data, 'customizations');
|
||||||
|
});
|
||||||
|
onSuccessGetEntities((event) => {
|
||||||
|
saveFile(event.data, 'entities');
|
||||||
|
});
|
||||||
|
onSuccessGetSchedule((event) => {
|
||||||
|
saveFile(event.data, 'schedule');
|
||||||
|
});
|
||||||
|
|
||||||
|
const downloadSettings = async () => {
|
||||||
|
await getSettings().catch((error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadCustomizations = async () => {
|
||||||
|
await getCustomizations().catch((error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadEntities = async () => {
|
||||||
|
await getEntities().catch((error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadSchedule = async () => {
|
||||||
|
await getSchedule().catch((error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = () => (
|
||||||
|
<>
|
||||||
|
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||||
|
{LL.UPLOAD()}
|
||||||
|
</Typography>
|
||||||
|
<Box mb={2} color="warning.main">
|
||||||
|
<Typography variant="body2">{LL.UPLOAD_TEXT()} </Typography>
|
||||||
|
</Box>
|
||||||
|
{md5 && (
|
||||||
|
<Box mb={2}>
|
||||||
|
<Typography variant="body2">{'MD5: ' + md5}</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<SingleUpload onDrop={startUpload} onCancel={cancelUpload} isUploading={isUploading} progress={progress} />
|
||||||
|
{!isUploading && (
|
||||||
|
<>
|
||||||
|
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||||
|
{LL.DOWNLOAD(0)}
|
||||||
|
</Typography>
|
||||||
|
<Box color="warning.main">
|
||||||
|
<Typography mb={1} variant="body2">
|
||||||
|
{LL.DOWNLOAD_SETTINGS_TEXT()}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSettings}>
|
||||||
|
{LL.SETTINGS_OF('')}
|
||||||
|
</Button>
|
||||||
|
<Box color="warning.main">
|
||||||
|
<Typography mt={2} mb={1} variant="body2">
|
||||||
|
{LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadCustomizations}>
|
||||||
|
{LL.CUSTOMIZATIONS()}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
sx={{ ml: 2 }}
|
||||||
|
startIcon={<DownloadIcon />}
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
onClick={downloadEntities}
|
||||||
|
>
|
||||||
|
{LL.CUSTOM_ENTITIES(0)}
|
||||||
|
</Button>
|
||||||
|
<Box color="warning.main">
|
||||||
|
<Typography mt={2} mb={1} variant="body2">
|
||||||
|
{LL.DOWNLOAD_SCHEDULE_TEXT()}{' '}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={downloadSchedule}>
|
||||||
|
{LL.SCHEDULE(0)}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<SectionContent title={LL.UPLOAD_DOWNLOAD()} titleGutter>
|
<SectionContent title={LL.UPLOAD_DOWNLOAD()} titleGutter>
|
||||||
{restarting ? <RestartMonitor /> : <GeneralFileUpload uploadGeneralFile={uploadFile.current} />}
|
{restarting ? <RestartMonitor /> : content()}
|
||||||
</SectionContent>
|
</SectionContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
1
interface/src/i18n/IT.svg
Normal file
1
interface/src/i18n/IT.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 513 342"><path fill="#FFF" d="M342 0H0v341.3h512V0z"/><path fill="#6DA544" d="M0 0h171v342H0z"/><path fill="#D80027" d="M342 0h171v342H342z"/></svg>
|
||||||
|
After Width: | Height: | Size: 201 B |
@@ -45,13 +45,12 @@ const de: Translation = {
|
|||||||
CHANGE_VALUE: 'Wert ändern',
|
CHANGE_VALUE: 'Wert ändern',
|
||||||
CANCEL: 'Abbrechen',
|
CANCEL: 'Abbrechen',
|
||||||
RESET: 'Zurücksetzen',
|
RESET: 'Zurücksetzen',
|
||||||
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
APPLY_CHANGES: 'Änderungen anwenden ({0})',
|
||||||
UPDATE: 'Update', // TODO translate
|
UPDATE: 'Update',
|
||||||
EXECUTE: 'Execute', // TODO translate
|
EXECUTE: 'Ausführen',
|
||||||
REMOVE: 'Entfernen',
|
REMOVE: 'Entfernen',
|
||||||
PROBLEM_UPDATING: 'Problem beim Aktualisieren',
|
PROBLEM_UPDATING: 'Problem beim Aktualisieren',
|
||||||
PROBLEM_LOADING: 'Problem beim Laden',
|
PROBLEM_LOADING: 'Problem beim Laden',
|
||||||
ACCESS_DENIED: 'Zugriff abgelehnt',
|
|
||||||
ANALOG_SENSOR: 'Analogsensor',
|
ANALOG_SENSOR: 'Analogsensor',
|
||||||
ANALOG_SENSORS: 'Analogsensoren',
|
ANALOG_SENSORS: 'Analogsensoren',
|
||||||
SETTINGS: 'Einstellungen',
|
SETTINGS: 'Einstellungen',
|
||||||
@@ -71,7 +70,6 @@ const de: Translation = {
|
|||||||
TEMP_SENSOR: 'Temperatursensor',
|
TEMP_SENSOR: 'Temperatursensor',
|
||||||
TEMP_SENSORS: 'Temperatursensoren',
|
TEMP_SENSORS: 'Temperatursensoren',
|
||||||
WRITE_CMD_SENT: 'Befehl schreiben wurde gesendet',
|
WRITE_CMD_SENT: 'Befehl schreiben wurde gesendet',
|
||||||
WRITE_CMD_FAILED: 'Befehl schreiben failed', // TODO translate
|
|
||||||
EMS_BUS_WARNING: 'EMS-Bus getrennt. Wenn diese Warnung nach einigen Sekunden immer noch besteht, überprüfen Sie bitte die Einstellungen und das Board-Profil',
|
EMS_BUS_WARNING: 'EMS-Bus getrennt. Wenn diese Warnung nach einigen Sekunden immer noch besteht, überprüfen Sie bitte die Einstellungen und das Board-Profil',
|
||||||
EMS_BUS_SCANNING: 'Suche nach EMS Geräten...',
|
EMS_BUS_SCANNING: 'Suche nach EMS Geräten...',
|
||||||
CONNECTED: 'Verbunden',
|
CONNECTED: 'Verbunden',
|
||||||
@@ -182,7 +180,7 @@ const de: Translation = {
|
|||||||
LOG_OF: '{0} Log',
|
LOG_OF: '{0} Log',
|
||||||
STATUS_OF: '{0} Status',
|
STATUS_OF: '{0} Status',
|
||||||
UPLOAD_DOWNLOAD: 'Hoch-/Herunterladen',
|
UPLOAD_DOWNLOAD: 'Hoch-/Herunterladen',
|
||||||
VERSION_ON: 'You are currently on', // TODO translate
|
VERSION_ON: 'Sie verwenden derzeit',
|
||||||
SYSTEM_APPLY_FIRMWARE: 'um die neue Firmware anzuwenden',
|
SYSTEM_APPLY_FIRMWARE: 'um die neue Firmware anzuwenden',
|
||||||
CLOSE: 'Schließen',
|
CLOSE: 'Schließen',
|
||||||
USE: 'Verwenden Sie',
|
USE: 'Verwenden Sie',
|
||||||
@@ -242,7 +240,7 @@ const de: Translation = {
|
|||||||
MQTT_PUBLISH_TEXT_2: 'Veröffentliche als Kommando-Topic (ioBroker)',
|
MQTT_PUBLISH_TEXT_2: 'Veröffentliche als Kommando-Topic (ioBroker)',
|
||||||
MQTT_PUBLISH_TEXT_3: 'Aktiviere `MQTT Discovery`',
|
MQTT_PUBLISH_TEXT_3: 'Aktiviere `MQTT Discovery`',
|
||||||
MQTT_PUBLISH_TEXT_4: 'Prefix für die `Discovery`-Topics',
|
MQTT_PUBLISH_TEXT_4: 'Prefix für die `Discovery`-Topics',
|
||||||
MQTT_PUBLISH_TEXT_5: 'Discovery type', // TODO translate
|
MQTT_PUBLISH_TEXT_5: 'Discovery Typ',
|
||||||
MQTT_PUBLISH_INTERVALS: 'Veröffentlichungs-Intervalle',
|
MQTT_PUBLISH_INTERVALS: 'Veröffentlichungs-Intervalle',
|
||||||
MQTT_INT_BOILER: 'Boiler und Wärmepumpen',
|
MQTT_INT_BOILER: 'Boiler und Wärmepumpen',
|
||||||
MQTT_INT_THERMOSTATS: 'Thermostate',
|
MQTT_INT_THERMOSTATS: 'Thermostate',
|
||||||
@@ -284,7 +282,7 @@ const de: Translation = {
|
|||||||
SCAN_AGAIN: 'Erneute Suche',
|
SCAN_AGAIN: 'Erneute Suche',
|
||||||
NETWORK_SCANNER: 'Netzwerk Suche',
|
NETWORK_SCANNER: 'Netzwerk Suche',
|
||||||
NETWORK_NO_WIFI: 'Keine WiFi Netzwerke gefunden',
|
NETWORK_NO_WIFI: 'Keine WiFi Netzwerke gefunden',
|
||||||
NETWORK_BLANK_SSID: 'Freilassen um WiFi zu deaktivieren',
|
NETWORK_BLANK_SSID: 'Freilassen um WiFi zu deaktivieren und ETH zu aktivieren',
|
||||||
TX_POWER: 'Tx Leistung',
|
TX_POWER: 'Tx Leistung',
|
||||||
HOSTNAME: 'Hostname',
|
HOSTNAME: 'Hostname',
|
||||||
NETWORK_DISABLE_SLEEP: 'Deaktiviere WiFi Schlafmodus',
|
NETWORK_DISABLE_SLEEP: 'Deaktiviere WiFi Schlafmodus',
|
||||||
@@ -321,10 +319,11 @@ const de: Translation = {
|
|||||||
SCHEDULE_TIMER_3: 'jede Stunde',
|
SCHEDULE_TIMER_3: 'jede Stunde',
|
||||||
CUSTOM_ENTITIES: 'Individuelle Entitäten',
|
CUSTOM_ENTITIES: 'Individuelle Entitäten',
|
||||||
ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus',
|
ENTITIES_HELP_1: 'Abfrage von Werten auf dem EMS-Bus',
|
||||||
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
ENTITIES_UPDATED: 'Entitäten gespeichert',
|
||||||
WRITEABLE: 'Schreibbar',
|
WRITEABLE: 'Schreibbar',
|
||||||
SHOWING: 'Showing', // TODO translate
|
SHOWING: 'Anzeigen von',
|
||||||
SEARCH: 'Search' // TODO translate
|
SEARCH: 'Suche',
|
||||||
|
CERT: 'TSL Zertifikat (Freilassen um TSL zu deaktivieren)'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default de;
|
export default de;
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ const en: Translation = {
|
|||||||
REMOVE: 'Remove',
|
REMOVE: 'Remove',
|
||||||
PROBLEM_UPDATING: 'Problem updating',
|
PROBLEM_UPDATING: 'Problem updating',
|
||||||
PROBLEM_LOADING: 'Problem loading',
|
PROBLEM_LOADING: 'Problem loading',
|
||||||
ACCESS_DENIED: 'Access Denied',
|
|
||||||
ANALOG_SENSOR: 'Analog Sensor',
|
ANALOG_SENSOR: 'Analog Sensor',
|
||||||
ANALOG_SENSORS: 'Analog Sensors',
|
ANALOG_SENSORS: 'Analog Sensors',
|
||||||
SETTINGS: 'Settings',
|
SETTINGS: 'Settings',
|
||||||
@@ -71,7 +70,6 @@ const en: Translation = {
|
|||||||
TEMP_SENSOR: 'Temperature Sensor',
|
TEMP_SENSOR: 'Temperature Sensor',
|
||||||
TEMP_SENSORS: 'Temperature Sensors',
|
TEMP_SENSORS: 'Temperature Sensors',
|
||||||
WRITE_CMD_SENT: 'Write command sent',
|
WRITE_CMD_SENT: 'Write command sent',
|
||||||
WRITE_CMD_FAILED: 'Write command failed',
|
|
||||||
EMS_BUS_WARNING: 'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile',
|
EMS_BUS_WARNING: 'EMS bus disconnected. If this warning still persists after a few seconds please check settings and board profile',
|
||||||
EMS_BUS_SCANNING: 'Scanning for EMS devices...',
|
EMS_BUS_SCANNING: 'Scanning for EMS devices...',
|
||||||
CONNECTED: 'Connected',
|
CONNECTED: 'Connected',
|
||||||
@@ -284,7 +282,7 @@ const en: Translation = {
|
|||||||
SCAN_AGAIN: 'Scan again',
|
SCAN_AGAIN: 'Scan again',
|
||||||
NETWORK_SCANNER: 'Network Scanner',
|
NETWORK_SCANNER: 'Network Scanner',
|
||||||
NETWORK_NO_WIFI: 'No WiFi networks found',
|
NETWORK_NO_WIFI: 'No WiFi networks found',
|
||||||
NETWORK_BLANK_SSID: 'leave blank to disable WiFi',
|
NETWORK_BLANK_SSID: 'leave blank to disable WiFi and enable ETH',
|
||||||
TX_POWER: 'Tx Power',
|
TX_POWER: 'Tx Power',
|
||||||
HOSTNAME: 'Hostname',
|
HOSTNAME: 'Hostname',
|
||||||
NETWORK_DISABLE_SLEEP: 'Disable WiFi Sleep Mode',
|
NETWORK_DISABLE_SLEEP: 'Disable WiFi Sleep Mode',
|
||||||
@@ -324,8 +322,8 @@ const en: Translation = {
|
|||||||
ENTITIES_UPDATED: 'Entities Updated',
|
ENTITIES_UPDATED: 'Entities Updated',
|
||||||
WRITEABLE: 'Writeable',
|
WRITEABLE: 'Writeable',
|
||||||
SHOWING: 'Showing',
|
SHOWING: 'Showing',
|
||||||
SEARCH: 'Search'
|
SEARCH: 'Search',
|
||||||
|
CERT: 'TSL root certificate (leave blank to disable TSL)'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default en;
|
export default en;
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ const fr: Translation = {
|
|||||||
REMOVE: 'Enlever',
|
REMOVE: 'Enlever',
|
||||||
PROBLEM_UPDATING: 'Problème lors de la mise à jour',
|
PROBLEM_UPDATING: 'Problème lors de la mise à jour',
|
||||||
PROBLEM_LOADING: 'Problème lors du chargement',
|
PROBLEM_LOADING: 'Problème lors du chargement',
|
||||||
ACCESS_DENIED: 'Accès refusé',
|
|
||||||
ANALOG_SENSOR: 'Capteur analogique',
|
ANALOG_SENSOR: 'Capteur analogique',
|
||||||
ANALOG_SENSORS: 'Capteurs analogiques',
|
ANALOG_SENSORS: 'Capteurs analogiques',
|
||||||
SETTINGS: 'Paramètres',
|
SETTINGS: 'Paramètres',
|
||||||
@@ -71,7 +70,6 @@ const fr: Translation = {
|
|||||||
TEMP_SENSOR: 'Capteur de température',
|
TEMP_SENSOR: 'Capteur de température',
|
||||||
TEMP_SENSORS: 'Capteurs de température',
|
TEMP_SENSORS: 'Capteurs de température',
|
||||||
WRITE_CMD_SENT: 'Envoyer la commande sent', // TODO translate
|
WRITE_CMD_SENT: 'Envoyer la commande sent', // TODO translate
|
||||||
WRITE_CMD_FAILED: 'Envoyer la commande failed', // TODO translate
|
|
||||||
EMS_BUS_WARNING: 'Bus EMS déconnecté. Si ce message persiste après quelques secondes, vérifiez les paramètres et la configuration de la carte.',
|
EMS_BUS_WARNING: 'Bus EMS déconnecté. Si ce message persiste après quelques secondes, vérifiez les paramètres et la configuration de la carte.',
|
||||||
EMS_BUS_SCANNING: 'Scan des appareils EMS...',
|
EMS_BUS_SCANNING: 'Scan des appareils EMS...',
|
||||||
CONNECTED: 'Connecté',
|
CONNECTED: 'Connecté',
|
||||||
@@ -284,7 +282,7 @@ const fr: Translation = {
|
|||||||
SCAN_AGAIN: 'Rescanner',
|
SCAN_AGAIN: 'Rescanner',
|
||||||
NETWORK_SCANNER: 'Scan réseau',
|
NETWORK_SCANNER: 'Scan réseau',
|
||||||
NETWORK_NO_WIFI: 'Pas de réseau WiFi trouvé',
|
NETWORK_NO_WIFI: 'Pas de réseau WiFi trouvé',
|
||||||
NETWORK_BLANK_SSID: 'laisser vide pour désactiver le WiFi',
|
NETWORK_BLANK_SSID: 'laisser vide pour désactiver le WiFi', // and enable ETH // TODO translate
|
||||||
TX_POWER: 'Puissance Tx',
|
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_DISABLE_SLEEP: 'Désactiver le mode veille du WiFi',
|
||||||
@@ -324,7 +322,8 @@ const fr: Translation = {
|
|||||||
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
||||||
WRITEABLE: 'Writeable', // TODO translate
|
WRITEABLE: 'Writeable', // TODO translate
|
||||||
SHOWING: 'Showing', // TODO translate
|
SHOWING: 'Showing', // TODO translate
|
||||||
SEARCH: 'Search' // TODO translate
|
SEARCH: 'Search', // TODO translate
|
||||||
|
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||||
};
|
};
|
||||||
|
|
||||||
export default fr;
|
export default fr;
|
||||||
|
|||||||
331
interface/src/i18n/it/index.ts
Normal file
331
interface/src/i18n/it/index.ts
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
import type { Translation } from '../i18n-types';
|
||||||
|
/* prettier-ignore */
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
const it: Translation = {
|
||||||
|
LANGUAGE: 'Lingua',
|
||||||
|
RETRY: 'Riprovare',
|
||||||
|
LOADING: 'Caricamento',
|
||||||
|
IS_REQUIRED: '{0} é richiesto',
|
||||||
|
SIGN_IN: 'Connettersi',
|
||||||
|
SIGN_OUT: 'Disconnettersi',
|
||||||
|
USERNAME: 'Nome Utente',
|
||||||
|
PASSWORD: 'Password',
|
||||||
|
SU_PASSWORD: 'su Password',
|
||||||
|
DASHBOARD: 'Pannello di Controllo',
|
||||||
|
SETTINGS_OF: 'Impostazioni {0}',
|
||||||
|
HELP_OF: '{0} Aiuto',
|
||||||
|
LOGGED_IN: 'Registrato come {name}',
|
||||||
|
PLEASE_SIGNIN: 'Prego registrarsi per continuare',
|
||||||
|
UPLOAD_SUCCESSFUL: 'Caricamento finito',
|
||||||
|
DOWNLOAD_SUCCESSFUL: 'Scaricamento finito',
|
||||||
|
INVALID_LOGIN: 'Dettagli accesso invalidi',
|
||||||
|
NETWORK: 'Rete',
|
||||||
|
SECURITY: 'Sicurezza',
|
||||||
|
ONOFF_CAP: 'ON/OFF',
|
||||||
|
ONOFF: 'on/off',
|
||||||
|
TYPE: 'Tipo',
|
||||||
|
DESCRIPTION: 'Descrizione',
|
||||||
|
ENTITIES: 'Entità',
|
||||||
|
REFRESH: 'Ricaricare',
|
||||||
|
EXPORT: 'Esporta',
|
||||||
|
DEVICE_DETAILS: 'Dettagli dispositivo',
|
||||||
|
ID_OF: '{0} ID',
|
||||||
|
DEVICE: 'Dispositivo',
|
||||||
|
PRODUCT: 'Prodotto',
|
||||||
|
VERSION: 'Versione',
|
||||||
|
BRAND: 'Marca',
|
||||||
|
ENTITY_NAME: 'Nome Entità',
|
||||||
|
VALUE: '{{Valore|valore}}',
|
||||||
|
DEVICE_DATA: 'Device Data',
|
||||||
|
SENSOR_DATA: 'Sensor Data',
|
||||||
|
DEVICES: 'Dispositivi',
|
||||||
|
SENSORS: 'Sensori',
|
||||||
|
RUN_COMMAND: 'Esegui',
|
||||||
|
CHANGE_VALUE: 'Cambia Valore',
|
||||||
|
CANCEL: 'Annulla',
|
||||||
|
RESET: 'Reset',
|
||||||
|
APPLY_CHANGES: 'Applica Cambiamenti ({0})',
|
||||||
|
UPDATE: 'Update',
|
||||||
|
EXECUTE: 'Execute',
|
||||||
|
REMOVE: 'Elimina',
|
||||||
|
PROBLEM_UPDATING: 'Problema aggiornamento',
|
||||||
|
PROBLEM_LOADING: 'Problema caricamento',
|
||||||
|
ACCESS_DENIED: 'Accesso Negato',
|
||||||
|
ANALOG_SENSOR: 'Sensore Analogico',
|
||||||
|
ANALOG_SENSORS: 'Sensori Analogici',
|
||||||
|
SETTINGS: 'Settings',
|
||||||
|
UPDATED_OF: '{0} Aggiornati',
|
||||||
|
UPDATE_OF: 'Aggiorna {0}',
|
||||||
|
REMOVED_OF: '{0} Rimossi',
|
||||||
|
DELETION_OF: '{0} Cancellati',
|
||||||
|
OFFSET: 'Offset',
|
||||||
|
FACTOR: 'Fattore',
|
||||||
|
FREQ: 'Frequenza',
|
||||||
|
DUTY_CYCLE: 'Ciclo di lavoro',
|
||||||
|
UNIT: 'UoM',
|
||||||
|
STARTVALUE: 'Valore di partenza',
|
||||||
|
WARN_GPIO: 'Avvertimento: prestare attenzione quando si assegna un GPIO!',
|
||||||
|
EDIT: 'Modifica',
|
||||||
|
SENSOR: 'Sensore',
|
||||||
|
TEMP_SENSOR: 'Sensore Temperatura',
|
||||||
|
TEMP_SENSORS: 'Sensori Temperatura',
|
||||||
|
WRITE_CMD_SENT: 'Scrittura comando inviata',
|
||||||
|
WRITE_CMD_FAILED: 'Scittura comando fallita',
|
||||||
|
EMS_BUS_WARNING: 'EMS bus disconnesso. Se questo avvertimento persiste dopo alcuni secondi prego verificare impostazioni scheda',
|
||||||
|
EMS_BUS_SCANNING: 'Scansione dispositivi EMS ...',
|
||||||
|
CONNECTED: 'Connesso',
|
||||||
|
TX_ISSUES: 'Problema di Tx - prova una modalità differente',
|
||||||
|
DISCONNECTED: 'Disconnesso',
|
||||||
|
EMS_SCAN: 'Sei sicuro di voler iniziare una scansione completa del bus EMS ?',
|
||||||
|
EMS_BUS_STATUS: 'Stato Bus EMS',
|
||||||
|
ACTIVE_DEVICES: 'Dispositivi & sensori attivi',
|
||||||
|
EMS_DEVICE: 'Dispositivo EMS ',
|
||||||
|
SUCCESS: 'SUCCESSO',
|
||||||
|
FAIL: 'FALLITO',
|
||||||
|
QUALITY: 'QUALITÂ',
|
||||||
|
SCAN_DEVICES: 'Scansione per nuovi dispositivi',
|
||||||
|
EMS_BUS_STATUS_TITLE: 'Bus EMS & Stato Attività',
|
||||||
|
SCAN: 'Scansione',
|
||||||
|
STATUS_NAMES: [
|
||||||
|
'Telegrammi EMS Ricevuti (Rx)',
|
||||||
|
'EMS Letti (Tx)',
|
||||||
|
'EMS Scritti (Tx)',
|
||||||
|
'Letture Sensori Temperatura',
|
||||||
|
'Letture Sensori Analogici',
|
||||||
|
'Pubblicazioni MQTT',
|
||||||
|
'Chiamate API',
|
||||||
|
'Messaggi Syslog'
|
||||||
|
],
|
||||||
|
NUM_DEVICES: '{num} Dispositivi {{s}}',
|
||||||
|
NUM_TEMP_SENSORS: '{num} Sensori Temperatura {{s}}',
|
||||||
|
NUM_ANALOG_SENSORS: '{num} Sensori Analogici {{s}}',
|
||||||
|
NUM_DAYS: '{num} giorni {{s}}',
|
||||||
|
NUM_SECONDS: '{num} secondi {{s}}',
|
||||||
|
NUM_HOURS: '{num} ore {{s}}',
|
||||||
|
NUM_MINUTES: '{num} minuti {{s}}',
|
||||||
|
APPLICATION_SETTINGS: 'Impostazione Applicazione',
|
||||||
|
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: 'Profilo Scheda',
|
||||||
|
CUSTOM: 'Personalizzazione',
|
||||||
|
GPIO_OF: 'GPIO {0}',
|
||||||
|
BUTTON: 'Pulsante',
|
||||||
|
TEMPERATURE: 'Temperatura',
|
||||||
|
PHY_TYPE: 'Eth PHY Type',
|
||||||
|
DISABLED: 'disattivato',
|
||||||
|
TX_MODE: 'Modo Tx ',
|
||||||
|
HARDWARE: 'Hardware',
|
||||||
|
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||||
|
GENERAL_OPTIONS: 'Opzioni Generali',
|
||||||
|
LANGUAGE_ENTITIES: 'Lingua (per entità dispositivi)',
|
||||||
|
HIDE_LED: 'Nascondi LED',
|
||||||
|
ENABLE_TELNET: 'Abilità la Console Telnet',
|
||||||
|
ENABLE_ANALOG: 'Abilita Sensori Analogici',
|
||||||
|
CONVERT_FAHRENHEIT: 'Converti valori temperatura in Fahrenheit',
|
||||||
|
BYPASS_TOKEN: 'Ignora autorizzazione del token di accesso sulle chiamate API',
|
||||||
|
READONLY: 'Abilita modalità sola-lettura (blocca tutti i comandi di scrittura EMS Tx in uscita)',
|
||||||
|
UNDERCLOCK_CPU: 'Abbassa velocità della CPU',
|
||||||
|
ENABLE_SHOWER_TIMER: 'Abilita timer doccia',
|
||||||
|
ENABLE_SHOWER_ALERT: 'Abilita avviso doccia',
|
||||||
|
TRIGGER_TIME: 'Tempo di avvio',
|
||||||
|
COLD_SHOT_DURATION: 'Durata colpo freddo',
|
||||||
|
FORMATTING_OPTIONS: 'Opzioni di formattazione',
|
||||||
|
BOOLEAN_FORMAT_DASHBOARD: 'Pannello di controllo in formato booleano',
|
||||||
|
BOOLEAN_FORMAT_API: 'Formato booleano API/MQTT',
|
||||||
|
ENUM_FORMAT: 'Enum Format API/MQTT',
|
||||||
|
INDEX: 'Indice',
|
||||||
|
ENABLE_PARASITE: 'Abilita potenza parassita',
|
||||||
|
LOGGING: 'Registrazione',
|
||||||
|
LOG_HEX: 'Registra telegrammi EMS in esadecimale',
|
||||||
|
ENABLE_SYSLOG: 'Abilita Syslog',
|
||||||
|
LOG_LEVEL: 'Livello registrazione',
|
||||||
|
MARK_INTERVAL: 'Segna Intervallo',
|
||||||
|
SECONDS: 'secondi',
|
||||||
|
MINUTES: 'minuti',
|
||||||
|
HOURS: 'ore',
|
||||||
|
RESTART: 'Riavvia',
|
||||||
|
RESTART_TEXT: 'EMS-ESP necessita di essere riavviato per applicare il cambio impostazioni del sistema',
|
||||||
|
RESTART_CONFIRM: 'Sei sicuro di voler riavviare EMS-ESP?',
|
||||||
|
COMMAND: 'Comando',
|
||||||
|
CUSTOMIZATIONS_RESTART: 'Tutte le personalizzazioni sono state rimosse. Riavvio ...',
|
||||||
|
CUSTOMIZATIONS_FULL: 'Le entità selezionate hanno superato il limite. Si prega di salvare in batch',
|
||||||
|
CUSTOMIZATIONS_SAVED: 'Personalizzazioni salvate',
|
||||||
|
CUSTOMIZATIONS_HELP_1: 'Seleziona un dispositivo e personalizza le opzioni delle entità o fai clic per rinominarlo',
|
||||||
|
CUSTOMIZATIONS_HELP_2: 'seleziona come preferito',
|
||||||
|
CUSTOMIZATIONS_HELP_3: 'disabilita azione scrittura',
|
||||||
|
CUSTOMIZATIONS_HELP_4: 'esculdi da MQTT e API',
|
||||||
|
CUSTOMIZATIONS_HELP_5: 'nascondi dal Pannello di controllo',
|
||||||
|
CUSTOMIZATIONS_HELP_6: 'rimuovi dalla memoria',
|
||||||
|
SELECT_DEVICE: 'Seleziona un dispositivo',
|
||||||
|
SET_ALL: 'imposta tutto',
|
||||||
|
OPTIONS: 'Opzioni',
|
||||||
|
NAME: 'Nome',
|
||||||
|
CUSTOMIZATIONS_RESET: 'Sei sicuro di voler rimuovere tutte le personalizzazioni incluse le impostazioni personalizzate dei sensori di temperatura e analogici?',
|
||||||
|
DEVICE_ENTITIES: 'Entità Dispositivo',
|
||||||
|
SUPPORT_INFORMATION: 'Informazioni di Supporto',
|
||||||
|
CLICK_HERE: 'Clicca qui',
|
||||||
|
HELP_INFORMATION_1: 'Visita il wiki online per ottenere istruzioni su come configurare EMS-ESP',
|
||||||
|
HELP_INFORMATION_2: 'Per la chat della community dal vivo unisciti al nostro server Discord',
|
||||||
|
HELP_INFORMATION_3: 'Per richiedere una funzionalità o segnalare un errore',
|
||||||
|
HELP_INFORMATION_4: 'ricordati di scaricare e allegare le informazioni del tuo sistema per una risposta più rapida quando segnali un problema',
|
||||||
|
HELP_INFORMATION_5: 'EMS-ESP è un progetto gratuito e open-source. Supporta il suo sviluppo futuro assegnandogli una stella su Github!',
|
||||||
|
SUPPORT_INFO: 'Info Supporto',
|
||||||
|
UPLOAD: 'Carica',
|
||||||
|
DOWNLOAD: 'Scarica',
|
||||||
|
ABORTED: 'Annullato',
|
||||||
|
FAILED: 'Fallito',
|
||||||
|
SUCCESSFUL: 'Riuscito',
|
||||||
|
SYSTEM: 'Sistema',
|
||||||
|
LOG_OF: 'Registro {0}',
|
||||||
|
STATUS_OF: 'Stato {0}',
|
||||||
|
UPLOAD_DOWNLOAD: 'Caricamento/Scaricamento',
|
||||||
|
VERSION_ON: 'Attualmente stai eseguendo la versione',
|
||||||
|
SYSTEM_APPLY_FIRMWARE: 'per applicare il nuovo firmware',
|
||||||
|
CLOSE: 'Chiudere',
|
||||||
|
USE: 'Usa',
|
||||||
|
FACTORY_RESET: 'Impostazioni di fabbrica',
|
||||||
|
SYSTEM_FACTORY_TEXT: 'Il dispositivo è stato ripristinato alle impostazioni di fabbrica e ora verrà riavviato',
|
||||||
|
SYSTEM_FACTORY_TEXT_DIALOG: 'Sei sicuro di voler ripristinare il dispositivo alle impostazioni di fabbrica??',
|
||||||
|
VERSION_CHECK: 'Verifica Versione',
|
||||||
|
THE_LATEST: 'Ultima',
|
||||||
|
OFFICIAL: 'ufficiale',
|
||||||
|
DEVELOPMENT: 'sviluppo',
|
||||||
|
RELEASE_IS: 'rilascio é',
|
||||||
|
RELEASE_NOTES: 'note rilascio',
|
||||||
|
EMS_ESP_VER: 'Versione EMS-ESP',
|
||||||
|
PLATFORM: 'Dispositivo (Piattaforma / SDK)',
|
||||||
|
UPTIME: 'Tempo di attività del sistema',
|
||||||
|
CPU_FREQ: 'Frequenza CPU ',
|
||||||
|
HEAP: 'Heap (Free / Max Alloc)',
|
||||||
|
PSRAM: 'PSRAM (Size / Free)',
|
||||||
|
FLASH: 'Flash Chip (Size / Speed)',
|
||||||
|
APPSIZE: 'Applicazione (Usata / Libera)',
|
||||||
|
FILESYSTEM: 'Memoria Sistema (Usata / Libera)',
|
||||||
|
BUFFER_SIZE: 'Max Buffer Size',
|
||||||
|
COMPACT: 'Compact',
|
||||||
|
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" ',
|
||||||
|
UPLOADING: 'Caricamento',
|
||||||
|
UPLOAD_DROP_TEXT: 'Trascina il file o clicca qui',
|
||||||
|
ERROR: 'Errore Inaspettato, prego tenta ancora',
|
||||||
|
TIME_SET: 'Imposta Ora',
|
||||||
|
MANAGE_USERS: 'Gestione Utenti',
|
||||||
|
IS_ADMIN: 'Amministratore',
|
||||||
|
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.',
|
||||||
|
GENERATING_TOKEN: 'Generazione token',
|
||||||
|
USER: 'Utente',
|
||||||
|
MODIFY: 'Modifica',
|
||||||
|
SU_TEXT: 'La password su (super utente) viene utilizzata per firmare i token di autenticazione e abilitare anche i privilegi di amministratore all interno della console.',
|
||||||
|
NOT_ENABLED: 'Non abilitato',
|
||||||
|
ERRORS_OF: 'Errori {0}',
|
||||||
|
DISCONNECT_REASON: 'Motivo disconnessione',
|
||||||
|
ENABLE_MQTT: 'Abilita MQTT',
|
||||||
|
BROKER: 'Broker',
|
||||||
|
CLIENT: 'Cliente',
|
||||||
|
BASE_TOPIC: 'Base',
|
||||||
|
OPTIONAL: 'Opzionale',
|
||||||
|
FORMATTING: 'Formattazione',
|
||||||
|
MQTT_FORMAT: 'Formato Topic/Payload ',
|
||||||
|
MQTT_NEST_1: 'Inserito in un singolo argomento',
|
||||||
|
MQTT_NEST_2: 'Come argomenti individuali',
|
||||||
|
MQTT_RESPONSE: 'Pubblica uscita del comando in un argomento di risposta',
|
||||||
|
MQTT_PUBLISH_TEXT_1: 'Pubblica argomenti a valore singolo sul cambiamento',
|
||||||
|
MQTT_PUBLISH_TEXT_2: 'Pubblica per comandare gli argomenti (ioBroker)',
|
||||||
|
MQTT_PUBLISH_TEXT_3: 'Abilita rilevamento MQTT (Home Assistant, Domoticz)',
|
||||||
|
MQTT_PUBLISH_TEXT_4: 'Prefisso per gli argomenti di scoperta',
|
||||||
|
MQTT_PUBLISH_TEXT_5: 'Discovery type',
|
||||||
|
MQTT_PUBLISH_INTERVALS: 'Pubblica intervalli',
|
||||||
|
MQTT_INT_BOILER: 'Caldaie e Pompe di Calore',
|
||||||
|
MQTT_INT_THERMOSTATS: 'Termostati',
|
||||||
|
MQTT_INT_SOLAR: 'Moduli solari',
|
||||||
|
MQTT_INT_MIXER: 'Moduli Mixer',
|
||||||
|
MQTT_INT_HEARTBEAT: 'Heartbeat',
|
||||||
|
MQTT_QUEUE: 'Coda MQTT',
|
||||||
|
DEFAULT: 'Predefinito',
|
||||||
|
MQTT_ENTITY_FORMAT: 'Formato ID entità',
|
||||||
|
MQTT_ENTITY_FORMAT_0: 'Singola istanza, nome lungo (v3.4)',
|
||||||
|
MQTT_ENTITY_FORMAT_1: 'Sinola istanza, nome breve',
|
||||||
|
MQTT_ENTITY_FORMAT_2: 'Istanze multiple, nome breve',
|
||||||
|
MQTT_CLEAN_SESSION: 'Imposta sessione pulita',
|
||||||
|
MQTT_RETAIN_FLAG: 'Imposta sempre il flag Retain',
|
||||||
|
INACTIVE: 'Inattivo',
|
||||||
|
ACTIVE: 'Attivo',
|
||||||
|
UNKNOWN: 'Sconosciuto',
|
||||||
|
SET_TIME: 'Imposta ora',
|
||||||
|
SET_TIME_TEXT: 'Immettere la data e l ora locale di seguito per impostare l ora',
|
||||||
|
LOCAL_TIME: 'Ora locale',
|
||||||
|
UTC_TIME: 'Ora UTC',
|
||||||
|
ENABLE_NTP: 'Abilita NTP',
|
||||||
|
NTP_SERVER: 'Server NTP',
|
||||||
|
TIME_ZONE: 'Fuso orario',
|
||||||
|
ACCESS_POINT: 'Access Point',
|
||||||
|
AP_PROVIDE: 'Abilita Access Point',
|
||||||
|
AP_PROVIDE_TEXT_1: 'sempre',
|
||||||
|
AP_PROVIDE_TEXT_2: 'quando WiFi é disconnessa',
|
||||||
|
AP_PROVIDE_TEXT_3: 'mai',
|
||||||
|
AP_PREFERRED_CHANNEL: 'Canale preferito',
|
||||||
|
AP_HIDE_SSID: 'Nascondi SSID',
|
||||||
|
AP_CLIENTS: 'Clienti AP',
|
||||||
|
AP_MAX_CLIENTS: 'Clienti Massimi',
|
||||||
|
AP_LOCAL_IP: 'IP Locale',
|
||||||
|
NETWORK_SCAN: 'Scansione reti WiFi',
|
||||||
|
IDLE: 'Inattivo',
|
||||||
|
LOST: 'Perso',
|
||||||
|
SCANNING: 'Scansione',
|
||||||
|
SCAN_AGAIN: 'Scansiona ancora',
|
||||||
|
NETWORK_SCANNER: 'Scansione Rete',
|
||||||
|
NETWORK_NO_WIFI: 'Nessuana rete WiFi trovata',
|
||||||
|
NETWORK_BLANK_SSID: 'lasciare vuoto per disattivare WiFi',
|
||||||
|
TX_POWER: 'Potenza Tx',
|
||||||
|
HOSTNAME: 'Nome ospite',
|
||||||
|
NETWORK_DISABLE_SLEEP: 'Disabilita la modalità sospensione Wi-Fi',
|
||||||
|
NETWORK_LOW_BAND: 'Usa una larghezza di banda WiFi inferiore',
|
||||||
|
NETWORK_USE_DNS: 'Abilita servizio mDNS',
|
||||||
|
NETWORK_ENABLE_CORS: 'Abilita CORS',
|
||||||
|
NETWORK_CORS_ORIGIN: 'origine CORS',
|
||||||
|
NETWORK_ENABLE_IPV6: 'Abilita supporto IPv6',
|
||||||
|
NETWORK_FIXED_IP: 'Usa indirizzo IP fisso',
|
||||||
|
NETWORK_GATEWAY: 'Gateway',
|
||||||
|
NETWORK_SUBNET: 'Maschera Sottorete',
|
||||||
|
NETWORK_DNS: 'Server DNS',
|
||||||
|
ADDRESS_OF: 'Indirizzo {0}',
|
||||||
|
ADMIN: 'Amministratore',
|
||||||
|
GUEST: 'Ospite',
|
||||||
|
NEW: 'Nuovo',
|
||||||
|
NEW_NAME_OF: 'Nuovo nome {0}',
|
||||||
|
ENTITY: 'entità',
|
||||||
|
MIN: 'min',
|
||||||
|
MAX: 'max',
|
||||||
|
BLOCK_NAVIGATE_1: 'Hai modifiche non salvate',
|
||||||
|
BLOCK_NAVIGATE_2: 'Se passi a una pagina diversa, le modifiche non salvate andranno perse. Sei sicuro di voler lasciare questa pagina?',
|
||||||
|
STAY: 'Stai',
|
||||||
|
LEAVE: 'Esci',
|
||||||
|
SCHEDULER: 'Programma eventi',
|
||||||
|
SCHEDULER_HELP_1: "Automatizza i comandi aggiungendo gli eventi programmati di seguito. Imposta un nome univoco per abilitare/disabilitare l'attivazione tramite API/MQTT.",
|
||||||
|
SCHEDULER_HELP_2: "per attivare una volta all'avvio",
|
||||||
|
SCHEDULE: 'Programma',
|
||||||
|
TIME: 'Ora',
|
||||||
|
TIMER: 'Orologio',
|
||||||
|
SCHEDULE_UPDATED: 'Calendario aggiornato',
|
||||||
|
SCHEDULE_TIMER_1: 'All avvio',
|
||||||
|
SCHEDULE_TIMER_2: 'Ogni minuto',
|
||||||
|
SCHEDULE_TIMER_3: 'Ogni ora',
|
||||||
|
CUSTOM_ENTITIES: 'Entità personalizzate',
|
||||||
|
ENTITIES_HELP_1: 'Recupera entità personalizzate dal BUS EMS',
|
||||||
|
ENTITIES_UPDATED: 'Entità aggiornate',
|
||||||
|
WRITEABLE: 'Scrivibile',
|
||||||
|
SHOWING: 'Visualizza',
|
||||||
|
SEARCH: 'Ricerca',
|
||||||
|
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||||
|
};
|
||||||
|
|
||||||
|
export default it;
|
||||||
@@ -45,13 +45,12 @@ const nl: Translation = {
|
|||||||
CHANGE_VALUE: 'Wijzig waarde',
|
CHANGE_VALUE: 'Wijzig waarde',
|
||||||
CANCEL: 'Annuleren',
|
CANCEL: 'Annuleren',
|
||||||
RESET: 'Reset',
|
RESET: 'Reset',
|
||||||
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
|
APPLY_CHANGES: 'Aanpassen ({0})',
|
||||||
UPDATE: 'Update', // TODO translate
|
UPDATE: 'Update',
|
||||||
EXECUTE: 'Execute', // TODO translate
|
EXECUTE: 'Uitvoeren',
|
||||||
REMOVE: 'Verwijderen',
|
REMOVE: 'Verwijderen',
|
||||||
PROBLEM_UPDATING: 'Probleem met updaten',
|
PROBLEM_UPDATING: 'Probleem met updaten',
|
||||||
PROBLEM_LOADING: 'Probleem met laden',
|
PROBLEM_LOADING: 'Probleem met laden',
|
||||||
ACCESS_DENIED: 'Toegang geweigerd',
|
|
||||||
ANALOG_SENSOR: 'Analoge sensor',
|
ANALOG_SENSOR: 'Analoge sensor',
|
||||||
ANALOG_SENSORS: 'Analoge Sensoren',
|
ANALOG_SENSORS: 'Analoge Sensoren',
|
||||||
SETTINGS: 'Instellingen',
|
SETTINGS: 'Instellingen',
|
||||||
@@ -70,8 +69,7 @@ const nl: Translation = {
|
|||||||
SENSOR: 'Sensor',
|
SENSOR: 'Sensor',
|
||||||
TEMP_SENSOR: 'Temperatuur sensor',
|
TEMP_SENSOR: 'Temperatuur sensor',
|
||||||
TEMP_SENSORS: 'Temperatuur Sensoren',
|
TEMP_SENSORS: 'Temperatuur Sensoren',
|
||||||
WRITE_CMD_SENT: 'Schrijf commando sent', // TODO translate
|
WRITE_CMD_SENT: 'Schrijf commando gestuurd',
|
||||||
WRITE_CMD_FAILED: 'Schrijf commando failed', // TODO translate
|
|
||||||
EMS_BUS_WARNING: 'EMS bus niet gevonden. Als deze waarschuwing blijft staan na een paar seconden dan loop de instellingen na en in het bijzonder het apparaat type profiel na.',
|
EMS_BUS_WARNING: 'EMS bus niet gevonden. Als deze waarschuwing blijft staan na een paar seconden dan loop de instellingen na en in het bijzonder het apparaat type profiel na.',
|
||||||
EMS_BUS_SCANNING: 'Scannen naar EMS apparaten...',
|
EMS_BUS_SCANNING: 'Scannen naar EMS apparaten...',
|
||||||
CONNECTED: 'Verbonden',
|
CONNECTED: 'Verbonden',
|
||||||
@@ -158,7 +156,7 @@ const nl: Translation = {
|
|||||||
CUSTOMIZATIONS_HELP_3: 'Zet schrijfacties uit',
|
CUSTOMIZATIONS_HELP_3: 'Zet schrijfacties uit',
|
||||||
CUSTOMIZATIONS_HELP_4: 'Uitsluiten van MQTT en API',
|
CUSTOMIZATIONS_HELP_4: 'Uitsluiten van MQTT en API',
|
||||||
CUSTOMIZATIONS_HELP_5: 'verberg van het Dashboard',
|
CUSTOMIZATIONS_HELP_5: 'verberg van het Dashboard',
|
||||||
CUSTOMIZATIONS_HELP_6: 'remove from memory', // TODO translate
|
CUSTOMIZATIONS_HELP_6: 'verwijderen van memory',
|
||||||
SELECT_DEVICE: 'Selecteer een apparaat',
|
SELECT_DEVICE: 'Selecteer een apparaat',
|
||||||
SET_ALL: 'Alles aanzetten',
|
SET_ALL: 'Alles aanzetten',
|
||||||
OPTIONS: 'Opties',
|
OPTIONS: 'Opties',
|
||||||
@@ -182,7 +180,7 @@ const nl: Translation = {
|
|||||||
LOG_OF: '{0} Log',
|
LOG_OF: '{0} Log',
|
||||||
STATUS_OF: '{0} Status',
|
STATUS_OF: '{0} Status',
|
||||||
UPLOAD_DOWNLOAD: 'Upload/Download',
|
UPLOAD_DOWNLOAD: 'Upload/Download',
|
||||||
VERSION_ON: 'You are currently on', // TODO translate
|
VERSION_ON: 'U bevindt zich momenteel op',
|
||||||
SYSTEM_APPLY_FIRMWARE: 'om de nieuwe firmware te activeren',
|
SYSTEM_APPLY_FIRMWARE: 'om de nieuwe firmware te activeren',
|
||||||
CLOSE: 'Sluiten',
|
CLOSE: 'Sluiten',
|
||||||
USE: 'Gebruik',
|
USE: 'Gebruik',
|
||||||
@@ -208,7 +206,7 @@ const nl: Translation = {
|
|||||||
COMPACT: 'Compact',
|
COMPACT: 'Compact',
|
||||||
ENABLE_OTA: 'Acitveer OTA Updates',
|
ENABLE_OTA: 'Acitveer OTA Updates',
|
||||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download alle custom instellingen',
|
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download alle custom instellingen',
|
||||||
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events', // TODO translate
|
DOWNLOAD_SCHEDULE_TEXT: 'Download Scheduler Events',
|
||||||
DOWNLOAD_SETTINGS_TEXT: 'Download de applicatie settings. Wees voorzichting met het delen van dit bestand want het bevat o.a. de wachtwoorden in plain text',
|
DOWNLOAD_SETTINGS_TEXT: 'Download de applicatie settings. Wees voorzichting met het delen van dit bestand want het bevat o.a. de wachtwoorden in plain text',
|
||||||
UPLOAD_TEXT: 'Upload een nieuwe firmware (.bin) file, instellingen of custom instellingen (.json) bestand hieronder',
|
UPLOAD_TEXT: 'Upload een nieuwe firmware (.bin) file, instellingen of custom instellingen (.json) bestand hieronder',
|
||||||
UPLOADING: 'Uploading',
|
UPLOADING: 'Uploading',
|
||||||
@@ -242,7 +240,7 @@ const nl: Translation = {
|
|||||||
MQTT_PUBLISH_TEXT_2: 'Publiceer naar commando topics (ioBroker)',
|
MQTT_PUBLISH_TEXT_2: 'Publiceer naar commando topics (ioBroker)',
|
||||||
MQTT_PUBLISH_TEXT_3: 'Activeer MQTT Discovery',
|
MQTT_PUBLISH_TEXT_3: 'Activeer MQTT Discovery',
|
||||||
MQTT_PUBLISH_TEXT_4: 'Prefix voor de Discovery topics',
|
MQTT_PUBLISH_TEXT_4: 'Prefix voor de Discovery topics',
|
||||||
MQTT_PUBLISH_TEXT_5: 'Discovery type', // TODO translate
|
MQTT_PUBLISH_TEXT_5: 'Discovery type',
|
||||||
MQTT_PUBLISH_INTERVALS: 'Publicatie intervallen',
|
MQTT_PUBLISH_INTERVALS: 'Publicatie intervallen',
|
||||||
MQTT_INT_BOILER: 'CV ketels en warmtepompen',
|
MQTT_INT_BOILER: 'CV ketels en warmtepompen',
|
||||||
MQTT_INT_THERMOSTATS: 'Thermostaten',
|
MQTT_INT_THERMOSTATS: 'Thermostaten',
|
||||||
@@ -251,10 +249,10 @@ const nl: Translation = {
|
|||||||
MQTT_INT_HEARTBEAT: 'Heartbeat',
|
MQTT_INT_HEARTBEAT: 'Heartbeat',
|
||||||
MQTT_QUEUE: 'MQTT Queue',
|
MQTT_QUEUE: 'MQTT Queue',
|
||||||
DEFAULT: 'Default',
|
DEFAULT: 'Default',
|
||||||
MQTT_ENTITY_FORMAT: 'Entity ID format', // TODO translate
|
MQTT_ENTITY_FORMAT: 'Entity ID formaat',
|
||||||
MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)', // TODO translate
|
MQTT_ENTITY_FORMAT_0: 'Eén instantie, lange naam (v3.4)',
|
||||||
MQTT_ENTITY_FORMAT_1: 'Single instance, short name', // TODO translate
|
MQTT_ENTITY_FORMAT_1: 'Eén instantie, korte naam',
|
||||||
MQTT_ENTITY_FORMAT_2: 'Multiple instances, short name', // TODO translate
|
MQTT_ENTITY_FORMAT_2: 'Meerdere instanties, korte naam',
|
||||||
MQTT_CLEAN_SESSION: 'Clean Session aan',
|
MQTT_CLEAN_SESSION: 'Clean Session aan',
|
||||||
MQTT_RETAIN_FLAG: 'Retain flag aan',
|
MQTT_RETAIN_FLAG: 'Retain flag aan',
|
||||||
INACTIVE: 'Inactief',
|
INACTIVE: 'Inactief',
|
||||||
@@ -284,7 +282,7 @@ const nl: Translation = {
|
|||||||
SCAN_AGAIN: 'Opnieuw scannen',
|
SCAN_AGAIN: 'Opnieuw scannen',
|
||||||
NETWORK_SCANNER: 'Netwerk Scanner',
|
NETWORK_SCANNER: 'Netwerk Scanner',
|
||||||
NETWORK_NO_WIFI: 'Geen WiFi networken gevonden',
|
NETWORK_NO_WIFI: 'Geen WiFi networken gevonden',
|
||||||
NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen',
|
NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen', // and enable ETH // TODO translate
|
||||||
TX_POWER: 'Tx Vermogen',
|
TX_POWER: 'Tx Vermogen',
|
||||||
HOSTNAME: 'Hostname',
|
HOSTNAME: 'Hostname',
|
||||||
NETWORK_DISABLE_SLEEP: 'WiFi Sleep Mode uitzetten',
|
NETWORK_DISABLE_SLEEP: 'WiFi Sleep Mode uitzetten',
|
||||||
@@ -305,27 +303,27 @@ const nl: Translation = {
|
|||||||
ENTITY: 'Entiteit',
|
ENTITY: 'Entiteit',
|
||||||
MIN: 'min',
|
MIN: 'min',
|
||||||
MAX: 'max',
|
MAX: 'max',
|
||||||
BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate
|
BLOCK_NAVIGATE_1: 'U hebt niet-opgeslagen wijzigingen',
|
||||||
BLOCK_NAVIGATE_2: 'If you navigate to a different page, your unsaved changes will be lost. Are you sure you want to leave this page?', // TODO translate
|
BLOCK_NAVIGATE_2: 'Als u naar een andere pagina navigeert, gaan uw niet-opgeslagen wijzigingen verloren. Weet je zeker dat je deze pagina wilt verlaten?',
|
||||||
STAY: 'Stay', // TODO translate
|
STAY: 'Blijven',
|
||||||
LEAVE: 'Leave', // TODO translate
|
LEAVE: 'Verlaten',
|
||||||
SCHEDULER: 'Scheduler', // TODO translate
|
SCHEDULER: 'Scheduler',
|
||||||
SCHEDULER_HELP_1: 'Automate commands by adding scheduled events below. Set a unique Name to enable/disable activation via API/MQTT.', // TODO translate
|
SCHEDULER_HELP_1: 'Automate commands by adding scheduled events below. Set a unique Name to enable/disable activation via API/MQTT.',
|
||||||
SCHEDULER_HELP_2: 'Use 00:00 to trigger once on start-up', // TODO translate
|
SCHEDULER_HELP_2: 'Gebruik 00:00 om eenmaal te activeren bij het opstarten',
|
||||||
SCHEDULE: 'Schedule', // TODO translate
|
SCHEDULE: 'Schedule',
|
||||||
TIME: 'Time', // TODO translate
|
TIME: 'Tijd',
|
||||||
TIMER: 'Timer', // TODO translate
|
TIMER: 'Timer',
|
||||||
SCHEDULE_UPDATED: 'Schedule updated', // TODO translate
|
SCHEDULE_UPDATED: 'Schema bijgewerkt',
|
||||||
SCHEDULE_TIMER_1: 'on startup', // TODO translate
|
SCHEDULE_TIMER_1: 'bij het opstarten',
|
||||||
SCHEDULE_TIMER_2: 'every minute', // TODO translate
|
SCHEDULE_TIMER_2: 'elke minuut',
|
||||||
SCHEDULE_TIMER_3: 'every hour', // TODO translate
|
SCHEDULE_TIMER_3: 'elke huur',
|
||||||
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
|
CUSTOM_ENTITIES: 'Aangepaste Entiteiten',
|
||||||
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
|
ENTITIES_HELP_1: 'Aangepaste entiteiten ophalen uit de EMS-bus',
|
||||||
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
ENTITIES_UPDATED: 'Entiteiten bijgewerkt',
|
||||||
WRITEABLE: 'Writeable', // TODO translate
|
WRITEABLE: 'Beschrijfbare',
|
||||||
SHOWING: 'Showing', // TODO translate
|
SHOWING: 'Tonen',
|
||||||
SEARCH: 'Zoek'
|
SEARCH: 'Zoek',
|
||||||
|
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nl;
|
export default nl;
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ const no: Translation = {
|
|||||||
REMOVE: 'Fjern',
|
REMOVE: 'Fjern',
|
||||||
PROBLEM_UPDATING: 'Problem med oppdatering',
|
PROBLEM_UPDATING: 'Problem med oppdatering',
|
||||||
PROBLEM_LOADING: 'Problem med opplasting',
|
PROBLEM_LOADING: 'Problem med opplasting',
|
||||||
ACCESS_DENIED: 'Tilgang nektet',
|
|
||||||
ANALOG_SENSOR: 'Analog Sensor',
|
ANALOG_SENSOR: 'Analog Sensor',
|
||||||
ANALOG_SENSORS: 'Analoge Sensorer',
|
ANALOG_SENSORS: 'Analoge Sensorer',
|
||||||
SETTINGS: 'Innstillinger',
|
SETTINGS: 'Innstillinger',
|
||||||
@@ -71,7 +70,6 @@ const no: Translation = {
|
|||||||
TEMP_SENSOR: 'Temperatursensor',
|
TEMP_SENSOR: 'Temperatursensor',
|
||||||
TEMP_SENSORS: 'Temperaturesensorer',
|
TEMP_SENSORS: 'Temperaturesensorer',
|
||||||
WRITE_CMD_SENT: 'Skriv kommando sent',
|
WRITE_CMD_SENT: 'Skriv kommando sent',
|
||||||
WRITE_CMD_FAILED: 'Skriv kommando som har feilet',
|
|
||||||
EMS_BUS_WARNING: 'EMS bussen koblet ned. Hvis denne advarselen fortsetter etter noen f¨sekunder sjekk instillinger og prosessorkort',
|
EMS_BUS_WARNING: 'EMS bussen koblet ned. Hvis denne advarselen fortsetter etter noen f¨sekunder sjekk instillinger og prosessorkort',
|
||||||
EMS_BUS_SCANNING: 'Søker etter EMS enheter...',
|
EMS_BUS_SCANNING: 'Søker etter EMS enheter...',
|
||||||
CONNECTED: 'Tilkoblet',
|
CONNECTED: 'Tilkoblet',
|
||||||
@@ -284,7 +282,7 @@ const no: Translation = {
|
|||||||
SCAN_AGAIN: 'Søk igjen',
|
SCAN_AGAIN: 'Søk igjen',
|
||||||
NETWORK_SCANNER: 'Nettverk Scanner',
|
NETWORK_SCANNER: 'Nettverk Scanner',
|
||||||
NETWORK_NO_WIFI: 'Ingen trådløse nett funnet',
|
NETWORK_NO_WIFI: 'Ingen trådløse nett funnet',
|
||||||
NETWORK_BLANK_SSID: 'la feltet være blankt for å deaktivisere trådløst nettverk',
|
NETWORK_BLANK_SSID: 'la feltet være blankt for å deaktivisere trådløst nettverk', // TODO translate
|
||||||
TX_POWER: 'Tx Effekt',
|
TX_POWER: 'Tx Effekt',
|
||||||
HOSTNAME: 'Hostname',
|
HOSTNAME: 'Hostname',
|
||||||
NETWORK_DISABLE_SLEEP: 'Hindre at trådløst nettverk går i Sleep Mode',
|
NETWORK_DISABLE_SLEEP: 'Hindre at trådløst nettverk går i Sleep Mode',
|
||||||
@@ -324,8 +322,8 @@ const no: Translation = {
|
|||||||
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
||||||
WRITEABLE: 'Writeable', // TODO translate
|
WRITEABLE: 'Writeable', // TODO translate
|
||||||
SHOWING: 'Showing', // TODO translate
|
SHOWING: 'Showing', // TODO translate
|
||||||
SEARCH: 'Search' // TODO translate
|
SEARCH: 'Search', // TODO translate
|
||||||
|
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||||
};
|
};
|
||||||
|
|
||||||
export default no;
|
export default no;
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ const pl: BaseTranslation = {
|
|||||||
REMOVE: 'Usuń',
|
REMOVE: 'Usuń',
|
||||||
PROBLEM_UPDATING: 'Problem z uaktualnieniem!',
|
PROBLEM_UPDATING: 'Problem z uaktualnieniem!',
|
||||||
PROBLEM_LOADING: 'Problem z załadowaniem!',
|
PROBLEM_LOADING: 'Problem z załadowaniem!',
|
||||||
ACCESS_DENIED: 'Brak dostępu!',
|
|
||||||
ANALOG_SENSOR: '{{u|u||ustawienia u|ustawień u}}rządzeni{{a podłączonego do EMS-ESP|e||a podłączonego do EMS-ESP|a podłączonego do EMS-ESP}}',
|
ANALOG_SENSOR: '{{u|u||ustawienia u|ustawień u}}rządzeni{{a podłączonego do EMS-ESP|e||a podłączonego do EMS-ESP|a podłączonego do EMS-ESP}}',
|
||||||
ANALOG_SENSORS: 'Urządzenia podłączone do EMS-ESP',
|
ANALOG_SENSORS: 'Urządzenia podłączone do EMS-ESP',
|
||||||
SETTINGS: 'ustawienia',
|
SETTINGS: 'ustawienia',
|
||||||
@@ -71,7 +70,6 @@ const pl: BaseTranslation = {
|
|||||||
TEMP_SENSOR: 'czujnika temperatury',
|
TEMP_SENSOR: 'czujnika temperatury',
|
||||||
TEMP_SENSORS: 'Czujniki temperatury 1-Wire®',
|
TEMP_SENSORS: 'Czujniki temperatury 1-Wire®',
|
||||||
WRITE_CMD_SENT: 'Komenda zapisu została wysłana.',
|
WRITE_CMD_SENT: 'Komenda zapisu została wysłana.',
|
||||||
WRITE_CMD_FAILED: 'Komenda zapisu nie powiodła się!',
|
|
||||||
EMS_BUS_WARNING: 'Brak połączenia z magistralą EMS. Jeśli ten błąd występuje dłużej niż kilka sekund, sprawdź ustawienia oraz profil płytki interfejsu.',
|
EMS_BUS_WARNING: 'Brak połączenia z magistralą EMS. Jeśli ten błąd występuje dłużej niż kilka sekund, sprawdź ustawienia oraz profil płytki interfejsu.',
|
||||||
EMS_BUS_SCANNING: 'Trwa skanowanie urządzeń na magistrali EMS...',
|
EMS_BUS_SCANNING: 'Trwa skanowanie urządzeń na magistrali EMS...',
|
||||||
CONNECTED: '{{połączono|połączenie|}}',
|
CONNECTED: '{{połączono|połączenie|}}',
|
||||||
@@ -284,7 +282,7 @@ const pl: BaseTranslation = {
|
|||||||
SCAN_AGAIN: 'Skanuj ponownie',
|
SCAN_AGAIN: 'Skanuj ponownie',
|
||||||
NETWORK_SCANNER: 'Skaner sieci WiFi',
|
NETWORK_SCANNER: 'Skaner sieci WiFi',
|
||||||
NETWORK_NO_WIFI: 'Brak sieci WiFi w zasięgu',
|
NETWORK_NO_WIFI: 'Brak sieci WiFi w zasięgu',
|
||||||
NETWORK_BLANK_SSID: 'pozostaw puste aby wyłączyć WiFi',
|
NETWORK_BLANK_SSID: 'pozostaw puste aby wyłączyć WiFi', // and enable ETH // TODO translate
|
||||||
TX_POWER: 'Moc nadawania',
|
TX_POWER: 'Moc nadawania',
|
||||||
HOSTNAME: 'Nazwa w sieci',
|
HOSTNAME: 'Nazwa w sieci',
|
||||||
NETWORK_DISABLE_SLEEP: 'Wyłącz tryb uśpienia WiFi',
|
NETWORK_DISABLE_SLEEP: 'Wyłącz tryb uśpienia WiFi',
|
||||||
@@ -324,8 +322,8 @@ const pl: BaseTranslation = {
|
|||||||
ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.',
|
ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.',
|
||||||
WRITEABLE: 'zapisywalna',
|
WRITEABLE: 'zapisywalna',
|
||||||
SHOWING: 'Wyświetlane',
|
SHOWING: 'Wyświetlane',
|
||||||
SEARCH: 'Szukaj'
|
SEARCH: 'Szukaj',
|
||||||
|
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||||
};
|
};
|
||||||
|
|
||||||
export default pl;
|
export default pl;
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ const sv: Translation = {
|
|||||||
REMOVE: 'Ta bort',
|
REMOVE: 'Ta bort',
|
||||||
PROBLEM_UPDATING: 'Problem vid uppdatering',
|
PROBLEM_UPDATING: 'Problem vid uppdatering',
|
||||||
PROBLEM_LOADING: 'Problem vid hämtning',
|
PROBLEM_LOADING: 'Problem vid hämtning',
|
||||||
ACCESS_DENIED: 'Åtkomst Nekad',
|
|
||||||
ANALOG_SENSOR: 'Analog Sensor',
|
ANALOG_SENSOR: 'Analog Sensor',
|
||||||
ANALOG_SENSORS: 'Analoga Sensorer',
|
ANALOG_SENSORS: 'Analoga Sensorer',
|
||||||
SETTINGS: 'Inställningar',
|
SETTINGS: 'Inställningar',
|
||||||
@@ -71,7 +70,6 @@ const sv: Translation = {
|
|||||||
TEMP_SENSOR: 'Temperatursensor',
|
TEMP_SENSOR: 'Temperatursensor',
|
||||||
TEMP_SENSORS: 'Temperatursensorer',
|
TEMP_SENSORS: 'Temperatursensorer',
|
||||||
WRITE_CMD_SENT: 'Skrivkommandon skickade',
|
WRITE_CMD_SENT: 'Skrivkommandon skickade',
|
||||||
WRITE_CMD_FAILED: 'Skrivkommandon misslyckade',
|
|
||||||
EMS_BUS_WARNING: 'EMS-buss nedkopplad. Om denna varning kvarstår efter några sekunder, kontrollera inställningar och enhets-profil.',
|
EMS_BUS_WARNING: 'EMS-buss nedkopplad. Om denna varning kvarstår efter några sekunder, kontrollera inställningar och enhets-profil.',
|
||||||
EMS_BUS_SCANNING: 'Söker efter EMS-enheter...',
|
EMS_BUS_SCANNING: 'Söker efter EMS-enheter...',
|
||||||
CONNECTED: 'Ansluten',
|
CONNECTED: 'Ansluten',
|
||||||
@@ -284,7 +282,7 @@ const sv: Translation = {
|
|||||||
SCAN_AGAIN: 'Sök igen',
|
SCAN_AGAIN: 'Sök igen',
|
||||||
NETWORK_SCANNER: 'Hittade nätverk',
|
NETWORK_SCANNER: 'Hittade nätverk',
|
||||||
NETWORK_NO_WIFI: 'Inga WiFi-nätverk hittades',
|
NETWORK_NO_WIFI: 'Inga WiFi-nätverk hittades',
|
||||||
NETWORK_BLANK_SSID: 'lämna blankt för att inaktivera WiFi',
|
NETWORK_BLANK_SSID: 'lämna blankt för att inaktivera WiFi', // and enable ETH // TODO translate
|
||||||
TX_POWER: 'Tx Effekt',
|
TX_POWER: 'Tx Effekt',
|
||||||
HOSTNAME: 'Värdnamn',
|
HOSTNAME: 'Värdnamn',
|
||||||
NETWORK_DISABLE_SLEEP: 'Inaktivera sömnläge',
|
NETWORK_DISABLE_SLEEP: 'Inaktivera sömnläge',
|
||||||
@@ -324,8 +322,8 @@ const sv: Translation = {
|
|||||||
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
||||||
WRITEABLE: 'Writeable', // TODO translate
|
WRITEABLE: 'Writeable', // TODO translate
|
||||||
SHOWING: 'Showing', // TODO translate
|
SHOWING: 'Showing', // TODO translate
|
||||||
SEARCH: 'Search' // TODO translate
|
SEARCH: 'Search', // TODO translate
|
||||||
|
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||||
};
|
};
|
||||||
|
|
||||||
export default sv;
|
export default sv;
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ const tr: Translation = {
|
|||||||
REMOVE: 'Kaldır',
|
REMOVE: 'Kaldır',
|
||||||
PROBLEM_UPDATING: 'Güncelleme Sorunu',
|
PROBLEM_UPDATING: 'Güncelleme Sorunu',
|
||||||
PROBLEM_LOADING: 'Yükleme Sorunu',
|
PROBLEM_LOADING: 'Yükleme Sorunu',
|
||||||
ACCESS_DENIED: 'Erişim Reddedildi',
|
|
||||||
ANALOG_SENSOR: 'Analog Sensör',
|
ANALOG_SENSOR: 'Analog Sensör',
|
||||||
ANALOG_SENSORS: 'Analog Sensörler',
|
ANALOG_SENSORS: 'Analog Sensörler',
|
||||||
SETTINGS: 'Ayarlar',
|
SETTINGS: 'Ayarlar',
|
||||||
@@ -71,7 +70,6 @@ const tr: Translation = {
|
|||||||
TEMP_SENSOR: 'Sıcaklık Sensörü',
|
TEMP_SENSOR: 'Sıcaklık Sensörü',
|
||||||
TEMP_SENSORS: 'Sıcaklık Sensörleri',
|
TEMP_SENSORS: 'Sıcaklık Sensörleri',
|
||||||
WRITE_CMD_SENT: 'Yazma komutu gönderildi',
|
WRITE_CMD_SENT: 'Yazma komutu gönderildi',
|
||||||
WRITE_CMD_FAILED: 'Yazma komutu başarısız oldu',
|
|
||||||
EMS_BUS_WARNING: 'EMS hat bağlantısı kesildi. Eğer bu uyarı birkaç saniye sonra devam ediyorsa lütfen ayarları ve kart tipini kontrol edin',
|
EMS_BUS_WARNING: 'EMS hat bağlantısı kesildi. Eğer bu uyarı birkaç saniye sonra devam ediyorsa lütfen ayarları ve kart tipini kontrol edin',
|
||||||
EMS_BUS_SCANNING: 'EMS cihazları aranıyor...',
|
EMS_BUS_SCANNING: 'EMS cihazları aranıyor...',
|
||||||
CONNECTED: 'Bağlandı',
|
CONNECTED: 'Bağlandı',
|
||||||
@@ -284,7 +282,7 @@ const tr: Translation = {
|
|||||||
SCAN_AGAIN: 'Tekrar tara',
|
SCAN_AGAIN: 'Tekrar tara',
|
||||||
NETWORK_SCANNER: 'Ağ Tarayıcısı',
|
NETWORK_SCANNER: 'Ağ Tarayıcısı',
|
||||||
NETWORK_NO_WIFI: 'Hiçbir Kablosuz Ağ bulunamadı',
|
NETWORK_NO_WIFI: 'Hiçbir Kablosuz Ağ bulunamadı',
|
||||||
NETWORK_BLANK_SSID: 'Kablosuz ağı devre dışı bırakmak için boş bırakın',
|
NETWORK_BLANK_SSID: 'Kablosuz ağı devre dışı bırakmak için boş bırakın', // TODO translate
|
||||||
TX_POWER: 'Aktarım gücü',
|
TX_POWER: 'Aktarım gücü',
|
||||||
HOSTNAME: 'Ana Makine Adı',
|
HOSTNAME: 'Ana Makine Adı',
|
||||||
NETWORK_DISABLE_SLEEP: 'Kablosuz uyku modunu devre dışına al',
|
NETWORK_DISABLE_SLEEP: 'Kablosuz uyku modunu devre dışına al',
|
||||||
@@ -324,8 +322,8 @@ const tr: Translation = {
|
|||||||
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
|
||||||
WRITEABLE: 'Writeable', // TODO translate
|
WRITEABLE: 'Writeable', // TODO translate
|
||||||
SHOWING: 'Showing', // TODO translate
|
SHOWING: 'Showing', // TODO translate
|
||||||
SEARCH: 'Search' // TODO translate
|
SEARCH: 'Search', // TODO translate
|
||||||
|
CERT: 'TSL root certificate (leave blank to disable TSL)' // TODO translate
|
||||||
};
|
};
|
||||||
|
|
||||||
export default tr;
|
export default tr;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { useRowSelect } from '@table-library/react-table-library/select';
|
|||||||
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
|
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
|
||||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||||
import { useTheme } from '@table-library/react-table-library/theme';
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
|
import { useRequest } from 'alova';
|
||||||
import { useState, useContext, useEffect, useCallback, useLayoutEffect } from 'react';
|
import { useState, useContext, useEffect, useCallback, useLayoutEffect } from 'react';
|
||||||
|
|
||||||
import { IconContext } from 'react-icons';
|
import { IconContext } from 'react-icons';
|
||||||
@@ -42,27 +43,39 @@ import { formatValue } from './deviceValue';
|
|||||||
|
|
||||||
import { DeviceValueUOM_s, DeviceEntityMask, DeviceType } from './types';
|
import { DeviceValueUOM_s, DeviceEntityMask, DeviceType } from './types';
|
||||||
import { deviceValueItemValidation } from './validators';
|
import { deviceValueItemValidation } from './validators';
|
||||||
import type { Device, CoreData, DeviceData, DeviceValue } from './types';
|
import type { Device, DeviceValue } from './types';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { ButtonRow, SectionContent, MessageBox } from 'components';
|
import { ButtonRow, SectionContent, MessageBox } from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
const DashboardDevices: FC = () => {
|
const DashboardDevices: FC = () => {
|
||||||
const [size, setSize] = useState([0, 0]);
|
const [size, setSize] = useState([0, 0]);
|
||||||
const { me } = useContext(AuthenticatedContext);
|
const { me } = useContext(AuthenticatedContext);
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const [deviceData, setDeviceData] = useState<DeviceData>({ data: [] });
|
|
||||||
const [selectedDeviceValue, setSelectedDeviceValue] = useState<DeviceValue>();
|
const [selectedDeviceValue, setSelectedDeviceValue] = useState<DeviceValue>();
|
||||||
const [onlyFav, setOnlyFav] = useState(false);
|
const [onlyFav, setOnlyFav] = useState(false);
|
||||||
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false);
|
const [deviceValueDialogOpen, setDeviceValueDialogOpen] = useState(false);
|
||||||
const [showDeviceInfo, setShowDeviceInfo] = useState<boolean>(false);
|
const [showDeviceInfo, setShowDeviceInfo] = useState<boolean>(false);
|
||||||
const [selectedDevice, setSelectedDevice] = useState<number>();
|
const [selectedDevice, setSelectedDevice] = useState<number>();
|
||||||
const [coreData, setCoreData] = useState<CoreData>({
|
|
||||||
connected: true,
|
const { data: coreData, send: readCoreData } = useRequest(() => EMSESP.readCoreData(), {
|
||||||
devices: []
|
initialData: {
|
||||||
|
connected: true,
|
||||||
|
devices: []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: deviceData, send: readDeviceData } = useRequest((id) => EMSESP.readDeviceData(id), {
|
||||||
|
initialData: {
|
||||||
|
data: []
|
||||||
|
},
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const { loading: submitting, send: writeDeviceValue } = useRequest((data) => EMSESP.writeDeviceValue(data), {
|
||||||
|
immediate: false
|
||||||
});
|
});
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@@ -148,7 +161,7 @@ const DashboardDevices: FC = () => {
|
|||||||
common_theme,
|
common_theme,
|
||||||
{
|
{
|
||||||
Table: `
|
Table: `
|
||||||
--data-table-library_grid-template-columns: minmax(0, 1fr) minmax(150px, auto) 40px;
|
--data-table-library_grid-template-columns: minmax(200px, auto) minmax(150px, auto) 40px;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
@@ -212,19 +225,10 @@ const DashboardDevices: FC = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchDeviceData = async (id: number) => {
|
async function onSelectChange(action: any, state: any) {
|
||||||
try {
|
|
||||||
setDeviceData((await EMSESP.readDeviceData({ id })).data);
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function onSelectChange(action: any, state: any) {
|
|
||||||
setDeviceData({ data: [] });
|
|
||||||
setSelectedDevice(state.id);
|
setSelectedDevice(state.id);
|
||||||
if (action.type === 'ADD_BY_ID_EXCLUSIVELY') {
|
if (action.type === 'ADD_BY_ID_EXCLUSIVELY') {
|
||||||
void fetchDeviceData(state.id);
|
await readDeviceData(state.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,27 +261,14 @@ const DashboardDevices: FC = () => {
|
|||||||
};
|
};
|
||||||
}, [escFunction]);
|
}, [escFunction]);
|
||||||
|
|
||||||
const fetchCoreData = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
setSelectedDevice(undefined);
|
|
||||||
setCoreData((await EMSESP.readCoreData()).data);
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
|
||||||
}
|
|
||||||
}, [LL]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void fetchCoreData();
|
|
||||||
}, [fetchCoreData]);
|
|
||||||
|
|
||||||
const refreshData = () => {
|
const refreshData = () => {
|
||||||
if (deviceValueDialogOpen) {
|
if (deviceValueDialogOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selectedDevice) {
|
if (selectedDevice) {
|
||||||
void fetchDeviceData(selectedDevice);
|
void readDeviceData(selectedDevice);
|
||||||
} else {
|
} else {
|
||||||
void fetchCoreData();
|
void readCoreData();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -346,27 +337,20 @@ const DashboardDevices: FC = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const deviceValueDialogSave = async (dv: DeviceValue) => {
|
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
|
||||||
const selectedDeviceID = Number(device_select.state.id);
|
const id = Number(device_select.state.id);
|
||||||
try {
|
await writeDeviceValue({ id, devicevalue })
|
||||||
const response = await EMSESP.writeDeviceValue({
|
.then(() => {
|
||||||
id: selectedDeviceID,
|
|
||||||
devicevalue: dv
|
|
||||||
});
|
|
||||||
if (response.status === 204) {
|
|
||||||
toast.error(LL.WRITE_CMD_FAILED());
|
|
||||||
} else if (response.status === 403) {
|
|
||||||
toast.error(LL.ACCESS_DENIED());
|
|
||||||
} else {
|
|
||||||
toast.success(LL.WRITE_CMD_SENT());
|
toast.success(LL.WRITE_CMD_SENT());
|
||||||
}
|
})
|
||||||
} catch (error) {
|
.catch((error) => {
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
toast.error(error.message);
|
||||||
} finally {
|
})
|
||||||
setDeviceValueDialogOpen(false);
|
.finally(async () => {
|
||||||
await fetchDeviceData(selectedDeviceID);
|
setDeviceValueDialogOpen(false);
|
||||||
setSelectedDeviceValue(undefined);
|
await readDeviceData(id);
|
||||||
}
|
setSelectedDeviceValue(undefined);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderDeviceDetails = () => {
|
const renderDeviceDetails = () => {
|
||||||
@@ -500,20 +484,11 @@ const DashboardDevices: FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ border: '1px solid #177ac9' }}>
|
<Box sx={{ border: '1px solid #177ac9' }}>
|
||||||
<Grid container justifyContent="space-between">
|
<Typography noWrap variant="subtitle1" color="warning.main" sx={{ mx: 1 }}>
|
||||||
<Box color="warning.main" ml={1}>
|
{coreData.devices[deviceIndex].n}
|
||||||
<Typography noWrap variant="h6">
|
</Typography>
|
||||||
{coreData.devices[deviceIndex].n}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Grid item zeroMinWidth justifyContent="flex-end">
|
|
||||||
<IconButton onClick={resetDeviceSelect}>
|
|
||||||
<CancelIcon color="info" sx={{ fontSize: 18, verticalAlign: 'middle' }} />
|
|
||||||
</IconButton>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item xs>
|
<Grid container justifyContent="space-between">
|
||||||
<Typography sx={{ ml: 1 }} variant="subtitle2" color="primary">
|
<Typography sx={{ ml: 1 }} variant="subtitle2" color="primary">
|
||||||
{shown_data.length + ' ' + LL.ENTITIES(shown_data.length)}
|
{shown_data.length + ' ' + LL.ENTITIES(shown_data.length)}
|
||||||
<IconButton onClick={() => setShowDeviceInfo(true)}>
|
<IconButton onClick={() => setShowDeviceInfo(true)}>
|
||||||
@@ -533,6 +508,11 @@ const DashboardDevices: FC = () => {
|
|||||||
<RefreshIcon color="primary" sx={{ fontSize: 18, verticalAlign: 'middle' }} />
|
<RefreshIcon color="primary" sx={{ fontSize: 18, verticalAlign: 'middle' }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Grid item zeroMinWidth justifyContent="flex-end">
|
||||||
|
<IconButton onClick={resetDeviceSelect}>
|
||||||
|
<CancelIcon color="info" sx={{ fontSize: 18, verticalAlign: 'middle' }} />
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -612,6 +592,7 @@ const DashboardDevices: FC = () => {
|
|||||||
!hasMask(selectedDeviceValue.id, DeviceEntityMask.DV_READONLY)
|
!hasMask(selectedDeviceValue.id, DeviceEntityMask.DV_READONLY)
|
||||||
}
|
}
|
||||||
validator={deviceValueItemValidation(selectedDeviceValue)}
|
validator={deviceValueItemValidation(selectedDeviceValue)}
|
||||||
|
progress={submitting}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ButtonRow>
|
<ButtonRow>
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ import {
|
|||||||
FormHelperText,
|
FormHelperText,
|
||||||
Grid,
|
Grid,
|
||||||
Box,
|
Box,
|
||||||
Typography
|
Typography,
|
||||||
|
CircularProgress
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
import { green } from '@mui/material/colors';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
|
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
|
||||||
@@ -35,6 +37,7 @@ type DashboardDevicesDialogProps = {
|
|||||||
selectedItem: DeviceValue;
|
selectedItem: DeviceValue;
|
||||||
writeable: boolean;
|
writeable: boolean;
|
||||||
validator: Schema;
|
validator: Schema;
|
||||||
|
progress: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DashboarDevicesDialog = ({
|
const DashboarDevicesDialog = ({
|
||||||
@@ -43,7 +46,8 @@ const DashboarDevicesDialog = ({
|
|||||||
onSave,
|
onSave,
|
||||||
selectedItem,
|
selectedItem,
|
||||||
writeable,
|
writeable,
|
||||||
validator
|
validator,
|
||||||
|
progress
|
||||||
}: DashboardDevicesDialogProps) => {
|
}: DashboardDevicesDialogProps) => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const [editItem, setEditItem] = useState<DeviceValue>(selectedItem);
|
const [editItem, setEditItem] = useState<DeviceValue>(selectedItem);
|
||||||
@@ -94,12 +98,11 @@ const DashboarDevicesDialog = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let helperText = '<';
|
let helperText = '<';
|
||||||
if (dv.u !== DeviceValueUOM.NONE) {
|
if (dv.s) {
|
||||||
helperText += 'n';
|
helperText += 'n';
|
||||||
if (dv.m && dv.x) {
|
if (dv.m !== undefined && dv.x !== undefined) {
|
||||||
helperText += ' between ' + dv.m + ' and ' + dv.x;
|
helperText += ' between ' + dv.m + ' and ' + dv.x;
|
||||||
}
|
} else {
|
||||||
if (dv.s) {
|
|
||||||
helperText += ' , step ' + dv.s;
|
helperText += ' , step ' + dv.s;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -115,7 +118,8 @@ const DashboarDevicesDialog = ({
|
|||||||
sx={{
|
sx={{
|
||||||
'& .MuiDialog-paper': {
|
'& .MuiDialog-paper': {
|
||||||
borderRadius: '12px'
|
borderRadius: '12px'
|
||||||
}
|
},
|
||||||
|
backdropFilter: 'blur(1px)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
@@ -144,7 +148,7 @@ const DashboarDevicesDialog = ({
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</TextField>
|
</TextField>
|
||||||
) : editItem.u !== DeviceValueUOM.NONE ? (
|
) : editItem.s || editItem.u !== DeviceValueUOM.NONE ? (
|
||||||
<ValidatedTextField
|
<ValidatedTextField
|
||||||
fieldErrors={fieldErrors}
|
fieldErrors={fieldErrors}
|
||||||
name="v"
|
name="v"
|
||||||
@@ -155,7 +159,7 @@ const DashboarDevicesDialog = ({
|
|||||||
type="number"
|
type="number"
|
||||||
sx={{ width: '30ch' }}
|
sx={{ width: '30ch' }}
|
||||||
onChange={updateFormValue}
|
onChange={updateFormValue}
|
||||||
inputProps={editItem.u ? { min: editItem.m, max: editItem.x, step: editItem.s } : {}}
|
inputProps={editItem.s ? { min: editItem.m, max: editItem.x, step: editItem.s } : {}}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: <InputAdornment position="start">{setUom(editItem.u)}</InputAdornment>
|
startAdornment: <InputAdornment position="start">{setUom(editItem.u)}</InputAdornment>
|
||||||
}}
|
}}
|
||||||
@@ -184,14 +188,32 @@ const DashboarDevicesDialog = ({
|
|||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
{writeable ? (
|
{writeable ? (
|
||||||
<>
|
<Box
|
||||||
|
sx={{
|
||||||
|
'& button, & a, & .MuiCard-root': {
|
||||||
|
mx: 0.6
|
||||||
|
},
|
||||||
|
position: 'relative'
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={close} color="secondary">
|
<Button startIcon={<CancelIcon />} variant="outlined" onClick={close} color="secondary">
|
||||||
{LL.CANCEL()}
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
<Button startIcon={<WarningIcon color="warning" />} variant="contained" onClick={save} color="info">
|
<Button startIcon={<WarningIcon color="warning" />} variant="contained" onClick={save} color="info">
|
||||||
{selectedItem.v === '' && selectedItem.c ? LL.EXECUTE() : LL.UPDATE()}
|
{selectedItem.v === '' && selectedItem.c ? LL.EXECUTE() : LL.UPDATE()}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
{progress && (
|
||||||
|
<CircularProgress
|
||||||
|
size={24}
|
||||||
|
sx={{
|
||||||
|
color: green[500],
|
||||||
|
position: 'absolute',
|
||||||
|
right: '20%',
|
||||||
|
marginTop: '6px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Button variant="outlined" onClick={close} color="secondary">
|
<Button variant="outlined" onClick={close} color="secondary">
|
||||||
{LL.CLOSE()}
|
{LL.CLOSE()}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import { Button, Typography, Box } from '@mui/material';
|
|||||||
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
|
import { useSort, SortToggleType } from '@table-library/react-table-library/sort';
|
||||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||||
import { useTheme } from '@table-library/react-table-library/theme';
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
import { useState, useContext, useCallback, useEffect } from 'react';
|
import { useRequest } from 'alova';
|
||||||
|
import { useState, useContext, useEffect } from 'react';
|
||||||
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
@@ -17,24 +18,38 @@ import * as EMSESP from './api';
|
|||||||
|
|
||||||
import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames } from './types';
|
import { DeviceValueUOM, DeviceValueUOM_s, AnalogTypeNames } from './types';
|
||||||
import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators';
|
import { temperatureSensorItemValidation, analogSensorItemValidation } from './validators';
|
||||||
import type { SensorData, TemperatureSensor, AnalogSensor } from './types';
|
import type { TemperatureSensor, AnalogSensor } from './types';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { ButtonRow, SectionContent } from 'components';
|
import { ButtonRow, SectionContent } from 'components';
|
||||||
|
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
const DashboardSensors: FC = () => {
|
const DashboardSensors: FC = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const { me } = useContext(AuthenticatedContext);
|
const { me } = useContext(AuthenticatedContext);
|
||||||
const [sensorData, setSensorData] = useState<SensorData>({ ts: [], as: [], analog_enabled: false });
|
|
||||||
const [selectedTemperatureSensor, setSelectedTemperatureSensor] = useState<TemperatureSensor>();
|
const [selectedTemperatureSensor, setSelectedTemperatureSensor] = useState<TemperatureSensor>();
|
||||||
const [selectedAnalogSensor, setSelectedAnalogSensor] = useState<AnalogSensor>();
|
const [selectedAnalogSensor, setSelectedAnalogSensor] = useState<AnalogSensor>();
|
||||||
const [temperatureDialogOpen, setTemperatureDialogOpen] = useState<boolean>(false);
|
const [temperatureDialogOpen, setTemperatureDialogOpen] = useState<boolean>(false);
|
||||||
const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false);
|
const [analogDialogOpen, setAnalogDialogOpen] = useState<boolean>(false);
|
||||||
const [creating, setCreating] = useState<boolean>(false);
|
const [creating, setCreating] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const { data: sensorData, send: fetchSensorData } = useRequest(() => EMSESP.readSensorData(), {
|
||||||
|
initialData: {
|
||||||
|
ts: [],
|
||||||
|
as: [],
|
||||||
|
analog_enabled: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { send: writeTemperatureSensor } = useRequest((data) => EMSESP.writeTemperatureSensor(data), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const { send: writeAnalogSensor } = useRequest((data) => EMSESP.writeAnalogSensor(data), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
const isAdmin = me.admin;
|
const isAdmin = me.admin;
|
||||||
|
|
||||||
const common_theme = useTheme({
|
const common_theme = useTheme({
|
||||||
@@ -101,20 +116,6 @@ const DashboardSensors: FC = () => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const fetchSensorData = useCallback(async () => {
|
|
||||||
if (!analogDialogOpen && !temperatureDialogOpen) {
|
|
||||||
try {
|
|
||||||
setSensorData((await EMSESP.readSensorData()).data);
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [LL, analogDialogOpen, temperatureDialogOpen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void fetchSensorData();
|
|
||||||
}, [fetchSensorData]);
|
|
||||||
|
|
||||||
const getSortIcon = (state: any, sortKey: any) => {
|
const getSortIcon = (state: any, sortKey: any) => {
|
||||||
if (state.sortKey === sortKey && state.reverse) {
|
if (state.sortKey === sortKey && state.reverse) {
|
||||||
return <KeyboardArrowDownOutlinedIcon />;
|
return <KeyboardArrowDownOutlinedIcon />;
|
||||||
@@ -229,26 +230,18 @@ const DashboardSensors: FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onTemperatureDialogSave = async (ts: TemperatureSensor) => {
|
const onTemperatureDialogSave = async (ts: TemperatureSensor) => {
|
||||||
try {
|
await writeTemperatureSensor({ id: ts.id, name: ts.n, offset: ts.o })
|
||||||
const response = await EMSESP.writeTemperatureSensor({
|
.then(() => {
|
||||||
id: ts.id,
|
|
||||||
name: ts.n,
|
|
||||||
offset: ts.o
|
|
||||||
});
|
|
||||||
if (response.status === 204) {
|
|
||||||
toast.error(LL.UPDATE_OF(LL.SENSOR(2)) + ' ' + LL.FAILED(1));
|
|
||||||
} else if (response.status === 403) {
|
|
||||||
toast.error(LL.ACCESS_DENIED());
|
|
||||||
} else {
|
|
||||||
toast.success(LL.UPDATED_OF(LL.SENSOR(1)));
|
toast.success(LL.UPDATED_OF(LL.SENSOR(1)));
|
||||||
}
|
})
|
||||||
} catch (error) {
|
.catch(() => {
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
toast.error(LL.UPDATE_OF(LL.SENSOR(2)) + ' ' + LL.FAILED(1));
|
||||||
} finally {
|
})
|
||||||
setTemperatureDialogOpen(false);
|
.finally(async () => {
|
||||||
setSelectedTemperatureSensor(undefined);
|
setTemperatureDialogOpen(false);
|
||||||
await fetchSensorData();
|
setSelectedTemperatureSensor(undefined);
|
||||||
}
|
await fetchSensorData();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateAnalogSensor = (as: AnalogSensor) => {
|
const updateAnalogSensor = (as: AnalogSensor) => {
|
||||||
@@ -280,32 +273,27 @@ const DashboardSensors: FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onAnalogDialogSave = async (as: AnalogSensor) => {
|
const onAnalogDialogSave = async (as: AnalogSensor) => {
|
||||||
try {
|
await writeAnalogSensor({
|
||||||
const response = await EMSESP.writeAnalogSensor({
|
id: as.id,
|
||||||
id: as.id,
|
gpio: as.g,
|
||||||
gpio: as.g,
|
name: as.n,
|
||||||
name: as.n,
|
offset: as.o,
|
||||||
offset: as.o,
|
factor: as.f,
|
||||||
factor: as.f,
|
uom: as.u,
|
||||||
uom: as.u,
|
type: as.t,
|
||||||
type: as.t,
|
deleted: as.d
|
||||||
deleted: as.d
|
})
|
||||||
});
|
.then(() => {
|
||||||
|
|
||||||
if (response.status === 204) {
|
|
||||||
toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR(5)) + ' ' + LL.FAILED(1));
|
|
||||||
} else if (response.status === 403) {
|
|
||||||
toast.error(LL.ACCESS_DENIED());
|
|
||||||
} else {
|
|
||||||
toast.success(LL.UPDATED_OF(LL.ANALOG_SENSOR(2)));
|
toast.success(LL.UPDATED_OF(LL.ANALOG_SENSOR(2)));
|
||||||
}
|
})
|
||||||
} catch (error) {
|
.catch(() => {
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
toast.error(LL.UPDATE_OF(LL.ANALOG_SENSOR(5)) + ' ' + LL.FAILED(1));
|
||||||
} finally {
|
})
|
||||||
setAnalogDialogOpen(false);
|
.finally(async () => {
|
||||||
setSelectedAnalogSensor(undefined);
|
setAnalogDialogOpen(false);
|
||||||
await fetchSensorData();
|
setSelectedAnalogSensor(undefined);
|
||||||
}
|
await fetchSensorData();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const RenderTemperatureSensors = () => (
|
const RenderTemperatureSensors = () => (
|
||||||
@@ -431,7 +419,7 @@ const DashboardSensors: FC = () => {
|
|||||||
{sensorData?.analog_enabled === true && (
|
{sensorData?.analog_enabled === true && (
|
||||||
<>
|
<>
|
||||||
<Typography sx={{ pt: 4, pb: 1 }} variant="h6" color="secondary">
|
<Typography sx={{ pt: 4, pb: 1 }} variant="h6" color="secondary">
|
||||||
{LL.ANALOG_SENSORS(0)}
|
{LL.ANALOG_SENSORS()}
|
||||||
</Typography>
|
</Typography>
|
||||||
<RenderAnalogSensors />
|
<RenderAnalogSensors />
|
||||||
{selectedAnalogSensor && (
|
{selectedAnalogSensor && (
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
} from '@mui/material';
|
} 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 { useTheme as tableTheme } from '@table-library/react-table-library/theme';
|
||||||
|
import { useRequest } from 'alova';
|
||||||
import { useContext, useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
@@ -32,7 +33,6 @@ import type { FC } from 'react';
|
|||||||
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
import { ButtonRow, FormLoader, SectionContent } from 'components';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { extractErrorMessage, useRest } from 'utils';
|
|
||||||
|
|
||||||
export const isConnected = ({ status }: Status) => status !== busConnectionStatus.BUS_STATUS_OFFLINE;
|
export const isConnected = ({ status }: Status) => status !== busConnectionStatus.BUS_STATUS_OFFLINE;
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ const showQuality = (stat: Stat) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DashboardStatus: FC = () => {
|
const DashboardStatus: FC = () => {
|
||||||
const { loadData, data, errorMessage } = useRest<Status>({ read: EMSESP.readStatus });
|
const { data: data, send: loadData, error } = useRequest(EMSESP.readStatus);
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
@@ -73,6 +73,10 @@ const DashboardStatus: FC = () => {
|
|||||||
|
|
||||||
const { me } = useContext(AuthenticatedContext);
|
const { me } = useContext(AuthenticatedContext);
|
||||||
|
|
||||||
|
const { send: scanDevices } = useRequest(EMSESP.scanDevices, {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
const stats_theme = tableTheme({
|
const stats_theme = tableTheme({
|
||||||
Table: `
|
Table: `
|
||||||
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px;
|
--data-table-library_grid-template-columns: repeat(1, minmax(0, 1fr)) 90px 90px 80px;
|
||||||
@@ -158,14 +162,14 @@ const DashboardStatus: FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const scan = async () => {
|
const scan = async () => {
|
||||||
try {
|
await scanDevices()
|
||||||
await EMSESP.scanDevices();
|
.then(() => {
|
||||||
toast.info(LL.SCANNING() + '...');
|
toast.info(LL.SCANNING() + '...');
|
||||||
} catch (error) {
|
})
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
.catch((err) => {
|
||||||
} finally {
|
toast.error(err.message);
|
||||||
setConfirmScan(false);
|
});
|
||||||
}
|
setConfirmScan(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderScanDialog = () => (
|
const renderScanDialog = () => (
|
||||||
@@ -185,7 +189,7 @@ const DashboardStatus: FC = () => {
|
|||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
|
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import DownloadIcon from '@mui/icons-material/GetApp';
|
|||||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||||
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
|
import MenuBookIcon from '@mui/icons-material/MenuBookTwoTone';
|
||||||
import { Typography, Button, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@mui/material';
|
import { Typography, Button, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@mui/material';
|
||||||
|
import { useRequest } from 'alova';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import * as EMSESP from './api';
|
import * as EMSESP from './api';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
@@ -11,16 +12,19 @@ import type { FC } from 'react';
|
|||||||
import { SectionContent } from 'components';
|
import { SectionContent } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
const HelpInformation: FC = () => {
|
const HelpInformation: FC = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const saveFile = (json: any, endpoint: string) => {
|
const { send: API, onSuccess: onSuccessAPI } = useRequest((data) => EMSESP.API(data), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
onSuccessAPI((event) => {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
const filename = 'emsesp_' + endpoint + '.txt';
|
const filename = 'emsesp_info.txt';
|
||||||
a.href = URL.createObjectURL(
|
a.href = URL.createObjectURL(
|
||||||
new Blob([JSON.stringify(json, null, 2)], {
|
new Blob([JSON.stringify(event.data, null, 2)], {
|
||||||
type: 'text/plain'
|
type: 'text/plain'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -29,23 +33,12 @@ const HelpInformation: FC = () => {
|
|||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||||
};
|
});
|
||||||
|
|
||||||
const callAPI = async (endpoint: string) => {
|
const callAPI = async () => {
|
||||||
try {
|
await API({ device: 'system', entity: 'info', id: 0 }).catch((error) => {
|
||||||
const response = await EMSESP.API({
|
toast.error(error.message);
|
||||||
device: 'system',
|
});
|
||||||
entity: endpoint,
|
|
||||||
id: 0
|
|
||||||
});
|
|
||||||
if (response.status !== 200) {
|
|
||||||
toast.error(LL.PROBLEM_LOADING());
|
|
||||||
} else {
|
|
||||||
saveFile(response.data, endpoint);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -96,7 +89,7 @@ const HelpInformation: FC = () => {
|
|||||||
size="small"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => callAPI('info')}
|
onClick={() => callAPI()}
|
||||||
>
|
>
|
||||||
{LL.SUPPORT_INFO()}
|
{LL.SUPPORT_INFO()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import CancelIcon from '@mui/icons-material/Cancel';
|
|||||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment, TextField } from '@mui/material';
|
import { Box, Button, Checkbox, MenuItem, Grid, Typography, Divider, InputAdornment, TextField } from '@mui/material';
|
||||||
|
import { useRequest } from 'alova';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ import { createSettingsValidator } from './validators';
|
|||||||
import type { Settings } from './types';
|
import type { Settings } from './types';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
import * as SystemApi from 'api/system';
|
||||||
import {
|
import {
|
||||||
SectionContent,
|
SectionContent,
|
||||||
FormLoader,
|
FormLoader,
|
||||||
@@ -23,7 +25,7 @@ import {
|
|||||||
|
|
||||||
import RestartMonitor from 'framework/system/RestartMonitor';
|
import RestartMonitor from 'framework/system/RestartMonitor';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { numberValue, extractErrorMessage, updateValueDirty, useRest } from 'utils';
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
import { validate } from 'validators';
|
import { validate } from 'validators';
|
||||||
|
|
||||||
export function boardProfileSelectItems() {
|
export function boardProfileSelectItems() {
|
||||||
@@ -39,7 +41,7 @@ const SettingsApplication: FC = () => {
|
|||||||
loadData,
|
loadData,
|
||||||
saveData,
|
saveData,
|
||||||
saving,
|
saving,
|
||||||
setData,
|
updateDataValue,
|
||||||
data,
|
data,
|
||||||
origData,
|
origData,
|
||||||
dirtyFlags,
|
dirtyFlags,
|
||||||
@@ -51,39 +53,48 @@ const SettingsApplication: FC = () => {
|
|||||||
read: EMSESP.readSettings,
|
read: EMSESP.readSettings,
|
||||||
update: EMSESP.writeSettings
|
update: EMSESP.writeSettings
|
||||||
});
|
});
|
||||||
|
|
||||||
const [restarting, setRestarting] = useState<boolean>();
|
const [restarting, setRestarting] = useState<boolean>();
|
||||||
|
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, setData);
|
const updateFormValue = updateValueDirty(origData, dirtyFlags, setDirtyFlags, updateDataValue);
|
||||||
|
|
||||||
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
|
||||||
const [processingBoard, setProcessingBoard] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const updateBoardProfile = async (boardProfile: string) => {
|
const {
|
||||||
setProcessingBoard(true);
|
loading: processingBoard,
|
||||||
try {
|
send: readBoardProfile,
|
||||||
const response = await EMSESP.getBoardProfile({ board_profile: boardProfile });
|
onSuccess: onSuccessBoardProfile
|
||||||
if (data) {
|
} = useRequest((boardProfile) => EMSESP.getBoardProfile(boardProfile), {
|
||||||
setData({
|
immediate: false
|
||||||
...data,
|
});
|
||||||
board_profile: boardProfile,
|
|
||||||
led_gpio: response.data.led_gpio,
|
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||||
dallas_gpio: response.data.dallas_gpio,
|
immediate: false
|
||||||
rx_gpio: response.data.rx_gpio,
|
});
|
||||||
tx_gpio: response.data.tx_gpio,
|
|
||||||
pbutton_gpio: response.data.pbutton_gpio,
|
onSuccessBoardProfile((event) => {
|
||||||
phy_type: response.data.phy_type,
|
const response = event.data as Settings;
|
||||||
eth_power: response.data.eth_power,
|
updateDataValue({
|
||||||
eth_phy_addr: response.data.eth_phy_addr,
|
...data,
|
||||||
eth_clock_mode: response.data.eth_clock_mode
|
board_profile: response.board_profile,
|
||||||
});
|
led_gpio: response.led_gpio,
|
||||||
}
|
dallas_gpio: response.dallas_gpio,
|
||||||
} catch (error) {
|
rx_gpio: response.rx_gpio,
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
tx_gpio: response.tx_gpio,
|
||||||
} finally {
|
pbutton_gpio: response.pbutton_gpio,
|
||||||
setProcessingBoard(false);
|
phy_type: response.phy_type,
|
||||||
}
|
eth_power: response.eth_power,
|
||||||
|
eth_phy_addr: response.eth_phy_addr,
|
||||||
|
eth_clock_mode: response.eth_clock_mode
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateBoardProfile = async (board_profile: string) => {
|
||||||
|
await readBoardProfile(board_profile).catch((error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const content = () => {
|
const content = () => {
|
||||||
@@ -95,9 +106,10 @@ const SettingsApplication: FC = () => {
|
|||||||
try {
|
try {
|
||||||
setFieldErrors(undefined);
|
setFieldErrors(undefined);
|
||||||
await validate(createSettingsValidator(data), data);
|
await validate(createSettingsValidator(data), data);
|
||||||
await saveData();
|
|
||||||
} catch (errors: any) {
|
} catch (errors: any) {
|
||||||
setFieldErrors(errors);
|
setFieldErrors(errors);
|
||||||
|
} finally {
|
||||||
|
await saveData();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -105,7 +117,7 @@ const SettingsApplication: FC = () => {
|
|||||||
const boardProfile = event.target.value;
|
const boardProfile = event.target.value;
|
||||||
updateFormValue(event);
|
updateFormValue(event);
|
||||||
if (boardProfile === 'CUSTOM') {
|
if (boardProfile === 'CUSTOM') {
|
||||||
setData({
|
updateDataValue({
|
||||||
...data,
|
...data,
|
||||||
board_profile: boardProfile
|
board_profile: boardProfile
|
||||||
});
|
});
|
||||||
@@ -116,12 +128,10 @@ const SettingsApplication: FC = () => {
|
|||||||
|
|
||||||
const restart = async () => {
|
const restart = async () => {
|
||||||
await validateAndSubmit();
|
await validateAndSubmit();
|
||||||
try {
|
await restartCommand().catch((error) => {
|
||||||
await EMSESP.restart();
|
toast.error(error.message);
|
||||||
setRestarting(true);
|
});
|
||||||
} catch (error) {
|
setRestarting(true);
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -367,10 +377,10 @@ const SettingsApplication: FC = () => {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
select
|
select
|
||||||
>
|
>
|
||||||
<MenuItem value="en">English (EN)</MenuItem>
|
|
||||||
<Divider />
|
|
||||||
<MenuItem value="de">Deutsch (DE)</MenuItem>
|
<MenuItem value="de">Deutsch (DE)</MenuItem>
|
||||||
|
<MenuItem value="en">English (EN)</MenuItem>
|
||||||
<MenuItem value="fr">Français (FR)</MenuItem>
|
<MenuItem value="fr">Français (FR)</MenuItem>
|
||||||
|
<MenuItem value="it">Italiano (IT)</MenuItem>
|
||||||
<MenuItem value="nl">Nederlands (NL)</MenuItem>
|
<MenuItem value="nl">Nederlands (NL)</MenuItem>
|
||||||
<MenuItem value="no">Norsk (NO)</MenuItem>
|
<MenuItem value="no">Norsk (NO)</MenuItem>
|
||||||
<MenuItem value="pl">Polski (PL)</MenuItem>
|
<MenuItem value="pl">Polski (PL)</MenuItem>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||||
import { useTheme } from '@table-library/react-table-library/theme';
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
|
import { useRequest } from 'alova';
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -32,13 +33,13 @@ import SettingsCustomizationDialog from './SettingsCustomizationDialog';
|
|||||||
import * as EMSESP from './api';
|
import * as EMSESP from './api';
|
||||||
|
|
||||||
import { DeviceEntityMask } from './types';
|
import { DeviceEntityMask } from './types';
|
||||||
import type { DeviceShort, Devices, DeviceEntity } from './types';
|
import type { DeviceShort, DeviceEntity } from './types';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { ButtonRow, FormLoader, SectionContent, MessageBox, BlockNavigation } from 'components';
|
import * as SystemApi from 'api/system';
|
||||||
|
import { ButtonRow, SectionContent, MessageBox, BlockNavigation } from 'components';
|
||||||
|
|
||||||
import RestartMonitor from 'framework/system/RestartMonitor';
|
import RestartMonitor from 'framework/system/RestartMonitor';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
export const APIURL = window.location.origin + '/api/';
|
export const APIURL = window.location.origin + '/api/';
|
||||||
|
|
||||||
@@ -46,11 +47,10 @@ const SettingsCustomization: FC = () => {
|
|||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const [numChanges, setNumChanges] = useState<number>(0);
|
const [numChanges, setNumChanges] = useState<number>(0);
|
||||||
const blocker = useBlocker(numChanges !== 0);
|
const blocker = useBlocker(numChanges !== 0);
|
||||||
|
|
||||||
const [restarting, setRestarting] = useState<boolean>(false);
|
const [restarting, setRestarting] = useState<boolean>(false);
|
||||||
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
|
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
|
||||||
const [deviceEntities, setDeviceEntities] = useState<DeviceEntity[]>();
|
const [deviceEntities, setDeviceEntities] = useState<DeviceEntity[]>([]);
|
||||||
const [devices, setDevices] = useState<Devices>();
|
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
|
||||||
const [selectedDevice, setSelectedDevice] = useState<number>(-1);
|
const [selectedDevice, setSelectedDevice] = useState<number>(-1);
|
||||||
const [confirmReset, setConfirmReset] = useState<boolean>(false);
|
const [confirmReset, setConfirmReset] = useState<boolean>(false);
|
||||||
const [selectedFilters, setSelectedFilters] = useState<number>(0);
|
const [selectedFilters, setSelectedFilters] = useState<number>(0);
|
||||||
@@ -58,6 +58,31 @@ const SettingsCustomization: FC = () => {
|
|||||||
const [selectedDeviceEntity, setSelectedDeviceEntity] = useState<DeviceEntity>();
|
const [selectedDeviceEntity, setSelectedDeviceEntity] = useState<DeviceEntity>();
|
||||||
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const { send: resetCustomizations } = useRequest(EMSESP.resetCustomizations(), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: devices } = useRequest(EMSESP.readDevices);
|
||||||
|
|
||||||
|
const { send: writeCustomEntities } = useRequest((data) => EMSESP.writeCustomEntities(data), { immediate: false });
|
||||||
|
|
||||||
|
const { send: readDeviceEntities, onSuccess: onSuccess } = useRequest((data) => EMSESP.readDeviceEntities(data), {
|
||||||
|
initialData: [],
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
|
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 })));
|
||||||
|
};
|
||||||
|
|
||||||
|
onSuccess((event) => {
|
||||||
|
setOriginalSettings(event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
const { send: restartCommand } = useRequest(SystemApi.restart(), {
|
||||||
|
immediate: false
|
||||||
|
});
|
||||||
|
|
||||||
const entities_theme = useTheme({
|
const entities_theme = useTheme({
|
||||||
Table: `
|
Table: `
|
||||||
--data-table-library_grid-template-columns: 150px repeat(1, minmax(80px, 1fr)) 45px minmax(45px, auto) minmax(120px, auto);
|
--data-table-library_grid-template-columns: 150px repeat(1, minmax(80px, 1fr)) 45px minmax(45px, auto) minmax(120px, auto);
|
||||||
@@ -131,7 +156,7 @@ const SettingsCustomization: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (deviceEntities) {
|
if (deviceEntities.length) {
|
||||||
setNumChanges(
|
setNumChanges(
|
||||||
deviceEntities
|
deviceEntities
|
||||||
.filter((de) => hasEntityChanged(de))
|
.filter((de) => hasEntityChanged(de))
|
||||||
@@ -148,29 +173,11 @@ const SettingsCustomization: FC = () => {
|
|||||||
}
|
}
|
||||||
}, [deviceEntities]);
|
}, [deviceEntities]);
|
||||||
|
|
||||||
const fetchDevices = useCallback(async () => {
|
const restart = async () => {
|
||||||
try {
|
await restartCommand().catch((error) => {
|
||||||
setDevices((await EMSESP.readDevices()).data);
|
toast.error(error.message);
|
||||||
} catch (error) {
|
});
|
||||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
setRestarting(true);
|
||||||
}
|
|
||||||
}, [LL]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void fetchDevices();
|
|
||||||
}, [fetchDevices]);
|
|
||||||
|
|
||||||
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 })));
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchDeviceEntities = async (unique_id: number) => {
|
|
||||||
try {
|
|
||||||
const new_deviceEntities = (await EMSESP.readDeviceEntities({ id: unique_id })).data;
|
|
||||||
setOriginalSettings(new_deviceEntities);
|
|
||||||
} catch (error) {
|
|
||||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatValue(value: any) {
|
function formatValue(value: any) {
|
||||||
@@ -226,7 +233,7 @@ const SettingsCustomization: FC = () => {
|
|||||||
|
|
||||||
const maskDisabled = (set: boolean) => {
|
const maskDisabled = (set: boolean) => {
|
||||||
setDeviceEntities(
|
setDeviceEntities(
|
||||||
deviceEntities?.map(function (de) {
|
deviceEntities.map(function (de) {
|
||||||
if ((de.m & selectedFilters || !selectedFilters) && de.id.toLowerCase().includes(search.toLowerCase())) {
|
if ((de.m & selectedFilters || !selectedFilters) && de.id.toLowerCase().includes(search.toLowerCase())) {
|
||||||
return {
|
return {
|
||||||
...de,
|
...de,
|
||||||
@@ -246,31 +253,22 @@ const SettingsCustomization: FC = () => {
|
|||||||
const selected_device = parseInt(event.target.value, 10);
|
const selected_device = parseInt(event.target.value, 10);
|
||||||
setSelectedDevice(selected_device);
|
setSelectedDevice(selected_device);
|
||||||
setNumChanges(0);
|
setNumChanges(0);
|
||||||
void fetchDeviceEntities(devices?.devices[selected_device].i);
|
void readDeviceEntities(devices?.devices[selected_device].i);
|
||||||
setRestartNeeded(false);
|
setRestartNeeded(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetCustomization = async () => {
|
const resetCustomization = async () => {
|
||||||
try {
|
try {
|
||||||
await EMSESP.resetCustomizations();
|
await resetCustomizations();
|
||||||
toast.info(LL.CUSTOMIZATIONS_RESTART());
|
toast.info(LL.CUSTOMIZATIONS_RESTART());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
toast.error(error.message);
|
||||||
} finally {
|
} finally {
|
||||||
setConfirmReset(false);
|
setConfirmReset(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const restart = async () => {
|
|
||||||
try {
|
|
||||||
await EMSESP.restart();
|
|
||||||
setRestarting(true);
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDialogClose = () => {
|
const onDialogClose = () => {
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
};
|
};
|
||||||
@@ -300,7 +298,7 @@ const SettingsCustomization: FC = () => {
|
|||||||
const saveCustomization = async () => {
|
const saveCustomization = async () => {
|
||||||
if (devices && deviceEntities && selectedDevice !== -1) {
|
if (devices && deviceEntities && selectedDevice !== -1) {
|
||||||
const masked_entities = deviceEntities
|
const masked_entities = deviceEntities
|
||||||
.filter((de) => hasEntityChanged(de))
|
.filter((de: DeviceEntity) => hasEntityChanged(de))
|
||||||
.map(
|
.map(
|
||||||
(new_de) =>
|
(new_de) =>
|
||||||
new_de.m.toString(16).padStart(2, '0') +
|
new_de.m.toString(16).padStart(2, '0') +
|
||||||
@@ -318,72 +316,56 @@ const SettingsCustomization: FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
await writeCustomEntities({ id: devices?.devices[selectedDevice].i, entity_ids: masked_entities }).catch(
|
||||||
const response = await EMSESP.writeCustomEntities({
|
(error) => {
|
||||||
id: devices?.devices[selectedDevice].i,
|
if (error.message === 'Reboot required') {
|
||||||
entity_ids: masked_entities
|
setRestartNeeded(true);
|
||||||
});
|
} else {
|
||||||
if (response.status === 200) {
|
toast.error(error.message);
|
||||||
toast.success(LL.CUSTOMIZATIONS_SAVED());
|
}
|
||||||
} else if (response.status === 201) {
|
|
||||||
setRestartNeeded(true);
|
|
||||||
} else {
|
|
||||||
toast.error(LL.PROBLEM_UPDATING());
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
);
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
|
||||||
}
|
|
||||||
setOriginalSettings(deviceEntities);
|
setOriginalSettings(deviceEntities);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderDeviceList = () => {
|
const renderDeviceList = () => (
|
||||||
if (!devices) {
|
<>
|
||||||
return <FormLoader errorMessage={errorMessage} />;
|
<Box mb={2} color="warning.main">
|
||||||
}
|
<Typography variant="body2">{LL.CUSTOMIZATIONS_HELP_1()}.</Typography>
|
||||||
|
<Typography variant="body2" mt={1}>
|
||||||
return (
|
<OptionIcon type="favorite" isSet={true} />={LL.CUSTOMIZATIONS_HELP_2()}
|
||||||
<>
|
<OptionIcon type="readonly" isSet={true} />={LL.CUSTOMIZATIONS_HELP_3()}
|
||||||
<Box mb={2} color="warning.main">
|
<OptionIcon type="api_mqtt_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_4()}
|
||||||
<Typography variant="body2">{LL.CUSTOMIZATIONS_HELP_1()}.</Typography>
|
<OptionIcon type="web_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_5()}
|
||||||
<Typography variant="body2" mt={1}>
|
<OptionIcon type="deleted" isSet={true} />={LL.CUSTOMIZATIONS_HELP_6()}
|
||||||
<OptionIcon type="favorite" isSet={true} />={LL.CUSTOMIZATIONS_HELP_2()}
|
</Typography>
|
||||||
<OptionIcon type="readonly" isSet={true} />={LL.CUSTOMIZATIONS_HELP_3()}
|
</Box>
|
||||||
<OptionIcon type="api_mqtt_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_4()}
|
<TextField
|
||||||
<OptionIcon type="web_exclude" isSet={true} />={LL.CUSTOMIZATIONS_HELP_5()}
|
name="device"
|
||||||
<OptionIcon type="deleted" isSet={true} />={LL.CUSTOMIZATIONS_HELP_6()}
|
label={LL.EMS_DEVICE()}
|
||||||
</Typography>
|
variant="outlined"
|
||||||
</Box>
|
fullWidth
|
||||||
<TextField
|
value={selectedDevice}
|
||||||
name="device"
|
disabled={numChanges !== 0}
|
||||||
label={LL.EMS_DEVICE()}
|
onChange={changeSelectedDevice}
|
||||||
variant="outlined"
|
margin="normal"
|
||||||
fullWidth
|
select
|
||||||
value={selectedDevice}
|
>
|
||||||
disabled={numChanges !== 0}
|
<MenuItem disabled key={0} value={-1}>
|
||||||
onChange={changeSelectedDevice}
|
{LL.SELECT_DEVICE()}...
|
||||||
margin="normal"
|
</MenuItem>
|
||||||
select
|
{devices.devices.map((device: DeviceShort, index) => (
|
||||||
>
|
<MenuItem key={index} value={index}>
|
||||||
<MenuItem disabled key={0} value={-1}>
|
{device.s}
|
||||||
{LL.SELECT_DEVICE()}...
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{devices.devices.map((device: DeviceShort, index) => (
|
))}
|
||||||
<MenuItem key={index} value={index}>
|
</TextField>
|
||||||
{device.s}
|
</>
|
||||||
</MenuItem>
|
);
|
||||||
))}
|
|
||||||
</TextField>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderDeviceData = () => {
|
const renderDeviceData = () => {
|
||||||
if (!deviceEntities) {
|
if (deviceEntities.length === 0) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (devices?.devices.length === 0 || deviceEntities[0].id === '') {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,7 +503,7 @@ const SettingsCustomization: FC = () => {
|
|||||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||||
{LL.DEVICE_ENTITIES()}
|
{LL.DEVICE_ENTITIES()}
|
||||||
</Typography>
|
</Typography>
|
||||||
{renderDeviceList()}
|
{devices && renderDeviceList()}
|
||||||
{renderDeviceData()}
|
{renderDeviceData()}
|
||||||
{restartNeeded && (
|
{restartNeeded && (
|
||||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
|
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
|
||||||
@@ -539,7 +521,7 @@ const SettingsCustomization: FC = () => {
|
|||||||
startIcon={<CancelIcon />}
|
startIcon={<CancelIcon />}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onClick={() => devices && fetchDeviceEntities(devices.devices[selectedDevice].i)}
|
onClick={() => devices && readDeviceEntities(devices.devices[selectedDevice].i)}
|
||||||
>
|
>
|
||||||
{LL.CANCEL()}
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: Se
|
|||||||
{LL.CANCEL()}
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
<Button startIcon={<DoneIcon />} variant="outlined" onClick={save} color="primary">
|
<Button startIcon={<DoneIcon />} variant="outlined" onClick={save} color="primary">
|
||||||
{LL.UPDATE(0)}
|
{LL.UPDATE()}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import WarningIcon from '@mui/icons-material/Warning';
|
|||||||
import { Button, Typography, Box } from '@mui/material';
|
import { Button, Typography, Box } from '@mui/material';
|
||||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||||
import { useTheme } from '@table-library/react-table-library/theme';
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
// eslint-disable-next-line import/named
|
||||||
|
import { updateState, useRequest } from 'alova';
|
||||||
|
import { useState, useCallback } from 'react';
|
||||||
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
||||||
|
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -18,18 +20,26 @@ import type { FC } from 'react';
|
|||||||
import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components';
|
import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
const SettingsEntities: FC = () => {
|
const SettingsEntities: FC = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
const [numChanges, setNumChanges] = useState<number>(0);
|
const [numChanges, setNumChanges] = useState<number>(0);
|
||||||
const blocker = useBlocker(numChanges !== 0);
|
const blocker = useBlocker(numChanges !== 0);
|
||||||
const [entities, setEntities] = useState<EntityItem[]>();
|
|
||||||
const [selectedEntityItem, setSelectedEntityItem] = useState<EntityItem>();
|
const [selectedEntityItem, setSelectedEntityItem] = useState<EntityItem>();
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
|
||||||
const [creating, setCreating] = useState<boolean>(false);
|
const [creating, setCreating] = useState<boolean>(false);
|
||||||
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: entities,
|
||||||
|
send: fetchEntities,
|
||||||
|
error
|
||||||
|
} = useRequest(EMSESP.readEntities, {
|
||||||
|
initialData: [],
|
||||||
|
force: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const { send: writeEntities } = useRequest((data) => EMSESP.writeEntities(data), { immediate: false });
|
||||||
|
|
||||||
function hasEntityChanged(ei: EntityItem) {
|
function hasEntityChanged(ei: EntityItem) {
|
||||||
return (
|
return (
|
||||||
ei.id !== ei.o_id ||
|
ei.id !== ei.o_id ||
|
||||||
@@ -45,12 +55,6 @@ const SettingsEntities: FC = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (entities) {
|
|
||||||
setNumChanges(entities ? entities.filter((ei) => hasEntityChanged(ei)).length : 0);
|
|
||||||
}
|
|
||||||
}, [entities]);
|
|
||||||
|
|
||||||
const entity_theme = useTheme({
|
const entity_theme = useTheme({
|
||||||
Table: `
|
Table: `
|
||||||
--data-table-library_grid-template-columns: repeat(1, minmax(60px, 1fr)) minmax(80px, auto) 80px 80px 80px;
|
--data-table-library_grid-template-columns: repeat(1, minmax(60px, 1fr)) minmax(80px, auto) 80px 80px 80px;
|
||||||
@@ -105,62 +109,32 @@ const SettingsEntities: FC = () => {
|
|||||||
`
|
`
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchEntities = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const response = await EMSESP.readEntities();
|
|
||||||
setEntities(
|
|
||||||
response.data.entities.map((ei) => ({
|
|
||||||
...ei,
|
|
||||||
o_id: ei.id,
|
|
||||||
o_device_id: ei.device_id,
|
|
||||||
o_type_id: ei.type_id,
|
|
||||||
o_offset: ei.offset,
|
|
||||||
o_factor: ei.factor,
|
|
||||||
o_uom: ei.uom,
|
|
||||||
o_value_type: ei.value_type,
|
|
||||||
o_name: ei.name,
|
|
||||||
o_writeable: ei.writeable,
|
|
||||||
o_deleted: ei.deleted
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
|
||||||
}
|
|
||||||
}, [LL]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void fetchEntities();
|
|
||||||
}, [fetchEntities]);
|
|
||||||
|
|
||||||
const saveEntities = async () => {
|
const saveEntities = async () => {
|
||||||
if (entities) {
|
await writeEntities({
|
||||||
try {
|
entities: entities
|
||||||
const response = await EMSESP.writeEntities({
|
.filter((ei) => !ei.deleted)
|
||||||
entities: entities
|
.map((condensed_ei) => ({
|
||||||
.filter((ei) => !ei.deleted)
|
id: condensed_ei.id,
|
||||||
.map((condensed_ei) => ({
|
name: condensed_ei.name,
|
||||||
id: condensed_ei.id,
|
device_id: condensed_ei.device_id,
|
||||||
name: condensed_ei.name,
|
type_id: condensed_ei.type_id,
|
||||||
device_id: condensed_ei.device_id,
|
offset: condensed_ei.offset,
|
||||||
type_id: condensed_ei.type_id,
|
factor: condensed_ei.factor,
|
||||||
offset: condensed_ei.offset,
|
uom: condensed_ei.uom,
|
||||||
factor: condensed_ei.factor,
|
writeable: condensed_ei.writeable,
|
||||||
uom: condensed_ei.uom,
|
value_type: condensed_ei.value_type
|
||||||
writeable: condensed_ei.writeable,
|
}))
|
||||||
value_type: condensed_ei.value_type
|
})
|
||||||
}))
|
.then(() => {
|
||||||
});
|
toast.success(LL.ENTITIES_UPDATED());
|
||||||
|
})
|
||||||
if (response.status === 200) {
|
.catch((err) => {
|
||||||
toast.success(LL.ENTITIES_UPDATED());
|
toast.error(err.message);
|
||||||
} else {
|
})
|
||||||
toast.error(LL.PROBLEM_UPDATING());
|
.finally(async () => {
|
||||||
}
|
|
||||||
await fetchEntities();
|
await fetchEntities();
|
||||||
} catch (error) {
|
setNumChanges(0);
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const editEntityItem = useCallback((ei: EntityItem) => {
|
const editEntityItem = useCallback((ei: EntityItem) => {
|
||||||
@@ -173,13 +147,22 @@ const SettingsEntities: FC = () => {
|
|||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDialogCancel = async () => {
|
||||||
|
await fetchEntities().then(() => {
|
||||||
|
setNumChanges(0);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onDialogSave = (updatedItem: EntityItem) => {
|
const onDialogSave = (updatedItem: EntityItem) => {
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
if (entities && creating) {
|
|
||||||
setEntities([...entities.filter((ei) => creating || ei.o_id !== updatedItem.o_id), updatedItem]);
|
updateState('entities', (data) => {
|
||||||
} else {
|
const new_data = creating
|
||||||
setEntities(entities?.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;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const addEntityItem = () => {
|
const addEntityItem = () => {
|
||||||
@@ -208,14 +191,12 @@ const SettingsEntities: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showHex(value: number, digit: number) {
|
function showHex(value: number, digit: number) {
|
||||||
return digit === 4
|
return '0x' + value.toString(16).toUpperCase().padStart(digit, '0');
|
||||||
? '0x' + ('000' + value.toString(16).toUpperCase()).slice(-4)
|
|
||||||
: '0x' + ('0' + value.toString(16).toUpperCase()).slice(-2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderEntity = () => {
|
const renderEntity = () => {
|
||||||
if (!entities) {
|
if (!entities) {
|
||||||
return <FormLoader errorMessage={errorMessage} />;
|
return <FormLoader onRetry={fetchEntities} errorMessage={error?.message} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -236,7 +217,7 @@ const SettingsEntities: FC = () => {
|
|||||||
<Row key={ei.name} item={ei} onClick={() => editEntityItem(ei)}>
|
<Row key={ei.name} item={ei} onClick={() => editEntityItem(ei)}>
|
||||||
<Cell>{ei.name}</Cell>
|
<Cell>{ei.name}</Cell>
|
||||||
<Cell>{showHex(ei.device_id as number, 2)}</Cell>
|
<Cell>{showHex(ei.device_id as number, 2)}</Cell>
|
||||||
<Cell>{showHex(ei.type_id as number, 4)}</Cell>
|
<Cell>{showHex(ei.type_id as number, 3)}</Cell>
|
||||||
<Cell>{ei.offset}</Cell>
|
<Cell>{ei.offset}</Cell>
|
||||||
<Cell>{formatValue(ei.value, ei.uom)}</Cell>
|
<Cell>{formatValue(ei.value, ei.uom)}</Cell>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -272,7 +253,7 @@ const SettingsEntities: FC = () => {
|
|||||||
<Box flexGrow={1}>
|
<Box flexGrow={1}>
|
||||||
{numChanges > 0 && (
|
{numChanges > 0 && (
|
||||||
<ButtonRow>
|
<ButtonRow>
|
||||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={fetchEntities} color="secondary">
|
<Button startIcon={<CancelIcon />} variant="outlined" onClick={onDialogCancel} color="secondary">
|
||||||
{LL.CANCEL()}
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { DeviceValueUOM_s } from './types';
|
import { DeviceValueUOM_s, DeviceValueType } from './types';
|
||||||
import type { EntityItem } from './types';
|
import type { EntityItem } from './types';
|
||||||
import type Schema from 'async-validator';
|
import type Schema from 'async-validator';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
import type { ValidateFieldsError } from 'async-validator';
|
||||||
@@ -58,8 +58,8 @@ const SettingsEntitiesDialog = ({
|
|||||||
// convert to hex strings straight away
|
// convert to hex strings straight away
|
||||||
setEditItem({
|
setEditItem({
|
||||||
...selectedItem,
|
...selectedItem,
|
||||||
device_id: selectedItem.device_id.toString(16).toUpperCase().slice(-2),
|
device_id: selectedItem.device_id.toString(16).toUpperCase(),
|
||||||
type_id: selectedItem.type_id.toString(16).toUpperCase().slice(-4)
|
type_id: selectedItem.type_id.toString(16).toUpperCase()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [open, selectedItem]);
|
}, [open, selectedItem]);
|
||||||
@@ -166,17 +166,18 @@ const SettingsEntitiesDialog = ({
|
|||||||
fullWidth
|
fullWidth
|
||||||
select
|
select
|
||||||
>
|
>
|
||||||
<MenuItem value={0}>BOOL</MenuItem>
|
<MenuItem value={DeviceValueType.BOOL}>BOOL</MenuItem>
|
||||||
<MenuItem value={1}>INT</MenuItem>
|
<MenuItem value={DeviceValueType.INT}>INT</MenuItem>
|
||||||
<MenuItem value={2}>UINT</MenuItem>
|
<MenuItem value={DeviceValueType.UINT}>UINT</MenuItem>
|
||||||
<MenuItem value={3}>SHORT</MenuItem>
|
<MenuItem value={DeviceValueType.SHORT}>SHORT</MenuItem>
|
||||||
<MenuItem value={4}>USHORT</MenuItem>
|
<MenuItem value={DeviceValueType.USHORT}>USHORT</MenuItem>
|
||||||
<MenuItem value={5}>ULONG</MenuItem>
|
<MenuItem value={DeviceValueType.ULONG}>ULONG</MenuItem>
|
||||||
<MenuItem value={6}>TIME</MenuItem>
|
<MenuItem value={DeviceValueType.TIME}>TIME</MenuItem>
|
||||||
|
<MenuItem value={DeviceValueType.STRING}>RAW</MenuItem>
|
||||||
</TextField>
|
</TextField>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{editItem.value_type !== 0 && (
|
{editItem.value_type !== DeviceValueType.BOOL && editItem.value_type !== DeviceValueType.STRING && (
|
||||||
<>
|
<>
|
||||||
<Grid item xs={4}>
|
<Grid item xs={4}>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -210,6 +211,21 @@ const SettingsEntitiesDialog = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{editItem.value_type === DeviceValueType.STRING && (
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<TextField
|
||||||
|
name="factor"
|
||||||
|
label="Bytes"
|
||||||
|
value={editItem.factor}
|
||||||
|
variant="outlined"
|
||||||
|
onChange={updateFormValue}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
type="number"
|
||||||
|
inputProps={{ min: '1', max: '27', step: '1' }}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import WarningIcon from '@mui/icons-material/Warning';
|
|||||||
import { Box, Typography, Divider, Stack, Button } from '@mui/material';
|
import { Box, Typography, Divider, Stack, Button } from '@mui/material';
|
||||||
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
import { Table, Header, HeaderRow, HeaderCell, Body, Row, Cell } from '@table-library/react-table-library/table';
|
||||||
import { useTheme } from '@table-library/react-table-library/theme';
|
import { useTheme } from '@table-library/react-table-library/theme';
|
||||||
|
// eslint-disable-next-line import/named
|
||||||
|
import { updateState, useRequest } from 'alova';
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@@ -19,23 +21,31 @@ import type { FC } from 'react';
|
|||||||
import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components';
|
import { ButtonRow, FormLoader, SectionContent, BlockNavigation } from 'components';
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { extractErrorMessage } from 'utils';
|
|
||||||
|
|
||||||
const SettingsScheduler: FC = () => {
|
const SettingsScheduler: FC = () => {
|
||||||
const { LL, locale } = useI18nContext();
|
const { LL, locale } = useI18nContext();
|
||||||
const [numChanges, setNumChanges] = useState<number>(0);
|
const [numChanges, setNumChanges] = useState<number>(0);
|
||||||
const blocker = useBlocker(numChanges !== 0);
|
const blocker = useBlocker(numChanges !== 0);
|
||||||
const [schedule, setSchedule] = useState<ScheduleItem[]>([]);
|
|
||||||
const [selectedScheduleItem, setSelectedScheduleItem] = useState<ScheduleItem>();
|
const [selectedScheduleItem, setSelectedScheduleItem] = useState<ScheduleItem>();
|
||||||
const [dow, setDow] = useState<string[]>([]);
|
const [dow, setDow] = useState<string[]>([]);
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
|
||||||
const [creating, setCreating] = useState<boolean>(false);
|
const [creating, setCreating] = useState<boolean>(false);
|
||||||
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: schedule,
|
||||||
|
send: fetchSchedule,
|
||||||
|
error
|
||||||
|
} = useRequest(EMSESP.readSchedule, {
|
||||||
|
initialData: [],
|
||||||
|
force: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const { send: writeSchedule } = useRequest((data) => EMSESP.writeSchedule(data), { immediate: false });
|
||||||
|
|
||||||
function hasScheduleChanged(si: ScheduleItem) {
|
function hasScheduleChanged(si: ScheduleItem) {
|
||||||
return (
|
return (
|
||||||
si.id !== si.o_id ||
|
si.id !== si.o_id ||
|
||||||
(si?.name || '') !== (si?.o_name || '') ||
|
(si.name || '') !== (si.o_name || '') ||
|
||||||
si.active !== si.o_active ||
|
si.active !== si.o_active ||
|
||||||
si.deleted !== si.o_deleted ||
|
si.deleted !== si.o_deleted ||
|
||||||
si.flags !== si.o_flags ||
|
si.flags !== si.o_flags ||
|
||||||
@@ -46,10 +56,13 @@ const SettingsScheduler: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (schedule) {
|
const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' });
|
||||||
setNumChanges(schedule ? schedule.filter((si) => hasScheduleChanged(si)).length : 0);
|
const days = [1, 2, 3, 4, 5, 6, 7].map((day) => {
|
||||||
}
|
const dd = day < 10 ? `0${day}` : day;
|
||||||
}, [schedule]);
|
return new Date(`2017-01-${dd}T00:00:00+00:00`);
|
||||||
|
});
|
||||||
|
setDow(days.map((date) => formatter.format(date)));
|
||||||
|
}, [locale]);
|
||||||
|
|
||||||
const schedule_theme = useTheme({
|
const schedule_theme = useTheme({
|
||||||
Table: `
|
Table: `
|
||||||
@@ -96,63 +109,30 @@ const SettingsScheduler: FC = () => {
|
|||||||
`
|
`
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchSchedule = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const response = await EMSESP.readSchedule();
|
|
||||||
setSchedule(
|
|
||||||
response.data.schedule.map((si) => ({
|
|
||||||
...si,
|
|
||||||
o_id: si.id,
|
|
||||||
o_active: si.active,
|
|
||||||
o_deleted: si.deleted,
|
|
||||||
o_flags: si.flags,
|
|
||||||
o_time: si.time,
|
|
||||||
o_cmd: si.cmd,
|
|
||||||
o_value: si.value,
|
|
||||||
o_name: si.name
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
|
|
||||||
}
|
|
||||||
}, [LL]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
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`);
|
|
||||||
});
|
|
||||||
setDow(days.map((date) => formatter.format(date)));
|
|
||||||
void fetchSchedule();
|
|
||||||
}, [locale, fetchSchedule]);
|
|
||||||
|
|
||||||
const saveSchedule = async () => {
|
const saveSchedule = async () => {
|
||||||
if (schedule) {
|
await writeSchedule({
|
||||||
try {
|
schedule: schedule
|
||||||
const response = await EMSESP.writeSchedule({
|
.filter((si) => !si.deleted)
|
||||||
schedule: schedule
|
.map((condensed_si) => ({
|
||||||
.filter((si) => !si.deleted)
|
id: condensed_si.id,
|
||||||
.map((condensed_si) => ({
|
active: condensed_si.active,
|
||||||
id: condensed_si.id,
|
flags: condensed_si.flags,
|
||||||
active: condensed_si.active,
|
time: condensed_si.time,
|
||||||
flags: condensed_si.flags,
|
cmd: condensed_si.cmd,
|
||||||
time: condensed_si.time,
|
value: condensed_si.value,
|
||||||
cmd: condensed_si.cmd,
|
name: condensed_si.name
|
||||||
value: condensed_si.value,
|
}))
|
||||||
name: condensed_si.name
|
})
|
||||||
}))
|
.then(() => {
|
||||||
});
|
toast.success(LL.SCHEDULE_UPDATED());
|
||||||
if (response.status === 200) {
|
})
|
||||||
toast.success(LL.SCHEDULE_UPDATED());
|
.catch((err) => {
|
||||||
} else {
|
toast.error(err.message);
|
||||||
toast.error(LL.PROBLEM_UPDATING());
|
})
|
||||||
}
|
.finally(async () => {
|
||||||
await fetchSchedule();
|
await fetchSchedule();
|
||||||
} catch (error) {
|
setNumChanges(0);
|
||||||
toast.error(extractErrorMessage(error, LL.PROBLEM_UPDATING()));
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const editScheduleItem = useCallback((si: ScheduleItem) => {
|
const editScheduleItem = useCallback((si: ScheduleItem) => {
|
||||||
@@ -165,13 +145,22 @@ const SettingsScheduler: FC = () => {
|
|||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDialogCancel = async () => {
|
||||||
|
await fetchSchedule().then(() => {
|
||||||
|
setNumChanges(0);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onDialogSave = (updatedItem: ScheduleItem) => {
|
const onDialogSave = (updatedItem: ScheduleItem) => {
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
if (schedule && creating) {
|
|
||||||
setSchedule([...schedule.filter((si) => creating || si.o_id !== updatedItem.o_id), updatedItem]);
|
updateState('schedule', (data) => {
|
||||||
} else {
|
const new_data = creating
|
||||||
setSchedule(schedule?.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);
|
||||||
|
return new_data;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const addScheduleItem = () => {
|
const addScheduleItem = () => {
|
||||||
@@ -191,7 +180,7 @@ const SettingsScheduler: FC = () => {
|
|||||||
|
|
||||||
const renderSchedule = () => {
|
const renderSchedule = () => {
|
||||||
if (!schedule) {
|
if (!schedule) {
|
||||||
return <FormLoader errorMessage={errorMessage} />;
|
return <FormLoader onRetry={fetchSchedule} errorMessage={error?.message} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dayBox = (si: ScheduleItem, flag: number) => (
|
const dayBox = (si: ScheduleItem, flag: number) => (
|
||||||
@@ -270,7 +259,7 @@ const SettingsScheduler: FC = () => {
|
|||||||
onClose={onDialogClose}
|
onClose={onDialogClose}
|
||||||
onSave={onDialogSave}
|
onSave={onDialogSave}
|
||||||
selectedItem={selectedScheduleItem}
|
selectedItem={selectedScheduleItem}
|
||||||
validator={schedulerItemValidation()}
|
validator={schedulerItemValidation(schedule, selectedScheduleItem)}
|
||||||
dow={dow}
|
dow={dow}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -279,7 +268,7 @@ const SettingsScheduler: FC = () => {
|
|||||||
<Box flexGrow={1}>
|
<Box flexGrow={1}>
|
||||||
{numChanges !== 0 && (
|
{numChanges !== 0 && (
|
||||||
<ButtonRow>
|
<ButtonRow>
|
||||||
<Button startIcon={<CancelIcon />} variant="outlined" onClick={fetchSchedule} color="secondary">
|
<Button startIcon={<CancelIcon />} variant="outlined" onClick={onDialogCancel} color="secondary">
|
||||||
{LL.CANCEL()}
|
{LL.CANCEL()}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ const SettingsSchedulerDialog = ({
|
|||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
name="value"
|
name="value"
|
||||||
label={LL.VALUE(0)}
|
label={LL.VALUE(1)}
|
||||||
multiline
|
multiline
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@@ -1,121 +1,107 @@
|
|||||||
import type {
|
import type {
|
||||||
BoardProfile,
|
|
||||||
BoardProfileName,
|
|
||||||
APIcall,
|
APIcall,
|
||||||
Settings,
|
Settings,
|
||||||
Status,
|
Status,
|
||||||
CoreData,
|
CoreData,
|
||||||
Devices,
|
Devices,
|
||||||
DeviceData,
|
|
||||||
DeviceEntity,
|
DeviceEntity,
|
||||||
UniqueID,
|
|
||||||
CustomEntities,
|
|
||||||
WriteDeviceValue,
|
|
||||||
WriteTemperatureSensor,
|
WriteTemperatureSensor,
|
||||||
WriteAnalogSensor,
|
WriteAnalogSensor,
|
||||||
SensorData,
|
SensorData,
|
||||||
Schedule,
|
Entities,
|
||||||
Entities
|
DeviceData,
|
||||||
|
ScheduleItem,
|
||||||
|
EntityItem
|
||||||
} from './types';
|
} from './types';
|
||||||
import type { AxiosPromise } from 'axios';
|
import { alovaInstance } from 'api/endpoints';
|
||||||
import { AXIOS, AXIOS_API, AXIOS_BIN } from 'api/endpoints';
|
|
||||||
|
|
||||||
export function restart(): AxiosPromise<void> {
|
// DashboardDevices
|
||||||
return AXIOS.post('/restart');
|
export const readCoreData = () => alovaInstance.Get<CoreData>(`/rest/coreData`);
|
||||||
}
|
export const readDeviceData = (id: number) =>
|
||||||
|
alovaInstance.Get<DeviceData>('/rest/deviceData', {
|
||||||
|
params: { id },
|
||||||
|
responseType: 'arraybuffer' // uses msgpack
|
||||||
|
});
|
||||||
|
export const writeDeviceValue = (data: any) => alovaInstance.Post('/rest/writeDeviceValue', data);
|
||||||
|
|
||||||
export function readSettings(): AxiosPromise<Settings> {
|
// SettingsApplication
|
||||||
return AXIOS.get('/settings');
|
export const readSettings = () => alovaInstance.Get<Settings>('/rest/settings');
|
||||||
}
|
export const writeSettings = (data: any) => alovaInstance.Post('/rest/settings', data);
|
||||||
|
export const getBoardProfile = (boardProfile: string) =>
|
||||||
|
alovaInstance.Get('/rest/boardProfile', {
|
||||||
|
params: { boardProfile }
|
||||||
|
});
|
||||||
|
|
||||||
export function writeSettings(settings: Settings): AxiosPromise<Settings> {
|
// DashboardSensors
|
||||||
return AXIOS.post('/settings', settings);
|
export const readSensorData = () => alovaInstance.Get<SensorData>('/rest/sensorData');
|
||||||
}
|
export const writeTemperatureSensor = (ts: WriteTemperatureSensor) =>
|
||||||
|
alovaInstance.Post('/rest/writeTemperatureSensor', ts);
|
||||||
|
export const writeAnalogSensor = (as: WriteAnalogSensor) => alovaInstance.Post('/rest/writeAnalogSensor', as);
|
||||||
|
|
||||||
export function getBoardProfile(boardProfile: BoardProfileName): AxiosPromise<BoardProfile> {
|
// DashboardStatus
|
||||||
return AXIOS.post('/boardProfile', boardProfile);
|
export const readStatus = () => alovaInstance.Get<Status>('/rest/status');
|
||||||
}
|
export const scanDevices = () => alovaInstance.Post('/rest/scanDevices');
|
||||||
|
|
||||||
export function readStatus(): AxiosPromise<Status> {
|
// HelpInformation
|
||||||
return AXIOS.get('/status');
|
export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall);
|
||||||
}
|
|
||||||
|
|
||||||
export function readCoreData(): AxiosPromise<CoreData> {
|
// UploadFileForm
|
||||||
return AXIOS.get('/coreData');
|
export const getSettings = () => alovaInstance.Get('/rest/getSettings');
|
||||||
}
|
export const getCustomizations = () => alovaInstance.Get('/rest/getCustomizations');
|
||||||
|
export const getEntities = () => alovaInstance.Get<Entities>('/rest/getEntities');
|
||||||
|
export const getSchedule = () => alovaInstance.Get('/rest/getSchedule');
|
||||||
|
|
||||||
export function readDevices(): AxiosPromise<Devices> {
|
// SettingsCustomization
|
||||||
return AXIOS.get('/devices');
|
export const readDeviceEntities = (id: number) =>
|
||||||
}
|
alovaInstance.Get<DeviceEntity[]>('/rest/deviceEntities', {
|
||||||
|
params: { id },
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
transformData(data: any) {
|
||||||
|
return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export const readDevices = () => alovaInstance.Get<Devices>('/rest/devices');
|
||||||
|
export const resetCustomizations = () => alovaInstance.Post('/rest/resetCustomizations');
|
||||||
|
export const writeCustomEntities = (data: any) => alovaInstance.Post('/rest/customEntities', data);
|
||||||
|
|
||||||
export function scanDevices(): AxiosPromise<void> {
|
// SettingsScheduler
|
||||||
return AXIOS.post('/scanDevices');
|
export const readSchedule = () =>
|
||||||
}
|
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
|
||||||
|
name: 'schedule',
|
||||||
|
transformData(data: any) {
|
||||||
|
return data.schedule.map((si: ScheduleItem) => ({
|
||||||
|
...si,
|
||||||
|
o_id: si.id,
|
||||||
|
o_active: si.active,
|
||||||
|
o_deleted: si.deleted,
|
||||||
|
o_flags: si.flags,
|
||||||
|
o_time: si.time,
|
||||||
|
o_cmd: si.cmd,
|
||||||
|
o_value: si.value,
|
||||||
|
o_name: si.name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export const writeSchedule = (data: any) => alovaInstance.Post('/rest/schedule', data);
|
||||||
|
|
||||||
export function readDeviceData(unique_id: UniqueID): AxiosPromise<DeviceData> {
|
// SettingsEntities
|
||||||
return AXIOS_BIN.post('/deviceData', unique_id);
|
export const readEntities = () =>
|
||||||
}
|
alovaInstance.Get<EntityItem[]>('/rest/entities', {
|
||||||
|
name: 'entities',
|
||||||
export function readSensorData(): AxiosPromise<SensorData> {
|
transformData(data: any) {
|
||||||
return AXIOS.get('/sensorData');
|
return data.entities.map((ei: EntityItem) => ({
|
||||||
}
|
...ei,
|
||||||
|
o_id: ei.id,
|
||||||
export function readDeviceEntities(unique_id: UniqueID): AxiosPromise<DeviceEntity[]> {
|
o_device_id: ei.device_id,
|
||||||
return AXIOS_BIN.post('/deviceEntities', unique_id);
|
o_type_id: ei.type_id,
|
||||||
}
|
o_offset: ei.offset,
|
||||||
|
o_factor: ei.factor,
|
||||||
export function writeCustomEntities(customEntities: CustomEntities): AxiosPromise<void> {
|
o_uom: ei.uom,
|
||||||
return AXIOS.post('/customEntities', customEntities);
|
o_value_type: ei.value_type,
|
||||||
}
|
o_name: ei.name,
|
||||||
|
o_writeable: ei.writeable,
|
||||||
export function writeDeviceValue(dv: WriteDeviceValue): AxiosPromise<void> {
|
o_deleted: ei.deleted
|
||||||
return AXIOS.post('/writeDeviceValue', dv);
|
}));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
export function writeTemperatureSensor(ts: WriteTemperatureSensor): AxiosPromise<void> {
|
export const writeEntities = (data: any) => alovaInstance.Post('/rest/entities', data);
|
||||||
return AXIOS.post('/writeTemperatureSensor', ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function writeAnalogSensor(as: WriteAnalogSensor): AxiosPromise<void> {
|
|
||||||
return AXIOS.post('/writeAnalogSensor', as);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resetCustomizations(): AxiosPromise<void> {
|
|
||||||
return AXIOS.post('/resetCustomizations');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function API(apiCall: APIcall): AxiosPromise<void> {
|
|
||||||
return AXIOS_API.post('/', apiCall);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSettings(): AxiosPromise<void> {
|
|
||||||
return AXIOS.get('/getSettings');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCustomizations(): AxiosPromise<void> {
|
|
||||||
return AXIOS.get('/getCustomizations');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSchedule(): AxiosPromise<Schedule> {
|
|
||||||
return AXIOS.get('/getSchedule');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readSchedule(): AxiosPromise<Schedule> {
|
|
||||||
return AXIOS.get('/schedule');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function writeSchedule(schedule: Schedule): AxiosPromise<void> {
|
|
||||||
return AXIOS.post('/schedule', schedule);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEntities(): AxiosPromise<Entities> {
|
|
||||||
return AXIOS.get('/getEntities');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readEntities(): AxiosPromise<Entities> {
|
|
||||||
return AXIOS.get('/entities');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function writeEntities(entities: Entities): AxiosPromise<void> {
|
|
||||||
return AXIOS.post('/entities', entities);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ export interface DeviceValue {
|
|||||||
m?: number; // min, optional
|
m?: number; // min, optional
|
||||||
x?: number; // max, optional
|
x?: number; // max, optional
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceData {
|
export interface DeviceData {
|
||||||
data: DeviceValue[];
|
data: DeviceValue[];
|
||||||
}
|
}
|
||||||
@@ -151,15 +150,6 @@ export interface DeviceEntity {
|
|||||||
o_ma?: number; // original max value
|
o_ma?: number; // original max value
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomEntities {
|
|
||||||
id: number;
|
|
||||||
entity_ids: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UniqueID {
|
|
||||||
id: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum DeviceValueUOM {
|
export enum DeviceValueUOM {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
DEGREES,
|
DEGREES,
|
||||||
@@ -253,13 +243,10 @@ export const BOARD_PROFILES: BoardProfiles = {
|
|||||||
OLIMEXPOE: 'Olimex ESP32-POE',
|
OLIMEXPOE: 'Olimex ESP32-POE',
|
||||||
C3MINI: 'Wemos C3 Mini',
|
C3MINI: 'Wemos C3 Mini',
|
||||||
S2MINI: 'Wemos S2 Mini',
|
S2MINI: 'Wemos S2 Mini',
|
||||||
S3MINI: 'Liligo S3'
|
S3MINI: 'Liligo S3',
|
||||||
|
S32S3: 'BBQKees Gateway S3'
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface BoardProfileName {
|
|
||||||
board_profile: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BoardProfile {
|
export interface BoardProfile {
|
||||||
board_profile: string;
|
board_profile: string;
|
||||||
led_gpio: number;
|
led_gpio: number;
|
||||||
@@ -278,12 +265,6 @@ export interface APIcall {
|
|||||||
entity: string;
|
entity: string;
|
||||||
id: any;
|
id: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WriteDeviceValue {
|
|
||||||
id: number;
|
|
||||||
devicevalue: DeviceValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WriteAnalogSensor {
|
export interface WriteAnalogSensor {
|
||||||
id: number;
|
id: number;
|
||||||
gpio: number;
|
gpio: number;
|
||||||
@@ -312,7 +293,7 @@ export interface ScheduleItem {
|
|||||||
time: string;
|
time: string;
|
||||||
cmd: string;
|
cmd: string;
|
||||||
value: string;
|
value: string;
|
||||||
name?: string; // optional
|
name: string; // optional
|
||||||
o_id?: number;
|
o_id?: number;
|
||||||
o_active?: boolean;
|
o_active?: boolean;
|
||||||
o_deleted?: boolean;
|
o_deleted?: boolean;
|
||||||
@@ -323,10 +304,6 @@ export interface ScheduleItem {
|
|||||||
o_name?: string;
|
o_name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Schedule {
|
|
||||||
schedule: ScheduleItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ScheduleFlag {
|
export enum ScheduleFlag {
|
||||||
SCHEDULE_SUN = 1,
|
SCHEDULE_SUN = 1,
|
||||||
SCHEDULE_MON = 2,
|
SCHEDULE_MON = 2,
|
||||||
@@ -388,3 +365,17 @@ export const enum DeviceType {
|
|||||||
CUSTOM,
|
CUSTOM,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// matches emsdevicevalue.h
|
||||||
|
export const enum DeviceValueType {
|
||||||
|
BOOL,
|
||||||
|
INT,
|
||||||
|
UINT,
|
||||||
|
SHORT,
|
||||||
|
USHORT,
|
||||||
|
ULONG,
|
||||||
|
TIME, // same as ULONG (32 bits)
|
||||||
|
ENUM,
|
||||||
|
STRING,
|
||||||
|
CMD
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Schema from 'async-validator';
|
import Schema from 'async-validator';
|
||||||
import type { AnalogSensor, DeviceValue, Settings } from './types';
|
import type { AnalogSensor, DeviceValue, ScheduleItem, Settings } from './types';
|
||||||
import type { InternalRuleItem } from 'async-validator';
|
import type { InternalRuleItem } from 'async-validator';
|
||||||
import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared';
|
import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared';
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ export const GPIO_VALIDATOR = {
|
|||||||
if (
|
if (
|
||||||
value &&
|
value &&
|
||||||
(value === 1 ||
|
(value === 1 ||
|
||||||
(value >= 10 && value <= 12) ||
|
(value >= 6 && value <= 12) ||
|
||||||
(value >= 14 && value <= 15) ||
|
(value >= 14 && value <= 15) ||
|
||||||
value === 20 ||
|
value === 20 ||
|
||||||
value === 24 ||
|
value === 24 ||
|
||||||
@@ -43,6 +43,23 @@ export const GPIO_VALIDATORS2 = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GPIO_VALIDATORS3 = {
|
||||||
|
validator(rule: InternalRuleItem, value: number, callback: (error?: string) => void) {
|
||||||
|
if (
|
||||||
|
value &&
|
||||||
|
((value >= 19 && value <= 20) ||
|
||||||
|
(value >= 22 && value <= 37) ||
|
||||||
|
(value >= 39 && value <= 42) ||
|
||||||
|
value > 48 ||
|
||||||
|
value < 0)
|
||||||
|
) {
|
||||||
|
callback('Must be an valid GPIO port');
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const createSettingsValidator = (settings: Settings) =>
|
export const createSettingsValidator = (settings: Settings) =>
|
||||||
new Schema({
|
new Schema({
|
||||||
...(settings.board_profile === 'CUSTOM' &&
|
...(settings.board_profile === 'CUSTOM' &&
|
||||||
@@ -69,6 +86,14 @@ export const createSettingsValidator = (settings: Settings) =>
|
|||||||
tx_gpio: [{ required: true, message: 'Tx 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]
|
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]
|
||||||
|
}),
|
||||||
...(settings.syslog_enabled && {
|
...(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: [
|
syslog_port: [
|
||||||
@@ -86,14 +111,26 @@ export const createSettingsValidator = (settings: Settings) =>
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export const schedulerItemValidation = () =>
|
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)) {
|
||||||
|
callback('Name already in use');
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const schedulerItemValidation = (schedule: ScheduleItem[], scheduleItem: ScheduleItem) =>
|
||||||
new Schema({
|
new Schema({
|
||||||
name: [
|
name: [
|
||||||
{
|
{
|
||||||
|
required: true,
|
||||||
type: 'string',
|
type: 'string',
|
||||||
pattern: /^[a-zA-Z0-9_\\.]{0,15}$/,
|
pattern: /^[a-zA-Z0-9_\\.]{0,15}$/,
|
||||||
message: "Must be <15 characters: alpha numeric, '_' or '.'"
|
message: "Must be <15 characters: alpha numeric, '_' or '.'"
|
||||||
}
|
},
|
||||||
|
...[uniqueNameValidator(schedule, scheduleItem.o_name)]
|
||||||
],
|
],
|
||||||
cmd: [
|
cmd: [
|
||||||
{ required: true, message: 'Command is required' },
|
{ required: true, message: 'Command is required' },
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
export interface Features {
|
export interface Features {
|
||||||
project: boolean;
|
version: string;
|
||||||
security: boolean;
|
platform: string; // "ESP32-C3" "ESP32-S2" "ESP32-S3" "ESP32"
|
||||||
mqtt: boolean;
|
|
||||||
ntp: boolean;
|
|
||||||
ota: boolean;
|
|
||||||
upload_firmware: boolean;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
export enum MqttDisconnectReason {
|
export enum MqttDisconnectReason {
|
||||||
TCP_DISCONNECTED = 0,
|
USER_OK = 0,
|
||||||
MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1,
|
MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1,
|
||||||
MQTT_IDENTIFIER_REJECTED = 2,
|
MQTT_IDENTIFIER_REJECTED = 2,
|
||||||
MQTT_SERVER_UNAVAILABLE = 3,
|
MQTT_SERVER_UNAVAILABLE = 3,
|
||||||
MQTT_MALFORMED_CREDENTIALS = 4,
|
MQTT_MALFORMED_CREDENTIALS = 4,
|
||||||
MQTT_NOT_AUTHORIZED = 5,
|
MQTT_NOT_AUTHORIZED = 5,
|
||||||
ESP8266_NOT_ENOUGH_SPACE = 6,
|
TLS_BAD_FINGERPRINT = 6,
|
||||||
TLS_BAD_FINGERPRINT = 7
|
TCP_DISCONNECTED = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MqttStatus {
|
export interface MqttStatus {
|
||||||
@@ -24,6 +24,7 @@ export interface MqttSettings {
|
|||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
base: string;
|
base: string;
|
||||||
|
rootCA?: string;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
client_id: string;
|
client_id: string;
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ export enum WiFiEncryptionType {
|
|||||||
WIFI_AUTH_WPA_PSK = 2,
|
WIFI_AUTH_WPA_PSK = 2,
|
||||||
WIFI_AUTH_WPA2_PSK = 3,
|
WIFI_AUTH_WPA2_PSK = 3,
|
||||||
WIFI_AUTH_WPA_WPA2_PSK = 4,
|
WIFI_AUTH_WPA_WPA2_PSK = 4,
|
||||||
WIFI_AUTH_WPA2_ENTERPRISE = 5
|
WIFI_AUTH_WPA2_ENTERPRISE = 5,
|
||||||
|
WIFI_AUTH_WPA3_PSK = 6,
|
||||||
|
WIFI_AUTH_WPA2_WPA3_PSK = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NetworkStatus {
|
export interface NetworkStatus {
|
||||||
|
|||||||
@@ -42,10 +42,6 @@ export interface LogEntry {
|
|||||||
m: string;
|
m: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LogEntries {
|
|
||||||
events: LogEntry[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LogSettings {
|
export interface LogSettings {
|
||||||
level: number;
|
level: number;
|
||||||
max_messages: number;
|
max_messages: number;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
type UpdateEntity<S> = (state: (prevState: Readonly<S>) => S) => void;
|
|
||||||
|
|
||||||
export const numberValue = (value: number) => (isNaN(value) ? '' : value.toString());
|
export const numberValue = (value: number) => (isNaN(value) ? '' : value.toString());
|
||||||
|
|
||||||
export const extractEventValue = (event: React.ChangeEvent<HTMLInputElement>) => {
|
export const extractEventValue = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -13,6 +11,8 @@ export const extractEventValue = (event: React.ChangeEvent<HTMLInputElement>) =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UpdateEntity<S> = (state: (prevState: Readonly<S>) => S) => void;
|
||||||
|
|
||||||
export const updateValue =
|
export const updateValue =
|
||||||
<S>(updateEntity: UpdateEntity<S>) =>
|
<S>(updateEntity: UpdateEntity<S>) =>
|
||||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -23,11 +23,12 @@ export const updateValue =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const updateValueDirty =
|
export const updateValueDirty =
|
||||||
<S>(origData: any, dirtyFlags: any, setDirtyFlags: any, updateEntity: UpdateEntity<S>) =>
|
(origData: any, dirtyFlags: any, setDirtyFlags: any, updateDataValue: any) =>
|
||||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const updated_value = extractEventValue(event);
|
const updated_value = extractEventValue(event);
|
||||||
const name = event.target.name;
|
const name = event.target.name;
|
||||||
updateEntity((prevState) => ({
|
|
||||||
|
updateDataValue((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
[name]: updated_value
|
[name]: updated_value
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
export const extractErrorMessage = (error: any, defaultMessage: string) => {
|
|
||||||
if (error.request) {
|
|
||||||
return defaultMessage + ' (' + error.request.status + ': ' + error.request.statusText + ')';
|
|
||||||
} else if (error instanceof Error) {
|
|
||||||
return defaultMessage + ' (' + error.message + ')';
|
|
||||||
}
|
|
||||||
return defaultMessage;
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
export * from './binding';
|
export * from './binding';
|
||||||
export * from './endpoints';
|
|
||||||
export * from './route';
|
export * from './route';
|
||||||
export * from './submit';
|
export * from './submit';
|
||||||
export * from './time';
|
export * from './time';
|
||||||
|
|||||||
@@ -1,85 +1,78 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useRequest, type Method } from 'alova';
|
||||||
|
import { useState } from 'react';
|
||||||
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
import { unstable_useBlocker as useBlocker } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { extractErrorMessage } from '.';
|
|
||||||
import type { AxiosPromise } from 'axios';
|
|
||||||
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
export interface RestRequestOptions<D> {
|
export interface RestRequestOptions2<D> {
|
||||||
read: () => AxiosPromise<D>;
|
read: () => Method<any, any, any, any, any, any, any>;
|
||||||
update?: (value: D) => AxiosPromise<D>;
|
update: (value: D) => Method<any, any, any, any, any, any, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useRest = <D>({ read, update }: RestRequestOptions<D>) => {
|
export const useRest = <D>({ read, update }: RestRequestOptions2<D>) => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|
||||||
const [data, setData] = useState<D>();
|
|
||||||
const [saving, setSaving] = useState<boolean>(false);
|
|
||||||
const [errorMessage, setErrorMessage] = useState<string>();
|
const [errorMessage, setErrorMessage] = useState<string>();
|
||||||
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
|
const [restartNeeded, setRestartNeeded] = useState<boolean>(false);
|
||||||
|
|
||||||
const [origData, setOrigData] = useState<D>();
|
const [origData, setOrigData] = useState<D>();
|
||||||
const [dirtyFlags, setDirtyFlags] = useState<string[]>();
|
|
||||||
|
|
||||||
const blocker = useBlocker(dirtyFlags?.length !== 0);
|
const [dirtyFlags, setDirtyFlags] = useState<string[]>([]);
|
||||||
|
const blocker = useBlocker(dirtyFlags.length !== 0);
|
||||||
|
|
||||||
const loadData = useCallback(async () => {
|
const { data: data, send: readData, update: updateData, onComplete: onReadComplete } = useRequest(read());
|
||||||
setData(undefined);
|
|
||||||
|
const {
|
||||||
|
loading: saving,
|
||||||
|
send: writeData,
|
||||||
|
onSuccess: onWriteSuccess
|
||||||
|
} = useRequest((newData: D) => update(newData), { immediate: false });
|
||||||
|
|
||||||
|
const updateDataValue = (new_data: D) => {
|
||||||
|
updateData({ data: new_data });
|
||||||
|
};
|
||||||
|
|
||||||
|
onWriteSuccess(() => {
|
||||||
|
toast.success(LL.UPDATED_OF(LL.SETTINGS()));
|
||||||
|
setDirtyFlags([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
onReadComplete((event) => {
|
||||||
|
setOrigData(event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
setDirtyFlags([]);
|
setDirtyFlags([]);
|
||||||
setErrorMessage(undefined);
|
setErrorMessage(undefined);
|
||||||
try {
|
await readData().catch((error) => {
|
||||||
const fetch_data = (await read()).data;
|
toast.error(error.message);
|
||||||
setData(fetch_data);
|
setErrorMessage(error.message);
|
||||||
setOrigData(fetch_data);
|
});
|
||||||
} catch (error) {
|
};
|
||||||
const message = extractErrorMessage(error, LL.PROBLEM_LOADING());
|
|
||||||
toast.error(message);
|
const saveData = async () => {
|
||||||
setErrorMessage(message);
|
if (!data) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}, [read, LL]);
|
setRestartNeeded(false);
|
||||||
|
setErrorMessage(undefined);
|
||||||
const save = useCallback(
|
setDirtyFlags([]);
|
||||||
async (toSave: D) => {
|
setOrigData(data);
|
||||||
if (!update) {
|
await writeData(data).catch((error) => {
|
||||||
return;
|
if (error.message === 'Reboot required') {
|
||||||
|
setRestartNeeded(true);
|
||||||
|
} else {
|
||||||
|
toast.error(error.message);
|
||||||
|
setErrorMessage(error.message);
|
||||||
}
|
}
|
||||||
setSaving(true);
|
});
|
||||||
setRestartNeeded(false);
|
};
|
||||||
setErrorMessage(undefined);
|
|
||||||
try {
|
|
||||||
const response = await update(toSave);
|
|
||||||
setOrigData(response.data);
|
|
||||||
setData(response.data);
|
|
||||||
if (response.status === 202) {
|
|
||||||
setRestartNeeded(true);
|
|
||||||
} else {
|
|
||||||
toast.success(LL.UPDATED_OF(LL.SETTINGS()));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const message = extractErrorMessage(error, LL.PROBLEM_UPDATING());
|
|
||||||
toast.error(message);
|
|
||||||
setErrorMessage(message);
|
|
||||||
} finally {
|
|
||||||
setSaving(false);
|
|
||||||
setDirtyFlags([]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[update, LL]
|
|
||||||
);
|
|
||||||
|
|
||||||
const saveData = () => data && save(data);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void loadData();
|
|
||||||
}, [loadData]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loadData,
|
loadData,
|
||||||
saveData,
|
saveData,
|
||||||
saving,
|
saving,
|
||||||
setData,
|
updateDataValue,
|
||||||
data,
|
data,
|
||||||
origData,
|
origData,
|
||||||
dirtyFlags,
|
dirtyFlags,
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { defineConfig, type PluginOption } from 'vite';
|
import { defineConfig, type PluginOption } from 'vite';
|
||||||
import react from '@vitejs/plugin-react-swc';
|
|
||||||
import viteTsconfigPaths from 'vite-tsconfig-paths';
|
import viteTsconfigPaths from 'vite-tsconfig-paths';
|
||||||
import svgrPlugin from 'vite-plugin-svgr';
|
import svgrPlugin from 'vite-plugin-svgr';
|
||||||
import { visualizer } from 'rollup-plugin-visualizer';
|
import { visualizer } from 'rollup-plugin-visualizer';
|
||||||
import ProgmemGenerator from './progmem-generator';
|
import ProgmemGenerator from './progmem-generator';
|
||||||
|
import preact from '@preact/preset-vite';
|
||||||
|
|
||||||
export default defineConfig(({ command, mode }) => {
|
export default defineConfig(({ command, mode }) => {
|
||||||
if (mode === 'hosted') {
|
if (mode === 'hosted') {
|
||||||
return {
|
return {
|
||||||
// hosted, ignore all errors, output to dist
|
// hosted, ignore all errors, output to dist
|
||||||
plugins: [react(), viteTsconfigPaths(), svgrPlugin(), visualizer({ gzipSize: true }) as PluginOption]
|
plugins: [preact(), viteTsconfigPaths(), svgrPlugin(), visualizer({ gzipSize: true }) as PluginOption]
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// normal build
|
// normal build
|
||||||
return {
|
return {
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
preact(),
|
||||||
viteTsconfigPaths(),
|
viteTsconfigPaths(),
|
||||||
svgrPlugin(),
|
svgrPlugin(),
|
||||||
ProgmemGenerator({ outputPath: '../lib/framework/WWWData.h', bytesPerLine: 20 })
|
ProgmemGenerator({ outputPath: '../lib/framework/WWWData.h', bytesPerLine: 20 })
|
||||||
@@ -26,7 +26,25 @@ export default defineConfig(({ command, mode }) => {
|
|||||||
chunkSizeWarningLimit: 1024,
|
chunkSizeWarningLimit: 1024,
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
manifest: false,
|
manifest: false,
|
||||||
minify: mode === 'development' ? false : 'terser'
|
minify: mode === 'development' ? false : 'terser',
|
||||||
|
rollupOptions: {
|
||||||
|
/**
|
||||||
|
* Ignore "use client" waning since we are not using SSR
|
||||||
|
*/
|
||||||
|
onwarn(warning, warn) {
|
||||||
|
if (warning.code === 'MODULE_LEVEL_DIRECTIVE' && warning.message.includes(`"use client"`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
warn(warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onwarn(warning, warn) {
|
||||||
|
if (warning.code === 'MODULE_LEVEL_DIRECTIVE') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
warn(warning);
|
||||||
},
|
},
|
||||||
|
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
3632
interface/yarn.lock
3632
interface/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,28 @@
|
|||||||
ArduinoJson: change log
|
ArduinoJson: change log
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
v6.21.3 (2023-07-23)
|
||||||
|
-------
|
||||||
|
|
||||||
|
* Fix compatibility with the Blynk libary (issue #1914)
|
||||||
|
* Fix double lookup in `to<JsonVariant>()`
|
||||||
|
* Fix double call to `size()` in `serializeMsgPack()`
|
||||||
|
* Include `ARDUINOJSON_SLOT_OFFSET_SIZE` in the namespace name
|
||||||
|
* Show a link to the documentation when user passes an unsupported input type
|
||||||
|
|
||||||
|
v6.21.2 (2023-04-12)
|
||||||
|
-------
|
||||||
|
|
||||||
|
* Fix compatibility with the Zephyr Project (issue #1905)
|
||||||
|
* Allow using PROGMEM outside of Arduino (issue #1903)
|
||||||
|
* Set default for `ARDUINOJSON_ENABLE_PROGMEM` to `1` on AVR
|
||||||
|
|
||||||
|
v6.21.1 (2023-03-27)
|
||||||
|
-------
|
||||||
|
|
||||||
|
* Double speed of `DynamicJsonDocument::garbageCollect()`
|
||||||
|
* Fix compatibility with GCC 5.2 (issue #1897)
|
||||||
|
|
||||||
v6.21.0 (2023-03-14)
|
v6.21.0 (2023-03-14)
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|||||||
161
lib/ArduinoJson/README.md
Normal file
161
lib/ArduinoJson/README.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://arduinojson.org/"><img alt="ArduinoJson" src="https://arduinojson.org/images/logo.svg" width="200" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A6.x)
|
||||||
|
[](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/6.x)
|
||||||
|
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)
|
||||||
|
[](https://coveralls.io/github/bblanchon/ArduinoJson?branch=6.x)
|
||||||
|
[](https://www.ardu-badge.com/ArduinoJson/6.21.3)
|
||||||
|
[](https://registry.platformio.org/packages/libraries/bblanchon/ArduinoJson?version=6.21.3)
|
||||||
|
[](https://components.espressif.com/components/bblanchon/arduinojson)
|
||||||
|
[](https://github.com/bblanchon/ArduinoJson/stargazers)
|
||||||
|
[](https://github.com/sponsors/bblanchon)
|
||||||
|
|
||||||
|
ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* [JSON deserialization](https://arduinojson.org/v6/api/json/deserializejson/)
|
||||||
|
* [Optionally decodes UTF-16 escape sequences to UTF-8](https://arduinojson.org/v6/api/config/decode_unicode/)
|
||||||
|
* [Optionally stores links to the input buffer (zero-copy)](https://arduinojson.org/v6/api/json/deserializejson/)
|
||||||
|
* [Optionally supports comments in the input](https://arduinojson.org/v6/api/config/enable_comments/)
|
||||||
|
* [Optionally filters the input to keep only desired values](https://arduinojson.org/v6/api/json/deserializejson/#filtering)
|
||||||
|
* Supports single quotes as a string delimiter
|
||||||
|
* Compatible with [NDJSON](http://ndjson.org/) and [JSON Lines](https://jsonlines.org/)
|
||||||
|
* [JSON serialization](https://arduinojson.org/v6/api/json/serializejson/)
|
||||||
|
* [Can write to a buffer or a stream](https://arduinojson.org/v6/api/json/serializejson/)
|
||||||
|
* [Optionally indents the document (prettified JSON)](https://arduinojson.org/v6/api/json/serializejsonpretty/)
|
||||||
|
* [MessagePack serialization](https://arduinojson.org/v6/api/msgpack/serializemsgpack/)
|
||||||
|
* [MessagePack deserialization](https://arduinojson.org/v6/api/msgpack/deserializemsgpack/)
|
||||||
|
* Efficient
|
||||||
|
* [Twice smaller than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
|
||||||
|
* [Almost 10% faster than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
|
||||||
|
* [Consumes roughly 10% less RAM than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
|
||||||
|
* [Fixed memory allocation, no heap fragmentation](https://arduinojson.org/v6/api/jsondocument/)
|
||||||
|
* [Optionally works without heap memory (zero malloc)](https://arduinojson.org/v6/api/staticjsondocument/)
|
||||||
|
* [Deduplicates strings](https://arduinojson.org/news/2020/08/01/version-6-16-0/)
|
||||||
|
* Versatile
|
||||||
|
* Supports [custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v6/how-to/use-external-ram-on-esp32/)
|
||||||
|
* Supports [`String`](https://arduinojson.org/v6/api/config/enable_arduino_string/), [`std::string`](https://arduinojson.org/v6/api/config/enable_std_string/), and [`std::string_view`](https://arduinojson.org/v6/api/config/enable_string_view/)
|
||||||
|
* Supports [`Stream`](https://arduinojson.org/v6/api/config/enable_arduino_stream/) and [`std::istream`/`std::ostream`](https://arduinojson.org/v6/api/config/enable_std_stream/)
|
||||||
|
* Supports [Flash strings](https://arduinojson.org/v6/api/config/enable_progmem/)
|
||||||
|
* Supports [custom readers](https://arduinojson.org/v6/api/json/deserializejson/#custom-reader) and [custom writers](https://arduinojson.org/v6/api/json/serializejson/#custom-writer)
|
||||||
|
* Supports [custom converters](https://arduinojson.org/news/2021/05/04/version-6-18-0/)
|
||||||
|
* Portable
|
||||||
|
* Usable on any C++ project (not limited to Arduino)
|
||||||
|
* Compatible with C++11, C++14 and C++17
|
||||||
|
* Support for C++98/C++03 available on [ArduinoJson 6.20.x](https://github.com/bblanchon/ArduinoJson/tree/6.20.x)
|
||||||
|
* Zero warnings with `-Wall -Wextra -pedantic` and `/W4`
|
||||||
|
* [Header-only library](https://en.wikipedia.org/wiki/Header-only)
|
||||||
|
* Works with virtually any board
|
||||||
|
* Arduino boards: [Uno](https://amzn.to/38aL2ik), [Due](https://amzn.to/36YkWi2), [Micro](https://amzn.to/35WkdwG), [Nano](https://amzn.to/2QTvwRX), [Mega](https://amzn.to/36XWhuf), [Yun](https://amzn.to/30odURc), [Leonardo](https://amzn.to/36XWjlR)...
|
||||||
|
* Espressif chips: [ESP8266](https://amzn.to/36YluV8), [ESP32](https://amzn.to/2G4pRCB)
|
||||||
|
* Lolin (WeMos) boards: [D1 mini](https://amzn.to/2QUpz7q), [D1 Mini Pro](https://amzn.to/36UsGSs)...
|
||||||
|
* Teensy boards: [4.0](https://amzn.to/30ljXGq), [3.2](https://amzn.to/2FT0EuC), [2.0](https://amzn.to/2QXUMXj)
|
||||||
|
* Particle boards: [Argon](https://amzn.to/2FQHa9X), [Boron](https://amzn.to/36WgLUd), [Electron](https://amzn.to/30vEc4k), [Photon](https://amzn.to/387F9Cd)...
|
||||||
|
* Texas Instruments boards: [MSP430](https://amzn.to/30nJWgg)...
|
||||||
|
* Soft cores: [Nios II](https://en.wikipedia.org/wiki/Nios_II)...
|
||||||
|
* Tested on all major development environments
|
||||||
|
* [Arduino IDE](https://www.arduino.cc/en/Main/Software)
|
||||||
|
* [Atmel Studio](http://www.atmel.com/microsite/atmel-studio/)
|
||||||
|
* [Atollic TrueSTUDIO](https://atollic.com/truestudio/)
|
||||||
|
* [Energia](http://energia.nu/)
|
||||||
|
* [IAR Embedded Workbench](https://www.iar.com/iar-embedded-workbench/)
|
||||||
|
* [Keil uVision](http://www.keil.com/)
|
||||||
|
* [MPLAB X IDE](http://www.microchip.com/mplab/mplab-x-ide)
|
||||||
|
* [Particle](https://www.particle.io/)
|
||||||
|
* [PlatformIO](http://platformio.org/)
|
||||||
|
* [Sloeber plugin for Eclipse](https://eclipse.baeyens.it/)
|
||||||
|
* [Visual Micro](http://www.visualmicro.com/)
|
||||||
|
* [Visual Studio](https://www.visualstudio.com/)
|
||||||
|
* [Even works with online compilers like wandbox.org](https://wandbox.org/permlink/RlZSKy17DjJ6HcdN)
|
||||||
|
* [CMake friendly](https://arduinojson.org/v6/how-to/use-arduinojson-with-cmake/)
|
||||||
|
* Well designed
|
||||||
|
* [Elegant API](http://arduinojson.org/v6/example/)
|
||||||
|
* [Thread-safe](https://en.wikipedia.org/wiki/Thread_safety)
|
||||||
|
* Self-contained (no external dependency)
|
||||||
|
* `const` friendly
|
||||||
|
* [`for` friendly](https://arduinojson.org/v6/api/jsonobject/begin_end/)
|
||||||
|
* [TMP friendly](https://en.wikipedia.org/wiki/Template_metaprogramming)
|
||||||
|
* Handles [integer overflows](https://arduinojson.org/v6/api/jsonvariant/as/#integer-overflows)
|
||||||
|
* Well tested
|
||||||
|
* [Unit test coverage close to 100%](https://coveralls.io/github/bblanchon/ArduinoJson?branch=6.x)
|
||||||
|
* Continuously tested on
|
||||||
|
* [Visual Studio 2017, 2019, 2022](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/6.x)
|
||||||
|
* [GCC 5, 6, 7, 8, 9, 10, 11](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
|
||||||
|
* [Clang 3.8, 3.9, 4.0, 5.0, 6.0, 7, 8, 9, 10](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
|
||||||
|
* [Continuously fuzzed with Google OSS Fuzz](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)
|
||||||
|
* Passes all default checks of [clang-tidy](https://releases.llvm.org/10.0.0/tools/clang/tools/extra/docs/clang-tidy/)
|
||||||
|
* Well documented
|
||||||
|
* [Tutorials](https://arduinojson.org/v6/doc/deserialization/)
|
||||||
|
* [Examples](https://arduinojson.org/v6/example/)
|
||||||
|
* [How-tos](https://arduinojson.org/v6/example/)
|
||||||
|
* [FAQ](https://arduinojson.org/v6/faq/)
|
||||||
|
* [Troubleshooter](https://arduinojson.org/v6/troubleshooter/)
|
||||||
|
* [Book](https://arduinojson.org/book/)
|
||||||
|
* [Changelog](CHANGELOG.md)
|
||||||
|
* Vibrant user community
|
||||||
|
* Most popular of all Arduino libraries on [GitHub](https://github.com/search?o=desc&q=arduino+library&s=stars&type=Repositories)
|
||||||
|
* [Used in hundreds of projects](https://www.hackster.io/search?i=projects&q=arduinojson)
|
||||||
|
* [Responsive support](https://github.com/bblanchon/ArduinoJson/issues?q=is%3Aissue+is%3Aclosed)
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
### Deserialization
|
||||||
|
|
||||||
|
Here is a program that parses a JSON document with ArduinoJson.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
char json[] = "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
|
||||||
|
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
deserializeJson(doc, json);
|
||||||
|
|
||||||
|
const char* sensor = doc["sensor"];
|
||||||
|
long time = doc["time"];
|
||||||
|
double latitude = doc["data"][0];
|
||||||
|
double longitude = doc["data"][1];
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [tutorial on arduinojson.org](https://arduinojson.org/v6/doc/deserialization/)
|
||||||
|
|
||||||
|
### Serialization
|
||||||
|
|
||||||
|
Here is a program that generates a JSON document with ArduinoJson:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
|
||||||
|
doc["sensor"] = "gps";
|
||||||
|
doc["time"] = 1351824120;
|
||||||
|
doc["data"][0] = 48.756080;
|
||||||
|
doc["data"][1] = 2.302038;
|
||||||
|
|
||||||
|
serializeJson(doc, Serial);
|
||||||
|
// This prints:
|
||||||
|
// {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [tutorial on arduinojson.org](https://arduinojson.org/v6/doc/serialization/)
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
ArduinoJson is thankful to its sponsors. Please give them a visit; they deserve it!
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://www.programmingelectronics.com/" rel="sponsored">
|
||||||
|
<img src="https://arduinojson.org/images/2021/10/programmingeleactronicsacademy.png" alt="Programming Electronics Academy" width="200">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="https://github.com/1technophile" rel="sponsored">
|
||||||
|
<img alt="1technophile" src="https://avatars.githubusercontent.com/u/12672732?s=40&v=4">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
If you run a commercial project that embeds ArduinoJson, think about [sponsoring the library's development](https://github.com/sponsors/bblanchon): it ensures the code that your products rely on stays actively maintained. It can also give your project some exposure to the makers' community.
|
||||||
|
|
||||||
|
If you are an individual user and want to support the development (or give a sign of appreciation), consider purchasing the book [Mastering ArduinoJson](https://arduinojson.org/book/) ❤, or simply [cast a star](https://github.com/bblanchon/ArduinoJson/stargazers) ⭐.
|
||||||
40
lib/ArduinoJson/keywords.txt
Normal file
40
lib/ArduinoJson/keywords.txt
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Macros
|
||||||
|
JSON_ARRAY_SIZE KEYWORD2
|
||||||
|
JSON_OBJECT_SIZE KEYWORD2
|
||||||
|
JSON_STRING_SIZE KEYWORD2
|
||||||
|
|
||||||
|
# Free functions
|
||||||
|
deserializeJson KEYWORD2
|
||||||
|
deserializeMsgPack KEYWORD2
|
||||||
|
serialized KEYWORD2
|
||||||
|
serializeJson KEYWORD2
|
||||||
|
serializeJsonPretty KEYWORD2
|
||||||
|
serializeMsgPack KEYWORD2
|
||||||
|
measureJson KEYWORD2
|
||||||
|
measureJsonPretty KEYWORD2
|
||||||
|
measureMsgPack KEYWORD2
|
||||||
|
|
||||||
|
# Methods
|
||||||
|
add KEYWORD2
|
||||||
|
as KEYWORD2
|
||||||
|
createNestedArray KEYWORD2
|
||||||
|
createNestedObject KEYWORD2
|
||||||
|
get KEYWORD2
|
||||||
|
set KEYWORD2
|
||||||
|
to KEYWORD2
|
||||||
|
|
||||||
|
# Type names
|
||||||
|
DeserializationError KEYWORD1 DATA_TYPE
|
||||||
|
DynamicJsonDocument KEYWORD1 DATA_TYPE
|
||||||
|
JsonArray KEYWORD1 DATA_TYPE
|
||||||
|
JsonArrayConst KEYWORD1 DATA_TYPE
|
||||||
|
JsonDocument KEYWORD1 DATA_TYPE
|
||||||
|
JsonFloat KEYWORD1 DATA_TYPE
|
||||||
|
JsonInteger KEYWORD1 DATA_TYPE
|
||||||
|
JsonObject KEYWORD1 DATA_TYPE
|
||||||
|
JsonObjectConst KEYWORD1 DATA_TYPE
|
||||||
|
JsonString KEYWORD1 DATA_TYPE
|
||||||
|
JsonUInt KEYWORD1 DATA_TYPE
|
||||||
|
JsonVariant KEYWORD1 DATA_TYPE
|
||||||
|
JsonVariantConst KEYWORD1 DATA_TYPE
|
||||||
|
StaticJsonDocument KEYWORD1 DATA_TYPE
|
||||||
11
lib/ArduinoJson/library.properties
Normal file
11
lib/ArduinoJson/library.properties
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
name=ArduinoJson
|
||||||
|
version=6.21.3
|
||||||
|
author=Benoit Blanchon <blog.benoitblanchon.fr>
|
||||||
|
maintainer=Benoit Blanchon <blog.benoitblanchon.fr>
|
||||||
|
sentence=A simple and efficient JSON library for embedded C++.
|
||||||
|
paragraph=ArduinoJson supports ✔ serialization, ✔ deserialization, ✔ MessagePack, ✔ fixed allocation, ✔ zero-copy, ✔ streams, ✔ filtering, and more. It is the most popular Arduino library on GitHub ❤❤❤❤❤. Check out arduinojson.org for a comprehensive documentation.
|
||||||
|
category=Data Processing
|
||||||
|
url=https://arduinojson.org/?utm_source=meta&utm_medium=library.properties
|
||||||
|
architectures=*
|
||||||
|
repository=https://github.com/bblanchon/ArduinoJson.git
|
||||||
|
license=MIT
|
||||||
@@ -13,7 +13,8 @@
|
|||||||
// Include Arduino.h before stdlib.h to avoid conflict with atexit()
|
// Include Arduino.h before stdlib.h to avoid conflict with atexit()
|
||||||
// https://github.com/bblanchon/ArduinoJson/pull/1693#issuecomment-1001060240
|
// https://github.com/bblanchon/ArduinoJson/pull/1693#issuecomment-1001060240
|
||||||
#if ARDUINOJSON_ENABLE_ARDUINO_STRING || ARDUINOJSON_ENABLE_ARDUINO_STREAM || \
|
#if ARDUINOJSON_ENABLE_ARDUINO_STRING || ARDUINOJSON_ENABLE_ARDUINO_STREAM || \
|
||||||
ARDUINOJSON_ENABLE_ARDUINO_PRINT || ARDUINOJSON_ENABLE_PROGMEM
|
ARDUINOJSON_ENABLE_ARDUINO_PRINT || \
|
||||||
|
(ARDUINOJSON_ENABLE_PROGMEM && defined(ARDUINO))
|
||||||
# include <Arduino.h>
|
# include <Arduino.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ class ElementProxy : public VariantRefBase<ElementProxy<TUpstream>>,
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
ElementProxy(TUpstream upstream, size_t index)
|
ElementProxy(TUpstream upstream, size_t index)
|
||||||
: _upstream(upstream), _index(index) {}
|
: upstream_(upstream), index_(index) {}
|
||||||
|
|
||||||
ElementProxy(const ElementProxy& src)
|
ElementProxy(const ElementProxy& src)
|
||||||
: _upstream(src._upstream), _index(src._index) {}
|
: upstream_(src.upstream_), index_(src.index_) {}
|
||||||
|
|
||||||
FORCE_INLINE ElementProxy& operator=(const ElementProxy& src) {
|
FORCE_INLINE ElementProxy& operator=(const ElementProxy& src) {
|
||||||
this->set(src);
|
this->set(src);
|
||||||
@@ -41,20 +41,20 @@ class ElementProxy : public VariantRefBase<ElementProxy<TUpstream>>,
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
FORCE_INLINE MemoryPool* getPool() const {
|
FORCE_INLINE MemoryPool* getPool() const {
|
||||||
return VariantAttorney::getPool(_upstream);
|
return VariantAttorney::getPool(upstream_);
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCE_INLINE VariantData* getData() const {
|
FORCE_INLINE VariantData* getData() const {
|
||||||
return variantGetElement(VariantAttorney::getData(_upstream), _index);
|
return variantGetElement(VariantAttorney::getData(upstream_), index_);
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCE_INLINE VariantData* getOrCreateData() const {
|
FORCE_INLINE VariantData* getOrCreateData() const {
|
||||||
return variantGetOrAddElement(VariantAttorney::getOrCreateData(_upstream),
|
return variantGetOrAddElement(VariantAttorney::getOrCreateData(upstream_),
|
||||||
_index, VariantAttorney::getPool(_upstream));
|
index_, VariantAttorney::getPool(upstream_));
|
||||||
}
|
}
|
||||||
|
|
||||||
TUpstream _upstream;
|
TUpstream upstream_;
|
||||||
size_t _index;
|
size_t index_;
|
||||||
};
|
};
|
||||||
|
|
||||||
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||||
|
|||||||
@@ -20,32 +20,32 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
|
|||||||
typedef JsonArrayIterator iterator;
|
typedef JsonArrayIterator iterator;
|
||||||
|
|
||||||
// Constructs an unbound reference.
|
// Constructs an unbound reference.
|
||||||
FORCE_INLINE JsonArray() : _data(0), _pool(0) {}
|
FORCE_INLINE JsonArray() : data_(0), pool_(0) {}
|
||||||
|
|
||||||
// INTERNAL USE ONLY
|
// INTERNAL USE ONLY
|
||||||
FORCE_INLINE JsonArray(detail::MemoryPool* pool, detail::CollectionData* data)
|
FORCE_INLINE JsonArray(detail::MemoryPool* pool, detail::CollectionData* data)
|
||||||
: _data(data), _pool(pool) {}
|
: data_(data), pool_(pool) {}
|
||||||
|
|
||||||
// Returns a JsonVariant pointing to the array.
|
// Returns a JsonVariant pointing to the array.
|
||||||
// https://arduinojson.org/v6/api/jsonvariant/
|
// https://arduinojson.org/v6/api/jsonvariant/
|
||||||
operator JsonVariant() {
|
operator JsonVariant() {
|
||||||
void* data = _data; // prevent warning cast-align
|
void* data = data_; // prevent warning cast-align
|
||||||
return JsonVariant(_pool, reinterpret_cast<detail::VariantData*>(data));
|
return JsonVariant(pool_, reinterpret_cast<detail::VariantData*>(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a read-only reference to the array.
|
// Returns a read-only reference to the array.
|
||||||
// https://arduinojson.org/v6/api/jsonarrayconst/
|
// https://arduinojson.org/v6/api/jsonarrayconst/
|
||||||
operator JsonArrayConst() const {
|
operator JsonArrayConst() const {
|
||||||
return JsonArrayConst(_data);
|
return JsonArrayConst(data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Appends a new (null) element to the array.
|
// Appends a new (null) element to the array.
|
||||||
// Returns a reference to the new element.
|
// Returns a reference to the new element.
|
||||||
// https://arduinojson.org/v6/api/jsonarray/add/
|
// https://arduinojson.org/v6/api/jsonarray/add/
|
||||||
JsonVariant add() const {
|
JsonVariant add() const {
|
||||||
if (!_data)
|
if (!data_)
|
||||||
return JsonVariant();
|
return JsonVariant();
|
||||||
return JsonVariant(_pool, _data->addElement(_pool));
|
return JsonVariant(pool_, data_->addElement(pool_));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Appends a value to the array.
|
// Appends a value to the array.
|
||||||
@@ -65,9 +65,9 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
|
|||||||
// Returns an iterator to the first element of the array.
|
// Returns an iterator to the first element of the array.
|
||||||
// https://arduinojson.org/v6/api/jsonarray/begin/
|
// https://arduinojson.org/v6/api/jsonarray/begin/
|
||||||
FORCE_INLINE iterator begin() const {
|
FORCE_INLINE iterator begin() const {
|
||||||
if (!_data)
|
if (!data_)
|
||||||
return iterator();
|
return iterator();
|
||||||
return iterator(_pool, _data->head());
|
return iterator(pool_, data_->head());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an iterator following the last element of the array.
|
// Returns an iterator following the last element of the array.
|
||||||
@@ -79,41 +79,41 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
|
|||||||
// Copies an array.
|
// Copies an array.
|
||||||
// https://arduinojson.org/v6/api/jsonarray/set/
|
// https://arduinojson.org/v6/api/jsonarray/set/
|
||||||
FORCE_INLINE bool set(JsonArrayConst src) const {
|
FORCE_INLINE bool set(JsonArrayConst src) const {
|
||||||
if (!_data || !src._data)
|
if (!data_ || !src.data_)
|
||||||
return false;
|
return false;
|
||||||
return _data->copyFrom(*src._data, _pool);
|
return data_->copyFrom(*src.data_, pool_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compares the content of two arrays.
|
// Compares the content of two arrays.
|
||||||
FORCE_INLINE bool operator==(JsonArray rhs) const {
|
FORCE_INLINE bool operator==(JsonArray rhs) const {
|
||||||
return JsonArrayConst(_data) == JsonArrayConst(rhs._data);
|
return JsonArrayConst(data_) == JsonArrayConst(rhs.data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes the element at the specified iterator.
|
// Removes the element at the specified iterator.
|
||||||
// ⚠️ Doesn't release the memory associated with the removed element.
|
// ⚠️ Doesn't release the memory associated with the removed element.
|
||||||
// https://arduinojson.org/v6/api/jsonarray/remove/
|
// https://arduinojson.org/v6/api/jsonarray/remove/
|
||||||
FORCE_INLINE void remove(iterator it) const {
|
FORCE_INLINE void remove(iterator it) const {
|
||||||
if (!_data)
|
if (!data_)
|
||||||
return;
|
return;
|
||||||
_data->removeSlot(it._slot);
|
data_->removeSlot(it.slot_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes the element at the specified index.
|
// Removes the element at the specified index.
|
||||||
// ⚠️ Doesn't release the memory associated with the removed element.
|
// ⚠️ Doesn't release the memory associated with the removed element.
|
||||||
// https://arduinojson.org/v6/api/jsonarray/remove/
|
// https://arduinojson.org/v6/api/jsonarray/remove/
|
||||||
FORCE_INLINE void remove(size_t index) const {
|
FORCE_INLINE void remove(size_t index) const {
|
||||||
if (!_data)
|
if (!data_)
|
||||||
return;
|
return;
|
||||||
_data->removeElement(index);
|
data_->removeElement(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes all the elements of the array.
|
// Removes all the elements of the array.
|
||||||
// ⚠️ Doesn't release the memory associated with the removed elements.
|
// ⚠️ Doesn't release the memory associated with the removed elements.
|
||||||
// https://arduinojson.org/v6/api/jsonarray/clear/
|
// https://arduinojson.org/v6/api/jsonarray/clear/
|
||||||
void clear() const {
|
void clear() const {
|
||||||
if (!_data)
|
if (!data_)
|
||||||
return;
|
return;
|
||||||
_data->clear();
|
data_->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets or sets the element at the specified index.
|
// Gets or sets the element at the specified index.
|
||||||
@@ -133,54 +133,54 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
operator JsonVariantConst() const {
|
operator JsonVariantConst() const {
|
||||||
return JsonVariantConst(collectionToVariant(_data));
|
return JsonVariantConst(collectionToVariant(data_));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the reference is unbound.
|
// Returns true if the reference is unbound.
|
||||||
// https://arduinojson.org/v6/api/jsonarray/isnull/
|
// https://arduinojson.org/v6/api/jsonarray/isnull/
|
||||||
FORCE_INLINE bool isNull() const {
|
FORCE_INLINE bool isNull() const {
|
||||||
return _data == 0;
|
return data_ == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the reference is bound.
|
// Returns true if the reference is bound.
|
||||||
// https://arduinojson.org/v6/api/jsonarray/isnull/
|
// https://arduinojson.org/v6/api/jsonarray/isnull/
|
||||||
FORCE_INLINE operator bool() const {
|
FORCE_INLINE operator bool() const {
|
||||||
return _data != 0;
|
return data_ != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the number of bytes occupied by the array.
|
// Returns the number of bytes occupied by the array.
|
||||||
// https://arduinojson.org/v6/api/jsonarray/memoryusage/
|
// https://arduinojson.org/v6/api/jsonarray/memoryusage/
|
||||||
FORCE_INLINE size_t memoryUsage() const {
|
FORCE_INLINE size_t memoryUsage() const {
|
||||||
return _data ? _data->memoryUsage() : 0;
|
return data_ ? data_->memoryUsage() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the depth (nesting level) of the array.
|
// Returns the depth (nesting level) of the array.
|
||||||
// https://arduinojson.org/v6/api/jsonarray/nesting/
|
// https://arduinojson.org/v6/api/jsonarray/nesting/
|
||||||
FORCE_INLINE size_t nesting() const {
|
FORCE_INLINE size_t nesting() const {
|
||||||
return variantNesting(collectionToVariant(_data));
|
return variantNesting(collectionToVariant(data_));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the number of elements in the array.
|
// Returns the number of elements in the array.
|
||||||
// https://arduinojson.org/v6/api/jsonarray/size/
|
// https://arduinojson.org/v6/api/jsonarray/size/
|
||||||
FORCE_INLINE size_t size() const {
|
FORCE_INLINE size_t size() const {
|
||||||
return _data ? _data->size() : 0;
|
return data_ ? data_->size() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
detail::MemoryPool* getPool() const {
|
detail::MemoryPool* getPool() const {
|
||||||
return _pool;
|
return pool_;
|
||||||
}
|
}
|
||||||
|
|
||||||
detail::VariantData* getData() const {
|
detail::VariantData* getData() const {
|
||||||
return collectionToVariant(_data);
|
return collectionToVariant(data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
detail::VariantData* getOrCreateData() const {
|
detail::VariantData* getOrCreateData() const {
|
||||||
return collectionToVariant(_data);
|
return collectionToVariant(data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
detail::CollectionData* _data;
|
detail::CollectionData* data_;
|
||||||
detail::MemoryPool* _pool;
|
detail::MemoryPool* pool_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
|
|||||||
// Returns an iterator to the first element of the array.
|
// Returns an iterator to the first element of the array.
|
||||||
// https://arduinojson.org/v6/api/jsonarrayconst/begin/
|
// https://arduinojson.org/v6/api/jsonarrayconst/begin/
|
||||||
FORCE_INLINE iterator begin() const {
|
FORCE_INLINE iterator begin() const {
|
||||||
if (!_data)
|
if (!data_)
|
||||||
return iterator();
|
return iterator();
|
||||||
return iterator(_data->head());
|
return iterator(data_->head());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an iterator to the element following the last element of the array.
|
// Returns an iterator to the element following the last element of the array.
|
||||||
@@ -36,18 +36,18 @@ class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Creates an unbound reference.
|
// Creates an unbound reference.
|
||||||
FORCE_INLINE JsonArrayConst() : _data(0) {}
|
FORCE_INLINE JsonArrayConst() : data_(0) {}
|
||||||
|
|
||||||
// INTERNAL USE ONLY
|
// INTERNAL USE ONLY
|
||||||
FORCE_INLINE JsonArrayConst(const detail::CollectionData* data)
|
FORCE_INLINE JsonArrayConst(const detail::CollectionData* data)
|
||||||
: _data(data) {}
|
: data_(data) {}
|
||||||
|
|
||||||
// Compares the content of two arrays.
|
// Compares the content of two arrays.
|
||||||
// Returns true if the two arrays are equal.
|
// Returns true if the two arrays are equal.
|
||||||
FORCE_INLINE bool operator==(JsonArrayConst rhs) const {
|
FORCE_INLINE bool operator==(JsonArrayConst rhs) const {
|
||||||
if (_data == rhs._data)
|
if (data_ == rhs.data_)
|
||||||
return true;
|
return true;
|
||||||
if (!_data || !rhs._data)
|
if (!data_ || !rhs.data_)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
iterator it1 = begin();
|
iterator it1 = begin();
|
||||||
@@ -70,49 +70,49 @@ class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
|
|||||||
// Returns the element at the specified index.
|
// Returns the element at the specified index.
|
||||||
// https://arduinojson.org/v6/api/jsonarrayconst/subscript/
|
// https://arduinojson.org/v6/api/jsonarrayconst/subscript/
|
||||||
FORCE_INLINE JsonVariantConst operator[](size_t index) const {
|
FORCE_INLINE JsonVariantConst operator[](size_t index) const {
|
||||||
return JsonVariantConst(_data ? _data->getElement(index) : 0);
|
return JsonVariantConst(data_ ? data_->getElement(index) : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
operator JsonVariantConst() const {
|
operator JsonVariantConst() const {
|
||||||
return JsonVariantConst(collectionToVariant(_data));
|
return JsonVariantConst(collectionToVariant(data_));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the reference is unbound.
|
// Returns true if the reference is unbound.
|
||||||
// https://arduinojson.org/v6/api/jsonarrayconst/isnull/
|
// https://arduinojson.org/v6/api/jsonarrayconst/isnull/
|
||||||
FORCE_INLINE bool isNull() const {
|
FORCE_INLINE bool isNull() const {
|
||||||
return _data == 0;
|
return data_ == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the reference is bound.
|
// Returns true if the reference is bound.
|
||||||
// https://arduinojson.org/v6/api/jsonarrayconst/isnull/
|
// https://arduinojson.org/v6/api/jsonarrayconst/isnull/
|
||||||
FORCE_INLINE operator bool() const {
|
FORCE_INLINE operator bool() const {
|
||||||
return _data != 0;
|
return data_ != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the number of bytes occupied by the array.
|
// Returns the number of bytes occupied by the array.
|
||||||
// https://arduinojson.org/v6/api/jsonarrayconst/memoryusage/
|
// https://arduinojson.org/v6/api/jsonarrayconst/memoryusage/
|
||||||
FORCE_INLINE size_t memoryUsage() const {
|
FORCE_INLINE size_t memoryUsage() const {
|
||||||
return _data ? _data->memoryUsage() : 0;
|
return data_ ? data_->memoryUsage() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the depth (nesting level) of the array.
|
// Returns the depth (nesting level) of the array.
|
||||||
// https://arduinojson.org/v6/api/jsonarrayconst/nesting/
|
// https://arduinojson.org/v6/api/jsonarrayconst/nesting/
|
||||||
FORCE_INLINE size_t nesting() const {
|
FORCE_INLINE size_t nesting() const {
|
||||||
return variantNesting(collectionToVariant(_data));
|
return variantNesting(collectionToVariant(data_));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the number of elements in the array.
|
// Returns the number of elements in the array.
|
||||||
// https://arduinojson.org/v6/api/jsonarrayconst/size/
|
// https://arduinojson.org/v6/api/jsonarrayconst/size/
|
||||||
FORCE_INLINE size_t size() const {
|
FORCE_INLINE size_t size() const {
|
||||||
return _data ? _data->size() : 0;
|
return data_ ? data_->size() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const detail::VariantData* getData() const {
|
const detail::VariantData* getData() const {
|
||||||
return collectionToVariant(_data);
|
return collectionToVariant(data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
const detail::CollectionData* _data;
|
const detail::CollectionData* data_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
|||||||
@@ -12,110 +12,110 @@ ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
|
|||||||
class VariantPtr {
|
class VariantPtr {
|
||||||
public:
|
public:
|
||||||
VariantPtr(detail::MemoryPool* pool, detail::VariantData* data)
|
VariantPtr(detail::MemoryPool* pool, detail::VariantData* data)
|
||||||
: _variant(pool, data) {}
|
: variant_(pool, data) {}
|
||||||
|
|
||||||
JsonVariant* operator->() {
|
JsonVariant* operator->() {
|
||||||
return &_variant;
|
return &variant_;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonVariant& operator*() {
|
JsonVariant& operator*() {
|
||||||
return _variant;
|
return variant_;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
JsonVariant _variant;
|
JsonVariant variant_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class JsonArrayIterator {
|
class JsonArrayIterator {
|
||||||
friend class JsonArray;
|
friend class JsonArray;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
JsonArrayIterator() : _slot(0) {}
|
JsonArrayIterator() : slot_(0) {}
|
||||||
explicit JsonArrayIterator(detail::MemoryPool* pool,
|
explicit JsonArrayIterator(detail::MemoryPool* pool,
|
||||||
detail::VariantSlot* slot)
|
detail::VariantSlot* slot)
|
||||||
: _pool(pool), _slot(slot) {}
|
: pool_(pool), slot_(slot) {}
|
||||||
|
|
||||||
JsonVariant operator*() const {
|
JsonVariant operator*() const {
|
||||||
return JsonVariant(_pool, _slot->data());
|
return JsonVariant(pool_, slot_->data());
|
||||||
}
|
}
|
||||||
VariantPtr operator->() {
|
VariantPtr operator->() {
|
||||||
return VariantPtr(_pool, _slot->data());
|
return VariantPtr(pool_, slot_->data());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const JsonArrayIterator& other) const {
|
bool operator==(const JsonArrayIterator& other) const {
|
||||||
return _slot == other._slot;
|
return slot_ == other.slot_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(const JsonArrayIterator& other) const {
|
bool operator!=(const JsonArrayIterator& other) const {
|
||||||
return _slot != other._slot;
|
return slot_ != other.slot_;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonArrayIterator& operator++() {
|
JsonArrayIterator& operator++() {
|
||||||
_slot = _slot->next();
|
slot_ = slot_->next();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonArrayIterator& operator+=(size_t distance) {
|
JsonArrayIterator& operator+=(size_t distance) {
|
||||||
_slot = _slot->next(distance);
|
slot_ = slot_->next(distance);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
detail::MemoryPool* _pool;
|
detail::MemoryPool* pool_;
|
||||||
detail::VariantSlot* _slot;
|
detail::VariantSlot* slot_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class VariantConstPtr {
|
class VariantConstPtr {
|
||||||
public:
|
public:
|
||||||
VariantConstPtr(const detail::VariantData* data) : _variant(data) {}
|
VariantConstPtr(const detail::VariantData* data) : variant_(data) {}
|
||||||
|
|
||||||
JsonVariantConst* operator->() {
|
JsonVariantConst* operator->() {
|
||||||
return &_variant;
|
return &variant_;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonVariantConst& operator*() {
|
JsonVariantConst& operator*() {
|
||||||
return _variant;
|
return variant_;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
JsonVariantConst _variant;
|
JsonVariantConst variant_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class JsonArrayConstIterator {
|
class JsonArrayConstIterator {
|
||||||
friend class JsonArray;
|
friend class JsonArray;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
JsonArrayConstIterator() : _slot(0) {}
|
JsonArrayConstIterator() : slot_(0) {}
|
||||||
explicit JsonArrayConstIterator(const detail::VariantSlot* slot)
|
explicit JsonArrayConstIterator(const detail::VariantSlot* slot)
|
||||||
: _slot(slot) {}
|
: slot_(slot) {}
|
||||||
|
|
||||||
JsonVariantConst operator*() const {
|
JsonVariantConst operator*() const {
|
||||||
return JsonVariantConst(_slot->data());
|
return JsonVariantConst(slot_->data());
|
||||||
}
|
}
|
||||||
VariantConstPtr operator->() {
|
VariantConstPtr operator->() {
|
||||||
return VariantConstPtr(_slot->data());
|
return VariantConstPtr(slot_->data());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const JsonArrayConstIterator& other) const {
|
bool operator==(const JsonArrayConstIterator& other) const {
|
||||||
return _slot == other._slot;
|
return slot_ == other.slot_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(const JsonArrayConstIterator& other) const {
|
bool operator!=(const JsonArrayConstIterator& other) const {
|
||||||
return _slot != other._slot;
|
return slot_ != other.slot_;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonArrayConstIterator& operator++() {
|
JsonArrayConstIterator& operator++() {
|
||||||
_slot = _slot->next();
|
slot_ = slot_->next();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonArrayConstIterator& operator+=(size_t distance) {
|
JsonArrayConstIterator& operator+=(size_t distance) {
|
||||||
_slot = _slot->next(distance);
|
slot_ = slot_->next(distance);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const detail::VariantSlot* _slot;
|
const detail::VariantSlot* slot_;
|
||||||
};
|
};
|
||||||
|
|
||||||
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
ARDUINOJSON_END_PUBLIC_NAMESPACE
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ class VariantData;
|
|||||||
class VariantSlot;
|
class VariantSlot;
|
||||||
|
|
||||||
class CollectionData {
|
class CollectionData {
|
||||||
VariantSlot* _head;
|
VariantSlot* head_;
|
||||||
VariantSlot* _tail;
|
VariantSlot* tail_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Must be a POD!
|
// Must be a POD!
|
||||||
@@ -67,7 +67,7 @@ class CollectionData {
|
|||||||
bool copyFrom(const CollectionData& src, MemoryPool* pool);
|
bool copyFrom(const CollectionData& src, MemoryPool* pool);
|
||||||
|
|
||||||
VariantSlot* head() const {
|
VariantSlot* head() const {
|
||||||
return _head;
|
return head_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void movePointers(ptrdiff_t stringDistance, ptrdiff_t variantDistance);
|
void movePointers(ptrdiff_t stringDistance, ptrdiff_t variantDistance);
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ inline VariantSlot* CollectionData::addSlot(MemoryPool* pool) {
|
|||||||
if (!slot)
|
if (!slot)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (_tail) {
|
if (tail_) {
|
||||||
ARDUINOJSON_ASSERT(pool->owns(_tail)); // Can't alter a linked array/object
|
ARDUINOJSON_ASSERT(pool->owns(tail_)); // Can't alter a linked array/object
|
||||||
_tail->setNextNotNull(slot);
|
tail_->setNextNotNull(slot);
|
||||||
_tail = slot;
|
tail_ = slot;
|
||||||
} else {
|
} else {
|
||||||
_head = slot;
|
head_ = slot;
|
||||||
_tail = slot;
|
tail_ = slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
slot->clear();
|
slot->clear();
|
||||||
@@ -45,8 +45,8 @@ inline VariantData* CollectionData::addMember(TAdaptedString key,
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void CollectionData::clear() {
|
inline void CollectionData::clear() {
|
||||||
_head = 0;
|
head_ = 0;
|
||||||
_tail = 0;
|
tail_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename TAdaptedString>
|
template <typename TAdaptedString>
|
||||||
@@ -57,7 +57,7 @@ inline bool CollectionData::containsKey(const TAdaptedString& key) const {
|
|||||||
inline bool CollectionData::copyFrom(const CollectionData& src,
|
inline bool CollectionData::copyFrom(const CollectionData& src,
|
||||||
MemoryPool* pool) {
|
MemoryPool* pool) {
|
||||||
clear();
|
clear();
|
||||||
for (VariantSlot* s = src._head; s; s = s->next()) {
|
for (VariantSlot* s = src.head_; s; s = s->next()) {
|
||||||
VariantData* var;
|
VariantData* var;
|
||||||
if (s->key() != 0) {
|
if (s->key() != 0) {
|
||||||
JsonString key(s->key(),
|
JsonString key(s->key(),
|
||||||
@@ -78,7 +78,7 @@ template <typename TAdaptedString>
|
|||||||
inline VariantSlot* CollectionData::getSlot(TAdaptedString key) const {
|
inline VariantSlot* CollectionData::getSlot(TAdaptedString key) const {
|
||||||
if (key.isNull())
|
if (key.isNull())
|
||||||
return 0;
|
return 0;
|
||||||
VariantSlot* slot = _head;
|
VariantSlot* slot = head_;
|
||||||
while (slot) {
|
while (slot) {
|
||||||
if (stringEquals(key, adaptString(slot->key())))
|
if (stringEquals(key, adaptString(slot->key())))
|
||||||
break;
|
break;
|
||||||
@@ -88,13 +88,13 @@ inline VariantSlot* CollectionData::getSlot(TAdaptedString key) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline VariantSlot* CollectionData::getSlot(size_t index) const {
|
inline VariantSlot* CollectionData::getSlot(size_t index) const {
|
||||||
if (!_head)
|
if (!head_)
|
||||||
return 0;
|
return 0;
|
||||||
return _head->next(index);
|
return head_->next(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline VariantSlot* CollectionData::getPreviousSlot(VariantSlot* target) const {
|
inline VariantSlot* CollectionData::getPreviousSlot(VariantSlot* target) const {
|
||||||
VariantSlot* current = _head;
|
VariantSlot* current = head_;
|
||||||
while (current) {
|
while (current) {
|
||||||
VariantSlot* next = current->next();
|
VariantSlot* next = current->next();
|
||||||
if (next == target)
|
if (next == target)
|
||||||
@@ -132,7 +132,7 @@ inline VariantData* CollectionData::getElement(size_t index) const {
|
|||||||
|
|
||||||
inline VariantData* CollectionData::getOrAddElement(size_t index,
|
inline VariantData* CollectionData::getOrAddElement(size_t index,
|
||||||
MemoryPool* pool) {
|
MemoryPool* pool) {
|
||||||
VariantSlot* slot = _head;
|
VariantSlot* slot = head_;
|
||||||
while (slot && index > 0) {
|
while (slot && index > 0) {
|
||||||
slot = slot->next();
|
slot = slot->next();
|
||||||
index--;
|
index--;
|
||||||
@@ -154,9 +154,9 @@ inline void CollectionData::removeSlot(VariantSlot* slot) {
|
|||||||
if (prev)
|
if (prev)
|
||||||
prev->setNext(next);
|
prev->setNext(next);
|
||||||
else
|
else
|
||||||
_head = next;
|
head_ = next;
|
||||||
if (!next)
|
if (!next)
|
||||||
_tail = prev;
|
tail_ = prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void CollectionData::removeElement(size_t index) {
|
inline void CollectionData::removeElement(size_t index) {
|
||||||
@@ -165,7 +165,7 @@ inline void CollectionData::removeElement(size_t index) {
|
|||||||
|
|
||||||
inline size_t CollectionData::memoryUsage() const {
|
inline size_t CollectionData::memoryUsage() const {
|
||||||
size_t total = 0;
|
size_t total = 0;
|
||||||
for (VariantSlot* s = _head; s; s = s->next()) {
|
for (VariantSlot* s = head_; s; s = s->next()) {
|
||||||
total += sizeof(VariantSlot) + s->data()->memoryUsage();
|
total += sizeof(VariantSlot) + s->data()->memoryUsage();
|
||||||
if (s->ownsKey())
|
if (s->ownsKey())
|
||||||
total += strlen(s->key()) + 1;
|
total += strlen(s->key()) + 1;
|
||||||
@@ -174,7 +174,7 @@ inline size_t CollectionData::memoryUsage() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline size_t CollectionData::size() const {
|
inline size_t CollectionData::size() const {
|
||||||
return slotSize(_head);
|
return slotSize(head_);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@@ -188,9 +188,9 @@ inline void movePointer(T*& p, ptrdiff_t offset) {
|
|||||||
|
|
||||||
inline void CollectionData::movePointers(ptrdiff_t stringDistance,
|
inline void CollectionData::movePointers(ptrdiff_t stringDistance,
|
||||||
ptrdiff_t variantDistance) {
|
ptrdiff_t variantDistance) {
|
||||||
movePointer(_head, variantDistance);
|
movePointer(head_, variantDistance);
|
||||||
movePointer(_tail, variantDistance);
|
movePointer(tail_, variantDistance);
|
||||||
for (VariantSlot* slot = _head; slot; slot = slot->next())
|
for (VariantSlot* slot = head_; slot; slot = slot->next())
|
||||||
slot->movePointers(stringDistance, variantDistance);
|
slot->movePointers(stringDistance, variantDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,9 +130,13 @@
|
|||||||
# define ARDUINOJSON_ENABLE_ARDUINO_PRINT 0
|
# define ARDUINOJSON_ENABLE_ARDUINO_PRINT 0
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
// Disable support for PROGMEM
|
// Enable PROGMEM support on AVR only
|
||||||
# ifndef ARDUINOJSON_ENABLE_PROGMEM
|
# ifndef ARDUINOJSON_ENABLE_PROGMEM
|
||||||
# define ARDUINOJSON_ENABLE_PROGMEM 0
|
# ifdef __AVR__
|
||||||
|
# define ARDUINOJSON_ENABLE_PROGMEM 1
|
||||||
|
# else
|
||||||
|
# define ARDUINOJSON_ENABLE_PROGMEM 0
|
||||||
|
# endif
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
#endif // ARDUINO
|
#endif // ARDUINO
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user