diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 7aac9dfa277..cfe0f487e25 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -162,6 +162,7 @@ function boardRoutes() { } /> } /> } /> + } /> } /> } /> } /> @@ -352,6 +353,7 @@ export function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/ui/src/pages/ExecutionWorkspaceDetail.tsx b/ui/src/pages/ExecutionWorkspaceDetail.tsx index 743fc07729b..3aa4a95fb96 100644 --- a/ui/src/pages/ExecutionWorkspaceDetail.tsx +++ b/ui/src/pages/ExecutionWorkspaceDetail.tsx @@ -34,7 +34,7 @@ type WorkspaceFormState = { workspaceRuntime: string; }; -type ExecutionWorkspaceTab = "configuration" | "issues"; +type ExecutionWorkspaceTab = "configuration" | "runtime_logs" | "issues"; function resolveExecutionWorkspaceTab(pathname: string, workspaceId: string): ExecutionWorkspaceTab | null { const segments = pathname.split("/").filter(Boolean); @@ -42,10 +42,16 @@ function resolveExecutionWorkspaceTab(pathname: string, workspaceId: string): Ex if (executionWorkspacesIndex === -1 || segments[executionWorkspacesIndex + 1] !== workspaceId) return null; const tab = segments[executionWorkspacesIndex + 2]; if (tab === "issues") return "issues"; + if (tab === "runtime-logs") return "runtime_logs"; if (tab === "configuration") return "configuration"; return null; } +function executionWorkspaceTabPath(workspaceId: string, tab: ExecutionWorkspaceTab) { + const segment = tab === "runtime_logs" ? "runtime-logs" : tab; + return `/execution-workspaces/${workspaceId}/${segment}`; +} + function isSafeExternalUrl(value: string | null | undefined) { if (!value) return false; try { @@ -450,18 +456,18 @@ export function ExecutionWorkspaceDetail() { let cachedTab: ExecutionWorkspaceTab = "configuration"; try { const storedTab = localStorage.getItem(`paperclip:execution-workspace-tab:${workspaceId}`); - if (storedTab === "issues" || storedTab === "configuration") { + if (storedTab === "issues" || storedTab === "configuration" || storedTab === "runtime_logs") { cachedTab = storedTab; } } catch {} - return ; + return ; } const handleTabChange = (tab: ExecutionWorkspaceTab) => { try { localStorage.setItem(`paperclip:execution-workspace-tab:${workspace.id}`, tab); } catch {} - navigate(`/execution-workspaces/${workspace.id}/${tab}`); + navigate(executionWorkspaceTabPath(workspace.id, tab)); }; const saveChanges = () => { @@ -485,7 +491,7 @@ export function ExecutionWorkspaceDetail() { return ( <> -
+
+
+
+
+
Runtime services
+

Attached services

+

+ Source: {runtimeConfigSource === "execution_workspace" + ? "execution workspace override" + : runtimeConfigSource === "project_workspace" + ? "project workspace default" + : "none"} +

+
+
+ + + +
+
+ + {workspace.runtimeServices && workspace.runtimeServices.length > 0 ? ( +
+ {workspace.runtimeServices.map((service) => ( +
+
+
+
{service.serviceName}
+
{service.status} · {service.lifecycle}
+
+ {service.url ? ( + + {service.url} + + + ) : null} + {service.port ?
Port {service.port}
: null} + {service.command ? : null} + {service.cwd ? : null} +
+
+ {service.healthStatus} +
+
+ ))} +
+ ) : ( +

+ {effectiveRuntimeConfig + ? "No runtime services are currently running for this execution workspace." + : "No runtime config is defined for this execution workspace yet."} +

+ )} + {!errorMessage && runtimeActionMessage ?

{runtimeActionMessage}

: null} +
+ handleTabChange(value as ExecutionWorkspaceTab)}> {activeTab === "configuration" ? ( -
-
-
-
-
-
- Configuration -
-

Workspace settings

-

- Edit the concrete path, repo, branch, provisioning, teardown, and runtime overrides attached to this execution workspace. -

-
- -
- - - -
- - setForm((current) => current ? { ...current, name: event.target.value } : current)} - placeholder="Execution workspace name" - /> - - - setForm((current) => current ? { ...current, branchName: event.target.value } : current)} - placeholder="PAP-946-workspace" - /> - -
- -
- - setForm((current) => current ? { ...current, cwd: event.target.value } : current)} - placeholder="/absolute/path/to/workspace" - /> - - - setForm((current) => current ? { ...current, providerRef: event.target.value } : current)} - placeholder="/path/to/worktree or provider ref" - /> - -
- -
- - setForm((current) => current ? { ...current, repoUrl: event.target.value } : current)} - placeholder="https://github.com/org/repo" - /> - - - setForm((current) => current ? { ...current, baseRef: event.target.value } : current)} - placeholder="origin/main" - /> - -
- -
- -