66 Commits

Author SHA1 Message Date
proddy
eaa277fef0 v3.7.2 2025-03-22 10:32:03 +01:00
proddy
e418b7d8e7 fix routing in tabs - #2264 2024-11-30 21:31:42 +01:00
proddy
a4e3be6a69 3.7.1 changelog 2024-11-29 10:30:14 +01:00
proddy
a1bc5bb055 3.7.1 2024-11-29 10:30:05 +01:00
proddy
f444ca31e0 Merge remote-tracking branch 'origin/dev' 2024-10-27 11:41:47 +01:00
proddy
1b70b55989 update demo url 2024-08-10 14:58:48 +02:00
proddy
1487f30c43 Merge remote-tracking branch 'origin/dev' for 3.6.5 2024-03-23 17:56:05 +01:00
Proddy
e00eb8e64f 3.6.4 2023-11-26 20:11:36 +01:00
Proddy
f41bb3671c 3.6.4 2023-11-24 07:36:36 +01:00
Proddy
22c75e6df3 3.6.4 2023-11-24 07:36:29 +01:00
proddy
5ab10b7aa6 fixes #1450 2023-11-22 09:48:09 +01:00
Proddy
ee5fd4d0eb 3.6.3 2023-11-21 14:40:47 +01:00
Proddy
46f35bc67c another patch on 3.6.3 2023-11-21 14:40:32 +01:00
Proddy
ec85a7ec24 3.6.3 (refershed) 2023-11-19 21:24:01 +01:00
Proddy
02f2389587 add workflow_dispatch: 2023-11-18 18:51:12 +01:00
Proddy
7f140021aa quick fix - https://github.com/emsesp/EMS-ESP32/pull/1438 2023-11-18 18:47:28 +01:00
Proddy
6796962c1e 3.6.3 2023-11-18 13:35:20 +01:00
Proddy
df6de21cf4 Merge remote-tracking branch 'origin/dev' 2023-11-18 13:35:04 +01:00
Proddy
df9f75a5c9 updated yarn for 3.6.2 2023-10-01 17:42:54 +02:00
Proddy
7bd8710eb6 3.6.2 2023-10-01 17:40:09 +02:00
Proddy
32f2c6d341 Merge remote-tracking branch 'origin/dev' 2023-10-01 17:40:02 +02:00
Proddy
86919c1684 Merge branch 'origin/dev' 2023-09-09 14:12:07 +02:00
Proddy
86e29515e7 build s3 2023-08-15 18:44:24 +02:00
Proddy
46eb4185d7 update with 3.6.0 2023-08-13 14:37:13 +02:00
Proddy
8da6761a48 Merge branch 'dev' 2023-08-13 14:32:41 +02:00
Proddy
9233f0dfcc v3.5.1 - merge with patch 2023-03-11 16:06:05 +01:00
Proddy
292f743b14 Update bug_report.md 2023-02-19 11:18:08 +01:00
Proddy
dd6dfffd57 Delete questions---troubleshooting.md 2023-02-19 10:49:52 +01:00
Proddy
ec705a5307 Delete feature_request.md 2023-02-19 10:49:45 +01:00
Proddy
f45f071710 Create config.yml 2023-02-19 10:49:32 +01:00
Proddy
f3858546de Merge branch 'origin/dev' 2023-02-06 21:58:27 +01:00
Proddy
d0ac0b7804 update with version on dev 2022-10-30 16:58:37 +01:00
Proddy
d8284ec09f Merge pull request #705 from MichaelDvP/main
v3.4.4 Fix for new installations with filesystem not initializing
2022-10-29 10:46:41 +02:00
MichaelDvP
6e982acde8 v3.4.4 Fix for new installations with filesystem not initializing 2022-10-28 10:50:51 +02:00
Proddy
8c94ce99b2 quick fix for filesystem initialization 2022-10-08 09:23:00 +02:00
proddy
fc057d18c9 Merge remote-tracking branch 'origin/dev' 2022-09-18 14:33:23 +02:00
Proddy
18e9b99413 Merge remote-tracking branch 'origin/dev' into main 2022-05-29 16:16:38 +02:00
Proddy
a47e0e8266 update for 3.4.0 2022-05-23 21:20:45 +02:00
Proddy
f412ddc716 Merge remote-tracking branch 'origin/dev' into main 2022-05-23 21:20:36 +02:00
proddy
29110e96e5 Merge remote-tracking branch 'origin/dev' 2022-01-20 10:51:40 +01:00
proddy
b65866217a 3.4.0 2021-11-28 23:03:28 +01:00
proddy
611e3b1243 Merge remote-tracking branch 'origin/dev' 2021-11-28 23:03:15 +01:00
proddy
2ca0a0c634 v3.2.1 merged from dev 2021-08-08 14:46:14 +02:00
proddy
7eb1f061b7 Merge remote-tracking branch 'origin/dev' for 3.2.0 release 2021-08-06 12:06:08 +02:00
proddy
50459a23fe force v16 of nodejs 2021-06-26 11:13:07 +02:00
proddy
5bf53c3389 3.1.1 2021-06-26 11:03:03 +02:00
proddy
4b7aa95be3 Merge remote-tracking branch 'origin/dev' 2021-06-26 11:02:55 +02:00
Proddy
70943f5758 Update pre_release.yml 2021-05-16 15:52:09 +02:00
Proddy
3bc280b817 Delete check_code.yml 2021-05-16 15:51:56 +02:00
Proddy
62b15a5319 Update pre_release.yml 2021-05-16 15:35:06 +02:00
Proddy
8dd18802d6 Update tagged_release.yml 2021-05-16 15:34:45 +02:00
proddy
57a516a83a updated README and images 2021-05-09 15:13:16 +02:00
proddy
a57fdaa4b3 Merge remote-tracking branch 'origin/dev' into main 2021-05-04 12:21:51 +02:00
proddy
4841e42286 Merge remote-tracking branch 'origin/dev' into main 2021-03-30 16:35:18 +02:00
proddy
8c2d2b06ed cleaned up old changelog 2021-03-18 20:59:09 +01:00
proddy
38c8b1b7f0 3.0.0 2021-03-18 20:58:21 +01:00
proddy
6fb5933a02 Merge remote-tracking branch 'origin/dev' into main 2021-03-18 20:58:12 +01:00
proddy
c0944433be remove workspace.code-workspace 2021-03-16 17:41:42 +01:00
Proddy
478e6362c9 Merge pull request #27 from FauthD:main
Add global names to Dallas sensors to avoid ugly <unknown> and other …
2021-03-16 17:39:00 +01:00
fauthd
4d6354db78 Add stuff to gitignore, add vscode workspace 2021-03-16 16:47:09 +01:00
fauthd
beab0f0c77 Add global names to Dallas sensors to avoid ugly <unknown> and other issues in HA 2021-03-16 16:00:23 +01:00
Proddy
c17749bd22 Update README.md 2021-03-14 23:43:33 +01:00
proddy
2bad769c5c build: include assets 2021-03-14 21:20:51 +01:00
proddy
8ad89ca64b move repo 2021-03-14 21:05:15 +01:00
proddy
9244d8daec Semantic Commit Messages 2021-03-14 18:10:57 +01:00
proddy
02d01334b2 update new build 2021-03-14 17:37:18 +01:00
228 changed files with 30733 additions and 24700 deletions

View File

@@ -1,6 +1,7 @@
name: 'Publish releases to discord'
name: 'github-releases-to-discord'
on:
workflow_dispatch:
release:
types: [published]

View File

@@ -1,4 +1,4 @@
name: 'Pre-check on PR'
name: 'pr_check'
on:
workflow_dispatch:

View File

