diff --git a/cueweb/components/ui/job-dependency-graph.tsx b/cueweb/components/ui/job-dependency-graph.tsx index f0baeee18..b7cb26fd4 100644 --- a/cueweb/components/ui/job-dependency-graph.tsx +++ b/cueweb/components/ui/job-dependency-graph.tsx @@ -18,6 +18,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useRouter } from "next/navigation"; +import { useSession } from "next-auth/react"; import { Background, Controls, @@ -27,13 +28,38 @@ import { NodeProps, Position, ReactFlow, + type ReactFlowInstance, } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; import dagre from "dagre"; import { useTheme } from "next-themes"; +import { + TbCheck, + TbHelp, + TbLayoutGrid, + TbLink, + TbPacman, + TbReload, + TbSettings, +} from "react-icons/tb"; +import { MdOutlineCancel } from "react-icons/md"; import { Job } from "@/app/jobs/columns"; +import { Layer } from "@/app/layers/layer-columns"; import { Depend } from "@/app/utils/get_utils"; +import { UNKNOWN_USER } from "@/app/utils/constants"; +import { + viewLayerDependenciesGivenRow, + layerDependencyWizardGivenRow, + markdoneLayerGivenRow, + reorderLayerFramesGivenRow, + staggerLayerFramesGivenRow, + layerPropertiesGivenRow, + killLayerGivenRow, + eatLayerFramesGivenRow, + retryLayerFramesGivenRow, + retryLayerDeadFramesGivenRow, +} from "@/app/utils/action_utils"; // Silent POST that intentionally bypasses accessGetApi - the BFS below // expects partial failure (some jobs in the tree may have been @@ -127,6 +153,9 @@ type GraphNodeData = { kind: NodeKind; jobName?: string; isFocus: boolean; + // The full Layer object for LAYER nodes that belong to the focus job, so the + // right-click menu can run the same actions as the Layers table. + layer?: Layer; }; // Custom node renderer: monospace, truncates long names, full text in @@ -262,6 +291,59 @@ function ingestDepend( return { erJob: er.jobName, onJob: on.jobName }; } +// Fetch the focus job's layers and add them as LAYER nodes hanging off the +// job node. CueGUI's JobMonitorGraph is a *layer* graph (it draws one node per +// `job.getLayers()`), so the panel should always show the job's layers - even +// when the job has no cross-job dependencies (otherwise a normal job renders +// an empty "No dependencies found" panel). Layers that participate in a depend +// already get a node from the BFS below (same id scheme), so this only fills in +// the dependency-less layers and wires every layer to the job node. +async function ingestFocusLayers( + focus: Job, + nodes: Map, + edges: Map, +): Promise { + // /api/job/getlayers already unwraps to the layers array; keep fallbacks in + // case the gateway shape changes. + const data = await silentPost("/api/job/getlayers", { job: { id: focus.id } }); + const layers: any[] = Array.isArray(data) + ? data + : (data?.layers?.layers ?? data?.layers ?? []); + if (!Array.isArray(layers) || layers.length === 0) return; + + const focusJobId = `job:${focus.name}`; + for (const layer of layers) { + const layerName = layer?.name; + if (!layerName) continue; + const ep = describeEndpoint(focus.name, layerName, ""); + const existing = nodes.get(ep.id); + if (existing) { + // A depend may have created this layer node already; attach the full + // Layer so its right-click menu works. + (existing.data as GraphNodeData).layer = layer as Layer; + } else { + nodes.set(ep.id, { + id: ep.id, + type: "dep", + position: { x: 0, y: 0 }, + data: { + label: ep.label, + fullName: ep.label, + kind: ep.kind, + jobName: ep.jobName, + isFocus: false, + layer: layer as Layer, + } satisfies GraphNodeData, + } as Node); + } + // Structural "job contains layer" edge so the layer is never an island. + const edgeId = `contains:${focusJobId}__${ep.id}`; + if (!edges.has(edgeId)) { + edges.set(edgeId, { id: edgeId, source: focusJobId, target: ep.id } as Edge); + } + } +} + // Recursively walk the dependency tree starting from `focus`. Follows // both directions (what this job depends on AND what depends on this // job), bounded by maxDepth and a visited-job set to prevent infinite @@ -295,6 +377,10 @@ async function walkDependencyTree( } satisfies GraphNodeData, } as Node); + // Always show the focus job's layers (CueGUI JobMonitorGraph parity) so a + // job with no cross-job dependencies still renders its structure. + await ingestFocusLayers(focus, nodeMap, edgeMap); + const visited = new Set([focus.name]); let frontier: string[] = [focus.name]; @@ -379,12 +465,14 @@ export function JobDependencyGraph({ }; }, [job.id, job.name, maxDepth]); - const handleNodeClick = useCallback( + // Double-click (not single-click) opens the job detail page, mirroring the + // Frames table's double-click-to-open behavior. A single click just selects + // the node so it doesn't navigate away accidentally. + const handleNodeDoubleClick = useCallback( (_event: React.MouseEvent, node: Node) => { const data = node.data as unknown as GraphNodeData; // Prefer parent-supplied navigation; otherwise navigate to the - // tabbed job-detail page so the click actually does something - // useful (the original component fired a misleading toast). + // tabbed job-detail page so the action actually does something useful. if (onNodeNavigate) { onNodeNavigate(data.jobName ?? data.label); return; @@ -395,6 +483,45 @@ export function JobDependencyGraph({ [onNodeNavigate, router], ); + // --- Right-click node menu (CueGUI JobMonitorGraph node menu parity) ----- + const { data: session } = useSession(); + const username = + session?.user?.name ?? session?.user?.email?.split("@")[0] ?? UNKNOWN_USER; + const rfInstanceRef = useRef(null); + const [menu, setMenu] = useState<{ x: number; y: number; data: GraphNodeData } | null>(null); + const closeMenu = useCallback(() => setMenu(null), []); + + const handleNodeContextMenu = useCallback((event: React.MouseEvent, node: Node) => { + event.preventDefault(); + setMenu({ + x: event.clientX, + y: event.clientY, + data: node.data as unknown as GraphNodeData, + }); + }, []); + + // Re-run the dagre layout and fit the view (CueGUI "Auto Layout Nodes"). + const autoLayout = useCallback(() => { + setNodes((nds) => layoutNodes(nds, edges)); + requestAnimationFrame(() => rfInstanceRef.current?.fitView({ duration: 300 })); + }, [edges]); + + // Close the menu on any outside click / Escape. Listeners are attached only + // while the menu is open so the contextmenu event that opened it can't also + // close it. + useEffect(() => { + if (!menu) return; + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") closeMenu(); + }; + window.addEventListener("click", closeMenu); + window.addEventListener("keydown", onKey); + return () => { + window.removeEventListener("click", closeMenu); + window.removeEventListener("keydown", onKey); + }; + }, [menu, closeMenu]); + // Theme-scoped cursor URL. Memoized per theme + per instance so two // graphs on the page don't fight each other and the SVG isn't rebuilt // every render. CSS selector is namespaced via `data-graph-id` so the @@ -414,10 +541,10 @@ export function JobDependencyGraph({ ); } - if (nodes.length <= 1) { + if (nodes.length === 0) { return (
- No dependencies found for this job. + No layers or dependencies found for this job.
); } @@ -435,12 +562,117 @@ export function JobDependencyGraph({ nodeTypes={nodeTypes} fitView colorMode={resolvedTheme === "dark" ? "dark" : "light"} - onNodeClick={handleNodeClick} + onNodeDoubleClick={handleNodeDoubleClick} + onNodeContextMenu={handleNodeContextMenu} + onPaneClick={closeMenu} + onInit={(inst) => { + rfInstanceRef.current = inst; + }} proOptions={{ hideAttribution: true }} > + + {menu && ( + + )} + + ); +} + +// Cursor-positioned right-click menu for a graph node. For LAYER nodes that +// carry a Layer object it runs the exact same actions as the Layers table +// (CueGUI JobMonitorGraph node menu parity); every node also offers +// "Auto Layout Nodes". +function NodeContextMenu({ + x, + y, + data, + username, + onAutoLayout, + onClose, +}: { + x: number; + y: number; + data: GraphNodeData; + username: string; + onAutoLayout: () => void; + onClose: () => void; +}) { + const layer = data.layer; + // The action helpers only read `row.original`, so a shim row is enough. + const row = layer ? ({ original: layer } as any) : null; + + function run(fn: () => void) { + fn(); + onClose(); + } + + const Item = ({ + label, + icon, + onClick, + danger, + }: { + label: string; + icon: React.ReactNode; + onClick: () => void; + danger?: boolean; + }) => ( + + ); + const Sep = () =>
; + + return ( +
e.stopPropagation()} + onContextMenu={(e) => e.preventDefault()} + > + } + onClick={onAutoLayout} + /> + {row && ( + <> + +
+ Dependencies +
+ } onClick={() => viewLayerDependenciesGivenRow(row)} /> + } onClick={() => layerDependencyWizardGivenRow(row)} /> + } onClick={() => markdoneLayerGivenRow(row)} /> + + } onClick={() => reorderLayerFramesGivenRow(row)} /> + } onClick={() => staggerLayerFramesGivenRow(row)} /> + + } onClick={() => layerPropertiesGivenRow(row)} /> + + } danger onClick={() => killLayerGivenRow(row, username)} /> + } onClick={() => eatLayerFramesGivenRow(row)} /> + } onClick={() => retryLayerFramesGivenRow(row)} /> + } onClick={() => retryLayerDeadFramesGivenRow(row)} /> + + )}
); } diff --git a/docs/_docs/concepts/cueweb-rest-gateway.md b/docs/_docs/concepts/cueweb-rest-gateway.md index 8debf0132..4b272d404 100644 --- a/docs/_docs/concepts/cueweb-rest-gateway.md +++ b/docs/_docs/concepts/cueweb-rest-gateway.md @@ -39,6 +39,7 @@ CueWeb is a web-based application that brings the core functionality of CueGUI t - **Breadcrumb Navigation**: detail views (frame log page, per-job comments page) render a "Home > Jobs > ..." trail above the content. Long labels truncate with an ellipsis; the full text is recoverable on hover. - **Job / Layer / Frame tables (CueGUI parity)**: Full CueGUI column sets (Comments / Launched / Eligible / Finished / User Color on Jobs; Eligible on Layers; LLU / Memory (RSS) / Memory (PSS) / Remain / Eligible Time / Submission Time / Last Line on Frames). The Jobs table's dedicated **Comments** column shows a sortable sticky-note icon next to Name, so jobs with comments can be pulled to the top in one click. Per-table substring filter, hide / show + `← / →` reorder + **Reset to Default** in each table's Columns dropdown. Both visibility and ordering persist in `localStorage`. - **Inline Layers + Frames panel**: Clicking a job row reveals the associated Layers and Frames tables stacked below the Jobs grid; clicking a layer narrows the frames panel to that layer and pushes the layer attributes into the docked Attributes panel; double-clicking a frame opens the log viewer. +- **Job Dependency Graph** (Cuetopia ▸ View Job Graph): a read-only, interactive node graph mirroring CueGUI's `JobMonitorGraph`. Shows the focus job with its layers (so a job with no cross-job dependencies still renders its structure) plus the cross-job dependency tree; double-click a node to open its detail page, right-click a layer node for the same actions as the Layers table. - **CueGUI-parity context menus**: right-clicking any row in the Jobs, Layers, or Frames tables opens a menu that mirrors the CueGUI Monitor Jobs / Monitor Job Details menus. Touch devices get the same menu via a `⋮` Actions button as the leftmost cell of each row. Includes **View Job Details** (opens the tabbed `/jobs/` page with Overview / Layers / Frames / Comments / Dependencies), **Copy Job Name** / **Copy Layer Name** / **Copy Frame Name** / **Copy Log Path** (works on plain-HTTP LAN deployments too), plus **View Log** / **Tail Log** (in-browser viewer) and an optional **View Log on \** that launches the log file directly in a desktop editor (configured at build time via `NEXT_PUBLIC_LOG_EDITOR_URL`, defaults to VSCode in the sandbox). - **Animated progress bar (Jobs AND Layers)**: shared stacked-segment renderer with a hover tooltip showing per-state counts and percentages. - **Real-time Updates**: Automatic refresh of job, layer, and frame status diff --git a/docs/_docs/developer-guide/cueweb-development.md b/docs/_docs/developer-guide/cueweb-development.md index 48ebc1877..5649bb5d4 100644 --- a/docs/_docs/developer-guide/cueweb-development.md +++ b/docs/_docs/developer-guide/cueweb-development.md @@ -188,7 +188,7 @@ loads at runtime are copies under `cueweb/public/`. - **`CueWebIcon`** (`components/ui/cuewebicon.tsx`): OpenCue icon + **CueWeb** wordmark, sized off a single `height` prop. Used by the login page, LDAP login page, frame log page, and comments page. Reads the brand assets from `cueweb/public/opencue-icon-{black,white}.png`. - **`JobsTable`** (`app/jobs/data-table.tsx`): Main jobs dashboard table (no longer renders its own inline header - the global `AppHeader` owns that chrome). Each `TableRow` left-click dispatches `setAttributeSelection(...)` so the Attributes panel updates as the user inspects rows and also surfaces the inline Layers + Frames panel below the grid via `JobDetailsInline`. Destructive toolbar actions (Eat / Retry / Pause / Unpause / Kill) consume `useDisableJobInteraction()` and dim themselves when the safety flag is on. Wires TanStack's `columnVisibility`, `columnOrder`, and `globalFilter` state into the reducer State so each is persisted to `localStorage` (`columnVisibility`, `columnOrder`); the per-table substring filter is purely component-state. - **`JobDetailsInline`** (`components/ui/job-details-inline.tsx`): Inline Layers + Frames panel rendered below the Jobs table when a row is selected. Polls layers and frames every 5s with cancellation guards. Layer-row clicks toggle a frames-table filter to that layer and push the layer's attributes into the docked Attributes panel. When `useShowDependencyGraph()` is on, it also mounts `JobDependencyGraph` as a third stacked panel (`id="job-dependency-graph-panel"`) below Frames, with a header naming the focus job plus show/hide and close controls. -- **`JobDependencyGraph`** (`components/ui/job-dependency-graph.tsx`): Read-only, interactive node graph of a job's dependency tree, built with React Flow (`@xyflow/react`) + dagre. Mirrors CueGUI's `JobMonitorGraph`. A breadth-first walk from the focus job follows both `GetDepends` (downstream) and `GetWhatDependsOnThis` (upstream, active-only), bounded by `maxDepth` (default 4) and a visited-set to break cycles. Each hop resolves a job name to its UUID via `/api/job/getjobs` anchored-regex (Cuebot rejects name-only depend lookups), memoized in a `Map` so the whole walk costs ~one lookup per distinct job. All BFS fetches go through a `silentPost` helper that bypasses `accessGetApi`, so jobs in other shows / unmonitored + pruned don't cascade into red toasts. The custom `DependencyNode` renderer truncates long names (full name in a `title` tooltip), color-codes the left border by kind (JOB/LAYER/FRAME), rings the focus job, and shows hierarchical labels for layer/frame nodes. dagre lays out fresh per call (no module-level singleton); the data fetch is keyed on `job.id` so flipping the theme doesn't re-walk the tree, and the crosshair-cursor SVG is scoped per instance via a `data-graph-id` attribute. Clicking a node calls `onNodeNavigate(jobName)` if supplied, else `router.push("/jobs/?tab=overview")`. +- **`JobDependencyGraph`** (`components/ui/job-dependency-graph.tsx`): Read-only, interactive node graph of a job's dependency tree, built with React Flow (`@xyflow/react`) + dagre. Mirrors CueGUI's `JobMonitorGraph`. A breadth-first walk from the focus job follows both `GetDepends` (downstream) and `GetWhatDependsOnThis` (upstream, active-only), bounded by `maxDepth` (default 4) and a visited-set to break cycles. Each hop resolves a job name to its UUID via `/api/job/getjobs` anchored-regex (Cuebot rejects name-only depend lookups), memoized in a `Map` so the whole walk costs ~one lookup per distinct job. All BFS fetches go through a `silentPost` helper that bypasses `accessGetApi`, so jobs in other shows / unmonitored + pruned don't cascade into red toasts. The custom `DependencyNode` renderer truncates long names (full name in a `title` tooltip), color-codes the left border by kind (JOB/LAYER/FRAME), rings the focus job, and shows hierarchical labels for layer/frame nodes. dagre lays out fresh per call (no module-level singleton); the data fetch is keyed on `job.id` so flipping the theme doesn't re-walk the tree, and the crosshair-cursor SVG is scoped per instance via a `data-graph-id` attribute. It also fetches the focus job's layers (`ingestFocusLayers`) so a job with no cross-job depends still shows its layers. **Double-clicking** a node navigates (`onNodeNavigate(jobName)` or `router.push("/jobs/?tab=overview")`); **right-clicking a layer node** opens a menu reusing the Layers-table actions (Auto Layout Nodes, View Dependencies / Dependency Wizard / Mark done, Reorder / Stagger, Properties, Kill / Eat / Retry / Retry Dead Frames). - **`JobDetailsPage`** (`app/jobs/[job-name]/page.tsx`): Standalone tabbed job-details route reached via the **View Job Details** right-click entry (or the row's `⋮` Actions button). Resolves the job by name through `findJobByName(...)`, polls layers + frames every 5s with cancellation guards, and exposes five tabs - **Overview**, **Layers**, **Frames**, **Comments**, **Dependencies**. The active tab is mirrored to the URL as `?tab=` and read back through `useSearchParams()` + `router.replace(...)` so the page is bookmarkable and browser back/forward walks between tabs. `isTabKey(value)` rejects unknown query values so the URL can never select a missing tab. The Comments tab embeds a read-only preview of `getJobComments(...)` with a link out to the full `/jobs//comments` editor; Dependencies is currently a placeholder. The standard `Breadcrumbs` + `EmptyState` (`FileX` icon, "Job not found") wrappers cover loading and missing-job paths. - **`SimpleDataTable`** (`components/ui/simple-data-table.tsx`): Shared TanStack-table wrapper used by Layers, Frames, the Monitor Hosts table, the host detail page's procs table, the Shows table, the Allocations table, and the Limits table (plus the standalone log-viewer / per-job detail page). Owns the per-table substring filter (`globalFilter` + `getFilteredRowModel`), column-visibility persistence (`columnVisibilityStorageKey`), and column-order persistence (a parallel `cueweb..columnOrder` key derived from the visibility key). Renders the Columns dropdown that holds the `←` / `→` reorder buttons and the **Reset to Default** action. The mutually-exclusive `isFramesTable` / `isFramesLogTable` / `isHostsTable` / `isProcsTable` / `isShowsTable` / `isAllocationsTable` / `isLimitsTable` flags select per-table filter/empty-state copy and which row context menu renders (`isHostsTable` → `HostContextMenu`; `isShowsTable` → `ShowContextMenu`; `isLimitsTable` → `LimitContextMenu`; frames → `FrameContextMenu`; `isProcsTable` / `isAllocationsTable` → none, read-only; otherwise `LayerContextMenu`). - **`JobProgressBar` / `LayerProgressBar`** (`components/ui/{job,layer}-progress-bar.tsx`): Stacked progress bars with a hover tooltip showing per-state counts and percentages. Both delegate to the shared `` renderer in `components/ui/progressbar.tsx`. Segment colors and ordering come from `app/utils/{job,layer}_progress_utils.ts`. @@ -932,9 +932,27 @@ counterpart to the Group-By Dependent tree - it mirrors CueGUI's - **Decoupled effects.** The data fetch is keyed on `[job.id, job.name, maxDepth]` so flipping the theme doesn't re-walk the tree; the crosshair-cursor SVG is memoized on `resolvedTheme` and scoped to the - instance via a `data-graph-id` attribute. Node clicks call - `onNodeNavigate(jobName)` when supplied, else - `router.push("/jobs/?tab=overview")`. + instance via a `data-graph-id` attribute. Node **double**-clicks + (`onNodeDoubleClick`) call `onNodeNavigate(jobName)` when supplied, else + `router.push("/jobs/?tab=overview")`; a single click only selects. +- **Focus-job layers.** `ingestFocusLayers(focus, nodes, edges)` fetches the + focus job's layers (`/api/job/getlayers`) and adds a LAYER node per layer + wired to the job node with a "contains" edge, so a job with no cross-job + dependencies still renders its structure (CueGUI's `JobMonitorGraph` is a + layer graph). Layers already created by a depend reuse the same node id; the + full `Layer` object is stored in the node's `data.layer`. The empty state + now only shows when there are zero nodes. +- **Node context menu.** `onNodeContextMenu` opens a cursor-positioned + `NodeContextMenu` for layer nodes that reuses the Layers-table action + handlers (`viewLayerDependenciesGivenRow`, `layerDependencyWizardGivenRow`, + `markdoneLayerGivenRow`, `reorderLayerFramesGivenRow`, + `staggerLayerFramesGivenRow`, `layerPropertiesGivenRow`, `killLayerGivenRow`, + `eatLayerFramesGivenRow`, `retryLayerFramesGivenRow`, + `retryLayerDeadFramesGivenRow`) via a `{ original: layer }` shim - those + helpers only read `row.original`. Plus an **Auto Layout Nodes** item that + re-runs `layoutNodes` and `fitView`. The layer dialogs + `DependencyWizardDialog` + are already mounted by the host (`data-table.tsx` for the inline panel, the + job page for `/jobs/[name]`), so the dispatched events resolve in both. --- diff --git a/docs/_docs/other-guides/cueweb.md b/docs/_docs/other-guides/cueweb.md index 2f549ef0e..021df88fc 100644 --- a/docs/_docs/other-guides/cueweb.md +++ b/docs/_docs/other-guides/cueweb.md @@ -154,7 +154,8 @@ CueWeb replicates the core functionality of [CueGUI](https://www.opencue.io/docs 27. **Job dependency graph (Cuetopia → View Job Graph):** - A read-only, interactive node graph of a job's dependency tree, mirroring CueGUI's Monitor-Jobs dependency-graph dock. - Toggled from the checkable **Cuetopia → View Job Graph** entry (header dropdown and sidebar); the choice is persisted and synced across tabs. - - When on, selecting a job in Monitor Jobs mounts the graph as a third panel under the inline Layers and Frames panels. It walks the depends in both directions (what the job depends on and what depends on the job), color-codes nodes by kind (JOB / LAYER / FRAME), rings the focus job, truncates long names with a full-name tooltip, and lets you click a node to open that job's detail page. Pan / zoom / fit controls and a "No dependencies found" empty state are included. + - When on, selecting a job in Monitor Jobs mounts the graph as a third panel under the inline Layers and Frames panels. It shows the focus job with its **layers** (so a job with no cross-job dependencies still renders its structure) and walks cross-job depends in both directions, color-codes nodes by kind (JOB / LAYER / FRAME), rings the focus job, and truncates long names with a full-name tooltip. Pan / zoom / fit controls are included. + - **Double-click** a node to open that job's detail page (a single click only selects it). **Right-click a layer node** for the CueGUI Job-Graph layer menu: **Auto Layout Nodes**; **Dependencies** (View Dependencies… / Dependency Wizard… / Mark done); **Reorder Frames…**; **Stagger Frames…**; **Properties…**; **Kill / Eat / Retry / Retry Dead Frames** - the same actions as the Layers table. 28. **Monitor Cue (CueCommander → Monitor Cue):** - A show-grouped job tree at `/monitor-cue`, the CueWeb equivalent of CueGUI's CueCommander Monitor Cue window (previously a dead sidebar link). Pick one or more shows from the **Shows** menu (All Shows / Clear / per-show, persisted) to load every job for those shows, grouped under their show and groups. @@ -628,7 +629,7 @@ After **Apply**, a toast confirms the new value and the **Priority** column in t **Figure 69: Toast confirming the priority change and immediate column update** ![Toast confirming the priority change](/assets/images/cueweb/cueweb_cuetopia_monitor_jobs_set_priority_confirmation.png) -The checkable **Cuetopia → View Job Graph** entry (Figure 70) toggles a read-only dependency-graph panel. With it on, selecting a job in Monitor Jobs mounts an interactive node graph of the job's dependency tree as a third panel under the inline Layers and Frames panels (Figure 71). The graph walks the depends in both directions, color-codes nodes by kind (JOB / LAYER / FRAME), rings the focus job, and lets you click a node to open that job's detail page (Figure 72). +The checkable **Cuetopia → View Job Graph** entry (Figure 70) toggles a read-only dependency-graph panel. With it on, selecting a job in Monitor Jobs mounts an interactive node graph as a third panel under the inline Layers and Frames panels (Figure 71). The graph shows the focus job with its **layers** (so a job with no cross-job dependencies still renders its structure) and walks cross-job depends in both directions, color-codes nodes by kind (JOB / LAYER / FRAME), and rings the focus job (Figure 72). **Double-click** a node to open that job's detail page; **right-click a layer node** for the CueGUI Job-Graph layer menu (Auto Layout Nodes; Dependencies: View Dependencies… / Dependency Wizard… / Mark done; Reorder Frames…; Stagger Frames…; Properties…; Kill / Eat / Retry / Retry Dead Frames), shown in Figure 73. **Figure 70: View Job Graph entry in the Cuetopia menu** ![View Job Graph entry in the Cuetopia menu](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_menu.png) @@ -636,8 +637,11 @@ The checkable **Cuetopia → View Job Graph** entry (Figure 70) toggles a rea **Figure 71: Dependency graph panel below the inline Layers and Frames panels** ![Dependency graph panel below Layers and Frames](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_monitor_jobs_dependency_graph.png) -**Figure 72: The dependency graph panel on its own** -![The dependency graph panel on its own](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_monitor_jobs_dependency_graph_only.png) +**Figure 72: The dependency graph panel showing the focus job and its layer** +![The Job Dependency Graph showing the focus job and its layer](/assets/images/cueweb/cueweb_dependency_graph.png) + +**Figure 73: Right-click layer-node menu in the Job Dependency Graph** +![Right-click layer-node menu in the Job Dependency Graph](/assets/images/cueweb/cueweb_dependency_graph_menu_options.png) ## Conclusion diff --git a/docs/_docs/quick-starts/quick-start-cueweb.md b/docs/_docs/quick-starts/quick-start-cueweb.md index 114c5a125..a93e05605 100644 --- a/docs/_docs/quick-starts/quick-start-cueweb.md +++ b/docs/_docs/quick-starts/quick-start-cueweb.md @@ -257,7 +257,7 @@ Click a job row to reveal the inline Layers and Frames panels below the jobs tab - **Kill Jobs**: Use the stop button to terminate jobs - **Job Details (inline)**: Click on a job row to reveal the inline Layers + Frames panel below the Jobs table. - **Job Details (tabbed page)**: Right-click a job and choose **View Job Details** to open the tabbed `/jobs/` page with Overview / Layers / Frames / Comments / Dependencies tabs. The active tab is stored in the URL so the page is bookmarkable. -- **Job Dependency Graph**: Toggle **Cuetopia → View Job Graph**, then click a job to mount a read-only, interactive node graph of its dependency tree below the inline Layers + Frames panels. Nodes are color-coded by kind (JOB / LAYER / FRAME), the focus job is ringed, and clicking a node opens that job's detail page. +- **Job Dependency Graph**: Toggle **Cuetopia → View Job Graph**, then click a job to mount a read-only, interactive node graph below the inline Layers + Frames panels. It shows the focus job with its **layers** (so even a job with no cross-job dependencies renders its structure) plus any cross-job depends, color-coded by kind (JOB / LAYER / FRAME) with the focus job ringed. **Double-click** a node to open that job's detail page; **right-click a layer node** for the Layers-table actions (Auto Layout Nodes, View/Wizard dependencies, Mark done, Reorder/Stagger, Properties, Kill/Eat/Retry/Retry Dead). ![Dependency graph panel below Layers and Frames](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_monitor_jobs_dependency_graph_only.png) - **Job Comments**: Right-click a job and choose **Comments**, or click the sticky-note icon in the Jobs table's **Comments** column (sortable, sits right after Name), to open the Comments page where you can list / add / edit / delete comments and manage predefined-comment macros. diff --git a/docs/_docs/reference/cueweb.md b/docs/_docs/reference/cueweb.md index e096d01fa..cb02c0c74 100644 --- a/docs/_docs/reference/cueweb.md +++ b/docs/_docs/reference/cueweb.md @@ -382,20 +382,22 @@ A read-only, interactive node graph of a job's dependency tree, rendered with [R ![Dependency graph panel below the inline Layers and Frames panels](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_monitor_jobs_dependency_graph.png) -![Dependency graph panel below the inline Layers and Frames panels (dark mode)](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_monitor_jobs_dependency_graph_dark.png) - | Behavior | Description | |----------|-------------| | **Tree walk** | Breadth-first search from the focus job over both directions - `GetDepends` (downstream) and `GetWhatDependsOnThis` (upstream, active depends only) - bounded by `maxDepth` (default 4) and a visited-job set to break cycles. Mirrors CueGUI's `JobMonitorGraph.getRecursiveDependentJobs`. | | **Name resolution** | Each BFS hop first resolves a job name to its UUID via `/api/job/getjobs` with an anchored `^name$` regex (Cuebot rejects name-only depend lookups). Resolved IDs are memoized in a `Map`, so a 12-job chain costs ~12 lookups across the whole walk, not 12 per hop. | | **Silent fetches** | All BFS requests go through a `silentPost` helper that bypasses `accessGetApi`. Partial failures (jobs in other shows, unmonitored/finished + pruned) return `null` instead of cascading red "Resource not found" toasts. | +| **Focus-job layers** | `ingestFocusLayers()` fetches the focus job's layers (`/api/job/getlayers`) and adds a **LAYER** node per layer wired to the job node, so a job with no cross-job dependencies still renders its layers (CueGUI `JobMonitorGraph` is a layer graph). Layers that also appear via a depend reuse the same node id, so nothing is duplicated. | | **Nodes** | Custom `DependencyNode` renderer: monospace, truncated label with the full name in a `title` tooltip, a kind label and color-coded left border (JOB = blue, LAYER = amber, FRAME = emerald), and a stronger ring on the focus job. Layer / frame nodes carry a hierarchical label so their parent job/layer is visible. | -| **Edges** | Directed upstream → downstream (top-to-bottom); animated when the depend is active. | -| **Navigation** | Clicking a node calls `onNodeNavigate(jobName)` if supplied, else `router.push("/jobs/?tab=overview")`. | +| **Edges** | Directed upstream → downstream (top-to-bottom); animated when the depend is active. Layer nodes also get a structural "contains" edge from the job node. | +| **Navigation** | **Double-clicking** a node (`onNodeDoubleClick`) calls `onNodeNavigate(jobName)` if supplied, else `router.push("/jobs/?tab=overview")`. A single click only selects the node. | +| **Node menu** | Right-clicking a layer node (`onNodeContextMenu`) opens a cursor-positioned menu reusing the Layers-table actions via a `{ original: layer }` shim: **Auto Layout Nodes** (re-layout + `fitView`), **Dependencies** (View Dependencies… / Dependency Wizard… / Mark done), **Reorder Frames…**, **Stagger Frames…**, **Properties…**, **Kill**, **Eat**, **Retry**, **Retry Dead Frames**. The same layer dialogs + Dependency Wizard are already mounted by the host page (`data-table.tsx` / the job page), so the events resolve in both contexts. | | **Theme-aware** | dagre lays out fresh per call (no module-level singleton); the data fetch is keyed on `job.id` so toggling dark/light does not re-walk the tree. The crosshair-cursor SVG is scoped per instance via a `data-graph-id` attribute so two graphs on a page do not collide. | -| **Empty / loading states** | `Loading dependency graph...` while walking; `No dependencies found for this job.` when only the focus node remains. | +| **Empty / loading states** | `Loading dependency graph...` while walking; `No layers or dependencies found for this job.` only when there are zero nodes. | + +![The Job Dependency Graph: focus job + its layer](/assets/images/cueweb/cueweb_dependency_graph.png) -![The dependency graph panel on its own](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_monitor_jobs_dependency_graph_only.png) +![Right-click layer-node menu in the Job Dependency Graph](/assets/images/cueweb/cueweb_dependency_graph_menu_options.png) ### Monitor Cue diff --git a/docs/_docs/tutorials/cueweb-tutorial.md b/docs/_docs/tutorials/cueweb-tutorial.md index 4611ae198..177d25b82 100644 --- a/docs/_docs/tutorials/cueweb-tutorial.md +++ b/docs/_docs/tutorials/cueweb-tutorial.md @@ -267,9 +267,13 @@ When you want to *see* a render chain rather than read a table of depends, turn ![Dependency graph panel below the inline Layers and Frames panels](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_monitor_jobs_dependency_graph.png) -3. **Read and navigate the graph.** Each node carries a kind label (JOB / LAYER / FRAME) and a color-coded left border; the job you opened the panel for is ringed. Hover a node to see its full name, and click a node to open that job's detail page. Use the corner controls to pan, zoom, and fit. Collapse or close the panel from the **Dependency Graph** button above Layers or the panel's **×** button. A job with no depends shows **No dependencies found for this job.** +3. **Read the graph.** The focus job is shown with its **layers**, so even a job with no cross-job dependencies still renders its structure. Each node carries a kind label (JOB / LAYER / FRAME) and a color-coded left border; the job you opened the panel for is ringed. Hover a node to see its full name. Use the corner controls to pan, zoom, and fit. Collapse or close the panel from the **Dependency Graph** button above Layers or the panel's **×** button. - ![The dependency graph panel on its own](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_monitor_jobs_dependency_graph_only.png) + ![The Job Dependency Graph showing the focus job and its layer](/assets/images/cueweb/cueweb_dependency_graph.png) + +4. **Navigate and act.** **Double-click** a node to open that job's detail page (a single click only selects it). **Right-click a layer node** for the same actions as the Layers table - **Auto Layout Nodes**; **Dependencies** (View Dependencies… / Dependency Wizard… / Mark done); **Reorder Frames…**; **Stagger Frames…**; **Properties…**; **Kill / Eat / Retry / Retry Dead Frames**. + + ![Right-click layer-node menu in the Job Dependency Graph](/assets/images/cueweb/cueweb_dependency_graph_menu_options.png) --- diff --git a/docs/_docs/user-guides/cueweb-user-guide.md b/docs/_docs/user-guides/cueweb-user-guide.md index 49b1989f3..09fb2b156 100644 --- a/docs/_docs/user-guides/cueweb-user-guide.md +++ b/docs/_docs/user-guides/cueweb-user-guide.md @@ -727,8 +727,6 @@ The **Job Dependency Graph** is a read-only, interactive node graph of a job's d ![View Job Graph entry in the Cuetopia menu](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_menu.png) -![View Job Graph entry in the Cuetopia menu (dark mode)](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_menu_dark.png) - **Where it appears.** With the toggle on, click any job row in **Monitor Jobs**. The graph mounts as a third panel stacked under the inline **Layers** and **Frames** panels. The panel header names the focus job and has a close (**×**) button; you can also collapse or expand it from the **Dependency Graph** button above the Layers panel. ![Monitor Jobs with the dependency graph panel open below Layers and Frames](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_monitor_jobs_dependency_graph.png) @@ -736,19 +734,27 @@ The **Job Dependency Graph** is a read-only, interactive node graph of a job's d **Reading the graph.** - Each box is a node. A small kind label (**JOB**, **LAYER**, or **FRAME**) and a color-coded left border tell you what the node represents; layer and frame nodes also show their parent job below the name. +- The focus job is shown with its **layers** hanging off it (CueGUI Job-Graph parity), so a job that has no cross-job dependencies still shows its structure rather than an empty panel. - The job you opened the panel for - the *focus* node - is highlighted with a ring. - Long names are truncated; hover any node to see its full name in a tooltip. -- Edges flow from upstream (top) to the jobs that wait on them (bottom). -- **Click a node** to open that job's tabbed detail page. +- Edges flow from upstream (top) to the jobs/layers that wait on them (bottom). - Use the zoom / fit / lock controls in the corner to pan and zoom; the view fits the whole tree on first render. -![The dependency graph panel on its own](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_monitor_jobs_dependency_graph_only.png) +![The Job Dependency Graph showing the focus job and its layer](/assets/images/cueweb/cueweb_dependency_graph.png) + +**Open a job.** **Double-click** a node to open that job's tabbed detail page. A single click just selects the node, so you won't navigate away by accident. + +**Layer actions (right-click a layer node).** Right-clicking a layer node opens the same action menu as the Layers table (CueGUI Job-Graph node menu parity): -The graph is theme-aware: it follows the light/dark toggle without re-fetching the dependency tree. +- **Auto Layout Nodes** - re-runs the automatic layout and refits the view. +- **Dependencies** - **View Dependencies…**, **Dependency Wizard…**, **Mark done**. +- **Reorder Frames…**, **Stagger Frames…**. +- **Properties…** - edit the layer's cores / memory / GPU memory / threadable / tags. +- **Kill**, **Eat**, **Retry**, **Retry Dead Frames**. -![The dependency graph panel in dark mode](/assets/images/cueweb/cueweb_cuetopia_view_job_graph_monitor_jobs_dependency_graph_only_dark.png) +![Right-click menu on a layer node in the Job Dependency Graph](/assets/images/cueweb/cueweb_dependency_graph_menu_options.png) -If the selected job has no dependencies, the panel shows **No dependencies found for this job.** The graph is read-only - to create or remove depends, use the [dependency context-menu actions](#managing-job-dependencies). +The graph is theme-aware: it follows the light/dark toggle without re-fetching the dependency tree. To create or remove cross-job depends, use the [dependency context-menu actions](#managing-job-dependencies). ### Layer Operations diff --git a/docs/assets/images/cueweb/cueweb_dependency_graph.png b/docs/assets/images/cueweb/cueweb_dependency_graph.png new file mode 100644 index 000000000..3ad614de8 Binary files /dev/null and b/docs/assets/images/cueweb/cueweb_dependency_graph.png differ diff --git a/docs/assets/images/cueweb/cueweb_dependency_graph_dark.png b/docs/assets/images/cueweb/cueweb_dependency_graph_dark.png new file mode 100644 index 000000000..1294a5c03 Binary files /dev/null and b/docs/assets/images/cueweb/cueweb_dependency_graph_dark.png differ diff --git a/docs/assets/images/cueweb/cueweb_dependency_graph_menu_options.png b/docs/assets/images/cueweb/cueweb_dependency_graph_menu_options.png new file mode 100644 index 000000000..21d67c11e Binary files /dev/null and b/docs/assets/images/cueweb/cueweb_dependency_graph_menu_options.png differ diff --git a/docs/assets/images/cueweb/cueweb_dependency_graph_menu_options_dark.png b/docs/assets/images/cueweb/cueweb_dependency_graph_menu_options_dark.png new file mode 100644 index 000000000..04b27b1a1 Binary files /dev/null and b/docs/assets/images/cueweb/cueweb_dependency_graph_menu_options_dark.png differ