Files
fusion/packages/dashboard/app/components/ProjectNodeSelector.tsx
gsxdsm b9f6f8e56c feat(FN-1081): add dashboard nodes management workflow
- Add node API typings and client methods plus a polling useNodes hook with visibility-triggered refresh
- Introduce NodesView with NodeCard, AddNodeModal, and NodeDetailModal to register, inspect, update, health-check, and remove nodes
- Wire the Nodes surface into the app shell/header and add project node assignment UI with project card node badges
- Add unit coverage for useNodes, NodesView, NodeCard, Header, and ProjectCard behavior, plus node-specific dashboard styling
2026-04-07 23:30:32 -07:00

58 lines
1.5 KiB
TypeScript

import { useMemo } from "react";
import type { NodeInfo } from "../api";
interface ProjectNodeSelectorProps {
projectId: string;
currentNodeId?: string;
onSelect: (nodeId: string | null) => void;
nodes: NodeInfo[];
disabled?: boolean;
}
const STATUS_DOT: Record<NodeInfo["status"], string> = {
online: "🟢",
offline: "🔴",
connecting: "🟡",
error: "🔴",
};
export function ProjectNodeSelector({
projectId,
currentNodeId,
onSelect,
nodes,
disabled = false,
}: ProjectNodeSelectorProps) {
const sortedNodes = useMemo(() => {
return [...nodes].sort((a, b) => a.name.localeCompare(b.name));
}, [nodes]);
const selectedValue = currentNodeId ?? "";
return (
<label className="project-node-selector" htmlFor={`project-node-selector-${projectId}`}>
<span className="project-node-selector__label">Runtime Node</span>
<select
id={`project-node-selector-${projectId}`}
value={selectedValue}
onChange={(event) => {
const value = event.target.value;
onSelect(value ? value : null);
}}
disabled={disabled}
>
<option value="">Auto (no assignment)</option>
{sortedNodes.map((node) => (
<option
key={node.id}
value={node.id}
className={node.status === "offline" || node.status === "error" ? "project-node-selector__option--dim" : ""}
>
{STATUS_DOT[node.status]} {node.name} ({node.type})
</option>
))}
</select>
</label>
);
}