feat(web): add project workspace page with tasks and agent sessions (#479)
Some checks failed
ci/woodpecker/push/web Pipeline failed

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #479.
This commit is contained in:
2026-02-23 04:29:39 +00:00
committed by jason.woltje
parent 172ed1d40f
commit a78a8b88e1
2 changed files with 1185 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,100 @@
/**
* Runner Jobs API Client
* Handles runner-job-related API requests
*/
import { apiGet, type ApiResponse } from "./client";
/**
* Runner job status enum (matches backend RunnerJobStatus)
*/
export enum RunnerJobStatus {
PENDING = "PENDING",
QUEUED = "QUEUED",
RUNNING = "RUNNING",
COMPLETED = "COMPLETED",
FAILED = "FAILED",
CANCELLED = "CANCELLED",
}
/**
* Runner job response interface (matches Prisma RunnerJob model)
*/
export interface RunnerJob {
id: string;
workspaceId: string;
agentTaskId: string | null;
type: string;
status: RunnerJobStatus;
priority: number;
progressPercent: number;
version: number;
result: Record<string, unknown> | null;
error: string | null;
createdAt: string;
startedAt: string | null;
completedAt: string | null;
}
/**
* Filters for querying runner jobs
*/
export interface RunnerJobFilters {
workspaceId?: string;
status?: RunnerJobStatus | RunnerJobStatus[];
type?: string;
agentTaskId?: string;
page?: number;
limit?: number;
}
/**
* Paginated runner jobs response
*/
export interface PaginatedRunnerJobs {
data: RunnerJob[];
meta?: {
total?: number;
page?: number;
limit?: number;
};
}
/**
* Fetch runner jobs with optional filters
*/
export async function fetchRunnerJobs(filters?: RunnerJobFilters): Promise<RunnerJob[]> {
const params = new URLSearchParams();
if (filters?.status) {
const statuses = Array.isArray(filters.status) ? filters.status : [filters.status];
for (const s of statuses) {
params.append("status", s);
}
}
if (filters?.type) {
params.append("type", filters.type);
}
if (filters?.agentTaskId) {
params.append("agentTaskId", filters.agentTaskId);
}
if (filters?.page !== undefined) {
params.append("page", String(filters.page));
}
if (filters?.limit !== undefined) {
params.append("limit", String(filters.limit));
}
const queryString = params.toString();
const endpoint = queryString ? `/api/runner-jobs?${queryString}` : "/api/runner-jobs";
const response = await apiGet<ApiResponse<RunnerJob[]>>(endpoint, filters?.workspaceId);
return response.data;
}
/**
* Fetch a single runner job by ID
*/
export async function fetchRunnerJob(id: string, workspaceId?: string): Promise<RunnerJob> {
return apiGet<RunnerJob>(`/api/runner-jobs/${id}`, workspaceId);
}