// Complete Configuration Editor // Purpose: Connects the one hierarchical configuration form to revision, secret, validation, and save behavior. // Scope: Edits and saves one complete configuration document as one immutable revision. import { useEffect, useMemo, useState } from 'react'; import CardFrame from '../../components/CardFrame/index.jsx'; import { updateConfiguration } from '../api.js'; import SchemaConfigurationForm from './SchemaConfigurationForm.jsx'; function clone(value) { return JSON.parse(JSON.stringify(value)); } export default function ConfigurationEditor({ snapshot, socket, runSensitive, onSnapshot, onReload }) { const serverValue = snapshot?.configuration?.config; const schema = snapshot?.configuration?.schema; const revision = snapshot?.configuration?.revision; const [draft, setDraft] = useState(() => clone(serverValue)); const [secretOperations, setSecretOperations] = useState({}); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const [validationErrors, setValidationErrors] = useState([]); useEffect(() => { setDraft(clone(serverValue)); setSecretOperations({}); setError(''); setValidationErrors([]); }, [revision, serverValue]); const dirty = useMemo( () => JSON.stringify(draft) !== JSON.stringify(serverValue) || Object.keys(secretOperations).length > 0, [draft, secretOperations, serverValue], ); if (!serverValue || !schema) { return
The configuration document is unavailable.
; } const setSecretOperation = (path, operation) => setSecretOperations((current) => { const next = { ...current }; if (operation) next[path] = operation; else delete next[path]; return next; }); async function save() { setSaving(true); setError(''); setValidationErrors([]); try { const response = await runSensitive(() => updateConfiguration(socket, { value: draft, expectedRevision: revision, secretOperations, })); onSnapshot(response.snapshot); } catch (saveError) { setError(saveError.message); setValidationErrors(saveError.validationErrors || []); } finally { setSaving(false); } } return (Saved changes apply after an application restart.
{/* All document actions stay together at the start of the toolbar. The editor may use a wide canvas, but width is never used to separate a control from the content that explains it. */}{error}
: null} {validationErrors.length ? (Configuration could not be saved