// Schema-Generated Configuration Form // Purpose: Renders the server-provided JSON Schema as one hierarchical form without feature-specific React components. // Scope: Adds only the generic secret interaction that ordinary JSON Schema form controls cannot safely infer. import { useMemo } from 'react'; import Form from '@rjsf/core'; import validator from '@rjsf/validator-ajv8'; function buildUiSchema(schema, path = '') { /* `writeOnly` is standard JSON Schema metadata and is the sole reason a field needs a special widget. The walk records its dotted path for the existing server secret-operation protocol; it contains no service or field names. */ if (!schema || typeof schema !== 'object') return {}; if (schema.writeOnly === true) { return { 'ui:widget': 'SecretWidget', 'ui:options': { secretPath: path }, }; } if (schema.type !== 'object' || !schema.properties) return {}; return Object.fromEntries(Object.entries(schema.properties).map(([key, childSchema]) => [ key, buildUiSchema(childSchema, path ? `${path}.${key}` : key), ])); } function SecretWidget({ id, disabled, readonly, options, registry }) { const secretPath = options.secretPath; const context = registry.formContext || {}; const operation = context.secretOperations?.[secretPath]; const configured = Boolean(context.configuredSecrets?.[secretPath]); const replacing = operation?.action === 'replace'; const clearing = operation?.action === 'clear'; const unavailable = disabled || readonly; function setOperation(nextOperation) { if (!unavailable) context.setSecretOperation?.(secretPath, nextOperation); } return (
{clearing ? 'Will be cleared when saved' : replacing ? 'Replacement pending' : configured ? 'Configured' : 'Not configured'}
{replacing ? ( setOperation({ action: 'replace', value: event.target.value })} /> ) : null}
); } export default function SchemaConfigurationForm({ schema, value, onChange, configuredSecrets, secretOperations, setSecretOperation }) { const uiSchema = useMemo(() => buildUiSchema(schema), [schema]); const widgets = useMemo(() => ({ SecretWidget }), []); const formContext = useMemo(() => ({ configuredSecrets, secretOperations, setSecretOperation, }), [configuredSecrets, secretOperations, setSecretOperation]); return (
onChange(formData)} > {/* Saving is owned by the sticky revision-aware toolbar above the form. */} <>
); }