"use client" import { useState, useEffect } from "react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card" import { Wrench, Package, Ruler, HeartPulse, Cpu, MemoryStick, HardDrive, CircleDot, Network, Server, Settings2, FileText, RefreshCw, Shield, AlertTriangle, Info, Loader2, Check } from "lucide-react" import { NotificationSettings } from "./notification-settings" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" import { Input } from "./ui/input" import { Badge } from "./ui/badge" import { getNetworkUnit } from "../lib/format-network" import { fetchApi } from "../lib/api-config" interface SuppressionCategory { key: string label: string category: string icon: string hours: number } const SUPPRESSION_OPTIONS = [ { value: "24", label: "24 hours" }, { value: "72", label: "3 days" }, { value: "168", label: "1 week" }, { value: "720", label: "1 month" }, { value: "8760", label: "1 year" }, { value: "custom", label: "Custom" }, { value: "-1", label: "Permanent" }, ] const CATEGORY_ICONS: Record = { cpu: Cpu, memory: MemoryStick, storage: HardDrive, disk: CircleDot, network: Network, vms: Server, services: Settings2, logs: FileText, updates: RefreshCw, security: Shield, } interface ProxMenuxTool { key: string name: string enabled: boolean } export function Settings() { const [proxmenuxTools, setProxmenuxTools] = useState([]) const [loadingTools, setLoadingTools] = useState(true) const [networkUnitSettings, setNetworkUnitSettings] = useState<"Bytes" | "Bits">("Bytes") const [loadingUnitSettings, setLoadingUnitSettings] = useState(true) // Health Monitor suppression settings const [suppressionCategories, setSuppressionCategories] = useState([]) const [loadingHealth, setLoadingHealth] = useState(true) const [healthEditMode, setHealthEditMode] = useState(false) const [savingAllHealth, setSavingAllHealth] = useState(false) const [savedAllHealth, setSavedAllHealth] = useState(false) const [pendingChanges, setPendingChanges] = useState>({}) const [customValues, setCustomValues] = useState>({}) useEffect(() => { loadProxmenuxTools() getUnitsSettings() loadHealthSettings() }, []) const loadProxmenuxTools = async () => { try { const data = await fetchApi("/api/proxmenux/installed-tools") if (data.success) { setProxmenuxTools(data.installed_tools || []) } } catch (err) { console.error("Failed to load ProxMenux tools:", err) } finally { setLoadingTools(false) } } const changeNetworkUnit = (unit: string) => { const networkUnit = unit as "Bytes" | "Bits" localStorage.setItem("proxmenux-network-unit", networkUnit) setNetworkUnitSettings(networkUnit) window.dispatchEvent(new CustomEvent("networkUnitChanged", { detail: networkUnit })) window.dispatchEvent(new StorageEvent("storage", { key: "proxmenux-network-unit", newValue: networkUnit, url: window.location.href })) } const getUnitsSettings = () => { const networkUnit = getNetworkUnit() setNetworkUnitSettings(networkUnit) setLoadingUnitSettings(false) } const loadHealthSettings = async () => { try { const data = await fetchApi("/api/health/settings") if (data.categories) { setSuppressionCategories(data.categories) } } catch (err) { console.error("Failed to load health settings:", err) } finally { setLoadingHealth(false) } } const getSelectValue = (hours: number, key: string): string => { if (hours === -1) return "-1" const preset = SUPPRESSION_OPTIONS.find(o => o.value === String(hours)) if (preset && preset.value !== "custom") return String(hours) return "custom" } const getEffectiveHours = (cat: SuppressionCategory): number => { if (cat.key in pendingChanges) return pendingChanges[cat.key] return cat.hours } const handleSuppressionChange = (settingKey: string, value: string) => { if (value === "custom") { const current = suppressionCategories.find(c => c.key === settingKey) const effectiveHours = current ? getEffectiveHours(current) : 48 setCustomValues(prev => ({ ...prev, [settingKey]: String(effectiveHours > 0 ? effectiveHours : 48) })) // Mark as custom mode in pending setPendingChanges(prev => ({ ...prev, [settingKey]: -2 })) return } const hours = parseInt(value, 10) if (isNaN(hours)) return setPendingChanges(prev => ({ ...prev, [settingKey]: hours })) // Clear custom input if switching away setCustomValues(prev => { const next = { ...prev } delete next[settingKey] return next }) } const handleCustomConfirm = (settingKey: string) => { const raw = customValues[settingKey] const hours = parseInt(raw, 10) if (isNaN(hours) || hours < 1) return setPendingChanges(prev => ({ ...prev, [settingKey]: hours })) setCustomValues(prev => { const next = { ...prev } delete next[settingKey] return next }) } const handleCancelEdit = () => { setHealthEditMode(false) setPendingChanges({}) setCustomValues({}) } const handleSaveAllHealth = async () => { // Merge pending changes into a payload: only changed categories const payload: Record = {} for (const cat of suppressionCategories) { if (cat.key in pendingChanges && pendingChanges[cat.key] !== -2) { payload[cat.key] = String(pendingChanges[cat.key]) } } if (Object.keys(payload).length === 0) { setHealthEditMode(false) setPendingChanges({}) return } setSavingAllHealth(true) try { await fetchApi("/api/health/settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }) // Update local state with saved values setSuppressionCategories(prev => prev.map(c => { if (c.key in pendingChanges && pendingChanges[c.key] !== -2) { return { ...c, hours: pendingChanges[c.key] } } return c }) ) setPendingChanges({}) setCustomValues({}) setHealthEditMode(false) setSavedAllHealth(true) setTimeout(() => setSavedAllHealth(false), 3000) } catch (err) { console.error("Failed to save health settings:", err) } finally { setSavingAllHealth(false) } } const hasPendingChanges = Object.keys(pendingChanges).some( k => pendingChanges[k] !== -2 ) return (

