import { useState, useEffect } from 'react' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { settingsApi, proxmoxApi, type ProxmoxConfigData } from '@/api/client' import { useCanvasStore } from '@/stores/canvasStore' import { toast } from 'sonner' import { type AlignmentSettings, readAlignmentSettings, writeAlignmentSettings, subscribeAlignmentSettings, } from '@/utils/alignmentSettings' const STANDALONE = import.meta.env.VITE_STANDALONE === 'true' interface SettingsModalProps { open: boolean onClose: () => void } export function SettingsModal({ open, onClose }: SettingsModalProps) { const [interval, setIntervalValue] = useState(60) const [serviceCheckEnabled, setServiceCheckEnabled] = useState(false) const [serviceInterval, setServiceInterval] = useState(300) const [saving, setSaving] = useState(false) const [pmConfig, setPmConfig] = useState(null) const [pmSyncEnabled, setPmSyncEnabled] = useState(false) const [pmInterval, setPmInterval] = useState(3600) const [pmSyncing, setPmSyncing] = useState(false) const [alignment, setAlignment] = useState(readAlignmentSettings) const hideIp = useCanvasStore((s) => s.hideIp) const setHideIp = useCanvasStore((s) => s.setHideIp) useEffect(() => { if (!open || STANDALONE) return settingsApi.get() .then((res) => { setIntervalValue(res.data.interval_seconds) setServiceCheckEnabled(res.data.service_check_enabled) setServiceInterval(res.data.service_check_interval) }) .catch(() => {/* use default */}) proxmoxApi.getConfig() .then((res) => { setPmConfig(res.data) setPmSyncEnabled(res.data.sync_enabled) setPmInterval(res.data.sync_interval) }) .catch(() => {/* proxmox not configured */}) }, [open]) useEffect(() => subscribeAlignmentSettings(setAlignment), []) const updateAlignment = (patch: Partial) => { const next = { ...alignment, ...patch } setAlignment(next) writeAlignmentSettings(next) } const handleSyncNow = async () => { setPmSyncing(true) try { await proxmoxApi.syncNow() toast.success('Proxmox sync started') } catch { toast.error('Failed to start Proxmox sync') } finally { setPmSyncing(false) } } const handleSave = async () => { // Canvas prefs (alignment, hide-IP) persist on change; only the backend // status-check interval needs an API round-trip. if (STANDALONE) { onClose() return } setSaving(true) try { await settingsApi.save({ interval_seconds: interval, service_check_enabled: serviceCheckEnabled, service_check_interval: serviceInterval, }) if (pmConfig) { // Connection config (host/port/token/verify) is env-only; only the // auto-sync activation is persisted. await proxmoxApi.saveConfig({ sync_enabled: pmSyncEnabled, sync_interval: pmInterval, }) } toast.success('Settings saved') onClose() } catch { toast.error('Failed to save settings') } finally { setSaving(false) } } return ( !v && onClose()}> Settings
{/* Status checker */} {!STANDALONE && (
{ const v = Number(e.target.value); if (!isNaN(v)) setIntervalValue(v) }} className="w-24 px-2 py-1 rounded-md text-xs font-mono bg-[#0d1117] border border-border text-foreground focus:outline-none focus:border-[#00d4ff]" /> seconds

How often node health is polled (ping, HTTP, SSH…)

{ const v = Number(e.target.value); if (!isNaN(v)) setServiceInterval(v) }} className="w-24 px-2 py-1 rounded-md text-xs font-mono bg-[#0d1117] border border-border text-foreground focus:outline-none focus:border-[#00d4ff]" aria-label="Service check interval" /> seconds

Probes each service port. Offline services turn red. Default 300s (5 min).

)} {/* Proxmox auto-sync */} {!STANDALONE && pmConfig && (
Proxmox auto-sync {!pmConfig.token_configured ? (

No API token configured. Set PROXMOX_TOKEN_ID and{' '} PROXMOX_TOKEN_SECRET in the server .env to enable auto-sync.

) : ( <>
{ const v = Number(e.target.value); if (!isNaN(v)) setPmInterval(v) }} className="w-24 px-2 py-1 rounded-md text-xs font-mono bg-[#0d1117] border border-border text-foreground focus:outline-none focus:border-[#e57000]" aria-label="Proxmox sync interval" /> seconds

Re-imports hosts/VMs/LXC into the pending inventory. Min 300s (5 min).

{pmConfig.host ? (
Runs one import immediately using the server .env config.
) : (

Set PROXMOX_HOST in the server .env to enable manual re-sync.

)} )}
)} {/* Canvas */}
Canvas
updateAlignment({ threshold: Number(e.target.value) })} className="flex-1 cursor-pointer accent-[#00d4ff]" aria-label="Alignment snap threshold" /> {alignment.threshold}px

Distance at which dragged nodes snap to neighbours. Hold Alt while dragging to disable.

) }