From b5d8ad1494d6a6ea70b535bb977b3ed139699301 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sat, 28 Feb 2026 23:24:22 -0500 Subject: [PATCH] Add columns/list toggle on Runs page and Settings page mockup Runs: add a view toggle (columns vs list) with collapsible status-grouped sections in list view. Settings: populate with grouped panels (General, Git & VCS, Compute, Notifications, Security) containing key/value fields with text inputs, selects, and toggles. Hide redundant header. Co-Authored-By: Claude Opus 4.6 --- apps/arc-web/app/routes/pipelines.tsx | 121 ++++++++++++++++++-- apps/arc-web/app/routes/settings.tsx | 155 +++++++++++++++++++++++++- 2 files changed, 266 insertions(+), 10 deletions(-) diff --git a/apps/arc-web/app/routes/pipelines.tsx b/apps/arc-web/app/routes/pipelines.tsx index 8c59f3b02..fe75c5649 100644 --- a/apps/arc-web/app/routes/pipelines.tsx +++ b/apps/arc-web/app/routes/pipelines.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import { Link } from "react-router"; -import { ChevronDownIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; -import { columns, ciConfig } from "../data/runs"; -import type { CiStatus, RunItem } from "../data/runs"; +import { ChevronDownIcon, ChevronRightIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import { columns, ciConfig, statusColors } from "../data/runs"; +import type { CiStatus, RunItem, RunWithStatus } from "../data/runs"; import type { Route } from "./+types/pipelines"; export function meta({}: Route.MetaArgs) { @@ -208,11 +208,53 @@ function BoardColumn({ column }: { column: (typeof columns)[number] }) { ); } +type ViewMode = "columns" | "list"; + +function RunRow({ run }: { run: RunWithStatus }) { + return ( + + + {run.elapsed} + + + + {run.repo} + {run.title} + {run.comments != null && run.comments > 0 && ( + + + {run.comments} + + )} + + + + {run.additions != null && +{run.additions.toLocaleString()}} + {run.deletions != null && -{run.deletions.toLocaleString()}} + + + + {run.number != null && ( + <> + + #{run.number} + {run.ci != null && } + + )} + + + ); +} + const allRepos = [...new Set(columns.flatMap((col) => col.items.map((item) => item.repo)))].sort(); export default function Pipelines() { const [query, setQuery] = useState(""); const [repoFilter, setRepoFilter] = useState("all"); + const [view, setView] = useState("columns"); + const [collapsed, setCollapsed] = useState>(new Set()); const lowerQuery = query.toLowerCase(); const filteredColumns = columns.map((col) => ({ @@ -253,12 +295,75 @@ export default function Pipelines() { +
+ + +
-
- {filteredColumns.map((col) => ( - - ))} -
+ + {view === "columns" ? ( +
+ {filteredColumns.map((col) => ( + + ))} +
+ ) : ( +
+ {filteredColumns.map((col) => { + const isCollapsed = collapsed.has(col.id); + return ( +
+ + {!isCollapsed && (col.items.length > 0 ? ( +
+ {col.items.map((item) => ( + + ))} +
+ ) : ( +

No runs

+ ))} +
+ ); + })} +
+ )} ); } diff --git a/apps/arc-web/app/routes/settings.tsx b/apps/arc-web/app/routes/settings.tsx index 5f68c2730..776ba234e 100644 --- a/apps/arc-web/app/routes/settings.tsx +++ b/apps/arc-web/app/routes/settings.tsx @@ -4,6 +4,157 @@ export function meta({}: Route.MetaArgs) { return [{ title: "Settings — Arc" }]; } -export default function Settings() { - return null; +export const handle = { hideHeader: true }; + +interface SettingField { + key: string; + label: string; + value: string; + type: "text" | "select" | "toggle"; + options?: string[]; + description?: string; +} + +interface SettingGroup { + id: string; + name: string; + description: string; + fields: SettingField[]; +} + +const settingGroups: SettingGroup[] = [ + { + id: "general", + name: "General", + description: "Core platform settings and defaults.", + fields: [ + { key: "org_name", label: "Organization name", value: "Acme Corp", type: "text" }, + { key: "default_branch", label: "Default branch", value: "main", type: "text" }, + { key: "timezone", label: "Timezone", value: "America/New_York", type: "select", options: ["America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles", "UTC", "Europe/London", "Europe/Berlin", "Asia/Tokyo"] }, + { key: "auto_cancel", label: "Auto-cancel superseded runs", value: "true", type: "toggle" }, + ], + }, + { + id: "git", + name: "Git & VCS", + description: "Version control integration and repository settings.", + fields: [ + { key: "github_org", label: "GitHub organization", value: "acme-corp", type: "text" }, + { key: "clone_protocol", label: "Clone protocol", value: "SSH", type: "select", options: ["SSH", "HTTPS"] }, + { key: "auto_merge", label: "Auto-merge when checks pass", value: "false", type: "toggle" }, + { key: "delete_branch", label: "Delete branch after merge", value: "true", type: "toggle" }, + { key: "commit_signing", label: "Require commit signing", value: "false", type: "toggle" }, + ], + }, + { + id: "compute", + name: "Compute", + description: "Resource allocation and execution environment.", + fields: [ + { key: "default_cpu", label: "Default CPU", value: "4", type: "select", options: ["2", "4", "8", "16"] }, + { key: "default_memory", label: "Default memory", value: "8 GB", type: "select", options: ["4 GB", "8 GB", "16 GB", "32 GB"] }, + { key: "max_parallel", label: "Max parallel runs", value: "10", type: "text" }, + { key: "timeout_minutes", label: "Run timeout (minutes)", value: "120", type: "text" }, + { key: "gpu_enabled", label: "GPU acceleration", value: "false", type: "toggle" }, + ], + }, + { + id: "notifications", + name: "Notifications", + description: "Alerts and notification delivery preferences.", + fields: [ + { key: "slack_webhook", label: "Slack webhook URL", value: "https://hooks.slack.com/services/T00/B00/xxxx", type: "text" }, + { key: "notify_on_failure", label: "Notify on failure", value: "true", type: "toggle" }, + { key: "notify_on_success", label: "Notify on success", value: "false", type: "toggle" }, + { key: "notify_on_approval", label: "Notify on approval needed", value: "true", type: "toggle" }, + { key: "email_digest", label: "Daily email digest", value: "false", type: "toggle" }, + ], + }, + { + id: "security", + name: "Security", + description: "Access control and security policies.", + fields: [ + { key: "sso_provider", label: "SSO provider", value: "Okta", type: "select", options: ["None", "Okta", "Azure AD", "Google Workspace", "OneLogin"] }, + { key: "mfa_required", label: "Require MFA", value: "true", type: "toggle" }, + { key: "session_timeout", label: "Session timeout", value: "8 hours", type: "select", options: ["1 hour", "4 hours", "8 hours", "24 hours", "7 days"] }, + { key: "audit_log", label: "Audit logging", value: "true", type: "toggle" }, + { key: "ip_allowlist", label: "IP allowlist", value: "", type: "text", description: "Comma-separated CIDRs. Leave empty to allow all." }, + ], + }, +]; + +function ToggleSwitch({ enabled }: { enabled: boolean }) { + return ( + + ); +} + +function SettingRow({ field }: { field: SettingField }) { + return ( +
+
+

{field.label}

+ {field.description != null && ( +

{field.description}

+ )} +
+ +
+ {field.type === "toggle" ? ( + + ) : field.type === "select" ? ( + + ) : ( + + )} +
+
+ ); +} + +function SettingsSection({ group }: { group: SettingGroup }) { + return ( +
+
+

{group.name}

+

{group.description}

+
+
+ {group.fields.map((field) => ( + + ))} +
+
+ ); +} + +export default function Settings() { + return ( +
+ {settingGroups.map((group) => ( + + ))} +
+ ); }