import { useEffect, useMemo, useState } from 'react'; import { Link, useSearchParams } from 'react-router'; import { Background, Controls, ReactFlow } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import { Button, Card, Col, Container, Row, Spinner, Table } from 'react-bootstrap'; import PageBreadcrumb from '@/components/PageBreadcrumb'; import { isSuccess, signedGet, signedPost } from '@/api/client'; import { getSession } from '@/auth/storage'; import { toFlow } from '@/views/workflow/graph'; export default function WorkflowMonitor() { const [params] = useSearchParams(); const initialId = params.get('id') || ''; const definitionId = params.get('definition_id') || ''; const [items, setItems] = useState([]); const [selectedId, setSelectedId] = useState(initialId); const [instance, setInstance] = useState(null); const [definition, setDefinition] = useState(null); const [steps, setSteps] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [comment, setComment] = useState(''); const loadList = async () => { const query = definitionId ? { definition_id: definitionId } : {}; const response = await signedGet('workflow', 'workflow_instance/get_all', query); if (!isSuccess(response)) { setError(response?.message || 'Unable to load runs'); return []; } const rows = response.data?.items || []; setItems(rows); return rows; }; const loadRun = async (id) => { if (!id) { setInstance(null); setDefinition(null); setSteps([]); return; } const [one, log] = await Promise.all([ signedGet('workflow', 'workflow_instance/get_one', { id }), signedGet('workflow', 'workflow_step/list', { instance_id: id }), ]); if (!isSuccess(one)) { setError(one?.message || 'Unable to load run'); return; } const row = one.data.item; setInstance(row); setSteps(isSuccess(log) ? log.data?.items || [] : []); const versionId = `${row.definition_id}#v${row.definition_version}`; const graph = await signedGet('workflow', 'workflow_definition/get_one', { id: versionId }); setDefinition(isSuccess(graph) ? graph.data.item : null); }; useEffect(() => { const boot = async () => { setLoading(true); const rows = await loadList(); const next = initialId || rows[0]?.id || ''; setSelectedId(next); await loadRun(next); setLoading(false); }; boot(); }, [definitionId, initialId]); const flow = useMemo(() => { if (!definition) return { nodes: [], edges: [] }; const mapped = toFlow(definition); return { ...mapped, nodes: mapped.nodes.map((node) => ({ ...node, style: node.id === instance?.current_node_id ? { border: '2px solid #0d6efd', borderRadius: 8 } : undefined, })), }; }, [definition, instance]); const session = getSession() || {}; const user = session.user || {}; const canAct = (instance?.waiting_for || []).some( (token) => token === user.id || (session.roles || user.roles || []).includes(token) ); const signal = async (action) => { setError(''); const response = await signedPost('workflow', 'workflow_instance/signal', { instance_id: instance.id, action, comment, }); if (!isSuccess(response)) { setError(response?.message || 'Unable to update the run'); return; } await loadList(); await loadRun(instance.id); }; if (loading) { return ( Loading runs ); } return ( {error ?
{error}
: null} Runs {items.map((row) => ( { setSelectedId(row.id); loadRun(row.id); }} role="button"> ))} {!items.length ? ( ) : null}
Definition Status Waiting
{row.definition_name || row.definition_id} {row.status} {(row.waiting_for || []).join(', ')}
No runs yet.
{instance ? ( <>
{instance.definition_name} v{instance.definition_version} · {instance.status} {instance.outcome ? ` · ${instance.outcome}` : ''}
{canAct && instance.status === 'waiting' ? (
setComment(event.target.value)} />
) : null} Step log {steps.map((step) => ( ))}
# Event Node Actor When
{step.seq} {step.event} {step.node_id} {step.actor} {step.at}
) : (

Select a run to see its graph and log.

)}
); }