Merge pull request #226 from builderz-labs/fix/issue-215-workspace-pending-state

fix(workspaces): unblock pending tenant bootstrap flow
This commit is contained in:
nyk 2026-03-05 21:41:45 +07:00 committed by GitHub
commit 008bba4afb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 38 additions and 5 deletions

View File

@ -395,6 +395,34 @@ export function SuperAdminPanel() {
} }
} }
const approveAndRunJob = async (jobId: number) => {
setBusyJobId(jobId)
try {
const approveRes = await fetch(`/api/super/provision-jobs/${jobId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'approve' }),
})
const approveJson = await approveRes.json().catch(() => ({}))
if (!approveRes.ok) throw new Error(approveJson?.error || `Failed to approve job #${jobId}`)
const runRes = await fetch(`/api/super/provision-jobs/${jobId}/run`, { method: 'POST' })
const runJson = await runRes.json().catch(() => ({}))
if (!runRes.ok) throw new Error(runJson?.error || `Failed to run job #${jobId}`)
showFeedback(true, `Job #${jobId} approved and executed`)
await load()
await loadJobDetail(jobId)
} catch (e: any) {
showFeedback(false, e?.message || `Failed to approve/run job #${jobId}`)
await load()
await loadJobDetail(jobId)
} finally {
setBusyJobId(null)
setOpenActionMenu(null)
}
}
const openDecommissionDialog = (tenant: TenantRow) => { const openDecommissionDialog = (tenant: TenantRow) => {
setOpenActionMenu(null) setOpenActionMenu(null)
setDecommissionDialog({ setDecommissionDialog({
@ -884,11 +912,11 @@ export function SuperAdminPanel() {
View events View events
</button> </button>
<button <button
onClick={() => setJobState(job.id, 'approve')} onClick={() => Number(job.dry_run) === 1 ? approveAndRunJob(job.id) : setJobState(job.id, 'approve')}
disabled={busyJobId === job.id || !['queued', 'rejected', 'failed'].includes(job.status)} disabled={busyJobId === job.id || !['queued', 'rejected', 'failed'].includes(job.status)}
className="w-full px-3 py-2 text-xs text-emerald-400 hover:bg-emerald-500/10 disabled:opacity-40" className="w-full px-3 py-2 text-xs text-emerald-400 hover:bg-emerald-500/10 disabled:opacity-40"
> >
Approve {Number(job.dry_run) === 1 ? 'Approve + Run' : 'Approve'}
</button> </button>
<button <button
onClick={() => setJobState(job.id, 'reject')} onClick={() => setJobState(job.id, 'reject')}

View File

@ -817,9 +817,14 @@ export async function executeProvisionJob(jobId: number, actor: string) {
jobId, jobId,
) )
const completedTenantStatus = dryRun const completedTenantStatus = (() => {
? previousTenantStatus if (jobType === 'decommission') {
: (jobType === 'decommission' ? 'suspended' : 'active') return dryRun ? previousTenantStatus : 'suspended'
}
// For bootstrap/update jobs, mark tenant active when the workflow completes,
// even in dry-run mode, so workspace lifecycle is not stuck in pending.
return 'active'
})()
db.prepare(` db.prepare(`
UPDATE tenants UPDATE tenants
SET status = ?, updated_at = (unixepoch()) SET status = ?, updated_at = (unixepoch())