From 63d2ff9f698914e1b03bb6a0e6a027e26be1b4a9 Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Mon, 8 Jun 2026 03:48:39 -0400 Subject: [PATCH] feat(ToolsPage): add firmware management overlay with installation functionality --- src/app/components/ToolsPage.tsx | 204 +++++++++++++++++++++++++++---- 1 file changed, 177 insertions(+), 27 deletions(-) diff --git a/src/app/components/ToolsPage.tsx b/src/app/components/ToolsPage.tsx index 9873946..552654d 100644 --- a/src/app/components/ToolsPage.tsx +++ b/src/app/components/ToolsPage.tsx @@ -1,12 +1,145 @@ -import { Wrench, Download, Upload, RotateCcw, Database, FileText, HardDrive } from 'lucide-react'; +import { useState, useEffect } from 'react'; +import { Wrench, Download, Upload, RotateCcw, Database, FileText, HardDrive, X, Loader2, Cpu, ChevronRight, AlertCircle } from 'lucide-react'; import { toast } from 'sonner'; +import { getFileContents, putFileContents, humanFileSize } from '../webdav'; interface ToolsPageProps { config: any; setConfig: (config: any) => void; } +interface FirmwareEntry { + version: string; + date: string; + description: string; + url: string; + size?: number; +} + +function FirmwareOverlay({ onClose }: { onClose: () => void }) { + const [entries, setEntries] = useState(null); + const [loadError, setLoadError] = useState(null); + const [installing, setInstalling] = useState(null); + + useEffect(() => { + getFileContents('/.sys/firmware.json') + .then(async (blob) => { + const parsed = JSON.parse(await blob.text()); + if (Array.isArray(parsed)) { + setEntries(parsed); + } else { + setLoadError('Unexpected firmware manifest format.'); + } + }) + .catch((e: any) => setLoadError(e?.message || 'Failed to load firmware list.')); + }, []); + + const install = async (entry: FirmwareEntry) => { + setInstalling(entry.url); + try { + let data: ArrayBuffer; + if (/^https?:\/\//.test(entry.url)) { + const r = await fetch(entry.url); + if (!r.ok) throw new Error(`Download failed: ${r.status} ${r.statusText}`); + data = await r.arrayBuffer(); + } else { + const blob = await getFileContents(entry.url); + data = await blob.arrayBuffer(); + } + await putFileContents('/sd/.bin', data); + toast.success(`Firmware ${entry.version} saved to /sd/.bin`); + onClose(); + } catch (e: any) { + toast.error(`Install failed: ${e?.message || String(e)}`); + } finally { + setInstalling(null); + } + }; + + return ( +
+
+
+

Change Firmware

+

Selected firmware will be saved to /sd/.bin

+
+ +
+ +
+ {entries === null && !loadError && ( +
+ + Loading firmware list… +
+ )} + + {loadError && ( +
+ + {loadError} +
+ )} + + {entries && entries.length === 0 && ( +
+ No firmware updates available. +
+ )} + + {entries && entries.map((entry) => { + const isInstalling = installing === entry.url; + const busy = installing !== null; + return ( +
+
+
+
+ {entry.version} + {entry.date} + {entry.size != null && ( + · {humanFileSize(entry.size)} + )} +
+

{entry.description}

+
+ +
+
+ ); + })} +
+
+ ); +} + export default function ToolsPage({ config }: ToolsPageProps) { + const [showFirmware, setShowFirmware] = useState(false); + const handleBackup = () => { const dataStr = JSON.stringify(config, null, 2); const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); @@ -140,32 +273,6 @@ export default function ToolsPage({ config }: ToolsPageProps) {

System

- - - -
@@ -190,7 +297,50 @@ export default function ToolsPage({ config }: ToolsPageProps) {
+ + + + + + +
+ + {showFirmware && setShowFirmware(false)} />} ); }