1bbdd7acba
- Add apps/api/ — Hono REST API server for managing pentest scans via K8s Jobs - POST/GET /api/scans, GET /api/scans/:id, cancel, report endpoints - Bearer token auth, Temporal client integration, K8s Job builder - Dockerfile, Kustomize manifests (Deployment, Service, RBAC) - Add CLI orchestrator abstraction (docker.ts → Orchestrator interface) - DockerOrchestrator and K8sOrchestrator implementations - Backend detection via SHANNON_BACKEND env var or --backend flag - Add CI workflow: type-check + lint on PR, build+push both images on main - Switch all workflows to self-hosted runners (runners-farhoodliquor) - Add shannon-api image build to release and release-beta workflows - Add root infra/kustomization.yaml as Flux entry point - Export PipelineProgress from @shannon/worker/pipeline Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
72 lines
2.3 KiB
TypeScript
72 lines
2.3 KiB
TypeScript
/**
|
|
* Workspace reader — reads session.json and deliverables from the shared workspaces PVC.
|
|
*/
|
|
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
export interface SessionInfo {
|
|
readonly workspace: string;
|
|
readonly originalWorkflowId?: string;
|
|
readonly webUrl?: string;
|
|
readonly startTime?: number;
|
|
readonly cost?: number;
|
|
readonly resumeAttempts?: readonly { workflowId: string; timestamp: number }[];
|
|
}
|
|
|
|
export function readSessionJson(workspacesDir: string, workspace: string): SessionInfo | null {
|
|
const sessionPath = path.join(workspacesDir, workspace, 'session.json');
|
|
try {
|
|
const raw = fs.readFileSync(sessionPath, 'utf-8');
|
|
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
const session = data.session as Record<string, unknown> | undefined;
|
|
const originalWorkflowId = session?.originalWorkflowId as string | undefined;
|
|
const webUrl = session?.webUrl as string | undefined;
|
|
const startTime = session?.startTime as number | undefined;
|
|
const cost = session?.totalCostUsd as number | undefined;
|
|
const resumeAttempts = session?.resumeAttempts as SessionInfo['resumeAttempts'];
|
|
|
|
return {
|
|
workspace,
|
|
...(originalWorkflowId && { originalWorkflowId }),
|
|
...(webUrl && { webUrl }),
|
|
...(startTime && { startTime }),
|
|
...(cost && { cost }),
|
|
...(resumeAttempts && { resumeAttempts }),
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function readReport(workspacesDir: string, workspace: string): string | null {
|
|
const delivDir = path.join(workspacesDir, workspace, 'deliverables');
|
|
try {
|
|
const files = fs.readdirSync(delivDir);
|
|
const reportFile = files.find((f) => f.includes('report') && f.endsWith('.md'));
|
|
if (!reportFile) return null;
|
|
return fs.readFileSync(path.join(delivDir, reportFile), 'utf-8');
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function listWorkspaces(workspacesDir: string): SessionInfo[] {
|
|
try {
|
|
const entries = fs.readdirSync(workspacesDir, { withFileTypes: true });
|
|
const results: SessionInfo[] = [];
|
|
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory()) continue;
|
|
const session = readSessionJson(workspacesDir, entry.name);
|
|
if (session) {
|
|
results.push(session);
|
|
}
|
|
}
|
|
|
|
return results.sort((a, b) => (b.startTime ?? 0) - (a.startTime ?? 0));
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|