@@ -1,60 +1,53 @@
name: 'Build dev release'
name: 'pre-release'
on:
workflow_dispatch:
push:
paths:
- 'src/emsesp_version.h'
branches:
- 'dev'
permissions:
contents: write
jobs:
pre-release:
name: 'Build Dev Release'
name: 'Automatic pre-release build'
runs-on: ubuntu-latest
steps:
- name: Install python 3.13
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22
- name: Checkout repository
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable pnpm
- name: Get the EMS-ESP version
- name: Enable Corepack
run: corepack enable
- name: Install python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Node.js 20
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Get EMS-ESP version
id: build_info
run: |
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/emsesp_version.h | awk -F'"' '{print $2}'`
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
python -m pip install intelhex
- name: Build the WebUI
- name: Build WebUI
run: |
cd interface
pnpm install
pnpm typesafe-i18n --no-watch
yarn install
yarn typesafe-i18n --no-watch
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
pnpm build
pnpm webUI
yarn build
yarn webUI
- name: Build all PIO target environments
- name: Build all PIO target environments from default_envs
run: |
platformio run
env:

View File

@@ -20,12 +20,15 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Build Wrapper
uses: SonarSource/sonarqube-scan-action/install-build-wrapper@master
- name: Run Build Wrapper
run: build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make all
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@master
- name: Install sonar-scanner and build-wrapper
uses: SonarSource/sonarcloud-github-c-cpp@v2
- name: Run build-wrapper
run: build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make all
- name: Run sonar-scanner
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: sonar-scanner --define sonar.cfamily.compile-commands="${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json"

View File

@@ -1,23 +0,0 @@
name: "Mark or close stale issues and PRs"
on:
schedule:
- cron: "30 1 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30
days-before-close: 5
stale-issue-message: "This issue is stale because it has been open 30 days with no activity. Remove stale label or comment otherwise this will be closed in 5 days."
stale-pr-message: "This PR has been automatically marked as stale because there has been no activity in last 30 days. It will be closed if no further activity occurs. Thank you for your contributions."
close-issue-message: "This issue was closed because it has been stalled for 5 days with no activity."
close-pr-message: "This PR was automatically closed because of being stale."
stale-pr-label: "stale"
stale-issue-label: "stale"
exempt-issue-labels: "bug,enhancement,pinned,security"
exempt-pr-labels: "bug,enhancement,pinned,security"

View File

@@ -1,7 +1,4 @@
name: 'Build stable release'
permissions:
contents: write
name: 'tagged-release'
on:
workflow_dispatch:
@@ -11,42 +8,40 @@ on:
jobs:
tagged-release:
name: 'Build Stable Release'
name: 'Tagged Release'
runs-on: ubuntu-latest
steps:
- name: Install python 3.13
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22
- name: Checkout repository
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable pnpm
run: corepack enable
- name: Install python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Node.js 20
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Install PlatformIO
run: |
python -m pip install --upgrade pip
pip install -U platformio
python -m pip install intelhex
- name: Build the WebUI
- name: Build WebUI
run: |
cd interface
pnpm install
pnpm typesafe-i18n --no-watch
yarn install
yarn typesafe-i18n --no-watch
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
pnpm build
pnpm webUI
yarn build
yarn webUI
- name: Build all PIO target environments
- name: Build all PIO target environments from default_envs
run: |
platformio run
env:

View File

@@ -1,4 +1,4 @@
name: 'Build test release'
name: 'test-release'
on:
workflow_dispatch:
@@ -6,55 +6,46 @@ on:
branches:
- 'dev2'
permissions:
contents: read
jobs:
pre-release:
name: 'Build Test Release'
name: 'Automatic test-release build'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Install python 3.13
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22
- name: Checkout repository
uses: actions/checkout@v4
- uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable pnpm
run: corepack enable
- name: Get the EMS-ESP version
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Use Node.js 20.x
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Get EMS-ESP source code and version
id: build_info
run: |
version=`grep -E '^#define EMSESP_APP_VERSION' ./src/emsesp_version.h | awk -F'"' '{print $2}'`
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
python -m pip install intelhex
- name: Build the WebUI
- name: Build WebUI
run: |
cd interface
pnpm install
pnpm typesafe-i18n --no-watch
yarn install
yarn typesafe-i18n --no-watch
sed -i "s/= 'pl'/= 'en'/" ./src/i18n/i18n-util.ts
pnpm build
pnpm webUI
yarn build
yarn webUI
- name: Build all target environments
- name: Build all target environments from default_envs
run: |
platformio run
env:

4
.gitignore vendored
View File

@@ -28,10 +28,14 @@ stats.html
*.sln
*.sw?
.pnp.*
*/.yarn/cache/*
*/.yarn/install-state.gz
analyse.html
interface/vite.config.ts.timestamp*
*.local
src/ESP32React/WWWData.h
.yarn/*
.yarnrc.yml
# i18n generated files
interface/src/i18n/i18n-react.tsx

View File

@@ -217,7 +217,7 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
## **IMPORTANT! BREAKING CHANGES**
Writeable Text entities have moved from type `sensor` to `text` in Home Assistant to make them also editable within an HA dashboard. Examples are `datetime`, `holidays`, `switchtime`, `vacations`, `maintenancedate`... You will need to manually remove any old discovery topics from your MQTT broker using an application like MQTT Explorer.
Writeable Text entities have moved from type `sensor` to `text` in Home Assistant to make them also editable within an HA dashboard. Examples are `datetime`, `holidays`, `switchtime`, `vacations`, `maintenancedate`. You will need to manually remove any old discovery topics from your MQTT broker using an application like MQTT Explorer.
## Added

View File

@@ -1,56 +1 @@
# Changelog
For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
## [3.7.3]
## Added
- analogsensor types: NTC and RGB-Led
- Flag for HMC310 [#2465](https://github.com/emsesp/EMS-ESP32/issues/2465)
- boiler auxheatersource [#2489](https://github.com/emsesp/EMS-ESP32/discussions/2489)
- thermostat last error for RC100/300 [#2501](https://github.com/emsesp/EMS-ESP32/issues/2501)
- boiler 0xC6 telegram [#1963](https://github.com/emsesp/EMS-ESP32/issues/1963)
- CS6800i changes [#2448](https://github.com/emsesp/EMS-ESP32/issues/2448), [#2449](https://github.com/emsesp/EMS-ESP32/issues/2449)
- charging pump [#2544](https://github.com/emsesp/EMS-ESP32/issues/2544)
- hybrid CSH5800iG [#2569](https://github.com/emsesp/EMS-ESP32/issues/2569)
- add EMS Device details to Home Assistant MQTT Discovery
- disinfection command [#2601](https://github.com/emsesp/EMS-ESP32/issues/2601)
- added new board profile for upcoming BBQKees E32V2.2
- set differential pressure entity in Mixer device
- set set climate action cooling/heating in HA [#2583](https://github.com/emsesp/EMS-ESP32/issues/2583)
- Internal sensors of E32V2_2
- FW200 display options [#2610](https://github.com/emsesp/EMS-ESP32/discussions/2610)
- CR11 mode settings OFF/MANUAL depends on selTemp [#2437](https://github.com/emsesp/EMS-ESP32/issues/2437)
- Fuse settings for BBQKees boards
- Analogsensors for pulse output [#2624](https://github.com/emsesp/EMS-ESP32/discussions/2624)
- Analogsensors frequency input [#2631](https://github.com/emsesp/EMS-ESP32/discussions/2631)
- SRC plus thermostats [#2636](https://github.com/emsesp/EMS-ESP32/issues/2636)
- Greenstar 2000 [#2645](https://github.com/emsesp/EMS-ESP32/issues/2645)
- RC3xx `dhw modetype` [#2659](https://github.com/emsesp/EMS-ESP32/discussions/2659)
## Fixed
- dhw/switchtime [#2490](https://github.com/emsesp/EMS-ESP32/issues/2490)
- switch to secure mqtt [#2492](https://github.com/emsesp/EMS-ESP32/issues/2492)
- update link buttons [#2497](https://github.com/emsesp/EMS-ESP32/issues/2497)
- refresh scheduler states [#2502](https://github.com/emsesp/EMS-ESP32/discussions/2502)
- also rebuild HA config on mqtt connect for scheduler, custom and shower
- FB100 controls the hc, not the master [#2510](https://github.com/emsesp/EMS-ESP32/issues/2510)
- IPM DHW module, [#2524](https://github.com/emsesp/EMS-ESP32/issues/2524)
- charge optimization [#2543](https://github.com/emsesp/EMS-ESP32/issues/2543)
- shower active state retained, shows correctly in HA
- MQTT Command Topic with slashes [#2571](https://github.com/emsesp/EMS-ESP32/issues/2571)
- Add pulsed water meter input to V1.3 gateway with Lilygo S3 [#2550](https://github.com/emsesp/EMS-ESP32/issues/2550)
- fix missing long 10-second press of Button to perform a factory reset
- fix wwMaxPower on Junkers ZBS14 [#2609](https://github.com/emsesp/EMS-ESP32/issues/2609)
- ventilation bypass state from telegram 0x55C [#1197](https://github.com/emsesp/EMS-ESP32/issues/1197)
- set selflowtemp for ems+ boilers [#2641](https://github.com/emsesp/EMS-ESP32/discussions/2641)
## Changed
- show console log with ISO date/time [#2533](https://github.com/emsesp/EMS-ESP32/discussions/2533)
- remove ESP32 CPU temperature
- updated core libraries like AsyncTCP, AsyncWebServer and Modbus
- remove command `scan deep`
- ignore repeated `forceheatingoff` commands [#2641](https://github.com/emsesp/EMS-ESP32/discussions/2641)

View File

@@ -69,7 +69,7 @@ Format: `<type>(<scope>): <subject>`
## Example
```text
```
feat: add hat wobble
^--^ ^------------^
| |
@@ -96,7 +96,7 @@ References:
## Contributor License Agreement (CLA)
```text
```
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I

View File

@@ -98,10 +98,13 @@ CXX := /usr/bin/g++
# LDFLAGS Linker Flags
#----------------------------------------------------------------------
CPPFLAGS += $(DEFINES) $(DEFAULTS) $(INCLUDE)
CPPFLAGS += -ggdb -g3 -MMD
CPPFLAGS += -ggdb -g3 -O3
CPPFLAGS += -MMD
CPPFLAGS += -flto=auto -fno-lto
CPPFLAGS += -Wall -Wextra -Werror -Wswitch-enum
CPPFLAGS += -Wno-unused-parameter -Wno-missing-braces -Wno-vla-cxx-extension
CPPFLAGS += -Wall -Wextra -Werror
CPPFLAGS += -Wswitch-enum
CPPFLAGS += -Wno-unused-parameter
CPPFLAGS += -Wno-missing-braces
CPPFLAGS += $(EXTRA_CPPFLAGS)

View File

@@ -35,7 +35,7 @@
[![chat](https://img.shields.io/discord/816637840644505620.svg?style=flat-square&color=blueviolet)](https://discord.gg/3J3GgnzpyT)
[![GitHub stars](https://img.shields.io/github/stars/emsesp/EMS-ESP32.svg?style=social&label=Star)](https://github.com/emsesp/EMS-ESP32/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/emsesp/EMS-ESP32.svg?style=social&label=Fork)](https://github.com/emsesp/EMS-ESP32/network)
[![GitHub forks](https://img.shields.io/github/forks/emsesp/EMS-ESP32.svg?style=social&label=Fork)](https://github.com/emsesp/EMS-ES32P/network)
[![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/paypalme/prderbyshire/2)
**EMS-ESP** is an open-source firmware for the Espressif ESP32 microcontroller to communicate with **EMS** (Energy Management System) compatible equipment from manufacturers such as Bosch, Buderus, Nefit, Junkers, Worcester, Sieger, elm.leblanc and iVT.
@@ -60,7 +60,7 @@ It requires a small circuit to interface with the EMS bus which can be purchased
## 🚀&nbsp; **Installing**
Head over to [download.emsesp.org](https://download.emsesp.org) for instructions on how to install EMS-ESP. There is also further details on which boards are supported in [this section](https://docs.emsesp.org/Installing/) of the documentation.
Head over to [download.emsesp.org](https://download.emsesp.org) for instructions on how to install EMS-ESP. There is also further details on which boards are supported in [this section](https://docs.emsesp.org/Getting-Started/#first-time-install) of the documentation.
## 📋&nbsp; **Documentation**
@@ -88,7 +88,7 @@ If you like **EMS-ESP**, please give it a ✨ on GitHub, or even better fork it
- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these awesome open source libraries
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for all the JSON processing
- [espMqttClient](https://github.com/bertmelis/espMqttClient) for the MQTT client
- [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) and [AsyncTCP](https://github.com/ESP32Async/AsyncTCP) for the Web server and TCP backends
- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance
## 📜&nbsp; **License**

View File

@@ -1,48 +0,0 @@
{
"build": {
"core": "esp32",
"extra_flags": [
"-DARDUINO_XIAO_ESP32C6",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1"
],
"f_cpu": "160000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [
[
"0x2886",
"0x0046"
],
[
"0x303a",
"0x1001"
]
],
"mcu": "esp32c6",
"variant": "XIAO_ESP32C6"
},
"connectivity": [
"wifi",
"bluetooth",
"zigbee",
"thread"
],
"debug": {
"openocd_target": "esp32c6.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "Seeed Studio XIAO ESP32C6",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"require_upload_port": true,
"speed": 460800
},
"url": "https://wiki.seeedstudio.com/XIAO_ESP32C6_Getting_Started/",
"vendor": "Seeed Studio"
}

View File

@@ -32,8 +32,6 @@
"**/*.json",
"src/core/modbus_entity_parameters.hpp",
"sdkconfig.*",
"managed_components/**",
"pnpm-*.yaml",
"vite.config.ts"
"managed_components/**"
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,227 @@
telegram_type_id,name,is_fetched
0x04,UBAFactory,fetched
0x06,RCTime,
0x0A,EasyMonitor,fetched
0x10,UBAErrorMessage1,
0x11,UBAErrorMessage2,
0x12,RCErrorMessage,
0x13,RCErrorMessage2,
0x14,UBATotalUptime,fetched
0x15,UBAMaintenanceData,
0x16,UBAParameters,fetched
0x18,UBAMonitorFast,
0x19,UBAMonitorSlow,
0x1A,UBASetPoints,
0x1C,UBAMaintenanceStatus,
0x1E,WM10TempMessage,
0x23,JunkersSetMixer,fetched
0x26,UBASettingsWW,fetched
0x28,WeatherComp,fetched
0x2A,MC110Status,
0x2E,Meters,
0x33,UBAParameterWW,fetched
0x34,UBAMonitorWW,
0x35,UBAFlags,
0x37,WWSettings,fetched
0x38,WWTimer,fetched
0x39,WWCircTimer,fetched
0x3A,RC30WWSettings,fetched
0x3B,Energy,
0x3D,RC35Set,
0x3E,RC35Monitor,
0x3F,RC35Timer,
0x40,RC30Temp,
0x41,RC30Monitor,
0x42,RC35Timer2,
0x47,RC35Set,
0x48,RC35Monitor,
0x49,RC35Timer,
0x4C,RC35Timer2,
0x51,RC35Set,
0x52,RC35Monitor,
0x53,RC35Timer,
0x56,RC35Timer2,
0x5B,RC35Set,
0x5C,RC35Monitor,
0x5D,RC35Timer,
0x60,RC35Timer2,
0x96,SM10Config,fetched
0x97,SM10Monitor,
0x9C,WM10MonitorMessage,
0x9D,WM10SetMessage,
0xA2,RCError,
0xA3,RCOutdoorTemp,
0xA5,IBASettings,fetched
0xA7,RC30Set,
0xA9,RC30Vacation,fetched
0xAA,MMConfigMessage,fetched
0xAB,MMStatusMessage,
0xAC,MMSetMessage,
0xAF,RC20Remote,
0xB0,RC10Set,
0xB1,RC10Monitor,
0xBB,HybridSettings,fetched
0xBF,ErrorMessage,
0xC2,UBAErrorMessage3,
0xD1,UBAOutdoorTemp,
0xE3,UBAMonitorSlowPlus2,
0xE4,UBAMonitorFastPlus,
0xE5,UBAMonitorSlowPlus,
0xE6,UBAParametersPlus,fetched
0xE9,UBAMonitorWWPlus,
0xEA,UBAParameterWWPlus,fetched
0x0101,ISM1Set,fetched
0x0103,ISM1StatusMessage,fetched
0x0104,ISM2StatusMessage,
0x010C,IPMStatusMessage,
0x011E,IPMTempMessage,
0x012E,HPEnergy1,
0x013B,HPEnergy2,
0x0165,JunkersSet,
0x0166,JunkersSet,
0x0167,JunkersSet,
0x0168,JunkersSet,
0x016E,Absent,fetched
0x016F,JunkersMonitor,
0x0170,JunkersMonitor,
0x0171,JunkersMonitor,
0x0172,JunkersMonitor,
0x0179,JunkersSet,
0x017A,JunkersSet,
0x017B,JunkersSet,
0x017C,JunkersSet,
0x01D3,JunkersDhw,fetched
0x023A,RC300OutdoorTemp,fetched
0x023E,PVSettings,fetched
0x0240,RC300Settings,fetched
0x0241,RC300Settings,fetched
0x0267,RC300Floordry,
0x0269,RC300Holiday,fetched
0x0291,HPMode,fetched
0x0292,HPMode,fetched
0x0293,HPMode,fetched
0x0294,HPMode,fetched
0x029B,RC300Curves,
0x029C,RC300Curves,
0x029D,RC300Curves,
0x029E,RC300Curves,
0x029F,RC300Curves,
0x02A0,RC300Curves,
0x02A1,RC300Curves,
0x02A2,RC300Curves,
0x02A5,RC300Monitor,fetched
0x02A6,RC300Monitor,
0x02A7,CRFMonitor,
0x02A8,RC300Monitor,
0x02A9,RC300Monitor,
0x02AA,RC300Monitor,
0x02AB,RC300Monitor,
0x02AC,RC300Monitor,
0x02AF,RC300Summer,
0x02B0,RC300Summer,
0x02B1,RC300Summer,
0x02B2,RC300Summer,
0x02B3,RC300Summer,
0x02B4,RC300Summer,
0x02B5,RC300Summer,
0x02B6,RC300Summer,
0x02B9,RC300Set,
0x02BA,RC300Set,
0x02BB,RC300Set,
0x02BC,RC300Set,
0x02BD,RC300Set,
0x02BE,RC300Set,
0x02BF,RC300Set,
0x02C0,RC300Set,
0x02CC,HPPressure,fetched
0x02CD,MMPLUSConfigMessage,fetched
0x02CE,RC300Set2,
0x02D0,RC300Set2,
0x02D2,RC300Set2,
0x02D6,HPPump2,fetched
0x02D7,MMPLUSStatusMessage,
0x02F5,RC300WWmode,fetched
0x02F6,RC300WW2mode,fetched
0x0313,MMPLUSConfigMessage_WWC,fetched
0x031B,RC300WWtemp,fetched
0x031D,RC300WWmode2,
0x031E,RC300WWmode2,
0x0331,MMPLUSStatusMessage_WWC,
0x0358,SM100SystemConfig,fetched
0x035A,SM100CircuitConfig,fetched
0x035C,SM100HeatAssist,fetched
0x035D,SM100Circuit2Config,fetched
0x035F,SM100Config1,fetched
0x0361,SM100Differential,fetched
0x0362,SM100Monitor,
0x0363,SM100Monitor2,
0x0364,SM100Status,
0x0366,SM100Config,
0x036A,SM100Status2,
0x0380,SM100CollectorConfig,fetched
0x038E,SM100Energy,fetched
0x0391,SM100Time,fetched
0x043F,CRHolidays,fetched
0x0467,HPSet,
0x0468,HPSet,
0x0469,HPSet,
0x046A,HPSet,
0x0471,RC300Summer2,
0x0472,RC300Summer2,
0x0473,RC300Summer2,
0x0474,RC300Summer2,
0x0475,RC300Summer2,
0x0476,RC300Summer2,
0x0477,RC300Summer2,
0x0478,RC300Summer2,
0x047B,HP2,
0x0484,HPSilentMode,fetched
0x0485,HpCooling,fetched
0x0486,HpInConfig,fetched
0x0488,HPValve,fetched
0x048A,HpPool,fetched
0x048B,HPPumps,fetched
0x048D,HpPower,fetched
0x048F,HpTemperatures,
0x0491,HPAdditionalHeater,fetched
0x0492,HpHeaterConfig,fetched
0x0494,UBAEnergySupplied,
0x0495,UBAInformation,
0x0499,HPDhwSettings,fetched
0x049C,HPSettings2,fetched
0x049D,HPSettings3,fetched
0x04A2,HpInput,fetched
0x04A5,HPFan,fetched
0x04A7,HPPowerLimit,fetched
0x04AA,HPPower2,fetched
0x04AE,HPEnergy,fetched
0x04AF,HPMeters,fetched
0x056B,VentilationMode,fetched
0x0583,VentilationMonitor,
0x0585,Blowerspeed,
0x0587,Bypass,
0x05BA,HpPoolStatus,fetched
0x05D9,Airquality,
0x0772,HIUSettings,
0x0779,HIUMonitor,
0x07A5,SM100wwCirc,fetched
0x07A6,SM100wwParam,fetched
0x07AA,SM100wwStatus,
0x07AB,SM100wwCommand,
0x07AC,SM100wwParam1,
0x07AD,SM100ValveStatus,
0x07AE,SM100wwKeepWarm,fetched
0x07D6,SM100wwTemperature,
0x07E0,SM100wwStatus2,fetched
0x0935,EM100SetMessage,fetched
0x0936,EM100OutMessage,
0x0937,EM100TempMessage,
0x0938,EM100InputMessage,
0x0939,EM100MonitorMessage,
0x093A,EM100ConfigMessage,
0x0998,HPSettings,fetched
0x0999,HPFunctionTest,fetched
0x099A,HPStarts,
0x099B,HPFlowTemp,
0x099C,HPComp,
0x09A0,HPTemperature,
1 telegram_type_id name is_fetched
1 telegram_type_id name is_fetched
2 0x04 UBAFactory fetched
3 0x06 RCTime
4 0x0A EasyMonitor fetched
5 0x10 UBAErrorMessage1
6 0x11 UBAErrorMessage2
7 0x12 RCErrorMessage
8 0x13 RCErrorMessage2
9 0x14 UBATotalUptime fetched
10 0x15 UBAMaintenanceData
11 0x16 UBAParameters fetched
12 0x18 UBAMonitorFast
13 0x19 UBAMonitorSlow
14 0x1A UBASetPoints
15 0x1C UBAMaintenanceStatus
16 0x1E WM10TempMessage
17 0x23 JunkersSetMixer fetched
18 0x26 UBASettingsWW fetched
19 0x28 WeatherComp fetched
20 0x2A MC110Status
21 0x2E Meters
22 0x33 UBAParameterWW fetched
23 0x34 UBAMonitorWW
24 0x35 UBAFlags
25 0x37 WWSettings fetched
26 0x38 WWTimer fetched
27 0x39 WWCircTimer fetched
28 0x3A RC30WWSettings fetched
29 0x3B Energy
30 0x3D RC35Set
31 0x3E RC35Monitor
32 0x3F RC35Timer
33 0x40 RC30Temp
34 0x41 RC30Monitor
35 0x42 RC35Timer2
36 0x47 RC35Set
37 0x48 RC35Monitor
38 0x49 RC35Timer
39 0x4C RC35Timer2
40 0x51 RC35Set
41 0x52 RC35Monitor
42 0x53 RC35Timer
43 0x56 RC35Timer2
44 0x5B RC35Set
45 0x5C RC35Monitor
46 0x5D RC35Timer
47 0x60 RC35Timer2
48 0x96 SM10Config fetched
49 0x97 SM10Monitor
50 0x9C WM10MonitorMessage
51 0x9D WM10SetMessage
52 0xA2 RCError
53 0xA3 RCOutdoorTemp
54 0xA5 IBASettings fetched
55 0xA7 RC30Set
56 0xA9 RC30Vacation fetched
57 0xAA MMConfigMessage fetched
58 0xAB MMStatusMessage
59 0xAC MMSetMessage
60 0xAF RC20Remote
61 0xB0 RC10Set
62 0xB1 RC10Monitor
63 0xBB HybridSettings fetched
64 0xBF ErrorMessage
65 0xC2 UBAErrorMessage3
66 0xD1 UBAOutdoorTemp
67 0xE3 UBAMonitorSlowPlus2
68 0xE4 UBAMonitorFastPlus
69 0xE5 UBAMonitorSlowPlus
70 0xE6 UBAParametersPlus fetched
71 0xE9 UBAMonitorWWPlus
72 0xEA UBAParameterWWPlus fetched
73 0x0101 ISM1Set fetched
74 0x0103 ISM1StatusMessage fetched
75 0x0104 ISM2StatusMessage
76 0x010C IPMStatusMessage
77 0x011E IPMTempMessage
78 0x012E HPEnergy1
79 0x013B HPEnergy2
80 0x0165 JunkersSet
81 0x0166 JunkersSet
82 0x0167 JunkersSet
83 0x0168 JunkersSet
84 0x016E Absent fetched
85 0x016F JunkersMonitor
86 0x0170 JunkersMonitor
87 0x0171 JunkersMonitor
88 0x0172 JunkersMonitor
89 0x0179 JunkersSet
90 0x017A JunkersSet
91 0x017B JunkersSet
92 0x017C JunkersSet
93 0x01D3 JunkersDhw fetched
94 0x023A RC300OutdoorTemp fetched
95 0x023E PVSettings fetched
96 0x0240 RC300Settings fetched
97 0x0241 RC300Settings fetched
98 0x0267 RC300Floordry
99 0x0269 RC300Holiday fetched
100 0x0291 HPMode fetched
101 0x0292 HPMode fetched
102 0x0293 HPMode fetched
103 0x0294 HPMode fetched
104 0x029B RC300Curves
105 0x029C RC300Curves
106 0x029D RC300Curves
107 0x029E RC300Curves
108 0x029F RC300Curves
109 0x02A0 RC300Curves
110 0x02A1 RC300Curves
111 0x02A2 RC300Curves
112 0x02A5 RC300Monitor fetched
113 0x02A6 RC300Monitor
114 0x02A7 CRFMonitor
115 0x02A8 RC300Monitor
116 0x02A9 RC300Monitor
117 0x02AA RC300Monitor
118 0x02AB RC300Monitor
119 0x02AC RC300Monitor
120 0x02AF RC300Summer
121 0x02B0 RC300Summer
122 0x02B1 RC300Summer
123 0x02B2 RC300Summer
124 0x02B3 RC300Summer
125 0x02B4 RC300Summer
126 0x02B5 RC300Summer
127 0x02B6 RC300Summer
128 0x02B9 RC300Set
129 0x02BA RC300Set
130 0x02BB RC300Set
131 0x02BC RC300Set
132 0x02BD RC300Set
133 0x02BE RC300Set
134 0x02BF RC300Set
135 0x02C0 RC300Set
136 0x02CC HPPressure fetched
137 0x02CD MMPLUSConfigMessage fetched
138 0x02CE RC300Set2
139 0x02D0 RC300Set2
140 0x02D2 RC300Set2
141 0x02D6 HPPump2 fetched
142 0x02D7 MMPLUSStatusMessage
143 0x02F5 RC300WWmode fetched
144 0x02F6 RC300WW2mode fetched
145 0x0313 MMPLUSConfigMessage_WWC fetched
146 0x031B RC300WWtemp fetched
147 0x031D RC300WWmode2
148 0x031E RC300WWmode2
149 0x0331 MMPLUSStatusMessage_WWC
150 0x0358 SM100SystemConfig fetched
151 0x035A SM100CircuitConfig fetched
152 0x035C SM100HeatAssist fetched
153 0x035D SM100Circuit2Config fetched
154 0x035F SM100Config1 fetched
155 0x0361 SM100Differential fetched
156 0x0362 SM100Monitor
157 0x0363 SM100Monitor2
158 0x0364 SM100Status
159 0x0366 SM100Config
160 0x036A SM100Status2
161 0x0380 SM100CollectorConfig fetched
162 0x038E SM100Energy fetched
163 0x0391 SM100Time fetched
164 0x043F CRHolidays fetched
165 0x0467 HPSet
166 0x0468 HPSet
167 0x0469 HPSet
168 0x046A HPSet
169 0x0471 RC300Summer2
170 0x0472 RC300Summer2
171 0x0473 RC300Summer2
172 0x0474 RC300Summer2
173 0x0475 RC300Summer2
174 0x0476 RC300Summer2
175 0x0477 RC300Summer2
176 0x0478 RC300Summer2
177 0x047B HP2
178 0x0484 HPSilentMode fetched
179 0x0485 HpCooling fetched
180 0x0486 HpInConfig fetched
181 0x0488 HPValve fetched
182 0x048A HpPool fetched
183 0x048B HPPumps fetched
184 0x048D HpPower fetched
185 0x048F HpTemperatures
186 0x0491 HPAdditionalHeater fetched
187 0x0492 HpHeaterConfig fetched
188 0x0494 UBAEnergySupplied
189 0x0495 UBAInformation
190 0x0499 HPDhwSettings fetched
191 0x049C HPSettings2 fetched
192 0x049D HPSettings3 fetched
193 0x04A2 HpInput fetched
194 0x04A5 HPFan fetched
195 0x04A7 HPPowerLimit fetched
196 0x04AA HPPower2 fetched
197 0x04AE HPEnergy fetched
198 0x04AF HPMeters fetched
199 0x056B VentilationMode fetched
200 0x0583 VentilationMonitor
201 0x0585 Blowerspeed
202 0x0587 Bypass
203 0x05BA HpPoolStatus fetched
204 0x05D9 Airquality
205 0x0772 HIUSettings
206 0x0779 HIUMonitor
207 0x07A5 SM100wwCirc fetched
208 0x07A6 SM100wwParam fetched
209 0x07AA SM100wwStatus
210 0x07AB SM100wwCommand
211 0x07AC SM100wwParam1
212 0x07AD SM100ValveStatus
213 0x07AE SM100wwKeepWarm fetched
214 0x07D6 SM100wwTemperature
215 0x07E0 SM100wwStatus2 fetched
216 0x0935 EM100SetMessage fetched
217 0x0936 EM100OutMessage
218 0x0937 EM100TempMessage
219 0x0938 EM100InputMessage
220 0x0939 EM100MonitorMessage
221 0x093A EM100ConfigMessage
222 0x0998 HPSettings fetched
223 0x0999 HPFunctionTest fetched
224 0x099A HPStarts
225 0x099B HPFlowTemp
226 0x099C HPComp
227 0x09A0 HPTemperature

4
interface/.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
/.yarn/** linguist-vendored
/.yarn/releases/* binary
/.yarn/plugins/**/* binary
/.pnp.* binary linguist-generated

View File

@@ -4,4 +4,5 @@ dist/
src/i18n/*
.prettierrc
.typesafe-i18n.json
.yarn/
.typesafe-i18n.json

935
interface/.yarn/releases/yarn-4.7.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

3
interface/.yarnrc.yml Normal file
View File

@@ -0,0 +1,3 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.7.0.cjs

View File

@@ -10,7 +10,8 @@ export default tseslint.config(
{
languageOptions: {
parserOptions: {
project: true
project: true,
tsconfigRootDir: import.meta.dirname
}
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "EMS-ESP",
"version": "3.7.3",
"version": "3.7.2",
"description": "EMS-ESP WebUI",
"homepage": "https://emsesp.org",
"author": "proddy, emsesp.org",
@@ -8,60 +8,59 @@
"private": true,
"type": "module",
"scripts": {
"preinstall": "npx only-allow pnpm",
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"build-hosted": "typesafe-i18n && vite build --mode hosted",
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"pnpm:mock-rest\" \"vite preview\"",
"mock-rest": "bun --watch ../mock-api/restServer.ts",
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"pnpm:mock-rest\" \"vite\"",
"build-hosted": "typesafe-i18n --no-watch && vite build --mode hosted",
"preview-standalone": "typesafe-i18n --no-watch && vite build && concurrently -c \"auto\" \"yarn:mock-rest\" \"vite preview\"",
"mock-rest": "bun --watch ../mock-api/rest_server.ts",
"standalone": "concurrently -c \"auto\" \"typesafe-i18n\" \"yarn:mock-rest\" \"vite\"",
"typesafe-i18n": "typesafe-i18n --no-watch",
"webUI": "node progmem-generator.js",
"format": "prettier -l -w '**/*.{ts,tsx,js,css,json,md}'",
"lint": "eslint . --fix"
},
"dependencies": {
"@alova/adapter-xhr": "2.2.1",
"@alova/adapter-xhr": "2.1.1",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.4",
"@mui/material": "^7.3.4",
"@table-library/react-table-library": "4.1.15",
"alova": "3.3.4",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.4.8",
"@mui/material": "^6.4.8",
"@table-library/react-table-library": "4.1.12",
"alova": "3.2.10",
"async-validator": "^4.2.5",
"formidable": "^3.5.4",
"jwt-decode": "^4.0.0",
"magic-string": "^0.30.19",
"mime-types": "^3.0.1",
"preact": "^10.27.2",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"mime-types": "^2.1.35",
"preact": "^10.26.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"react-router": "^7.9.4",
"react-router": "^7.4.0",
"react-toastify": "^11.0.5",
"typesafe-i18n": "^5.26.2",
"typescript": "^5.9.3"
"typescript": "^5.8.2"
},
"devDependencies": {
"@babel/core": "^7.28.4",
"@eslint/js": "^9.38.0",
"@babel/core": "^7.26.10",
"@eslint/js": "^9.23.0",
"@preact/compat": "^18.3.1",
"@preact/preset-vite": "^2.10.2",
"@preact/preset-vite": "^2.10.1",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/node": "^24.9.1",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"concurrently": "^9.2.1",
"eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.6.2",
"rollup-plugin-visualizer": "^6.0.5",
"terser": "^5.44.0",
"typescript-eslint": "^8.46.2",
"vite": "^7.1.11",
"@types/formidable": "^3",
"@types/node": "^22.13.11",
"@types/react": "^19.0.12",
"@types/react-dom": "^19.0.4",
"concurrently": "^9.1.2",
"eslint": "^9.23.0",
"eslint-config-prettier": "^10.1.1",
"formidable": "^3.5.2",
"prettier": "^3.5.3",
"rollup-plugin-visualizer": "^5.14.0",
"terser": "^5.39.0",
"typescript-eslint": "8.27.0",
"vite": "^6.2.2",
"vite-plugin-imagemin": "^0.6.1",
"vite-tsconfig-paths": "^5.1.4"
},
"packageManager": "pnpm@10.19.0+sha512.c9fc7236e92adf5c8af42fd5bf1612df99c2ceb62f27047032f4720b33f8eacdde311865e91c411f2774f618d82f320808ecb51718bfa82c060c4ba7c76a32b8"
"packageManager": "yarn@4.7.0"
}

6056
interface/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
onlyBuiltDependencies:
- cwebp-bin
- esbuild
- gifsicle
- jpegtran-bin
- mozjpeg
- optipng-bin
- pngquant-bin

View File

@@ -4,7 +4,6 @@ import {
existsSync,
readFileSync,
readdirSync,
statSync,
unlinkSync
} from 'fs';
import mime from 'mime-types';
@@ -16,78 +15,66 @@ const INDENT = ' ';
const outputPath = '../src/ESP32React/WWWData.h';
const sourcePath = './dist';
const bytesPerLine = 20;
let totalSize = 0;
let bundleStats = {
js: { count: 0, uncompressed: 0, compressed: 0 },
css: { count: 0, uncompressed: 0, compressed: 0 },
html: { count: 0, uncompressed: 0, compressed: 0 },
svg: { count: 0, uncompressed: 0, compressed: 0 },
other: { count: 0, uncompressed: 0, compressed: 0 }
};
var totalSize = 0;
const generateWWWClass =
() => `typedef std::function<void(const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash)> RouteRegistrationHandler;
// Bundle Statistics:
// - Total compressed size: ${(totalSize / 1000).toFixed(1)} KB
// - Total uncompressed size: ${(Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) / 1000).toFixed(1)} KB
// - Compression ratio: ${(((Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) - totalSize) / Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0)) * 100).toFixed(1)}%
// - Generated on: ${new Date().toISOString()}
const generateWWWClass = () =>
`typedef std::function<void(const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash)> RouteRegistrationHandler;
// Total size is ${totalSize} bytes
class WWWData {
${INDENT}public:
${INDENT.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
${fileInfo.map((f) => `${INDENT.repeat(3)}handler("${f.uri}", "${f.mimeType}", ${f.variable}, ${f.size}, "${f.hash}");`).join('\n')}
${INDENT.repeat(2)}}
${indent}public:
${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
${fileInfo.map((file) => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size}, "${file.hash}");`).join('\n')}
${indent.repeat(2)}}
};
`;
const getFilesSync = (dir, files = []) => {
function getFilesSync(dir, files = []) {
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
const entryPath = resolve(dir, entry.name);
entry.isDirectory() ? getFilesSync(entryPath, files) : files.push(entryPath);
if (entry.isDirectory()) {
getFilesSync(entryPath, files);
} else {
files.push(entryPath);
}
});
return files;
};
}
const cleanAndOpen = (path) => {
existsSync(path) && unlinkSync(path);
function cleanAndOpen(path) {
if (existsSync(path)) {
unlinkSync(path);
}
return createWriteStream(path, { flags: 'w+' });
};
const getFileType = (filePath) => {
const ext = filePath.split('.').pop().toLowerCase();
if (ext === 'js') return 'js';
if (ext === 'css') return 'css';
if (ext === 'html') return 'html';
if (ext === 'svg') return 'svg';
return 'other';
};
}
const writeFile = (relativeFilePath, buffer) => {
const variable = `ESP_REACT_DATA_${fileInfo.length}`;
const variable = 'ESP_REACT_DATA_' + fileInfo.length;
const mimeType = mime.lookup(relativeFilePath);
const fileType = getFileType(relativeFilePath);
let size = 0;
writeStream.write(`const uint8_t ${variable}[] = {`);
var size = 0;
writeStream.write('const uint8_t ' + variable + '[] = {');
// const zipBuffer = zlib.brotliCompressSync(buffer, { quality: 1 });
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
const hash = crypto.createHash('sha256').update(zipBuffer).digest('hex');
// create sha
const hashSum = crypto.createHash('sha256');
hashSum.update(zipBuffer);
const hash = hashSum.digest('hex');
zipBuffer.forEach((b) => {
if (!(size % bytesPerLine)) {
writeStream.write('\n' + INDENT);
writeStream.write('\n');
writeStream.write(indent);
}
writeStream.write('0x' + b.toString(16).toUpperCase().padStart(2, '0') + ',');
writeStream.write('0x' + ('00' + b.toString(16).toUpperCase()).slice(-2) + ',');
size++;
});
size % bytesPerLine && writeStream.write('\n');
writeStream.write('};\n\n');
if (size % bytesPerLine) {
writeStream.write('\n');
}
// Update bundle statistics
bundleStats[fileType].count++;
bundleStats[fileType].uncompressed += buffer.length;
bundleStats[fileType].compressed += zipBuffer.length;
writeStream.write('};\n\n');
fileInfo.push({
uri: '/' + relativeFilePath.replace(sep, '/'),
@@ -97,52 +84,32 @@ const writeFile = (relativeFilePath, buffer) => {
hash
});
// console.log(relativeFilePath + ' (size ' + size + ' bytes)');
totalSize += size;
};
console.log(`Generating ${outputPath} from ${sourcePath}`);
// start
console.log('Generating ' + outputPath + ' from ' + sourcePath);
const includes = ARDUINO_INCLUDES;
const indent = INDENT;
const fileInfo = [];
const writeStream = cleanAndOpen(resolve(outputPath));
writeStream.write(ARDUINO_INCLUDES);
// includes
writeStream.write(includes);
// process static files
const buildPath = resolve(sourcePath);
for (const filePath of getFilesSync(buildPath)) {
writeFile(relative(buildPath, filePath), readFileSync(filePath));
const readStream = readFileSync(filePath);
const relativeFilePath = relative(buildPath, filePath);
writeFile(relativeFilePath, readStream);
}
// add class
writeStream.write(generateWWWClass());
// end
writeStream.end();
// Calculate and display bundle statistics
const totalUncompressed = Object.values(bundleStats).reduce(
(sum, stat) => sum + stat.uncompressed,
0
);
const totalCompressed = Object.values(bundleStats).reduce(
(sum, stat) => sum + stat.compressed,
0
);
const compressionRatio = (
((totalUncompressed - totalCompressed) / totalUncompressed) *
100
).toFixed(1);
console.log('\n📊 Bundle Size Analysis:');
console.log('='.repeat(50));
console.log(`Total compressed size: ${(totalSize / 1000).toFixed(1)} KB`);
console.log(`Total uncompressed size: ${(totalUncompressed / 1000).toFixed(1)} KB`);
console.log(`Compression ratio: ${compressionRatio}%`);
console.log('\n📁 File Type Breakdown:');
Object.entries(bundleStats).forEach(([type, stats]) => {
if (stats.count > 0) {
const ratio = (
((stats.uncompressed - stats.compressed) / stats.uncompressed) *
100
).toFixed(1);
console.log(
`${type.toUpperCase().padEnd(4)}: ${stats.count} files, ${(stats.uncompressed / 1000).toFixed(1)} KB → ${(stats.compressed / 1000).toFixed(1)} KB (${ratio}% compression)`
);
}
});
console.log('='.repeat(50));
console.log('Total size: ' + totalSize / 1000 + ' KB');

View File

@@ -37,9 +37,10 @@ const AuthenticatedRouting = () => {
<Route path="/dashboard/*" element={<Dashboard />} />
<Route path="/devices/*" element={<Devices />} />
<Route path="/sensors/*" element={<Sensors />} />
<Route path="/help/*" element={<Help />} />
<Route path="/status/*" element={<Status />} />
<Route path="/help/*" element={<Help />} />
<Route path="/*" element={<Navigate to="/" />} />
<Route path="/status/hardwarestatus/*" element={<HardwareStatus />} />
<Route path="/status/activity" element={<Activity />} />
<Route path="/status/log" element={<SystemLog />} />
@@ -67,8 +68,6 @@ const AuthenticatedRouting = () => {
<Route path="/customentities" element={<CustomEntities />} />
</>
)}
<Route path="/*" element={<Navigate to="/" />} />
</Routes>
</Layout>
);

View File

@@ -98,7 +98,7 @@ const SignIn = () => {
<Box display="flex" flexDirection="column" alignItems="center">
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
disabled={processing}
sx={{
width: 240
@@ -117,7 +117,7 @@ const SignIn = () => {
}}
/>
<ValidatedPasswordField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
disabled={processing}
sx={{
width: 240

View File

@@ -70,7 +70,6 @@ export const readDeviceEntities = (id: number) =>
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
params: { id },
responseType: 'arraybuffer',
// @ts-expect-error - exactOptionalPropertyTypes compatibility issue
transform(data) {
return (data as DeviceEntity[]).map((de: DeviceEntity) => ({
...de,
@@ -93,7 +92,6 @@ export const writeDeviceName = (data: { id: number; name: string }) =>
// SettingsScheduler
export const readSchedule = () =>
alovaInstance.Get<ScheduleItem[]>('/rest/schedule', {
// @ts-expect-error - exactOptionalPropertyTypes compatibility issue
transform(data) {
return (data as Schedule).schedule.map((si: ScheduleItem) => ({
...si,
@@ -131,7 +129,6 @@ export const writeModules = (data: {
// CustomEntities
export const readCustomEntities = () =>
alovaInstance.Get<EntityItem[]>('/rest/customEntities', {
// @ts-expect-error - exactOptionalPropertyTypes compatibility issue
transform(data) {
return (data as Entities).entities.map((ei: EntityItem) => ({
...ei,
@@ -146,8 +143,7 @@ export const readCustomEntities = () =>
o_name: ei.name,
o_writeable: ei.writeable,
o_value: ei.value,
o_deleted: ei.deleted,
o_hide: ei.hide
o_deleted: ei.deleted
}));
}
});

View File

@@ -30,7 +30,7 @@ export const getDevVersion = () =>
cacheFor: 60 * 10 * 1000,
transform(response: { data: { name: string; published_at: string } }) {
return {
name: response.data.name.split(/\s+/).splice(-1)[0]?.substring(1) || '',
name: response.data.name.split(/\s+/).splice(-1)[0].substring(1),
published_at: response.data.published_at
};
}

View File

@@ -1,37 +1,40 @@
// @ts-nocheck - Optimized MessagePack unpacking library for EMS-ESP32
let decoder,
src,
srcEnd,
position = 0,
strings = [],
stringPosition = 0,
currentUnpackr = {},
currentStructures,
srcString,
srcStringStart = 0,
srcStringEnd = 0,
bundledStrings,
referenceMap,
dataView;
const EMPTY_ARRAY = [],
currentExtensions = [];
const defaultOptions = { useRecords: false, mapsAsObjects: true };
let decoder;
try {
decoder = new TextDecoder();
} catch (error) {}
class C1Type {}
const C1 = new C1Type();
let src;
let srcEnd;
let position = 0;
const EMPTY_ARRAY = [];
let strings = EMPTY_ARRAY;
let stringPosition = 0;
let currentUnpackr = {};
let currentStructures;
let srcString;
let srcStringStart = 0;
let srcStringEnd = 0;
let bundledStrings;
let referenceMap;
const currentExtensions = [];
let dataView;
const defaultOptions = {
useRecords: false,
mapsAsObjects: true
};
export class C1Type {}
export const C1 = new C1Type();
C1.name = 'MessagePack 0xC1';
let sequentialMode = false,
inlineObjectReadThreshold = 2,
readStruct,
onLoadedStructures,
onSaveState;
let sequentialMode = false;
let inlineObjectReadThreshold = 2;
let readStruct, onLoadedStructures, onSaveState;
// no-eval build
try {
new Function('');
} catch (error) {
// if eval variants are not supported, do not create inline object readers ever
inlineObjectReadThreshold = Infinity;
}
export class Unpackr {
constructor(options) {
if (options) {
@@ -47,15 +50,19 @@ export class Unpackr {
if (options.structures)
options.structures.sharedLength = options.structures.length;
else if (options.getStructures) {
(options.structures = []).uninitialized = true;
(options.structures = []).uninitialized = true; // this is what we use to denote an uninitialized structures
options.structures.sharedLength = 0;
}
if (options.int64AsNumber) options.int64AsType = 'number';
if (options.int64AsNumber) {
options.int64AsType = 'number';
}
}
Object.assign(this, options);
}
unpack(source, options?: any) {
if (src) {
// re-entrant execution, save the state and restore it after we do this unpack
return saveState(() => {
clearSource();
return this
@@ -79,6 +86,9 @@ export class Unpackr {
strings = EMPTY_ARRAY;
bundledStrings = null;
src = source;
// this provides cached access to the data view for a buffer if it is getting reused, which is a recommend
// technique for getting data from a database where it can be copied into an existing buffer instead of creating
// new ones
try {
dataView =
source.dataView ||
@@ -181,10 +191,10 @@ export class Unpackr {
return this.unpack(source, end);
}
}
function getPosition() {
export function getPosition() {
return position;
}
function checkedRead(options: any) {
export function checkedRead(options: any) {
try {
if (!currentUnpackr.trusted && !sequentialMode) {
const sharedLength = currentStructures.sharedLength || 0;
@@ -254,7 +264,7 @@ function restoreStructures() {
currentStructures.restoreStructures = null;
}
function read() {
export function read() {
let token = src[position++];
if (token < 0xa0) {
if (token < 0x80) {
@@ -579,7 +589,7 @@ const createSecondByteReader = (firstId, read0) =>
return structure.read();
};
function loadStructures() {
export function loadStructures() {
const loadedStructures = saveState(() => {
// save the state in case getStructures modifies our buffer
src = null;
@@ -595,8 +605,9 @@ var readFixedString = readStringJS;
var readString8 = readStringJS;
var readString16 = readStringJS;
var readString32 = readStringJS;
let isNativeAccelerationEnabled = false;
function setExtractor(extractStrings) {
export let isNativeAccelerationEnabled = false;
export function setExtractor(extractStrings) {
isNativeAccelerationEnabled = true;
readFixedString = readString(1);
readString8 = readString(2);
@@ -690,7 +701,7 @@ function readStringJS(length) {
return result;
}
function readString(source, start, length) {
export function readString(source, start, length) {
const existingSrc = src;
src = source;
position = start;
@@ -1054,7 +1065,7 @@ currentExtensions[0x70] = (data) => {
currentExtensions[0x73] = () => new Set(read());
const typedArrays = [
export const typedArrays = [
'Int8',
'Uint8',
'Uint8Clamped',
@@ -1166,20 +1177,44 @@ function saveState(callback) {
dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
return value;
}
function clearSource() {
export function clearSource() {
src = null;
referenceMap = null;
currentStructures = null;
}
function addExtension(extension) {
export function addExtension(extension) {
if (extension.unpack) currentExtensions[extension.type] = extension.unpack;
else currentExtensions[extension.type] = extension;
}
const mult10 = new Array(147);
export const mult10 = new Array(147); // this is a table matching binary exponents to the multiplier to determine significant digit rounding
for (let i = 0; i < 256; i++) {
mult10[i] = +('1e' + Math.floor(45.15 - i * 0.30103));
}
const defaultUnpackr = new Unpackr({ useRecords: false });
export const Decoder = Unpackr;
var defaultUnpackr = new Unpackr({ useRecords: false });
export const unpack = defaultUnpackr.unpack;
export const unpackMultiple = defaultUnpackr.unpackMultiple;
export const decode = defaultUnpackr.unpack;
export const FLOAT32_OPTIONS = {
NEVER: 0,
ALWAYS: 1,
DECIMAL_ROUND: 3,
DECIMAL_FIT: 4
};
const f32Array = new Float32Array(1);
const u8Array = new Uint8Array(f32Array.buffer, 0, 4);
export function roundFloat32(float32Number) {
f32Array[0] = float32Number;
const multiplier = mult10[((u8Array[3] & 0x7f) << 1) | (u8Array[2] >> 7)];
return (
((multiplier * float32Number + (float32Number > 0 ? 0.5 : -0.5)) >> 0) /
multiplier
);
}
export function setReadStruct(updatedReadStruct, loadedStructs, saveState) {
readStruct = updatedReadStruct;
onLoadedStructures = loadedStructs;
onSaveState = saveState;
}

View File

@@ -76,7 +76,6 @@ const CustomEntities = () => {
ei.factor !== ei.o_factor ||
ei.value_type !== ei.o_value_type ||
ei.writeable !== ei.o_writeable ||
ei.hide !== ei.o_hide ||
ei.deleted !== ei.o_deleted ||
(ei.value || '') !== (ei.o_value || '')
);
@@ -137,8 +136,8 @@ const CustomEntities = () => {
const saveEntities = async () => {
await writeEntities({
entities: entities
.filter((ei: EntityItem) => !ei.deleted)
.map((condensed_ei: EntityItem) => ({
.filter((ei) => !ei.deleted)
.map((condensed_ei) => ({
id: condensed_ei.id,
ram: condensed_ei.ram,
name: condensed_ei.name,
@@ -148,7 +147,6 @@ const CustomEntities = () => {
factor: condensed_ei.factor,
uom: condensed_ei.uom,
writeable: condensed_ei.writeable,
hide: condensed_ei.hide,
value_type: condensed_ei.value_type,
value: condensed_ei.value
}))
@@ -211,7 +209,6 @@ const CustomEntities = () => {
value_type: item.value_type,
writeable: item.writeable,
deleted: false,
hide: item.hide,
value: item.value
});
setDialogOpen(true);
@@ -231,7 +228,6 @@ const CustomEntities = () => {
value_type: 0,
writeable: false,
deleted: false,
hide: false,
value: ''
});
setDialogOpen(true);
@@ -252,17 +248,15 @@ const CustomEntities = () => {
const renderEntity = () => {
if (!entities) {
return (
<FormLoader onRetry={fetchEntities} errorMessage={error?.message || ''} />
);
return <FormLoader onRetry={fetchEntities} errorMessage={error?.message} />;
}
return (
<Table
data={{
nodes: entities
.filter((ei: EntityItem) => !ei.deleted)
.sort((a: EntityItem, b: EntityItem) => a.name.localeCompare(b.name))
.filter((ei) => !ei.deleted)
.sort((a, b) => a.name.localeCompare(b.name))
}}
theme={entity_theme}
layout={{ custom: true }}

View File

@@ -2,11 +2,7 @@ import { useEffect, useState } from 'react';
import AddIcon from '@mui/icons-material/Add';
import CancelIcon from '@mui/icons-material/Cancel';
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
import DoneIcon from '@mui/icons-material/Done';
import EditOffOutlinedIcon from '@mui/icons-material/EditOffOutlined';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import InsertCommentOutlinedIcon from '@mui/icons-material/InsertCommentOutlined';
import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
import {
Box,
@@ -16,7 +12,7 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid,
Grid2 as Grid,
InputAdornment,
MenuItem,
TextField
@@ -74,10 +70,7 @@ const CustomEntitiesDialog = ({
}
}, [open, selectedItem]);
const handleClose = (
_event: React.SyntheticEvent,
reason: 'backdropClick' | 'escapeKeyDown'
) => {
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
if (reason !== 'backdropClick') {
onClose();
}
@@ -126,7 +119,7 @@ const CustomEntitiesDialog = ({
<Grid container spacing={2} rowSpacing={0}>
<Grid size={12}>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="name"
label={LL.NAME(0)}
value={editItem.name}
@@ -135,20 +128,6 @@ const CustomEntitiesDialog = ({
onChange={updateFormValue}
/>
</Grid>
<Grid mt={3}>
<BlockFormControlLabel
control={
<Checkbox
icon={<InsertCommentOutlinedIcon htmlColor="white" />}
checkedIcon={<CommentsDisabledOutlinedIcon color="primary" />}
checked={editItem.hide}
onChange={updateFormValue}
name="hide"
/>
}
label="API/MQTT"
/>
</Grid>
<Grid>
<TextField
name="ram"
@@ -198,12 +177,10 @@ const CustomEntitiesDialog = ({
)}
{editItem.ram === 0 && (
<>
<Grid mt={3}>
<Grid mt={3} size={9}>
<BlockFormControlLabel
control={
<Checkbox
icon={<EditOffOutlinedIcon color="primary" />}
checkedIcon={<EditOutlinedIcon htmlColor="white" />}
checked={editItem.writeable}
onChange={updateFormValue}
name="writeable"
@@ -214,7 +191,7 @@ const CustomEntitiesDialog = ({
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="device_id"
label={LL.ID_OF(LL.DEVICE())}
margin="normal"
@@ -234,7 +211,7 @@ const CustomEntitiesDialog = ({
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="type_id"
label={LL.ID_OF(LL.TYPE(1))}
margin="normal"
@@ -254,7 +231,7 @@ const CustomEntitiesDialog = ({
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="offset"
label={LL.OFFSET()}
margin="normal"
@@ -346,7 +323,7 @@ const CustomEntitiesDialog = ({
editItem.device_id !== '0' && (
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="factor"
label={LL.BYTES()}
value={numberValue(editItem.factor as number)}
@@ -364,7 +341,7 @@ const CustomEntitiesDialog = ({
{editItem.value_type === DeviceValueType.BOOL && (
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="factor"
label={LL.BITMASK()}
value={editItem.factor as string}

View File

@@ -16,7 +16,7 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid,
Grid2 as Grid,
InputAdornment,
Link,
MenuItem,
@@ -125,22 +125,13 @@ const Customizations = () => {
const setOriginalSettings = (data: DeviceEntity[]) => {
setDeviceEntities(
data.map((de) => {
const result: DeviceEntity = {
...de,
o_m: de.m
};
if (de.cn !== undefined) {
result.o_cn = de.cn;
}
if (de.mi !== undefined) {
result.o_mi = de.mi;
}
if (de.ma !== undefined) {
result.o_ma = de.ma;
}
return result;
})
data.map((de) => ({
...de,
o_m: de.m,
o_cn: de.cn,
o_mi: de.mi,
o_ma: de.ma
}))
);
};
@@ -253,11 +244,8 @@ const Customizations = () => {
setSelectedDevice(-1);
setSelectedDeviceTypeNameURL('');
} else {
const device = devices.devices[index];
if (device) {
setSelectedDeviceTypeNameURL(device.url || '');
setSelectedDeviceName(device.n);
}
setSelectedDeviceTypeNameURL(devices.devices[index].url || '');
setSelectedDeviceName(devices.devices[index].n);
setNumChanges(0);
setRestartNeeded(false);
}
@@ -318,7 +306,7 @@ const Customizations = () => {
const filter_entity = (de: DeviceEntity) =>
(de.m & selectedFilters || !selectedFilters) &&
formatName(de, true).toLowerCase().includes(search.toLowerCase());
formatName(de, true).includes(search);
const maskDisabled = (set: boolean) => {
setDeviceEntities(
@@ -408,20 +396,14 @@ const Customizations = () => {
await sendCustomizationEntities({
id: selectedDevice,
entity_ids: masked_entities
})
.then(() => {
toast.success(LL.CUSTOMIZATIONS_SAVED());
})
.catch((error: Error) => {
if (error.message === 'Reboot required') {
setRestartNeeded(true);
} else {
toast.error(error.message);
}
})
.finally(() => {
setOriginalSettings(deviceEntities);
});
}).catch((error: Error) => {
if (error.message === 'Reboot required') {
setRestartNeeded(true);
} else {
toast.error(error.message);
}
});
setOriginalSettings(deviceEntities);
}
};
@@ -563,7 +545,7 @@ const Customizations = () => {
size="small"
color="secondary"
value={getMaskString(selectedFilters)}
onChange={(_, mask: string[]) => {
onChange={(event, mask: string[]) => {
setSelectedFilters(getMaskNumber(mask));
}}
>

View File

@@ -10,7 +10,7 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid,
Grid2 as Grid,
TextField,
Typography
} from '@mui/material';
@@ -54,10 +54,7 @@ const CustomizationsDialog = ({
}
}, [open, selectedItem]);
const handleClose = (
_event: React.SyntheticEvent,
reason: 'backdropClick' | 'escapeKeyDown'
) => {
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
if (reason !== 'backdropClick') {
onClose();
}

View File

@@ -1,4 +1,4 @@
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useContext, useEffect, useState } from 'react';
import { IconContext } from 'react-icons/lib';
import { Link } from 'react-router';
import { toast } from 'react-toastify';
@@ -76,40 +76,35 @@ const Dashboard = () => {
}
);
const deviceValueDialogSave = useCallback(
async (devicevalue: DeviceValue) => {
if (!selectedDashboardItem) {
return;
}
const id = selectedDashboardItem.id; // this is the parent ID
await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
.then(() => {
toast.success(LL.WRITE_CMD_SENT());
})
.catch((error: Error) => {
toast.error(error.message);
})
.finally(() => {
setDeviceValueDialogOpen(false);
setSelectedDashboardItem(undefined);
});
},
[selectedDashboardItem, sendDeviceValue, LL]
);
const deviceValueDialogSave = async (devicevalue: DeviceValue) => {
if (!selectedDashboardItem) {
return;
}
const id = selectedDashboardItem.parentNode.id; // this is the parent ID
await sendDeviceValue({ id, c: devicevalue.c ?? '', v: devicevalue.v })
.then(() => {
toast.success(LL.WRITE_CMD_SENT());
})
.catch((error: Error) => {
toast.error(error.message);
})
.finally(() => {
setDeviceValueDialogOpen(false);
setSelectedDashboardItem(undefined);
});
};
const dashboard_theme = useMemo(
() =>
useTheme({
Table: `
const dashboard_theme = useTheme({
Table: `
--data-table-library_grid-template-columns: minmax(80px, auto) 120px 32px;
`,
BaseRow: `
BaseRow: `
font-size: 14px;
.td {
height: 28px;
}
`,
Row: `
Row: `
cursor: pointer;
background-color: #1e1e1e;
&:nth-of-type(odd) .td {
@@ -119,7 +114,7 @@ const Dashboard = () => {
background-color: #177ac9;
},
`,
BaseCell: `
BaseCell: `
&:nth-of-type(2) {
text-align: right;
}
@@ -127,14 +122,12 @@ const Dashboard = () => {
text-align: right;
}
`
}),
[]
);
});
const tree = useTree(
{ nodes: data.nodes },
{
onChange: () => {} // not used but needed
onChange: undefined // not used but needed
},
{
treeIcon: {
@@ -169,31 +162,28 @@ const Dashboard = () => {
: tree.fns.onRemoveAll(); // collapse tree
}, [parentNodes]);
const showType = useCallback(
(n?: string, t?: number) => {
// if we have a name show it
if (n) {
return n;
const showType = (n?: string, t?: number) => {
// if we have a name show it
if (n) {
return n;
}
if (t) {
// otherwise pick translation based on type
switch (t) {
case DeviceType.CUSTOM:
return LL.CUSTOM_ENTITIES(0);
case DeviceType.ANALOGSENSOR:
return LL.ANALOG_SENSORS();
case DeviceType.TEMPERATURESENSOR:
return LL.TEMP_SENSORS();
case DeviceType.SCHEDULER:
return LL.SCHEDULER();
default:
break;
}
if (t) {
// otherwise pick translation based on type
switch (t) {
case DeviceType.CUSTOM:
return LL.CUSTOM_ENTITIES(0);
case DeviceType.ANALOGSENSOR:
return LL.ANALOG_SENSORS();
case DeviceType.TEMPERATURESENSOR:
return LL.TEMP_SENSORS();
case DeviceType.SCHEDULER:
return LL.SCHEDULER();
default:
break;
}
}
return '';
},
[LL]
);
}
return '';
};
const showName = (di: DashboardItem) => {
if (di.id < 100) {
@@ -211,24 +201,20 @@ const Dashboard = () => {
if (di.dv) {
return <span>{di.dv.id.slice(2)}</span>;
}
return null;
};
const hasMask = (id: string, mask: number) =>
(parseInt(id.slice(0, 2), 16) & mask) === mask;
const editDashboardValue = useCallback(
(di: DashboardItem) => {
if (me.admin && di.dv?.c) {
setSelectedDashboardItem(di);
setDeviceValueDialogOpen(true);
}
},
[me.admin]
);
const editDashboardValue = (di: DashboardItem) => {
if (me.admin && di.dv?.c) {
setSelectedDashboardItem(di);
setDeviceValueDialogOpen(true);
}
};
const handleShowAll = (
_event: React.MouseEvent<HTMLElement>,
event: React.MouseEvent<HTMLElement>,
toggle: boolean | null
) => {
if (toggle !== null) {
@@ -239,9 +225,7 @@ const Dashboard = () => {
const renderContent = () => {
if (!data) {
return (
<FormLoader onRetry={fetchDashboard} errorMessage={error?.message || ''} />
);
return <FormLoader onRetry={fetchDashboard} errorMessage={error?.message} />;
}
const hasFavEntities = data.nodes.filter(

View File

@@ -31,7 +31,7 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid,
Grid2 as Grid,
IconButton,
InputAdornment,
List,
@@ -329,16 +329,13 @@ const Devices = () => {
const handleDownloadCsv = () => {
const deviceIndex = coreData.devices.findIndex(
(d: Device) => d.id === device_select.state.id
(d) => d.id === device_select.state.id
);
if (deviceIndex === -1) {
return;
}
const selectedDevice = coreData.devices[deviceIndex];
if (!selectedDevice) {
return;
}
const filename = selectedDevice.tn + '_' + selectedDevice.n;
const filename =
coreData.devices[deviceIndex].tn + '_' + coreData.devices[deviceIndex].n;
const columns = [
{
@@ -353,7 +350,7 @@ const Devices = () => {
{
accessor: (dv: DeviceValue) =>
dv.u !== undefined && DeviceValueUOM_s[dv.u]
? DeviceValueUOM_s[dv.u]?.replace(/[^a-zA-Z0-9]/g, '')
? DeviceValueUOM_s[dv.u].replace(/[^a-zA-Z0-9]/g, '')
: '',
name: 'UoM'
},
@@ -376,9 +373,7 @@ const Devices = () => {
];
const data = onlyFav
? deviceData.nodes.filter((dv: DeviceValue) =>
hasMask(dv.id, DeviceEntityMask.DV_FAVORITE)
)
? deviceData.nodes.filter((dv) => hasMask(dv.id, DeviceEntityMask.DV_FAVORITE))
: deviceData.nodes;
const csvData = data.reduce(
@@ -438,14 +433,10 @@ const Devices = () => {
const renderDeviceDetails = () => {
if (showDeviceInfo) {
const deviceIndex = coreData.devices.findIndex(
(d: Device) => d.id === device_select.state.id
(d) => d.id === device_select.state.id
);
if (deviceIndex === -1) {
return null;
}
const deviceDetails = coreData.devices[deviceIndex];
if (!deviceDetails) {
return null;
return;
}
return (
@@ -458,35 +449,47 @@ const Devices = () => {
<DialogContent dividers>
<List dense={true}>
<ListItem>
<ListItemText primary={LL.TYPE(0)} secondary={deviceDetails.tn} />
<ListItemText
primary={LL.TYPE(0)}
secondary={coreData.devices[deviceIndex].tn}
/>
</ListItem>
<ListItem>
<ListItemText primary={LL.NAME(0)} secondary={deviceDetails.n} />
<ListItemText
primary={LL.NAME(0)}
secondary={coreData.devices[deviceIndex].n}
/>
</ListItem>
{deviceDetails.t !== DeviceType.CUSTOM && (
{coreData.devices[deviceIndex].t !== DeviceType.CUSTOM && (
<>
<ListItem>
<ListItemText primary={LL.BRAND()} secondary={deviceDetails.b} />
<ListItemText
primary={LL.BRAND()}
secondary={coreData.devices[deviceIndex].b}
/>
</ListItem>
<ListItem>
<ListItemText
primary={LL.ID_OF(LL.DEVICE())}
secondary={
'0x' +
('00' + deviceDetails.d.toString(16).toUpperCase()).slice(-2)
(
'00' +
coreData.devices[deviceIndex].d.toString(16).toUpperCase()
).slice(-2)
}
/>
</ListItem>
<ListItem>
<ListItemText
primary={LL.ID_OF(LL.PRODUCT())}
secondary={deviceDetails.p}
secondary={coreData.devices[deviceIndex].p}
/>
</ListItem>
<ListItem>
<ListItemText
primary={LL.VERSION()}
secondary={deviceDetails.v}
secondary={coreData.devices[deviceIndex].v}
/>
</ListItem>
</>
@@ -505,7 +508,6 @@ const Devices = () => {
</Dialog>
);
}
return null;
};
const renderCoreData = () => (
@@ -596,26 +598,19 @@ const Devices = () => {
const shown_data = onlyFav
? deviceData.nodes.filter(
(dv: DeviceValue) =>
(dv) =>
hasMask(dv.id, DeviceEntityMask.DV_FAVORITE) &&
dv.id.slice(2).toLowerCase().includes(search.toLowerCase())
dv.id.slice(2).includes(search)
)
: deviceData.nodes.filter((dv: DeviceValue) =>
dv.id.slice(2).toLowerCase().includes(search.toLowerCase())
);
: deviceData.nodes.filter((dv) => dv.id.slice(2).includes(search));
const deviceIndex = coreData.devices.findIndex(
(d: Device) => d.id === device_select.state.id
(d) => d.id === device_select.state.id
);
if (deviceIndex === -1) {
return;
}
const deviceInfo = coreData.devices[deviceIndex];
if (!deviceInfo) {
return;
}
const [, height] = size;
return (
<Box
sx={{
@@ -626,15 +621,15 @@ const Devices = () => {
bottom: 0,
top: 64,
zIndex: 'modal',
maxHeight: () => (height || 0) - 126,
maxHeight: () => size[1] - 126,
border: '1px solid #177ac9'
}}
>
<Box sx={{ p: 1 }}>
<Grid container justifyContent="space-between">
<Typography noWrap variant="subtitle1" color="warning.main">
{deviceInfo.n}&nbsp;(
{deviceInfo.tn})
{coreData.devices[deviceIndex].n}&nbsp;(
{coreData.devices[deviceIndex].tn})
</Typography>
<Grid justifyContent="flex-end">
<ButtonTooltip title={LL.CLOSE()}>
@@ -704,7 +699,7 @@ const Devices = () => {
' ' +
shown_data.length +
'/' +
deviceInfo.e +
coreData.devices[deviceIndex].e +
' ' +
LL.ENTITIES(shown_data.length)}
</span>

View File

@@ -11,7 +11,7 @@ import {
DialogContent,
DialogTitle,
FormHelperText,
Grid,
Grid2 as Grid,
InputAdornment,
MenuItem,
TextField,
@@ -120,7 +120,7 @@ const DevicesDialog = ({
{editItem.l ? (
<TextField
name="v"
// label={LL.VALUE(0)}
label={LL.VALUE(0)}
value={editItem.v}
disabled={!writeable}
sx={{ width: '30ch' }}
@@ -135,7 +135,7 @@ const DevicesDialog = ({
</TextField>
) : editItem.s || editItem.u !== DeviceValueUOM.NONE ? (
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="v"
label={LL.VALUE(0)}
value={numberValue(Math.round((editItem.v as number) * 10) / 10)}
@@ -159,7 +159,7 @@ const DevicesDialog = ({
/>
) : (
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="v"
label={LL.VALUE(0)}
value={editItem.v}

View File

@@ -43,7 +43,7 @@ const EntityMaskToggle = ({ onUpdate, de }: EntityMaskToggleProps) => {
size="small"
color="secondary"
value={getMaskString(de.m)}
onChange={(_event, mask: string[]) => {
onChange={(event, mask: string[]) => {
de.m = getMaskNumber(mask);
if (de.n === '' && de.m & DeviceEntityMask.DV_READONLY) {
de.m = de.m | DeviceEntityMask.DV_WEB_EXCLUDE;

View File

@@ -41,8 +41,7 @@ const Help = () => {
useRequest(() => callAction({ action: 'getCustomSupport' })).onSuccess((event) => {
if (event && event.data && Object.keys(event.data).length !== 0) {
const data = (event.data as { Support: { img_url?: string; html?: string[] } })
.Support;
const data = event.data.Support;
if (data.img_url) {
setCustomSupportIMG(data.img_url);
}
@@ -60,7 +59,7 @@ const Help = () => {
toast.info(LL.DOWNLOAD_SUCCESSFUL());
})
.onError((error) => {
toast.error(String(error.error?.message || 'An error occurred'));
toast.error(error.message);
});
return (

View File

@@ -133,15 +133,13 @@ const Modules = () => {
};
const saveModules = async () => {
await Promise.all(
modules.map((condensed_mi: ModuleItem) =>
updateModules({
key: condensed_mi.key,
enabled: condensed_mi.enabled,
license: condensed_mi.license
})
)
)
await updateModules({
modules: modules.map((condensed_mi) => ({
key: condensed_mi.key,
enabled: condensed_mi.enabled,
license: condensed_mi.license
}))
})
.then(() => {
toast.success(LL.MODULES_UPDATED());
})
@@ -156,9 +154,7 @@ const Modules = () => {
const renderContent = () => {
if (!modules) {
return (
<FormLoader onRetry={fetchModules} errorMessage={error?.message || ''} />
);
return <FormLoader onRetry={fetchModules} errorMessage={error?.message} />;
}
if (modules.length === 0) {

View File

@@ -10,7 +10,7 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid,
Grid2 as Grid,
TextField
} from '@mui/material';

View File

@@ -27,7 +27,6 @@ import {
useLayoutTitle
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import { useInterval } from 'utils';
import { readSchedule, writeSchedule } from '../../api/app';
import SettingsSchedulerDialog from './SchedulerDialog';
@@ -74,12 +73,6 @@ const Scheduler = () => {
);
}
useInterval(() => {
if (numChanges === 0) {
void fetchSchedule();
}
});
useEffect(() => {
const formatter = new Intl.DateTimeFormat(locale, {
weekday: 'short',
@@ -135,8 +128,8 @@ const Scheduler = () => {
const saveSchedule = async () => {
await updateSchedule({
schedule: schedule
.filter((si: ScheduleItem) => !si.deleted)
.map((condensed_si: ScheduleItem) => ({
.filter((si) => !si.deleted)
.map((condensed_si) => ({
id: condensed_si.id,
active: condensed_si.active,
flags: condensed_si.flags,
@@ -212,9 +205,7 @@ const Scheduler = () => {
const renderSchedule = () => {
if (!schedule) {
return (
<FormLoader onRetry={fetchSchedule} errorMessage={error?.message || ''} />
);
return <FormLoader onRetry={fetchSchedule} errorMessage={error?.message} />;
}
const dayBox = (si: ScheduleItem, flag: number) => (
@@ -253,8 +244,8 @@ const Scheduler = () => {
<Table
data={{
nodes: schedule
.filter((si: ScheduleItem) => !si.deleted)
.sort((a: ScheduleItem, b: ScheduleItem) => a.flags - b.flags)
.filter((si) => !si.deleted)
.sort((a, b) => a.flags - b.flags)
}}
theme={schedule_theme}
layout={{ custom: true }}

View File

@@ -13,7 +13,7 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid,
Grid2 as Grid,
TextField,
ToggleButton,
ToggleButtonGroup,
@@ -144,10 +144,7 @@ const SchedulerDialog = ({
</Typography>
);
const handleClose = (
_event: React.SyntheticEvent,
reason: 'backdropClick' | 'escapeKeyDown'
) => {
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
if (reason !== 'backdropClick') {
onClose();
}
@@ -328,7 +325,7 @@ const SchedulerDialog = ({
</>
)}
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="cmd"
label={LL.COMMAND(0)}
multiline
@@ -347,7 +344,7 @@ const SchedulerDialog = ({
onChange={updateFormValue}
/>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="name"
label={LL.NAME(0) + ' (' + LL.OPTIONAL() + ')'}
value={editItem.name}

View File

@@ -439,8 +439,7 @@ const Sensors = () => {
<Cell>{a.n}</Cell>
<Cell stiff>{AnalogTypeNames[a.t]} </Cell>
{(a.t === AnalogType.DIGITAL_OUT && a.g !== 25 && a.g !== 26) ||
a.t === AnalogType.DIGITAL_IN ||
a.t === AnalogType.PULSE ? (
a.t === AnalogType.DIGITAL_IN ? (
<Cell stiff>{a.v ? LL.ON() : LL.OFF()}</Cell>
) : (
<Cell stiff>{a.t ? formatValue(a.v, a.u) : ''}</Cell>

View File

@@ -10,7 +10,7 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid,
Grid2 as Grid,
InputAdornment,
MenuItem,
TextField,
@@ -57,10 +57,7 @@ const SensorsAnalogDialog = ({
}
}, [open, selectedItem]);
const handleClose = (
_event: React.SyntheticEvent,
reason: 'backdropClick' | 'escapeKeyDown'
) => {
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
if (reason !== 'backdropClick') {
onClose();
}
@@ -91,7 +88,7 @@ const SensorsAnalogDialog = ({
<Grid container spacing={2}>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="g"
label="GPIO"
sx={{ width: '11ch' }}
@@ -110,7 +107,7 @@ const SensorsAnalogDialog = ({
)}
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="n"
label={LL.NAME(0)}
value={editItem.n}
@@ -135,9 +132,7 @@ const SensorsAnalogDialog = ({
))}
</TextField>
</Grid>
{((editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE) ||
(editItem.t >= AnalogType.FREQ_0 &&
editItem.t <= AnalogType.FREQ_2)) && (
{editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && (
<Grid>
<TextField
name="u"
@@ -176,27 +171,6 @@ const SensorsAnalogDialog = ({
/>
</Grid>
)}
{editItem.t === AnalogType.NTC && (
<Grid>
<TextField
name="o"
label={LL.OFFSET()}
value={numberValue(editItem.o)}
sx={{ width: '11ch' }}
type="number"
variant="outlined"
onChange={updateFormValue}
slotProps={{
input: {
startAdornment: (
<InputAdornment position="start">°C</InputAdornment>
)
},
htmlInput: { min: '-20', max: '20', step: '0.1' }
}}
/>
</Grid>
)}
{editItem.t === AnalogType.COUNTER && (
<Grid>
<TextField
@@ -213,19 +187,6 @@ const SensorsAnalogDialog = ({
/>
</Grid>
)}
{editItem.t === AnalogType.RGB && (
<Grid>
<TextField
name="o"
label={'RGB ' + LL.VALUE(0)}
value={numberValue(editItem.o)}
type="number"
sx={{ width: '11ch' }}
variant="outlined"
onChange={updateFormValue}
/>
</Grid>
)}
{editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && (
<Grid>
<TextField
@@ -353,42 +314,6 @@ const SensorsAnalogDialog = ({
</Grid>
</>
)}
{editItem.t === AnalogType.PULSE && (
<>
<Grid>
<TextField
name="o"
label={LL.POLARITY()}
value={editItem.o}
sx={{ width: '11ch' }}
select
onChange={updateFormValue}
>
<MenuItem value={0}>{LL.ACTIVEHIGH()}</MenuItem>
<MenuItem value={1}>{LL.ACTIVELOW()}</MenuItem>
</TextField>
</Grid>
<Grid>
<TextField
name="f"
label="Pulse"
value={numberValue(editItem.f)}
type="number"
sx={{ width: '15ch' }}
variant="outlined"
onChange={updateFormValue}
slotProps={{
input: {
startAdornment: (
<InputAdornment position="start">s</InputAdornment>
)
},
htmlInput: { min: '0', max: '10000', step: '0.1' }
}}
/>
</Grid>
</>
)}
</Grid>
</DialogContent>
<DialogActions>

View File

@@ -9,7 +9,7 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Grid,
Grid2 as Grid,
InputAdornment,
TextField,
Typography
@@ -52,10 +52,7 @@ const SensorsTemperatureDialog = ({
}
}, [open, selectedItem]);
const handleClose = (
_event: React.SyntheticEvent,
reason: 'backdropClick' | 'escapeKeyDown'
) => {
const handleClose = (_event, reason: 'backdropClick' | 'escapeKeyDown') => {
if (reason !== 'backdropClick') {
onClose();
}
@@ -85,7 +82,7 @@ const SensorsTemperatureDialog = ({
<Grid container spacing={2}>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="n"
label={LL.NAME(0)}
value={editItem.n}

View File

@@ -188,8 +188,7 @@ export enum DeviceValueUOM {
VOLTS,
MBAR,
LH,
CTKWH,
HZ
CTKWH
}
export const DeviceValueUOM_s = [
@@ -219,8 +218,7 @@ export const DeviceValueUOM_s = [
'V',
'mbar',
'l/h',
'ct/kWh',
'Hz'
'ct/kWh'
];
export enum AnalogType {
@@ -234,32 +232,20 @@ export enum AnalogType {
DIGITAL_OUT = 6,
PWM_0 = 7,
PWM_1 = 8,
PWM_2 = 9,
NTC = 10,
RGB = 11,
PULSE = 12,
FREQ_0 = 13,
FREQ_1 = 14,
FREQ_2 = 15
PWM_2 = 9
}
export const AnalogTypeNames = [
'(disabled)',
'Digital In',
'Counter',
'ADC In',
'ADC',
'Timer',
'Rate',
'Digital Out',
'PWM 0',
'PWM 1',
'PWM 2',
'NTC Temp.',
'RGB Led',
'Pulse',
'Freq 0',
'Freq 1',
'Freq 2'
'PWM 2'
];
type BoardProfiles = Record<string, string>;
@@ -269,7 +255,6 @@ export const BOARD_PROFILES: BoardProfiles = {
S32S3: 'BBQKees Gateway S3',
E32: 'BBQKees Gateway E32',
E32V2: 'BBQKees Gateway E32 V2',
E32V2_2: 'BBQKees Gateway E32 V2.2',
NODEMCU: 'NodeMCU 32S',
'MH-ET': 'MH-ET Live D1 Mini',
LOLIN: 'Lolin D32',
@@ -395,7 +380,6 @@ export interface EntityItem {
value_type: number;
value?: unknown;
writeable: boolean;
hide: boolean;
deleted?: boolean;
o_id?: number;
o_ram?: number;
@@ -409,7 +393,6 @@ export interface EntityItem {
o_deleted?: boolean;
o_writeable?: boolean;
o_value?: unknown;
o_hide?: boolean;
}
export interface Entities {

View File

@@ -13,7 +13,7 @@ import type {
export const GPIO_VALIDATOR = {
validator(
_rule: InternalRuleItem,
rule: InternalRuleItem,
value: number,
callback: (error?: string) => void
) {
@@ -36,7 +36,7 @@ export const GPIO_VALIDATOR = {
export const GPIO_VALIDATORR = {
validator(
_rule: InternalRuleItem,
rule: InternalRuleItem,
value: number,
callback: (error?: string) => void
) {
@@ -60,7 +60,7 @@ export const GPIO_VALIDATORR = {
export const GPIO_VALIDATORC3 = {
validator(
_rule: InternalRuleItem,
rule: InternalRuleItem,
value: number,
callback: (error?: string) => void
) {
@@ -74,7 +74,7 @@ export const GPIO_VALIDATORC3 = {
export const GPIO_VALIDATORS2 = {
validator(
_rule: InternalRuleItem,
rule: InternalRuleItem,
value: number,
callback: (error?: string) => void
) {
@@ -94,7 +94,7 @@ export const GPIO_VALIDATORS2 = {
export const GPIO_VALIDATORS3 = {
validator(
_rule: InternalRuleItem,
rule: InternalRuleItem,
value: number,
callback: (error?: string) => void
) {
@@ -279,7 +279,7 @@ export const createSettingsValidator = (settings: Settings) =>
export const uniqueNameValidator = (schedule: ScheduleItem[], o_name?: string) => ({
validator(
_rule: InternalRuleItem,
rule: InternalRuleItem,
name: string,
callback: (error?: string) => void
) {
@@ -324,7 +324,7 @@ export const uniqueCustomNameValidator = (
o_name?: string
) => ({
validator(
_rule: InternalRuleItem,
rule: InternalRuleItem,
name: string,
callback: (error?: string) => void
) {
@@ -353,7 +353,7 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte
device_id: [
{
validator(
_rule: InternalRuleItem,
rule: InternalRuleItem,
value: string,
callback: (error?: string) => void
) {
@@ -367,7 +367,7 @@ export const entityItemValidation = (entity: EntityItem[], entityItem: EntityIte
type_id: [
{
validator(
_rule: InternalRuleItem,
rule: InternalRuleItem,
value: string,
callback: (error?: string) => void
) {
@@ -389,7 +389,7 @@ export const uniqueTemperatureNameValidator = (
sensors: TemperatureSensor[],
o_name?: string
) => ({
validator(_rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
validator(rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
if (
(o_name === undefined || o_name.toLowerCase() !== n.toLowerCase()) &&
n !== '' &&
@@ -419,7 +419,7 @@ export const temperatureSensorItemValidation = (
export const isGPIOUniqueValidator = (sensors: AnalogSensor[]) => ({
validator(
_rule: InternalRuleItem,
rule: InternalRuleItem,
gpio: number,
callback: (error?: string) => void
) {
@@ -435,7 +435,7 @@ export const uniqueAnalogNameValidator = (
sensors: AnalogSensor[],
o_name?: string
) => ({
validator(_rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
validator(rule: InternalRuleItem, n: string, callback: (error?: string) => void) {
if (
(o_name === undefined || o_name.toLowerCase() !== n.toLowerCase()) &&
n !== '' &&
@@ -482,7 +482,7 @@ export const deviceValueItemValidation = (dv: DeviceValue) =>
{ required: true, message: 'Value is required' },
{
validator(
_rule: InternalRuleItem,
rule: InternalRuleItem,
value: unknown,
callback: (error?: string) => void
) {

View File

@@ -54,12 +54,12 @@ const APSettings = () => {
origData,
dirtyFlags,
setDirtyFlags,
updateDataValue as (value: unknown) => void
updateDataValue
);
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
}
const validateAndSubmit = async () => {
@@ -80,7 +80,7 @@ const APSettings = () => {
return (
<>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="provision_mode"
label={LL.AP_PROVIDE() + '...'}
value={data.provision_mode}
@@ -103,7 +103,7 @@ const APSettings = () => {
{isAPEnabled(data) && (
<>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="ssid"
label={LL.ACCESS_POINT(2) + ' SSID'}
fullWidth
@@ -113,7 +113,7 @@ const APSettings = () => {
margin="normal"
/>
<ValidatedPasswordField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="password"
label={LL.ACCESS_POINT(2) + ' ' + LL.PASSWORD()}
fullWidth
@@ -123,7 +123,7 @@ const APSettings = () => {
margin="normal"
/>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="channel"
label={LL.AP_PREFERRED_CHANNEL()}
value={numberValue(data.channel)}
@@ -151,7 +151,7 @@ const APSettings = () => {
label={LL.AP_HIDE_SSID()}
/>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="max_clients"
label={LL.AP_MAX_CLIENTS()}
value={numberValue(data.max_clients)}
@@ -169,7 +169,7 @@ const APSettings = () => {
))}
</ValidatedTextField>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="local_ip"
label={LL.AP_LOCAL_IP()}
fullWidth
@@ -179,7 +179,7 @@ const APSettings = () => {
margin="normal"
/>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="gateway_ip"
label={LL.NETWORK_GATEWAY()}
fullWidth
@@ -189,7 +189,7 @@ const APSettings = () => {
margin="normal"
/>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="subnet_mask"
label={LL.NETWORK_SUBNET()}
fullWidth

View File

@@ -9,7 +9,7 @@ import {
Button,
Checkbox,
Divider,
Grid,
Grid2 as Grid,
InputAdornment,
MenuItem,
TextField,
@@ -75,7 +75,7 @@ const ApplicationSettings = () => {
origData,
dirtyFlags,
setDirtyFlags,
updateDataValue as (value: unknown) => void
updateDataValue
);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -135,7 +135,7 @@ const ApplicationSettings = () => {
const content = () => {
if (!data || !hardwareData) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
}
const validateAndSubmit = async () => {
@@ -219,7 +219,7 @@ const ApplicationSettings = () => {
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="modbus_max_clients"
label={LL.AP_MAX_CLIENTS()}
variant="outlined"
@@ -231,7 +231,7 @@ const ApplicationSettings = () => {
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="modbus_port"
label="Port"
variant="outlined"
@@ -243,7 +243,7 @@ const ApplicationSettings = () => {
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="modbus_timeout"
label="Timeout"
slotProps={{
@@ -273,7 +273,7 @@ const ApplicationSettings = () => {
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="syslog_host"
label="Host"
variant="outlined"
@@ -284,7 +284,7 @@ const ApplicationSettings = () => {
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="syslog_port"
label="Port"
variant="outlined"
@@ -315,7 +315,7 @@ const ApplicationSettings = () => {
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="syslog_mark_interval"
label={LL.MARK_INTERVAL()}
slotProps={{
@@ -485,7 +485,7 @@ const ApplicationSettings = () => {
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="rx_gpio"
label={LL.GPIO_OF('Rx')}
fullWidth
@@ -498,7 +498,7 @@ const ApplicationSettings = () => {
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="tx_gpio"
label={LL.GPIO_OF('Tx')}
fullWidth
@@ -511,7 +511,7 @@ const ApplicationSettings = () => {
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="pbutton_gpio"
label={LL.GPIO_OF(LL.BUTTON())}
fullWidth
@@ -524,7 +524,7 @@ const ApplicationSettings = () => {
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="dallas_gpio"
label={
LL.GPIO_OF(LL.TEMPERATURE()) + ' (0=' + LL.DISABLED(1) + ')'
@@ -539,7 +539,7 @@ const ApplicationSettings = () => {
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="led_gpio"
label={LL.GPIO_OF('LED') + ' (0=' + LL.DISABLED(1) + ')'}
fullWidth
@@ -554,7 +554,7 @@ const ApplicationSettings = () => {
<Grid>
<TextField
name="led_type"
label={'LED ' + LL.TYPE(0)}
label={'LED ' + LL.TYPE()}
value={data.led_type}
fullWidth
variant="outlined"
@@ -743,7 +743,7 @@ const ApplicationSettings = () => {
{data.remote_timeout_en && (
<Box mt={2}>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="remote_timeout"
label={LL.REMOTE_TIMEOUT()}
slotProps={{
@@ -783,7 +783,7 @@ const ApplicationSettings = () => {
{data.shower_timer && (
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="shower_min_duration"
label={LL.MIN_DURATION()}
slotProps={{
@@ -801,7 +801,7 @@ const ApplicationSettings = () => {
<>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="shower_alert_trigger"
label={LL.TRIGGER_TIME()}
slotProps={{
@@ -817,7 +817,7 @@ const ApplicationSettings = () => {
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="shower_alert_coldshot"
label={LL.COLD_SHOT_DURATION()}
slotProps={{

View File

@@ -2,7 +2,7 @@ import { useState } from 'react';
import { toast } from 'react-toastify';
import DownloadIcon from '@mui/icons-material/GetApp';
import { Box, Button, Grid, Typography } from '@mui/material';
import { Box, Button, Grid2 as Grid, Typography } from '@mui/material';
import * as SystemApi from 'api/system';
import { API, callAction } from 'api/app';
@@ -35,7 +35,7 @@ const DownloadUpload = () => {
toast.info(LL.DOWNLOAD_SUCCESSFUL());
})
.onError((error) => {
toast.error(String(error.error?.message || 'An error occurred'));
toast.error(error.message);
});
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
@@ -57,7 +57,7 @@ const DownloadUpload = () => {
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (

View File

@@ -5,7 +5,7 @@ import WarningIcon from '@mui/icons-material/Warning';
import {
Button,
Checkbox,
Grid,
Grid2 as Grid,
InputAdornment,
MenuItem,
TextField,
@@ -56,7 +56,7 @@ const MqttSettings = () => {
origData,
dirtyFlags,
setDirtyFlags,
updateDataValue as (value: unknown) => void
updateDataValue
);
const SecondsInputProps = {
@@ -65,7 +65,7 @@ const MqttSettings = () => {
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
}
const validateAndSubmit = async () => {
@@ -93,7 +93,7 @@ const MqttSettings = () => {
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="host"
label={LL.ADDRESS_OF(LL.BROKER())}
multiline
@@ -105,7 +105,7 @@ const MqttSettings = () => {
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="port"
label="Port"
variant="outlined"
@@ -117,7 +117,7 @@ const MqttSettings = () => {
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="base"
label={LL.BASE_TOPIC()}
variant="outlined"
@@ -158,7 +158,7 @@ const MqttSettings = () => {
</Grid>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="keep_alive"
label="Keep Alive"
slotProps={{
@@ -254,107 +254,109 @@ const MqttSettings = () => {
}
label={LL.MQTT_RESPONSE()}
/>
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<BlockFormControlLabel
control={
<Checkbox
name="publish_single"
checked={data.publish_single}
onChange={updateFormValue}
disabled={data.ha_enabled}
/>
}
label={LL.MQTT_PUBLISH_TEXT_1()}
/>
</Grid>
{data.publish_single && (
{!data.ha_enabled && (
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<BlockFormControlLabel
control={
<Checkbox
name="publish_single2cmd"
checked={data.publish_single2cmd}
name="publish_single"
checked={data.publish_single}
onChange={updateFormValue}
/>
}
label={LL.MQTT_PUBLISH_TEXT_2()}
label={LL.MQTT_PUBLISH_TEXT_1()}
/>
</Grid>
)}
</Grid>
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<BlockFormControlLabel
control={
<Checkbox
name="ha_enabled"
checked={data.ha_enabled}
onChange={updateFormValue}
disabled={data.publish_single}
{data.publish_single && (
<Grid>
<BlockFormControlLabel
control={
<Checkbox
name="publish_single2cmd"
checked={data.publish_single2cmd}
onChange={updateFormValue}
/>
}
label={LL.MQTT_PUBLISH_TEXT_2()}
/>
}
label={LL.MQTT_PUBLISH_TEXT_3()}
/>
</Grid>
)}
</Grid>
{data.ha_enabled && (
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<TextField
name="discovery_type"
label={LL.MQTT_PUBLISH_TEXT_5()}
value={data.discovery_type}
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={0}>Home Assistant</MenuItem>
<MenuItem value={1}>Domoticz</MenuItem>
<MenuItem value={2}>Domoticz (latest)</MenuItem>
</TextField>
</Grid>
<Grid>
<TextField
name="discovery_prefix"
label={LL.MQTT_PUBLISH_TEXT_4()}
variant="outlined"
value={data.discovery_prefix}
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid>
<TextField
name="entity_format"
label={LL.MQTT_ENTITY_FORMAT()}
value={data.entity_format}
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={0}>{LL.MQTT_ENTITY_FORMAT_0()}</MenuItem>
<MenuItem value={3}>
{LL.MQTT_ENTITY_FORMAT_1()}&nbsp;(v3.6)
</MenuItem>
<MenuItem value={4}>
{LL.MQTT_ENTITY_FORMAT_2()}&nbsp;(v3.6)
</MenuItem>
<MenuItem value={1}>{LL.MQTT_ENTITY_FORMAT_1()}</MenuItem>
<MenuItem value={2}>{LL.MQTT_ENTITY_FORMAT_2()}</MenuItem>
</TextField>
</Grid>
)}
{!data.publish_single && (
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<BlockFormControlLabel
control={
<Checkbox
name="ha_enabled"
checked={data.ha_enabled}
onChange={updateFormValue}
/>
}
label={LL.MQTT_PUBLISH_TEXT_3()}
/>
</Grid>
)}
</Grid>
{data.ha_enabled && (
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<TextField
name="discovery_type"
label={LL.MQTT_PUBLISH_TEXT_5()}
value={data.discovery_type}
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={0}>Home Assistant</MenuItem>
<MenuItem value={1}>Domoticz</MenuItem>
<MenuItem value={2}>Domoticz (latest)</MenuItem>
</TextField>
</Grid>
<Grid>
<TextField
name="discovery_prefix"
label={LL.MQTT_PUBLISH_TEXT_4()}
variant="outlined"
value={data.discovery_prefix}
onChange={updateFormValue}
margin="normal"
/>
</Grid>
<Grid>
<TextField
name="entity_format"
label={LL.MQTT_ENTITY_FORMAT()}
value={data.entity_format}
variant="outlined"
onChange={updateFormValue}
margin="normal"
select
>
<MenuItem value={0}>{LL.MQTT_ENTITY_FORMAT_0()}</MenuItem>
<MenuItem value={3}>
{LL.MQTT_ENTITY_FORMAT_1()}&nbsp;(v3.6)
</MenuItem>
<MenuItem value={4}>
{LL.MQTT_ENTITY_FORMAT_2()}&nbsp;(v3.6)
</MenuItem>
<MenuItem value={1}>{LL.MQTT_ENTITY_FORMAT_1()}</MenuItem>
<MenuItem value={2}>{LL.MQTT_ENTITY_FORMAT_2()}</MenuItem>
</TextField>
</Grid>
</Grid>
)}
</Grid>
)}
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
{LL.MQTT_PUBLISH_INTERVALS()}&nbsp;(0=auto)
</Typography>
<Grid container spacing={2} rowSpacing={0}>
<Grid>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="publish_time_heartbeat"
label="Heartbeat"
slotProps={{
@@ -440,7 +442,7 @@ const MqttSettings = () => {
<Grid>
<TextField
name="publish_time_sensor"
label={LL.SENSORS()}
label={LL.TEMP_SENSORS()}
variant="outlined"
value={numberValue(data.publish_time_sensor)}
type="number"

View File

@@ -1,27 +1,12 @@
import { useState } from 'react';
import { toast } from 'react-toastify';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import CancelIcon from '@mui/icons-material/Cancel';
import WarningIcon from '@mui/icons-material/Warning';
import {
Box,
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
MenuItem,
TextField,
Typography
} from '@mui/material';
import { Button, Checkbox, MenuItem } from '@mui/material';
import * as NTPApi from 'api/ntp';
import { readNTPSettings } from 'api/ntp';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova/client';
import { updateState } from 'alova/client';
import type { ValidateFieldsError } from 'async-validator';
import {
@@ -34,8 +19,8 @@ import {
useLayoutTitle
} from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { NTPSettingsType, Time } from 'types';
import { formatLocalDateTime, updateValueDirty, useRest } from 'utils';
import type { NTPSettingsType } from 'types';
import { updateValueDirty, useRest } from 'utils';
import { validate } from 'validators';
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
@@ -61,101 +46,18 @@ const NTPSettings = () => {
const { LL } = useI18nContext();
useLayoutTitle('NTP');
const [localTime, setLocalTime] = useState<string>('');
const [settingTime, setSettingTime] = useState<boolean>(false);
const [processing, setProcessing] = useState<boolean>(false);
const { send: updateTime } = useRequest(
(local_time: Time) => NTPApi.updateTime(local_time),
{
immediate: false
}
);
const updateFormValue = updateValueDirty(
origData,
dirtyFlags,
setDirtyFlags,
updateDataValue as (value: unknown) => void
updateDataValue
);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) =>
setLocalTime(event.target.value);
const openSetTime = () => {
setLocalTime(formatLocalDateTime(new Date()));
setSettingTime(true);
};
const configureTime = async () => {
setProcessing(true);
await updateTime({ 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);
});
};
const renderSetTimeDialog = () => (
<Dialog
sx={dialogStyle}
open={settingTime}
onClose={() => setSettingTime(false)}
>
<DialogTitle>{LL.SET_TIME(1)}</DialogTitle>
<DialogContent dividers>
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
<Typography variant="body2">{LL.SET_TIME_TEXT()}</Typography>
</Box>
<TextField
label={LL.LOCAL_TIME(0)}
type="datetime-local"
value={localTime}
onChange={updateLocalTime}
disabled={processing}
fullWidth
slotProps={{
inputLabel: {
shrink: true
}
}}
/>
</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={() => setSettingTime(false)}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<AccessTimeIcon />}
variant="outlined"
onClick={configureTime}
disabled={processing}
color="primary"
>
{LL.UPDATE()}
</Button>
</DialogActions>
</Dialog>
);
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
}
const validateAndSubmit = async () => {
@@ -190,7 +92,7 @@ const NTPSettings = () => {
label={LL.ENABLE_NTP()}
/>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="server"
label={LL.NTP_SERVER()}
fullWidth
@@ -200,7 +102,7 @@ const NTPSettings = () => {
margin="normal"
/>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="tz_label"
label={LL.TIME_ZONE()}
fullWidth
@@ -213,25 +115,6 @@ const NTPSettings = () => {
<MenuItem disabled>{LL.TIME_ZONE()}...</MenuItem>
{timeZoneSelectItems()}
</ValidatedTextField>
<Box display="flex" flexWrap="wrap">
{!data.enabled && !dirtyFlags.length && (
<Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow>
<Button
onClick={openSetTime}
variant="outlined"
color="primary"
startIcon={<AccessTimeIcon />}
>
{LL.SET_TIME(0)}
</Button>
</ButtonRow>
</Box>
)}
</Box>
{renderSetTimeDialog()}
{dirtyFlags && dirtyFlags.length !== 0 && (
<ButtonRow>
<Button

View File

@@ -35,7 +35,7 @@ const Network = () => {
],
useLocation()
);
const routerTab = matchedRoutes?.[0]?.route.path || false;
const routerTab = matchedRoutes?.[0].route.path || false;
const navigate = useNavigate();
@@ -56,7 +56,7 @@ const Network = () => {
return (
<WiFiConnectionContext.Provider
value={{
...(selectedNetwork && { selectedNetwork }),
selectedNetwork,
selectNetwork,
deselectNetwork
}}

View File

@@ -104,7 +104,7 @@ const NetworkSettings = () => {
origData,
dirtyFlags,
setDirtyFlags,
updateDataValue as (value: unknown) => void
updateDataValue
);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -113,7 +113,7 @@ const NetworkSettings = () => {
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
}
const validateAndSubmit = async () => {
@@ -172,7 +172,7 @@ const NetworkSettings = () => {
</List>
) : (
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="ssid"
label={'SSID (' + LL.NETWORK_BLANK_SSID() + ')'}
fullWidth
@@ -183,7 +183,7 @@ const NetworkSettings = () => {
/>
)}
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="bssid"
label={'BSSID (' + LL.NETWORK_BLANK_BSSID() + ')'}
fullWidth
@@ -194,7 +194,7 @@ const NetworkSettings = () => {
/>
{(!selectedNetwork || !isNetworkOpen(selectedNetwork)) && (
<ValidatedPasswordField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="password"
label={LL.PASSWORD()}
fullWidth
@@ -251,7 +251,7 @@ const NetworkSettings = () => {
{LL.GENERAL_OPTIONS()}
</Typography>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="hostname"
label={LL.HOSTNAME()}
fullWidth
@@ -304,7 +304,7 @@ const NetworkSettings = () => {
{data.static_ip_config && (
<>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="local_ip"
label={LL.AP_LOCAL_IP()}
fullWidth
@@ -314,7 +314,7 @@ const NetworkSettings = () => {
margin="normal"
/>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="gateway_ip"
label={LL.NETWORK_GATEWAY()}
fullWidth
@@ -324,7 +324,7 @@ const NetworkSettings = () => {
margin="normal"
/>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="subnet_mask"
label={LL.NETWORK_SUBNET()}
fullWidth
@@ -334,7 +334,7 @@ const NetworkSettings = () => {
margin="normal"
/>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="dns_ip_1"
label="DNS #1"
fullWidth
@@ -344,7 +344,7 @@ const NetworkSettings = () => {
margin="normal"
/>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="dns_ip_2"
label="DNS #2"
fullWidth

View File

@@ -50,7 +50,9 @@ const WiFiNetworkScanner = () => {
const renderNetworkScanner = () => {
if (!networkList) {
return <FormLoader errorMessage={errorMessage || ''} />;
return (
<FormLoader message={LL.SCANNING() + '...'} errorMessage={errorMessage} />
);
}
return <WiFiNetworkSelector networkList={networkList} />;
};

View File

@@ -97,7 +97,7 @@ const ManageUsers = () => {
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
}
const noAdminConfigured = () => !data.users.find((u) => u.admin);
@@ -260,20 +260,15 @@ const ManageUsers = () => {
</Box>
</Box>
<GenerateToken
username={generatingToken || ''}
onClose={closeGenerateToken}
<GenerateToken username={generatingToken} onClose={closeGenerateToken} />
<User
user={user}
setUser={setUser}
creating={creating}
onDoneEditing={doneEditingUser}
onCancelEditing={cancelEditingUser}
validator={createUserValidator(data.users, creating)}
/>
{user && (
<User
user={user}
setUser={setUser}
creating={creating}
onDoneEditing={doneEditingUser}
onCancelEditing={cancelEditingUser}
validator={createUserValidator(data.users, creating)}
/>
)}
</>
);
};

View File

@@ -19,7 +19,7 @@ const Security = () => {
],
useLocation()
);
const routerTab = matchedRoutes?.[0]?.route.path || false;
const routerTab = matchedRoutes?.[0].route.path || false;
return (
<>

View File

@@ -47,12 +47,12 @@ const SecuritySettings = () => {
origData,
dirtyFlags,
setDirtyFlags,
updateDataValue as (value: unknown) => void
updateDataValue
);
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
}
const validateAndSubmit = async () => {
@@ -69,7 +69,7 @@ const SecuritySettings = () => {
return (
<>
<ValidatedPasswordField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="jwt_secret"
label={LL.SU_PASSWORD()}
fullWidth

View File

@@ -82,7 +82,7 @@ const User: FC<UserFormProps> = ({
</DialogTitle>
<DialogContent dividers>
<ValidatedTextField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="username"
label={LL.USERNAME(1)}
fullWidth
@@ -93,7 +93,7 @@ const User: FC<UserFormProps> = ({
margin="normal"
/>
<ValidatedPasswordField
fieldErrors={fieldErrors || {}}
fieldErrors={fieldErrors}
name="password"
label={LL.PASSWORD()}
fullWidth

View File

@@ -61,7 +61,7 @@ const APStatus = () => {
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (

View File

@@ -67,8 +67,7 @@ const SystemActivity = () => {
});
const showName = (id: number) => {
const name: keyof Translation['STATUS_NAMES'] =
id.toString() as keyof Translation['STATUS_NAMES'];
const name: keyof Translation['STATUS_NAMES'] = id;
return LL.STATUS_NAMES[name]();
};
@@ -88,7 +87,7 @@ const SystemActivity = () => {
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (

View File

@@ -41,7 +41,7 @@ const HardwareStatus = () => {
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (

View File

@@ -99,7 +99,7 @@ const MqttStatus = () => {
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
const renderConnectionStatus = () => (

View File

@@ -1,27 +1,40 @@
import { useState } from 'react';
import { toast } from 'react-toastify';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import CancelIcon from '@mui/icons-material/Cancel';
import DnsIcon from '@mui/icons-material/Dns';
import SwapVerticalCircleIcon from '@mui/icons-material/SwapVerticalCircle';
import UpdateIcon from '@mui/icons-material/Update';
import {
Avatar,
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Divider,
List,
ListItem,
ListItemAvatar,
ListItemText,
TextField,
Typography,
useTheme
} from '@mui/material';
import type { Theme } from '@mui/material';
import * as NTPApi from 'api/ntp';
import { dialogStyle } from 'CustomTheme';
import { useRequest } from 'alova/client';
import { FormLoader, SectionContent, useLayoutTitle } from 'components';
import { ButtonRow, FormLoader, SectionContent, useLayoutTitle } from 'components';
import { useI18nContext } from 'i18n/i18n-react';
import type { NTPStatusType } from 'types';
import type { NTPStatusType, Time } from 'types';
import { NTPSyncStatus } from 'types';
import { useInterval } from 'utils';
import { formatDateTime } from 'utils';
import { formatDateTime, formatLocalDateTime } from 'utils';
const NTPStatus = () => {
const { data, send: loadData, error } = useRequest(NTPApi.readNTPStatus);
@@ -30,11 +43,24 @@ const NTPStatus = () => {
void loadData();
});
const [localTime, setLocalTime] = useState<string>('');
const [settingTime, setSettingTime] = useState<boolean>(false);
const [processing, setProcessing] = useState<boolean>(false);
const { LL } = useI18nContext();
useLayoutTitle('NTP');
const { send: updateTime } = useRequest(
(local_time: Time) => NTPApi.updateTime(local_time),
{
immediate: false
}
);
NTPApi.updateTime;
const isNtpActive = ({ status }: NTPStatusType) =>
status === NTPSyncStatus.NTP_ACTIVE;
const isNtpEnabled = ({ status }: NTPStatusType) =>
status !== NTPSyncStatus.NTP_DISABLED;
@@ -51,6 +77,14 @@ const NTPStatus = () => {
}
};
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) =>
setLocalTime(event.target.value);
const openSetTime = () => {
setLocalTime(formatLocalDateTime(new Date()));
setSettingTime(true);
};
const theme = useTheme();
const ntpStatus = ({ status }: NTPStatusType) => {
@@ -66,9 +100,73 @@ const NTPStatus = () => {
}
};
const configureTime = async () => {
setProcessing(true);
await updateTime({ 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);
});
};
const renderSetTimeDialog = () => (
<Dialog
sx={dialogStyle}
open={settingTime}
onClose={() => setSettingTime(false)}
>
<DialogTitle>{LL.SET_TIME(1)}</DialogTitle>
<DialogContent dividers>
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
<Typography variant="body2">{LL.SET_TIME_TEXT()}</Typography>
</Box>
<TextField
label={LL.LOCAL_TIME(0)}
type="datetime-local"
value={localTime}
onChange={updateLocalTime}
disabled={processing}
fullWidth
slotProps={{
inputLabel: {
shrink: true
}
}}
/>
</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={() => setSettingTime(false)}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<AccessTimeIcon />}
variant="outlined"
onClick={configureTime}
disabled={processing}
color="primary"
>
{LL.UPDATE()}
</Button>
</DialogActions>
</Dialog>
);
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (
@@ -121,6 +219,23 @@ const NTPStatus = () => {
</ListItem>
<Divider variant="inset" component="li" />
</List>
<Box display="flex" flexWrap="wrap">
{data && !isNtpActive(data) && (
<Box flexWrap="nowrap" whiteSpace="nowrap">
<ButtonRow>
<Button
onClick={openSetTime}
variant="outlined"
color="primary"
startIcon={<AccessTimeIcon />}
>
{LL.SET_TIME(0)}
</Button>
</ButtonRow>
</Box>
)}
</Box>
{renderSetTimeDialog()}
</>
);
};

View File

@@ -120,7 +120,7 @@ const NetworkStatus = () => {
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (

View File

@@ -248,7 +248,7 @@ const SystemStatus = () => {
const content = () => {
if (!data || !LL) {
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
return (

View File

@@ -8,7 +8,7 @@ import {
Box,
Button,
Checkbox,
Grid,
Grid2 as Grid,
IconButton,
MenuItem,
TextField,
@@ -31,14 +31,13 @@ import type { LogEntry, LogSettings } from 'types';
import { LogLevel } from 'types';
import { updateValueDirty, useRest } from 'utils';
const TextColors: Record<LogLevel, string> = {
const TextColors = {
[LogLevel.ERROR]: '#ff0000', // red
[LogLevel.WARNING]: '#ff0000', // red
[LogLevel.NOTICE]: '#ffffff', // white
[LogLevel.INFO]: '#ffcc00', // yellow
[LogLevel.DEBUG]: '#00ffff', // cyan
[LogLevel.TRACE]: '#00ffff', // cyan
[LogLevel.ALL]: '#ffffff' // white
[LogLevel.TRACE]: '#00ffff' // cyan
};
const LogEntryLine = styled('span')(
@@ -110,7 +109,7 @@ const SystemLog = () => {
origData,
dirtyFlags,
setDirtyFlags,
updateDataValue as (value: unknown) => void
updateDataValue
);
useSSE(fetchLogES, {
@@ -191,7 +190,7 @@ const SystemLog = () => {
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={errorMessage || ''} />;
return <FormLoader onRetry={loadData} errorMessage={errorMessage} />;
}
return (

View File

@@ -51,7 +51,7 @@ const SystemMonitor = () => {
}
})
.onError((error) => {
setErrorMessage(String(error.error?.message || 'An error occurred'));
setErrorMessage(error.message);
});
useInterval(() => {
@@ -97,7 +97,7 @@ const SystemMonitor = () => {
color="error"
onClick={onCancel}
>
{LL.RESTART()}
{LL.RESET(0)}
</Button>
</MessageBox>
) : (

View File

@@ -2,9 +2,9 @@ import { useContext, useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import CancelIcon from '@mui/icons-material/Cancel';
import CloseIcon from '@mui/icons-material/Close';
import CheckIcon from '@mui/icons-material/Done';
import DownloadIcon from '@mui/icons-material/GetApp';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import WarningIcon from '@mui/icons-material/Warning';
import {
Box,
@@ -15,7 +15,7 @@ import {
DialogContent,
DialogTitle,
FormControlLabel,
Grid,
Grid2 as Grid,
Link,
Typography
} from '@mui/material';
@@ -44,10 +44,7 @@ const Version = () => {
const [restarting, setRestarting] = useState<boolean>(false);
const [openInstallDialog, setOpenInstallDialog] = useState<boolean>(false);
const [usingDevVersion, setUsingDevVersion] = useState<boolean>(false);
const [fetchDevVersion, setFetchDevVersion] = useState<boolean>(false);
const [devUpgradeAvailable, setDevUpgradeAvailable] = useState<boolean>(false);
const [stableUpgradeAvailable, setStableUpgradeAvailable] =
useState<boolean>(false);
const [upgradeAvailable, setUpgradeAvailable] = useState<boolean>(false);
const [internetLive, setInternetLive] = useState<boolean>(false);
const [downloadOnly, setDownloadOnly] = useState<boolean>(false);
@@ -65,13 +62,8 @@ const Version = () => {
immediate: false
}
).onSuccess((event) => {
const data = event.data as {
emsesp_version: string;
dev_upgradeable: boolean;
stable_upgradeable: boolean;
};
setDevUpgradeAvailable(data.dev_upgradeable);
setStableUpgradeAvailable(data.stable_upgradeable);
const data = event.data as { emsesp_version: string; upgradeable: boolean };
setUpgradeAvailable(data.upgradeable);
});
const {
@@ -79,7 +71,7 @@ const Version = () => {
send: loadData,
error
} = useRequest(SystemApi.readSystemStatus).onSuccess((event) => {
// older version of EMS-ESP using ESP32 (not S3) and no PSRAM, can't use OTA because of SSL support in HttpClient
// older version of EMS-ESP on 4MB boards, can't use OTA because of SSL support in HttpClient
if (event.data.arduino_version.startsWith('Tasmota')) {
setDownloadOnly(true);
}
@@ -110,7 +102,7 @@ const Version = () => {
}, [latestVersion, latestDevVersion]);
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
const DIVISIONS: Array<{ amount: number; name: string }> = [
const DIVISIONS = [
{ amount: 60, name: 'seconds' },
{ amount: 60, name: 'minutes' },
{ amount: 24, name: 'hours' },
@@ -119,21 +111,18 @@ const Version = () => {
{ amount: 12, name: 'months' },
{ amount: Number.POSITIVE_INFINITY, name: 'years' }
];
function formatTimeAgo(date: Date) {
function formatTimeAgo(date) {
let duration = (date.getTime() - new Date().getTime()) / 1000;
for (let i = 0; i < DIVISIONS.length; i++) {
const division = DIVISIONS[i];
if (division && Math.abs(duration) < division.amount) {
if (Math.abs(duration) < division.amount) {
return rtf.format(
Math.round(duration),
division.name as Intl.RelativeTimeFormatUnit
);
}
if (division) {
duration /= division.amount;
}
duration /= division.amount;
}
return rtf.format(0, 'seconds');
}
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
@@ -149,21 +138,20 @@ const Version = () => {
);
};
const getBinURL = (showingDev: boolean) => {
const getBinURL = () => {
if (!internetLive) {
return '';
}
const filename =
'EMS-ESP-' +
(showingDev ? latestDevVersion.name : latestVersion.name).replaceAll(
(usingDevVersion ? latestDevVersion.name : latestVersion.name).replaceAll(
'.',
'_'
) +
'-' +
getPlatform() +
'.bin';
return showingDev
return usingDevVersion
? DEV_URL + filename
: STABLE_URL + 'v' + latestVersion.name + '/' + filename;
};
@@ -184,127 +172,107 @@ const Version = () => {
useLayoutTitle('EMS-ESP Firmware');
const renderInstallDialog = () => {
const binURL = getBinURL(fetchDevVersion);
return (
<Dialog
sx={dialogStyle}
open={openInstallDialog}
onClose={() => closeInstallDialog()}
>
<DialogTitle>
{LL.UPDATE() +
' ' +
(fetchDevVersion ? LL.DEVELOPMENT() : LL.STABLE()) +
' Firmware'}
</DialogTitle>
<DialogContent dividers>
<Typography mb={2}>
{LL.INSTALL_VERSION(
downloadOnly ? LL.DOWNLOAD(1) : LL.INSTALL(),
fetchDevVersion ? latestDevVersion?.name : latestVersion?.name
)}
</Typography>
</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={() => closeInstallDialog()}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
onClick={() => closeInstallDialog()}
color="primary"
>
<Link underline="none" target="_blank" href={binURL} color="primary">
{LL.DOWNLOAD(0)}
</Link>
</Button>
{!downloadOnly && (
<Button
startIcon={<WarningIcon color="warning" />}
variant="outlined"
onClick={() => installFirmwareURL(binURL)}
color="primary"
>
{LL.INSTALL()}
</Button>
const renderInstallDialog = () => (
<Dialog
sx={dialogStyle}
open={openInstallDialog}
onClose={() => closeInstallDialog()}
>
<DialogTitle>
{LL.INSTALL() +
' ' +
(usingDevVersion ? LL.DEVELOPMENT() : LL.STABLE()) +
' Firmware'}
</DialogTitle>
<DialogContent dividers>
<Typography mb={2}>
{LL.INSTALL_VERSION(
usingDevVersion ? latestDevVersion?.name : latestVersion?.name
)}
</DialogActions>
</Dialog>
);
};
</Typography>
</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
onClick={() => closeInstallDialog()}
color="secondary"
>
{LL.CANCEL()}
</Button>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
onClick={() => closeInstallDialog()}
color="primary"
>
<Link underline="none" target="_blank" href={getBinURL()} color="primary">
{LL.DOWNLOAD(1)}
</Link>
</Button>
<Button
startIcon={<WarningIcon color="warning" />}
variant="outlined"
onClick={() => installFirmwareURL(getBinURL())}
color="primary"
>
{LL.INSTALL()}
</Button>
</DialogActions>
</Dialog>
);
const showFirmwareDialog = (useDevVersion: boolean) => {
setFetchDevVersion(useDevVersion);
const showFirmwareDialog = (useDevVersion?: boolean) => {
setUsingDevVersion(useDevVersion || usingDevVersion);
setOpenInstallDialog(true);
};
const closeInstallDialog = () => {
setOpenInstallDialog(false);
setUsingDevVersion(data.emsesp_version.includes('dev'));
};
const showButtons = (showingDev: boolean) => {
const choice = showingDev
? !usingDevVersion
? LL.SWITCH_RELEASE_TYPE(LL.DEVELOPMENT())
: devUpgradeAvailable
? LL.UPDATE_AVAILABLE()
: undefined
: usingDevVersion
? LL.SWITCH_RELEASE_TYPE(LL.STABLE())
: stableUpgradeAvailable
? LL.UPDATE_AVAILABLE()
: undefined;
if (!choice) {
return (
<>
<CheckIcon
color="success"
sx={{ verticalAlign: 'middle', ml: 0.5, mr: 0.5 }}
/>
<span style={{ color: '#66bb6a', fontSize: '0.8em' }}>
{LL.LATEST_VERSION(usingDevVersion ? LL.DEVELOPMENT() : LL.STABLE())}
</span>
<Button
sx={{ ml: 2 }}
variant="outlined"
size="small"
onClick={() => showFirmwareDialog(showingDev)}
>
{LL.REINSTALL()}
</Button>
</>
);
}
const showButtons = (showDev?: boolean) => {
if (!me.admin) {
return;
}
if (downloadOnly) {
return (
<Button
sx={{ ml: 2 }}
startIcon={<DownloadIcon />}
variant="outlined"
onClick={() => setOpenInstallDialog(false)}
color="warning"
size="small"
>
<Link underline="none" target="_blank" href={getBinURL()} color="warning">
{LL.DOWNLOAD(1)}
</Link>
</Button>
);
}
return (
<Button
sx={{ ml: 2 }}
variant="outlined"
color={choice === LL.UPDATE_AVAILABLE() ? 'success' : 'warning'}
color="warning"
size="small"
onClick={() => showFirmwareDialog(showingDev)}
onClick={() => showFirmwareDialog()}
>
{choice}
{upgradeAvailable || (!usingDevVersion && showDev)
? LL.UPGRADE()
: LL.REINSTALL()}
&hellip;
</Button>
);
};
const content = () => {
if (!data) {
return <FormLoader onRetry={loadData} errorMessage={error?.message || ''} />;
return <FormLoader onRetry={loadData} errorMessage={error?.message} />;
}
const isDev = data.emsesp_version.includes('dev');
@@ -346,25 +314,7 @@ const Version = () => {
<Typography>
{getPlatform()}
<Typography variant="caption">
&nbsp; &#40;
{data.psram ? (
<CheckIcon
color="success"
sx={{
fontSize: '1.5em',
verticalAlign: 'middle'
}}
/>
) : (
<CloseIcon
color="error"
sx={{
fontSize: '1.5em',
verticalAlign: 'middle'
}}
/>
)}
PSRAM&#41;
&nbsp; &#40;{data.psram ? '+PSRAM' : '-PSRAM'}&#41;
</Typography>
</Typography>
</Grid>
@@ -445,7 +395,7 @@ const Version = () => {
{formatTimeAgo(new Date(latestVersion.published_at))})
</Typography>
)}
{showButtons(false)}
{!usingDevVersion && showButtons(false)}
</Typography>
</Grid>
@@ -467,6 +417,24 @@ const Version = () => {
</Typography>
</Grid>
</Grid>
{upgradeAvailable ? (
<Typography mt={2} color="warning">
<InfoOutlinedIcon
color="warning"
sx={{ verticalAlign: 'middle', mr: 2 }}
/>
{LL.UPGRADE_AVAILABLE()}
</Typography>
) : (
<Typography mt={2} color="success">
<CheckIcon
color="success"
sx={{ verticalAlign: 'middle', mr: 2 }}
/>
{LL.LATEST_VERSION()}
</Typography>
)}
</>
) : (
<Typography mt={2} color="warning">

View File

@@ -1,24 +1,26 @@
import { memo } from 'react';
import type { FC } from 'react';
import { Box } from '@mui/material';
import type { BoxProps } from '@mui/material';
const ButtonRow = memo<BoxProps>(({ children, ...rest }) => (
const ButtonRow: FC<BoxProps> = ({ children, ...rest }) => (
<Box
sx={{
'& button, & a, & .MuiCard-root': {
mt: 2,
mx: 0.6,
'&:last-child': { mr: 0 },
'&:first-of-type': { ml: 0 }
'&:last-child': {
mr: 0
},
'&:first-of-type': {
ml: 0
}
}
}}
{...rest}
>
{children}
</Box>
));
ButtonRow.displayName = 'ButtonRow';
);
export default ButtonRow;

View File

@@ -1,12 +1,7 @@
import { Tooltip, type TooltipProps, styled, tooltipClasses } from '@mui/material';
export const ButtonTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip
{...props}
placement="top"
arrow
classes={{ ...(className && { popper: className }) }}
/>
<Tooltip {...props} placement="top" arrow classes={{ popper: className }} />
))(({ theme }) => ({
[`& .${tooltipClasses.arrow}`]: {
color: theme.palette.success.main

View File

@@ -1,15 +1,10 @@
// Optimized exports - use direct exports to reduce bundle size
export { default as SectionContent } from './SectionContent';
export { default as ButtonRow } from './ButtonRow';
export { default as MessageBox } from './MessageBox';
export { default as ButtonTooltip } from './ButtonTooltip';
// Re-export sub-modules
export * from './inputs';
export * from './layout';
export * from './loading';
export * from './routing';
export * from './upload';
// Specific routing exports
export { default as SectionContent } from './SectionContent';
export { default as ButtonRow } from './ButtonRow';
export { default as MessageBox } from './MessageBox';
export { default as BlockNavigation } from './routing/BlockNavigation';
export { default as ButtonTooltip } from './ButtonTooltip';

View File

@@ -16,14 +16,14 @@ const ValidatedTextField: FC<ValidatedTextFieldProps> = ({
fieldErrors,
...rest
}) => {
const errors = fieldErrors?.[rest.name];
const errors = fieldErrors && fieldErrors[rest.name];
const renderErrors = () =>
errors &&
errors.map((e) => <FormHelperText key={e.message}>{e.message}</FormHelperText>);
return (
<>
<TextField error={!!errors} {...rest} />
{errors?.map((e) => (
<FormHelperText key={e.message}>{e.message}</FormHelperText>
))}
{renderErrors()}
</>
);
};

View File

@@ -23,12 +23,7 @@ const LayoutMenuItem = ({
const selected = routeMatches(to, pathname);
return (
<ListItemButton
component={Link}
to={to}
disabled={disabled || false}
selected={selected}
>
<ListItemButton component={Link} to={to} disabled={disabled} selected={selected}>
<ListItemIcon sx={{ color: selected ? '#90caf9' : '#9e9e9e' }}>
<Icon />
</ListItemIcon>

View File

@@ -58,22 +58,12 @@ const LayoutMenuItem = ({
}
>
<ListItemButton component={Link} to={to}>
<RenderIcon
icon={icon}
{...(bgcolor && { bgcolor })}
label={label}
text={text}
/>
<RenderIcon icon={icon} bgcolor={bgcolor} label={label} text={text} />
</ListItemButton>
</ListItem>
) : (
<ListItem>
<RenderIcon
icon={icon}
{...(bgcolor && { bgcolor })}
label={label}
text={text}
/>
<RenderIcon icon={icon} bgcolor={bgcolor} label={label} text={text} />
</ListItem>
)}
</>

View File

@@ -1,20 +0,0 @@
import { Box, CircularProgress } from '@mui/material';
const LazyLoader = () => (
<Box
display="flex"
justifyContent="center"
alignItems="center"
minHeight="200px"
sx={{
backgroundColor: 'background.default',
borderRadius: 1,
border: '1px solid',
borderColor: 'divider'
}}
>
<CircularProgress size={40} />
</Box>
);
export default LazyLoader;

View File

@@ -1,3 +1,2 @@
export { default as LoadingSpinner } from './LoadingSpinner';
export { default as FormLoader } from './FormLoader';
export { default as LazyLoader } from './LazyLoader';

View File

@@ -1,5 +1,6 @@
import type { Path } from 'react-router';
import type * as H from 'history';
import { jwtDecode } from 'jwt-decode';
import type { Me, SignInRequest, SignInResponse } from 'types';
@@ -17,7 +18,7 @@ export function getStorage() {
return localStorage || sessionStorage;
}
export function storeLoginRedirect(location?: { pathname: string; search: string }) {
export function storeLoginRedirect(location?: H.Location) {
if (location) {
getStorage().setItem(SIGN_IN_PATHNAME, location.pathname);
getStorage().setItem(SIGN_IN_SEARCH, location.search);
@@ -35,7 +36,7 @@ export function fetchLoginRedirect(): Partial<Path> {
clearLoginRedirect();
return {
pathname: signInPathname || `/dashboard`,
...(signInPathname && signInSearch && { search: signInSearch })
search: (signInPathname && signInSearch) || undefined
};
}

View File

@@ -1,59 +1,16 @@
// Code inspired by Prince Azubuike from https://medium.com/@dprincecoder/creating-a-drag-and-drop-file-upload-component-in-react-a-step-by-step-guide-4d93b6cc21e0
import {
type ChangeEvent,
type DragEvent,
type MouseEvent,
useRef,
useState
} from 'react';
import { type ChangeEvent, useRef, useState } from 'react';
import CancelIcon from '@mui/icons-material/Cancel';
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import UploadIcon from '@mui/icons-material/Upload';
import { Box, Button, Typography, styled } from '@mui/material';
import { Box, Button } from '@mui/material';
import { useI18nContext } from 'i18n/i18n-react';
const DocumentUploader = styled(Box)<{ active?: boolean }>(({ theme, active }) => ({
border: `2px dashed ${active ? '#6dc24b' : '#4282fe'}`,
backgroundColor: '#2e3339',
padding: theme.spacing(1.25),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
borderRadius: theme.spacing(1),
cursor: 'pointer',
minHeight: '120px',
transition: 'border-color 0.2s ease-in-out'
}));
import './dragNdrop.css';
const UploadInfo = styled(Box)({
display: 'flex',
alignItems: 'center'
});
const FileInfo = styled(Box)({
display: 'flex',
flexDirection: 'column',
width: '100%',
justifyContent: 'space-between',
alignItems: 'center'
});
const FileName = styled(Typography)(({ theme }) => ({
fontSize: '14px',
color: '#6dc24b',
margin: theme.spacing(1, 0)
}));
interface DragNdropProps {
text: string;
onFileSelected: (file: File) => void;
}
const DragNdrop = ({ text, onFileSelected }: DragNdropProps) => {
const DragNdrop = ({ text, onFileSelected }) => {
const [file, setFile] = useState<File>();
const [dragged, setDragged] = useState(false);
const inputRef = useRef<HTMLInputElement | null>(null);
@@ -71,17 +28,14 @@ const DragNdrop = ({ text, onFileSelected }: DragNdropProps) => {
};
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.files || e.target.files.length === 0) {
if (!e.target.files) {
return;
}
const selectedFile = e.target.files[0];
if (selectedFile) {
checkFileExtension(selectedFile);
}
checkFileExtension(e.target.files[0]);
e.target.value = ''; // this is to allow the same file to be selected again
};
const handleDrop = (event: DragEvent<HTMLDivElement>) => {
const handleDrop = (event) => {
event.preventDefault();
const droppedFiles = event.dataTransfer.files;
if (droppedFiles.length > 0) {
@@ -89,40 +43,38 @@ const DragNdrop = ({ text, onFileSelected }: DragNdropProps) => {
}
};
const handleRemoveFile = (event: MouseEvent<HTMLButtonElement>) => {
const handleRemoveFile = (event) => {
event.stopPropagation();
setFile(undefined);
setDragged(false);
};
const handleUploadClick = (event: MouseEvent<HTMLButtonElement>) => {
const handleUploadClick = (event) => {
event.stopPropagation();
if (file) {
onFileSelected(file);
}
onFileSelected(file);
};
const handleBrowseClick = () => {
inputRef.current?.click();
};
const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
const handleDragOver = (event) => {
event.preventDefault(); // prevent file from being opened
setDragged(true);
};
return (
<DocumentUploader
active={!!(file || dragged)}
<div
className={`document-uploader ${file || dragged ? 'active' : ''}`}
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragLeave={() => setDragged(false)}
onClick={handleBrowseClick}
>
<UploadInfo>
<div className="upload-info">
<CloudUploadIcon sx={{ marginRight: 4 }} color="primary" fontSize="large" />
<Typography>{text}</Typography>
</UploadInfo>
<p>{text}</p>
</div>
<input
type="file"
@@ -136,9 +88,9 @@ const DragNdrop = ({ text, onFileSelected }: DragNdropProps) => {
{file && (
<>
<FileInfo>
<FileName>{file.name}</FileName>
</FileInfo>
<div className="file-info">
<p>{file.name}</p>
</div>
<Box>
<Button
startIcon={<CancelIcon />}
@@ -160,7 +112,7 @@ const DragNdrop = ({ text, onFileSelected }: DragNdropProps) => {
</Box>
</>
)}
</DocumentUploader>
</div>
);
};

View File

@@ -12,12 +12,7 @@ import { useI18nContext } from 'i18n/i18n-react';
import DragNdrop from './DragNdrop';
import { LinearProgressWithLabel } from './LinearProgressWithLabel';
interface SingleUploadProps {
text: string;
doRestart: () => void;
}
const SingleUpload = ({ text, doRestart }: SingleUploadProps) => {
const SingleUpload = ({ text, doRestart }) => {
const [md5, setMd5] = useState<string>();
const [file, setFile] = useState<File>();
const { LL } = useI18nContext();
@@ -30,8 +25,8 @@ const SingleUpload = ({ text, doRestart }: SingleUploadProps) => {
} = useRequest(SystemApi.uploadFile, {
immediate: false
}).onSuccess(({ data }) => {
if (data && typeof data === 'object' && 'md5' in data) {
setMd5((data as { md5: string }).md5);
if (data) {
setMd5(data.md5 as string);
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
setFile(undefined);
} else {
@@ -39,19 +34,16 @@ const SingleUpload = ({ text, doRestart }: SingleUploadProps) => {
}
});
useEffect(() => {
const uploadFile = async () => {
if (file) {
await sendUpload(file).catch((error: Error) => {
if (error.message.includes('The user aborted a request')) {
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
} else {
toast.warning('Invalid file extension or incompatible bin file');
}
});
}
};
void uploadFile();
useEffect(async () => {
if (file) {
await sendUpload(file).catch((error: Error) => {
if (error.message === 'The user aborted a request') {
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
} else {
toast.warning('Invalid file extension or incompatible bin file');
}
});
}
}, [file]);
return (

View File

@@ -0,0 +1,33 @@
.document-uploader {
border: 2px dashed #4282fe;
background-color: #2e3339;
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
border-radius: 8px;
cursor: pointer;
&.active {
border-color: #6dc24b;
}
.upload-info {
display: flex;
align-items: center;
}
.file-info {
display: flex;
flex-direction: column;
width: 100%;
justify-content: space-between;
align-items: center;
p {
font-size: 14px;
color: #6dc24b;
}
}
}

View File

@@ -69,12 +69,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
// cache object to prevent re-renders
const obj = useMemo(
() => ({
signIn,
signOut,
refresh,
...(me && { me })
}),
() => ({ signIn, signOut, me, refresh }),
[signIn, signOut, me, refresh]
);

View File

@@ -162,7 +162,6 @@ const cz: Translation = {
UPLOAD: 'Nahrát',
DOWNLOAD: '{{S|s|s}}táhnout',
INSTALL: 'Instalovat',
REINSTALL: 'Znovu instalovat',
ABORTED: 'přerušeno',
FAILED: 'neúspěšné',
SUCCESSFUL: 'úspěšné',
@@ -188,7 +187,7 @@ const cz: Translation = {
COMPACT: 'Kompaktní',
DOWNLOAD_SETTINGS_TEXT: 'Vytvořte zálohu svého nastavení a konfigurace',
UPLOAD_TEXT: 'Nahrajte nový soubor firmwaru (.bin) nebo záložní soubor (.json)',
UPLOAD_DROP_TEXT: 'Přetáhněte soubor sem nebo klikněte pro výběr',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Neočekávaná chyba, zkuste to prosím znovu',
TIME_SET: 'Čas nastaven',
MANAGE_USERS: 'Spravovat uživatele',
@@ -332,27 +331,28 @@ const cz: Translation = {
ALLVALUES: 'Všechny hodnoty',
SPECIAL_FUNCTIONS: 'Speciální funkce',
WAIT_FIRMWARE: 'Firmware se nahrává a instaluje',
INSTALL_VERSION: 'Tímto se {0} verze {1}. Jste si jistí?',
UPDATE_AVAILABLE: 'aktualizace dostupná',
LATEST_VERSION: 'Používáte nejnovější verzi {0}firmwaru',
INSTALL_VERSION: 'Tímto se instalovat verze {0}. Jste si jistí?',
UPGRADE_AVAILABLE: 'Je k dispozici aktualizace firmwaru!',
LATEST_VERSION: 'Používáte nejnovější verzi firmwaru',
PLEASE_WAIT: 'Prosím čekejte',
RESTARTING_PRE: 'Inicializace',
RESTARTING_POST: 'Příprava',
AUTO_SCROLL: 'Automatické rolování',
DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Režim vývojáře',
BYTES: 'Bajty',
BITMASK: 'Bit Mask',
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplikát',
UPGRADE: 'Upgrade',
DASHBOARD_1: 'Všechny aktivní entity EMS jsou označené jako oblíbené. Všechny vlastní entity, harmonogramy a externí sensory jsou zobrazeny níže.',
NO_DATA_1: 'Nebyly nalezeny žádné oblíbené entity. Použijte modul',
NO_DATA_2: 'pro jejich výběr.',
NO_DATA_3: 'Pro zobrazení všech dostupných entit navštivte stránku',
NO_DATA_1: 'Nebyly nalezeny žádné oblíbené entity. Použijte',
NO_DATA_2: 'modul sloužící k jejich výběru.',
NO_DATA_3: 'Pro zobrazení všech dostupných entit navštivte',
THIS_VERSION: 'Tato verze',
PLATFORM: 'Platforma',
RELEASE_TYPE: 'Typ sestavení',
REINSTALL: 'Přeinstalovat',
INTERNET_CONNECTION_REQUIRED: 'Pro automatickou kontrolu a instalaci aktualizací je třeba internetové připojení',
SWITCH_RELEASE_TYPE: 'Přepnout na {0} verzi'
};
export default cz;

View File

@@ -42,7 +42,7 @@ const de: Translation = {
CANCEL: 'Abbrechen',
RESET: 'Zurücksetzen',
APPLY_CHANGES: 'Änderungen anwenden ({0})',
UPDATE: 'Aktualisieren',
UPDATE: 'Update',
EXECUTE: 'Ausführen',
REMOVE: 'Entfernen',
PROBLEM_UPDATING: 'Problem beim Aktualisieren',
@@ -160,9 +160,8 @@ const de: Translation = {
HELP_INFORMATION_3: 'Um neue Funktionen anzufragen oder Fehler zu melden, eröffnen Sie ein Issue auf GitHub',
HELP_INFORMATION_4: 'Bitte laden Sie die Systemdetails und hängen Sie sie an das Support-Issue an',
UPLOAD: 'Hochladen',
DOWNLOAD: '{{Herunterladen|heruntergeladen|}}',
DOWNLOAD: '{{H|h|h}}erunterladen',
INSTALL: 'Installieren',
REINSTALL: 'Neu installieren',
ABORTED: 'abgebrochen',
FAILED: 'gescheitert',
SUCCESSFUL: 'erfolgreich',
@@ -332,9 +331,9 @@ const de: Translation = {
ALLVALUES: 'Alle Werte',
SPECIAL_FUNCTIONS: 'Sonderfunktionen',
WAIT_FIRMWARE: 'Die Firmware wird hochgeladen und installiert.',
INSTALL_VERSION: 'Dadurch wird die Version {1} {0}. Sind Sie sicher?',
UPDATE_AVAILABLE: 'Firmware-Update verfügbar',
LATEST_VERSION: 'Sie verwenden die neueste {0} Firmware-Version',
INSTALL_VERSION: 'Dadurch wird die Version {0} heruntergeladen. Sind Sie sicher?',
UPGRADE_AVAILABLE: 'Es ist ein Firmware-Upgrade verfügbar.',
LATEST_VERSION: 'Sie verwenden die neueste Firmware-Version',
PLEASE_WAIT: 'Bitte warten',
RESTARTING_PRE: 'Initialisierung',
RESTARTING_POST: 'Vorbereitung',
@@ -344,6 +343,7 @@ const de: Translation = {
BYTES: 'Bytes',
BITMASK: 'Bit Maske',
DUPLICATE: 'Kopieren',
UPGRADE: 'Aktualisieren',
DASHBOARD_1: 'Alle EMS-Entitäten, die aktiv und als Favorit markiert sind, sowie alle benutzerdefinierten Entitäten, Zeitpläne und externen Sensordaten werden unten angezeigt.',
NO_DATA_1: 'Keine favorisierten EMS-Entitäten gefunden! Verwenden Sie das Modul',
NO_DATA_2: ', um sie zu markieren.',
@@ -351,8 +351,8 @@ const de: Translation = {
THIS_VERSION: 'Diese Version',
PLATFORM: 'Plattform',
RELEASE_TYPE: 'Release Typ',
INTERNET_CONNECTION_REQUIRED: 'Für die automatische Versionsprüfung und Aktualisierung ist eine Internetverbindung erforderlich',
SWITCH_RELEASE_TYPE: 'Zum {0}-Release wechseln'
REINSTALL: 'Neu installieren',
INTERNET_CONNECTION_REQUIRED: 'Internetverbindung erforderlich für automatische Version-Überprüfung und -Aktualisierung',
};
export default de;

View File

@@ -162,7 +162,6 @@ const en: Translation = {
UPLOAD: 'Upload',
DOWNLOAD: '{{D|d|d}}ownload',
INSTALL: 'Install',
REINSTALL: 'Reinstall',
ABORTED: 'aborted',
FAILED: 'failed',
SUCCESSFUL: 'successful',
@@ -332,9 +331,9 @@ const en: Translation = {
ALLVALUES: 'All Values',
SPECIAL_FUNCTIONS: 'Special Functions',
WAIT_FIRMWARE: 'Firmware is uploading and installing',
INSTALL_VERSION: 'This will {0} version {1}. Are you sure?',
UPDATE_AVAILABLE: 'update available',
LATEST_VERSION: 'You are using the latest {0} firmware version',
INSTALL_VERSION: 'This will install version {0}. Are you sure?',
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!',
LATEST_VERSION: 'You are using the latest firmware version',
PLEASE_WAIT: 'Please wait',
RESTARTING_PRE: 'Initializing',
RESTARTING_POST: 'Preparing',
@@ -345,14 +344,15 @@ const en: Translation = {
BYTES: 'Bytes',
BITMASK: 'Bit Mask',
DUPLICATE: 'Duplicate',
UPGRADE: 'Upgrade',
NO_DATA_1: 'No favorite EMS entities found yet. Use the',
NO_DATA_2: 'module to mark them.',
NO_DATA_3: 'To see all available entities go to',
THIS_VERSION: 'This Version',
PLATFORM: 'Platform',
RELEASE_TYPE: 'Release Type',
REINSTALL: 'Re-install',
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
SWITCH_RELEASE_TYPE: 'Switch to {0} release'
};
export default en;

View File

@@ -41,9 +41,9 @@ const fr: Translation = {
CHANGE_VALUE: 'Changer la valeur',
CANCEL: 'Annuler',
RESET: 'Réinitialiser',
APPLY_CHANGES: 'Appliquer les changements ({0})',
UPDATE: 'Update',
EXECUTE: 'Execute',
APPLY_CHANGES: 'Apply Changes ({0})', // TODO translate
UPDATE: 'Update', // TODO translate
EXECUTE: 'Execute', // TODO translate
REMOVE: 'Enlever',
PROBLEM_UPDATING: 'Problème lors de la mise à jour',
PROBLEM_LOADING: 'Problème lors du chargement',
@@ -66,13 +66,13 @@ const fr: Translation = {
TEMP_SENSOR: 'Capteur de température',
TEMP_SENSORS: 'Capteurs de température',
WRITE_CMD_SENT: 'Envoyer la commande sent',
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...',
CONNECTED: 'Connecté',
TX_ISSUES: 'Problèmes de transmission (Tx) - Essayez un autre mode Tx',
DISCONNECTED: 'Déconnecté',
EMS_SCAN: 'Etes-vous sûr de vouloir lancer un scan complet du bus EMS ?',
DATA_TRAFFIC: 'Traffic des données',
DATA_TRAFFIC: 'Data Traffic', // TODO translate
EMS_DEVICE: 'Appareils EMS',
SUCCESS: 'SUCCÈS',
FAIL: 'ÉCHEC',
@@ -114,9 +114,9 @@ const fr: Translation = {
BYPASS_TOKEN: "Contourner l'autorisation du jeton d'accès sur les appels API",
READONLY: 'Activer le mode lecture uniquement (bloque toutes les commandes EMS sortantes en écriture Tx)',
UNDERCLOCK_CPU: 'Underclock du CPU',
HEATINGOFF: 'Démarrer le chauffage avec le chauffage forcé éteint',
HEATINGOFF: 'Start boiler with forced heating off', // TODO translate
REMOTE_TIMEOUT: 'Remote timeout',
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature',
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature', // TODO translate
MIN_DURATION: 'Wait time',
ENABLE_SHOWER_TIMER: 'Activer la minuterie de la douche',
ENABLE_SHOWER_ALERT: 'Activer les alertes de durée de douche',
@@ -148,7 +148,7 @@ const fr: Translation = {
CUSTOMIZATIONS_HELP_3: "désactiver l'action d'écriture",
CUSTOMIZATIONS_HELP_4: "exclure de MQTT et de l'API",
CUSTOMIZATIONS_HELP_5: 'cacher des appareils',
CUSTOMIZATIONS_HELP_6: 'supprimer de la mémoire',
CUSTOMIZATIONS_HELP_6: 'remove from memory', // TODO translate
SELECT_DEVICE: 'Sélectionnez un appareil',
SET_ALL: 'tout régler',
OPTIONS: 'Options',
@@ -162,21 +162,20 @@ const fr: Translation = {
UPLOAD: 'Upload',
DOWNLOAD: '{{D|d|d}}ownload',
INSTALL: 'Installer',
REINSTALL: 'Réinstaller',
ABORTED: 'annulé',
FAILED: 'échoué',
SUCCESSFUL: 'réussi',
SYSTEM: 'Système',
LOG_OF: '{0} Log',
STATUS_OF: 'Statut {0}',
DOWNLOAD_UPLOAD: 'Télécharger/Mettre à jour',
DOWNLOAD_UPLOAD: 'Download/Upload', // TODO translate
CLOSE: 'Fermer',
USE: 'Utiliser',
FACTORY_RESET: 'Réinitialisation',
SYSTEM_FACTORY_TEXT: "L'appareil a été réinitialisé et va maintenant redémarrer",
SYSTEM_FACTORY_TEXT_DIALOG: "Êtes-vous sûr de vouloir réinitialiser l'appareil à ses paramètres d'usine ?",
AVAILABLE_VERSION: 'Versions disponibles',
STABLE: 'Stable',
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
STABLE: 'Stable', // TODO translate
DEVELOPMENT: 'Développement',
UPTIME: 'Durée de fonctionnement du système',
FREE_MEMORY: 'Libre Memory',
@@ -186,9 +185,9 @@ const fr: Translation = {
FILESYSTEM: 'File System (Utilisée / Libre)',
BUFFER_SIZE: 'Max taille du buffer',
COMPACT: 'Compact',
DOWNLOAD_SETTINGS_TEXT: 'Créer une sauvegarde de vos paramètres et configurations',
UPLOAD_TEXT: 'Télécharger un nouveau fichier firmware (.bin) ou une sauvegarde (.json)',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Erreur inattendue, veuillez réessayer',
TIME_SET: 'Time set',
MANAGE_USERS: 'Gérer les utilisateurs',
@@ -218,7 +217,7 @@ const fr: Translation = {
MQTT_PUBLISH_TEXT_2: 'Publier vers des topics de commande (ioBroker)',
MQTT_PUBLISH_TEXT_3: 'Activer la découverte MQTT',
MQTT_PUBLISH_TEXT_4: 'Préfixe pour les topics découverte',
MQTT_PUBLISH_TEXT_5: 'Type de découverte',
MQTT_PUBLISH_TEXT_5: 'Discovery type', // TODO translate
MQTT_PUBLISH_INTERVALS: 'Intervalles de publication',
MQTT_INT_BOILER: 'Chaudières et pompes à chaleur',
MQTT_INT_THERMOSTATS: 'Thermostats',
@@ -227,10 +226,10 @@ const fr: Translation = {
MQTT_INT_WATER: 'Modules eau',
MQTT_QUEUE: 'Queue MQTT',
DEFAULT: 'Défaut',
MQTT_ENTITY_FORMAT: 'Format de l\'ID de l\'entité',
MQTT_ENTITY_FORMAT_0: 'Instance unique, nom long (v3.4)',
MQTT_ENTITY_FORMAT_1: 'Instance unique, nom court',
MQTT_ENTITY_FORMAT_2: 'Instances multiples, nom court',
MQTT_ENTITY_FORMAT: 'Entity ID format', // TODO translate
MQTT_ENTITY_FORMAT_0: 'Single instance, long name (v3.4)', // TODO translate
MQTT_ENTITY_FORMAT_1: 'Single instance, short name', // TODO translate
MQTT_ENTITY_FORMAT_2: 'Multiple instances, short name', // TODO translate
MQTT_CLEAN_SESSION: 'Flag Clean Session',
MQTT_RETAIN_FLAG: 'Toujours activer le Retain Flag',
INACTIVE: 'Inactif',
@@ -261,7 +260,7 @@ const fr: Translation = {
NETWORK_SCANNER: 'Scan réseau',
NETWORK_NO_WIFI: 'Pas de réseau WiFi trouvé',
NETWORK_BLANK_SSID: 'laisser vide pour désactiver le WiFi',
NETWORK_BLANK_BSSID: 'laisser vide pour utiliser uniquement le SSID',
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
TX_POWER: 'Puissance Tx',
HOSTNAME: "Nom d'hôte",
NETWORK_DISABLE_SLEEP: 'Désactiver le mode veille du WiFi',
@@ -281,78 +280,79 @@ const fr: Translation = {
ENTITY: 'entité',
MIN: 'min',
MAX: 'max',
BLOCK_NAVIGATE_1: 'Vous avez des modifications non enregistrées',
BLOCK_NAVIGATE_2: 'Si vous naviguez vers une autre page, vos modifications non enregistrées seront perdues. Êtes-vous sûr de vouloir quitter cette page ?',
STAY: 'Rester',
LEAVE: 'Quitter',
SCHEDULER: 'Scheduler',
SCHEDULER_HELP_1: 'Automatiser les commandes en ajoutant des événements programmés ci-dessous. Définissez un nom unique pour activer/désactiver l\'activation via API/MQTT',
SCHEDULER_HELP_2: 'Utiliser 00:00 pour déclencher une fois au démarrage',
SCHEDULE: 'Programme',
TIME: 'Temps',
TIMER: 'Minuteur',
ONCHANGE: 'Sur le changement',
CONDITION: 'Condition',
IMMEDIATE: 'Immédiat',
SCHEDULE_UPDATED: 'Programme mis à jour',
SCHEDULE_TIMER_1: 'au démarrage',
SCHEDULE_TIMER_2: 'toutes les minutes',
SCHEDULE_TIMER_3: 'toutes les heures',
CUSTOM_ENTITIES: 'Entités personnalisées',
ENTITIES_HELP_1: 'Récupérer les entités personnalisées du bus EMS',
ENTITIES_UPDATED: 'Entités mises à jour',
WRITEABLE: 'Écriture',
SHOWING: 'Affichage',
SEARCH: 'Rechercher',
CERT: 'Certificat racine TLS (laisser vide pour l\'insecurité)',
BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate
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
STAY: 'Stay', // TODO translate
LEAVE: 'Leave', // TODO translate
SCHEDULER: 'Scheduler', // TODO translate
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_2: 'Use 00:00 to trigger once on start-up', // TODO translate
SCHEDULE: 'Schedule', // TODO translate
TIME: 'Time', // TODO translate
TIMER: 'Timer', // TODO translate
ONCHANGE: 'Sur le changement', // TODO translate
CONDITION: 'Condition', // TODO translate
IMMEDIATE: 'Immédiate', // TODO translate
SCHEDULE_UPDATED: 'Schedule updated', // TODO translate
SCHEDULE_TIMER_1: 'on startup', // TODO translate
SCHEDULE_TIMER_2: 'every minute', // TODO translate
SCHEDULE_TIMER_3: 'every hour', // TODO translate
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
WRITEABLE: 'Writeable', // TODO translate
SHOWING: 'Showing', // TODO translate
SEARCH: 'Search', // TODO translate
CERT: 'TLS root certificate (leave blank for insecure)', // TODO translate
ENABLE_TLS: 'Activer TLS',
ON: 'On',
OFF: 'Off',
POLARITY: 'Polarity',
ACTIVEHIGH: 'Actif haut',
ACTIVELOW: 'Actif bas',
UNCHANGED: 'Inchangé',
ALWAYS: 'Toujours',
ACTIVITY: 'Activité',
CONFIGURE: 'Configurer {0}',
SYSTEM_MEMORY: 'Mémoire système',
APPLICATION_SETTINGS_1: 'Modifier les paramètres de l\'application EMS-ESP',
SECURITY_1: 'Ajouter ou supprimer des utilisateurs',
DOWNLOAD_UPLOAD_1: 'Télécharger et mettre à jour les paramètres et le firmware',
MODULES: 'Module',
ON: 'On', // TODO translate
OFF: 'Off', // TODO translate
POLARITY: 'Polarity', // TODO translate
ACTIVEHIGH: 'Active High', // TODO translate
ACTIVELOW: 'Active Low', // TODO translate
UNCHANGED: 'Unchanged', // TODO translate
ALWAYS: 'Always', // TODO translate
ACTIVITY: 'Activity', // TODO translate
CONFIGURE: 'Configure {0}', // TODO translate
SYSTEM_MEMORY: 'System Memory', // TODO translate
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
MODULES: 'Module', // TODO translate
MODULES_1: 'Activer ou désactiver les modules externes',
MODULES_UPDATED: 'Modules mis à jour',
MODULES_DESCRIPTION: 'Cliquer sur le module pour activer ou désactiver les modules EMS-ESP',
MODULES_NONE: 'Aucun module externe détecté',
RENAME: 'Renommer',
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate
ENABLE_MODBUS: 'Activer Modbus',
VIEW_LOG: 'Voir le journal pour diagnostiquer les problèmes',
UPLOAD_DRAG: 'glisser-déposer un fichier ici ou cliquer pour en sélectionner un',
SERVICES: 'Services',
ALLVALUES: 'Toutes les valeurs',
SPECIAL_FUNCTIONS: 'Fonctions spéciales',
WAIT_FIRMWARE: 'Firmware en cours de téléchargement et d\'installation',
INSTALL_VERSION: 'Cela va {0} la version {1}. Êtes-vous sûr ?',
UPDATE_AVAILABLE: 'mise à jour disponible',
LATEST_VERSION: 'Vous utilisez la dernière version {0} du firmware',
PLEASE_WAIT: 'Veuillez patienter',
RESTARTING_PRE: 'Initialisation',
RESTARTING_POST: 'Préparation',
AUTO_SCROLL: 'Défilement automatique',
DASHBOARD: 'Tableau de bord',
DEVELOPER_MODE: 'Mode développeur',
BYTES: 'Octets',
BITMASK: 'Masque de bits',
DUPLICATE: 'Dupliquer',
DASHBOARD_1: 'Toutes les entités EMS actives et marquées comme favoris, plus toutes les entités personnalisées, les programmes et les données des capteurs externes sont affichées ci-dessous.',
NO_DATA_1: 'Aucune entité EMS favorite trouvée. Utilisez le',
NO_DATA_2: 'module pour les marquer.',
NO_DATA_3: 'Pour voir toutes les entités disponibles, aller à',
THIS_VERSION: 'Cette version',
PLATFORM: 'Plateforme',
RELEASE_TYPE: 'Type de version',
INTERNET_CONNECTION_REQUIRED: 'Connexion Internet requise pour la vérification automatique des versions et la mise à niveau',
SWITCH_RELEASE_TYPE: 'Passer à la version {0}'
VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values', // TODO translate
SPECIAL_FUNCTIONS: 'Special Functions',
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
DEVELOPER_MODE: 'Developer Mode', // TODO translate
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicate', // TODO translate
UPGRADE: 'Upgrade', // TODO translate
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
NO_DATA_2: 'module to mark them.', // TODO translate
NO_DATA_3: 'To see all available entities go to', // TODO translate
THIS_VERSION: 'This Version', // TODO translate
PLATFORM: 'Platform', // TODO translate
RELEASE_TYPE: 'Release Type', // TODO translate
REINSTALL: 'Re-install', // TODO translate
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
};
export default fr;

View File

@@ -72,7 +72,7 @@ const it: Translation = {
TX_ISSUES: 'Problema di Tx - prova una modalità differente',
DISCONNECTED: 'Disconnesso',
EMS_SCAN: 'Sei sicuro di voler iniziare una scansione completa del bus EMS ?',
DATA_TRAFFIC: 'Traffico dati',
DATA_TRAFFIC: 'Data Traffic', // TODO translate
EMS_DEVICE: 'Dispositivo EMS ',
SUCCESS: 'SUCCESSO',
FAIL: 'FALLITO',
@@ -115,7 +115,7 @@ const it: Translation = {
READONLY: 'Abilita modalità sola-lettura (blocca tutti i comandi di scrittura EMS Tx in uscita)',
UNDERCLOCK_CPU: 'Abbassa velocità della CPU',
REMOTE_TIMEOUT: 'Remote timeout',
REMOTE_TIMEOUT_EN: 'Disabilitare il telecomando in caso di temperatura ambiente mancante',
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature', // TODO translate
HEATINGOFF: 'Avviamento caldaia con riscaldamento forzato spento',
MIN_DURATION: 'Wait time',
ENABLE_SHOWER_TIMER: 'Abilita timer doccia',
@@ -162,7 +162,6 @@ const it: Translation = {
UPLOAD: 'Carica',
DOWNLOAD: 'Scarica',
INSTALL: 'Installare {0}',
REINSTALL: 'Riavviare',
ABORTED: 'Annullato',
FAILED: 'Fallito',
SUCCESSFUL: 'Riuscito',
@@ -175,8 +174,8 @@ const it: Translation = {
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??',
AVAILABLE_VERSION: 'Versioni disponibili',
STABLE: 'Stabile',
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
STABLE: 'Stable', // TODO translate
DEVELOPMENT: 'Sviluppo',
UPTIME: 'Tempo di attività del sistema',
FREE_MEMORY: 'Free Memory',
@@ -185,10 +184,10 @@ const it: Translation = {
APPSIZE: 'Applicazione (Partizione: Usata / Libera)',
FILESYSTEM: 'Memoria Sistema (Usata / Libera)',
BUFFER_SIZE: 'Max Buffer Size',
COMPACT: 'Compatto',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings',
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here',
COMPACT: 'Compact',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Errore Inaspettato, prego tenta ancora',
TIME_SET: 'Imposta Ora',
MANAGE_USERS: 'Gestione Utenti',
@@ -261,7 +260,7 @@ const it: Translation = {
NETWORK_SCANNER: 'Scansione Rete',
NETWORK_NO_WIFI: 'Nessuana rete WiFi trovata',
NETWORK_BLANK_SSID: 'lasciare vuoto per disattivare WiFi',
NETWORK_BLANK_BSSID: 'lasciare vuoto per usare solo SSID',
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
TX_POWER: 'Potenza Tx',
HOSTNAME: 'Nome ospite',
NETWORK_DISABLE_SLEEP: 'Disabilita la modalità sospensione Wi-Fi',
@@ -304,55 +303,56 @@ const it: Translation = {
WRITEABLE: 'Scrivibile',
SHOWING: 'Visualizza',
SEARCH: 'Ricerca',
CERT: 'Certificato radice TLS (lasciare vuoto per l\'insecure)',
CERT: 'TLS root certificate (leave blank for insecure)', // TODO translate
ENABLE_TLS: 'Abilita TLS',
ON: 'On',
OFF: 'Off',
POLARITY: 'Polarity',
ACTIVEHIGH: 'Active High',
ACTIVELOW: 'Active Low',
UNCHANGED: 'Unchanged',
ALWAYS: 'Always',
ACTIVITY: 'Activity',
CONFIGURE: 'Configure {0}',
SYSTEM_MEMORY: 'System Memory',
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings',
SECURITY_1: 'Add or remove users',
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware',
MODULES: 'Module',
MODULES_1: 'Attiva o disattiva i moduli esterni',
MODULES_UPDATED: 'Modules updated',
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules',
MODULES_NONE: 'No external modules detected',
RENAME: 'Rename',
ON: 'On', // TODO translate
OFF: 'Off', // TODO translate
POLARITY: 'Polarity', // TODO translate
ACTIVEHIGH: 'Active High', // TODO translate
ACTIVELOW: 'Active Low', // TODO translate
UNCHANGED: 'Unchanged', // TODO translate
ALWAYS: 'Always', // TODO translate
ACTIVITY: 'Activity', // TODO translate
CONFIGURE: 'Configure {0}', // TODO translate
SYSTEM_MEMORY: 'System Memory', // TODO translate
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
MODULES: 'Module', // TODO translate
MODULES_1: 'Attiva o disattiva i moduli esterni', // TODO translate
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate
ENABLE_MODBUS: 'Abilita Modbus',
VIEW_LOG: 'Visualizza log per diagnosticare problemi',
UPLOAD_DRAG: 'trascina e rilascia un file qui o clicca per selezionare uno',
SERVICES: 'Servizi',
ALLVALUES: 'Tutti i valori',
SPECIAL_FUNCTIONS: 'Funzioni speciali',
WAIT_FIRMWARE: 'Firmware è in upload e installazione',
INSTALL_VERSION: 'Questo installerà la versione {1} {0}. Sei sicuro?',
UPDATE_AVAILABLE: 'aggiornamento disponibile',
LATEST_VERSION: 'Stai usando la versione più recente del firmware {0}',
PLEASE_WAIT: 'Attendere',
RESTARTING_PRE: 'Inizializzazione',
RESTARTING_POST: 'Preparazione',
AUTO_SCROLL: 'Scorrimento automatico',
DASHBOARD: 'Pannello di controllo',
DEVELOPER_MODE: 'Modalità sviluppatore',
BYTES: 'Byte',
BITMASK: 'Bitmask',
DUPLICATE: 'Duplicato',
DASHBOARD_1: 'Tutte le entità EMS che sono attive e marcate come preferite, più tutte le entità personalizzate, piani di programmazione e dati dei sensori esterni sono visualizzati di seguito.',
NO_DATA_1: 'Nessuna entità EMS preferita trovata. Usa il',
NO_DATA_2: 'modulo per marcarle.',
NO_DATA_3: 'Per vedere tutte le entità disponibili vai a',
THIS_VERSION: 'Questa versione',
PLATFORM: 'Piattaforma',
RELEASE_TYPE: 'Tipo di rilascio',
INTERNET_CONNECTION_REQUIRED: 'Connessione internet richiesta per il controllo automatico delle versioni e l\'aggiornamento',
SWITCH_RELEASE_TYPE: 'Cambia in {0} rilascio'
VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values', // TODO translate
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
DEVELOPER_MODE: 'Developer Mode', // TODO translate
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicate', // TODO translate
UPGRADE: 'Upgrade', // TODO translate
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
NO_DATA_2: 'module to mark them.', // TODO translate
NO_DATA_3: 'To see all available entities go to', // TODO translate
THIS_VERSION: 'This Version', // TODO translate
PLATFORM: 'Platform', // TODO translate
RELEASE_TYPE: 'Release Type', // TODO translate
REINSTALL: 'Re-install', // TODO translate
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
};
export default it;

View File

@@ -162,7 +162,6 @@ const nl: Translation = {
UPLOAD: 'Upload',
DOWNLOAD: '{{D|d|d}}ownload',
INSTALL: 'Installeren',
REINSTALL: 'Opnieuw installeren',
ABORTED: 'afgebroken',
FAILED: 'mislukt',
SUCCESSFUL: 'successvol',
@@ -253,13 +252,13 @@ const nl: Translation = {
AP_CLIENTS: 'AP Clients',
AP_MAX_CLIENTS: 'Max Clients',
AP_LOCAL_IP: 'Local IP',
NETWORK_SCAN: 'Scan WiFi Netwerken',
NETWORK_SCAN: 'Scan WiFi Networken',
IDLE: 'Idle',
LOST: 'Verloren',
SCANNING: 'Scannen',
SCAN_AGAIN: 'Opnieuw scannen',
NETWORK_SCANNER: 'Netwerk Scanner',
NETWORK_NO_WIFI: 'Geen WiFi netwerken gevonden',
NETWORK_NO_WIFI: 'Geen WiFi networken gevonden',
NETWORK_BLANK_SSID: 'laat leeg om WiFi uit te schakelen',
NETWORK_BLANK_BSSID: 'laat leeg om alleen SSID te bebruiken',
TX_POWER: 'Tx Vermogen',
@@ -274,7 +273,7 @@ const nl: Translation = {
NETWORK_SUBNET: 'Subnetmasker',
NETWORK_DNS: 'DNS Servers',
ADDRESS_OF: '{0} Address',
ADMINISTRATOR: 'Administrator',
ADMINISTRATOR: 'Administrator',
GUEST: 'Gast',
NEW: 'Nieuwe',
NEW_NAME_OF: 'Hernoem {0}',
@@ -321,7 +320,7 @@ const nl: Translation = {
DOWNLOAD_UPLOAD_1: 'Download en upload instellingen en firmware',
MODULES: 'Module',
MODULES_1: 'Externe modules activeren of deactiveren',
MODULES_UPDATED: 'Modules geüpdatet',
MODULES_UPDATED: 'Modules geüpdatet',
MODULES_DESCRIPTION: 'Klik op de module om EMS-ESP library modules te activeren of te deactiveren',
MODULES_NONE: 'Geen externe modules gedetecteerd',
RENAME: 'Hernoemen',
@@ -332,18 +331,19 @@ const nl: Translation = {
ALLVALUES: 'All waarden',
SPECIAL_FUNCTIONS: 'Speciale functies',
WAIT_FIRMWARE: 'Firmware wordt geüpload en geïnstalleerd',
INSTALL_VERSION: 'Hiermee wordt versie {1} {0}. Weet je het zeker?',
UPDATE_AVAILABLE: 'update beschikbaar',
LATEST_VERSION: 'U gebruikt de nieuwste {0} firmwareversie',
INSTALL_VERSION: 'Hiermee wordt versie {0} geïnstalleerd. Weet je het zeker?',
UPGRADE_AVAILABLE: 'Er is een firmware-upgrade beschikbaar!',
LATEST_VERSION: 'U gebruikt de nieuwste firmwareversie',
PLEASE_WAIT: 'Een ogenblik geduld',
RESTARTING_PRE: 'Initialiseren',
RESTARTING_POST: 'Voorbereiding',
AUTO_SCROLL: 'Automatisch Scrollen',
DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Ontwikkelaarsmodus',
BYTES: 'Bytes',
BITMASK: 'Bit Mask',
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicaat',
UPGRADE: 'Upgraden',
DASHBOARD_1: 'Alle EMS-entiteiten die actief zijn en als favoriet zijn gemarkeerd, plus alle aangepaste entiteiten en externe sensorgegevens worden hieronder weergegeven.',
NO_DATA_1: 'Er zijn nog geen favoriete EMS-entiteiten gevonden. Gebruik de',
NO_DATA_2: 'module om ze te markeren.',
@@ -351,8 +351,8 @@ const nl: Translation = {
THIS_VERSION: 'Deze Versie',
PLATFORM: 'Platform',
RELEASE_TYPE: 'Release Typ',
REINSTALL: 'Opnieuw Installeren',
INTERNET_CONNECTION_REQUIRED: 'Internetverbinding vereist voor automatische versiecontrole en -upgrade',
SWITCH_RELEASE_TYPE: 'Switch naar {0} release'
};
export default nl;

View File

@@ -43,7 +43,7 @@ const no: Translation = {
RESET: 'Nullstill',
APPLY_CHANGES: 'Utfør endringer({0})',
UPDATE: 'Oppdater',
EXECUTE: 'Utfør',
EXECUTE: 'Execute', // TODO translate
REMOVE: 'Fjern',
PROBLEM_UPDATING: 'Problem med oppdatering',
PROBLEM_LOADING: 'Problem med opplasting',
@@ -72,7 +72,7 @@ const no: Translation = {
TX_ISSUES: 'Tx problemer - prøv en annen Tx Modus',
DISCONNECTED: 'Frakoblet',
EMS_SCAN: 'Er du sikker på du vil starte full søking av EMS bussen?',
DATA_TRAFFIC: 'Data trafikk',
DATA_TRAFFIC: 'Data Traffic', // TODO translate
EMS_DEVICE: 'EMS Enhet',
SUCCESS: 'VELLYKKET',
FAIL: 'MISLYKKET',
@@ -115,9 +115,9 @@ const no: Translation = {
READONLY: 'Aktiver read-only modus (blokker all EMS Tx Skriving)',
UNDERCLOCK_CPU: 'Underklokking av prosessorhastighet',
REMOTE_TIMEOUT: 'Remote timeout',
REMOTE_TIMEOUT_EN: 'Deaktiver fjernstyring på manglende romtemperatur',
HEATINGOFF: 'Start kjele med tvingt oppvarming av',
MIN_DURATION: 'Ventetid',
REMOTE_TIMEOUT_EN: 'Disable remote control on missing room temperature', // TODO translate
HEATINGOFF: 'Start boiler with forced heating off', // TODO translate
MIN_DURATION: 'Wait time',
ENABLE_SHOWER_TIMER: 'Aktiver Dusjtimer',
ENABLE_SHOWER_ALERT: 'Aktiver Dusj-varsling',
TRIGGER_TIME: 'Aktiveringstid',
@@ -162,7 +162,6 @@ const no: Translation = {
UPLOAD: 'Opplasning',
DOWNLOAD: '{{N|n|n}}edlasting',
INSTALL: 'Installer',
REINSTALL: 'Ominstaller',
ABORTED: 'avbrutt',
FAILED: 'feilet',
SUCCESSFUL: 'vellykket',
@@ -175,9 +174,9 @@ const no: Translation = {
FACTORY_RESET: 'Sett tilbake til fabrikkinstilling',
SYSTEM_FACTORY_TEXT: 'Enhet har blitt satt tilbake til fabrikkinstilling og vil restarte',
SYSTEM_FACTORY_TEXT_DIALOG: 'Er du sikker på at du vil resette enheten til fabrikkinstillinger?',
AVAILABLE_VERSION: 'Tilgjengelige versjoner',
STABLE: 'Stabil',
DEVELOPMENT: 'Utvikling',
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
STABLE: 'Stable', // TODO translate
DEVELOPMENT: 'Development',
UPTIME: 'System Oppetid',
FREE_MEMORY: 'Ledig Memory',
PSRAM: 'PSRAM (Størrelse / Ledig)',
@@ -186,9 +185,9 @@ const no: Translation = {
FILESYSTEM: 'File System (Brukt / Ledig)',
BUFFER_SIZE: 'Max Buffer Størrelse',
COMPACT: 'Komprimere',
DOWNLOAD_SETTINGS_TEXT: 'Lag en sikkerhetskopi av dine konfigurasjon og innstillinger',
UPLOAD_TEXT: 'Last opp en ny firmware fil (.bin) eller en sikkerhetskopi fil (.json)',
UPLOAD_DROP_TEXT: 'Dropp en firmware fil (.bin) eller klikk her',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Ukjent feil, prøv igjen',
TIME_SET: 'Still in tid',
MANAGE_USERS: 'Administrer Brukere',
@@ -224,7 +223,7 @@ const no: Translation = {
MQTT_INT_THERMOSTATS: 'Termostat',
MQTT_INT_SOLAR: 'Solpaneler',
MQTT_INT_MIXER: 'Blandeventil',
MQTT_INT_WATER: 'Vannmoduler',
MQTT_INT_WATER: 'Water Modules', // TODO translate
MQTT_QUEUE: 'MQTT Queue',
DEFAULT: 'Standard',
MQTT_ENTITY_FORMAT: 'Enhets ID format',
@@ -261,7 +260,7 @@ const no: Translation = {
NETWORK_SCANNER: 'Nettverk Scanner',
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_BSSID: 'la feltet være blankt for å bruke kun SSID',
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
TX_POWER: 'Tx Effekt',
HOSTNAME: 'Hostname',
NETWORK_DISABLE_SLEEP: 'Hindre at trådløst nettverk går i Sleep Mode',
@@ -298,61 +297,62 @@ const no: Translation = {
SCHEDULE_TIMER_1: 'ved oppstart',
SCHEDULE_TIMER_2: 'hvert minutt',
SCHEDULE_TIMER_3: 'hver time',
CUSTOM_ENTITIES: 'Personlige entiteter',
ENTITIES_HELP_1: 'Hent personlige entiteter fra EMS bussen',
ENTITIES_UPDATED: 'Entiteter oppdatert',
WRITEABLE: 'Skrivbar',
SHOWING: 'Viser',
SEARCH: 'Søk',
CERT: 'TLS rot sertifikat (la feltet stå blankt for usikkert)',
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
WRITEABLE: 'Writeable', // TODO translate
SHOWING: 'Showing', // TODO translate
SEARCH: 'Search', // TODO translate
CERT: 'TLS root certificate (leave blank for insecure)', // TODO translate
ENABLE_TLS: 'Aktiviser TLS',
ON: '',
OFF: 'Av',
POLARITY: 'Polarity',
ACTIVEHIGH: 'Active High',
ACTIVELOW: 'Active Low',
UNCHANGED: 'Unchanged',
ALWAYS: 'Always',
ACTIVITY: 'Aktivitet',
CONFIGURE: 'Konfigurer {0}',
SYSTEM_MEMORY: 'System Memory',
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings',
SECURITY_1: 'Add or remove users',
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware',
MODULES: 'Modul',
ON: 'On', // TODO translate
OFF: 'Off', // TODO translate
POLARITY: 'Polarity', // TODO translate
ACTIVEHIGH: 'Active High', // TODO translate
ACTIVELOW: 'Active Low', // TODO translate
UNCHANGED: 'Unchanged', // TODO translate
ALWAYS: 'Always', // TODO translate
ACTIVITY: 'Activity', // TODO translate
CONFIGURE: 'Configure {0}', // TODO translate
SYSTEM_MEMORY: 'System Memory', // TODO translate
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
MODULES: 'Module', // TODO translate
MODULES_1: 'Aktiver eller deaktiver eksterne moduler',
MODULES_UPDATED: 'Moduler oppdatert',
MODULES_DESCRIPTION: 'Klikk på modulen for å aktivere eller deaktivere EMS-ESP biblioteksmoduler',
MODULES_NONE: 'Ingen eksterne moduler funnet',
RENAME: 'Gi nytt navn',
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate
ENABLE_MODBUS: 'Aktiver Modbus',
VIEW_LOG: 'Se logg for å diagnostisere problemer',
UPLOAD_DRAG: 'dra og slippe en fil her eller klikk for å velge en',
SERVICES: 'Tjenester',
ALLVALUES: 'Alle verdier',
SPECIAL_FUNCTIONS: 'Spesielle funksjoner',
WAIT_FIRMWARE: 'Firmware er i opplasting og installasjon',
INSTALL_VERSION: 'Dette vil {0} versjon {1}. Er du sikker?',
UPDATE_AVAILABLE: 'oppdatering tilgjengelig',
LATEST_VERSION: 'Du bruker den nyeste {0} firmware versjonen',
PLEASE_WAIT: 'Vennligst vent',
RESTARTING_PRE: 'Initialiserer',
RESTARTING_POST: 'Forbereder',
AUTO_SCROLL: 'Automatisk rulling',
DASHBOARD: 'Dashboard',
DEVELOPER_MODE: 'Utvikler modus',
BYTES: 'Bytes',
BITMASK: 'Bitmask',
DUPLICATE: 'Duplikat',
DASHBOARD_1: 'Alle EMS enheter som er aktive og merket som favoritt, pluss alle personlige enheter, planlegg og eksterne sensor data er vist nedenfor.',
NO_DATA_1: 'Ingen favoritte EMS enheter funnet enda. Bruk',
NO_DATA_2: 'modul for å markere dem.',
NO_DATA_3: 'For å se alle tilgjengelige enheter, gå til',
THIS_VERSION: 'Denne versjonen',
PLATFORM: 'Plattform',
RELEASE_TYPE: 'Utgivelses type',
INTERNET_CONNECTION_REQUIRED: 'Internettilkobling kreves for automatisk versjonskontroll og oppgradering',
SWITCH_RELEASE_TYPE: 'Bytt til {0} utgivelse'
VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values', // TODO translate
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
DEVELOPER_MODE: 'Developer Mode', // TODO translate
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicate', // TODO translate
UPGRADE: 'Upgrade', // TODO translate
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
NO_DATA_2: 'module to mark them.', // TODO translate
NO_DATA_3: 'To see all available entities go to', // TODO translate
THIS_VERSION: 'This Version', // TODO translate
PLATFORM: 'Platform', // TODO translate
RELEASE_TYPE: 'Release Type', // TODO translate
REINSTALL: 'Re-install', // TODO translate
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading',
};
export default no;

View File

@@ -72,7 +72,7 @@ const pl: BaseTranslation = {
TX_ISSUES: 'problem z zapisem na magistralę EMS, spróbuj wybrać inny "Tryb transmisji (Tx)"',
DISCONNECTED: 'brak połączenia',
EMS_SCAN: 'Czy na pewno wykonać pełne skanowanie magistrali EMS?',
DATA_TRAFFIC: 'Ruch Danych',
DATA_TRAFFIC: 'Data Traffic', // TODO translate
EMS_DEVICE: 'Urządzenie EMS',
SUCCESS: 'Udane',
FAIL: 'Nieudane',
@@ -115,7 +115,7 @@ const pl: BaseTranslation = {
READONLY: 'Tryb pracy "tylko do odczytu" (blokuje wszystkie komendy zapisu na magistralę EMS)',
UNDERCLOCK_CPU: 'Obniż taktowanie CPU',
REMOTE_TIMEOUT: 'Remote timeout',
REMOTE_TIMEOUT_EN: 'Wyłącz kontrolę zdalną przy braku temperatury pomieszczenia',
REMOTE_TIMEOUT_EN: 'Disable remote control on missing room temperature', // TODO translate
HEATINGOFF: 'Uruchom kocioł z wymuszonym wyłączonym grzaniem',
MIN_DURATION: 'Wait time',
ENABLE_SHOWER_TIMER: 'Aktywuj minutnik prysznica',
@@ -162,7 +162,6 @@ const pl: BaseTranslation = {
UPLOAD: 'Wysyłanie',
DOWNLOAD: '{{P|p||P}}obier{{anie|z||z}}',
INSTALL: 'Zainstalować',
REINSTALL: 'Zainstalować ponownie',
ABORTED: 'zostało przerwane!',
FAILED: 'nie powiodł{{o|a|}} się!',
SUCCESSFUL: 'powiodło się.',
@@ -175,9 +174,9 @@ const pl: BaseTranslation = {
FACTORY_RESET: 'Ustawienia fabryczne',
SYSTEM_FACTORY_TEXT: 'Interfejs EMS-ESP został przywrócony do ustawień fabrycznych i zostanie teraz ponownie uruchomiony.',
SYSTEM_FACTORY_TEXT_DIALOG: 'Na pewno chcesz przywrócić ustawienia fabryczne interfejsu EMS-ESP?',
AVAILABLE_VERSION: 'Najnowsze dostępne wersje',
STABLE: 'Stabilna',
DEVELOPMENT: 'Testowa',
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
STABLE: 'Stable', // TODO translate
DEVELOPMENT: 'Testowe',
UPTIME: 'Czas działania systemu',
FREE_MEMORY: 'Wolne Memory',
PSRAM: 'PSRAM (rozmiar / wolne)',
@@ -186,9 +185,9 @@ const pl: BaseTranslation = {
FILESYSTEM: 'System plików (wykorzystane / wolne)',
BUFFER_SIZE: 'Maksymalna pojemność bufora (ilość wpisów)',
COMPACT: 'Kompaktowy',
DOWNLOAD_SETTINGS_TEXT: 'Utwórz kopię swoich ustawień i konfiguracji',
UPLOAD_TEXT: 'Wgraj nowy plik firmware (.bin) lub kopię ustawień (.json)',
UPLOAD_DROP_TEXT: 'Upuść plik firmware .bin lub kliknij tutaj',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
TIME_SET: 'Zegar został ustawiony.',
MANAGE_USERS: 'Zarządzanie użytkownikami',
@@ -319,40 +318,41 @@ const pl: BaseTranslation = {
APPLICATION_SETTINGS_1: 'Modyfikacja ustawień aplikacji EMS-ESP',
SECURITY_1: 'Dodawanie i usuwanie użytkowników',
DOWNLOAD_UPLOAD_1: 'Pobieranie/wysyłanie ustawień i firmware',
MODULES: 'Moduł',
MODULES: 'Module', // TODO translate
MODULES_1: 'Aktywuj lub dezaktywuj moduły zewnętrzne',
MODULES_UPDATED: 'Zaktualizowano moduły',
MODULES_DESCRIPTION: 'Kliknij na moduł aby aktywować lub dezaktywować bibliotekę modułów EMS-ESP',
MODULES_NONE: 'Brak wykrytych modułów zewnętrznych',
RENAME: 'Zmień nazwę',
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate
ENABLE_MODBUS: 'Aktywuj Modbus',
VIEW_LOG: 'Zdiagnozuj problemy',
UPLOAD_DRAG: 'przeciągnij i upuść plik lub kliknij tutaj',
SERVICES: 'Usługi',
ALLVALUES: 'Wszystkie wartości',
SPECIAL_FUNCTIONS: 'Specjalne funkcje',
WAIT_FIRMWARE: 'Firma jest wysyłana i instaluje się',
INSTALL_VERSION: 'To zainstaluje wersję {1} {0}. Jesteś pewny?',
UPDATE_AVAILABLE: 'aktualizacja dostępna',
LATEST_VERSION: 'Jesteś używając najnowszej wersji firmware {0}',
PLEASE_WAIT: 'Proszę czekać',
RESTARTING_PRE: 'Inicjalizacja',
RESTARTING_POST: 'Przygotowanie',
AUTO_SCROLL: 'Auto Scroll',
DASHBOARD: 'Pulpit',
DEVELOPER_MODE: 'Tryb programisty',
BYTES: 'Bajty',
BITMASK: 'Bit Mask',
DUPLICATE: 'Duplicate',
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.',
NO_DATA_1: 'Brak ulubionych encji EMS. Użyj',
NO_DATA_2: 'moduł do ich oznaczenia.',
NO_DATA_3: 'Aby zobaczyć wszystkie dostępne encje przejdź do',
THIS_VERSION: 'Ta wersja',
PLATFORM: 'Platforma',
RELEASE_TYPE: 'Typ wydania',
INTERNET_CONNECTION_REQUIRED: 'Połączenie internetowe jest wymagane do automatycznej kontroli wersji i aktualizacji',
SWITCH_RELEASE_TYPE: 'Zmień na {0} wydanie'
VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values', // TODO translate
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version', // TODO translate
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
DEVELOPER_MODE: 'Developer Mode', // TODO translate
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicate', // TODO translate
UPGRADE: 'Upgrade', // TODO translate
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
NO_DATA_2: 'module to mark them.', // TODO translate
NO_DATA_3: 'To see all available entities go to', // TODO translate
THIS_VERSION: 'This Version', // TODO translate
PLATFORM: 'Platform', // TODO translate
RELEASE_TYPE: 'Release Type', // TODO translate
REINSTALL: 'Re-install', // TODO translate
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading', // TODO translate
};
export default pl;

View File

@@ -155,14 +155,13 @@ const sk: Translation = {
NAME: 'Názov',
CUSTOMIZATIONS_RESET: 'Naozaj chcete odstrániť všetky prispôsobenia vrátane vlastných nastavení snímačov teploty a analógových snímačov?',
SUPPORT_INFORMATION: 'Informácie pre podporu',
HELP_INFORMATION_1: 'Navštívte online wiki, kde nájdete pokyny na konfiguráciu EMS-ESP',
HELP_INFORMATION_1: 'Navštívte online wiki, kde nájdete pokyny na konfiguráciu EMS-ESP',
HELP_INFORMATION_2: 'Pre živý komunitný chat sa pripojte na náš Discord server',
HELP_INFORMATION_3: 'Ak chcete požiadať o funkciu alebo nahlásiť chybu',
HELP_INFORMATION_4: 'nezabudnite si stiahnuť a pripojiť informácie o vašom systéme, aby ste mohli rýchlejšie reagovať pri nahlasovaní problému',
UPLOAD: 'Nahrať',
DOWNLOAD: '{{S|s|s}}tiahnuť',
INSTALL: 'Inštalovať',
REINSTALL: 'Inštalovať znova',
ABORTED: 'zrušené',
FAILED: 'chybné',
SUCCESSFUL: 'úspešné',
@@ -188,7 +187,7 @@ const sk: Translation = {
COMPACT: 'Kompaktné',
DOWNLOAD_SETTINGS_TEXT: 'Vytvorte zálohu svojej konfigurácie a nastavení',
UPLOAD_TEXT: 'Nahrajte nový súbor firmvéru (.bin) alebo súbor zálohy (.json)',
UPLOAD_DROP_TEXT: 'Presuňte súbor .bin firmvéru alebo kliknite sem',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Neočakávaná chyba, prosím skúste to znova',
TIME_SET: 'Nastavený čas',
MANAGE_USERS: 'Správa používateľov',
@@ -324,7 +323,7 @@ const sk: Translation = {
MODULES_UPDATED: 'Aktualizované moduly',
MODULES_DESCRIPTION: 'Kliknutím na modul aktivujete alebo deaktivujete moduly knižnice EMS-ESP',
MODULES_NONE: 'Neboli zistené žiadne externé moduly',
RENAME: 'Premenovať',
RENAME: 'Premenovať',
ENABLE_MODBUS: 'Povoliť Modbus',
VIEW_LOG: 'Zobrazte log na diagnostiku problémov',
UPLOAD_DRAG: 'presuňte sem súbor alebo ho kliknutím vyberte',
@@ -332,18 +331,19 @@ const sk: Translation = {
ALLVALUES: 'Všetky hodnoty',
SPECIAL_FUNCTIONS: 'Špeciálne funkcie',
WAIT_FIRMWARE: 'Firmvér sa nahráva a inštaluje',
INSTALL_VERSION: 'Týmto sa {0} verzia {1}. Si si istý?',
UPDATE_AVAILABLE: 'dostupná aktualizácia',
LATEST_VERSION: 'Používate poslednú {0} verziu firmvéru',
INSTALL_VERSION: 'Týmto sa inštalovať verzia {0}. Si si istý?',
UPGRADE_AVAILABLE: 'K dispozícii je aktualizácia firmvéru!',
LATEST_VERSION: 'Používate poslednú verziu firmvéru',
PLEASE_WAIT: 'Čakajte prosím',
RESTARTING_PRE: 'Prebieha inicializácia',
RESTARTING_POST: 'Príprava',
AUTO_SCROLL: 'Automatické rolovanie',
DASHBOARD: 'Panel',
DEVELOPER_MODE: 'Režim vývojára',
BYTES: 'Bytov',
BITMASK: 'Bitová maska',
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicitné',
UPGRADE: 'Inovovať',
DASHBOARD_1: 'Všetky entity EMS, ktoré sú aktívne a označené ako obľúbené, plus všetky vlastné entity, plány a údaje externých senzorov sú zobrazené nižšie.',
NO_DATA_1: 'Nenašli sa žiadne obľúbené entity EMS. Použite',
NO_DATA_2: 'modul na ich označenie.',
@@ -351,8 +351,8 @@ const sk: Translation = {
THIS_VERSION: 'Táto verzia',
PLATFORM: 'Platforma',
RELEASE_TYPE: 'Typ vydania',
REINSTALL: 'Preinštalovať',
INTERNET_CONNECTION_REQUIRED: 'Internetové pripojenie je potrebné pre automatickú kontrolu a aktualizáciu',
SWITCH_RELEASE_TYPE: 'Prepnúť na {0} verziu'
};
export default sk;

View File

@@ -162,7 +162,6 @@ const sv: Translation = {
UPLOAD: 'Uppladdning',
DOWNLOAD: '{{N|n|n}}edladdning',
INSTALL: 'Installera',
REINSTALL: 'Återinstallera',
ABORTED: 'Avbruten',
FAILED: 'Misslyckades',
SUCCESSFUL: 'Lyckades',
@@ -188,7 +187,7 @@ const sv: Translation = {
COMPACT: 'Komprimerad',
DOWNLOAD_SETTINGS_TEXT: 'Skapa en säkerhetskopia av din konfiguration och inställningar',
UPLOAD_TEXT: 'Ladda upp en ny firmwarefil (.bin) eller en säkerhetskopiafil (.json)',
UPLOAD_DROP_TEXT: 'Droppa en firmware .bin fil eller klicka här',
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Okänt fel, var god försök igen',
TIME_SET: 'Ställ in tid',
MANAGE_USERS: 'Användare',
@@ -332,27 +331,28 @@ const sv: Translation = {
ALLVALUES: 'Alla värden',
SPECIAL_FUNCTIONS: 'Specialfunktioner',
WAIT_FIRMWARE: 'Firmware laddas upp och installeras',
INSTALL_VERSION: 'Det här kommer {0} version {1}. Är du säker?',
UPDATE_AVAILABLE: 'uppdatering tillgänglig',
LATEST_VERSION: 'Du använder den senaste {0} firmwareversionen.',
INSTALL_VERSION: 'Det här kommer installera version {0}. Är du säker?',
UPGRADE_AVAILABLE: 'Det finns en tillgänglig firmwareupgradering!',
LATEST_VERSION: 'Du använder den senaste firmwareversionen.',
PLEASE_WAIT: 'Var god vänta',
RESTARTING_PRE: 'Initialiserar',
RESTARTING_POST: 'Förbereder',
AUTO_SCROLL: 'Autoskrolla',
DASHBOARD: 'Kontrollpanel',
DEVELOPER_MODE: 'Utvecklarläge',
BYTES: 'Bytes',
BITMASK: 'Bitmask',
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Dublett',
DASHBOARD_1: 'Alla EMS-enheter som är aktiva och markerade som favorit, plus alla anpassade entiteter, scheman och externa sensor-data visas nedan.',
NO_DATA_1: 'Inga favorit EMS enheter hittade än. Använd',
NO_DATA_2: 'modul för att markera dem.',
NO_DATA_3: 'För att se alla tillgängliga enheter, gå till',
THIS_VERSION: 'Denna version',
PLATFORM: 'Plattform',
RELEASE_TYPE: 'Utgivelsestyp',
UPGRADE: 'Uppgradera',
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
NO_DATA_2: 'module to mark them.', // TODO translate
NO_DATA_3: 'To see all available entities go to', // TODO translate
THIS_VERSION: 'This Version', // TODO translate
PLATFORM: 'Platform', // TODO translate
RELEASE_TYPE: 'Release Type', // TODO translate
REINSTALL: 'Re-install', // TODO translate
INTERNET_CONNECTION_REQUIRED: 'Internetanslutning krävs för automatisk version kontroll och uppdatering',
SWITCH_RELEASE_TYPE: 'Byt till {0} utgåva'
};
export default sv;

View File

@@ -43,7 +43,7 @@ const tr: Translation = {
RESET: 'Reset',
APPLY_CHANGES: 'Apply Changes ({0})',
UPDATE: 'Update',
EXECUTE: 'Uygulamak',
EXECUTE: 'Execute', // TODO translate
REMOVE: 'Kaldır',
PROBLEM_UPDATING: 'Güncelleme Sorunu',
PROBLEM_LOADING: 'Yükleme Sorunu',
@@ -72,7 +72,7 @@ const tr: Translation = {
TX_ISSUES: 'Tx sorunu - başka bir Tx Modu deneyin',
DISCONNECTED: 'Bağlantı kesildi',
EMS_SCAN: 'EMS Hattında tam bir cihaz taraması başlatmak istediğinizden emin misiniz?',
DATA_TRAFFIC: 'Veri trafiği',
DATA_TRAFFIC: 'Data Traffic', // TODO translate
EMS_DEVICE: 'EMS Cihazı',
SUCCESS: 'BAŞARILI',
FAIL: 'HATA',
@@ -115,8 +115,8 @@ const tr: Translation = {
READONLY: 'Salt okunur modu devreye al (bütün giden EMS Tx Yazma komutlarını engeller)',
UNDERCLOCK_CPU: 'İşlemci hızını düşür',
REMOTE_TIMEOUT: 'Remote timeout',
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature',
HEATINGOFF: 'Start boiler with forced heating off',
REMOTE_TIMEOUT_EN: 'Disable remote on missing room temperature', // TODO translate
HEATINGOFF: 'Start boiler with forced heating off', // TODO translate
MIN_DURATION: 'Wait time',
ENABLE_SHOWER_TIMER: 'Duş Sayacını Devreye Al',
ENABLE_SHOWER_ALERT: 'Duş Alarmını Devreye Al',
@@ -162,7 +162,6 @@ const tr: Translation = {
UPLOAD: 'Yükleme',
DOWNLOAD: '{{İ|i|i}}İndirme',
INSTALL: 'Düzenlemek',
REINSTALL: 'Yeniden düzenlemek',
ABORTED: 'iptal edildi',
FAILED: 'başarısız',
SUCCESSFUL: 'başarılı',
@@ -175,8 +174,8 @@ const tr: Translation = {
FACTORY_RESET: 'Fabrika ayarına dönme',
SYSTEM_FACTORY_TEXT: 'Cihaz fabrika ayarlarına döndü ve şimdi yendiden başlatılacak',
SYSTEM_FACTORY_TEXT_DIALOG: 'Cihazı fabrika ayarlarına döndürmek istediğinize emin misiniz?',
AVAILABLE_VERSION: 'En son kullanılabilir sürümler',
STABLE: 'Kararlı',
AVAILABLE_VERSION: 'Latest Available Versions', // TODO translate
STABLE: 'Stable', // TODO translate
DEVELOPMENT: 'Geliştirme',
UPTIME: 'Sistem Çalışma Süresi',
FREE_MEMORY: 'Yığın Memory',
@@ -186,9 +185,9 @@ const tr: Translation = {
FILESYSTEM: 'Dosya Sistemi (Kullanılmış / Boş)',
BUFFER_SIZE: 'En fazla bellek boyutu',
COMPACT: 'Sıkışık',
DOWNLOAD_SETTINGS_TEXT: 'Yapılandırma ve ayarlarınızın yedekleme yapın',
UPLOAD_TEXT: 'Yeni bir firmware dosyası (.bin) veya yedek dosyası (.json) yükle',
UPLOAD_DROP_TEXT: 'Bir firmware .bin dosyası veya buraya tıklayın',
DOWNLOAD_SETTINGS_TEXT: 'Create a backup of your configuration and settings', // TODO translate
UPLOAD_TEXT: 'Upload a new firmware file (.bin) or a backup file (.json)', // TODO translate
UPLOAD_DROP_TEXT: 'Drop a firmware .bin file or click here', // TODO translate
ERROR: 'Beklenemedik hata, lütfen tekrar deneyin.',
TIME_SET: 'Zaman ayarı',
MANAGE_USERS: 'Kullanıcıları yönet',
@@ -224,7 +223,7 @@ const tr: Translation = {
MQTT_INT_THERMOSTATS: 'Termostatlar',
MQTT_INT_SOLAR: 'Güneş Enerjisi Modülleri',
MQTT_INT_MIXER: 'Karışım Modülleri',
MQTT_INT_WATER: 'Su Modülleri',
MQTT_INT_WATER: 'Water Modules', // TODO translate
MQTT_QUEUE: 'MQTT Sırası',
DEFAULT: 'Varsayılan',
MQTT_ENTITY_FORMAT: 'Varlık Kimlik biçimi',
@@ -261,7 +260,7 @@ const tr: Translation = {
NETWORK_SCANNER: 'Ağ Tarayıcısı',
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_BSSID: 'sadece SSID kullanmak için boş bırakın',
NETWORK_BLANK_BSSID: 'leave blank to use only SSID', // TODO translate
TX_POWER: 'Aktarım gücü',
HOSTNAME: 'Ana Makine Adı',
NETWORK_DISABLE_SLEEP: 'Kablosuz uyku modunu devre dışına al',
@@ -281,78 +280,79 @@ const tr: Translation = {
ENTITY: 'varlık',
MIN: 'min',
MAX: 'maks',
BLOCK_NAVIGATE_1: 'Değiştirilmişleriniz var',
BLOCK_NAVIGATE_2: 'Farklı bir sayfaya geçerseniz değiştirilmişleriniz kaybolacak. Bu sayfadan çıkmak istediğinize emin misiniz?',
STAY: 'Kal',
LEAVE: 'Çık',
SCHEDULER: 'Zamanlayıcı',
SCHEDULER_HELP_1: 'Komutları zamanlayarak otomatikleştirin. Benzersiz bir ad belirtin API/MQTT aracılığıyla etkinleştirmek/devre dışı bırakma',
SCHEDULER_HELP_2: 'Başlangıçta bir kere tetiklemek için 00:00 kullanın',
SCHEDULE: 'Zamanlama',
TIME: 'Zaman',
TIMER: 'Zamanlayıcı',
BLOCK_NAVIGATE_1: 'You have unsaved changes', // TODO translate
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
STAY: 'Stay', // TODO translate
LEAVE: 'Leave', // TODO translate
SCHEDULER: 'Scheduler', // TODO translate
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_2: 'Use 00:00 to trigger once on start-up', // TODO translate
SCHEDULE: 'Schedule', // TODO translate
TIME: 'Time', // TODO translate
TIMER: 'Timer', // TODO translate
ONCHANGE: 'Değişimde',
CONDITION: 'Durum',
IMMEDIATE: 'hemen',
SCHEDULE_UPDATED: 'Zamanlama güncellendi',
SCHEDULE_TIMER_1: 'Başlangıçta',
SCHEDULE_TIMER_2: 'her dakikada',
SCHEDULE_TIMER_3: 'her saatte',
CUSTOM_ENTITIES: 'Özel Varlıklar',
ENTITIES_HELP_1: 'Özel varlıkları EMS hattından alın',
ENTITIES_UPDATED: 'Varlıklar güncellendi',
WRITEABLE: 'Yazılabilir',
SHOWING: 'Görüntüleniyor',
SEARCH: 'Ara',
CERT: 'TLS kök sertifikası (güvenli olmayan için boş bırakın)',
CONDITION: 'Durum', // TODO translate
IMMEDIATE: 'hemen', // TODO translate
SCHEDULE_UPDATED: 'Schedule updated', // TODO translate
SCHEDULE_TIMER_1: 'on startup', // TODO translate
SCHEDULE_TIMER_2: 'every minute', // TODO translate
SCHEDULE_TIMER_3: 'every hour', // TODO translate
CUSTOM_ENTITIES: 'Custom Entities', // TODO translate
ENTITIES_HELP_1: 'Fetch custom entities from the EMS bus', // TODO translate
ENTITIES_UPDATED: 'Entities Updated', // TODO translate
WRITEABLE: 'Writeable', // TODO translate
SHOWING: 'Showing', // TODO translate
SEARCH: 'Search', // TODO translate
CERT: 'TLS root certificate (leave blank for insecure)',
ENABLE_TLS: 'TLS deveye al',
ON: 'On',
OFF: 'Off',
POLARITY: 'Polarity',
ACTIVEHIGH: 'Aktif Yüksek',
ACTIVELOW: 'Aktif Düşük',
UNCHANGED: 'Değiştirilmedi',
ALWAYS: 'Her zaman',
ACTIVITY: 'Faaliyet',
CONFIGURE: 'Yapılandır {0}',
SYSTEM_MEMORY: 'Sistem Belleği',
APPLICATION_SETTINGS_1: 'EMS-ESP Uygulama Ayarlarını Değiştir',
SECURITY_1: 'Kullanıcıları ekle veya kaldır',
DOWNLOAD_UPLOAD_1: 'Ayarları ve firmware dosyasını indir ve yükle',
MODULES: 'Modül',
ON: 'On', // TODO translate
OFF: 'Off', // TODO translate
POLARITY: 'Polarity', // TODO translate
ACTIVEHIGH: 'Active High', // TODO translate
ACTIVELOW: 'Active Low', // TODO translate
UNCHANGED: 'Unchanged', // TODO translate
ALWAYS: 'Always', // TODO translate
ACTIVITY: 'Activity', // TODO translate
CONFIGURE: 'Configure {0}', // TODO translate
SYSTEM_MEMORY: 'System Memory', // TODO translate
APPLICATION_SETTINGS_1: 'Modify EMS-ESP Application Settings', // TODO translate
SECURITY_1: 'Add or remove users', // TODO translate
DOWNLOAD_UPLOAD_1: 'Download and Upload Settings and Firmware', // TODO translate
MODULES: 'Module', // TODO translate
MODULES_1: 'Harici modülleri etkinleştirin veya devre dışı bırakın',
MODULES_UPDATED: 'Modülleri güncellendi',
MODULES_DESCRIPTION: 'Modüle tıklayarak etkinleştirin veya devre dışı bırakın EMS-ESP kütüphane modülleri',
MODULES_NONE: 'Hiçbir harici modül tespit edilmedi',
RENAME: 'Yeniden Adlandır',
ENABLE_MODBUS: 'Modbus\'ı etkinleştir',
VIEW_LOG: 'Sorunu tanımlamak için günlüğü görüntüleyin',
UPLOAD_DRAG: 'Bir dosya buraya sürükleyip bırakın veya seçmek için tıklayın',
SERVICES: 'Hizmetler',
ALLVALUES: 'Tüm Değerler',
SPECIAL_FUNCTIONS: 'Özel Fonksiyonlar',
WAIT_FIRMWARE: 'Firmware yükleniyor ve yükleniyor',
INSTALL_VERSION: 'Bu {0} sürümü {1} yükleyecek. Emin misiniz?',
UPDATE_AVAILABLE: 'güncellendi!',
LATEST_VERSION: 'En son {0} firmware sürümünü kullanıyorsunuz.',
PLEASE_WAIT: 'Lütfen bekleyin',
RESTARTING_PRE: 'Başlatılıyor',
RESTARTING_POST: 'Hazırlanıyor',
AUTO_SCROLL: 'Otomatik kaydırma',
DASHBOARD: 'Kontrol Paneli',
DEVELOPER_MODE: 'Geliştirici Modu',
BYTES: 'Bayt',
BITMASK: 'Bit Maskesi',
DUPLICATE: 'Çift',
DASHBOARD_1: 'Tüm aktif ve Favori olarak işaretlenmiş EMS varlıkları, artı tüm özel varlıklar, zamanlayıcılar ve harici sensör verileri aşağıda görüntüleniyor.',
NO_DATA_1: 'Henüz bir favori EMS varlığı bulunamadı. Kullanın',
NO_DATA_2: 'modülünü kullanın.',
NO_DATA_3: 'Tüm kullanılabilir varlıkları görmek için git',
THIS_VERSION: 'Bu Sürüm',
PLATFORM: 'Platforma',
RELEASE_TYPE: 'Sürüm Tipi',
INTERNET_CONNECTION_REQUIRED: 'Otomatik sürüm kontrolü ve güncelleme için internet bağlantısı gereklidir',
SWITCH_RELEASE_TYPE: '{0} sürümüne geç'
MODULES_UPDATED: 'Modules updated', // TODO translate
MODULES_DESCRIPTION: 'Click on the Module to activate or de-activate EMS-ESP library modules', // TODO translate
MODULES_NONE: 'No external modules detected', // TODO translate
RENAME: 'Rename', // TODO translate
ENABLE_MODBUS: 'Enable Modbus', // TODO translate
VIEW_LOG: 'View log to diagnose issues', // TODO translate
UPLOAD_DRAG: 'drag and drop a file here or click to select one', // TODO translate
SERVICES: 'Services', // TODO translate
ALLVALUES: 'All Values', // TODO translate
SPECIAL_FUNCTIONS: 'Special Functions', // TODO translate
WAIT_FIRMWARE: 'Firmware is uploading and installing', // TODO translate
INSTALL_VERSION: 'This will install version {0}. Are you sure?', // TODO translate
UPGRADE_AVAILABLE: 'There is a firmware upgrade available!', // TODO translate
LATEST_VERSION: 'You are using the latest firmware version.', // TODO translate
PLEASE_WAIT: 'Please wait', // TODO translate
RESTARTING_PRE: 'Initializing', // TODO translate
RESTARTING_POST: 'Preparing', // TODO translate
AUTO_SCROLL: 'Auto Scroll', // TODO translate
DASHBOARD: 'Dashboard', // TODO translate
DEVELOPER_MODE: 'Developer Mode', // TODO translate
BYTES: 'Bytes', // TODO translate
BITMASK: 'Bit Mask',// TODO translate
DUPLICATE: 'Duplicate', // TODO translate
UPGRADE: 'Upgrade', // TODO translate
DASHBOARD_1: 'All EMS entities that are active and marked as Favorite, plus all Custom Entities, Schedules and external Sensors data are displayed below.', // TODO translate
NO_DATA_1: 'No favorite EMS entities found yet. Use the', // TODO translate
NO_DATA_2: 'module to mark them.', // TODO translate
NO_DATA_3: 'To see all available entities go to', // TODO translate
THIS_VERSION: 'This Version', // TODO translate
PLATFORM: 'Platform', // TODO translate
RELEASE_TYPE: 'Release Type', // TODO translate
REINSTALL: 'Re-install', // TODO translate
INTERNET_CONNECTION_REQUIRED: 'Internet connection required for automatic version checking and upgrading', // TODO translate
};
export default tr;

View File

@@ -13,7 +13,7 @@ const router = createBrowserRouter(
createRoutesFromElements(<Route path="/*" element={<App />} />)
);
createRoot(document.getElementById('root')!).render(
createRoot(document.getElementById('root') as HTMLElement).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>

View File

@@ -29,10 +29,10 @@ export const updateValue =
export const updateValueDirty =
(
origData: unknown,
origData,
dirtyFlags: string[],
setDirtyFlags: React.Dispatch<React.SetStateAction<string[]>>,
updateDataValue: (value: unknown) => void
updateDataValue: (unknown) => void
) =>
(event: React.ChangeEvent<HTMLInputElement>) => {
const updated_value = extractEventValue(event);

View File

@@ -1,108 +1,31 @@
{
"compilerOptions": {
// Target modern browsers for better performance
"target": "ES2022",
"target": "ESNext",
"useDefineForClassFields": true,
// Optimized library selection
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["node", "vite/client"],
// JavaScript handling
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"types": ["node"],
"allowJs": false,
"checkJs": false,
// Module system optimized for Vite
"module": "ESNext",
"moduleResolution": "bundler",
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true,
// Emit configuration
"noEmit": true,
"declaration": false,
"declarationMap": false,
"sourceMap": false,
// React/JSX configuration
"jsx": "react-jsx",
"jsxImportSource": "react",
// Strict type checking for better code quality
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
// Additional checks
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"useUnknownInCatchVariables": true,
// Performance optimizations
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo",
// Path mapping for cleaner imports
"baseUrl": ".",
"composite": true,
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"useUnknownInCatchVariables": false,
"jsx": "react-jsx",
"noImplicitAny": false,
"baseUrl": "src",
"paths": {
"@/*": ["src/*"],
"@/components/*": ["src/components/*"],
"@/utils/*": ["src/utils/*"],
"@/types/*": ["src/types/*"],
"@/hooks/*": ["src/hooks/*"],
"@/services/*": ["src/services/*"],
"@/assets/*": ["src/assets/*"],
// Support for bare imports from src directory
"App": ["src/App"],
"AppRouting": ["src/AppRouting"],
"CustomTheme": ["src/CustomTheme"],
"SignIn": ["src/SignIn"],
"AuthenticatedRouting": ["src/AuthenticatedRouting"],
"env": ["src/env"],
"components": ["src/components"],
"contexts": ["src/contexts"],
"i18n": ["src/i18n"],
"utils": ["src/utils"],
"validators": ["src/validators"],
"types": ["src/types"],
"api": ["src/api"],
"app": ["src/app"],
// Wildcard patterns for subdirectories
"components/*": ["src/components/*"],
"contexts/*": ["src/contexts/*"],
"i18n/*": ["src/i18n/*"],
"utils/*": ["src/utils/*"],
"validators/*": ["src/validators/*"],
"types/*": ["src/types/*"],
"api/*": ["src/api/*"],
"app/*": ["src/app/*"]
"@": ["src"],
"@/*": ["src/*"]
}
},
"include": ["src/**/*", "vite.config.ts", "progmem-generator.js"],
"exclude": [
"node_modules",
"dist",
"build",
".tsbuildinfo",
"**/*.test.ts",
"**/*.test.tsx",
"**/*.spec.ts",
"**/*.spec.tsx"
],
"ts-node": {
"esm": true
}
"include": ["src/**/*", "vite.config.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -1,331 +1,131 @@
import preact from '@preact/preset-vite';
import fs from 'fs';
import path from 'path';
import { visualizer } from 'rollup-plugin-visualizer';
import { defineConfig } from 'vite';
import { Plugin } from 'vite';
import viteImagemin from 'vite-plugin-imagemin';
import viteTsconfigPaths from 'vite-tsconfig-paths';
import zlib from 'zlib';
// @ts-expect-error - mock server doesn't have type declarations
import mockServer from '../mock-api/mockServer.js';
// Plugin to display bundle size information
const bundleSizeReporter = (): Plugin => {
return {
name: 'bundle-size-reporter',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
writeBundle(options: any, bundle: any) {
console.log('\n📦 Bundle Size Report:');
console.log('='.repeat(50));
let totalSize = 0;
const files: Array<{ name: string; size: number; gzipSize?: number }> = [];
for (const [fileName, chunk] of Object.entries(
bundle as Record<string, unknown>
)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((chunk as any).type === 'chunk' || (chunk as any).type === 'asset') {
const filePath = path.join((options.dir as string) || 'dist', fileName);
let size = 0;
let gzipSize = 0;
try {
const stats = fs.statSync(filePath);
size = stats.size;
totalSize += size;
// Calculate gzip size
const fileContent = fs.readFileSync(filePath);
gzipSize = zlib.gzipSync(fileContent).length;
files.push({
name: fileName,
size,
gzipSize
});
} catch (error) {
console.warn(`Could not read file ${fileName}:`, error);
}
}
}
// Sort files by size (largest first)
files.sort((a, b) => b.size - a.size);
// Display individual file sizes
files.forEach((file) => {
const sizeKB = (file.size / 1024).toFixed(2);
const gzipKB = file.gzipSize ? (file.gzipSize / 1024).toFixed(2) : 'N/A';
console.log(
`📄 ${file.name.padEnd(30)} ${sizeKB.padStart(8)} KB (${gzipKB} KB gzipped)`
);
});
console.log('='.repeat(50));
console.log(`📊 Total Bundle Size: ${(totalSize / 1024).toFixed(2)} KB`);
// Calculate and display gzip total
const totalGzipSize = files.reduce(
(sum, file) => sum + (file.gzipSize || 0),
0
);
console.log(`🗜️ Total Gzipped Size: ${(totalGzipSize / 1024).toFixed(2)} KB`);
// Show compression ratio
const compressionRatio = (
((totalSize - totalGzipSize) / totalSize) *
100
).toFixed(1);
console.log(`📈 Compression Ratio: ${compressionRatio}%`);
console.log('='.repeat(50));
}
};
};
export default defineConfig(
({ command, mode }: { command: string; mode: string }) => {
if (command === 'serve') {
console.log('Preparing for standalone build with server, mode=' + mode);
return {
plugins: [
preact({
// Keep dev tools enabled for development
devToolsEnabled: true,
prefreshEnabled: true
}),
viteTsconfigPaths(),
bundleSizeReporter(), // Add bundle size reporting
mockServer()
],
server: {
open: true,
port: mode == 'production' ? 4173 : 3000,
proxy: {
'/api': {
target: 'http://localhost:3080',
changeOrigin: true,
secure: false
},
'/rest': 'http://localhost:3080',
'/gh': 'http://localhost:3080' // mock for GitHub API
}
},
// Optimize development builds
build: {
target: 'es2020',
minify: false, // Disable minification for faster dev builds
sourcemap: true // Enable source maps for debugging
}
};
}
if (mode === 'hosted') {
console.log('Preparing for hosted build');
return {
plugins: [
preact({
// Enable Preact optimizations for hosted build
devToolsEnabled: false,
prefreshEnabled: false
}),
viteTsconfigPaths(),
bundleSizeReporter() // Add bundle size reporting
],
build: {
target: 'es2020',
chunkSizeWarningLimit: 512,
minify: 'terser',
cssMinify: true,
assetsInlineLimit: 4096,
terserOptions: {
compress: {
passes: 3,
drop_console: true,
drop_debugger: true,
dead_code: true,
unused: true
},
mangle: {
toplevel: true
},
ecma: 2020
},
rollupOptions: {
treeshake: {
moduleSideEffects: false
},
output: {
manualChunks(id: string) {
if (id.includes('node_modules')) {
if (id.includes('preact')) {
return '@preact';
}
return 'vendor';
}
return undefined;
}
}
}
}
};
}
console.log('Preparing for production, optimized build');
export default defineConfig(({ command, mode }) => {
if (command === 'serve') {
console.log('Preparing for standalone build with server, mode=' + mode);
return {
plugins: [
preact({
// Enable Preact optimizations
devToolsEnabled: false,
prefreshEnabled: false
}),
viteTsconfigPaths(),
// Enable image optimization for size reduction
{
...viteImagemin({
verbose: false,
gifsicle: {
optimizationLevel: 7,
interlaced: false
},
optipng: {
optimizationLevel: 7
},
mozjpeg: {
quality: 20
},
pngquant: {
quality: [0.8, 0.9],
speed: 4
},
svgo: {
plugins: [
{
name: 'removeViewBox'
},
{
name: 'removeEmptyAttrs',
active: false
}
]
}
}),
enforce: 'pre'
},
visualizer({
template: 'treemap', // or sunburst
open: false,
gzipSize: true,
brotliSize: true,
filename: '../analyse.html' // will be saved in project's root
}),
bundleSizeReporter() // Add bundle size reporting
],
build: {
// Target modern browsers for smaller bundles
target: 'es2020',
chunkSizeWarningLimit: 512,
minify: 'terser',
// Enable CSS minification
cssMinify: true,
// Optimize asset handling
assetsInlineLimit: 4096, // Inline small assets
terserOptions: {
compress: {
passes: 6,
arrows: true,
drop_console: true,
drop_debugger: true,
sequences: true
// Additional aggressive compression options
// dead_code: true,
// hoist_funs: true,
// hoist_vars: true,
// if_return: true,
// join_vars: true,
// loops: true,
// pure_getters: true,
// reduce_vars: true,
// side_effects: false,
// switches: true,
// unsafe: true,
// unsafe_arrows: true,
// unsafe_comps: true,
// unsafe_Function: true,
// unsafe_math: true,
// unsafe_proto: true,
// unsafe_regexp: true,
// unsafe_undefined: true,
// unused: true
plugins: [preact(), viteTsconfigPaths(), mockServer()],
server: {
open: true,
port: mode == 'production' ? 4173 : 3000,
proxy: {
'/api': {
target: 'http://localhost:3080',
changeOrigin: true,
secure: false
},
mangle: {
toplevel: true, // Enable top-level mangling
module: true // Enable module mangling
// properties: {
// regex: /^_/ // Mangle properties starting with _
// }
},
ecma: 2020, // Updated to modern ECMAScript
enclose: false,
keep_classnames: false,
keep_fnames: false,
ie8: false,
module: false,
safari10: false,
toplevel: true // Enable top-level optimization
},
rollupOptions: {
// Enable tree shaking
treeshake: {
moduleSideEffects: false,
propertyReadSideEffects: false,
tryCatchDeoptimization: false
},
output: {
// Optimize chunk naming for better caching
chunkFileNames: 'assets/[name]-[hash].js',
entryFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]',
manualChunks(id: string) {
if (id.includes('node_modules')) {
// More granular chunk splitting for better caching
if (id.includes('react-router')) {
return '@react-router';
}
if (id.includes('preact')) {
return '@preact';
}
if (id.includes('uuid')) {
return '@uuid';
}
if (id.includes('axios') || id.includes('fetch')) {
return '@http';
}
if (id.includes('lodash') || id.includes('ramda')) {
return '@utils';
}
return 'vendor';
}
// Split large application modules
if (id.includes('components/')) {
return 'components';
}
if (id.includes('pages/') || id.includes('routes/')) {
return 'pages';
}
return undefined;
},
// Enable source maps for debugging (optional)
sourcemap: false // Disable for production to save space
}
'/rest': 'http://localhost:3080',
'/gh': 'http://localhost:3080' // mock for GitHub API
}
}
};
}
);
if (mode === 'hosted') {
console.log('Preparing for hosted build');
return {
plugins: [preact(), viteTsconfigPaths()],
build: {
chunkSizeWarningLimit: 1024
}
};
}
console.log('Preparing for production, optimized build');
return {
plugins: [
preact(),
viteTsconfigPaths(),
{
...viteImagemin({
verbose: false,
gifsicle: {
optimizationLevel: 7,
interlaced: false
},
optipng: {
optimizationLevel: 7
},
mozjpeg: {
quality: 20
},
pngquant: {
quality: [0.8, 0.9],
speed: 4
},
svgo: {
plugins: [
{
name: 'removeViewBox'
},
{
name: 'removeEmptyAttrs',
active: false
}
]
}
}),
enforce: 'pre'
},
visualizer({
template: 'treemap', // or sunburst
open: false,
gzipSize: true,
brotliSize: true,
filename: '../analyse.html' // will be saved in project's root
})
],
build: {
// target: 'es2022',
chunkSizeWarningLimit: 1024,
minify: 'terser',
terserOptions: {
compress: {
passes: 4,
arrows: true,
drop_console: true,
drop_debugger: true,
sequences: true
},
mangle: {
// toplevel: true
// module: true
// properties: {
// regex: /^_/
// }
},
ecma: 5,
enclose: false,
keep_classnames: false,
keep_fnames: false,
ie8: false,
module: false,
nameCache: null,
safari10: false,
toplevel: false
},
rollupOptions: {
output: {
manualChunks(id: string) {
if (id.includes('node_modules')) {
// creating a chunk to react routes deps. Reducing the vendor chunk size
if (id.includes('react-router')) {
return '@react-router';
}
return 'vendor';
}
}
}
}
}
};
});

Some files were not shown because too many files have changed in this diff Show More