feat(services): initial config on ADD SERVICE — schema modal in DeckyCard, MazeNET drag, and Inspector
- DeckyServiceAddRequest gains an optional `config: dict` field, validated
against the service's config_schema before any state mutation (400 on
bad type, no half-written rows).
- Engine: add_service threads `config` into _add_topology_service /
_add_fleet_service, persisting validated cfg to decky_config.service_config
BEFORE compose regen so the first `up -d --build` materialises the env on
the new container. No follow-up apply needed.
- Frontend: shared AddServiceConfigModal — same wizard accordion shape, used by:
* DeckyCard's ADD SERVICE picker (Fleet & MazeNET inspectors via shared component)
* MazeNET Inspector's ADD SERVICE picker
* MazeNET palette drag-drop onto a deployed decky
Empty-schema services short-circuit to a one-click add (no modal flash).
Operator can cancel; errors surface in the modal.
- Tests: add_service config plumbing — persist, drop unknown keys, 400-equivalent
on bad types, back-compat empty-config.
- Drive-by: fix stale repo-method names in test_services_live.py
(create_topology_decky → add_topology_decky, get_topology_decky → list+pick helper,
service.added → service_added topic).
This commit is contained in:
@@ -34,7 +34,10 @@ interface Props {
|
||||
// topologyStatus (active/degraded → live, pending/anything else →
|
||||
// design-time only). Wiring these props from MazeNET.tsx is the
|
||||
// single switch that turns chips into live controls.
|
||||
onLiveAddService?: (nodeName: string, slug: string) => Promise<void>;
|
||||
/** Trigger the schema-driven add-service flow. Synchronous: opens
|
||||
* the AddServiceConfigModal at the page level (or auto-confirms if
|
||||
* the service has no schema fields). Errors surface inside the modal. */
|
||||
onLiveAddService?: (nodeName: string, slug: string) => void;
|
||||
onLiveRemoveService?: (nodeName: string, slug: string) => Promise<void>;
|
||||
/** Per-decky-eligible service slugs, fetched via useServiceRegistry. */
|
||||
availableServices?: string[];
|
||||
@@ -202,21 +205,15 @@ const Inspector: React.FC<Props> = ({
|
||||
<button
|
||||
type="button"
|
||||
disabled={!addSlug || busy === addSlug}
|
||||
onClick={async () => {
|
||||
onClick={() => {
|
||||
if (!addSlug) return;
|
||||
setOpError(null);
|
||||
setBusy(addSlug);
|
||||
try {
|
||||
await onLiveAddService!(node.name, addSlug);
|
||||
setAddOpen(false);
|
||||
setAddSlug('');
|
||||
} catch (err) {
|
||||
const msg = (err as { response?: { data?: { detail?: string } } })?.response?.data?.detail
|
||||
?? 'Add failed.';
|
||||
setOpError(msg);
|
||||
} finally {
|
||||
setBusy(null);
|
||||
}
|
||||
// Fire-and-forget: opens the schema-driven config
|
||||
// modal at the page level (or auto-confirms for
|
||||
// schema-less services). Errors surface in the modal.
|
||||
onLiveAddService!(node.name, addSlug);
|
||||
setAddOpen(false);
|
||||
setAddSlug('');
|
||||
}}
|
||||
style={{
|
||||
padding: '4px 10px', fontSize: '0.7rem',
|
||||
|
||||
Reference in New Issue
Block a user