mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-06 08:18:58 +00:00
Add run detail page, link run cards, and custom workflow icon colors
- Add id field to RunItem and assign IDs to all mock runs - Create run detail page at /runs/:id with status, changes, PR, CI info - Link run cards in both /runs kanban board and /workflows/:name/runs list - Add per-workflow icon color with tinted border on workflow cards Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
62c401336f
commit
bc1b5d54ab
6 changed files with 99 additions and 11 deletions
|
|
@ -1,6 +1,7 @@
|
|||
export type CiStatus = "passing" | "failing" | "pending";
|
||||
|
||||
export interface RunItem {
|
||||
id: string;
|
||||
repo: string;
|
||||
title: string;
|
||||
number?: number;
|
||||
|
|
@ -39,18 +40,21 @@ export const columns: {
|
|||
actions: ["Watch", "Steer"],
|
||||
items: [
|
||||
{
|
||||
id: "run-1",
|
||||
repo: "api-server",
|
||||
title: "Add rate limiting to auth endpoints",
|
||||
resources: "4 CPU / 8 GB",
|
||||
elapsed: "7m",
|
||||
},
|
||||
{
|
||||
id: "run-2",
|
||||
repo: "web-dashboard",
|
||||
title: "Migrate to React Router v7",
|
||||
resources: "8 CPU / 16 GB",
|
||||
elapsed: "2h 15m",
|
||||
},
|
||||
{
|
||||
id: "run-3",
|
||||
repo: "cli-tools",
|
||||
title: "Fix config parsing for nested values",
|
||||
resources: "2 CPU / 4 GB",
|
||||
|
|
@ -67,6 +71,7 @@ export const columns: {
|
|||
actions: ["Answer Question"],
|
||||
items: [
|
||||
{
|
||||
id: "run-4",
|
||||
repo: "api-server",
|
||||
title: "Update OpenAPI spec for v3",
|
||||
additions: 567,
|
||||
|
|
@ -74,6 +79,7 @@ export const columns: {
|
|||
elapsed: "1h 12m",
|
||||
},
|
||||
{
|
||||
id: "run-5",
|
||||
repo: "shared-types",
|
||||
title: "Add pipeline event types",
|
||||
additions: 145,
|
||||
|
|
@ -91,6 +97,7 @@ export const columns: {
|
|||
actions: ["Resolve"],
|
||||
items: [
|
||||
{
|
||||
id: "run-6",
|
||||
repo: "web-dashboard",
|
||||
title: "Add dark mode toggle",
|
||||
number: 889,
|
||||
|
|
@ -101,6 +108,7 @@ export const columns: {
|
|||
comments: 4,
|
||||
},
|
||||
{
|
||||
id: "run-7",
|
||||
repo: "infrastructure",
|
||||
title: "Terraform module for Redis cluster",
|
||||
number: 156,
|
||||
|
|
@ -122,6 +130,7 @@ export const columns: {
|
|||
actions: ["Merge"],
|
||||
items: [
|
||||
{
|
||||
id: "run-8",
|
||||
repo: "api-server",
|
||||
title: "Implement webhook retry logic",
|
||||
number: 1249,
|
||||
|
|
@ -133,6 +142,7 @@ export const columns: {
|
|||
comments: 7,
|
||||
},
|
||||
{
|
||||
id: "run-9",
|
||||
repo: "cli-tools",
|
||||
title: "Add --verbose flag to run command",
|
||||
number: 430,
|
||||
|
|
@ -143,6 +153,7 @@ export const columns: {
|
|||
comments: 2,
|
||||
},
|
||||
{
|
||||
id: "run-10",
|
||||
repo: "shared-types",
|
||||
title: "Export utility type helpers",
|
||||
number: 76,
|
||||
|
|
@ -166,6 +177,10 @@ export function allRunsFlat(): RunWithStatus[] {
|
|||
);
|
||||
}
|
||||
|
||||
export function findRun(id: string): RunWithStatus | undefined {
|
||||
return allRunsFlat().find((r) => r.id === id);
|
||||
}
|
||||
|
||||
export const statusColors: Record<ColumnStatus, { dot: string; text: string }> = {
|
||||
working: { dot: "bg-teal-500", text: "text-teal-500" },
|
||||
pending: { dot: "bg-amber", text: "text-amber" },
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export default [
|
|||
route("runs", "routes/workflow-runs.tsx"),
|
||||
]),
|
||||
route("runs", "routes/pipelines.tsx"),
|
||||
route("runs/:id", "routes/run-detail.tsx"),
|
||||
route("insights", "routes/insights.tsx"),
|
||||
route("settings", "routes/settings.tsx"),
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { Link } from "react-router";
|
||||
import { columns, ciConfig } from "../data/runs";
|
||||
import type { CiStatus, RunItem } from "../data/runs";
|
||||
import type { Route } from "./+types/pipelines";
|
||||
|
|
@ -80,7 +81,7 @@ function PrCard({
|
|||
actions?: string[];
|
||||
}) {
|
||||
return (
|
||||
<div className="group rounded-lg border border-white/[0.06] bg-navy-800/80 p-4 transition-all duration-200 hover:border-white/[0.12] hover:bg-navy-800 hover:shadow-lg hover:shadow-black/20">
|
||||
<Link to={`/runs/${pr.id}`} className="group block rounded-lg border border-white/[0.06] bg-navy-800/80 p-4 transition-all duration-200 hover:border-white/[0.12] hover:bg-navy-800 hover:shadow-lg hover:shadow-black/20">
|
||||
<div className="mb-2 flex items-center gap-1.5">
|
||||
<Icon className={`size-3.5 shrink-0 ${iconColor}`} />
|
||||
<span className="font-mono text-xs font-medium text-teal-500">
|
||||
|
|
@ -172,7 +173,7 @@ function PrCard({
|
|||
{pr.ci != null && <span className="ml-auto flex items-center"><CiBadge status={pr.ci} /></span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -193,7 +194,7 @@ function BoardColumn({ column }: { column: (typeof columns)[number] }) {
|
|||
<div className="flex flex-1 flex-col gap-3">
|
||||
{column.items.map((pr) => (
|
||||
<PrCard
|
||||
key={`${pr.repo}-${pr.number ?? pr.title}`}
|
||||
key={pr.id}
|
||||
pr={pr}
|
||||
icon={Icon}
|
||||
iconColor={column.iconColor}
|
||||
|
|
|
|||
66
apps/arc-web/app/routes/run-detail.tsx
Normal file
66
apps/arc-web/app/routes/run-detail.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { findRun, statusColors, ciConfig } from "../data/runs";
|
||||
import type { Route } from "./+types/run-detail";
|
||||
|
||||
export function meta({ params }: Route.MetaArgs) {
|
||||
const run = findRun(params.id);
|
||||
return [{ title: run ? `${run.title} — Arc` : "Run — Arc" }];
|
||||
}
|
||||
|
||||
export default function RunDetail({ params }: Route.ComponentProps) {
|
||||
const run = findRun(params.id);
|
||||
|
||||
if (!run) {
|
||||
return <p className="py-8 text-center text-sm text-navy-600">Run not found.</p>;
|
||||
}
|
||||
|
||||
const colors = statusColors[run.status];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-ice-100">{run.title}</h2>
|
||||
<div className="mt-2 flex items-center gap-3 text-sm">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className={`size-2 rounded-full ${colors.dot}`} />
|
||||
<span className={`font-medium ${colors.text}`}>{run.statusLabel}</span>
|
||||
</span>
|
||||
<span className="font-mono text-xs text-navy-600">{run.repo}</span>
|
||||
{run.elapsed && (
|
||||
<span className={`font-mono text-xs ${run.elapsedWarning ? "text-amber" : "text-navy-600"}`}>{run.elapsed}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-white/[0.06] bg-navy-800/80 p-5 space-y-4">
|
||||
{run.resources && (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-navy-600">Resources</span>
|
||||
<span className="font-mono text-sm text-ice-300">{run.resources}</span>
|
||||
</div>
|
||||
)}
|
||||
{(run.additions != null || run.deletions != null) && (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-navy-600">Changes</span>
|
||||
<span className="font-mono text-sm">
|
||||
{run.additions != null && <span className="text-mint">+{run.additions.toLocaleString()}</span>}
|
||||
{run.additions != null && run.deletions != null && <span className="text-navy-600"> / </span>}
|
||||
{run.deletions != null && <span className="text-coral">-{run.deletions.toLocaleString()}</span>}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{run.number != null && (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-navy-600">Pull Request</span>
|
||||
<span className="font-mono text-sm text-ice-300">#{run.number}</span>
|
||||
</div>
|
||||
)}
|
||||
{run.ci && (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-navy-600">CI</span>
|
||||
<span className={`font-mono text-sm ${ciConfig[run.ci].text}`}>{ciConfig[run.ci].label}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import { ChevronDownIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
import { Link } from "react-router";
|
||||
import { allRunsFlat, ciConfig, columns, statusColors } from "../data/runs";
|
||||
import type { ColumnStatus, RunWithStatus } from "../data/runs";
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ function GitPullRequestIcon({ className }: { className?: string }) {
|
|||
function RunRow({ run }: { run: RunWithStatus }) {
|
||||
const colors = statusColors[run.status];
|
||||
return (
|
||||
<div className="grid items-center rounded-lg border border-white/[0.06] bg-navy-800/80 px-4 py-3 transition-all duration-200 hover:border-white/[0.12] hover:bg-navy-800" style={{ gridColumn: "1 / -1", gridTemplateColumns: "subgrid" }}>
|
||||
<Link to={`/runs/${run.id}`} className="grid items-center rounded-lg border border-white/[0.06] bg-navy-800/80 px-4 py-3 transition-all duration-200 hover:border-white/[0.12] hover:bg-navy-800" style={{ gridColumn: "1 / -1", gridTemplateColumns: "subgrid" }}>
|
||||
<span className="flex items-center gap-2 pr-2">
|
||||
<span className={`size-2 shrink-0 rounded-full ${colors.dot}`} />
|
||||
<span className={`text-xs font-medium ${colors.text}`}>{run.statusLabel}</span>
|
||||
|
|
@ -52,7 +53,7 @@ function RunRow({ run }: { run: RunWithStatus }) {
|
|||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +97,7 @@ export default function WorkflowRuns() {
|
|||
</div>
|
||||
<div className="grid gap-2" style={{ gridTemplateColumns: "auto 5rem 1fr 8rem auto" }}>
|
||||
{filtered.map((run) => (
|
||||
<RunRow key={`${run.repo}-${run.number ?? run.title}`} run={run} />
|
||||
<RunRow key={run.id} run={run} />
|
||||
))}
|
||||
{filtered.length === 0 && (
|
||||
<p className="py-8 text-center text-sm text-navy-600">No runs match "{query}"</p>
|
||||
|
|
|
|||
|
|
@ -61,13 +61,14 @@ interface Workflow {
|
|||
filename: string;
|
||||
lastRun: string;
|
||||
icon: ComponentType<{ className?: string }>;
|
||||
color: string;
|
||||
}
|
||||
|
||||
const workflows: Workflow[] = [
|
||||
{ name: "Fix Build", slug: "fix_build", filename: "fix_build.dot", lastRun: "2 hours ago", icon: WrenchIcon },
|
||||
{ name: "Implement Feature", slug: "implement", filename: "implement.dot", lastRun: "4 days ago", icon: CodeBracketIcon },
|
||||
{ name: "Sync Drift", slug: "sync_drift", filename: "sync_drift.dot", lastRun: "1 day ago", icon: ArrowsRightLeftIcon },
|
||||
{ name: "Expand Product", slug: "expand", filename: "expand.dot", lastRun: "2 weeks ago", icon: RocketLaunchIcon },
|
||||
{ name: "Fix Build", slug: "fix_build", filename: "fix_build.dot", lastRun: "2 hours ago", icon: WrenchIcon, color: "#F0A45B" },
|
||||
{ name: "Implement Feature", slug: "implement", filename: "implement.dot", lastRun: "4 days ago", icon: CodeBracketIcon, color: "#67B2D7" },
|
||||
{ name: "Sync Drift", slug: "sync_drift", filename: "sync_drift.dot", lastRun: "1 day ago", icon: ArrowsRightLeftIcon, color: "#5AC8A8" },
|
||||
{ name: "Expand Product", slug: "expand", filename: "expand.dot", lastRun: "2 weeks ago", icon: RocketLaunchIcon, color: "#E86B6B" },
|
||||
];
|
||||
|
||||
function PlayIcon({ className }: { className?: string }) {
|
||||
|
|
@ -91,7 +92,10 @@ function WorkflowCard({ workflow }: { workflow: Workflow }) {
|
|||
return (
|
||||
<div className="group flex items-center gap-4 rounded-lg border border-white/[0.06] bg-navy-800/80 p-4 transition-all duration-200 hover:border-white/[0.12] hover:bg-navy-800 hover:shadow-lg hover:shadow-black/20">
|
||||
<Link to={`/workflows/${workflow.slug}`} className="flex min-w-0 flex-1 items-center gap-4">
|
||||
<div className="flex size-9 shrink-0 items-center justify-center rounded-md border border-white/[0.06] bg-navy-900/60 text-ice-300">
|
||||
<div
|
||||
className="flex size-9 shrink-0 items-center justify-center rounded-md border bg-navy-900/60"
|
||||
style={{ borderColor: `${workflow.color}33`, color: workflow.color }}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue