Refactor: Frontend-Fundament — Format-Utils, Dialog-Ref, Typen (Phase 3a)

- Neuer lib/format.ts: gb/fmtBytes/fmtSize/fmtEta/fmtCtx zentral; aus
  DashboardView/SystemView/ModelsView entdoppelt und importiert.
- CustomDialog: document.getElementById -> useRef (kein DOM-Query, robuster).
- api.ts: GitInfo/ComponentVersion/Versions typisiert, SystemStatus.versions
  ergänzt; App.tsx nutzt SystemStatus statt any.

tsc grün, Build grün, Dashboard verifiziert (keine Konsolenfehler, Formatierer
rendern identisch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-26 14:36:28 +02:00
parent 341ea870bb
commit 74f64731ab
9 changed files with 146 additions and 113 deletions
+1 -4
View File
@@ -2,13 +2,10 @@ import { useEffect, useState } from "react"
import { Bot, Cpu, ExternalLink, Brain, Layers, Plus, ShieldAlert, X, Power, Shield, Download, RefreshCw, Check, Coins } from "lucide-react"
import { api, type SystemStatus, type AgentStatus, type ModelInfo, type Memory, type UpdatesResp, type Job } from "@/lib/api"
import { cn, resolveExternalUrl } from "@/lib/utils"
import { gb } from "@/lib/format"
import { CustomDialog } from "@/components/CustomDialog"
function gb(b: number) {
return (b / 1024 ** 3).toFixed(1)
}
function RadialGauge({ value, label, detail }: { value: number; label: string; detail?: string }) {
const radius = 24
const circ = 2 * Math.PI * radius
+1 -22
View File
@@ -3,21 +3,10 @@ import { Download, Search, Trash2, Edit3, Star, Layers, Activity, HardDrive, X,
import { api, type DiscoverResp, type Fit, type Job, type ModelInfo, type RoutingResp, type UpdatesResp, type ConnectResp } from "@/lib/api"
import { CapsChips } from "@/components/CapsChips"
import { cn } from "@/lib/utils"
import { fmtBytes, fmtEta, fmtSize, fmtCtx } from "@/lib/format"
import { CustomDialog } from "@/components/CustomDialog"
function fmtBytes(b?: number) {
if (!b) return ""
if (b > 1024 ** 3) return `${(b / 1024 ** 3).toFixed(1)} GB`
return `${(b / 1024 ** 2).toFixed(0)} MB`
}
function fmtEta(s?: number) {
if (!s) return ""
const m = Math.floor(s / 60)
return m > 0 ? `${m} min` : `${s} s`
}
function JobsBar({ onError }: { onError?: (msg: string) => void }) {
const [jobs, setJobs] = useState<Job[]>([])
const [errorMsg, setErrorMsg] = useState<string | null>(null)
@@ -100,16 +89,6 @@ function JobsBar({ onError }: { onError?: (msg: string) => void }) {
)
}
function fmtSize(b?: number | null) {
if (!b) return "—"
const gb = b / 1024 ** 3
return gb >= 1 ? `${gb.toFixed(1)} GB` : `${(b / 1024 ** 2).toFixed(0)} MB`
}
function fmtCtx(c: number | null) {
return c ? `${Math.round(c / 1024)}k` : "—"
}
function FitBadge({ fit }: { fit: Fit }) {
const tone = {
perfect: "bg-emerald-500/15 text-emerald-400 border border-emerald-500/20",
+1 -4
View File
@@ -2,13 +2,10 @@ import { useEffect, useState } from "react"
import { ExternalLink, RefreshCw, Save, Cpu, HardDrive, Cpu as GpuIcon, Activity } from "lucide-react"
import { api, type ServicesResp, type SystemStatus } from "@/lib/api"
import { cn, resolveExternalUrl } from "@/lib/utils"
import { gb } from "@/lib/format"
import { CustomDialog } from "@/components/CustomDialog"
function gb(b: number) {
return (b / 1024 ** 3).toFixed(1)
}
function DiagnosticBar({ label, percent, detail, icon: Icon }: { label: string; percent: number; detail?: string; icon: any }) {
const barColor = percent > 90
? "bg-red-500 shadow-md shadow-red-500/20"