yoi/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte

121 lines
3.9 KiB
Svelte

<script lang="ts">
import { workspaceApiPath } from '$lib/workspace-api/http';
import { workerConsoleHref } from '$lib/workspace-console/model';
import { canShowWorkerInSidebar } from './workers';
import type { ListResponse, Worker } from './types';
const MAX_VISIBLE_WORKERS = 6;
type Props = {
currentPath?: string;
workspaceId: string;
};
let { currentPath = '/', workspaceId }: Props = $props();
function workerApiPath(path: string): string {
return workspaceApiPath(workspaceId, path);
}
let loading = $state(true);
let error = $state<string | null>(null);
let workers = $state<Worker[]>([]);
let placeholder = $state<string | null>(null);
$effect(() => {
if (!workspaceId) {
loading = false;
workers = [];
return;
}
const controller = new AbortController();
void loadWorkers(controller.signal);
return () => controller.abort();
});
async function loadWorkers(signal?: AbortSignal) {
loading = true;
error = null;
placeholder = null;
try {
const response = await fetch(workerApiPath('/workers'), { signal });
if (response.status === 404) {
workers = [];
placeholder = 'Worker API is not integrated in this build yet.';
return;
}
if (!response.ok) {
throw new Error(`workers request failed (${response.status})`);
}
const payload = (await response.json()) as ListResponse<Worker>;
workers = Array.isArray(payload.items)
? payload.items.filter(canShowWorkerInSidebar).slice(0, MAX_VISIBLE_WORKERS)
: [];
if (workers.length === 0) {
placeholder = 'No workers reported by the current API.';
}
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') {
return;
}
error = err instanceof Error ? err.message : 'workers request failed';
workers = [];
} finally {
if (!signal?.aborted) {
loading = false;
}
}
}
</script>
<section class="nav-section" aria-labelledby="workers-heading">
<div class="section-heading-row">
<h2 id="workers-heading">
<a
class="section-heading-link"
class:active={currentPath === `/w/${workspaceId}/workers`}
href={`/w/${workspaceId}/workers`}
aria-current={currentPath === `/w/${workspaceId}/workers` ? 'page' : undefined}
>workers</a>
</h2>
<a
class="section-action"
class:active={currentPath === `/w/${workspaceId}/workers/new`}
href={`/w/${workspaceId}/workers/new`}
aria-current={currentPath === `/w/${workspaceId}/workers/new` ? 'page' : undefined}
>
New
</a>
{#if !loading && !error && workers.length > 0}
<span class="section-count">{workers.length}</span>
{/if}
</div>
{#if loading}
<p class="section-state">Checking workers…</p>
{:else if error}
<p class="section-state error">{error}</p>
{:else if workers.length === 0}
<p class="section-state">{placeholder ?? 'Workers will appear here when an API is connected.'}</p>
{:else}
<ul class="nav-list" aria-label="Workers">
{#each workers as worker (`${worker.runtime_id}:${worker.worker_id}`)}
{@const href = workerConsoleHref(worker, workspaceId)}
<li>
<a href={href} class="nav-item worker-nav-item" class:active={currentPath === href} aria-current={currentPath === href ? 'page' : undefined}>
<span class="worker-title-row">
<span class="item-title">{worker.label}</span>
<span class="worker-task-title">-</span>
</span>
<span class="item-meta">
{worker.role ? `${worker.role} · ` : ''}{worker.state} · 🖥 {worker.host_id}
{worker.working_directory ? ` · wd:${worker.working_directory.repository_id}@${worker.working_directory.resolved_commit.slice(0, 8)}` : ''}
</span>
</a>
</li>
{/each}
</ul>
{/if}
</section>