Settings

Manage your dashboard preferences

{/* Network Units Settings */}
Network Units
Change how network traffic is displayed
{loadingUnitSettings ? (
) : (
Network Unit Display
)} {/* Health Monitor Settings */}
Health Monitor
{!loadingHealth && (
{savedAllHealth && ( Saved )} {healthEditMode ? ( <> ) : ( )}
)}
Configure how long dismissed alerts stay suppressed for each category. Changes apply immediately to both existing and future dismissed alerts.
{loadingHealth ? (
) : (
{/* Header */}
Category Suppression Duration
{/* Per-category rows */}
{suppressionCategories.map((cat) => { const IconComp = CATEGORY_ICONS[cat.icon] || HeartPulse const effectiveHours = getEffectiveHours(cat) const isCustomMode = effectiveHours === -2 || (cat.key in customValues) const isPermanent = effectiveHours === -1 const isLong = effectiveHours >= 720 && effectiveHours !== -1 && effectiveHours !== -2 const hasChanged = cat.key in pendingChanges && pendingChanges[cat.key] !== cat.hours const selectVal = isCustomMode ? "custom" : getSelectValue(effectiveHours, cat.key) return (
{cat.label} {hasChanged && healthEditMode && ( )}
{isCustomMode && healthEditMode ? (
setCustomValues(prev => ({ ...prev, [cat.key]: e.target.value }))} placeholder="Hours" /> h
) : ( )}
{/* Notice for Permanent */} {isPermanent && healthEditMode && (

Alerts for {cat.label} will be permanently suppressed when dismissed. {cat.category === "temperature" && ( Critical CPU temperature alerts will still trigger for hardware safety. )}

)} {/* Notice for long duration (> 1 month) */} {isLong && healthEditMode && (

Long suppression period. Dismissed alerts for this category will not reappear for an extended time.

)}
) })}
{/* Info footer */}

These settings apply when you dismiss a warning from the Health Monitor. Critical CPU temperature alerts always trigger regardless of settings to protect your hardware.

)} {/* Notification Settings */} {/* ProxMenux Optimizations */}
ProxMenux Optimizations
System optimizations and utilities installed via ProxMenux
{loadingTools ? (
) : proxmenuxTools.length === 0 ? (

No ProxMenux optimizations installed yet

Run ProxMenux to configure system optimizations

) : (
Installed Tools {proxmenuxTools.length} active
{proxmenuxTools.map((tool) => (
{tool.name}
))}
)}
) }