diff --git a/frontend/src/pages/swarm/node.tsx b/frontend/src/pages/swarm/node.tsx index a5e3bb1a3..f1aac69ab 100644 --- a/frontend/src/pages/swarm/node.tsx +++ b/frontend/src/pages/swarm/node.tsx @@ -1,13 +1,31 @@ -import { ResourceLink } from "@components/resources/common"; -import { PageHeaderName } from "@components/util"; -import { usePermissions, useRead, useSetTitle } from "@lib/hooks"; +import { + ResourceDescription, + ResourceLink, + ResourcePageHeader, +} from "@components/resources/common"; +import { + useLocalStorage, + usePermissions, + useRead, + useSetTitle, +} from "@lib/hooks"; import { Button } from "@ui/button"; -import { ChevronLeft, Loader2, Zap } from "lucide-react"; -import { useNavigate, useParams } from "react-router-dom"; +import { ChevronLeft, Loader2 } from "lucide-react"; +import { Link, useParams } from "react-router-dom"; import { MonacoEditor } from "@components/monaco"; import { SWARM_ICONS, useSwarm } from "@components/resources/swarm"; import { Section } from "@components/layouts"; +import { ExportButton } from "@components/export"; +import { + stroke_color_class_by_intention, + swarm_node_state_intention, +} from "@lib/color"; +import { ResourceNotifications } from "@pages/resource-notifications"; +import { Types } from "komodo_client"; +import { Dispatch, ReactNode, SetStateAction, useMemo, useState } from "react"; +import { MobileFriendlyTabsSelector } from "@ui/mobile-friendly-tabs"; import { RemoveSwarmResource } from "./remove"; +import { SwarmTasksTable } from "@components/resources/swarm/table"; export default function SwarmNodePage() { const { id, node: __node } = useParams() as { @@ -20,14 +38,14 @@ export default function SwarmNodePage() { swarm: id, node: _node, }); - useSetTitle( - `${swarm?.name} | Node | ${node?.Spec?.Name ?? node?.Description?.Hostname ?? node?.ID ?? "Unknown"}` - ); - const { canExecute } = usePermissions({ + const state = node?.Status?.State; + const { canWrite, canExecute } = usePermissions({ type: "Swarm", id, }); - const nav = useNavigate(); + useSetTitle( + `${swarm?.name} | Node | ${node?.Description?.Hostname ?? "Unknown"}` + ); if (isPending) { return ( @@ -42,57 +60,194 @@ export default function SwarmNodePage() { } const Icon = SWARM_ICONS.Node; + const intent = swarm_node_state_intention(state); return ( -
- {/* HEADER */} -
- {/* BACK */} -
- -
- - {/* TITLE */} +
-
- -
- -
- - {/* INFO */} -
- Swarm Node - +
- - {canExecute && node.ID && ( -
}> -
- + {/* HEADER */} +
+
+ + } + resource={undefined} + name={node.Description?.Hostname} + state={state} + status={node.Spec?.Role} /> +
+
+
Swarm Node
+ | + +
+
-
- )} + +
+ {/** NOTIFICATIONS */} + + - +
+ {/* Actions */} + {canExecute && node.ID && ( + + )} + + {/* Tabs */} +
+ {swarm && } +
+
); } + +type SwarmNodeTabsView = "Tasks" | "Inspect"; + +const SwarmNodeTabs = ({ + swarm, + _node, + node, +}: { + swarm: Types.SwarmListItem; + _node: string; + node: Types.SwarmNode; +}) => { + const [_view, setView] = useLocalStorage( + `swarm-${swarm.id}-node-${node}-tabs-v1`, + "Tasks" + ); + const { specificInspect } = usePermissions({ + type: "Swarm", + id: swarm.id, + }); + const _search = useState(""); + + const view = !specificInspect && _view === "Inspect" ? "Tasks" : _view; + + const tabs = useMemo( + () => [ + { + value: "Tasks", + }, + { + value: "Inspect", + disabled: !specificInspect, + }, + ], + [specificInspect] + ); + + const Selector = ( + + ); + + switch (view) { + case "Tasks": + return ( + + ); + case "Inspect": + return ( + + ); + } +}; + +const SwarmNodeTasks = ({ + id, + node_id, + Selector, + _search, +}: { + id: string; + node_id: string | undefined; + Selector: ReactNode; + _search: [string, Dispatch>]; +}) => { + const tasks = + useRead("ListSwarmTasks", { swarm: id }).data?.filter( + (service) => node_id && service.NodeID === node_id + ) ?? []; + return ( + + ); +}; + +const SwarmNodeInspect = ({ + swarm, + node, + titleOther, +}: { + swarm: string; + node: string; + titleOther: ReactNode; +}) => { + const { data: inspect, isPending } = useRead("InspectSwarmNode", { + swarm, + node, + }); + + if (isPending) { + return ( +
+ +
+ ); + } + + if (!node) { + return
Failed to inspect node.
; + } + + return ( +
+ +
+ ); +};