import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Check, ChevronRight, CircleCheck, Cpu, Database, HardDrive, Lightbulb, Network, ScanSearch, Server, ShieldCheck, Usb, Wrench } from "lucide-react"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { api } from "../api/client"; import type { ComponentHealth, SetupState } from "../api/types"; import { useToast } from "../components/Toast"; import { Badge, Button, EmptyState, ErrorPanel, StatusBadge } from "../components/ui"; interface PathStatus { path: string; exists: boolean; readable: boolean; writable: boolean } interface NodeStatus { path: string; readable: boolean; writable: boolean } interface Recovery { area: string; message: string } interface SetupReport { storage: PathStatus[]; openrgb: ComponentHealth | null; usb_devices: NodeStatus[]; i2c_devices: NodeStatus[]; serial_devices: NodeStatus[]; recovery: Recovery[]; checked_at: string; openrgb_inventory?: Record; } const steps = [ ["Welkom", Lightbulb], ["Opslagrechten", HardDrive], ["OpenRGB-proces", Server], ["SDK-verbinding", Network], ["USB-inventory", Usb], ["I²C-inventory", Cpu], ["OpenRGB-detectie", ScanSearch], ["Toegankelijkheid", ShieldCheck], ["Netwerkdiscovery", Network], ["Appdata & back-up", Database], ["Systeemrapport", CircleCheck], ] as const; export function SetupPage() { const navigate = useNavigate(); const queryClient = useQueryClient(); const { notify } = useToast(); const [active, setActive] = useState(0); const [networkDiscovery, setNetworkDiscovery] = useState(false); const [appdata, setAppdata] = useState(false); const [backup, setBackup] = useState(false); const state = useQuery({ queryKey: ["setup"], queryFn: () => api("/api/v1/setup") }); const inspect = useMutation({ mutationFn: () => api(`/api/v1/setup/inspect?include_network=${networkDiscovery}`, { method: "POST" }), onSuccess: () => { notify("Systeemcontrole afgerond."); setActive(10); }, onError: (error) => notify(error instanceof Error ? error.message : "Controle mislukt.", "danger") }); const complete = useMutation({ mutationFn: () => api("/api/v1/setup/complete", { method: "POST", body: JSON.stringify({ appdata_confirmed: appdata, backup_location_confirmed: backup }) }), onSuccess: (result) => { queryClient.setQueryData(["setup"], result); notify("LumaOps is klaar voor gebruik."); void navigate("/", { replace: true }); }, onError: (error) => notify(error instanceof Error ? error.message : "Setup afronden mislukt.", "danger") }); const report = normalizeSetupReport(inspect.data ?? state.data?.report); if (state.error) return void state.refetch()} />; return
Stap {active + 1} van {steps.length}
{active === 0 ? setActive(1)} /> : null} {active >= 1 && active <= 8 ? inspect.mutate()} onPrevious={() => setActive((value) => Math.max(0, value - 1))} onNext={() => setActive((value) => Math.min(10, value + 1))} /> : null} {active === 9 ? setActive(8)} onNext={() => setActive(10)} /> : null} {active === 10 ? inspect.mutate()} onComplete={() => complete.mutate()} /> : null}
; } function normalizeSetupReport(value: unknown): SetupReport | undefined { if (!value || typeof value !== "object") return undefined; const candidate = value as Partial; return { storage: Array.isArray(candidate.storage) ? candidate.storage : [], openrgb: candidate.openrgb ?? null, usb_devices: Array.isArray(candidate.usb_devices) ? candidate.usb_devices : [], i2c_devices: Array.isArray(candidate.i2c_devices) ? candidate.i2c_devices : [], serial_devices: Array.isArray(candidate.serial_devices) ? candidate.serial_devices : [], recovery: Array.isArray(candidate.recovery) ? candidate.recovery : [], checked_at: typeof candidate.checked_at === "string" ? candidate.checked_at : "", openrgb_inventory: candidate.openrgb_inventory, }; } function Welcome({ onNext }: { onNext: () => void }) { return
OpenRGB 1.0rc3 · SDK 5

Welkom bij LumaOps

We controleren eerst veilig je container, opslag en OpenRGB-verbinding. De wizard voert geen wijzigende hardwareopdrachten uit.

Loopback-only SDKPoort 6742 blijft intern.
Concrete herstelstappenGeen vage foutmeldingen.
Persistente appdataBack-up vóór risicovolle wijzigingen.
; } function CheckStep({ index, report, networkDiscovery, setNetworkDiscovery, busy, onInspect, onPrevious, onNext }: { index: number; report?: SetupReport; networkDiscovery: boolean; setNetworkDiscovery: (value: boolean) => void; busy: boolean; onInspect: () => void; onPrevious: () => void; onNext: () => void }) { const [title, Icon] = steps[index]!; const contents = index === 1 ? ({ name: item.path, ok: item.writable, detail: item.writable ? "Schrijfbaar" : "Niet schrijfbaar" })) ?? []} empty="Voer de systeemcontrole uit om mountrechten te testen." /> : index === 2 || index === 3 ? report?.openrgb ?
{report.openrgb.message}

