feat: direct docker-compose import into projects

POST /projects/:id/import-compose deploys a compose file straight into a
project — no template row required. Well-known DB images become managed
databases attached to the project network under their service name as DNS
alias; other services deploy in dependency order via deployTemplateGraph.

- api: extract composeToSpecs converter (shared by the template
  import-compose preview and the new project endpoint); fix dropped env
  warnings (pointer receiver); replace insertion sort with sort.Strings
- cascade: database_services.project_id links graph-provisioned DBs to
  their project so project delete tears down runtime + row (previously
  leaked container + network)
- cli: new `containr import` group, `import compose <project> <file|->`
- mcp: containr_projects_import_compose tool
- openapi + regenerated frontend types; docs snapshot + CLI.md
- frontend: compose import modal on the project workspace toolbar
- tests: composeToSpecs conversion, ordering, db-image map, warnings
This commit is contained in:
Tomas Dvorak committed 2026-10-09 09:33:38 +02:00
1 parent dca8aaa7b3
commit 5c2f2861bd
20 files changed
+711 -60

No files matched your search

+19
View File
@@ -1748,6 +1748,25 @@ export async function importComposeTemplate(composeYaml: string): Promise<Compos
return { config: payload.config, warnings: payload.warnings ?? [] };
}
export type ProjectComposeImportResult = {
created: Array<{ key: string; kind: string; id: string; name: string; deployment_id?: string }>;
warnings: string[];
};
export async function importComposeIntoProject(
projectId: string,
composeYaml: string,
): Promise<ProjectComposeImportResult> {
const payload = await requestJson<{
created?: ProjectComposeImportResult['created'];
warnings?: string[];
}>(`/projects/${projectId}/import-compose`, {
method: 'POST',
body: JSON.stringify({ compose_yaml: composeYaml }),
});
return { created: payload.created ?? [], warnings: payload.warnings ?? [] };
}
export function serviceStatusClass(status: ServiceStatus): string {
switch (status) {
case 'running':