feat: Tool Workshop manifest editor (PR 2)

- Add tool_definitions API client with types for manifests
- Add ManifestEditor component: base image selector, package editors
  (apt/npm/pip/node), script editors (build/startup), mount schema
  designer, runtime config, and live preview panel
- Integrate ManifestEditor into Tool Workshop as 'Manifest (Declarative)'
  definition type alongside Compose and Dockerfile
- Update ToolType API types to include manifest_id and 'manifest'
  definition_type
- Frontend builds clean, TypeScript typecheck passes
This commit is contained in:
Alex Blank
2026-05-28 14:35:04 +02:00
parent 5deee8c65c
commit e46b4f9249
6 changed files with 3006 additions and 1524 deletions
+108
View File
@@ -0,0 +1,108 @@
import { apiClient } from "./client";
export interface ToolDefinitionManifest {
id: string;
name: string;
display_name: string;
description: string | null;
category: string | null;
interface_type: string;
base_image: string | null;
base_definition_id: string | null;
base_version: string;
manifest: Record<string, unknown>;
dockerfile_cache: string | null;
compose_cache: string | null;
version: string;
is_base: boolean;
created_at: string;
updated_at: string;
}
export interface CreateToolDefinitionRequest {
name: string;
display_name: string;
description?: string;
category?: string;
interface_type?: string;
base_image?: string;
base_definition_id?: string;
base_version?: string;
manifest: Record<string, unknown>;
}
export interface UpdateToolDefinitionRequest {
display_name?: string;
description?: string;
category?: string;
manifest?: Record<string, unknown>;
base_version?: string;
}
export interface CompileResult {
id: string;
name: string;
dockerfile: string;
entrypoint: string;
compose: string;
image_tag: string;
}
export const listToolDefinitions = async (
includeBases = true,
): Promise<ToolDefinitionManifest[]> => {
const response = await apiClient.get<{
definitions: ToolDefinitionManifest[];
}>("/tool-definitions", {
params: { include_bases: includeBases },
});
return response.data.definitions;
};
export const getToolDefinition = async (
id: string,
): Promise<ToolDefinitionManifest> => {
const response = await apiClient.get<ToolDefinitionManifest>(
`/tool-definitions/${id}`,
);
return response.data;
};
export const createToolDefinition = async (
data: CreateToolDefinitionRequest,
): Promise<ToolDefinitionManifest> => {
const response = await apiClient.post<ToolDefinitionManifest>(
"/tool-definitions",
data,
);
return response.data;
};
export const updateToolDefinition = async (
id: string,
data: UpdateToolDefinitionRequest,
): Promise<ToolDefinitionManifest> => {
const response = await apiClient.put<ToolDefinitionManifest>(
`/tool-definitions/${id}`,
data,
);
return response.data;
};
export const deleteToolDefinition = async (
id: string,
): Promise<{ status: string; id: string }> => {
const response = await apiClient.delete<{ status: string; id: string }>(
`/tool-definitions/${id}`,
);
return response.data;
};
export const compileToolDefinition = async (
id: string,
): Promise<CompileResult> => {
const response = await apiClient.post<CompileResult>(
`/tool-definitions/${id}/compile`,
);
return response.data;
};
+74 -62
View File
@@ -1,90 +1,102 @@
import { apiClient } from "./client";
export interface ReadinessProbe {
command: string;
timeout: number;
interval: number;
command: string;
timeout: number;
interval: number;
}
export interface ToolType {
id: string;
name: string;
display_name: string;
description: string | null;
category: string;
interface_type: string;
requires_port: boolean;
default_port: number | null;
definition_type: 'compose' | 'dockerfile';
compose_template: string | null;
dockerfile_template: string | null;
build_context: Record<string, string> | null;
readiness_probe: ReadinessProbe | null;
startup_command: string | null;
required_variables: string[];
created_by_id: string | null;
created_at: string;
updated_at: string;
id: string;
name: string;
display_name: string;
description: string | null;
category: string;
interface_type: string;
requires_port: boolean;
default_port: number | null;
definition_type: "compose" | "dockerfile" | "manifest";
manifest_id: string | null;
compose_template: string | null;
dockerfile_template: string | null;
build_context: Record<string, string> | null;
readiness_probe: ReadinessProbe | null;
startup_command: string | null;
required_variables: string[];
created_by_id: string | null;
created_at: string;
updated_at: string;
}
export interface CreateToolTypeRequest {
name: string;
display_name: string;
description?: string;
category?: string;
interface_type?: string;
requires_port?: boolean;
default_port: number;
definition_type?: 'compose' | 'dockerfile';
compose_template?: string;
dockerfile_template?: string;
build_context?: Record<string, string>;
readiness_probe?: ReadinessProbe;
startup_command?: string;
required_variables: string[];
name: string;
display_name: string;
description?: string;
category?: string;
interface_type?: string;
requires_port?: boolean;
default_port: number;
definition_type?: "compose" | "dockerfile" | "manifest";
manifest_id?: string;
compose_template?: string;
dockerfile_template?: string;
build_context?: Record<string, string>;
readiness_probe?: ReadinessProbe;
startup_command?: string;
required_variables: string[];
}
export interface UpdateToolTypeRequest {
display_name?: string;
description?: string;
category?: string;
interface_type?: string;
requires_port?: boolean;
default_port?: number;
definition_type?: 'compose' | 'dockerfile';
compose_template?: string;
dockerfile_template?: string;
build_context?: Record<string, string>;
readiness_probe?: ReadinessProbe;
startup_command?: string;
required_variables?: string[];
display_name?: string;
description?: string;
category?: string;
interface_type?: string;
requires_port?: boolean;
default_port?: number;
definition_type?: "compose" | "dockerfile" | "manifest";
manifest_id?: string;
compose_template?: string;
dockerfile_template?: string;
build_context?: Record<string, string>;
readiness_probe?: ReadinessProbe;
startup_command?: string;
required_variables?: string[];
}
export const listToolTypes = async (): Promise<ToolType[]> => {
const response = await apiClient.get<ToolType[]>("/tool-types");
return response.data;
const response = await apiClient.get<ToolType[]>("/tool-types");
return response.data;
};
export const getToolType = async (id: string): Promise<ToolType> => {
const response = await apiClient.get<ToolType>(`/tool-types/${id}`);
return response.data;
const response = await apiClient.get<ToolType>(`/tool-types/${id}`);
return response.data;
};
export const createToolType = async (data: CreateToolTypeRequest): Promise<ToolType> => {
const response = await apiClient.post<ToolType>("/tool-types", data);
return response.data;
export const createToolType = async (
data: CreateToolTypeRequest,
): Promise<ToolType> => {
const response = await apiClient.post<ToolType>("/tool-types", data);
return response.data;
};
export const updateToolType = async (id: string, data: UpdateToolTypeRequest): Promise<ToolType> => {
const response = await apiClient.put<ToolType>(`/tool-types/${id}`, data);
return response.data;
export const updateToolType = async (
id: string,
data: UpdateToolTypeRequest,
): Promise<ToolType> => {
const response = await apiClient.put<ToolType>(`/tool-types/${id}`, data);
return response.data;
};
export const deleteToolType = async (id: string): Promise<void> => {
await apiClient.delete(`/tool-types/${id}`);
await apiClient.delete(`/tool-types/${id}`);
};
export const validateToolType = async (id: string): Promise<{ valid: boolean; errors?: string[] }> => {
const response = await apiClient.get<{ valid: boolean; errors?: string[] }>(`/tool-types/${id}/validate`);
return response.data;
export const validateToolType = async (
id: string,
): Promise<{ valid: boolean; errors?: string[] }> => {
const response = await apiClient.get<{ valid: boolean; errors?: string[] }>(
`/tool-types/${id}/validate`,
);
return response.data;
};