De SDK moet exact protocol 5 rapporteren en op loopback bereikbaar zijn.

: : index === 4 ? : index === 5 ? : index === 6 ? ({ name, ok: (value as { status?: string }).status === "completed", detail: JSON.stringify(value) })) : []} empty="Na de SDK-test wordt de OpenRGB-inventory read-only gesynchroniseerd." /> : index === 7 ?
{report?.recovery.length ? report.recovery.map((item) =>
{item.area}

{item.message}

) : }
: ; return
Stap {index + 1}

{title}

Deze controle is read-only en verandert geen apparaattoestand.

{contents}
; } function StorageConfirm({ appdata, backup, onAppdata, onBackup, onPrevious, onNext }: { appdata: boolean; backup: boolean; onAppdata: (value: boolean) => void; onBackup: (value: boolean) => void; onPrevious: () => void; onNext: () => void }) { return
Stap 10

Appdata & back-up

Bevestig dat de persistentie buiten de container is gekoppeld.

; } function Report({ report, appdata, backup, busy, onInspect, onComplete }: { report?: SetupReport; appdata: boolean; backup: boolean; busy: boolean; onInspect: () => void; onComplete: () => void }) { const ready = Boolean(report?.openrgb?.connected && report.storage.every((item) => item.writable) && appdata && backup); return
{ready ? "Klaar voor gebruik" : "Aandacht vereist"}

Systeemstatusrapport

{ready ? "De container, opslag en SDK-verbinding voldoen aan de veilige MVP-voorwaarden." : "LumaOps kan starten, maar los onderstaande herstelpunten op voor volledige hardwarebediening."}

{report?.storage.filter((item) => item.writable).length ?? 0}/{report?.storage.length ?? 4}opslagpaden
{report?.usb_devices.length ?? 0}USB-nodes
{report?.i2c_devices.length ?? 0}I²C-nodes
{report?.openrgb?.connected ? "SDK 5" : "Offline"}OpenRGB
{report?.recovery.map((item) =>
{item.area}

{item.message}

)}
; } function StatusList({ items, empty }: { items: Array<{ name: string; ok: boolean; detail: string }>; empty: string }) { if (!items.length) return

{empty}

; return
{items.map((item) =>
{item.ok ? : "!"}
{item.name}

{item.detail}

)}
; } function NodeList({ title, nodes }: { title: string; nodes: NodeStatus[] }) { return nodes.length ?

{title}

({ name: node.path, ok: node.readable && node.writable, detail: node.writable ? "Lees- en schrijfbaar" : node.readable ? "Alleen leesbaar" : "Niet toegankelijk" }))} empty="" />
: ; }