d9414e4cba
Full DAG engine app with CLI subcommands (run, list, status, validate, server) and React/Mantine web frontend. Uses net/http + embedded Vite build. SQLite store for run history. Scheduler with cron_ticker for automated execution. Compatible with existing dagu YAML format. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
86 lines
2.4 KiB
TypeScript
86 lines
2.4 KiB
TypeScript
import { Timeline, Text, Code, Collapse, Box, Group } from "@mantine/core";
|
|
import {
|
|
IconCircleCheck,
|
|
IconCircleX,
|
|
IconLoader,
|
|
IconCircleMinus,
|
|
IconClock,
|
|
} from "@tabler/icons-react";
|
|
import { useDisclosure } from "@mantine/hooks";
|
|
import type { DagStepResult } from "../types";
|
|
|
|
const iconMap: Record<string, React.ReactNode> = {
|
|
success: <IconCircleCheck size={16} color="var(--mantine-color-green-6)" />,
|
|
failed: <IconCircleX size={16} color="var(--mantine-color-red-6)" />,
|
|
running: <IconLoader size={16} color="var(--mantine-color-blue-6)" />,
|
|
skipped: <IconCircleMinus size={16} color="var(--mantine-color-dimmed)" />,
|
|
pending: <IconClock size={16} color="var(--mantine-color-gray-6)" />,
|
|
};
|
|
|
|
function StepItem({ step }: { step: DagStepResult }) {
|
|
const [opened, { toggle }] = useDisclosure(step.Status === "failed");
|
|
const hasOutput = step.Stdout || step.Stderr;
|
|
|
|
return (
|
|
<Timeline.Item
|
|
bullet={iconMap[step.Status] || iconMap.pending}
|
|
title={
|
|
<Group gap="xs">
|
|
<Text
|
|
size="sm"
|
|
fw={500}
|
|
onClick={hasOutput ? toggle : undefined}
|
|
style={hasOutput ? { cursor: "pointer" } : undefined}
|
|
>
|
|
{step.StepName}
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
{step.DurationMs}ms
|
|
</Text>
|
|
{step.ExitCode !== 0 && step.ExitCode !== -1 && (
|
|
<Text size="xs" c="red">
|
|
exit {step.ExitCode}
|
|
</Text>
|
|
)}
|
|
</Group>
|
|
}
|
|
>
|
|
{hasOutput && (
|
|
<Collapse in={opened}>
|
|
<Box mt="xs">
|
|
{step.Stdout && (
|
|
<Code block mb="xs" style={{ maxHeight: 200, overflow: "auto" }}>
|
|
{step.Stdout}
|
|
</Code>
|
|
)}
|
|
{step.Stderr && (
|
|
<Code
|
|
block
|
|
color="red"
|
|
style={{ maxHeight: 200, overflow: "auto" }}
|
|
>
|
|
{step.Stderr}
|
|
</Code>
|
|
)}
|
|
</Box>
|
|
</Collapse>
|
|
)}
|
|
</Timeline.Item>
|
|
);
|
|
}
|
|
|
|
export function StepTimeline({ steps }: { steps: DagStepResult[] }) {
|
|
const activeIndex = steps.findIndex((s) => s.Status === "running");
|
|
|
|
return (
|
|
<Timeline
|
|
active={activeIndex >= 0 ? activeIndex : steps.length - 1}
|
|
bulletSize={24}
|
|
>
|
|
{steps.map((step) => (
|
|
<StepItem key={step.ID} step={step} />
|
|
))}
|
|
</Timeline>
|
|
);
|
|
}
|