diff --git a/apps/arc-web/app/components/collapsible-file.tsx b/apps/arc-web/app/components/collapsible-file.tsx
new file mode 100644
index 000000000..d3b293591
--- /dev/null
+++ b/apps/arc-web/app/components/collapsible-file.tsx
@@ -0,0 +1,44 @@
+import { useState } from "react";
+import { ChevronRightIcon } from "@heroicons/react/20/solid";
+import type { FileContents } from "@pierre/diffs";
+import { File } from "@pierre/diffs/react";
+
+export function CollapsibleFile({
+ file,
+ defaultOpen = true,
+}: {
+ file: FileContents;
+ defaultOpen?: boolean;
+}) {
+ const [open, setOpen] = useState(defaultOpen);
+
+ const lines = file.contents.split("\n");
+ const lineCount = lines.length;
+ const loc = lines.filter((l) => l.trim().length > 0).length;
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/apps/arc-web/app/routes.ts b/apps/arc-web/app/routes.ts
index 31e463c39..dc0bad8ef 100644
--- a/apps/arc-web/app/routes.ts
+++ b/apps/arc-web/app/routes.ts
@@ -19,6 +19,8 @@ export default [
route("runs/:id", "routes/run-detail.tsx", [
index("routes/run-overview.tsx"),
route("stages/:stageId", "routes/run-stages.tsx"),
+ route("configuration", "routes/run-configuration.tsx"),
+ route("graph", "routes/run-graph.tsx"),
route("files", "routes/run-files-changed.tsx"),
route("usage", "routes/run-usage.tsx"),
]),
diff --git a/apps/arc-web/app/routes/run-configuration.tsx b/apps/arc-web/app/routes/run-configuration.tsx
new file mode 100644
index 000000000..b26cc6c32
--- /dev/null
+++ b/apps/arc-web/app/routes/run-configuration.tsx
@@ -0,0 +1,101 @@
+import { Link, useParams } from "react-router";
+import { CheckCircleIcon, ArrowPathIcon, PauseCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
+import { DocumentTextIcon, MapIcon } from "@heroicons/react/24/outline";
+import { findRun } from "../data/runs";
+import { workflowData } from "./workflow-detail";
+import { CollapsibleFile } from "../components/collapsible-file";
+
+export const handle = { wide: true };
+
+type StageStatus = "completed" | "running" | "pending" | "failed";
+
+interface Stage {
+ id: string;
+ name: string;
+ status: StageStatus;
+ duration: string;
+}
+
+const stages: Stage[] = [
+ { id: "detect-drift", name: "Detect Drift", status: "completed", duration: "1m 12s" },
+ { id: "propose-changes", name: "Propose Changes", status: "completed", duration: "2m 34s" },
+ { id: "review-changes", name: "Review Changes", status: "completed", duration: "0m 45s" },
+ { id: "apply-changes", name: "Apply Changes", status: "running", duration: "1m 58s" },
+];
+
+const statusConfig: Record = {
+ completed: { icon: CheckCircleIcon, color: "text-mint" },
+ running: { icon: ArrowPathIcon, color: "text-teal-500" },
+ pending: { icon: PauseCircleIcon, color: "text-navy-600" },
+ failed: { icon: XCircleIcon, color: "text-coral" },
+};
+
+export default function RunConfiguration() {
+ const { id } = useParams();
+ const run = findRun(id ?? "");
+ const workflow = run ? workflowData[run.workflow] : undefined;
+
+ return (
+
+
+
+
+ {workflow ? (
+
+ ) : (
+
No configuration found.
+ )}
+
+
+ );
+}
diff --git a/apps/arc-web/app/routes/run-graph.tsx b/apps/arc-web/app/routes/run-graph.tsx
new file mode 100644
index 000000000..c5552cb48
--- /dev/null
+++ b/apps/arc-web/app/routes/run-graph.tsx
@@ -0,0 +1,119 @@
+import { useEffect, useState } from "react";
+import { Link, useParams } from "react-router";
+import type { BundledLanguage } from "@pierre/diffs";
+import { CheckCircleIcon, ArrowPathIcon, PauseCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
+import { DocumentTextIcon, MapIcon } from "@heroicons/react/24/outline";
+import { findRun } from "../data/runs";
+import { workflowData } from "./workflow-detail";
+import { registerDotLanguage } from "../data/register-dot-language";
+import { CollapsibleFile } from "../components/collapsible-file";
+
+export const handle = { wide: true };
+
+type StageStatus = "completed" | "running" | "pending" | "failed";
+
+interface Stage {
+ id: string;
+ name: string;
+ status: StageStatus;
+ duration: string;
+}
+
+const stages: Stage[] = [
+ { id: "detect-drift", name: "Detect Drift", status: "completed", duration: "1m 12s" },
+ { id: "propose-changes", name: "Propose Changes", status: "completed", duration: "2m 34s" },
+ { id: "review-changes", name: "Review Changes", status: "completed", duration: "0m 45s" },
+ { id: "apply-changes", name: "Apply Changes", status: "running", duration: "1m 58s" },
+];
+
+const statusConfig: Record = {
+ completed: { icon: CheckCircleIcon, color: "text-mint" },
+ running: { icon: ArrowPathIcon, color: "text-teal-500" },
+ pending: { icon: PauseCircleIcon, color: "text-navy-600" },
+ failed: { icon: XCircleIcon, color: "text-coral" },
+};
+
+export default function RunGraph() {
+ const { id } = useParams();
+ const run = findRun(id ?? "");
+ const workflow = run ? workflowData[run.workflow] : undefined;
+ const [dotReady, setDotReady] = useState(false);
+
+ useEffect(() => {
+ let cancelled = false;
+ registerDotLanguage().then(() => {
+ if (!cancelled) setDotReady(true);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ return (
+
+
+
+
+ {workflow && dotReady ? (
+
+ ) : (
+
No workflow graph available.
+ )}
+
+
+ );
+}
diff --git a/apps/arc-web/app/routes/run-overview.tsx b/apps/arc-web/app/routes/run-overview.tsx
index 9ae41d59b..1dfba072f 100644
--- a/apps/arc-web/app/routes/run-overview.tsx
+++ b/apps/arc-web/app/routes/run-overview.tsx
@@ -295,7 +295,7 @@ export default function RunOverview() {
-
@@ -304,7 +304,7 @@ export default function RunOverview() {
-
diff --git a/apps/arc-web/app/routes/run-stages.tsx b/apps/arc-web/app/routes/run-stages.tsx
index 3eae0b4e1..6a4d0435a 100644
--- a/apps/arc-web/app/routes/run-stages.tsx
+++ b/apps/arc-web/app/routes/run-stages.tsx
@@ -192,7 +192,7 @@ export default function RunStages() {
-
@@ -201,7 +201,7 @@ export default function RunStages() {
-
diff --git a/apps/arc-web/app/routes/workflow-definition.tsx b/apps/arc-web/app/routes/workflow-definition.tsx
index 928fc60de..29a54f602 100644
--- a/apps/arc-web/app/routes/workflow-definition.tsx
+++ b/apps/arc-web/app/routes/workflow-definition.tsx
@@ -1,50 +1,9 @@
-import { ChevronRightIcon } from "@heroicons/react/20/solid";
import { useEffect, useState } from "react";
import { useParams } from "react-router";
-import type { BundledLanguage, FileContents } from "@pierre/diffs";
-import { File } from "@pierre/diffs/react";
+import type { BundledLanguage } from "@pierre/diffs";
import { registerDotLanguage } from "../data/register-dot-language";
import { workflowData } from "./workflow-detail";
-
-function CollapsibleFile({
- file,
- defaultOpen = true,
-}: {
- file: FileContents;
- defaultOpen?: boolean;
-}) {
- const [open, setOpen] = useState(defaultOpen);
-
- const lines = file.contents.split("\n");
- const lineCount = lines.length;
- const loc = lines.filter((l) => l.trim().length > 0).length;
-
- return (
-
-
-
-
-
- );
-}
+import { CollapsibleFile } from "../components/collapsible-file";
export default function WorkflowDefinition() {
const { name } = useParams();