feat(delivery): the Application page + owner consoles (v2 phase A3-6)

The user-visible payoff of the whole delivery workstream.

- (app)/app: four tabs — pipeline (runs + the eight steps + a live console),
  releases (with demo rollback), production (target summary, confirmed deploy,
  history), Git. The header answers "is it live": demo URL, live release,
  health state.
- Live console: batches onto an animation frame, stops following the tail the
  moment the user scrolls away from the bottom, strips ANSI colour codes and
  carriage-return redraws. A console that yanks the view back is worse than none.
- Production panel: the target is shown as the operator recognises their server
  plus has_secret/ثبت شده. No field anywhere could render a credential.
- contracts: the v2 delivery types and seven new WsEventType members.
- /admin/sandbox (owner): per-role container counts, running containers, per-org
  limits, image mapping, stop with the id typed back (audited). A provider without
  the capability reports an empty list rather than an error.
- /admin/domains (owner): every route with its owning app, attach/detach (host
  typed back), idempotent re-sync.
- /projects removed entirely, with a permanent redirect to /app.

Tests: 502 API (+3), 78 web (+11); ruff, mypy, tsc and lint clean.

Report: docs/plan/reports/agent-3-phase-6.md
This commit is contained in:
mjalmoudy94
2026-10-14 02:03:38 +03:30
parent 72408a0a4f
commit 3c8296e354
15 changed files with 2462 additions and 659 deletions
+200 -1
View File
@@ -795,7 +795,14 @@ export type WsEventType =
| "billing.wallet"
| "error.recorded"
| "audit.recorded"
| "worker.status";
| "worker.status"
// v2 delivery: CI/CD runs, releases and production deploys (org channel).
| "pipeline.queued"
| "pipeline.step"
| "pipeline.log"
| "pipeline.status"
| "release.live"
| "deploy.status";
/** One pushed event, as the browser receives it. */
export interface WsEvent<T = Record<string, unknown>> {
@@ -860,3 +867,195 @@ export interface WsSandboxCommandData {
duration_ms: number;
truncated: boolean;
}
/* -------------------------------------------------------------------------- */
/* v2 delivery (CI/CD, demo runtime, production deploy) */
/* -------------------------------------------------------------------------- */
export type PipelineRunStatus =
| "queued"
| "running"
| "succeeded"
| "failed"
| "cancelled"
| "superseded";
export type PipelineStepStatus = "pending" | "running" | "succeeded" | "failed" | "skipped";
/** The eight steps of a run, in the order the server executes them. */
export const PIPELINE_STEP_NAMES = [
"checkout",
"setup",
"build",
"test",
"package",
"deploy_demo",
"healthcheck",
"route",
] as const;
export interface PipelineStep {
id: string;
index: number;
name: string;
status: PipelineStepStatus;
exit_code: number | null;
log_tail: string;
duration_ms: number;
}
export interface PipelineRun {
id: string;
project_id: string;
branch: string;
sha: string;
trigger: "engine" | "poll" | "manual";
status: PipelineRunStatus;
error_summary: string;
started_at: string | null;
finished_at: string | null;
created_at: string;
}
export interface PipelineRunDetail extends PipelineRun {
steps: PipelineStep[];
}
export interface Release {
id: string;
project_id: string;
run_id: string;
sha: string;
is_demo_live: boolean;
created_at: string;
}
export interface AppRuntime {
project_id: string;
demo_host: string;
demo_url: string;
host_port: number | null;
status: "provisioning" | "live" | "degraded" | "stopped";
current_release_id: string | null;
updated_at: string;
}
export type ProductionDeployStatus =
| "queued"
| "running"
| "succeeded"
| "failed"
| "rolled_back";
/** The production target as a *user* sees it: never a secret, only a hint. */
export interface ProductionTarget {
kind: "ssh" | "sftp" | "ftp" | "ftps";
host: string;
port: number;
username: string;
auth_type: "password" | "key";
remote_path: string;
has_secret: boolean;
host_fingerprint: string | null;
last_tested_at: string | null;
last_test_ok: boolean | null;
last_test_message: string | null;
}
export interface ProductionDeploy {
id: string;
project_id: string;
kind: string;
host: string;
status: ProductionDeployStatus;
message: string;
log_tail: string;
release_id: string | null;
rolled_back_from: string | null;
started_at: string | null;
finished_at: string | null;
created_at: string;
}
export interface ProductionOverview {
target: ProductionTarget | null;
deploys: ProductionDeploy[];
}
/** Owner console: one managed preview route. */
export interface DomainRoute {
id: string;
host: string;
port: number;
status: string;
protected: boolean;
url: string;
organization_id: string;
project_id: string;
project_name: string;
created_at: string | null;
}
export interface SuggestedRoute {
label: string;
host: string;
port: number;
description: string;
enabled: boolean;
}
export interface DomainSyncResult {
ok: boolean;
message: string;
routes: number;
changed: boolean;
}
/* -- live event payloads (channel `org:<id>`) ------------------------------ */
export interface WsPipelineLogData {
run_id: string;
step: string;
lines: string[];
}
export interface WsPipelineStepData {
run_id: string;
step: string;
index: number;
status: PipelineStepStatus;
}
export interface WsPipelineStatusData {
run_id: string;
project_id: string;
status: PipelineRunStatus;
error?: string;
}
export interface WsPipelineQueuedData {
run_id: string;
project_id: string;
sha: string;
trigger: string;
superseded: number;
}
export interface WsReleaseLiveData {
project_id: string;
run_id?: string;
release_id?: string;
sha?: string;
demo_host?: string;
demo_url?: string;
rolled_back?: boolean;
status?: string;
}
export interface WsDeployStatusData {
project_id: string;
deploy_id: string;
kind: string;
status: ProductionDeployStatus;
message: string;
}