Files
technitium-console/src/screens/apps/AppConfig.tsx
T
DenozordecandClaude Opus 5.5 0e05039202 Hub stage 3: compare and sync app configs across servers
The hub keeps a baseline per app in /data/apps/<app>/baseline.json. Local
fields (overrides.json) are left out of the sync and out of the matrix
comparison. Each server gets the baseline plus its own values.

Applying goes target picker, preview with per-server diffs and warnings,
then apply. Apply refuses a server whose config changed since the
preview, saves the old config to history, then reads the new one back
to check it.

Large config bodies are read only for signed-in callers. The App config
dialog has Compare, Baseline and Local fields tabs and an empty state
that offers an import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:46:15 +07:00

98 lines
2.6 KiB
TypeScript

import { useEffect, useState } from 'react'
import { setAppConfig } from '../../api/apps'
import { Button } from '../../ui/Button'
import { Dialog } from '../../ui/Dialog'
import { Field } from '../../ui/Field'
import { error, type AlertState } from './Apps'
import styles from './Apps.module.css'
import { Notifier } from '../../ui/Notifier'
/*
A replica of `modalAppConfig` (index.html:6255-6280) and `saveAppConfig`
(apps.js:493-522).
It is a TEXT EDITOR, not a form: the content of `dnsApp.config` is defined by each
app and the console does not know what fields it has. Upstream puts a 15-row
`<textarea>` with the spellchecker off, and that is what is here.
The config arrives already read from the screen, because upstream also asks for it
BEFORE opening the modal (the "Config" button sits at "Loading...").
*/
export function AppConfig({
open,
onOpenChange,
token,
name,
initialConfig,
onSaved,
node = '',
}: {
open: boolean
onOpenChange: (o: boolean) => void
token: string | null
name: string
initialConfig: string
onSaved: (name: string) => void
/** Behind a hub: the one server it goes to. */
node?: string
}) {
const [config, setConfig] = useState(initialConfig)
const [alert, setAlert] = useState<AlertState | null>(null)
const [busy, setBusy] = useState(false)
useEffect(() => {
if (open) {
setConfig(initialConfig)
setAlert(null)
setBusy(false)
}
}, [open, initialConfig])
async function save() {
setBusy(true)
const outcome = await setAppConfig(token, name, config, node)
setBusy(false)
if (outcome.kind !== 'ok') {
setAlert(error(outcome))
return
}
onSaved(name)
}
return (
<Dialog
open={open}
onOpenChange={onOpenChange}
size="medium"
title={`App Config - ${name}`}
actions={
<>
<Button variant="primary" disabled={busy} onClick={() => void save()}>
Save
</Button>
</>
}
>
<Notifier notice={alert} onClose={() => setAlert(null)} />
<p className={styles.note}>
Edit the <code>dnsApp.config</code> config file below as required by the DNS application.
</p>
<Field label="Config File">
{(id) => (
<textarea
id={id}
className={styles.config}
spellCheck={false}
value={config}
onChange={(e) => setConfig(e.target.value)}
/>
)}
</Field>
<p className={styles.note}>
Note: The app will reload the config automatically after you save it.
</p>
</Dialog>
)
}