"use client"; import { useEffect, useState } from "react"; import { KeyRound, RefreshCcw, Save } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; type EditableConfig = { key: string; value: string; description: string; multiline?: boolean; persistent?: boolean; source?: string; updated_at?: string; updated_by?: string; }; type SensitiveConfig = { key: string; label: string; description: string; configured: boolean; masked: string; length: number; updated_at: string; updated_by: string; source: string; }; type SensitiveHealth = { ok?: boolean; status?: number; latency_ms?: number; points?: number; observation_time_local?: string; error?: string; }; export function ConfigPageClient() { const [configs, setConfigs] = useState([]); const [sensitiveConfigs, setSensitiveConfigs] = useState([]); const [editing, setEditing] = useState>({}); const [sensitiveEditing, setSensitiveEditing] = useState>({}); const [saving, setSaving] = useState(false); const [sensitiveSaving, setSensitiveSaving] = useState(false); const [result, setResult] = useState(""); const [sensitiveResult, setSensitiveResult] = useState(""); const [sensitiveHealth, setSensitiveHealth] = useState(null); const [sensitiveCheckedAt, setSensitiveCheckedAt] = useState(""); const load = async () => { try { const [res, sensitiveRes] = await Promise.all([ fetch("/api/ops/config"), fetch("/api/ops/sensitive-config"), ]); if (res.ok) { const data = (await res.json()) as { configs?: EditableConfig[] }; setConfigs(data.configs ?? []); } if (sensitiveRes.ok) { const data = (await sensitiveRes.json()) as { configs?: SensitiveConfig[] }; setSensitiveConfigs(data.configs ?? []); } } catch { /* backend not ready yet */ } }; const handleSave = async (key: string) => { const newVal = editing[key]; if (newVal == null) return; setSaving(true); setResult(""); try { const res = await fetch("/api/ops/config", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key, value: newVal }), }); if (res.ok) { const data = (await res.json().catch(() => null)) as Partial | null; setResult(`${key} 已更新`); setConfigs((prev) => prev.map((c) => ( c.key === key ? { ...c, ...(data ?? {}), value: String(data?.value ?? newVal) } : c ))); setEditing((prev) => { const n = { ...prev }; delete n[key]; return n; }); } else { setResult(`保存失败: ${await res.text().catch(() => "")}`); } } catch { setResult("保存失败"); } setSaving(false); }; const handleSensitiveSave = async (key: string) => { const newVal = sensitiveEditing[key]?.trim(); if (!newVal) return; setSensitiveSaving(true); setSensitiveResult(""); setSensitiveHealth(null); setSensitiveCheckedAt(""); try { const res = await fetch("/api/ops/sensitive-config", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key, value: newVal }), }); if (res.ok) { const data = (await res.json()) as { config?: SensitiveConfig; health?: SensitiveHealth | null; }; if (data.config) { setSensitiveConfigs((prev) => prev.map((cfg) => (cfg.key === key ? data.config as SensitiveConfig : cfg))); } setSensitiveEditing((prev) => { const n = { ...prev }; delete n[key]; return n; }); setSensitiveHealth(data.health ?? null); setSensitiveCheckedAt(new Date().toLocaleString("zh-CN", { hour12: false })); setSensitiveResult(`${key} 已轮换`); } else { setSensitiveResult(`轮换失败: ${await res.text().catch(() => "")}`); } } catch { setSensitiveResult("轮换失败"); } setSensitiveSaving(false); }; useEffect(() => { void load(); }, []); return (

系统配置

可编辑配置 {configs.length === 0 ? (

配置 API 尚未就绪(需要后端支持)

) : (
{configs.map((cfg) => { const currentValue = editing[cfg.key] ?? cfg.value; const dirty = editing[cfg.key] != null && editing[cfg.key] !== cfg.value; const persistent = Boolean(cfg.persistent || cfg.source === "runtime_config"); const sourceLabel = persistent ? "DB 持久化" : cfg.source === "environment" ? "环境变量" : cfg.source || "当前进程"; if (persistent) { return (
{cfg.key}
{sourceLabel}
{cfg.description}
更新人 {cfg.updated_by || "-"} 更新时间 {cfg.updated_at || "-"}
{cfg.multiline ? (