Files
headquarter/apps/web/src/api/config_profiles.ts
T
Alex Blank 090edf7ef6 feat: git mount URL validation with branch detection
- Add POST /config-profiles/validate-git-url endpoint:
  - Parses URL using existing parse_git_url utility
  - Suggests corrected URL for browser URLs
  - Runs git ls-remote --heads to verify reachability
  - Lists available branches from remote
  - Supports SSH key for private repos
  - Returns structured response: valid, suggested_url, branches,
    default_branch, error, error_code

- Update frontend GitMountEditor:
  - Add Check button next to URL field with loading state
  - Show validation result: valid (green), suggestion (yellow),
    invalid (red)
  - Suggestion includes Use this button to apply corrected URL
  - Branch field becomes dropdown when URL is validated,
    populated with remote branches
  - Mappings section disabled until URL is validated
  - Shows hint: Validate the URL first

- Quality gates: pytest (218 passed, 6 pre-existing),
  tsc --noEmit (clean)
2026-05-29 12:15:30 +02:00

190 lines
4.5 KiB
TypeScript

import { apiClient } from "./client";
export interface ConfigProfile {
id: string;
user_id: string;
name: string;
description: string | null;
project_id: string | null;
tool_type_id: string | null;
env_vars: Record<string, string>;
runtime_hints: Record<string, unknown>;
mounts: ConfigProfileMount[];
git_mounts: GitMount[];
files: Record<string, string>;
is_default: boolean;
includes: ConfigProfileInclude[];
created_at: string;
updated_at: string;
}
export interface ConfigProfileMount {
target: string;
mode: "ro" | "rw";
files: Record<string, string>;
}
export interface GitMountMapping {
source_path: string;
target_path: string;
}
export interface GitMount {
remote_url: string;
branch?: string;
mappings: GitMountMapping[];
// Legacy fields (for backward compatibility when reading old data)
source_path?: string;
target_path?: string;
}
export interface ConfigProfileInclude {
id: string;
included_profile_id: string;
order_index: number;
}
export interface ResolvedProfile {
profile_id: string;
profile_name: string;
env_vars: Record<string, string>;
runtime_hints: Record<string, unknown>;
mounts: ResolvedMount[];
git_mounts: GitMount[];
files: Record<string, string>;
overrides: {
env_vars: Record<string, string>;
runtime_hints: Record<string, string>;
files: Record<string, string>;
mounts: Record<string, string>;
};
included_profiles: Array<{ id: string; name: string }>;
}
export interface ResolvedMount {
target: string;
mode: "ro" | "rw";
files: Record<string, string>;
overridden_files: Record<string, string>;
}
export interface CreateConfigProfileRequest {
name: string;
description?: string;
project_id?: string;
tool_type_id?: string;
env_vars?: Record<string, string>;
runtime_hints?: Record<string, unknown>;
mounts?: ConfigProfileMount[];
git_mounts?: GitMount[];
files?: Record<string, string>;
is_default?: boolean;
}
export interface UpdateConfigProfileRequest {
name?: string;
description?: string;
project_id?: string;
tool_type_id?: string;
env_vars?: Record<string, string>;
runtime_hints?: Record<string, unknown>;
mounts?: ConfigProfileMount[];
git_mounts?: GitMount[];
files?: Record<string, string>;
is_default?: boolean;
}
export interface UpdateIncludesRequest {
includes: string[];
}
export const listConfigProfiles = async (
projectId?: string,
toolTypeId?: string,
): Promise<ConfigProfile[]> => {
const response = await apiClient.get<ConfigProfile[]>("/config-profiles", {
params: { project_id: projectId, tool_type_id: toolTypeId },
});
return response.data;
};
export const getConfigProfile = async (id: string): Promise<ConfigProfile> => {
const response = await apiClient.get<ConfigProfile>(`/config-profiles/${id}`);
return response.data;
};
export const createConfigProfile = async (
data: CreateConfigProfileRequest,
): Promise<ConfigProfile> => {
const response = await apiClient.post<ConfigProfile>(
"/config-profiles",
data,
);
return response.data;
};
export const updateConfigProfile = async (
id: string,
data: UpdateConfigProfileRequest,
): Promise<ConfigProfile> => {
const response = await apiClient.put<ConfigProfile>(
`/config-profiles/${id}`,
data,
);
return response.data;
};
export const deleteConfigProfile = async (id: string): Promise<void> => {
await apiClient.delete(`/config-profiles/${id}`);
};
export const updateProfileIncludes = async (
id: string,
data: UpdateIncludesRequest,
): Promise<ConfigProfile> => {
const response = await apiClient.put<ConfigProfile>(
`/config-profiles/${id}/includes`,
data,
);
return response.data;
};
export const previewConfigProfile = async (
id: string,
): Promise<ResolvedProfile> => {
const response = await apiClient.get<ResolvedProfile>(
`/config-profiles/${id}/preview`,
);
return response.data;
};
export const resolveDefaultProfile = async (
projectId: string,
toolTypeId: string,
): Promise<{ profile_id: string | null; profile_name: string | null }> => {
const response = await apiClient.get("/config-profiles/defaults/resolve", {
params: { project_id: projectId, tool_type_id: toolTypeId },
});
return response.data;
};
export interface ValidateGitUrlResponse {
valid: boolean;
suggested_url?: string;
branches?: string[];
default_branch?: string;
error?: string;
error_code?: string;
}
export const validateGitUrl = async (
url: string,
sshKeyId?: string,
): Promise<ValidateGitUrlResponse> => {
const response = await apiClient.post<ValidateGitUrlResponse>(
"/config-profiles/validate-git-url",
{ url, ssh_key_id: sshKeyId },
);
return response.data;
};