import { useEffect, useMemo, useState, type FormEvent, type ReactNode } from "react"; import { Button, GlassSurface, Icon, Select, SettingsCard, StatusBadge, TextAreaField, TextField, Window, WindowFooterActions, } from "@nodedc/ui-react"; import { createConfigurationRevision, ensureAdapterPackage, ensureDeviceBinding, ensureEdge, ensureRoute, registerAdapterVersion, registerModelProfile, revokeDeviceBinding, sendServicePing, setDesiredConfiguration, upsertProjectGrant, } from "./api"; import type { AdapterPackageView, AdapterVersionView, BindingView, DeviceManagerSession, EdgeView, ModelProfileView, ProjectWorkspace, } from "./types"; export type ControlViewId = | "catalog" | "infrastructure" | "sessions" | "bindings" | "commands" | "audit" | "access" | "settings"; type DialogId = | "adapter-package" | "adapter-version" | "model-profile" | "edge" | "route" | "binding" | "grant" | "configuration" | null; export function DeviceControlView({ view, workspace, session, onRefresh, onError, }: { view: ControlViewId; workspace: ProjectWorkspace; session: DeviceManagerSession; onRefresh: () => Promise; onError: (reason: unknown) => void; }) { const [dialog, setDialog] = useState(null); const capabilities = new Set(workspace.project.access.capabilities); const platformOwner = session.actor.hubRole === "owner"; const close = () => setDialog(null); const completed = async () => { close(); await onRefresh(); }; const mutateAndRefresh = async (mutation: () => Promise) => { try { await mutation(); await onRefresh(); } catch (reason) { onError(reason); } }; return ( <> {view === "catalog" ? ( setDialog("adapter-package")} onCreateVersion={() => setDialog("adapter-version")} onCreateProfile={() => setDialog("model-profile")} onActivateVersion={(version) => mutateAndRefresh(() => registerAdapterVersion({ adapterPackageRef: version.adapterPackageRef, version: version.version, runtimePackageRef: version.runtimePackageRef, contentDigest: version.contentDigest, contractVersion: version.contractVersion, capabilities: version.capabilities, lifecycleState: "active", }))} onActivateProfile={(profile) => mutateAndRefresh(() => registerModelProfile({ adapterVersionRef: profile.adapterVersionRef || "", profileRef: profile.modelProfileRef, schemaVersion: profile.schemaVersion, vendor: profile.vendor, model: profile.model, deviceType: profile.deviceType, protocol: profile.protocol, schemaArtifactRef: profile.schemaArtifactRef || "", profileDigest: profile.profileDigest || "", capabilities: profile.capabilities, lifecycleState: "active", }))} /> ) : null} {view === "infrastructure" ? ( setDialog("edge")} onCreateRoute={() => setDialog("route")} onActivateEdge={(edge) => mutateAndRefresh(() => ensureEdge({ edgeKey: edge.edgeKey, displayName: edge.displayName, deploymentRef: edge.deploymentRef, lifecycleState: "active", }))} onActivateRoute={(route) => mutateAndRefresh(() => ensureRoute({ projectRef: workspace.project.projectRef, routeKey: route.routeKey, displayName: route.displayName, edgeRef: route.edgeRef, modelProfileRef: route.modelProfileRef, listenerRef: route.listenerRef, protocol: route.protocol, direction: route.direction, lifecycleState: "active", }))} /> ) : null} {view === "sessions" ? : null} {view === "bindings" ? ( setDialog("binding")} onRevoke={(binding) => revokeDeviceBinding({ projectRef: workspace.project.projectRef, bindingRef: binding.bindingRef, resolutionCode: "operator.revoked", }).then(onRefresh).catch(onError)} /> ) : null} {view === "commands" ? ( ) : null} {view === "audit" ? : null} {view === "access" ? ( setDialog("grant")} /> ) : null} {view === "settings" ? ( setDialog("configuration")} /> ) : null} ); } function CatalogView({ workspace, canManage, onCreatePackage, onCreateVersion, onCreateProfile, onActivateVersion, onActivateProfile }: { workspace: ProjectWorkspace; canManage: boolean; onCreatePackage: () => void; onCreateVersion: () => void; onCreateProfile: () => void; onActivateVersion: (version: AdapterVersionView) => void; onActivateProfile: (profile: ModelProfileView) => void; }) { return ( : null} /> {workspace.modelProfiles.map((profile) => ( onActivateProfile(profile)}>Активировать ) : null} /> ))} {workspace.adapterVersions.map((version) => ( onActivateVersion(version)}>Активировать ) : null} /> ))} {workspace.adapterPackages.map((adapterPackage) => ( version.adapterPackageRef === adapterPackage.adapterPackageRef) .map((version) => `${version.version} · ${version.lifecycleState}`)} /> ))} ); } function InfrastructureView({ workspace, canManageCatalog, canManageRoutes, onCreateEdge, onCreateRoute, onActivateEdge, onActivateRoute }: { workspace: ProjectWorkspace; canManageCatalog: boolean; canManageRoutes: boolean; onCreateEdge: () => void; onCreateRoute: () => void; onActivateEdge: (edge: EdgeView) => void; onActivateRoute: (route: ProjectWorkspace["routes"][number]) => void; }) { return ( {canManageCatalog ? : null} {canManageRoutes ? : null} } /> {workspace.routes.map((route) => ( onActivateRoute(route)}>Активировать ) : null} /> ))} {workspace.edges.map((edge) => ( onActivateEdge(edge)}>Активировать ) : null} /> ))} ); } function SessionsView({ workspace }: { workspace: ProjectWorkspace }) { return ( {workspace.sessions.map((session) => ( ))} ); } function BindingsView({ workspace, canManage, onCreate, onRevoke }: { workspace: ProjectWorkspace; canManage: boolean; onCreate: () => void; onRevoke: (binding: BindingView) => void; }) { return ( Новый binding : null} /> {workspace.bindings.map((binding) => ( onRevoke(binding)}>Отозвать ) : binding.capabilities.join(", ")} /> ))} ); } function CommandsView({ workspace, canDispatch, onRefresh, onError }: { workspace: ProjectWorkspace; canDispatch: boolean; onRefresh: () => Promise; onError: (reason: unknown) => void; }) { const supportedDevices = workspace.devices.filter( (device) => device.modelProfileRef === "arusnavi.b2.internal.v1" && !["suspended", "retired"].includes(device.lifecycleState), ); const [deviceRef, setDeviceRef] = useState(supportedDevices[0]?.deviceRef ?? ""); const [accessCode, setAccessCode] = useState(""); const [submitting, setSubmitting] = useState(false); const enabled = workspace.policies.commandTransport === "typed-service-ping-v1"; useEffect(() => { if (!supportedDevices.some((device) => device.deviceRef === deviceRef)) { setDeviceRef(supportedDevices[0]?.deviceRef ?? ""); } }, [deviceRef, supportedDevices]); const submit = async (event: FormEvent) => { event.preventDefault(); if (!enabled || !canDispatch || !deviceRef || !/^\d{6}$/.test(accessCode)) return; setSubmitting(true); try { await sendServicePing({ projectRef: workspace.project.projectRef, deviceRef, accessCode, expiresInSeconds: 300, }); setAccessCode(""); await onRefresh(); } catch (reason) { onError(reason); } finally { setSubmitting(false); } }; return (
{enabled ? "Типизированный командный канал активен" : "Command transport выключен"}

{enabled ? "Доступна только безопасная проверка сервиса. Произвольные команды, прошивка, очистка памяти и перезагрузка отсутствуют. Код устройства существует только в памяти Core до отправки или истечения TTL." : "Ни UI, ни BFF не имеют raw command builder. acknowledged означает подтверждение протокола, verified — отдельное доказательство состояния."}

{workspace.policies.commandTransport}
{enabled ? (
({ value: item.adapterPackageRef, label: item.displayName }))} /> setVersion(event.target.value)} required placeholder="1.0.0" /> setRuntimeRef(event.target.value)} required /> setDigest(event.target.value)} required placeholder="sha256:…" /> setContractVersion(event.target.value)} required /> setCapabilities(event.target.value)} description="Через запятую" /> ; } function ModelProfileDialog({ versions, ...props }: DialogBaseProps & { versions: AdapterVersionView[] }) { const [versionRef, setVersionRef] = useState(versions[0]?.adapterVersionRef ?? ""); const [profileRef, setProfileRef] = useState(""); const [schemaVersion, setSchemaVersion] = useState(""); const [vendor, setVendor] = useState(""); const [model, setModel] = useState(""); const [deviceType, setDeviceType] = useState(""); const [protocol, setProtocol] = useState(""); const [schemaRef, setSchemaRef] = useState(""); const [digest, setDigest] = useState(""); const [capabilities, setCapabilities] = useState(""); useEffect(() => { if (!versions.some((item) => item.adapterVersionRef === versionRef)) { setVersionRef(versions[0]?.adapterVersionRef ?? ""); } }, [versionRef, versions]); return { await registerModelProfile({ adapterVersionRef: versionRef, profileRef, schemaVersion, vendor, model, deviceType, protocol: protocol.toUpperCase(), schemaArtifactRef: schemaRef, profileDigest: digest, capabilities: commaList(capabilities), lifecycleState: "draft", }); }}> ({ value: item.edgeRef, label: item.displayName, description: item.lifecycleState }))} /> setDisplayName(event.target.value)} required /> setTargetKind(event.target.value)} required placeholder="foundry.application" /> setTargetRef(event.target.value)} required /> setCapabilities(event.target.value)} description="observe, inspect, configure, command" required /> ; } function GrantDialog({ projectRef, ...props }: DialogBaseProps & { projectRef: string }) { const [principalKind, setPrincipalKind] = useState<"user" | "group">("user"); const [principalRef, setPrincipalRef] = useState(""); const [role, setRole] = useState("viewer"); const [allow, setAllow] = useState(""); const [deny, setDeny] = useState(""); return { await upsertProjectGrant({ projectRef, principalKind, principalRef, projectRole: role, capabilityAllow: commaList(allow), capabilityDeny: commaList(deny), lifecycleState: "active", }); }}> ({ value, label: value, disabled: value === "owner" && principalKind === "group" }))} /> setAllow(event.target.value)} description="Опциональные точечные добавления" /> setDeny(event.target.value)} description="Deny имеет приоритет" /> ; } function ConfigurationDialog({ workspace, ...props }: DialogBaseProps & { workspace: ProjectWorkspace }) { const [deviceRef, setDeviceRef] = useState(workspace.devices[0]?.deviceRef ?? ""); const [configuration, setConfiguration] = useState("{\n \"reporting_interval_seconds\": 30\n}"); const [summary, setSummary] = useState(""); useEffect(() => { if (!workspace.devices.some((item) => item.deviceRef === deviceRef)) { setDeviceRef(workspace.devices[0]?.deviceRef ?? ""); } }, [deviceRef, workspace.devices]); return { const parsed = JSON.parse(configuration) as Record; const created = await createConfigurationRevision({ projectRef: workspace.project.projectRef, deviceRef, configuration: parsed, changeSummary: summary || null, }); await setDesiredConfiguration({ projectRef: workspace.project.projectRef, deviceRef, configurationRevisionRef: created.result.configurationRevision.configurationRevisionRef, }); }}>