import { FormEvent, useEffect, useMemo, useRef, useState } from 'react'; import type { DashboardSnapshot, ProxyServiceRecord, SystemSettings, UpdateSystemInput } from './shared/contracts'; import type { PanelLanguage, PanelPreferences, PanelTheme } from './lib/panelPreferences'; import { getPanelText, getThemeLabel } from './lib/panelText'; import { getProtocolForCommand, validateSystemInput } from './shared/validation'; interface SystemTabProps { snapshot: DashboardSnapshot; preferences: PanelPreferences; onPreferencesChange: (next: PanelPreferences) => void; onSaveSystem: (input: UpdateSystemInput) => Promise; } export default function SystemTab({ snapshot, preferences, onPreferencesChange, onSaveSystem, }: SystemTabProps) { const [draft, setDraft] = useState(() => cloneSystemSettings(snapshot.system)); const [error, setError] = useState(''); const [isSaving, setIsSaving] = useState(false); const [removeServiceId, setRemoveServiceId] = useState(null); const lastAppliedSystemKey = useRef(serializeSystemSettings(cloneSystemSettings(snapshot.system))); const text = getPanelText(preferences.language); useEffect(() => { const incomingDraft = cloneSystemSettings(snapshot.system); const incomingKey = serializeSystemSettings(incomingDraft); const draftKey = serializeSystemSettings(draft); if (incomingKey === lastAppliedSystemKey.current) { return; } if (draftKey === lastAppliedSystemKey.current || draftKey === incomingKey) { setDraft(incomingDraft); setError(''); } lastAppliedSystemKey.current = incomingKey; }, [draft, snapshot.system]); const linkedUsersByService = useMemo(() => { const result = new Map(); snapshot.userRecords.forEach((user) => { const usernames = result.get(user.serviceId) ?? []; usernames.push(user.username); result.set(user.serviceId, usernames); }); return result; }, [snapshot.userRecords]); const removeTarget = draft.services.find((service) => service.id === removeServiceId) ?? null; const removeTargetUsers = removeTarget ? linkedUsersByService.get(removeTarget.id) ?? [] : []; const updateService = (serviceId: string, updater: (service: ProxyServiceRecord) => ProxyServiceRecord) => { setDraft((current) => ({ ...current, services: current.services.map((service) => (service.id === serviceId ? updater(service) : service)), })); }; const handleSubmit = async (event: FormEvent) => { event.preventDefault(); setIsSaving(true); setError(''); try { const nextServiceIds = new Set(draft.services.map((service) => service.id)); const remainingUsers = snapshot.userRecords.filter((user) => nextServiceIds.has(user.serviceId)); const validated = validateSystemInput(draft, remainingUsers); await onSaveSystem(validated); } catch (submitError) { setError(submitError instanceof Error ? submitError.message : 'Unable to save system settings.'); } finally { setIsSaving(false); } }; return ( <>

{text.settings.panelTitle}

{text.settings.title}

{draft.services.map((service, index) => (
{text.settings.serviceLabel} {index + 1}

{service.id}

))}
{error ?

{error}

: null}

{text.settings.generatedConfig}

{snapshot.system.previewConfig}
{removeTarget ? (
setRemoveServiceId(null)}>
event.stopPropagation()} >

{text.settings.serviceRemoveTitle}

{removeTarget.name}{' '} {removeTargetUsers.length > 0 ? text.settings.removeWarningUsers : text.settings.removeWarningNone}

{removeTargetUsers.length > 0 ? (

{text.settings.removeWarningCount} {removeTargetUsers.join(', ')}

) : null}
) : null} ); } function cloneSystemSettings(system: SystemSettings): UpdateSystemInput { return { publicHost: system.publicHost, configMode: system.configMode, reloadMode: system.reloadMode, storageMode: system.storageMode, services: system.services.map((service) => ({ ...service })), }; } function createServiceDraft(existingServices: ProxyServiceRecord[]): ProxyServiceRecord { const usedPorts = new Set(existingServices.map((service) => service.port)); let port = 1080; while (usedPorts.has(port) && port < 65535) { port += 1; } return { id: `service-${Math.random().toString(36).slice(2, 8)}`, name: `Service ${existingServices.length + 1}`, command: 'socks', protocol: 'socks5', description: 'Additional SOCKS5 entrypoint managed from the panel.', port, enabled: true, assignable: true, }; } function serializeSystemSettings(system: UpdateSystemInput): string { return JSON.stringify(system); }