mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
legacy importer in admin page aswell as setup.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
// 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 { importAdminConfigurationFile, updateConfiguration } from '../api.js';
|
||||
import SchemaConfigurationForm from './SchemaConfigurationForm.jsx';
|
||||
|
||||
function clone(value) {
|
||||
@@ -17,6 +17,9 @@ export default function ConfigurationEditor({ snapshot, socket, runSensitive, on
|
||||
const [draft, setDraft] = useState(() => clone(serverValue));
|
||||
const [secretOperations, setSecretOperations] = useState({});
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [importing, setImporting] = useState(false);
|
||||
const [configurationFile, setConfigurationFile] = useState(null);
|
||||
const [notice, setNotice] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [validationErrors, setValidationErrors] = useState([]);
|
||||
|
||||
@@ -24,6 +27,7 @@ export default function ConfigurationEditor({ snapshot, socket, runSensitive, on
|
||||
setDraft(clone(serverValue));
|
||||
setSecretOperations({});
|
||||
setError('');
|
||||
setConfigurationFile(null);
|
||||
setValidationErrors([]);
|
||||
}, [revision, serverValue]);
|
||||
|
||||
@@ -31,6 +35,7 @@ export default function ConfigurationEditor({ snapshot, socket, runSensitive, on
|
||||
() => JSON.stringify(draft) !== JSON.stringify(serverValue) || Object.keys(secretOperations).length > 0,
|
||||
[draft, secretOperations, serverValue],
|
||||
);
|
||||
const busy = saving || importing;
|
||||
|
||||
if (!serverValue || !schema) {
|
||||
return <p className="surface p-1 text-sm text-red-200">The configuration document is unavailable.</p>;
|
||||
@@ -62,6 +67,42 @@ export default function ConfigurationEditor({ snapshot, socket, runSensitive, on
|
||||
}
|
||||
}
|
||||
|
||||
async function importSelectedConfiguration() {
|
||||
if (!configurationFile) return;
|
||||
/*
|
||||
Import replaces the complete draft and applies it immediately, so the
|
||||
confirmation names both consequences before reading or transmitting the
|
||||
operator-selected file. Administrator accounts remain owned by the
|
||||
initialized database and are never imported from this screen.
|
||||
*/
|
||||
const confirmed = window.confirm(
|
||||
'Replace the current configuration with this YAML file and apply it now? Any unsaved edits will be discarded. Administrator accounts in the file will be ignored.',
|
||||
);
|
||||
if (!confirmed) return;
|
||||
|
||||
setImporting(true);
|
||||
setError('');
|
||||
setNotice('');
|
||||
setValidationErrors([]);
|
||||
try {
|
||||
const yamlText = await configurationFile.text();
|
||||
const response = await runSensitive(() => importAdminConfigurationFile(socket, {
|
||||
fileName: configurationFile.name,
|
||||
yaml: yamlText,
|
||||
expectedRevision: revision,
|
||||
}));
|
||||
onSnapshot(response.snapshot);
|
||||
setNotice(response.ignoredAdministratorCount > 0
|
||||
? `Configuration imported and applied. ${response.ignoredAdministratorCount} administrator entr${response.ignoredAdministratorCount === 1 ? 'y was' : 'ies were'} ignored.`
|
||||
: 'Configuration imported and applied.');
|
||||
} catch (importError) {
|
||||
setError(importError.message);
|
||||
setValidationErrors(importError.validationErrors || []);
|
||||
} finally {
|
||||
setImporting(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<CardFrame title="Configuration" meta={`revision ${revision}`} clipOverflow={false} bodyClassName="p-0.5">
|
||||
<div className="configuration-toolbar sticky top-0 z-20 mb-0.5 space-y-0.5 border border-neutral-500/60 bg-neutral-900/95 p-0.5 backdrop-blur">
|
||||
@@ -70,14 +111,33 @@ export default function ConfigurationEditor({ snapshot, socket, runSensitive, on
|
||||
editor may use a wide canvas, but width is never used to separate a
|
||||
control from the content that explains it. */}
|
||||
<div className="flex flex-wrap gap-0.5">
|
||||
<button type="button" className="button-dark" disabled={saving} onClick={onReload}>Reload</button>
|
||||
<button type="button" className="button-dark" disabled={!dirty || saving} onClick={() => {
|
||||
<button type="button" className="button-dark" disabled={busy} onClick={onReload}>Reload</button>
|
||||
<button type="button" className="button-dark" disabled={!dirty || busy} onClick={() => {
|
||||
setDraft(clone(serverValue));
|
||||
setSecretOperations({});
|
||||
}}>Reset</button>
|
||||
<button type="button" className="button-dark" disabled={!dirty || saving} onClick={save}>{saving ? 'Applying…' : 'Save configuration'}</button>
|
||||
<button type="button" className="button-dark" disabled={!dirty || busy} onClick={save}>{saving ? 'Applying…' : 'Save configuration'}</button>
|
||||
</div>
|
||||
</div>
|
||||
<CardFrame title="Import legacy YAML" bodyClassName="space-y-0.5 p-1 text-sm" clipOverflow={false}>
|
||||
<p className="text-sm text-slate-300">Replace this configuration from an explicitly selected legacy file. Unknown old settings and administrator accounts are ignored; current settings are validated and applied immediately.</p>
|
||||
{/* Keep the picker and its action beside each other at the start of the
|
||||
card. The configuration canvas can be wide, but this local action
|
||||
should never be separated from the file it operates on. */}
|
||||
<div className="flex max-w-3xl flex-col gap-0.5 md:flex-row">
|
||||
<input
|
||||
key={revision}
|
||||
className="field-input min-w-0 flex-1"
|
||||
type="file"
|
||||
accept=".yaml,.yml,text/yaml"
|
||||
disabled={busy}
|
||||
onChange={(event) => setConfigurationFile(event.target.files?.[0] || null)}
|
||||
/>
|
||||
<button type="button" className="button-dark" disabled={busy || !configurationFile} onClick={importSelectedConfiguration}>
|
||||
{importing ? 'Importing…' : 'Import selected YAML'}
|
||||
</button>
|
||||
</div>
|
||||
</CardFrame>
|
||||
{snapshot.configurationApplication?.services?.some((service) => service.status === 'failed') ? (
|
||||
<div className="mb-0.5 border border-amber-500/60 bg-amber-950/40 p-1 text-xs text-amber-100">
|
||||
<p className="font-semibold">Configuration was saved, but some services could not reload</p>
|
||||
@@ -89,6 +149,7 @@ export default function ConfigurationEditor({ snapshot, socket, runSensitive, on
|
||||
</div>
|
||||
) : null}
|
||||
{error ? <p className="border border-red-500/60 bg-red-950/40 p-1 text-xs text-red-100">{error}</p> : null}
|
||||
{notice ? <p className="border border-emerald-500/60 bg-emerald-950/40 p-1 text-sm text-emerald-100">{notice}</p> : null}
|
||||
{validationErrors.length ? (
|
||||
<div className="border border-red-500/60 bg-red-950/40 p-1 text-xs text-red-100">
|
||||
<p className="font-semibold">Configuration could not be saved</p>
|
||||
|
||||
Reference in New Issue
Block a user