feat: workspace-first UI refresh - PR-1 backend endpoints
- Add FileService for workspace-scoped file operations
- Add GitOperations service for workspace-scoped git commands
- Add workspace_files API: GET/POST /workspaces/{id}/files
- Add workspace_git API: status, branches, commit, push, pull, fetch, checkout, history
- Add workspace_instances API: list instances per workspace
- Add top-level POST /workspaces/ (accepts repo_id directly)
- Enrich GET /projects/ with nested repositories and workspaces
- Register all new routers in main.py
- 23 tests passing (17 existing + 6 new)
Quality gates: ruff clean
This commit is contained in:
@@ -13,7 +13,10 @@ 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);
|
||||
const [createTarget, setCreateTarget] = useState<{
|
||||
projectId: string;
|
||||
repoId: string;
|
||||
} | null>(null);
|
||||
|
||||
const { workspaces, loading, error, refresh } = useWorkspaces();
|
||||
const actions = useWorkspaceActions();
|
||||
@@ -27,11 +30,21 @@ export function WorkspacesPage() {
|
||||
};
|
||||
|
||||
const handleDelete = async (workspace: Workspace) => {
|
||||
await actions.delete(workspace.project_id, workspace.repo_id, workspace, refresh);
|
||||
await actions.delete(
|
||||
workspace.project_id,
|
||||
workspace.repo_id,
|
||||
workspace,
|
||||
refresh,
|
||||
);
|
||||
};
|
||||
|
||||
const handleSync = async (workspace: Workspace) => {
|
||||
await actions.sync(workspace.project_id, workspace.repo_id, workspace, refresh);
|
||||
await actions.sync(
|
||||
workspace.project_id,
|
||||
workspace.repo_id,
|
||||
workspace,
|
||||
refresh,
|
||||
);
|
||||
};
|
||||
|
||||
const handleStartTool = async (
|
||||
@@ -52,7 +65,12 @@ export function WorkspacesPage() {
|
||||
[],
|
||||
startWorkspace.id,
|
||||
);
|
||||
await startInstance(startWorkspace.project_id, startWorkspace.repo_id, instance.id, configProfileId);
|
||||
await startInstance(
|
||||
startWorkspace.project_id,
|
||||
startWorkspace.repo_id,
|
||||
instance.id,
|
||||
configProfileId,
|
||||
);
|
||||
setStartWorkspace(null);
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
@@ -72,20 +90,23 @@ export function WorkspacesPage() {
|
||||
>
|
||||
<Icon name="refresh" size="sm" />
|
||||
</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>
|
||||
<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>
|
||||
|
||||
@@ -96,7 +117,10 @@ export function WorkspacesPage() {
|
||||
projectId={createTarget.projectId}
|
||||
repoId={createTarget.repoId}
|
||||
onSubmit={handleCreate}
|
||||
onCancel={() => { setShowCreate(false); setCreateTarget(null); }}
|
||||
onCancel={() => {
|
||||
setShowCreate(false);
|
||||
setCreateTarget(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user