feat: add Provision IDE button for projects without a workspace

- POST /api/projects/[id]/workspace: provisions a Cloud Run Theia service
  on demand and saves the URL to the project record
- overview/page.tsx: shows 'Provision IDE' button when theiaWorkspaceUrl
  is null, 'Open IDE' link once provisioned
- Also fixes log spam: retired Firebase session tracking endpoint (410 Gone)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-19 15:36:18 -08:00
parent 81cca70542
commit aa2f5dbc3a
2 changed files with 105 additions and 10 deletions

View File

@@ -124,6 +124,7 @@ export default function ProjectOverviewPage() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [refreshing, setRefreshing] = useState(false);
const [provisioning, setProvisioning] = useState(false);
const fetchProject = async () => {
try {
@@ -153,6 +154,24 @@ export default function ProjectOverviewPage() {
fetchProject();
};
const handleProvisionWorkspace = async () => {
setProvisioning(true);
try {
const res = await fetch(`/api/projects/${projectId}/workspace`, { method: 'POST' });
const data = await res.json();
if (res.ok && data.workspaceUrl) {
toast.success('Workspace provisioned — starting up…');
await fetchProject();
} else {
toast.error(data.error || 'Failed to provision workspace');
}
} catch {
toast.error('An error occurred');
} finally {
setProvisioning(false);
}
};
if (loading) {
return (
<div className="flex items-center justify-center py-32">
@@ -200,16 +219,21 @@ export default function ProjectOverviewPage() {
<RefreshCw className={`h-4 w-4 mr-1.5 ${refreshing ? "animate-spin" : ""}`} />
Refresh
</Button>
<Button size="sm" asChild>
<a
href={project.theiaWorkspaceUrl ?? 'https://theia.vibnai.com'}
target="_blank"
rel="noopener noreferrer"
>
<Terminal className="h-4 w-4 mr-1.5" />
Open IDE
</a>
</Button>
{project.theiaWorkspaceUrl ? (
<Button size="sm" asChild>
<a href={project.theiaWorkspaceUrl} target="_blank" rel="noopener noreferrer">
<Terminal className="h-4 w-4 mr-1.5" />
Open IDE
</a>
</Button>
) : (
<Button size="sm" onClick={handleProvisionWorkspace} disabled={provisioning}>
{provisioning
? <><Loader2 className="h-4 w-4 mr-1.5 animate-spin" />Provisioning</>
: <><Terminal className="h-4 w-4 mr-1.5" />Provision IDE</>
}
</Button>
)}
</div>
</div>