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:
gsxdsm
2026-04-07 23:30:32 -07:00
parent 74cae4f870
commit 075402c11d
17 changed files with 2218 additions and 32 deletions

View File

@@ -1,6 +1,6 @@
import { useState, useMemo, useCallback, useEffect } from "react";
import { Plus, LayoutGrid, Filter, ArrowUpDown, Activity, CheckCircle, AlertCircle, Folder, Inbox } from "lucide-react";
import type { ProjectInfo, ProjectHealth } from "../api";
import type { ProjectInfo, ProjectHealth, NodeInfo } from "../api";
import type { ProjectStatus } from "@fusion/core";
import { ProjectCard } from "./ProjectCard";
import { ProjectGridSkeleton } from "./ProjectGridSkeleton";
@@ -15,6 +15,7 @@ export interface ProjectOverviewProps {
onResumeProject: (project: ProjectInfo) => void;
onRemoveProject: (project: ProjectInfo) => void;
onViewAllProjects?: () => void;
nodes?: NodeInfo[];
}
type FilterTab = "all" | "active" | "paused" | "errored";
@@ -43,6 +44,7 @@ export function ProjectOverview({
onPauseProject,
onResumeProject,
onRemoveProject,
nodes = [],
}: ProjectOverviewProps) {
const [activeFilter, setActiveFilter] = useState<FilterTab>("all");
const [sortBy, setSortBy] = useState<SortOption>("activity");
@@ -327,17 +329,21 @@ export function ProjectOverview({
{/* Project grid */}
<div className="project-grid">
{sortedProjects.map(({ project, health }) => (
<ProjectCard
key={project.id}
project={project}
health={health}
onSelect={handleSelectProject}
onPause={onPauseProject}
onResume={onResumeProject}
onRemove={onRemoveProject}
/>
))}
{sortedProjects.map(({ project, health }) => {
const projectNode = nodes.find((node) => node.id === project.nodeId);
return (
<ProjectCard
key={project.id}
project={project}
health={health}
node={projectNode}
onSelect={handleSelectProject}
onPause={onPauseProject}
onResume={onResumeProject}
onRemove={onRemoveProject}
/>
);
})}
</div>
{/* No results state */}