fix: workspace delete mixed-content error via top-level endpoint

- Add top-level DELETE /workspaces/{workspace_id} endpoint (avoids nested path)
- Frontend deleteWorkspace now uses /workspaces/{id}/?force=... (no project/repo needed)
- Update useWorkspaceActions, ProjectsPage, WorkspacesPage to match new signature

Quality gates: ruff clean, tsc --noEmit clean, pytest workspaces API (9 passed, 1 skipped)
This commit is contained in:
2026-06-01 20:03:24 +02:00
parent 9a036f1968
commit 95efa5d029
6 changed files with 46 additions and 24 deletions
+1 -3
View File
@@ -71,13 +71,11 @@ export async function updateWorkspace(
}
export async function deleteWorkspace(
projectId: string,
repoId: string,
workspaceId: string,
force = false,
): Promise<{ status: string }> {
const response = await apiClient.delete<{ status: string }>(
`${workspaceUrl(projectId, repoId, workspaceId)}?force=${force}`,
`/workspaces/${workspaceId}/?force=${force}`,
);
return response.data;
}
+3 -7
View File
@@ -17,8 +17,6 @@ export interface UseWorkspaceActionsResult {
data: CreateWorkspaceRequest,
) => Promise<Workspace>;
delete: (
projectId: string,
repoId: string,
workspace: Workspace,
onRefresh: () => Promise<void>,
) => Promise<void>;
@@ -61,14 +59,12 @@ export function useWorkspaceActions(): UseWorkspaceActionsResult {
const deleteAction = useCallback(
async (
projectId: string,
repoId: string,
workspace: Workspace,
onRefresh: () => Promise<void>,
) => {
setLoadingId(workspace.id);
try {
await deleteWorkspace(projectId, repoId, workspace.id);
await deleteWorkspace(workspace.id);
await onRefresh();
} catch (err) {
const error = err as ApiError;
@@ -81,7 +77,7 @@ export function useWorkspaceActions(): UseWorkspaceActionsResult {
`\n\nDelete workspace and all instances?`,
);
if (confirmed) {
await deleteWorkspace(projectId, repoId, workspace.id, true);
await deleteWorkspace(workspace.id, true);
await onRefresh();
}
} else {
@@ -118,7 +114,7 @@ export function useWorkspaceActions(): UseWorkspaceActionsResult {
`${message}\n\nDelete this workspace?`,
);
if (confirmed) {
await deleteWorkspace(projectId, repoId, workspace.id, true);
await deleteWorkspace(workspace.id, true);
await onRefresh();
}
} else {
+3 -7
View File
@@ -124,15 +124,11 @@ export const ProjectsPage = () => {
}
};
const handleDeleteWorkspace = async (
projectId: string,
repoId: string,
workspace: WorkspaceSummary,
) => {
const handleDeleteWorkspace = async (workspace: WorkspaceSummary) => {
if (!confirm(`Delete workspace "${workspace.name}"?`)) return;
setWorkspaceLoading(workspace.id);
try {
await deleteWorkspace(projectId, repoId, workspace.id);
await deleteWorkspace(workspace.id);
reload();
} catch (err) {
alert(err instanceof Error ? err.message : "Failed to delete workspace");
@@ -187,7 +183,7 @@ export const ProjectsPage = () => {
if (action === "sync") {
void handleSyncWorkspace(project.id, repoId, workspace);
} else if (action === "delete") {
void handleDeleteWorkspace(project.id, repoId, workspace);
void handleDeleteWorkspace(workspace);
}
}}
workspaceLoading={workspaceLoading}
+1 -6
View File
@@ -18,12 +18,7 @@ export function WorkspacesPage() {
const actions = useWorkspaceActions();
const handleDelete = async (workspace: Workspace) => {
await actions.delete(
workspace.project_id,
workspace.repo_id,
workspace,
refresh,
);
await actions.delete(workspace, refresh);
};
const handleSync = async (workspace: Workspace) => {