feat: consolidate database management work

Add catalog-owned logical relationships and runtime snapshots, extend the database-management UI and validation coverage, and document the updated operational workflow.

Keep active sensitive-generation status in a tooltip and indicator, and update the layout E2E to follow the history action in its new database-scoped location.
This commit is contained in:
Codex
2026-09-01 14:46:55 +02:00
parent f586152636
commit 076c9742c5
73 changed files with 6966 additions and 610 deletions
+61 -3
View File
@@ -3,6 +3,12 @@ import { joinBackendPath } from "./runtime-config";
export type DatabaseTransport = "postgres_direct" | "rest_api" | "ssh_tunnel";
export type ConnectionStatus = "untested" | "reachable" | "failed";
export type WorkspaceEvidenceState =
| "not_declared"
| "materialized_current_revision"
| "configuration_required"
| "configured_unverified"
| "workspace_unavailable";
export type CatalogSecretName =
| "password"
| "apiKey"
@@ -33,6 +39,16 @@ export interface CatalogDatabase {
workspaceName: string;
workspaceDescription?: string;
workspaceAvailable: boolean;
workspaceRevision: { commit: string; blob: string } | null;
workspaceEvidence: {
sourceType: "filesystem" | "http" | "s3" | null;
state: WorkspaceEvidenceState;
};
runtimeBinding: {
transport: DatabaseTransport;
configurationState: "ready" | "configuration_required";
sessionTransportSupported: boolean;
} | null;
configured: boolean;
engine: "postgres";
databaseName: string;
@@ -164,25 +180,37 @@ export interface CatalogRelationshipColumn {
targetColumnName: string;
}
export type CatalogRelationshipOrigin = "physical" | "generated" | "manual";
export type CatalogRelationshipStatus = "active" | "excluded";
export interface CatalogRelationship {
id: string;
databaseId: string;
constraintName: string;
constraintName: string | null;
sourceTableId: string;
sourceTableName: string;
targetTableId: string;
targetTableName: string;
updateRule: string;
deleteRule: string;
updateRule: string | null;
deleteRule: string | null;
deferrable: boolean;
initiallyDeferred: boolean;
columns: CatalogRelationshipColumn[];
origin: CatalogRelationshipOrigin;
status: CatalogRelationshipStatus;
lastSyncedDatabaseVersion: number | null;
lastSyncedAt: string | null;
createdAt: string;
updatedAt: string;
}
export interface CatalogRelationshipRebuildResult {
added: number;
alreadyPresent: number;
excluded: number;
ambiguous: number;
}
export type CatalogDatabaseMetadataDeleteTarget = "tables" | "relationships";
export type CatalogTableMetadataDeleteTarget = "columns" | "relationships";
@@ -452,6 +480,36 @@ export const listSensitiveDataSuggestionEvents = (runId: string, after = 0) =>
export const listCatalogRelationships = (databaseId: string) =>
apiFetch<CatalogRelationship[]>(`/catalog/databases/${encodeURIComponent(databaseId)}/relationships`);
export const createCatalogRelationship = (
databaseId: string,
sourceColumnId: string,
targetColumnId: string,
) => apiFetch<CatalogRelationship>(
`/catalog/databases/${encodeURIComponent(databaseId)}/relationships`,
{ method: "POST", body: JSON.stringify({ sourceColumnId, targetColumnId }) },
);
export const rebuildGeneratedRelationships = (databaseId: string) =>
apiFetch<CatalogRelationshipRebuildResult>(
`/catalog/databases/${encodeURIComponent(databaseId)}/relationships/rebuild-generated`,
{ method: "POST" },
);
export const setCatalogRelationshipStatus = (
databaseId: string,
relationshipId: string,
status: CatalogRelationshipStatus,
) => apiFetch<CatalogRelationship>(
`/catalog/databases/${encodeURIComponent(databaseId)}/relationships/${encodeURIComponent(relationshipId)}`,
{ method: "PATCH", body: JSON.stringify({ status }) },
);
export const deleteCatalogRelationship = (databaseId: string, relationshipId: string) =>
apiFetch<void>(
`/catalog/databases/${encodeURIComponent(databaseId)}/relationships/${encodeURIComponent(relationshipId)}`,
{ method: "DELETE" },
);
export const deleteCatalogDatabaseMetadata = (
databaseIds: string[],
target: CatalogDatabaseMetadataDeleteTarget,