mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
docs(api): bring OpenAPI spec to route parity, dedupe client types
openapi.yaml: 29 -> 114 paths / 154 operations covering every mounted route, including root probes (/live, /health, /ready) and /api/agents/* token routes via per-path servers overrides. Schemas corrected against the real Go structs (Service, GitProvider/GitRepository/GitBranch, NodeAgent capabilities/resources, HostMonitoring, UpgradeStatus, service metrics, project stats); stale source/build_config contract and phantom ServiceSource/BuildConfig/ResourceConfig schemas removed. Proxmox handlers are never registered - intentionally undocumented. api-client.ts: raw wire types now alias generated schemas (ServiceVariable, UpgradeStatus, HostMonitoring, NodeAgent, Service, ServiceMetrics, Git*); CreateServiceInput/CreateUserInput use the generated request types. CamelCase view models and normalizers remain - they are the frontend contract, not duplication. Frontend gate green: tsc, eslint, vitest, vite build. Authored By: TDvorak <[email protected]>
This commit is contained in:
1 parent
aef9eeeff8
commit
93e3074477
5 files changed
+9561
-389
No files matched your search
+12
-7
@@ -68,10 +68,12 @@ The backend exposes **~90 route registrations**; the frontend consumes roughly
|
|||||||
- ~~**React Flow edge warnings**~~ — **fixed**: `ServiceNode` now renders
|
- ~~**React Flow edge warnings**~~ — **fixed**: `ServiceNode` now renders
|
||||||
invisible `Handle` components (target left, source right), so inferred
|
invisible `Handle` components (target left, source right), so inferred
|
||||||
auto-connections attach without `#008` warnings.
|
auto-connections attach without `#008` warnings.
|
||||||
- **OpenAPI drift**: `docs/api/openapi.yaml` documents 29 paths; the router
|
- ~~**OpenAPI drift**~~ — **resolved**: spec now covers all 153 mounted
|
||||||
registers ~90. The generated `api-types.ts` therefore covers a fraction of
|
operations (114 path items, 154 operations incl. root probes). Schemas
|
||||||
the real surface, and `api-client.ts` hand-duplicates entity types on top —
|
were corrected against the real Go structs (Service, Git*, NodeAgent,
|
||||||
a direct violation of the "never duplicate API types" rule.
|
HostMonitoring, UpgradeStatus, metrics); stale `source`/`build_config`
|
||||||
|
contract removed. `api-types.ts` regenerated; `api-client.ts` raw types
|
||||||
|
now alias generated schemas — camelCase normalizers stay by design.
|
||||||
- **Two DB access patterns remain**: GORM is gone (agents handler ported to
|
- **Two DB access patterns remain**: GORM is gone (agents handler ported to
|
||||||
sqlc), but most handlers still hand-write `database/sql` — ~140 call sites
|
sqlc), but most handlers still hand-write `database/sql` — ~140 call sites
|
||||||
across ~14 files. Porting them to `sqlc/queries/` is mechanical but large;
|
across ~14 files. Porting them to `sqlc/queries/` is mechanical but large;
|
||||||
@@ -168,9 +170,12 @@ Everything else builds on a truthful API contract and a clean tree.
|
|||||||
dependency dropped, agent queries in `sqlc/queries/agents.sql`.
|
dependency dropped, agent queries in `sqlc/queries/agents.sql`.
|
||||||
Remaining hand-written `database/sql` in other handlers (~140 sites)
|
Remaining hand-written `database/sql` in other handlers (~140 sites)
|
||||||
is tracked as a follow-up pass — sqlc is the target for all new code.
|
is tracked as a follow-up pass — sqlc is the target for all new code.
|
||||||
- [ ] Bring `docs/api/openapi.yaml` to parity with `routes.go` (all ~90
|
- [x] Bring `docs/api/openapi.yaml` to parity with `routes.go`: 114 paths /
|
||||||
paths), then regenerate `api-types.ts` and delete the hand-written
|
154 operations cover every mounted route (incl. `/live`, `/health`,
|
||||||
entity types in `api-client.ts`.
|
`/ready`, `/api/agents/*` token routes with server overrides; Proxmox
|
||||||
|
handlers are registered nowhere, intentionally undocumented).
|
||||||
|
`api-types.ts` regenerated; `api-client.ts` contract types deduped to
|
||||||
|
generated schemas — normalized view models kept.
|
||||||
- [x] Fix auto-connection edges — `ServiceNode` lacked `Handle` components;
|
- [x] Fix auto-connection edges — `ServiceNode` lacked `Handle` components;
|
||||||
invisible target/source handles added in `nodes.tsx`.
|
invisible target/source handles added in `nodes.tsx`.
|
||||||
- [x] Rerun desloppify; record score. 2026-09-17 scan (`ref/` excluded):
|
- [x] Rerun desloppify; record score. 2026-09-17 scan (`ref/` excluded):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const demoProjects: ProjectEntity[] = [
|
|||||||
description: 'Sample project with mock services for UI preview.',
|
description: 'Sample project with mock services for UI preview.',
|
||||||
createdAt: new Date(Date.now() - 14 * 86_400_000).toISOString(),
|
createdAt: new Date(Date.now() - 14 * 86_400_000).toISOString(),
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
stats: { service_count: 5, deployment_count: 12, running_services: 5 },
|
stats: { service_count: 5, deployment_count: 12, running_services: 5, last_deployment: null },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'project-staging',
|
id: 'project-staging',
|
||||||
@@ -30,7 +30,7 @@ const demoProjects: ProjectEntity[] = [
|
|||||||
description: 'Pre-production environment for testing new releases.',
|
description: 'Pre-production environment for testing new releases.',
|
||||||
createdAt: new Date(Date.now() - 7 * 86_400_000).toISOString(),
|
createdAt: new Date(Date.now() - 7 * 86_400_000).toISOString(),
|
||||||
updatedAt: new Date(Date.now() - 2 * 86_400_000).toISOString(),
|
updatedAt: new Date(Date.now() - 2 * 86_400_000).toISOString(),
|
||||||
stats: { service_count: 2, deployment_count: 5, running_services: 1 },
|
stats: { service_count: 2, deployment_count: 5, running_services: 1, last_deployment: null },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -2,12 +2,7 @@ import type { components, paths } from '@/generated/api-types';
|
|||||||
|
|
||||||
export type ServiceStatus = 'running' | 'stopped' | 'building' | 'failed' | 'unknown';
|
export type ServiceStatus = 'running' | 'stopped' | 'building' | 'failed' | 'unknown';
|
||||||
|
|
||||||
export type ProjectStats = {
|
export type ProjectStats = components['schemas']['ProjectStats'];
|
||||||
service_count: number;
|
|
||||||
deployment_count: number;
|
|
||||||
running_services: number;
|
|
||||||
last_deployment?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ProjectEntity = {
|
export type ProjectEntity = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -148,11 +143,7 @@ export type UpdateUserProfileInput = {
|
|||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CreateUserInput = {
|
export type CreateUserInput = components['schemas']['ManualUserCreateRequest'];
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UpgradeStatus = {
|
export type UpgradeStatus = {
|
||||||
imageRef: string;
|
imageRef: string;
|
||||||
@@ -310,34 +301,11 @@ export type GetDeploymentLogsInput = {
|
|||||||
type?: 'all' | 'build' | 'runtime';
|
type?: 'all' | 'build' | 'runtime';
|
||||||
};
|
};
|
||||||
|
|
||||||
type RawProject = components['schemas']['Project'] & {
|
type RawProject = components['schemas']['Project'];
|
||||||
stats?: Partial<ProjectStats>;
|
type RawService = components['schemas']['Service'];
|
||||||
};
|
type RawUserProfile = components['schemas']['User'];
|
||||||
|
|
||||||
type RawService = components['schemas']['Service'] & {
|
type RawServiceVariable = components['schemas']['ServiceVariable'];
|
||||||
image?: string;
|
|
||||||
command?: string;
|
|
||||||
environment?: string;
|
|
||||||
git_repo?: string;
|
|
||||||
git_branch?: string;
|
|
||||||
build_path?: string;
|
|
||||||
cpu?: string;
|
|
||||||
memory?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RawUserProfile = components['schemas']['User'] & {
|
|
||||||
avatar_url?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RawServiceVariable = {
|
|
||||||
id?: string;
|
|
||||||
service_id?: string;
|
|
||||||
key?: string;
|
|
||||||
value?: string;
|
|
||||||
is_secret?: boolean;
|
|
||||||
created_at?: string;
|
|
||||||
updated_at?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RawBuildStatus = components['schemas']['BuildStatus'];
|
type RawBuildStatus = components['schemas']['BuildStatus'];
|
||||||
type RawBuildListResponse = components['schemas']['BuildListResponse'];
|
type RawBuildListResponse = components['schemas']['BuildListResponse'];
|
||||||
@@ -355,104 +323,13 @@ type RawServiceLog = components['schemas']['ServiceLogEntry'];
|
|||||||
type RawServiceLogsResponse = components['schemas']['ServiceLogsResponse'];
|
type RawServiceLogsResponse = components['schemas']['ServiceLogsResponse'];
|
||||||
type RawDeploymentLogsResponse = components['schemas']['DeploymentLogsResponse'];
|
type RawDeploymentLogsResponse = components['schemas']['DeploymentLogsResponse'];
|
||||||
type RawRollbackDeploymentResponse = components['schemas']['RollbackDeploymentResponse'];
|
type RawRollbackDeploymentResponse = components['schemas']['RollbackDeploymentResponse'];
|
||||||
type RawUpgradeStatus = {
|
type RawUpgradeStatus = components['schemas']['UpgradeStatus'];
|
||||||
image_ref?: string;
|
type RawHostMonitoring = components['schemas']['HostMonitoring'];
|
||||||
registry?: string;
|
type RawSystemLoad = NonNullable<RawHostMonitoring['load']>;
|
||||||
docker_available?: boolean;
|
type RawNodeAgent = components['schemas']['NodeAgent'];
|
||||||
installed?: boolean;
|
|
||||||
digest?: string;
|
|
||||||
size?: number;
|
|
||||||
auth_configured?: boolean;
|
|
||||||
message?: string;
|
|
||||||
};
|
|
||||||
type RawSystemLoad = {
|
|
||||||
load_1m?: number;
|
|
||||||
load_5m?: number;
|
|
||||||
load_15m?: number;
|
|
||||||
};
|
|
||||||
type RawHostMonitoring = {
|
|
||||||
hostname?: string;
|
|
||||||
os?: string;
|
|
||||||
architecture?: string;
|
|
||||||
cpu?: {
|
|
||||||
cores?: number;
|
|
||||||
};
|
|
||||||
memory?: {
|
|
||||||
total?: number;
|
|
||||||
used?: number;
|
|
||||||
available?: number;
|
|
||||||
usage_percent?: number;
|
|
||||||
};
|
|
||||||
storage?: {
|
|
||||||
path?: string;
|
|
||||||
total?: number;
|
|
||||||
used?: number;
|
|
||||||
available?: number;
|
|
||||||
usage_percent?: number;
|
|
||||||
};
|
|
||||||
load?: RawSystemLoad;
|
|
||||||
uptime_seconds?: number;
|
|
||||||
docker_available?: boolean;
|
|
||||||
docker?: {
|
|
||||||
containers?: number;
|
|
||||||
images?: number;
|
|
||||||
driver?: string;
|
|
||||||
server?: string;
|
|
||||||
};
|
|
||||||
collected_at?: string;
|
|
||||||
};
|
|
||||||
type RawNodeAgent = {
|
|
||||||
id?: string;
|
|
||||||
name?: string;
|
|
||||||
hostname?: string;
|
|
||||||
ip_address?: string;
|
|
||||||
port?: number;
|
|
||||||
status?: string;
|
|
||||||
version?: string;
|
|
||||||
capabilities?: {
|
|
||||||
container_runtimes?: string[];
|
|
||||||
supported_architectures?: string[];
|
|
||||||
max_containers?: number;
|
|
||||||
storage_driver?: string;
|
|
||||||
network_plugins?: string[];
|
|
||||||
features?: string[];
|
|
||||||
};
|
|
||||||
resources?: {
|
|
||||||
cpu?: {
|
|
||||||
cores?: number;
|
|
||||||
allocation?: number;
|
|
||||||
usage?: number;
|
|
||||||
};
|
|
||||||
memory?: {
|
|
||||||
total?: number;
|
|
||||||
allocated?: number;
|
|
||||||
used?: number;
|
|
||||||
available?: number;
|
|
||||||
};
|
|
||||||
storage?: {
|
|
||||||
total?: number;
|
|
||||||
allocated?: number;
|
|
||||||
used?: number;
|
|
||||||
available?: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
last_heartbeat?: string;
|
|
||||||
created_at?: string;
|
|
||||||
updated_at?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CreateProjectInput = components['schemas']['CreateProjectRequest'];
|
export type CreateProjectInput = components['schemas']['CreateProjectRequest'];
|
||||||
export type CreateServiceInput = components['schemas']['CreateServiceRequest'] & {
|
export type CreateServiceInput = components['schemas']['CreateServiceRequest'];
|
||||||
environment?: 'production' | 'preview' | 'development';
|
|
||||||
project_id?: string;
|
|
||||||
image?: string;
|
|
||||||
command?: string;
|
|
||||||
git_repo?: string;
|
|
||||||
git_branch?: string;
|
|
||||||
build_path?: string;
|
|
||||||
cpu?: string;
|
|
||||||
memory?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ApiError extends Error {
|
export class ApiError extends Error {
|
||||||
readonly status: number;
|
readonly status: number;
|
||||||
@@ -543,10 +420,10 @@ function normalizeProject(project: RawProject): ProjectEntity | null {
|
|||||||
createdAt: project.created_at,
|
createdAt: project.created_at,
|
||||||
updatedAt: project.updated_at,
|
updatedAt: project.updated_at,
|
||||||
stats: {
|
stats: {
|
||||||
service_count: project.stats?.service_count ?? project.services_count ?? 0,
|
service_count: project.stats?.service_count ?? 0,
|
||||||
deployment_count: project.stats?.deployment_count ?? 0,
|
deployment_count: project.stats?.deployment_count ?? 0,
|
||||||
running_services: project.stats?.running_services ?? 0,
|
running_services: project.stats?.running_services ?? 0,
|
||||||
last_deployment: project.stats?.last_deployment ?? undefined,
|
last_deployment: project.stats?.last_deployment ?? null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1293,29 +1170,8 @@ export function serviceStatusClass(status: ServiceStatus): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ServiceInstanceMetrics = {
|
export type ServiceInstanceMetrics = components['schemas']['ServiceInstanceMetrics'];
|
||||||
container_id: string;
|
export type ServiceMetrics = components['schemas']['ServiceMetrics'];
|
||||||
name: string;
|
|
||||||
state: string;
|
|
||||||
cpu_percent: number;
|
|
||||||
memory_usage_bytes: number;
|
|
||||||
memory_limit_bytes: number;
|
|
||||||
network_rx_bytes: number;
|
|
||||||
network_tx_bytes: number;
|
|
||||||
started_at?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ServiceMetrics = {
|
|
||||||
service_id: string;
|
|
||||||
status: 'ok' | 'no_containers' | 'docker_unavailable';
|
|
||||||
instances: ServiceInstanceMetrics[];
|
|
||||||
cpu_percent: number;
|
|
||||||
memory_usage_bytes: number;
|
|
||||||
memory_limit_bytes: number;
|
|
||||||
network_rx_bytes: number;
|
|
||||||
network_tx_bytes: number;
|
|
||||||
collected_at: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function getServiceMetrics(serviceId: string): Promise<ServiceMetrics> {
|
export async function getServiceMetrics(serviceId: string): Promise<ServiceMetrics> {
|
||||||
const payload = await requestJson<{ metrics?: ServiceMetrics }>(`/services/${serviceId}/metrics`);
|
const payload = await requestJson<{ metrics?: ServiceMetrics }>(`/services/${serviceId}/metrics`);
|
||||||
@@ -1325,41 +1181,17 @@ export async function getServiceMetrics(serviceId: string): Promise<ServiceMetri
|
|||||||
return payload.metrics;
|
return payload.metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GitProviderEntity = {
|
export type GitProviderEntity = components['schemas']['GitProvider'];
|
||||||
id: string;
|
export type GitRepositoryEntity = components['schemas']['GitRepository'];
|
||||||
name: string;
|
export type GitBranchEntity = components['schemas']['GitBranch'];
|
||||||
display_name: string;
|
export type CreateGitProviderInput = components['schemas']['CreateGitProviderRequest'];
|
||||||
api_url: string;
|
|
||||||
created_at?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GitRepositoryEntity = {
|
|
||||||
id: string;
|
|
||||||
provider_id: string;
|
|
||||||
name: string;
|
|
||||||
full_name: string;
|
|
||||||
description: string;
|
|
||||||
clone_url: string;
|
|
||||||
default_branch: string;
|
|
||||||
is_private: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GitBranchEntity = {
|
|
||||||
name: string;
|
|
||||||
protected: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function listGitProviders(): Promise<GitProviderEntity[]> {
|
export async function listGitProviders(): Promise<GitProviderEntity[]> {
|
||||||
const payload = await requestJson<{ providers?: GitProviderEntity[] }>('/git/providers');
|
const payload = await requestJson<{ providers?: GitProviderEntity[] }>('/git/providers');
|
||||||
return payload.providers ?? [];
|
return payload.providers ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createGitProvider(input: {
|
export async function createGitProvider(input: CreateGitProviderInput): Promise<GitProviderEntity> {
|
||||||
name: 'github' | 'gitlab' | 'bitbucket' | 'gitea';
|
|
||||||
display_name: string;
|
|
||||||
access_token: string;
|
|
||||||
api_url?: string;
|
|
||||||
}): Promise<GitProviderEntity> {
|
|
||||||
const payload = await requestJson<GitProviderEntity>('/git/providers', {
|
const payload = await requestJson<GitProviderEntity>('/git/providers', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(input),
|
body: JSON.stringify(input),
|
||||||
|
|||||||
+3991
-95
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user