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
This commit is contained in:
57
packages/dashboard/app/components/ProjectNodeSelector.tsx
Normal file
57
packages/dashboard/app/components/ProjectNodeSelector.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user