fix: add top-level GET /workspaces endpoint and derive project/repo from workspace data

- Add all_workspaces_router with GET /workspaces/ (no project/repo required)
- Include project_id in workspace responses
- Frontend: useWorkspaces() calls listAllWorkspaces when no args
- Frontend: WorkspacesPage uses top-level list, derives project/repo from workspace for mutations
- Fixes 422 from invalid UUID path params
This commit is contained in:
2026-06-01 00:20:19 +02:00
parent b05de96569
commit 59b125d8e2
6 changed files with 93 additions and 35 deletions
+29 -30
View File
@@ -13,29 +13,25 @@ import type { Workspace } from "../types/workspace";
export function WorkspacesPage() {
const [showCreate, setShowCreate] = useState(false);
const [startWorkspace, setStartWorkspace] = useState<Workspace | null>(null);
const [createTarget, setCreateTarget] = useState<{ projectId: string; repoId: string } | null>(null);
// TODO: Get projectId and repoId from URL params or context
const projectId = "default-project";
const repoId = "default-repo";
const { workspaces, loading, error, refresh } = useWorkspaces(
projectId,
repoId,
);
const { workspaces, loading, error, refresh } = useWorkspaces();
const actions = useWorkspaceActions();
const handleCreate = async (data: { name: string; branch: string }) => {
await actions.create(projectId, repoId, data);
if (!createTarget) return;
await actions.create(createTarget.projectId, createTarget.repoId, data);
setShowCreate(false);
setCreateTarget(null);
await refresh();
};
const handleDelete = async (workspace: Workspace) => {
await actions.delete(projectId, repoId, workspace, refresh);
await actions.delete(workspace.project_id, workspace.repo_id, workspace, refresh);
};
const handleSync = async (workspace: Workspace) => {
await actions.sync(projectId, repoId, workspace, refresh);
await actions.sync(workspace.project_id, workspace.repo_id, workspace, refresh);
};
const handleStartTool = async (
@@ -45,8 +41,8 @@ export function WorkspacesPage() {
if (!startWorkspace) return;
try {
const instance = await createInstance(
projectId,
repoId,
startWorkspace.project_id,
startWorkspace.repo_id,
toolTypeId,
`${startWorkspace.name} - ${toolTypeId}`,
undefined,
@@ -56,7 +52,7 @@ export function WorkspacesPage() {
[],
startWorkspace.id,
);
await startInstance(projectId, repoId, instance.id, configProfileId);
await startInstance(startWorkspace.project_id, startWorkspace.repo_id, instance.id, configProfileId);
setStartWorkspace(null);
await refresh();
} catch (err) {
@@ -76,23 +72,31 @@ export function WorkspacesPage() {
>
<Icon name="refresh" size="sm" />
</button>
<button
className="btn btn-primary"
onClick={() => setShowCreate(true)}
>
<Icon name="add" size="sm" /> New Workspace
</button>
<button
className="btn btn-primary"
onClick={() => {
if (workspaces.length > 0) {
const first = workspaces[0];
setCreateTarget({ projectId: first.project_id, repoId: first.repo_id });
setShowCreate(true);
} else {
alert("Navigate to a project to create your first workspace.");
}
}}
>
<Icon name="add" size="sm" /> New Workspace
</button>
</div>
</header>
{error && <div className="alert alert-error">{error}</div>}
{showCreate && (
{showCreate && createTarget && (
<WorkspaceCreateForm
projectId={projectId}
repoId={repoId}
projectId={createTarget.projectId}
repoId={createTarget.repoId}
onSubmit={handleCreate}
onCancel={() => setShowCreate(false)}
onCancel={() => { setShowCreate(false); setCreateTarget(null); }}
/>
)}
@@ -101,12 +105,7 @@ export function WorkspacesPage() {
) : workspaces.length === 0 ? (
<div className="empty-state">
<p>No workspaces yet.</p>
<button
className="btn btn-primary"
onClick={() => setShowCreate(true)}
>
<Icon name="add" size="sm" /> Create your first workspace
</button>
<p>Navigate to a project to create your first workspace.</p>
</div>
) : (
<div className="workspaces-grid">