feat: provision dedicated per-project Theia workspaces

- lib/coolify-workspace.ts: creates a Coolify docker-image app at
  {slug}.ide.vibnai.com for each project, patches in vibn-auth Traefik
  labels, sets env vars, and starts deployment
- create/route.ts: provisions Theia workspace after Gitea repo creation;
  stores theiaWorkspaceUrl + theiaAppUuid on the project record
- theia-auth/route.ts: for *.ide.vibnai.com hosts, verifies the
  authenticated user is the project owner (slug → fs_projects lookup)
- overview/page.tsx: Open IDE always links (dedicated URL or shared fallback)
- project-creation-modal.tsx: shows dedicated workspace URL in success screen

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-19 13:14:21 -08:00
parent 4678928ee0
commit a22d5a0f18
5 changed files with 237 additions and 41 deletions

View File

@@ -44,6 +44,7 @@ export function ProjectCreationModal({
const [selectedRepo, setSelectedRepo] = useState<any>(null);
const [createdProjectId, setCreatedProjectId] = useState<string | null>(null);
const [createdGiteaRepo, setCreatedGiteaRepo] = useState<{ repo: string; repoUrl: string } | null>(null);
const [createdTheiaUrl, setCreatedTheiaUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [githubConnected, setGithubConnected] = useState(false);
@@ -90,6 +91,7 @@ export function ProjectCreationModal({
setSelectedRepo(null);
setCreatedProjectId(null);
setCreatedGiteaRepo(null);
setCreatedTheiaUrl(null);
setLoading(false);
};
@@ -123,6 +125,7 @@ export function ProjectCreationModal({
const data = await response.json();
setCreatedProjectId(data.projectId);
if (data.gitea) setCreatedGiteaRepo(data.gitea);
if (data.theiaWorkspaceUrl) setCreatedTheiaUrl(data.theiaWorkspaceUrl);
toast.success('Project created!');
setStep(3);
} else {
@@ -322,12 +325,20 @@ export function ProjectCreationModal({
)}
Webhook registered (push, PR, issues Vibn)
</li>
<li className="flex items-center gap-2">
{createdTheiaUrl ? (
<CheckCircle2 className="h-3.5 w-3.5 text-green-500 shrink-0" />
) : (
<XCircle className="h-3.5 w-3.5 text-yellow-500 shrink-0" />
)}
Dedicated IDE workspace{createdTheiaUrl ? ` at ${createdTheiaUrl.replace('https://', '')}` : ' — provisioning failed'}
</li>
</ul>
</div>
<div className="flex gap-3">
<a
href="https://theia.vibnai.com"
href={createdTheiaUrl ?? 'https://theia.vibnai.com'}
target="_blank"
rel="noopener noreferrer"
className="flex-1"