ui: add runtime workdir pages
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
let workspaceId = $derived(data.workspace?.workspace_id ?? data.workspaceId);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -37,6 +38,19 @@
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="workspace-actions" aria-label="Workspace sections">
|
||||
<a class="workspace-action-card" href={`/w/${workspaceId}/runtimes`}>
|
||||
<span>Runtimes</span>
|
||||
<strong>Manage runtimes and workdirs</strong>
|
||||
<small>{data.hosts?.items.length ?? 0} host{(data.hosts?.items.length ?? 0) === 1 ? '' : 's'} visible</small>
|
||||
</a>
|
||||
<a class="workspace-action-card" href={`/w/${workspaceId}/workers`}>
|
||||
<span>Workers</span>
|
||||
<strong>Open worker list</strong>
|
||||
<small>Inspect status and attach to consoles</small>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Hosts</h2>
|
||||
{#if data.hosts}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import type { Runtime } from '$lib/workspace-sidebar/types';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
function runtimeScope(runtime: Runtime): string {
|
||||
return runtime.capabilities.workspace_scope;
|
||||
}
|
||||
|
||||
function runtimePlatform(runtime: Runtime): string {
|
||||
return `${runtime.capabilities.os} / ${runtime.capabilities.arch}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Runtimes · Yoi Workspace</title>
|
||||
<meta name="description" content="Workspace Runtimes" />
|
||||
</svelte:head>
|
||||
|
||||
<section class="runtimes-page" aria-labelledby="runtimes-heading">
|
||||
<header class="page-header-row">
|
||||
<div>
|
||||
<h1 id="runtimes-heading">Runtimes</h1>
|
||||
<p>Execution backends available to this workspace.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#if data.runtimesError}
|
||||
<p class="section-state error">{data.runtimesError}</p>
|
||||
{:else if !data.runtimes}
|
||||
<p class="section-state">Loading Runtimes…</p>
|
||||
{:else if data.runtimes.items.length === 0}
|
||||
<p class="section-state">No Runtimes are visible.</p>
|
||||
{:else}
|
||||
<div class="table-wrap">
|
||||
<table class="runtimes-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Runtime</th>
|
||||
<th>Kind</th>
|
||||
<th>Status</th>
|
||||
<th>Scope</th>
|
||||
<th>Platform</th>
|
||||
<th>Capacity</th>
|
||||
<th>Workdirs</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each data.runtimes.items as runtime}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{runtime.label}</strong>
|
||||
<small><code>{runtime.runtime_id}</code></small>
|
||||
</td>
|
||||
<td>{runtime.kind}</td>
|
||||
<td>{runtime.status}</td>
|
||||
<td>{runtimeScope(runtime)}</td>
|
||||
<td>{runtimePlatform(runtime)}</td>
|
||||
<td>{runtime.capabilities.max_workers} workers</td>
|
||||
<td>
|
||||
<a class="inline-link" href={`/w/${data.workspaceId}/runtimes/${encodeURIComponent(runtime.runtime_id)}/workdirs`}>
|
||||
Open workdirs
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
@@ -0,0 +1,16 @@
|
||||
import { loadJson, workspaceApiPath } from "$lib/workspace-api/http";
|
||||
import type { ListResponse, Runtime } from "$lib/workspace-sidebar/types";
|
||||
import type { PageLoad } from "./$types";
|
||||
|
||||
export const load: PageLoad = async ({ fetch, params }) => {
|
||||
const runtimes = await loadJson<ListResponse<Runtime>>(
|
||||
fetch,
|
||||
workspaceApiPath(params.workspaceId, "/runtimes"),
|
||||
);
|
||||
|
||||
return {
|
||||
workspaceId: params.workspaceId,
|
||||
runtimes: runtimes.data,
|
||||
runtimesError: runtimes.error,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import type { WorkingDirectorySummary } from '$lib/workspace-sidebar/types';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
let runtimeLabel = $derived(
|
||||
data.runtimes?.items.find((runtime) => runtime.runtime_id === data.runtimeId)?.label ?? data.runtimeId,
|
||||
);
|
||||
|
||||
function commitLabel(workdir: WorkingDirectorySummary): string {
|
||||
return workdir.resolved_commit ? workdir.resolved_commit.slice(0, 12) : '—';
|
||||
}
|
||||
|
||||
function selectorLabel(workdir: WorkingDirectorySummary): string {
|
||||
return workdir.requested_selector ?? 'HEAD';
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Workdirs · {runtimeLabel} · Yoi Workspace</title>
|
||||
<meta name="description" content="Runtime workdirs" />
|
||||
</svelte:head>
|
||||
|
||||
<section class="workdirs-page" aria-labelledby="workdirs-heading">
|
||||
<header class="page-header-row">
|
||||
<div>
|
||||
<p class="breadcrumb"><a href={`/w/${data.workspaceId}/runtimes`}>Runtimes</a> / {runtimeLabel}</p>
|
||||
<h1 id="workdirs-heading">Workdirs</h1>
|
||||
<p>Workdirs owned by <code>{data.runtimeId}</code>.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{#if data.workdirsError}
|
||||
<p class="section-state error">{data.workdirsError}</p>
|
||||
{:else if !data.workdirs}
|
||||
<p class="section-state">Loading workdirs…</p>
|
||||
{:else if data.workdirs.items.length === 0}
|
||||
<p class="section-state">No workdirs are visible for this Runtime.</p>
|
||||
{:else}
|
||||
<div class="table-wrap">
|
||||
<table class="workdirs-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Workdir</th>
|
||||
<th>Repository</th>
|
||||
<th>Selector</th>
|
||||
<th>Commit</th>
|
||||
<th>Status</th>
|
||||
<th>Policy</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each data.workdirs.items as workdir}
|
||||
<tr>
|
||||
<td><code>{workdir.working_directory_id}</code></td>
|
||||
<td>{workdir.repository_id}</td>
|
||||
<td>{selectorLabel(workdir)}</td>
|
||||
<td><code>{commitLabel(workdir)}</code></td>
|
||||
<td>{workdir.status}</td>
|
||||
<td>
|
||||
<span>{workdir.dirty_state_policy}</span>
|
||||
<small>{workdir.cleanup_policy}</small>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
@@ -0,0 +1,30 @@
|
||||
import { loadJson, workspaceApiPath } from "$lib/workspace-api/http";
|
||||
import type {
|
||||
BrowserWorkingDirectoryListResponse,
|
||||
ListResponse,
|
||||
Runtime,
|
||||
} from "$lib/workspace-sidebar/types";
|
||||
import type { PageLoad } from "./$types";
|
||||
|
||||
export const load: PageLoad = async ({ fetch, params }) => {
|
||||
const runtimeId = params.runtimeId;
|
||||
const [runtimes, workdirs] = await Promise.all([
|
||||
loadJson<ListResponse<Runtime>>(fetch, workspaceApiPath(params.workspaceId, "/runtimes")),
|
||||
loadJson<BrowserWorkingDirectoryListResponse>(
|
||||
fetch,
|
||||
workspaceApiPath(
|
||||
params.workspaceId,
|
||||
`/runtimes/${encodeURIComponent(runtimeId)}/working-directories`,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
return {
|
||||
workspaceId: params.workspaceId,
|
||||
runtimeId,
|
||||
runtimes: runtimes.data,
|
||||
runtimesError: runtimes.error,
|
||||
workdirs: workdirs.data,
|
||||
workdirsError: workdirs.error,
|
||||
};
|
||||
};
|
||||
@@ -51,7 +51,7 @@
|
||||
<th>Runtime</th>
|
||||
<th>Profile</th>
|
||||
<th>Status</th>
|
||||
<th>Working directory</th>
|
||||
<th>Workdir</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -99,11 +99,11 @@
|
||||
|
||||
async function createWorkingDirectory() {
|
||||
if (!runtimeId) {
|
||||
submitError = { message: 'select a runtime before creating a working directory', diagnostics: [] };
|
||||
submitError = { message: 'select a runtime before creating a workdir', diagnostics: [] };
|
||||
return;
|
||||
}
|
||||
if (!workingDirectoryRepositoryId) {
|
||||
submitError = { message: 'select a repository before creating a working directory', diagnostics: [] };
|
||||
submitError = { message: 'select a repository before creating a workdir', diagnostics: [] };
|
||||
return;
|
||||
}
|
||||
creatingWorkingDirectory = true;
|
||||
@@ -122,7 +122,7 @@
|
||||
},
|
||||
);
|
||||
if (!response.ok) {
|
||||
submitError = await responseDisplayError(response, 'working directory create failed');
|
||||
submitError = await responseDisplayError(response, 'workdir create failed');
|
||||
return;
|
||||
}
|
||||
const payload = (await response.json()) as BrowserWorkingDirectoryCreateResponse;
|
||||
@@ -138,7 +138,7 @@
|
||||
: options;
|
||||
workingDirectoryId = payload.item.working_directory_id;
|
||||
} catch (err) {
|
||||
submitError = exceptionDisplayError(err, 'working directory create failed');
|
||||
submitError = exceptionDisplayError(err, 'workdir create failed');
|
||||
} finally {
|
||||
creatingWorkingDirectory = false;
|
||||
}
|
||||
@@ -150,7 +150,7 @@
|
||||
return;
|
||||
}
|
||||
if (isNewWorkingDirectorySelected || !workingDirectoryId) {
|
||||
submitError = { message: 'select or create a working directory before starting a Worker', diagnostics: [] };
|
||||
submitError = { message: 'select or create a workdir before starting a Worker', diagnostics: [] };
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
<header class="worker-new-page-header">
|
||||
<div>
|
||||
<h1 id="new-worker-heading">New Worker</h1>
|
||||
<p>Create a Worker on a selected Runtime and working directory.</p>
|
||||
<p>Create a Worker on a selected Runtime and workdir.</p>
|
||||
</div>
|
||||
<a class="secondary-link" href={`/w/${workspaceId}`}>Back to workspace</a>
|
||||
</header>
|
||||
@@ -236,14 +236,14 @@
|
||||
|
||||
<div class="worker-launch-sentence">
|
||||
<span>Run at</span>
|
||||
<select class="worker-inline-select wd-select" bind:value={workingDirectoryId} aria-label="Working directory">
|
||||
<option value="">Select working directory</option>
|
||||
<select class="worker-inline-select wd-select" bind:value={workingDirectoryId} aria-label="Workdir">
|
||||
<option value="">Select workdir</option>
|
||||
{#each options?.working_directories ?? [] as directory}
|
||||
<option value={directory.working_directory_id} disabled={directory.status !== 'active'}>
|
||||
{directory.repository_id} · {directory.requested_selector ?? 'HEAD'}
|
||||
</option>
|
||||
{/each}
|
||||
<option value={NEW_WORKING_DIRECTORY_VALUE}>New working directory…</option>
|
||||
<option value={NEW_WORKING_DIRECTORY_VALUE}>New workdir…</option>
|
||||
</select>
|
||||
<span>in</span>
|
||||
<select class="worker-inline-select runtime-select" bind:value={runtimeId} required aria-label="Runtime">
|
||||
@@ -261,7 +261,7 @@
|
||||
|
||||
{#if isNewWorkingDirectorySelected}
|
||||
<div class="new-working-directory-panel">
|
||||
<h3>New working directory</h3>
|
||||
<h3>New workdir</h3>
|
||||
<div class="new-working-directory-fields">
|
||||
<label>
|
||||
<span>Repository</span>
|
||||
@@ -281,14 +281,14 @@
|
||||
</label>
|
||||
</div>
|
||||
<button type="button" disabled={creatingWorkingDirectory || !runtimeId || !workingDirectoryRepositoryId} onclick={() => void createWorkingDirectory()}>
|
||||
{creatingWorkingDirectory ? 'Creating…' : 'Create working directory'}
|
||||
{creatingWorkingDirectory ? 'Creating…' : 'Create workdir'}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<label class="relative-cwd-field">
|
||||
<span>Relative cwd inside WD</span>
|
||||
<input bind:value={relativeCwd} autocomplete="off" placeholder="Optional path inside working directory" />
|
||||
<span>Relative cwd inside workdir</span>
|
||||
<input bind:value={relativeCwd} autocomplete="off" placeholder="Optional path inside workdir" />
|
||||
</label>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user