import { useState, type FormEvent } from "react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { Button } from "@launchpad/ui"; import { useDevices, type DeviceWithServices } from "../../hooks/useDevices.js"; import { AdminPageHeader } from "./AdminPageHeader.js"; async function createDevice(input: { hostname: string; ip: string }) { const res = await fetch("/api/devices", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input), }); if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error(body.error ?? `Gerät konnte nicht angelegt werden (HTTP ${res.status})`); } return res.json(); } async function deleteDevice(id: string) { const res = await fetch(`/api/devices/${id}`, { method: "DELETE" }); if (!res.ok && res.status !== 404) { throw new Error(`Gerät konnte nicht gelöscht werden (HTTP ${res.status})`); } } interface ScanResult { scannedPorts: number; created: number; updated: number; } async function scanDevice(id: string): Promise { const res = await fetch(`/api/scan/devices/${id}`, { method: "POST" }); const body = await res.json(); if (!res.ok) { throw new Error(body.detail ?? body.error ?? `Scan fehlgeschlagen (HTTP ${res.status})`); } return body; } function DeviceRow({ device }: { device: DeviceWithServices }) { const queryClient = useQueryClient(); const [scanMessage, setScanMessage] = useState(null); const scanMutation = useMutation({ mutationFn: () => scanDevice(device.id), onSuccess: (result) => { setScanMessage( `${result.scannedPorts} Port(s) offen · ${result.created} neu · ${result.updated} aktualisiert` ); queryClient.invalidateQueries({ queryKey: ["devices"] }); queryClient.invalidateQueries({ queryKey: ["services"] }); }, onError: (err: Error) => setScanMessage(err.message), }); const deleteMutation = useMutation({ mutationFn: () => deleteDevice(device.id), onSuccess: () => queryClient.invalidateQueries({ queryKey: ["devices"] }), }); return (
{device.hostname}
{device.ip}
{device.online ? "Online" : "Offline"} {device.services.length} {device.lastScan ? new Date(device.lastScan).toLocaleString("de-DE") : "nie gescannt"}
{scanMessage ? ( {scanMessage} ) : null}
); } function AddDeviceForm() { const queryClient = useQueryClient(); const [hostname, setHostname] = useState(""); const [ip, setIp] = useState(""); const mutation = useMutation({ mutationFn: createDevice, onSuccess: () => { setHostname(""); setIp(""); queryClient.invalidateQueries({ queryKey: ["devices"] }); }, }); function handleSubmit(e: FormEvent) { e.preventDefault(); if (!hostname.trim() || !ip.trim()) return; mutation.mutate({ hostname: hostname.trim(), ip: ip.trim() }); } return (
setHostname(e.target.value)} placeholder="z. B. synology" className="rounded-lg border border-black/10 bg-white px-3 py-1.5 text-sm text-black outline-none focus:border-black/30 dark:border-white/10 dark:bg-white/5 dark:text-white dark:focus:border-white/30" />
setIp(e.target.value)} placeholder="192.168.1.10" className="rounded-lg border border-black/10 bg-white px-3 py-1.5 text-sm text-black outline-none focus:border-black/30 dark:border-white/10 dark:bg-white/5 dark:text-white dark:focus:border-white/30" />
{mutation.isError ? ( {(mutation.error as Error).message} ) : null}
); } export function DevicesPage() { const { data: devices, isLoading, isError } = useDevices(); return (
{isLoading ? (

Lade Geräte …

) : isError ? (

Geräte konnten nicht geladen werden.

) : devices && devices.length > 0 ? (
{devices.map((device) => ( ))}
Gerät Status Dienste Letzter Scan
) : (

Noch keine Geräte angelegt. Füge oben ein Gerät hinzu oder nutze den FritzBox-Scan unter „Scanner“.

)}
); }