add factory reset to Recovery

This commit is contained in:
proddy
2026-10-10 11:31:44 +02:00
parent e19a45f5b8
commit 8c835a9e8b
10 changed files with 172 additions and 79 deletions
+3 -3
View File
@@ -33,7 +33,7 @@
"@table-library/react-table-library": "4.1.15",
"alova": "^3.5.5",
"jwt-decode": "^4.0.0",
"preact": "^11.0.0",
"preact": "^11.0.1",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"react-icons": "^5.7.0",
@@ -47,7 +47,7 @@
"@types/node": "^26.6.4",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"concurrently": "^10.0.5",
"concurrently": "^10.0.6",
"eslint": "^10.12.0",
"eslint-config-prettier": "^10.1.8",
"etag": "^1.8.1",
@@ -57,7 +57,7 @@
"terser": "^5.51.2",
"typescript": "^6.0.3",
"typescript-eslint": "^8.71.1",
"vite": "^8.3.3",
"vite": "^8.3.4",
"vite-plugin-imagemin": "^0.6.1"
},
"packageManager": "pnpm@12.10.1"
+51 -51
View File
@@ -191,8 +191,8 @@ importers:
specifier: ^4.0.0
version: 4.0.0
preact:
specifier: ^11.0.0
version: 11.0.0
specifier: ^11.0.1
version: 11.0.1
react:
specifier: ^19.3.0
version: 19.3.0
@@ -214,7 +214,7 @@ importers:
version: 10.0.1(eslint@10.12.0(supports-color@10.2.2))
'@preact/preset-vite':
specifier: ^2.10.6
version: 2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@11.0.0)(supports-color@10.2.2)(vite@8.3.3(@types/node@26.6.4)(terser@5.51.2))
version: 2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@11.0.1)(supports-color@10.2.2)(vite@8.3.4(@types/node@26.6.4)(terser@5.51.2))
'@trivago/prettier-plugin-sort-imports':
specifier: ^6.0.2
version: 6.0.2(prettier@3.9.9)(supports-color@10.2.2)
@@ -228,8 +228,8 @@ importers:
specifier: ^19.3.0
version: 19.3.0(@types/react@19.3.0)
concurrently:
specifier: ^10.0.5
version: 10.0.5
specifier: ^10.0.6
version: 10.0.6
eslint:
specifier: ^10.12.0
version: 10.12.0(supports-color@10.2.2)
@@ -258,11 +258,11 @@ importers:
specifier: ^8.71.1
version: 8.71.1(eslint@10.12.0(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)
vite:
specifier: ^8.3.3
version: 8.3.3(@types/node@26.6.4)(terser@5.51.2)
specifier: ^8.3.4
version: 8.3.4(@types/node@26.6.4)(terser@5.51.2)
vite-plugin-imagemin:
specifier: ^0.6.1
version: 0.6.1(supports-color@10.2.2)(vite@8.3.3(@types/node@26.6.4)(terser@5.51.2))
version: 0.6.1(supports-color@10.2.2)(vite@8.3.4(@types/node@26.6.4)(terser@5.51.2))
packages:
@@ -1004,8 +1004,8 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
baseline-browser-mapping@2.11.27:
resolution: {integrity: sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==}
baseline-browser-mapping@2.11.28:
resolution: {integrity: sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -1157,8 +1157,8 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
concurrently@10.0.5:
resolution: {integrity: sha512-JaP/CoftUrCcAFW/g//RbgEGwlelnEae6cfBLgH6ZdO6s8jPkn6p9SB9u6pdVxYXoiSnFqseOlHfrEfF82TVOg==}
concurrently@10.0.6:
resolution: {integrity: sha512-qgICXXFp2/3nAebRLmxcINKEXM1mLK3ij/y31P02VTs/KIkddIeC05n73DxOXsXMB9LFSqNU1/na3lDlOhcbIw==}
engines: {node: '>=22'}
hasBin: true
@@ -1338,8 +1338,8 @@ packages:
duplexer3@0.1.5:
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
electron-to-chromium@1.5.449:
resolution: {integrity: sha512-dKeYk/3SFQz+nXi5whp3OuJzeWABEG4zCtkWJ9+Vby1E1iFM2y0gElRtQvNXRPJTsj8WAF4HMP7hvP790QtJ9g==}
electron-to-chromium@1.5.452:
resolution: {integrity: sha512-cVvk1YBSj6571VmV6hdJTqj94Y79xSu8cj6Y9t6ae4Y4431z8/7EJFvu3rVp90xhfuv9BpPKUmaLfTo4+FOGBg==}
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
@@ -2318,8 +2318,8 @@ packages:
node-html-parser@6.1.13:
resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==}
node-releases@2.0.57:
resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==}
node-releases@2.0.58:
resolution: {integrity: sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==}
engines: {node: '>=18'}
normalize-package-data@2.5.0:
@@ -2531,8 +2531,8 @@ packages:
resolution: {integrity: sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==}
engines: {node: '>=20'}
preact@11.0.0:
resolution: {integrity: sha512-LRTLKuSe6VYPhvBDjwFaX+nuZnpsw6IN2GSboisAvhGsncylBmrbDCERhgKDn6yP2mAwbGvsB5OVyTe33M8gBg==}
preact@11.0.1:
resolution: {integrity: sha512-TNNwEq++jYNHy747LziCjPRVgEwnLSOIOAw/oYcjI3Tdnubx2EXtt76I0an8PATvUWtx/DX1kFIg1eFN+O/fkw==}
peerDependencies:
preact-render-to-string: '>=6.7.0'
peerDependenciesMeta:
@@ -2768,8 +2768,8 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
shell-quote@1.9.0:
resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==}
shell-quote@1.12.0:
resolution: {integrity: sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==}
engines: {node: '>= 0.4'}
signal-exit@3.0.7:
@@ -3015,8 +3015,8 @@ packages:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
update-browserslist-db@1.3.3:
resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==}
update-browserslist-db@1.3.4:
resolution: {integrity: sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
@@ -3057,8 +3057,8 @@ packages:
peerDependencies:
vite: 5.x || 6.x || 7.x || 8.x
vite@8.3.3:
resolution: {integrity: sha512-cTAldKPImjg6c+gk48U19POPn3GCBzZwpdsN8ZMEEcbpes+6/wvfqUd0C2y3qYY4wsj8PwgFwrZ/1jBPVttMSg==}
vite@8.3.4:
resolution: {integrity: sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
@@ -3591,19 +3591,19 @@ snapshots:
'@popperjs/core@2.11.8': {}
'@preact/preset-vite@2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@11.0.0)(supports-color@10.2.2)(vite@8.3.3(@types/node@26.6.4)(terser@5.51.2))':
'@preact/preset-vite@2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@11.0.1)(supports-color@10.2.2)(vite@8.3.4(@types/node@26.6.4)(terser@5.51.2))':
dependencies:
'@babel/core': 7.29.7(supports-color@10.2.2)
'@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
'@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
'@prefresh/vite': 2.4.12(preact@11.0.0)(supports-color@10.2.2)(vite@8.3.3(@types/node@26.6.4)(terser@5.51.2))
'@prefresh/vite': 2.4.12(preact@11.0.1)(supports-color@10.2.2)(vite@8.3.4(@types/node@26.6.4)(terser@5.51.2))
'@rollup/pluginutils': 5.4.0
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.7(supports-color@10.2.2))
debug: 4.4.3(supports-color@10.2.2)
magic-string: 0.30.21
picocolors: 1.1.1
vite: 8.3.3(@types/node@26.6.4)(terser@5.51.2)
vite-prerender-plugin: 0.5.14(vite@8.3.3(@types/node@26.6.4)(terser@5.51.2))
vite: 8.3.4(@types/node@26.6.4)(terser@5.51.2)
vite-prerender-plugin: 0.5.14(vite@8.3.4(@types/node@26.6.4)(terser@5.51.2))
zimmerframe: 1.1.5
transitivePeerDependencies:
- preact
@@ -3614,21 +3614,21 @@ snapshots:
dependencies:
'@babel/core': 7.29.7(supports-color@10.2.2)
'@prefresh/core@1.5.11(preact@11.0.0)':
'@prefresh/core@1.5.11(preact@11.0.1)':
dependencies:
preact: 11.0.0
preact: 11.0.1
'@prefresh/utils@1.2.1': {}
'@prefresh/vite@2.4.12(preact@11.0.0)(supports-color@10.2.2)(vite@8.3.3(@types/node@26.6.4)(terser@5.51.2))':
'@prefresh/vite@2.4.12(preact@11.0.1)(supports-color@10.2.2)(vite@8.3.4(@types/node@26.6.4)(terser@5.51.2))':
dependencies:
'@babel/core': 7.29.7(supports-color@10.2.2)
'@prefresh/babel-plugin': 0.5.4(@babel/core@7.29.7(supports-color@10.2.2))
'@prefresh/core': 1.5.11(preact@11.0.0)
'@prefresh/core': 1.5.11(preact@11.0.1)
'@prefresh/utils': 1.2.1
'@rollup/pluginutils': 4.2.1
preact: 11.0.0
vite: 8.3.3(@types/node@26.6.4)(terser@5.51.2)
preact: 11.0.1
vite: 8.3.4(@types/node@26.6.4)(terser@5.51.2)
transitivePeerDependencies:
- supports-color
@@ -3942,7 +3942,7 @@ snapshots:
base64-js@1.5.1: {}
baseline-browser-mapping@2.11.27: {}
baseline-browser-mapping@2.11.28: {}
bin-build@3.0.0:
dependencies:
@@ -4003,11 +4003,11 @@ snapshots:
browserslist@4.29.3:
dependencies:
baseline-browser-mapping: 2.11.27
baseline-browser-mapping: 2.11.28
caniuse-lite: 1.0.30001815
electron-to-chromium: 1.5.449
node-releases: 2.0.57
update-browserslist-db: 1.3.3(browserslist@4.29.3)
electron-to-chromium: 1.5.452
node-releases: 2.0.58
update-browserslist-db: 1.3.4(browserslist@4.29.3)
buffer-alloc-unsafe@1.1.0: {}
@@ -4125,11 +4125,11 @@ snapshots:
concat-map@0.0.1: {}
concurrently@10.0.5:
concurrently@10.0.6:
dependencies:
chalk: 5.6.2
rxjs: 7.8.2
shell-quote: 1.9.0
shell-quote: 1.12.0
supports-color: 10.2.2
tree-kill: 1.2.2
yargs: 18.0.0
@@ -4370,7 +4370,7 @@ snapshots:
duplexer3@0.1.5: {}
electron-to-chromium@1.5.449: {}
electron-to-chromium@1.5.452: {}
emoji-regex@10.6.0: {}
@@ -5307,7 +5307,7 @@ snapshots:
css-select: 5.2.2
he: 1.2.0
node-releases@2.0.57: {}
node-releases@2.0.58: {}
normalize-package-data@2.5.0:
dependencies:
@@ -5501,7 +5501,7 @@ snapshots:
powershell-utils@0.2.1: {}
preact@11.0.0: {}
preact@11.0.1: {}
prelude-ls@1.2.1: {}
@@ -5724,7 +5724,7 @@ snapshots:
shebang-regex@3.0.0: {}
shell-quote@1.9.0: {}
shell-quote@1.12.0: {}
signal-exit@3.0.7: {}
@@ -5953,7 +5953,7 @@ snapshots:
universalify@2.0.1: {}
update-browserslist-db@1.3.3(browserslist@4.29.3):
update-browserslist-db@1.3.4(browserslist@4.29.3):
dependencies:
browserslist: 4.29.3
escalade: 3.2.0
@@ -5982,7 +5982,7 @@ snapshots:
spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1
vite-plugin-imagemin@0.6.1(supports-color@10.2.2)(vite@8.3.3(@types/node@26.6.4)(terser@5.51.2)):
vite-plugin-imagemin@0.6.1(supports-color@10.2.2)(vite@8.3.4(@types/node@26.6.4)(terser@5.51.2)):
dependencies:
'@types/imagemin': 7.0.1
'@types/imagemin-gifsicle': 7.0.4
@@ -6007,11 +6007,11 @@ snapshots:
imagemin-webp: 6.1.0
jpegtran-bin: 6.0.1
pathe: 0.2.0
vite: 8.3.3(@types/node@26.6.4)(terser@5.51.2)
vite: 8.3.4(@types/node@26.6.4)(terser@5.51.2)
transitivePeerDependencies:
- supports-color
vite-prerender-plugin@0.5.14(vite@8.3.3(@types/node@26.6.4)(terser@5.51.2)):
vite-prerender-plugin@0.5.14(vite@8.3.4(@types/node@26.6.4)(terser@5.51.2)):
dependencies:
kolorist: 1.8.0
magic-string: 0.30.21
@@ -6019,9 +6019,9 @@ snapshots:
simple-code-frame: 1.3.0
source-map: 0.7.6
stack-trace: 1.0.0
vite: 8.3.3(@types/node@26.6.4)(terser@5.51.2)
vite: 8.3.4(@types/node@26.6.4)(terser@5.51.2)
vite@8.3.3(@types/node@26.6.4)(terser@5.51.2):
vite@8.3.4(@types/node@26.6.4)(terser@5.51.2):
dependencies:
lightningcss: 1.33.0
picomatch: 4.0.7
+52 -1
View File
@@ -6,6 +6,7 @@ import LogoutIcon from '@mui/icons-material/Logout';
import MemoryIcon from '@mui/icons-material/Memory';
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
import RestartAltIcon from '@mui/icons-material/RestartAlt';
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
import {
Avatar,
Box,
@@ -32,7 +33,7 @@ import SectionContent from 'components/SectionContent';
import { toast } from 'components/toast';
import FirmwareUpload from './FirmwareUpload';
import { HttpError, restart, setBootPartition } from './api';
import { HttpError, factoryReset, restart, setBootPartition } from './api';
import type { RecoveryPartition, RecoveryStatus } from './types';
const formatMB = (bytes: number) => `${(bytes / 1048576).toFixed(2)} MB`;
@@ -97,6 +98,7 @@ interface DashboardProps {
const Dashboard = ({ status, onRestarting, onSignOut }: DashboardProps) => {
const [confirmBoot, setConfirmBoot] = useState<RecoveryPartition>();
const [confirmFactoryReset, setConfirmFactoryReset] = useState(false);
const [pickedTarget, setPickedTarget] = useState<string>();
const uploadTarget = pickedTarget ?? status.upload_target;
@@ -132,6 +134,16 @@ const Dashboard = ({ status, onRestarting, onSignOut }: DashboardProps) => {
}
};
const doFactoryReset = async () => {
setConfirmFactoryReset(false);
try {
await factoryReset();
onRestarting(status.boot);
} catch (error) {
handleError(error);
}
};
const otaPartitions = status.partitions.filter((p) => !p.factory);
const hasFirmware = otaPartitions.some((p) => p.valid);
const reason = recoveryReason(
@@ -317,6 +329,16 @@ const Dashboard = ({ status, onRestarting, onSignOut }: DashboardProps) => {
<InfoRow label="Hostname" value={status.hostname} />
<ButtonRow>
{!status.disable_reset && (
<Button
startIcon={<SettingsBackupRestoreIcon />}
variant="outlined"
color="error"
onClick={() => setConfirmFactoryReset(true)}
>
Factory Reset
</Button>
)}
<Button
startIcon={<RestartAltIcon />}
variant="outlined"
@@ -336,6 +358,35 @@ const Dashboard = ({ status, onRestarting, onSignOut }: DashboardProps) => {
</ButtonRow>
</SectionContent>
<Dialog
sx={dialogStyle}
open={confirmFactoryReset}
onClose={() => setConfirmFactoryReset(false)}
>
<DialogTitle>Factory Reset</DialogTitle>
<DialogContent dividers>
Are you sure you want to reset EMS-ESP to its factory defaults?
</DialogContent>
<DialogActions>
<Button
startIcon={<CancelIcon />}
variant="outlined"
color="secondary"
onClick={() => setConfirmFactoryReset(false)}
>
Cancel
</Button>
<Button
startIcon={<SettingsBackupRestoreIcon />}
variant="outlined"
color="error"
onClick={() => void doFactoryReset()}
>
Factory Reset
</Button>
</DialogActions>
</Dialog>
<Dialog
sx={dialogStyle}
open={!!confirmBoot}
+2
View File
@@ -59,6 +59,8 @@ export const setBootPartition = (partition: string) =>
export const restart = () => request<void>('POST', '/rest/recovery/restart');
export const factoryReset = () => request<void>('POST', '/rest/recovery/format');
export interface UploadHandle {
promise: Promise<UploadResult>;
abort: () => void;
+1
View File
@@ -39,6 +39,7 @@ export interface RecoveryStatus {
wifi_ip?: string;
wifi_rssi?: number;
static_ip: boolean;
disable_reset: boolean; // factory reset is disabled in the EMS-ESP settings
// only on boards with Ethernet configured
eth_state?: 'disabled' | 'failed' | 'no_link' | 'connecting' | 'connected';
eth_ip?: string;