mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-06-21 15:26:32 +03:00
@@ -30,7 +30,6 @@
|
|||||||
"@mui/material": "^9.1.1",
|
"@mui/material": "^9.1.1",
|
||||||
"@table-library/react-table-library": "4.1.15",
|
"@table-library/react-table-library": "4.1.15",
|
||||||
"alova": "^3.5.1",
|
"alova": "^3.5.1",
|
||||||
"async-validator": "^4.2.5",
|
|
||||||
"etag": "^1.8.1",
|
"etag": "^1.8.1",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"mime-types": "^3.0.2",
|
"mime-types": "^3.0.2",
|
||||||
@@ -38,8 +37,7 @@
|
|||||||
"react": "^19.2.7",
|
"react": "^19.2.7",
|
||||||
"react-dom": "^19.2.7",
|
"react-dom": "^19.2.7",
|
||||||
"react-icons": "^5.6.0",
|
"react-icons": "^5.6.0",
|
||||||
"react-router": "^7.17.0",
|
"react-router": "^8.0.1",
|
||||||
"react-toastify": "^11.1.0",
|
|
||||||
"typesafe-i18n": "^5.27.1",
|
"typesafe-i18n": "^5.27.1",
|
||||||
"typescript": "^6.0.3"
|
"typescript": "^6.0.3"
|
||||||
},
|
},
|
||||||
@@ -47,7 +45,7 @@
|
|||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@preact/preset-vite": "^2.10.5",
|
"@preact/preset-vite": "^2.10.5",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||||
"@types/node": "^25.9.3",
|
"@types/node": "^26.0.0",
|
||||||
"@types/react": "^19.2.17",
|
"@types/react": "^19.2.17",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"concurrently": "^10.0.3",
|
"concurrently": "^10.0.3",
|
||||||
@@ -56,9 +54,9 @@
|
|||||||
"prettier": "^3.8.4",
|
"prettier": "^3.8.4",
|
||||||
"rollup-plugin-visualizer": "^7.0.1",
|
"rollup-plugin-visualizer": "^7.0.1",
|
||||||
"terser": "^5.48.0",
|
"terser": "^5.48.0",
|
||||||
"typescript-eslint": "^8.61.0",
|
"typescript-eslint": "^8.61.1",
|
||||||
"vite": "^8.0.16",
|
"vite": "^8.0.16",
|
||||||
"vite-plugin-imagemin": "^0.6.1"
|
"vite-plugin-imagemin": "^0.6.1"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620"
|
"packageManager": "pnpm@11.8.0+sha512.c1f5e7c4cb241c8f174b743851d82f42b802324afc8b0f116b96adb15aa06664948dde36960a3ba1079ba5b4b29dd0140135b94b5b5f5263592249d68e555f26"
|
||||||
}
|
}
|
||||||
|
|||||||
280
interface/pnpm-lock.yaml
generated
280
interface/pnpm-lock.yaml
generated
@@ -29,9 +29,6 @@ importers:
|
|||||||
alova:
|
alova:
|
||||||
specifier: ^3.5.1
|
specifier: ^3.5.1
|
||||||
version: 3.5.1
|
version: 3.5.1
|
||||||
async-validator:
|
|
||||||
specifier: ^4.2.5
|
|
||||||
version: 4.2.5
|
|
||||||
etag:
|
etag:
|
||||||
specifier: ^1.8.1
|
specifier: ^1.8.1
|
||||||
version: 1.8.1
|
version: 1.8.1
|
||||||
@@ -54,11 +51,8 @@ importers:
|
|||||||
specifier: ^5.6.0
|
specifier: ^5.6.0
|
||||||
version: 5.6.0(react@19.2.7)
|
version: 5.6.0(react@19.2.7)
|
||||||
react-router:
|
react-router:
|
||||||
specifier: ^7.17.0
|
specifier: ^8.0.1
|
||||||
version: 7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
version: 8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||||
react-toastify:
|
|
||||||
specifier: ^11.1.0
|
|
||||||
version: 11.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
|
||||||
typesafe-i18n:
|
typesafe-i18n:
|
||||||
specifier: ^5.27.1
|
specifier: ^5.27.1
|
||||||
version: 5.27.1(typescript@6.0.3)
|
version: 5.27.1(typescript@6.0.3)
|
||||||
@@ -71,13 +65,13 @@ importers:
|
|||||||
version: 10.0.1(eslint@10.5.0)
|
version: 10.0.1(eslint@10.5.0)
|
||||||
'@preact/preset-vite':
|
'@preact/preset-vite':
|
||||||
specifier: ^2.10.5
|
specifier: ^2.10.5
|
||||||
version: 2.10.5(@babel/core@7.29.7)(preact@10.29.2)(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))
|
version: 2.10.5(@babel/core@7.29.7)(preact@10.29.2)(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0))
|
||||||
'@trivago/prettier-plugin-sort-imports':
|
'@trivago/prettier-plugin-sort-imports':
|
||||||
specifier: ^6.0.2
|
specifier: ^6.0.2
|
||||||
version: 6.0.2(prettier@3.8.4)
|
version: 6.0.2(prettier@3.8.4)
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.9.3
|
specifier: ^26.0.0
|
||||||
version: 25.9.3
|
version: 26.0.0
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^19.2.17
|
specifier: ^19.2.17
|
||||||
version: 19.2.17
|
version: 19.2.17
|
||||||
@@ -103,14 +97,14 @@ importers:
|
|||||||
specifier: ^5.48.0
|
specifier: ^5.48.0
|
||||||
version: 5.48.0
|
version: 5.48.0
|
||||||
typescript-eslint:
|
typescript-eslint:
|
||||||
specifier: ^8.61.0
|
specifier: ^8.61.1
|
||||||
version: 8.61.0(eslint@10.5.0)(typescript@6.0.3)
|
version: 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||||
vite:
|
vite:
|
||||||
specifier: ^8.0.16
|
specifier: ^8.0.16
|
||||||
version: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
|
version: 8.0.16(@types/node@26.0.0)(terser@5.48.0)
|
||||||
vite-plugin-imagemin:
|
vite-plugin-imagemin:
|
||||||
specifier: ^0.6.1
|
specifier: ^0.6.1
|
||||||
version: 0.6.1(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))
|
version: 0.6.1(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0))
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -688,8 +682,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==}
|
resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==}
|
||||||
deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed.
|
deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed.
|
||||||
|
|
||||||
'@types/node@25.9.3':
|
'@types/node@26.0.0':
|
||||||
resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==}
|
resolution: {integrity: sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==}
|
||||||
|
|
||||||
'@types/parse-json@4.0.2':
|
'@types/parse-json@4.0.2':
|
||||||
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
|
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
|
||||||
@@ -716,63 +710,63 @@ packages:
|
|||||||
'@types/svgo@2.6.4':
|
'@types/svgo@2.6.4':
|
||||||
resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==}
|
resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.61.0':
|
'@typescript-eslint/eslint-plugin@8.61.1':
|
||||||
resolution: {integrity: sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==}
|
resolution: {integrity: sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@typescript-eslint/parser': ^8.61.0
|
'@typescript-eslint/parser': ^8.61.1
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.61.0':
|
'@typescript-eslint/parser@8.61.1':
|
||||||
resolution: {integrity: sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==}
|
resolution: {integrity: sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/project-service@8.61.0':
|
'@typescript-eslint/project-service@8.61.1':
|
||||||
resolution: {integrity: sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==}
|
resolution: {integrity: sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/scope-manager@8.61.0':
|
'@typescript-eslint/scope-manager@8.61.1':
|
||||||
resolution: {integrity: sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==}
|
resolution: {integrity: sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.61.0':
|
'@typescript-eslint/tsconfig-utils@8.61.1':
|
||||||
resolution: {integrity: sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==}
|
resolution: {integrity: sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.61.0':
|
'@typescript-eslint/type-utils@8.61.1':
|
||||||
resolution: {integrity: sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==}
|
resolution: {integrity: sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/types@8.61.0':
|
'@typescript-eslint/types@8.61.1':
|
||||||
resolution: {integrity: sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==}
|
resolution: {integrity: sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.61.0':
|
'@typescript-eslint/typescript-estree@8.61.1':
|
||||||
resolution: {integrity: sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==}
|
resolution: {integrity: sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.61.0':
|
'@typescript-eslint/utils@8.61.1':
|
||||||
resolution: {integrity: sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==}
|
resolution: {integrity: sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '>=4.8.4 <6.1.0'
|
typescript: '>=4.8.4 <6.1.0'
|
||||||
|
|
||||||
'@typescript-eslint/visitor-keys@8.61.0':
|
'@typescript-eslint/visitor-keys@8.61.1':
|
||||||
resolution: {integrity: sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==}
|
resolution: {integrity: sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
acorn-jsx@5.3.2:
|
acorn-jsx@5.3.2:
|
||||||
@@ -827,9 +821,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
async-validator@4.2.5:
|
|
||||||
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
|
|
||||||
|
|
||||||
available-typed-arrays@1.0.7:
|
available-typed-arrays@1.0.7:
|
||||||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -853,8 +844,8 @@ packages:
|
|||||||
base64-js@1.5.1:
|
base64-js@1.5.1:
|
||||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
|
|
||||||
baseline-browser-mapping@2.10.37:
|
baseline-browser-mapping@2.10.38:
|
||||||
resolution: {integrity: sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==}
|
resolution: {integrity: sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -1024,9 +1015,8 @@ packages:
|
|||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
cookie@1.1.1:
|
cookie-es@3.1.1:
|
||||||
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
|
resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==}
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
core-util-is@1.0.3:
|
core-util-is@1.0.3:
|
||||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||||
@@ -1185,8 +1175,8 @@ packages:
|
|||||||
duplexer3@0.1.5:
|
duplexer3@0.1.5:
|
||||||
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
|
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
|
||||||
|
|
||||||
electron-to-chromium@1.5.372:
|
electron-to-chromium@1.5.376:
|
||||||
resolution: {integrity: sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==}
|
resolution: {integrity: sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==}
|
||||||
|
|
||||||
emoji-regex@10.6.0:
|
emoji-regex@10.6.0:
|
||||||
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
|
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
|
||||||
@@ -2146,8 +2136,8 @@ packages:
|
|||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
nanoid@3.3.12:
|
nanoid@3.3.13:
|
||||||
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
|
resolution: {integrity: sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==}
|
||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -2160,8 +2150,8 @@ packages:
|
|||||||
node-html-parser@6.1.13:
|
node-html-parser@6.1.13:
|
||||||
resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==}
|
resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==}
|
||||||
|
|
||||||
node-releases@2.0.47:
|
node-releases@2.0.48:
|
||||||
resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==}
|
resolution: {integrity: sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
normalize-package-data@2.5.0:
|
normalize-package-data@2.5.0:
|
||||||
@@ -2438,22 +2428,16 @@ packages:
|
|||||||
react-is@19.2.7:
|
react-is@19.2.7:
|
||||||
resolution: {integrity: sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==}
|
resolution: {integrity: sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==}
|
||||||
|
|
||||||
react-router@7.17.0:
|
react-router@8.0.1:
|
||||||
resolution: {integrity: sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==}
|
resolution: {integrity: sha512-5EL/fANovVUhRK50NLS8RYfX0BxrimoKsHWUPPy8v5UEl8i6vzF7e4POo3u+AhPItDwccUAJjMfIOmydxBJmQw==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=22.22.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=18'
|
react: '>=19.2.7'
|
||||||
react-dom: '>=18'
|
react-dom: '>=19.2.7'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
react-dom:
|
react-dom:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
react-toastify@11.1.0:
|
|
||||||
resolution: {integrity: sha512-e9h23x3phN0wbFeB6yovmWp7lobzV4CaCH0LO8nVP6H7Y+3GbcLpIzMm9dJhcp1RXbpyfvjgpfXqO80QAmn7sg==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^18 || ^19
|
|
||||||
react-dom: ^18 || ^19
|
|
||||||
|
|
||||||
react-transition-group@4.4.5:
|
react-transition-group@4.4.5:
|
||||||
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2582,14 +2566,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
semver@7.8.4:
|
semver@7.8.5:
|
||||||
resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==}
|
resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
set-cookie-parser@2.7.2:
|
|
||||||
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
|
|
||||||
|
|
||||||
set-function-length@1.2.2:
|
set-function-length@1.2.2:
|
||||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2827,8 +2808,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=3.5.1'
|
typescript: '>=3.5.1'
|
||||||
|
|
||||||
typescript-eslint@8.61.0:
|
typescript-eslint@8.61.1:
|
||||||
resolution: {integrity: sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==}
|
resolution: {integrity: sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
@@ -2842,8 +2823,8 @@ packages:
|
|||||||
unbzip2-stream@1.4.3:
|
unbzip2-stream@1.4.3:
|
||||||
resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==}
|
resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==}
|
||||||
|
|
||||||
undici-types@7.24.6:
|
undici-types@8.3.0:
|
||||||
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
|
resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
|
||||||
|
|
||||||
universalify@2.0.1:
|
universalify@2.0.1:
|
||||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||||
@@ -3424,19 +3405,19 @@ snapshots:
|
|||||||
|
|
||||||
'@popperjs/core@2.11.8': {}
|
'@popperjs/core@2.11.8': {}
|
||||||
|
|
||||||
'@preact/preset-vite@2.10.5(@babel/core@7.29.7)(preact@10.29.2)(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))':
|
'@preact/preset-vite@2.10.5(@babel/core@7.29.7)(preact@10.29.2)(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7
|
||||||
'@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7)
|
'@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7)
|
||||||
'@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7)
|
'@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7)
|
||||||
'@prefresh/vite': 2.4.12(preact@10.29.2)(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))
|
'@prefresh/vite': 2.4.12(preact@10.29.2)(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0))
|
||||||
'@rollup/pluginutils': 5.4.0
|
'@rollup/pluginutils': 5.4.0
|
||||||
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.7)
|
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.7)
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
vite: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
|
vite: 8.0.16(@types/node@26.0.0)(terser@5.48.0)
|
||||||
vite-prerender-plugin: 0.5.13(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))
|
vite-prerender-plugin: 0.5.13(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0))
|
||||||
zimmerframe: 1.1.4
|
zimmerframe: 1.1.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- preact
|
- preact
|
||||||
@@ -3451,7 +3432,7 @@ snapshots:
|
|||||||
|
|
||||||
'@prefresh/utils@1.2.1': {}
|
'@prefresh/utils@1.2.1': {}
|
||||||
|
|
||||||
'@prefresh/vite@2.4.12(preact@10.29.2)(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0))':
|
'@prefresh/vite@2.4.12(preact@10.29.2)(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.7
|
'@babel/core': 7.29.7
|
||||||
'@prefresh/babel-plugin': 0.5.3
|
'@prefresh/babel-plugin': 0.5.3
|
||||||
@@ -3459,7 +3440,7 @@ snapshots:
|
|||||||
'@prefresh/utils': 1.2.1
|
'@prefresh/utils': 1.2.1
|
||||||
'@rollup/pluginutils': 4.2.1
|
'@rollup/pluginutils': 4.2.1
|
||||||
preact: 10.29.2
|
preact: 10.29.2
|
||||||
vite: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
|
vite: 8.0.16(@types/node@26.0.0)(terser@5.48.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -3562,7 +3543,7 @@ snapshots:
|
|||||||
'@types/glob@7.2.0':
|
'@types/glob@7.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/minimatch': 6.0.0
|
'@types/minimatch': 6.0.0
|
||||||
'@types/node': 25.9.3
|
'@types/node': 26.0.0
|
||||||
|
|
||||||
'@types/imagemin-gifsicle@7.0.4':
|
'@types/imagemin-gifsicle@7.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3591,21 +3572,21 @@ snapshots:
|
|||||||
|
|
||||||
'@types/imagemin@7.0.1':
|
'@types/imagemin@7.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.9.3
|
'@types/node': 26.0.0
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/keyv@3.1.4':
|
'@types/keyv@3.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.9.3
|
'@types/node': 26.0.0
|
||||||
|
|
||||||
'@types/minimatch@6.0.0':
|
'@types/minimatch@6.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
minimatch: 10.2.5
|
minimatch: 10.2.5
|
||||||
|
|
||||||
'@types/node@25.9.3':
|
'@types/node@26.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.24.6
|
undici-types: 8.3.0
|
||||||
|
|
||||||
'@types/parse-json@4.0.2': {}
|
'@types/parse-json@4.0.2': {}
|
||||||
|
|
||||||
@@ -3625,20 +3606,20 @@ snapshots:
|
|||||||
|
|
||||||
'@types/responselike@1.0.3':
|
'@types/responselike@1.0.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.9.3
|
'@types/node': 26.0.0
|
||||||
|
|
||||||
'@types/svgo@2.6.4':
|
'@types/svgo@2.6.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.9.3
|
'@types/node': 26.0.0
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0)(typescript@6.0.3)':
|
'@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0)(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
'@typescript-eslint/parser': 8.61.0(eslint@10.5.0)(typescript@6.0.3)
|
'@typescript-eslint/parser': 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||||
'@typescript-eslint/scope-manager': 8.61.0
|
'@typescript-eslint/scope-manager': 8.61.1
|
||||||
'@typescript-eslint/type-utils': 8.61.0(eslint@10.5.0)(typescript@6.0.3)
|
'@typescript-eslint/type-utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||||
'@typescript-eslint/utils': 8.61.0(eslint@10.5.0)(typescript@6.0.3)
|
'@typescript-eslint/utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.61.0
|
'@typescript-eslint/visitor-keys': 8.61.1
|
||||||
eslint: 10.5.0
|
eslint: 10.5.0
|
||||||
ignore: 7.0.5
|
ignore: 7.0.5
|
||||||
natural-compare: 1.4.0
|
natural-compare: 1.4.0
|
||||||
@@ -3647,41 +3628,41 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.61.0(eslint@10.5.0)(typescript@6.0.3)':
|
'@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 8.61.0
|
'@typescript-eslint/scope-manager': 8.61.1
|
||||||
'@typescript-eslint/types': 8.61.0
|
'@typescript-eslint/types': 8.61.1
|
||||||
'@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3)
|
'@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.61.0
|
'@typescript-eslint/visitor-keys': 8.61.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 10.5.0
|
eslint: 10.5.0
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/project-service@8.61.0(typescript@6.0.3)':
|
'@typescript-eslint/project-service@8.61.1(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/tsconfig-utils': 8.61.0(typescript@6.0.3)
|
'@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3)
|
||||||
'@typescript-eslint/types': 8.61.0
|
'@typescript-eslint/types': 8.61.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/scope-manager@8.61.0':
|
'@typescript-eslint/scope-manager@8.61.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.61.0
|
'@typescript-eslint/types': 8.61.1
|
||||||
'@typescript-eslint/visitor-keys': 8.61.0
|
'@typescript-eslint/visitor-keys': 8.61.1
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.61.0(typescript@6.0.3)':
|
'@typescript-eslint/tsconfig-utils@8.61.1(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.61.0(eslint@10.5.0)(typescript@6.0.3)':
|
'@typescript-eslint/type-utils@8.61.1(eslint@10.5.0)(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.61.0
|
'@typescript-eslint/types': 8.61.1
|
||||||
'@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3)
|
'@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3)
|
||||||
'@typescript-eslint/utils': 8.61.0(eslint@10.5.0)(typescript@6.0.3)
|
'@typescript-eslint/utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 10.5.0
|
eslint: 10.5.0
|
||||||
ts-api-utils: 2.5.0(typescript@6.0.3)
|
ts-api-utils: 2.5.0(typescript@6.0.3)
|
||||||
@@ -3689,37 +3670,37 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/types@8.61.0': {}
|
'@typescript-eslint/types@8.61.1': {}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.61.0(typescript@6.0.3)':
|
'@typescript-eslint/typescript-estree@8.61.1(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/project-service': 8.61.0(typescript@6.0.3)
|
'@typescript-eslint/project-service': 8.61.1(typescript@6.0.3)
|
||||||
'@typescript-eslint/tsconfig-utils': 8.61.0(typescript@6.0.3)
|
'@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3)
|
||||||
'@typescript-eslint/types': 8.61.0
|
'@typescript-eslint/types': 8.61.1
|
||||||
'@typescript-eslint/visitor-keys': 8.61.0
|
'@typescript-eslint/visitor-keys': 8.61.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
minimatch: 10.2.5
|
minimatch: 10.2.5
|
||||||
semver: 7.8.4
|
semver: 7.8.5
|
||||||
tinyglobby: 0.2.17
|
tinyglobby: 0.2.17
|
||||||
ts-api-utils: 2.5.0(typescript@6.0.3)
|
ts-api-utils: 2.5.0(typescript@6.0.3)
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.61.0(eslint@10.5.0)(typescript@6.0.3)':
|
'@typescript-eslint/utils@8.61.1(eslint@10.5.0)(typescript@6.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0)
|
'@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0)
|
||||||
'@typescript-eslint/scope-manager': 8.61.0
|
'@typescript-eslint/scope-manager': 8.61.1
|
||||||
'@typescript-eslint/types': 8.61.0
|
'@typescript-eslint/types': 8.61.1
|
||||||
'@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3)
|
'@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3)
|
||||||
eslint: 10.5.0
|
eslint: 10.5.0
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/visitor-keys@8.61.0':
|
'@typescript-eslint/visitor-keys@8.61.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.61.0
|
'@typescript-eslint/types': 8.61.1
|
||||||
eslint-visitor-keys: 5.0.1
|
eslint-visitor-keys: 5.0.1
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.17.0):
|
acorn-jsx@5.3.2(acorn@8.17.0):
|
||||||
@@ -3762,8 +3743,6 @@ snapshots:
|
|||||||
|
|
||||||
array-union@2.1.0: {}
|
array-union@2.1.0: {}
|
||||||
|
|
||||||
async-validator@4.2.5: {}
|
|
||||||
|
|
||||||
available-typed-arrays@1.0.7:
|
available-typed-arrays@1.0.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
possible-typed-array-names: 1.1.0
|
possible-typed-array-names: 1.1.0
|
||||||
@@ -3784,7 +3763,7 @@ snapshots:
|
|||||||
|
|
||||||
base64-js@1.5.1: {}
|
base64-js@1.5.1: {}
|
||||||
|
|
||||||
baseline-browser-mapping@2.10.37: {}
|
baseline-browser-mapping@2.10.38: {}
|
||||||
|
|
||||||
bin-build@3.0.0:
|
bin-build@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3845,10 +3824,10 @@ snapshots:
|
|||||||
|
|
||||||
browserslist@4.28.2:
|
browserslist@4.28.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
baseline-browser-mapping: 2.10.37
|
baseline-browser-mapping: 2.10.38
|
||||||
caniuse-lite: 1.0.30001799
|
caniuse-lite: 1.0.30001799
|
||||||
electron-to-chromium: 1.5.372
|
electron-to-chromium: 1.5.376
|
||||||
node-releases: 2.0.47
|
node-releases: 2.0.48
|
||||||
update-browserslist-db: 1.2.3(browserslist@4.28.2)
|
update-browserslist-db: 1.2.3(browserslist@4.28.2)
|
||||||
|
|
||||||
buffer-alloc-unsafe@1.1.0: {}
|
buffer-alloc-unsafe@1.1.0: {}
|
||||||
@@ -3983,7 +3962,7 @@ snapshots:
|
|||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
cookie@1.1.1: {}
|
cookie-es@3.1.1: {}
|
||||||
|
|
||||||
core-util-is@1.0.3: {}
|
core-util-is@1.0.3: {}
|
||||||
|
|
||||||
@@ -4202,7 +4181,7 @@ snapshots:
|
|||||||
|
|
||||||
duplexer3@0.1.5: {}
|
duplexer3@0.1.5: {}
|
||||||
|
|
||||||
electron-to-chromium@1.5.372: {}
|
electron-to-chromium@1.5.376: {}
|
||||||
|
|
||||||
emoji-regex@10.6.0: {}
|
emoji-regex@10.6.0: {}
|
||||||
|
|
||||||
@@ -5121,7 +5100,7 @@ snapshots:
|
|||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
nanoid@3.3.12: {}
|
nanoid@3.3.13: {}
|
||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
@@ -5132,7 +5111,7 @@ snapshots:
|
|||||||
css-select: 5.2.2
|
css-select: 5.2.2
|
||||||
he: 1.2.0
|
he: 1.2.0
|
||||||
|
|
||||||
node-releases@2.0.47: {}
|
node-releases@2.0.48: {}
|
||||||
|
|
||||||
normalize-package-data@2.5.0:
|
normalize-package-data@2.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5320,7 +5299,7 @@ snapshots:
|
|||||||
|
|
||||||
postcss@8.5.15:
|
postcss@8.5.15:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.12
|
nanoid: 3.3.13
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
@@ -5378,20 +5357,13 @@ snapshots:
|
|||||||
|
|
||||||
react-is@19.2.7: {}
|
react-is@19.2.7: {}
|
||||||
|
|
||||||
react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
react-router@8.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||||
dependencies:
|
dependencies:
|
||||||
cookie: 1.1.1
|
cookie-es: 3.1.1
|
||||||
react: 19.2.7
|
react: 19.2.7
|
||||||
set-cookie-parser: 2.7.2
|
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
react-dom: 19.2.7(react@19.2.7)
|
react-dom: 19.2.7(react@19.2.7)
|
||||||
|
|
||||||
react-toastify@11.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
|
||||||
dependencies:
|
|
||||||
clsx: 2.1.1
|
|
||||||
react: 19.2.7
|
|
||||||
react-dom: 19.2.7(react@19.2.7)
|
|
||||||
|
|
||||||
react-transition-group@4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
react-transition-group@4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.7
|
'@babel/runtime': 7.29.7
|
||||||
@@ -5528,9 +5500,7 @@ snapshots:
|
|||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
|
|
||||||
semver@7.8.4: {}
|
semver@7.8.5: {}
|
||||||
|
|
||||||
set-cookie-parser@2.7.2: {}
|
|
||||||
|
|
||||||
set-function-length@1.2.2:
|
set-function-length@1.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5753,12 +5723,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
typescript-eslint@8.61.0(eslint@10.5.0)(typescript@6.0.3):
|
typescript-eslint@8.61.1(eslint@10.5.0)(typescript@6.0.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/eslint-plugin': 8.61.0(@typescript-eslint/parser@8.61.0(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0)(typescript@6.0.3)
|
'@typescript-eslint/eslint-plugin': 8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0)(typescript@6.0.3)
|
||||||
'@typescript-eslint/parser': 8.61.0(eslint@10.5.0)(typescript@6.0.3)
|
'@typescript-eslint/parser': 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||||
'@typescript-eslint/typescript-estree': 8.61.0(typescript@6.0.3)
|
'@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3)
|
||||||
'@typescript-eslint/utils': 8.61.0(eslint@10.5.0)(typescript@6.0.3)
|
'@typescript-eslint/utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3)
|
||||||
eslint: 10.5.0
|
eslint: 10.5.0
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -5771,7 +5741,7 @@ snapshots:
|
|||||||
buffer: 5.7.1
|
buffer: 5.7.1
|
||||||
through: 2.3.8
|
through: 2.3.8
|
||||||
|
|
||||||
undici-types@7.24.6: {}
|
undici-types@8.3.0: {}
|
||||||
|
|
||||||
universalify@2.0.1: {}
|
universalify@2.0.1: {}
|
||||||
|
|
||||||
@@ -5804,7 +5774,7 @@ snapshots:
|
|||||||
spdx-correct: 3.2.0
|
spdx-correct: 3.2.0
|
||||||
spdx-expression-parse: 3.0.1
|
spdx-expression-parse: 3.0.1
|
||||||
|
|
||||||
vite-plugin-imagemin@0.6.1(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0)):
|
vite-plugin-imagemin@0.6.1(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/imagemin': 7.0.1
|
'@types/imagemin': 7.0.1
|
||||||
'@types/imagemin-gifsicle': 7.0.4
|
'@types/imagemin-gifsicle': 7.0.4
|
||||||
@@ -5829,11 +5799,11 @@ snapshots:
|
|||||||
imagemin-webp: 6.1.0
|
imagemin-webp: 6.1.0
|
||||||
jpegtran-bin: 6.0.1
|
jpegtran-bin: 6.0.1
|
||||||
pathe: 0.2.0
|
pathe: 0.2.0
|
||||||
vite: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
|
vite: 8.0.16(@types/node@26.0.0)(terser@5.48.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
vite-prerender-plugin@0.5.13(vite@8.0.16(@types/node@25.9.3)(terser@5.48.0)):
|
vite-prerender-plugin@0.5.13(vite@8.0.16(@types/node@26.0.0)(terser@5.48.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
kolorist: 1.8.0
|
kolorist: 1.8.0
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
@@ -5841,9 +5811,9 @@ snapshots:
|
|||||||
simple-code-frame: 1.3.0
|
simple-code-frame: 1.3.0
|
||||||
source-map: 0.7.6
|
source-map: 0.7.6
|
||||||
stack-trace: 1.0.0
|
stack-trace: 1.0.0
|
||||||
vite: 8.0.16(@types/node@25.9.3)(terser@5.48.0)
|
vite: 8.0.16(@types/node@26.0.0)(terser@5.48.0)
|
||||||
|
|
||||||
vite@8.0.16(@types/node@25.9.3)(terser@5.48.0):
|
vite@8.0.16(@types/node@26.0.0)(terser@5.48.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
lightningcss: 1.32.0
|
lightningcss: 1.32.0
|
||||||
picomatch: 4.0.4
|
picomatch: 4.0.4
|
||||||
@@ -5851,7 +5821,7 @@ snapshots:
|
|||||||
rolldown: 1.0.3
|
rolldown: 1.0.3
|
||||||
tinyglobby: 0.2.17
|
tinyglobby: 0.2.17
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.9.3
|
'@types/node': 26.0.0
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
terser: 5.48.0
|
terser: 5.48.0
|
||||||
|
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ allowBuilds:
|
|||||||
minimumReleaseAgeExclude:
|
minimumReleaseAgeExclude:
|
||||||
- '@types/node@25.9.2'
|
- '@types/node@25.9.2'
|
||||||
- '@types/react@19.2.17'
|
- '@types/react@19.2.17'
|
||||||
|
- react-router@8.0.1
|
||||||
|
|||||||
@@ -46,6 +46,38 @@ ${fileInfo.map((f) => `${INDENT}{"${f.uri}", "${f.mimeType}", ${f.variable}, ${f
|
|||||||
static constexpr size_t WWW_ASSETS_COUNT = sizeof(WWW_ASSETS) / sizeof(WWW_ASSETS[0]);
|
static constexpr size_t WWW_ASSETS_COUNT = sizeof(WWW_ASSETS) / sizeof(WWW_ASSETS[0]);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Optional locale allow-list, shared with the Vite build via VITE_APP_LOCALES
|
||||||
|
// (e.g. "en,de,nl"). When set, locale chunks outside the list are NOT embedded
|
||||||
|
// into firmware flash. `en` is always kept as the fallback. Unset => embed all.
|
||||||
|
const ALL_LOCALES = [
|
||||||
|
'cz',
|
||||||
|
'de',
|
||||||
|
'en',
|
||||||
|
'fr',
|
||||||
|
'it',
|
||||||
|
'nl',
|
||||||
|
'no',
|
||||||
|
'pl',
|
||||||
|
'sk',
|
||||||
|
'sv',
|
||||||
|
'tr'
|
||||||
|
];
|
||||||
|
const localeAllowList = (process.env.VITE_APP_LOCALES || '')
|
||||||
|
.split(',')
|
||||||
|
.map((locale) => locale.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const isExcludedLocaleChunk = (relativeFilePath) => {
|
||||||
|
if (localeAllowList.length === 0) return false;
|
||||||
|
const base = relativeFilePath.split(sep).pop();
|
||||||
|
const match = /^([a-z]{2})-[A-Za-z0-9_-]+\.js$/.exec(base);
|
||||||
|
if (!match) return false;
|
||||||
|
const code = match[1];
|
||||||
|
// Only treat known locale codes as locale chunks; never drop the en fallback.
|
||||||
|
if (!ALL_LOCALES.includes(code) || code === 'en') return false;
|
||||||
|
return !localeAllowList.includes(code);
|
||||||
|
};
|
||||||
|
|
||||||
const getFilesSync = (dir, files = []) => {
|
const getFilesSync = (dir, files = []) => {
|
||||||
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
|
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
|
||||||
const entryPath = resolve(dir, entry.name);
|
const entryPath = resolve(dir, entry.name);
|
||||||
@@ -116,7 +148,12 @@ writeStream.write(ARDUINO_INCLUDES);
|
|||||||
|
|
||||||
const buildPath = resolve(sourcePath);
|
const buildPath = resolve(sourcePath);
|
||||||
for (const filePath of getFilesSync(buildPath)) {
|
for (const filePath of getFilesSync(buildPath)) {
|
||||||
writeFile(relative(buildPath, filePath), readFileSync(filePath));
|
const relativeFilePath = relative(buildPath, filePath);
|
||||||
|
if (isExcludedLocaleChunk(relativeFilePath)) {
|
||||||
|
console.log(`Skipping locale (not in VITE_APP_LOCALES): ${relativeFilePath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
writeFile(relativeFilePath, readFileSync(filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
writeStream.write(generateWWWClass());
|
writeStream.write(generateWWWClass());
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import { ToastContainer, Zoom } from 'react-toastify';
|
import { Outlet } from 'react-router';
|
||||||
|
|
||||||
import AppRouting from 'AppRouting';
|
|
||||||
import CustomTheme from 'CustomTheme';
|
import CustomTheme from 'CustomTheme';
|
||||||
|
import { Toaster } from 'components/toast';
|
||||||
|
import { Authentication } from 'contexts/authentication';
|
||||||
import TypesafeI18n from 'i18n/i18n-react';
|
import TypesafeI18n from 'i18n/i18n-react';
|
||||||
import type { Locales } from 'i18n/i18n-types';
|
import type { Locales } from 'i18n/i18n-types';
|
||||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||||
import { detectLocale, navigatorDetector } from 'typesafe-i18n/detectors';
|
import { detectLocale, navigatorDetector } from 'typesafe-i18n/detectors';
|
||||||
|
|
||||||
const AVAILABLE_LOCALES = [
|
const ALL_LOCALES = [
|
||||||
'de',
|
'de',
|
||||||
'en',
|
'en',
|
||||||
'it',
|
'it',
|
||||||
@@ -22,25 +23,19 @@ const AVAILABLE_LOCALES = [
|
|||||||
'cz'
|
'cz'
|
||||||
] as Locales[];
|
] as Locales[];
|
||||||
|
|
||||||
// Static toast configuration - no need to recreate on every render
|
// Optional build-time allow-list (e.g. VITE_APP_LOCALES="en,de,nl"). When unset,
|
||||||
const TOAST_CONTAINER_PROPS = {
|
// every locale is available. `en` is always kept as the fallback locale, and the
|
||||||
position: 'bottom-left' as const,
|
// progmem generator embeds the matching subset into firmware flash.
|
||||||
autoClose: 3000,
|
const localeAllowList = (import.meta.env.VITE_APP_LOCALES ?? '')
|
||||||
hideProgressBar: false,
|
.split(',')
|
||||||
newestOnTop: false,
|
.map((locale) => locale.trim())
|
||||||
closeOnClick: true,
|
.filter(Boolean);
|
||||||
rtl: false,
|
|
||||||
pauseOnFocusLoss: true,
|
const AVAILABLE_LOCALES: Locales[] = localeAllowList.length
|
||||||
draggable: false,
|
? ALL_LOCALES.filter(
|
||||||
pauseOnHover: false,
|
(locale) => locale === 'en' || localeAllowList.includes(locale)
|
||||||
transition: Zoom,
|
)
|
||||||
closeButton: false,
|
: ALL_LOCALES;
|
||||||
theme: 'dark' as const,
|
|
||||||
toastStyle: {
|
|
||||||
border: '1px solid #177ac9',
|
|
||||||
width: 'fit-content'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const App = memo(() => {
|
const App = memo(() => {
|
||||||
const [wasLoaded, setWasLoaded] = useState(false);
|
const [wasLoaded, setWasLoaded] = useState(false);
|
||||||
@@ -49,7 +44,12 @@ const App = memo(() => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeLocale = async () => {
|
const initializeLocale = async () => {
|
||||||
const browserLocale = detectLocale('en', AVAILABLE_LOCALES, navigatorDetector);
|
const browserLocale = detectLocale('en', AVAILABLE_LOCALES, navigatorDetector);
|
||||||
const newLocale = (localStorage.getItem('lang') || browserLocale) as Locales;
|
const stored = localStorage.getItem('lang');
|
||||||
|
// Ignore a stored locale that isn't available (e.g. trimmed from this build).
|
||||||
|
const newLocale =
|
||||||
|
stored && AVAILABLE_LOCALES.includes(stored as Locales)
|
||||||
|
? (stored as Locales)
|
||||||
|
: browserLocale;
|
||||||
localStorage.setItem('lang', newLocale);
|
localStorage.setItem('lang', newLocale);
|
||||||
setLocale(newLocale);
|
setLocale(newLocale);
|
||||||
await loadLocaleAsync(newLocale);
|
await loadLocaleAsync(newLocale);
|
||||||
@@ -63,8 +63,10 @@ const App = memo(() => {
|
|||||||
return (
|
return (
|
||||||
<TypesafeI18n locale={locale}>
|
<TypesafeI18n locale={locale}>
|
||||||
<CustomTheme>
|
<CustomTheme>
|
||||||
<AppRouting />
|
<Authentication>
|
||||||
<ToastContainer {...TOAST_CONTAINER_PROPS} />
|
<Outlet />
|
||||||
|
</Authentication>
|
||||||
|
<Toaster />
|
||||||
</CustomTheme>
|
</CustomTheme>
|
||||||
</TypesafeI18n>
|
</TypesafeI18n>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import { type FC, memo, useContext, useEffect, useRef } from 'react';
|
|
||||||
import { Navigate, Route, Routes } from 'react-router';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import AuthenticatedRouting from 'AuthenticatedRouting';
|
|
||||||
import SignIn from 'SignIn';
|
|
||||||
import { RequireAuthenticated, RequireUnauthenticated } from 'components';
|
|
||||||
import { Authentication, AuthenticationContext } from 'contexts/authentication';
|
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
|
||||||
|
|
||||||
interface SecurityRedirectProps {
|
|
||||||
readonly message: string;
|
|
||||||
readonly signOut?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RootRedirect: FC<SecurityRedirectProps> = memo(
|
|
||||||
({ message, signOut = false }) => {
|
|
||||||
const { signOut: contextSignOut } = useContext(AuthenticationContext);
|
|
||||||
const hasShownToast = useRef(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Prevent duplicate toasts on strict mode or re-renders
|
|
||||||
if (!hasShownToast.current) {
|
|
||||||
hasShownToast.current = true;
|
|
||||||
if (signOut) {
|
|
||||||
contextSignOut(false);
|
|
||||||
}
|
|
||||||
toast.success(message);
|
|
||||||
}
|
|
||||||
// Only run once on mount - using ref to track execution
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return <Navigate to="/" replace />;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const AppRouting: FC = memo(() => {
|
|
||||||
const { LL } = useI18nContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Authentication>
|
|
||||||
<Routes>
|
|
||||||
<Route
|
|
||||||
path="/unauthorized"
|
|
||||||
element={<RootRedirect message={LL.PLEASE_SIGNIN()} signOut />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/fileUpdated"
|
|
||||||
element={<RootRedirect message={LL.UPLOAD_SUCCESSFUL()} />}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/"
|
|
||||||
element={
|
|
||||||
<RequireUnauthenticated>
|
|
||||||
<SignIn />
|
|
||||||
</RequireUnauthenticated>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/*"
|
|
||||||
element={
|
|
||||||
<RequireAuthenticated>
|
|
||||||
<AuthenticatedRouting />
|
|
||||||
</RequireAuthenticated>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Routes>
|
|
||||||
</Authentication>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default AppRouting;
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import { memo, useContext } from 'react';
|
|
||||||
import { Navigate, Route, Routes } from 'react-router';
|
|
||||||
|
|
||||||
import Commands from 'app/main/Commands';
|
|
||||||
import CustomEntities from 'app/main/CustomEntities';
|
|
||||||
import Customizations from 'app/main/Customizations';
|
|
||||||
import Dashboard from 'app/main/Dashboard';
|
|
||||||
import Devices from 'app/main/Devices';
|
|
||||||
import Help from 'app/main/Help';
|
|
||||||
import Modules from 'app/main/Modules';
|
|
||||||
import Scheduler from 'app/main/Scheduler';
|
|
||||||
import Sensors from 'app/main/Sensors';
|
|
||||||
import UserProfile from 'app/main/UserProfile';
|
|
||||||
import APSettings from 'app/settings/APSettings';
|
|
||||||
import ApplicationSettings from 'app/settings/ApplicationSettings';
|
|
||||||
import DownloadUpload from 'app/settings/DownloadUpload';
|
|
||||||
import MqttSettings from 'app/settings/MqttSettings';
|
|
||||||
import NTPSettings from 'app/settings/NTPSettings';
|
|
||||||
import Settings from 'app/settings/Settings';
|
|
||||||
import Version from 'app/settings/Version';
|
|
||||||
import Network from 'app/settings/network/Network';
|
|
||||||
import Security from 'app/settings/security/Security';
|
|
||||||
import APStatus from 'app/status/APStatus';
|
|
||||||
import Activity from 'app/status/Activity';
|
|
||||||
import HardwareStatus from 'app/status/HardwareStatus';
|
|
||||||
import MqttStatus from 'app/status/MqttStatus';
|
|
||||||
import NTPStatus from 'app/status/NTPStatus';
|
|
||||||
import NetworkStatus from 'app/status/NetworkStatus';
|
|
||||||
import Status from 'app/status/Status';
|
|
||||||
import SystemLog from 'app/status/SystemLog';
|
|
||||||
import { Layout } from 'components';
|
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
|
||||||
|
|
||||||
const AuthenticatedRouting = memo(() => {
|
|
||||||
const { me } = useContext(AuthenticatedContext);
|
|
||||||
return (
|
|
||||||
<Layout>
|
|
||||||
<Routes>
|
|
||||||
<Route path="/dashboard/*" element={<Dashboard />} />
|
|
||||||
<Route path="/devices/*" element={<Devices />} />
|
|
||||||
<Route path="/sensors/*" element={<Sensors />} />
|
|
||||||
<Route path="/help/*" element={<Help />} />
|
|
||||||
<Route path="/user/*" element={<UserProfile />} />
|
|
||||||
|
|
||||||
<Route path="/status/*" element={<Status />} />
|
|
||||||
<Route path="/status/hardwarestatus/*" element={<HardwareStatus />} />
|
|
||||||
<Route path="/status/activity" element={<Activity />} />
|
|
||||||
<Route path="/status/log" element={<SystemLog />} />
|
|
||||||
<Route path="/status/mqtt" element={<MqttStatus />} />
|
|
||||||
<Route path="/status/ntp" element={<NTPStatus />} />
|
|
||||||
<Route path="/status/ap" element={<APStatus />} />
|
|
||||||
<Route path="/status/network" element={<NetworkStatus />} />
|
|
||||||
|
|
||||||
{me.admin && (
|
|
||||||
<>
|
|
||||||
<Route path="/settings" element={<Settings />} />
|
|
||||||
<Route path="/settings/version" element={<Version />} />
|
|
||||||
<Route path="/settings/application" element={<ApplicationSettings />} />
|
|
||||||
<Route path="/settings/mqtt" element={<MqttSettings />} />
|
|
||||||
<Route path="/settings/ntp" element={<NTPSettings />} />
|
|
||||||
<Route path="/settings/ap" element={<APSettings />} />
|
|
||||||
<Route path="/settings/modules" element={<Modules />} />
|
|
||||||
<Route path="/settings/downloadUpload" element={<DownloadUpload />} />
|
|
||||||
|
|
||||||
<Route path="/settings/network/*" element={<Network />} />
|
|
||||||
<Route path="/settings/security/*" element={<Security />} />
|
|
||||||
|
|
||||||
<Route path="/customizations" element={<Customizations />} />
|
|
||||||
<Route path="/commands" element={<Commands />} />
|
|
||||||
<Route path="/scheduler" element={<Scheduler />} />
|
|
||||||
<Route path="/customentities" element={<CustomEntities />} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Route path="/*" element={<Navigate to="/" />} />
|
|
||||||
</Routes>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default AuthenticatedRouting;
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import ForwardIcon from '@mui/icons-material/Forward';
|
import ForwardIcon from '@mui/icons-material/Forward';
|
||||||
import { Box, Button, Paper, Typography } from '@mui/material';
|
import { Box, Button, Paper, Typography } from '@mui/material';
|
||||||
@@ -7,18 +6,19 @@ import type { Theme } from '@mui/material/styles';
|
|||||||
|
|
||||||
import * as AuthenticationApi from 'components/routing/authentication';
|
import * as AuthenticationApi from 'components/routing/authentication';
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import {
|
import {
|
||||||
LanguageSelector,
|
LanguageSelector,
|
||||||
ValidatedPasswordField,
|
ValidatedPasswordField,
|
||||||
ValidatedTextField
|
ValidatedTextField
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { AuthenticationContext } from 'contexts/authentication';
|
import { AuthenticationContext } from 'contexts/authentication';
|
||||||
import { PROJECT_NAME } from 'env';
|
import { PROJECT_NAME } from 'env';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { SignInRequest } from 'types';
|
import type { SignInRequest } from 'types';
|
||||||
import { onEnterCallback, updateValue } from 'utils';
|
import { onEnterCallback, updateValue } from 'utils';
|
||||||
import { SIGN_IN_REQUEST_VALIDATOR, ValidationError, validate } from 'validators';
|
import { SIGN_IN_REQUEST_VALIDATOR, ValidationError, validate } from 'validators';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
const SignIn = memo(() => {
|
const SignIn = memo(() => {
|
||||||
const authenticationContext = useContext(AuthenticationContext);
|
const authenticationContext = useContext(AuthenticationContext);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useBlocker } from 'react-router';
|
import { useBlocker } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
@@ -27,6 +26,7 @@ import {
|
|||||||
SectionContent,
|
SectionContent,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { useInterval } from 'utils';
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
@@ -19,12 +18,13 @@ import {
|
|||||||
import { callAction } from '@/api/app';
|
import { callAction } from '@/api/app';
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import type Schema from 'async-validator';
|
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import { ValidatedTextField } from 'components';
|
import { ValidatedTextField } from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { updateValue } from 'utils';
|
import { updateValue } from 'utils';
|
||||||
import { ValidationError, validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
import type Schema from 'validators/schema';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
import type { CommandItem } from './types';
|
import type { CommandItem } from './types';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useBlocker } from 'react-router';
|
import { useBlocker } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
@@ -26,6 +25,7 @@ import {
|
|||||||
SectionContent,
|
SectionContent,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { useInterval } from 'utils';
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import type Schema from 'async-validator';
|
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { numberValue, updateValue } from 'utils';
|
import { numberValue, updateValue } from 'utils';
|
||||||
import { ValidationError, validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
import type Schema from 'validators/schema';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
import { DeviceValueType, DeviceValueTypeNames, DeviceValueUOM_s } from './types';
|
import { DeviceValueType, DeviceValueTypeNames, DeviceValueUOM_s } from './types';
|
||||||
import type { EntityItem } from './types';
|
import type { EntityItem } from './types';
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useBlocker, useLocation } from 'react-router';
|
import { useBlocker, useLocation } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
@@ -46,6 +45,7 @@ import {
|
|||||||
SectionContent,
|
SectionContent,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { memo, useContext, useEffect, useState } from 'react';
|
import { memo, useContext, useEffect, useState } from 'react';
|
||||||
import { IconContext } from 'react-icons/lib';
|
import { IconContext } from 'react-icons/lib';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
@@ -30,6 +29,7 @@ import {
|
|||||||
SectionContent,
|
SectionContent,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { useInterval, usePersistState } from 'utils';
|
import { useInterval, usePersistState } from 'utils';
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { IconContext } from 'react-icons';
|
import { IconContext } from 'react-icons';
|
||||||
import { Link, useNavigate } from 'react-router';
|
import { Link, useNavigate } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
import CommentsDisabledOutlinedIcon from '@mui/icons-material/CommentsDisabledOutlined';
|
||||||
import ConstructionIcon from '@mui/icons-material/Construction';
|
import ConstructionIcon from '@mui/icons-material/Construction';
|
||||||
@@ -64,6 +63,7 @@ import {
|
|||||||
SectionContent,
|
SectionContent,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { useInterval } from 'utils';
|
import { useInterval } from 'utils';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||||
@@ -23,12 +22,13 @@ import {
|
|||||||
import { callAction } from '@/api/app';
|
import { callAction } from '@/api/app';
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import type Schema from 'async-validator';
|
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import { ValidatedTextField } from 'components';
|
import { ValidatedTextField } from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { numberValue, updateValue } from 'utils';
|
import { numberValue, updateValue } from 'utils';
|
||||||
import { ValidationError, validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
import type Schema from 'validators/schema';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
|
import { DeviceValueUOM, DeviceValueUOM_s } from './types';
|
||||||
import type { DeviceValue } from './types';
|
import type { DeviceValue } from './types';
|
||||||
@@ -84,10 +84,9 @@ const DevicesDialog = ({
|
|||||||
} else {
|
} else {
|
||||||
await validate(validator, editItem);
|
await validate(validator, editItem);
|
||||||
}
|
}
|
||||||
|
onSave(editItem);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors((error as ValidationError).fieldErrors);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
} finally {
|
|
||||||
onSave(editItem);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { memo, useContext, useState } from 'react';
|
import { memo, useContext, useState } from 'react';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CommentIcon from '@mui/icons-material/CommentTwoTone';
|
import CommentIcon from '@mui/icons-material/CommentTwoTone';
|
||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
@@ -25,6 +24,7 @@ import type { SxProps, Theme } from '@mui/material/styles';
|
|||||||
|
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { SectionContent, useLayoutTitle } from 'components';
|
import { SectionContent, useLayoutTitle } from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { saveFile } from 'utils';
|
import { saveFile } from 'utils';
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { memo, useState } from 'react';
|
import { memo, useState } from 'react';
|
||||||
import { useBlocker } from 'react-router';
|
import { useBlocker } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import CircleIcon from '@mui/icons-material/Circle';
|
import CircleIcon from '@mui/icons-material/Circle';
|
||||||
@@ -25,6 +24,7 @@ import {
|
|||||||
SectionContent,
|
SectionContent,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import { readModules, writeModules } from '../../api/app';
|
import { readModules, writeModules } from '../../api/app';
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useBlocker } from 'react-router';
|
import { useBlocker } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
@@ -26,6 +25,7 @@ import {
|
|||||||
SectionContent,
|
SectionContent,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { useInterval } from 'utils';
|
import { useInterval } from 'utils';
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import type Schema from 'async-validator';
|
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
import { BlockFormControlLabel, ValidatedTextField } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { updateValue } from 'utils';
|
import { updateValue } from 'utils';
|
||||||
import { ValidationError, validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
import type Schema from 'validators/schema';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
import { ScheduleFlag } from './types';
|
import { ScheduleFlag } from './types';
|
||||||
import type { ScheduleItem } from './types';
|
import type { ScheduleItem } from './types';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useContext, useRef, useState } from 'react';
|
import { useContext, useRef, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
|
import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined';
|
||||||
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
import KeyboardArrowDownOutlinedIcon from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||||
@@ -21,6 +20,7 @@ import { useTheme } from '@table-library/react-table-library/theme';
|
|||||||
import type { State } from '@table-library/react-table-library/types/common';
|
import type { State } from '@table-library/react-table-library/types/common';
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { SectionContent, useLayoutTitle } from 'components';
|
import { SectionContent, useLayoutTitle } from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { useInterval } from 'utils';
|
import { useInterval } from 'utils';
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import type Schema from 'async-validator';
|
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import { ValidatedTextField } from 'components';
|
import { ValidatedTextField } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { numberValue, updateValue } from 'utils';
|
import { numberValue, updateValue } from 'utils';
|
||||||
import { ValidationError, validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
import type Schema from 'validators/schema';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
import { AnalogType, AnalogTypeNames, DeviceValueUOM_s } from './types';
|
import { AnalogType, AnalogTypeNames, DeviceValueUOM_s } from './types';
|
||||||
import type { AnalogSensor } from './types';
|
import type { AnalogSensor } from './types';
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import type Schema from 'async-validator';
|
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import { ValidatedTextField } from 'components';
|
import { ValidatedTextField } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { numberValue, updateValue } from 'utils';
|
import { numberValue, updateValue } from 'utils';
|
||||||
import { ValidationError, validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
import type Schema from 'validators/schema';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
import type { TemperatureSensor } from './types';
|
import type { TemperatureSensor } from './types';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Schema from 'async-validator';
|
import Schema from 'validators/schema';
|
||||||
import type { InternalRuleItem } from 'async-validator';
|
import type { InternalRuleItem } from 'validators/schema';
|
||||||
import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared';
|
import { IP_OR_HOSTNAME_VALIDATOR } from 'validators/shared';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { Button, Checkbox, MenuItem } from '@mui/material';
|
|||||||
|
|
||||||
import * as APApi from 'api/ap';
|
import * as APApi from 'api/ap';
|
||||||
|
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
BlockNavigation,
|
BlockNavigation,
|
||||||
@@ -22,6 +21,7 @@ import type { APSettingsType } from 'types';
|
|||||||
import { APProvisionMode } from 'types';
|
import { APProvisionMode } from 'types';
|
||||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
import { ValidationError, createAPSettingsValidator, validate } from 'validators';
|
import { ValidationError, createAPSettingsValidator, validate } from 'validators';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
export const isAPEnabled = ({ provision_mode }: APSettingsType) =>
|
export const isAPEnabled = ({ provision_mode }: APSettingsType) =>
|
||||||
provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
|
provision_mode === APProvisionMode.AP_MODE_DISCONNECTED;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew';
|
||||||
@@ -20,7 +19,6 @@ import { readSystemStatus } from 'api/system';
|
|||||||
|
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import SystemMonitor from 'app/status/SystemMonitor';
|
import SystemMonitor from 'app/status/SystemMonitor';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
BlockNavigation,
|
BlockNavigation,
|
||||||
@@ -32,9 +30,11 @@ import {
|
|||||||
ValidatedTextField,
|
ValidatedTextField,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
import { ValidationError, validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
import { API, getBoardProfile, readSettings, writeSettings } from '../../api/app';
|
import { API, getBoardProfile, readSettings, writeSettings } from '../../api/app';
|
||||||
import { BOARD_PROFILES } from '../main/types';
|
import { BOARD_PROFILES } from '../main/types';
|
||||||
@@ -140,10 +140,9 @@ const ApplicationSettings = () => {
|
|||||||
try {
|
try {
|
||||||
setFieldErrors(undefined);
|
setFieldErrors(undefined);
|
||||||
await validate(createSettingsValidator(data), data);
|
await validate(createSettingsValidator(data), data);
|
||||||
|
await saveData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setFieldErrors((error as ValidationError).fieldErrors);
|
setFieldErrors((error as ValidationError).fieldErrors);
|
||||||
} finally {
|
|
||||||
await saveData();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
@@ -27,6 +26,7 @@ import {
|
|||||||
SingleUpload,
|
SingleUpload,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import { saveFile } from 'utils';
|
import { saveFile } from 'utils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
||||||
@@ -17,7 +16,6 @@ import {
|
|||||||
|
|
||||||
import * as MqttApi from 'api/mqtt';
|
import * as MqttApi from 'api/mqtt';
|
||||||
|
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
BlockNavigation,
|
BlockNavigation,
|
||||||
@@ -28,10 +26,12 @@ import {
|
|||||||
ValidatedTextField,
|
ValidatedTextField,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { MqttSettingsType } from 'types';
|
import type { MqttSettingsType } from 'types';
|
||||||
import { numberValue, updateValueDirty, useRest } from 'utils';
|
import { numberValue, updateValueDirty, useRest } from 'utils';
|
||||||
import { ValidationError, createMqttSettingsValidator, validate } from 'validators';
|
import { ValidationError, createMqttSettingsValidator, validate } from 'validators';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
import { callAction } from '../../api/app';
|
import { callAction } from '../../api/app';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
@@ -23,7 +22,6 @@ import { readNTPSettings } from 'api/ntp';
|
|||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { updateState } from 'alova/client';
|
import { updateState } from 'alova/client';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
BlockNavigation,
|
BlockNavigation,
|
||||||
@@ -33,11 +31,13 @@ import {
|
|||||||
ValidatedTextField,
|
ValidatedTextField,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { NTPSettingsType, Time } from 'types';
|
import type { NTPSettingsType, Time } from 'types';
|
||||||
import { formatLocalDateTime, updateValueDirty, useRest } from 'utils';
|
import { formatLocalDateTime, updateValueDirty, useRest } from 'utils';
|
||||||
import { ValidationError, validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
import { NTP_SETTINGS_VALIDATOR } from 'validators/ntp';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
import { TIME_ZONES, selectedTimeZone, useTimeZoneSelectItems } from './TZ';
|
import { TIME_ZONES, selectedTimeZone, useTimeZoneSelectItems } from './TZ';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { memo, useContext, useMemo, useState } from 'react';
|
import { memo, useContext, useMemo, useState } from 'react';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
@@ -39,6 +38,7 @@ import {
|
|||||||
SingleUpload,
|
SingleUpload,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { TranslationFunctions } from 'i18n/i18n-types';
|
import type { TranslationFunctions } from 'i18n/i18n-types';
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import { memo, useState } from 'react';
|
import { memo, useState } from 'react';
|
||||||
import {
|
import { Outlet, useMatch, useNavigate } from 'react-router';
|
||||||
Navigate,
|
|
||||||
Route,
|
|
||||||
Routes,
|
|
||||||
matchRoutes,
|
|
||||||
useLocation,
|
|
||||||
useNavigate
|
|
||||||
} from 'react-router';
|
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
|
|
||||||
@@ -14,27 +7,13 @@ import { RouterTabs, useLayoutTitle } from 'components';
|
|||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { WiFiNetwork } from 'types';
|
import type { WiFiNetwork } from 'types';
|
||||||
|
|
||||||
import NetworkSettings from './NetworkSettings';
|
|
||||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||||
import WiFiNetworkScanner from './WiFiNetworkScanner';
|
|
||||||
|
|
||||||
const Network = () => {
|
const Network = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.NETWORK(0));
|
useLayoutTitle(LL.NETWORK(0));
|
||||||
|
|
||||||
// this also works!
|
const routerTab = useMatch('/settings/network/:tab')?.pathname || false;
|
||||||
// const routerTab = useMatch(`settings/network/:path/*`)?.pathname || false;
|
|
||||||
const matchedRoutes = matchRoutes(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
path: '/settings/network/settings',
|
|
||||||
element: <NetworkSettings />
|
|
||||||
},
|
|
||||||
{ path: '/settings/network/scan', element: <WiFiNetworkScanner /> }
|
|
||||||
],
|
|
||||||
useLocation()
|
|
||||||
);
|
|
||||||
const routerTab = matchedRoutes?.[0]?.route.path || false;
|
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -64,14 +43,7 @@ const Network = () => {
|
|||||||
/>
|
/>
|
||||||
<Tab value="/settings/network/scan" label={LL.NETWORK_SCAN()} />
|
<Tab value="/settings/network/scan" label={LL.NETWORK_SCAN()} />
|
||||||
</RouterTabs>
|
</RouterTabs>
|
||||||
<Routes>
|
<Outlet />
|
||||||
<Route path="scan" element={<WiFiNetworkScanner />} />
|
|
||||||
<Route path="settings" element={<NetworkSettings />} />
|
|
||||||
<Route
|
|
||||||
path="*"
|
|
||||||
element={<Navigate replace to="/settings/network/settings" />}
|
|
||||||
/>
|
|
||||||
</Routes>
|
|
||||||
</WiFiConnectionContext.Provider>
|
</WiFiConnectionContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { memo, useCallback, useContext, useEffect, useState } from 'react';
|
import { memo, useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
@@ -26,7 +25,6 @@ import { API } from 'api/app';
|
|||||||
|
|
||||||
import { updateState, useRequest } from 'alova/client';
|
import { updateState, useRequest } from 'alova/client';
|
||||||
import type { APIcall } from 'app/main/types';
|
import type { APIcall } from 'app/main/types';
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
BlockNavigation,
|
BlockNavigation,
|
||||||
@@ -37,11 +35,13 @@ import {
|
|||||||
ValidatedPasswordField,
|
ValidatedPasswordField,
|
||||||
ValidatedTextField
|
ValidatedTextField
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { NetworkSettingsType } from 'types';
|
import type { NetworkSettingsType } from 'types';
|
||||||
import { updateValueDirty, useRest } from 'utils';
|
import { updateValueDirty, useRest } from 'utils';
|
||||||
import { ValidationError, validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
import { createNetworkSettingsValidator } from 'validators/network';
|
import { createNetworkSettingsValidator } from 'validators/network';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
import SystemMonitor from '../../status/SystemMonitor';
|
import SystemMonitor from '../../status/SystemMonitor';
|
||||||
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
import { WiFiConnectionContext } from './WiFiConnectionContext';
|
||||||
|
|||||||
@@ -1,31 +1,16 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { Navigate, Route, Routes, matchRoutes, useLocation } from 'react-router';
|
import { Outlet, useMatch } from 'react-router';
|
||||||
|
|
||||||
import { Tab } from '@mui/material';
|
import { Tab } from '@mui/material';
|
||||||
|
|
||||||
import { RouterTabs, useLayoutTitle } from 'components';
|
import { RouterTabs, useLayoutTitle } from 'components';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import ManageUsers from './ManageUsers';
|
|
||||||
import SecuritySettings from './SecuritySettings';
|
|
||||||
|
|
||||||
const Security = () => {
|
const Security = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.SECURITY(0));
|
useLayoutTitle(LL.SECURITY(0));
|
||||||
|
|
||||||
const location = useLocation();
|
const routerTab = useMatch('/settings/security/:tab')?.pathname || false;
|
||||||
|
|
||||||
const matchedRoutes = matchRoutes(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
path: '/settings/security/settings',
|
|
||||||
element: <ManageUsers />
|
|
||||||
},
|
|
||||||
{ path: '/settings/security/users', element: <SecuritySettings /> }
|
|
||||||
],
|
|
||||||
location
|
|
||||||
);
|
|
||||||
const routerTab = matchedRoutes?.[0]?.route.path || false;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -36,14 +21,7 @@ const Security = () => {
|
|||||||
/>
|
/>
|
||||||
<Tab value="/settings/security/users" label={LL.MANAGE_USERS()} />
|
<Tab value="/settings/security/users" label={LL.MANAGE_USERS()} />
|
||||||
</RouterTabs>
|
</RouterTabs>
|
||||||
<Routes>
|
<Outlet />
|
||||||
<Route path="users" element={<ManageUsers />} />
|
|
||||||
<Route path="settings" element={<SecuritySettings />} />
|
|
||||||
<Route
|
|
||||||
path="*"
|
|
||||||
element={<Navigate replace to="/settings/security/settings" />}
|
|
||||||
/>
|
|
||||||
</Routes>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { Button } from '@mui/material';
|
|||||||
|
|
||||||
import * as SecurityApi from 'api/security';
|
import * as SecurityApi from 'api/security';
|
||||||
|
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import {
|
import {
|
||||||
BlockNavigation,
|
BlockNavigation,
|
||||||
ButtonRow,
|
ButtonRow,
|
||||||
@@ -20,6 +19,7 @@ import { useI18nContext } from 'i18n/i18n-react';
|
|||||||
import type { SecuritySettingsType } from 'types';
|
import type { SecuritySettingsType } from 'types';
|
||||||
import { updateValueDirty, useRest } from 'utils';
|
import { updateValueDirty, useRest } from 'utils';
|
||||||
import { SECURITY_SETTINGS_VALIDATOR, ValidationError, validate } from 'validators';
|
import { SECURITY_SETTINGS_VALIDATOR, ValidationError, validate } from 'validators';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
const SecuritySettings = () => {
|
const SecuritySettings = () => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import { dialogStyle } from 'CustomTheme';
|
import { dialogStyle } from 'CustomTheme';
|
||||||
import type Schema from 'async-validator';
|
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
|
||||||
import {
|
import {
|
||||||
BlockFormControlLabel,
|
BlockFormControlLabel,
|
||||||
ValidatedPasswordField,
|
ValidatedPasswordField,
|
||||||
@@ -25,6 +23,8 @@ import { useI18nContext } from 'i18n/i18n-react';
|
|||||||
import type { UserType } from 'types';
|
import type { UserType } from 'types';
|
||||||
import { updateValue } from 'utils';
|
import { updateValue } from 'utils';
|
||||||
import { ValidationError, validate } from 'validators';
|
import { ValidationError, validate } from 'validators';
|
||||||
|
import type Schema from 'validators/schema';
|
||||||
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
interface UserFormProps {
|
interface UserFormProps {
|
||||||
creating: boolean;
|
creating: boolean;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { memo, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
import { memo, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import DownloadIcon from '@mui/icons-material/GetApp';
|
import DownloadIcon from '@mui/icons-material/GetApp';
|
||||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||||
@@ -26,6 +25,7 @@ import {
|
|||||||
SectionContent,
|
SectionContent,
|
||||||
useLayoutTitle
|
useLayoutTitle
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { LogEntry, LogSettings } from 'types';
|
import type { LogEntry, LogSettings } from 'types';
|
||||||
import { LogLevel } from 'types';
|
import { LogLevel } from 'types';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { FC } from 'react';
|
|||||||
import { FormHelperText, TextField } from '@mui/material';
|
import { FormHelperText, TextField } from '@mui/material';
|
||||||
import type { TextFieldProps } from '@mui/material';
|
import type { TextFieldProps } from '@mui/material';
|
||||||
|
|
||||||
import type { ValidateFieldsError } from 'async-validator';
|
import type { ValidateFieldsError } from 'validators/schema';
|
||||||
|
|
||||||
interface ValidatedFieldProps {
|
interface ValidatedFieldProps {
|
||||||
fieldErrors?: ValidateFieldsError;
|
fieldErrors?: ValidateFieldsError;
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import { memo, useContext } from 'react';
|
import { memo, useContext } from 'react';
|
||||||
import type { FC } from 'react';
|
import { Navigate, Outlet } from 'react-router';
|
||||||
import { Navigate } from 'react-router';
|
|
||||||
|
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
import type { RequiredChildrenProps } from 'utils';
|
|
||||||
|
|
||||||
const RequireAdmin: FC<RequiredChildrenProps> = ({ children }) => {
|
// Layout-route guard: renders nested admin routes only for admins, otherwise
|
||||||
const authenticatedContext = useContext(AuthenticatedContext);
|
// redirects home. Must be used inside the authenticated route subtree so that
|
||||||
return authenticatedContext.me.admin ? (
|
// AuthenticatedContext (and `me`) is available.
|
||||||
<>{children}</>
|
const RequireAdmin = () => {
|
||||||
) : (
|
const { me } = useContext(AuthenticatedContext);
|
||||||
<Navigate replace to="/" />
|
return me.admin ? <Outlet /> : <Navigate replace to="/" />;
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(RequireAdmin);
|
export default memo(RequireAdmin);
|
||||||
|
|||||||
35
interface/src/components/routing/RootRedirect.tsx
Normal file
35
interface/src/components/routing/RootRedirect.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { memo, useContext, useEffect, useRef } from 'react';
|
||||||
|
import { Navigate } from 'react-router';
|
||||||
|
|
||||||
|
import { toast } from 'components/toast';
|
||||||
|
import { AuthenticationContext } from 'contexts/authentication';
|
||||||
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
|
type RootRedirectKind = 'unauthorized' | 'fileUpdated';
|
||||||
|
|
||||||
|
// Shows a one-shot toast and bounces back to "/". Used by the /unauthorized and
|
||||||
|
// /fileUpdated routes. Resolves its own i18n message so it can be used directly
|
||||||
|
// as a static route element.
|
||||||
|
const RootRedirect = ({ kind }: { kind: RootRedirectKind }) => {
|
||||||
|
const { LL } = useI18nContext();
|
||||||
|
const { signOut } = useContext(AuthenticationContext);
|
||||||
|
const hasShownToast = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Guard against StrictMode double-invoke / re-renders.
|
||||||
|
if (hasShownToast.current) return;
|
||||||
|
hasShownToast.current = true;
|
||||||
|
|
||||||
|
if (kind === 'unauthorized') {
|
||||||
|
signOut(false);
|
||||||
|
toast.success(LL.PLEASE_SIGNIN());
|
||||||
|
} else {
|
||||||
|
toast.success(LL.UPLOAD_SUCCESSFUL());
|
||||||
|
}
|
||||||
|
// Run once on mount.
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <Navigate to="/" replace />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(RootRedirect);
|
||||||
@@ -2,3 +2,4 @@ export { default as RouterTabs } from './RouterTabs';
|
|||||||
export { default as RequireAdmin } from './RequireAdmin';
|
export { default as RequireAdmin } from './RequireAdmin';
|
||||||
export { default as RequireAuthenticated } from './RequireAuthenticated';
|
export { default as RequireAuthenticated } from './RequireAuthenticated';
|
||||||
export { default as RequireUnauthenticated } from './RequireUnauthenticated';
|
export { default as RequireUnauthenticated } from './RequireUnauthenticated';
|
||||||
|
export { default as RootRedirect } from './RootRedirect';
|
||||||
|
|||||||
140
interface/src/components/toast/Toaster.tsx
Normal file
140
interface/src/components/toast/Toaster.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { memo, useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
||||||
|
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import Alert from '@mui/material/Alert';
|
||||||
|
import Grow from '@mui/material/Grow';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import LinearProgress from '@mui/material/LinearProgress';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type ToastItem,
|
||||||
|
type ToastSeverity,
|
||||||
|
getSnapshot,
|
||||||
|
removeToast,
|
||||||
|
subscribe
|
||||||
|
} from './toastStore';
|
||||||
|
|
||||||
|
const AUTO_CLOSE_MS = 3000;
|
||||||
|
const TICK_MS = 50;
|
||||||
|
|
||||||
|
// Semantic accent colors users already expect:
|
||||||
|
// success → green, error → red, warning → amber, info → blue.
|
||||||
|
const ACCENT: Record<ToastSeverity, string> = {
|
||||||
|
success: '#16a34a',
|
||||||
|
error: '#dc2626',
|
||||||
|
warning: '#f59e0b',
|
||||||
|
info: '#2563eb'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Single toast row: owns its auto-dismiss timer + countdown progress bar, pauses
|
||||||
|
// while the window is unfocused (matching react-toastify's pauseOnFocusLoss).
|
||||||
|
const ToastRow = memo(({ item }: { item: ToastItem }) => {
|
||||||
|
const [open, setOpen] = useState(true);
|
||||||
|
const [remaining, setRemaining] = useState(AUTO_CLOSE_MS);
|
||||||
|
const remainingRef = useRef(AUTO_CLOSE_MS);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let paused = document.hidden;
|
||||||
|
const onVisibility = () => {
|
||||||
|
paused = document.hidden;
|
||||||
|
};
|
||||||
|
document.addEventListener('visibilitychange', onVisibility);
|
||||||
|
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (paused) return;
|
||||||
|
remainingRef.current = Math.max(0, remainingRef.current - TICK_MS);
|
||||||
|
setRemaining(remainingRef.current);
|
||||||
|
if (remainingRef.current === 0) setOpen(false);
|
||||||
|
}, TICK_MS);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer);
|
||||||
|
document.removeEventListener('visibilitychange', onVisibility);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const accent = ACCENT[item.severity];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grow in={open} onExited={() => removeToast(item.id)}>
|
||||||
|
<Alert
|
||||||
|
severity={item.severity}
|
||||||
|
variant="standard"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
action={
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
aria-label="close"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
sx={{ color: '#9ca3af', '&:hover': { color: '#374151' } }}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
width: 'fit-content',
|
||||||
|
minWidth: 300,
|
||||||
|
maxWidth: 360,
|
||||||
|
cursor: 'pointer',
|
||||||
|
overflow: 'hidden',
|
||||||
|
borderRadius: '8px',
|
||||||
|
bgcolor: '#f3f4f6',
|
||||||
|
color: '#1f2937',
|
||||||
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.18)',
|
||||||
|
borderLeft: `4px solid ${accent}`,
|
||||||
|
alignItems: 'center',
|
||||||
|
'& .MuiAlert-icon': { color: accent, alignItems: 'center' },
|
||||||
|
// '& .MuiAlert-message': {
|
||||||
|
// py: '8px',
|
||||||
|
// color: '#1f2937'
|
||||||
|
// },
|
||||||
|
'& .MuiAlert-action': { alignItems: 'center', pt: 0, mr: '-4px' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.message}
|
||||||
|
<LinearProgress
|
||||||
|
variant="determinate"
|
||||||
|
value={(remaining / AUTO_CLOSE_MS) * 100}
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
height: 3,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'& .MuiLinearProgress-bar': { backgroundColor: accent, opacity: 0.55 }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Alert>
|
||||||
|
</Grow>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const Toaster = memo(() => {
|
||||||
|
const toasts = useSyncExternalStore(subscribe, getSnapshot);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
spacing={1}
|
||||||
|
sx={{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: 16,
|
||||||
|
left: 16,
|
||||||
|
zIndex: (theme) => theme.zIndex.snackbar,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
'& > *': { pointerEvents: 'auto' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{toasts.map((item) => (
|
||||||
|
<ToastRow key={item.id} item={item} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Toaster;
|
||||||
3
interface/src/components/toast/index.ts
Normal file
3
interface/src/components/toast/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as Toaster } from './Toaster';
|
||||||
|
export { toast } from './toastStore';
|
||||||
|
export type { ToastSeverity } from './toastStore';
|
||||||
47
interface/src/components/toast/toastStore.ts
Normal file
47
interface/src/components/toast/toastStore.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
export type ToastSeverity = 'success' | 'error' | 'info' | 'warning';
|
||||||
|
|
||||||
|
export interface ToastItem {
|
||||||
|
id: number;
|
||||||
|
severity: ToastSeverity;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let toasts: ToastItem[] = [];
|
||||||
|
let nextId = 1;
|
||||||
|
const listeners = new Set<() => void>();
|
||||||
|
|
||||||
|
const emit = () => {
|
||||||
|
for (const listener of listeners) listener();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const subscribe = (listener: () => void): (() => void) => {
|
||||||
|
listeners.add(listener);
|
||||||
|
return () => {
|
||||||
|
listeners.delete(listener);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSnapshot = (): ToastItem[] => toasts;
|
||||||
|
|
||||||
|
const add = (severity: ToastSeverity, message: string): number => {
|
||||||
|
const id = nextId++;
|
||||||
|
toasts = [...toasts, { id, severity, message }];
|
||||||
|
emit();
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeToast = (id: number): void => {
|
||||||
|
const next = toasts.filter((t) => t.id !== id);
|
||||||
|
if (next.length !== toasts.length) {
|
||||||
|
toasts = next;
|
||||||
|
emit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Imperative API mirroring the subset of react-toastify used across the app.
|
||||||
|
export const toast = {
|
||||||
|
success: (message: string) => add('success', message),
|
||||||
|
error: (message: string) => add('error', message),
|
||||||
|
info: (message: string) => add('info', message),
|
||||||
|
warning: (message: string) => add('warning', message)
|
||||||
|
};
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
|
||||||
@@ -28,6 +27,7 @@ import { callAction } from 'api/app';
|
|||||||
|
|
||||||
import { dialogStyle } from '@/CustomTheme';
|
import { dialogStyle } from '@/CustomTheme';
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
const DocumentUploader = styled(Box)<{ active?: boolean }>(({ theme, active }) => ({
|
const DocumentUploader = styled(Box)<{ active?: boolean }>(({ theme, active }) => ({
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import { Box, Button, Typography } from '@mui/material';
|
import { Box, Button, Typography } from '@mui/material';
|
||||||
@@ -7,6 +6,7 @@ import { Box, Button, Typography } from '@mui/material';
|
|||||||
import * as SystemApi from 'api/system';
|
import * as SystemApi from 'api/system';
|
||||||
|
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
import DragNdrop from './DragNdrop';
|
import DragNdrop from './DragNdrop';
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { redirect } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import { callAction } from 'api/app';
|
import { callAction } from 'api/app';
|
||||||
import { ACCESS_TOKEN } from 'api/endpoints';
|
import { ACCESS_TOKEN } from 'api/endpoints';
|
||||||
@@ -10,6 +9,7 @@ import * as AuthenticationApi from 'components/routing/authentication';
|
|||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
import { LoadingSpinner } from 'components';
|
import { LoadingSpinner } from 'components';
|
||||||
import { verifyAuthorization } from 'components/routing/authentication';
|
import { verifyAuthorization } from 'components/routing/authentication';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
import type { Me, VersionsResponse } from 'types';
|
import type { Me, VersionsResponse } from 'types';
|
||||||
import type { RequiredChildrenProps } from 'utils';
|
import type { RequiredChildrenProps } from 'utils';
|
||||||
@@ -18,6 +18,7 @@ import { AuthenticationContext } from './context';
|
|||||||
|
|
||||||
const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [initialized, setInitialized] = useState<boolean>(false);
|
const [initialized, setInitialized] = useState<boolean>(false);
|
||||||
const [me, setMe] = useState<Me>();
|
const [me, setMe] = useState<Me>();
|
||||||
@@ -60,7 +61,7 @@ const Authentication: FC<RequiredChildrenProps> = ({ children }) => {
|
|||||||
setMe(undefined);
|
setMe(undefined);
|
||||||
setVersions(undefined);
|
setVersions(undefined);
|
||||||
if (doRedirect) {
|
if (doRedirect) {
|
||||||
redirect('/');
|
void navigate('/', { replace: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import {
|
import {
|
||||||
|
Navigate,
|
||||||
|
Outlet,
|
||||||
Route,
|
Route,
|
||||||
RouterProvider,
|
RouterProvider,
|
||||||
createBrowserRouter,
|
createBrowserRouter,
|
||||||
@@ -9,6 +11,45 @@ import {
|
|||||||
} from 'react-router';
|
} from 'react-router';
|
||||||
|
|
||||||
import App from 'App';
|
import App from 'App';
|
||||||
|
import SignIn from 'SignIn';
|
||||||
|
import Commands from 'app/main/Commands';
|
||||||
|
import CustomEntities from 'app/main/CustomEntities';
|
||||||
|
import Customizations from 'app/main/Customizations';
|
||||||
|
import Dashboard from 'app/main/Dashboard';
|
||||||
|
import Devices from 'app/main/Devices';
|
||||||
|
import Help from 'app/main/Help';
|
||||||
|
import Modules from 'app/main/Modules';
|
||||||
|
import Scheduler from 'app/main/Scheduler';
|
||||||
|
import Sensors from 'app/main/Sensors';
|
||||||
|
import UserProfile from 'app/main/UserProfile';
|
||||||
|
import APSettings from 'app/settings/APSettings';
|
||||||
|
import ApplicationSettings from 'app/settings/ApplicationSettings';
|
||||||
|
import DownloadUpload from 'app/settings/DownloadUpload';
|
||||||
|
import MqttSettings from 'app/settings/MqttSettings';
|
||||||
|
import NTPSettings from 'app/settings/NTPSettings';
|
||||||
|
import Settings from 'app/settings/Settings';
|
||||||
|
import Version from 'app/settings/Version';
|
||||||
|
import Network from 'app/settings/network/Network';
|
||||||
|
import NetworkSettings from 'app/settings/network/NetworkSettings';
|
||||||
|
import WiFiNetworkScanner from 'app/settings/network/WiFiNetworkScanner';
|
||||||
|
import ManageUsers from 'app/settings/security/ManageUsers';
|
||||||
|
import Security from 'app/settings/security/Security';
|
||||||
|
import SecuritySettings from 'app/settings/security/SecuritySettings';
|
||||||
|
import APStatus from 'app/status/APStatus';
|
||||||
|
import Activity from 'app/status/Activity';
|
||||||
|
import HardwareStatus from 'app/status/HardwareStatus';
|
||||||
|
import MqttStatus from 'app/status/MqttStatus';
|
||||||
|
import NTPStatus from 'app/status/NTPStatus';
|
||||||
|
import NetworkStatus from 'app/status/NetworkStatus';
|
||||||
|
import Status from 'app/status/Status';
|
||||||
|
import SystemLog from 'app/status/SystemLog';
|
||||||
|
import {
|
||||||
|
Layout,
|
||||||
|
RequireAdmin,
|
||||||
|
RequireAuthenticated,
|
||||||
|
RequireUnauthenticated,
|
||||||
|
RootRedirect
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
const errorPageStyles = {
|
const errorPageStyles = {
|
||||||
container: {
|
container: {
|
||||||
@@ -105,7 +146,87 @@ function ErrorPage() {
|
|||||||
|
|
||||||
const router = createBrowserRouter(
|
const router = createBrowserRouter(
|
||||||
createRoutesFromElements(
|
createRoutesFromElements(
|
||||||
<Route path="/*" element={<App />} errorElement={<ErrorPage />} />
|
<Route path="/" element={<App />} errorElement={<ErrorPage />}>
|
||||||
|
<Route
|
||||||
|
index
|
||||||
|
element={
|
||||||
|
<RequireUnauthenticated>
|
||||||
|
<SignIn />
|
||||||
|
</RequireUnauthenticated>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route path="unauthorized" element={<RootRedirect kind="unauthorized" />} />
|
||||||
|
<Route path="fileUpdated" element={<RootRedirect kind="fileUpdated" />} />
|
||||||
|
|
||||||
|
<Route
|
||||||
|
element={
|
||||||
|
<RequireAuthenticated>
|
||||||
|
<Layout>
|
||||||
|
<Outlet />
|
||||||
|
</Layout>
|
||||||
|
</RequireAuthenticated>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route path="dashboard/*" element={<Dashboard />} />
|
||||||
|
<Route path="devices/*" element={<Devices />} />
|
||||||
|
<Route path="sensors/*" element={<Sensors />} />
|
||||||
|
<Route path="help/*" element={<Help />} />
|
||||||
|
<Route path="user/*" element={<UserProfile />} />
|
||||||
|
|
||||||
|
<Route path="status/*" element={<Status />} />
|
||||||
|
<Route path="status/hardwarestatus/*" element={<HardwareStatus />} />
|
||||||
|
<Route path="status/activity" element={<Activity />} />
|
||||||
|
<Route path="status/log" element={<SystemLog />} />
|
||||||
|
<Route path="status/mqtt" element={<MqttStatus />} />
|
||||||
|
<Route path="status/ntp" element={<NTPStatus />} />
|
||||||
|
<Route path="status/ap" element={<APStatus />} />
|
||||||
|
<Route path="status/network" element={<NetworkStatus />} />
|
||||||
|
|
||||||
|
<Route element={<RequireAdmin />}>
|
||||||
|
<Route path="settings" element={<Settings />} />
|
||||||
|
<Route path="settings/version" element={<Version />} />
|
||||||
|
<Route path="settings/application" element={<ApplicationSettings />} />
|
||||||
|
<Route path="settings/mqtt" element={<MqttSettings />} />
|
||||||
|
<Route path="settings/ntp" element={<NTPSettings />} />
|
||||||
|
<Route path="settings/ap" element={<APSettings />} />
|
||||||
|
<Route path="settings/modules" element={<Modules />} />
|
||||||
|
<Route path="settings/downloadUpload" element={<DownloadUpload />} />
|
||||||
|
|
||||||
|
<Route path="settings/network" element={<Network />}>
|
||||||
|
<Route
|
||||||
|
index
|
||||||
|
element={<Navigate replace to="/settings/network/settings" />}
|
||||||
|
/>
|
||||||
|
<Route path="settings" element={<NetworkSettings />} />
|
||||||
|
<Route path="scan" element={<WiFiNetworkScanner />} />
|
||||||
|
<Route
|
||||||
|
path="*"
|
||||||
|
element={<Navigate replace to="/settings/network/settings" />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="settings/security" element={<Security />}>
|
||||||
|
<Route
|
||||||
|
index
|
||||||
|
element={<Navigate replace to="/settings/security/settings" />}
|
||||||
|
/>
|
||||||
|
<Route path="settings" element={<SecuritySettings />} />
|
||||||
|
<Route path="users" element={<ManageUsers />} />
|
||||||
|
<Route
|
||||||
|
path="*"
|
||||||
|
element={<Navigate replace to="/settings/security/settings" />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="customizations" element={<Customizations />} />
|
||||||
|
<Route path="commands" element={<Commands />} />
|
||||||
|
<Route path="scheduler" element={<Scheduler />} />
|
||||||
|
<Route path="customentities" element={<CustomEntities />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="*" element={<Navigate replace to="/" />} />
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
62
interface/src/preact-react-shim.ts
Normal file
62
interface/src/preact-react-shim.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// preact/compat shim used as the `react` alias.
|
||||||
|
//
|
||||||
|
// react-router v8 statically imports/uses a couple of React 19 APIs that
|
||||||
|
// preact/compat doesn't implement. Aliasing `react` straight to preact/compat
|
||||||
|
// therefore (a) fails Rolldown's static export analysis during dependency
|
||||||
|
// optimization / build and (b) could crash at runtime. We re-export everything
|
||||||
|
// from preact/compat and add the missing pieces:
|
||||||
|
//
|
||||||
|
// - useOptimistic: called unconditionally at the top of `RouterProvider`
|
||||||
|
// (lib/components.js). The optimistic setter is only invoked when
|
||||||
|
// `unstable_useTransitions === true`, which this app doesn't enable, so a
|
||||||
|
// passthrough state + no-op setter is correct.
|
||||||
|
// - use: only invoked (on a promise) in react-router's RSC server-SSR path
|
||||||
|
// (lib/rsc/server.ssr.js), which a client-only `createBrowserRouter` app
|
||||||
|
// never executes. It still must exist as an export to avoid Rolldown's
|
||||||
|
// IMPORT_IS_UNDEFINED warning; we provide a Suspense-style promise unwrap
|
||||||
|
// just in case it is ever reached.
|
||||||
|
|
||||||
|
// preact/compat's type declarations use CommonJS `export =`, which TypeScript
|
||||||
|
// won't let us `export *` from, but Rolldown/Vite correctly re-export its named
|
||||||
|
// members at runtime (this is what makes react-router's `React.useState` etc.
|
||||||
|
// resolve through the alias).
|
||||||
|
// @ts-expect-error -- CJS `export =` interop; valid at bundle time.
|
||||||
|
export * from 'preact/compat';
|
||||||
|
export { default } from 'preact/compat';
|
||||||
|
|
||||||
|
export function useOptimistic<S>(passthrough: S): [S, (action: unknown) => void] {
|
||||||
|
return [passthrough, () => {}];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TrackedThenable<T> extends PromiseLike<T> {
|
||||||
|
status?: 'pending' | 'fulfilled' | 'rejected';
|
||||||
|
value?: T;
|
||||||
|
reason?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// React 19's `use`, narrowed to the promise-unwrap (Suspense) behavior that
|
||||||
|
// react-router relies on.
|
||||||
|
export function use<T>(usable: TrackedThenable<T>): T {
|
||||||
|
switch (usable.status) {
|
||||||
|
case 'fulfilled':
|
||||||
|
return usable.value as T;
|
||||||
|
case 'rejected':
|
||||||
|
throw usable.reason;
|
||||||
|
default:
|
||||||
|
if (usable.status === undefined) {
|
||||||
|
usable.status = 'pending';
|
||||||
|
void usable.then(
|
||||||
|
(value) => {
|
||||||
|
usable.status = 'fulfilled';
|
||||||
|
usable.value = value;
|
||||||
|
},
|
||||||
|
(reason) => {
|
||||||
|
usable.status = 'rejected';
|
||||||
|
usable.reason = reason;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/only-throw-error -- Suspense throws the thenable
|
||||||
|
throw usable;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useBlocker } from 'react-router';
|
import { useBlocker } from 'react-router';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
import type { AlovaGenerics, Method } from 'alova';
|
import type { AlovaGenerics, Method } from 'alova';
|
||||||
import { useRequest } from 'alova/client';
|
import { useRequest } from 'alova/client';
|
||||||
|
import { toast } from 'components/toast';
|
||||||
import { useI18nContext } from 'i18n/i18n-react';
|
import { useI18nContext } from 'i18n/i18n-react';
|
||||||
|
|
||||||
export interface RestRequestOptions<D> {
|
export interface RestRequestOptions<D> {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { isAPEnabled } from 'app/settings/APSettings';
|
import { isAPEnabled } from 'app/settings/APSettings';
|
||||||
import Schema from 'async-validator';
|
|
||||||
import type { APSettingsType } from 'types';
|
import type { APSettingsType } from 'types';
|
||||||
|
import Schema from 'validators/schema';
|
||||||
|
|
||||||
import { IP_ADDRESS_VALIDATOR } from './shared';
|
import { IP_ADDRESS_VALIDATOR } from './shared';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Schema from 'async-validator';
|
import Schema from 'validators/schema';
|
||||||
|
|
||||||
export const SIGN_IN_REQUEST_VALIDATOR = new Schema({
|
export const SIGN_IN_REQUEST_VALIDATOR = new Schema({
|
||||||
username: {
|
username: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Schema from 'async-validator';
|
|
||||||
import type { MqttSettingsType } from 'types';
|
import type { MqttSettingsType } from 'types';
|
||||||
|
import Schema from 'validators/schema';
|
||||||
|
|
||||||
import { IP_OR_HOSTNAME_VALIDATOR } from './shared';
|
import { IP_OR_HOSTNAME_VALIDATOR } from './shared';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Schema from 'async-validator';
|
|
||||||
import type { NetworkSettingsType } from 'types';
|
import type { NetworkSettingsType } from 'types';
|
||||||
|
import Schema from 'validators/schema';
|
||||||
|
|
||||||
import { HOSTNAME_VALIDATOR, IP_ADDRESS_VALIDATOR } from './shared';
|
import { HOSTNAME_VALIDATOR, IP_ADDRESS_VALIDATOR } from './shared';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Schema from 'async-validator';
|
import Schema from 'validators/schema';
|
||||||
|
|
||||||
import { IP_OR_HOSTNAME_VALIDATOR } from './shared';
|
import { IP_OR_HOSTNAME_VALIDATOR } from './shared';
|
||||||
|
|
||||||
|
|||||||
182
interface/src/validators/schema.ts
Normal file
182
interface/src/validators/schema.ts
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
// Minimal drop-in replacement for the subset of `async-validator` used by this
|
||||||
|
// app's form validators. It intentionally mirrors async-validator's runtime
|
||||||
|
// semantics for the rule features in use (required, type string/number with
|
||||||
|
// min/max/pattern, and custom `validator(rule, value, callback)` functions) so
|
||||||
|
// the existing schema definitions and the `ValidateFieldsError` consumers keep
|
||||||
|
// working unchanged.
|
||||||
|
//
|
||||||
|
// Notable async-validator semantics preserved:
|
||||||
|
// - A non-required built-in rule is skipped when the value is "empty"
|
||||||
|
// (undefined / null / ''); `0` is NOT empty.
|
||||||
|
// - Custom `validator` functions are always invoked (they guard empties
|
||||||
|
// themselves), matching async-validator's behavior for validator rules.
|
||||||
|
// - `min`/`max` mean numeric bounds for `type: 'number'` and length bounds
|
||||||
|
// otherwise.
|
||||||
|
// - All rule errors for a field are collected (no early-exit per field).
|
||||||
|
|
||||||
|
export interface ValidateError {
|
||||||
|
message?: string;
|
||||||
|
field?: string;
|
||||||
|
fieldValue?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ValidateFieldsError = Record<string, ValidateError[]>;
|
||||||
|
|
||||||
|
export interface InternalRuleItem {
|
||||||
|
field?: string;
|
||||||
|
fullField?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RuleValidator = (
|
||||||
|
rule: InternalRuleItem,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
value: any,
|
||||||
|
callback: (error?: string | Error) => void
|
||||||
|
) => void | Promise<void>;
|
||||||
|
|
||||||
|
export interface RuleItem {
|
||||||
|
type?: string;
|
||||||
|
required?: boolean;
|
||||||
|
pattern?: RegExp | string;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
message?: string;
|
||||||
|
validator?: RuleValidator;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Rules = Record<string, RuleItem | RuleItem[]>;
|
||||||
|
|
||||||
|
export interface ValidateOption {
|
||||||
|
first?: boolean;
|
||||||
|
firstFields?: boolean | string[];
|
||||||
|
suppressWarning?: boolean;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ValidateCallback = (
|
||||||
|
errors: ValidateError[] | null,
|
||||||
|
fields: ValidateFieldsError
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
const isEmpty = (value: unknown): boolean =>
|
||||||
|
value === undefined || value === null || value === '';
|
||||||
|
|
||||||
|
const runValidator = async (
|
||||||
|
rule: RuleItem,
|
||||||
|
field: string,
|
||||||
|
value: unknown
|
||||||
|
): Promise<string | undefined> => {
|
||||||
|
let captured: string | undefined;
|
||||||
|
const callback = (error?: string | Error) => {
|
||||||
|
if (error) captured = typeof error === 'string' ? error : error.message;
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const result = rule.validator!(
|
||||||
|
{ ...rule, field, fullField: field },
|
||||||
|
value,
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
await result;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
captured = error instanceof Error ? error.message : String(error);
|
||||||
|
}
|
||||||
|
return captured;
|
||||||
|
};
|
||||||
|
|
||||||
|
const runRule = async (
|
||||||
|
rule: RuleItem,
|
||||||
|
field: string,
|
||||||
|
value: unknown,
|
||||||
|
requiredMessage?: string
|
||||||
|
): Promise<string | undefined> => {
|
||||||
|
// Custom validators own their empty-value handling and run unconditionally.
|
||||||
|
if (typeof rule.validator === 'function') {
|
||||||
|
return runValidator(rule, field, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const empty = isEmpty(value);
|
||||||
|
|
||||||
|
if (rule.required && empty) {
|
||||||
|
return (
|
||||||
|
rule.message ?? requiredMessage?.replace('%s', field) ?? `${field} is required`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-required built-in rules don't validate empty values.
|
||||||
|
if (empty) return undefined;
|
||||||
|
|
||||||
|
if (rule.type === 'number') {
|
||||||
|
if (typeof value !== 'number' || Number.isNaN(value)) return rule.message;
|
||||||
|
if (typeof rule.min === 'number' && value < rule.min) return rule.message;
|
||||||
|
if (typeof rule.max === 'number' && value > rule.max) return rule.message;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// type 'string' or any rule carrying length/pattern constraints.
|
||||||
|
if (
|
||||||
|
rule.type === 'string' ||
|
||||||
|
typeof rule.min === 'number' ||
|
||||||
|
typeof rule.max === 'number' ||
|
||||||
|
rule.pattern != null
|
||||||
|
) {
|
||||||
|
const str = String(value);
|
||||||
|
if (typeof rule.min === 'number' && str.length < rule.min) return rule.message;
|
||||||
|
if (typeof rule.max === 'number' && str.length > rule.max) return rule.message;
|
||||||
|
if (rule.pattern != null) {
|
||||||
|
const re =
|
||||||
|
rule.pattern instanceof RegExp ? rule.pattern : new RegExp(rule.pattern);
|
||||||
|
if (!re.test(str)) return rule.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Schema {
|
||||||
|
private readonly rules: Rules;
|
||||||
|
private requiredMessage?: string;
|
||||||
|
|
||||||
|
constructor(descriptor: Rules) {
|
||||||
|
this.rules = descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors async-validator's `messages()`. Only the `required` template (with
|
||||||
|
// a `%s` placeholder for the field name) is consumed by this minimal schema.
|
||||||
|
messages(messages: { required?: string }): this {
|
||||||
|
this.requiredMessage = messages.required;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors async-validator's callback form. Always resolves (never rejects);
|
||||||
|
// callers (validators/shared.ts) inspect the `errors` argument.
|
||||||
|
async validate(
|
||||||
|
source: Record<string, unknown>,
|
||||||
|
_options?: ValidateOption,
|
||||||
|
callback?: ValidateCallback
|
||||||
|
): Promise<void> {
|
||||||
|
const fields: ValidateFieldsError = {};
|
||||||
|
const errors: ValidateError[] = [];
|
||||||
|
|
||||||
|
for (const field of Object.keys(this.rules)) {
|
||||||
|
const ruleDef = this.rules[field];
|
||||||
|
if (ruleDef === undefined) continue;
|
||||||
|
const ruleList = Array.isArray(ruleDef) ? ruleDef : [ruleDef];
|
||||||
|
const value = source[field];
|
||||||
|
|
||||||
|
for (const rule of ruleList) {
|
||||||
|
const message = await runRule(rule, field, value, this.requiredMessage);
|
||||||
|
if (message !== undefined) {
|
||||||
|
const error: ValidateError = { message, field, fieldValue: value };
|
||||||
|
(fields[field] ??= []).push(error);
|
||||||
|
errors.push(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback?.(errors.length > 0 ? errors : null, fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import Schema from 'async-validator';
|
|
||||||
import type { InternalRuleItem } from 'async-validator';
|
|
||||||
import type { UserType } from 'types';
|
import type { UserType } from 'types';
|
||||||
|
import Schema from 'validators/schema';
|
||||||
|
import type { InternalRuleItem } from 'validators/schema';
|
||||||
|
|
||||||
const USERNAME_PATTERN = /^[a-zA-Z0-9_\\.]{1,24}$/;
|
const USERNAME_PATTERN = /^[a-zA-Z0-9_\\.]{1,24}$/;
|
||||||
const JWT_SECRET_MAX_LENGTH = 64;
|
const JWT_SECRET_MAX_LENGTH = 64;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import type {
|
|||||||
InternalRuleItem,
|
InternalRuleItem,
|
||||||
ValidateFieldsError,
|
ValidateFieldsError,
|
||||||
ValidateOption
|
ValidateOption
|
||||||
} from 'async-validator';
|
} from 'validators/schema';
|
||||||
import type Schema from 'async-validator';
|
import type Schema from 'validators/schema';
|
||||||
|
|
||||||
export class ValidationError extends Error {
|
export class ValidationError extends Error {
|
||||||
readonly fieldErrors: ValidateFieldsError;
|
readonly fieldErrors: ValidateFieldsError;
|
||||||
|
|||||||
12
interface/src/vite-env.d.ts
vendored
12
interface/src/vite-env.d.ts
vendored
@@ -1 +1,13 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
// Optional comma-separated allow-list of locales to ship (e.g. "en,de,nl").
|
||||||
|
// Unset => all locales are bundled and embedded. `en` is always kept as the
|
||||||
|
// fallback. Consumed by App.tsx (UI language list) and progmem-generator.js
|
||||||
|
// (which locale chunks get embedded into firmware flash).
|
||||||
|
readonly VITE_APP_LOCALES?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,10 +16,18 @@ const CHUNK_SIZE_WARNING_LIMIT = 1024;
|
|||||||
const ASSETS_INLINE_LIMIT = 4096;
|
const ASSETS_INLINE_LIMIT = 4096;
|
||||||
|
|
||||||
// Common resolve aliases
|
// Common resolve aliases
|
||||||
|
// `react` points at a local shim (preact/compat + a useOptimistic stub) because
|
||||||
|
// react-router v8 statically imports useOptimistic from "react". See src/preact-react-shim.ts for details.
|
||||||
|
const REACT_SHIM = path.resolve(import.meta.dirname, 'src/preact-react-shim.ts');
|
||||||
|
// `react/jsx-runtime` is listed before `react` so the more specific alias wins
|
||||||
|
// (a bare `react` alias would otherwise also match `react/jsx-runtime`).
|
||||||
|
// NOTE: @preact/preset-vite is configured with `reactAliasesEnabled: false`
|
||||||
|
// so these aliases (not the preset's `react -> preact/compat`) are authoritative.
|
||||||
const RESOLVE_ALIASES = {
|
const RESOLVE_ALIASES = {
|
||||||
react: 'preact/compat',
|
'react/jsx-runtime': 'preact/jsx-runtime',
|
||||||
|
'react-dom/test-utils': 'preact/test-utils',
|
||||||
'react-dom': 'preact/compat',
|
'react-dom': 'preact/compat',
|
||||||
'react/jsx-runtime': 'preact/jsx-runtime'
|
react: REACT_SHIM
|
||||||
};
|
};
|
||||||
|
|
||||||
// Common resolve extensions - prioritize TypeScript/React files for Windows compatibility
|
// Common resolve extensions - prioritize TypeScript/React files for Windows compatibility
|
||||||
@@ -89,42 +97,30 @@ const bundleSizeReporter = (): Plugin => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Common preact plugin config
|
// Common preact plugin config
|
||||||
const createPreactPlugin = (devToolsEnabled: boolean) =>
|
const createPreactPlugin = (devToolsEnabled: boolean): PluginOption[] => {
|
||||||
preact({
|
const plugins = preact({
|
||||||
devToolsEnabled,
|
devToolsEnabled,
|
||||||
prefreshEnabled: false
|
prefreshEnabled: false,
|
||||||
|
// Disable the preset's built-in `react -> preact/compat` aliases so our
|
||||||
|
// RESOLVE_ALIASES (which routes `react` through the useOptimistic shim) win.
|
||||||
|
reactAliasesEnabled: false
|
||||||
});
|
});
|
||||||
|
// The preset always registers its devtools-only plugins (`preact:devtools`
|
||||||
// Patch preact/compat to export stub React 19 APIs (use, useOptimistic) so that
|
// and `preact:transform-hook-names`); the flag only gates their work, not
|
||||||
// react-router v7 doesn't trigger IMPORT_IS_UNDEFINED warnings from Rolldown.
|
// their presence. When disabled they're per-module no-ops that still run a
|
||||||
// Rolldown tracks the constant strings used in `React[REACT_USE]` /
|
// hook on every module and dominate [PLUGIN_TIMINGS], so drop them entirely.
|
||||||
// `React[USE_OPTIMISTIC]` lookups inside react-router and resolves them
|
const devToolsOnly = new Set(['preact:devtools', 'preact:transform-hook-names']);
|
||||||
// statically, so simply relying on a runtime guard is not enough — we need
|
return devToolsEnabled
|
||||||
// matching (stub) exports on the aliased preact/compat module.
|
? plugins
|
||||||
const preactCompatPatchPlugin = (): Plugin => ({
|
: plugins.filter((p) => !p || !devToolsOnly.has(p.name));
|
||||||
name: 'preact-compat-react19-patch',
|
};
|
||||||
transform(code, id) {
|
|
||||||
if (id.includes('preact') && id.includes('compat.module.js')) {
|
|
||||||
return {
|
|
||||||
code:
|
|
||||||
code +
|
|
||||||
'\nexport var use = undefined;\nexport var useOptimistic = undefined;\n',
|
|
||||||
map: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Common base plugins
|
// Common base plugins
|
||||||
const createBasePlugins = (
|
const createBasePlugins = (
|
||||||
devToolsEnabled: boolean,
|
devToolsEnabled: boolean,
|
||||||
includeBundleReporter = true
|
includeBundleReporter = true
|
||||||
): PluginOption[] => {
|
): PluginOption[] => {
|
||||||
const plugins: PluginOption[] = [
|
const plugins: PluginOption[] = [...createPreactPlugin(devToolsEnabled)];
|
||||||
createPreactPlugin(devToolsEnabled),
|
|
||||||
preactCompatPatchPlugin()
|
|
||||||
];
|
|
||||||
if (includeBundleReporter) {
|
if (includeBundleReporter) {
|
||||||
plugins.push(bundleSizeReporter());
|
plugins.push(bundleSizeReporter());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,5 +15,5 @@
|
|||||||
"itty-router": "^5.0.24",
|
"itty-router": "^5.0.24",
|
||||||
"prettier": "^3.8.4"
|
"prettier": "^3.8.4"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620"
|
"packageManager": "pnpm@11.8.0+sha512.c1f5e7c4cb241c8f174b743851d82f42b802324afc8b0f116b96adb15aa06664948dde36960a3ba1079ba5b4b29dd0140135b94b5b5f5263592249d68e555f26"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
#define EMSESP_APP_VERSION "3.9.0-dev.13"
|
#define EMSESP_APP_VERSION "3.9.0-dev.14"
|
||||||
|
|||||||
Reference in New Issue
Block a user