feat(ui): schema-driven ServiceConfigForm in Fleet & MazeNET inspectors
ServiceConfigForm.tsx fetches /topologies/services/{slug}/schema and renders
typed inputs (string/password/int/bool/textarea/enum) with reveal toggles for
secrets. SAVE persists via PUT (no restart); APPLY persists + force-recreates
the service container after a confirm dialog (matches the forwards_l3 pattern).
Mounts:
- DeckyFleet DeckyCard: clicking a service tag toggles the form below the
EXPOSED row, gated on liveServicesEnabled (admin + non-swarm).
- MazeNET Inspector: renders the form above REMOVE SERVICE when a service
is selected on a non-observed decky.
UI test plan is manual — no jsdom test infra in decnet_web yet.
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
} from '../../icons';
|
||||
import type { Net, MazeNode, Edge } from './types';
|
||||
import { DEFAULT_SERVICES } from './data';
|
||||
import ServiceConfigForm from '../ServiceConfigForm';
|
||||
|
||||
export type Selection =
|
||||
| { type: 'net'; id: string }
|
||||
@@ -18,6 +19,9 @@ interface Props {
|
||||
nets: Net[];
|
||||
nodes: MazeNode[];
|
||||
edges: Edge[];
|
||||
/** Topology ID (MazeNET-only) — required for the schema-driven service
|
||||
* config form to hit the per-topology REST path. Omit for fleet. */
|
||||
topologyId?: string;
|
||||
topologyStatus?: string;
|
||||
onClose?: () => void;
|
||||
onDeleteNet?: (id: string) => void;
|
||||
@@ -46,7 +50,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const Inspector: React.FC<Props> = ({
|
||||
selection, nets, nodes, edges, topologyStatus, onClose,
|
||||
selection, nets, nodes, edges, topologyId, topologyStatus, onClose,
|
||||
onDeleteNet, onDeleteNode, onDeleteEdge, onRemoveService,
|
||||
onLiveAddService, onLiveRemoveService, availableServices = [],
|
||||
onToggleGateway,
|
||||
@@ -440,6 +444,18 @@ const Inspector: React.FC<Props> = ({
|
||||
<div className="k">SUBNET</div>
|
||||
<div className="v">{serviceParentNet?.label ?? '—'}</div>
|
||||
</div>
|
||||
{topologyId && serviceParent && serviceParent.kind !== 'observed' && (
|
||||
<ServiceConfigForm
|
||||
key={`${serviceParent.name}:${serviceSel.id}`}
|
||||
deckyName={serviceParent.name}
|
||||
serviceSlug={serviceSel.id}
|
||||
topologyId={topologyId}
|
||||
currentConfig={
|
||||
((serviceParent.decky_config as { service_config?: Record<string, Record<string, unknown>> } | undefined)
|
||||
?.service_config?.[serviceSel.id]) ?? {}
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{onRemoveService && serviceParent && serviceParent.kind !== 'observed' && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -892,6 +892,7 @@ const MazeNET: React.FC = () => {
|
||||
nets={nets}
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
topologyId={topologyId || undefined}
|
||||
topologyStatus={topoStatus}
|
||||
onClose={() => setInspectorOpen(false)}
|
||||
onDeleteNet={removeNet}
|
||||
|
||||
Reference in New Issue
Block a user