Spaces:
Runtime error
Runtime error
Merge pull request #226 from builderz-labs/fix/issue-215-workspace-pending-state
Browse files
src/components/panels/super-admin-panel.tsx
CHANGED
|
@@ -395,6 +395,34 @@ export function SuperAdminPanel() {
|
|
| 395 |
}
|
| 396 |
}
|
| 397 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
const openDecommissionDialog = (tenant: TenantRow) => {
|
| 399 |
setOpenActionMenu(null)
|
| 400 |
setDecommissionDialog({
|
|
@@ -884,11 +912,11 @@ export function SuperAdminPanel() {
|
|
| 884 |
View events
|
| 885 |
</button>
|
| 886 |
<button
|
| 887 |
-
onClick={() => setJobState(job.id, 'approve')}
|
| 888 |
disabled={busyJobId === job.id || !['queued', 'rejected', 'failed'].includes(job.status)}
|
| 889 |
className="w-full px-3 py-2 text-xs text-emerald-400 hover:bg-emerald-500/10 disabled:opacity-40"
|
| 890 |
>
|
| 891 |
-
Approve
|
| 892 |
</button>
|
| 893 |
<button
|
| 894 |
onClick={() => setJobState(job.id, 'reject')}
|
|
|
|
| 395 |
}
|
| 396 |
}
|
| 397 |
|
| 398 |
+
const approveAndRunJob = async (jobId: number) => {
|
| 399 |
+
setBusyJobId(jobId)
|
| 400 |
+
try {
|
| 401 |
+
const approveRes = await fetch(`/api/super/provision-jobs/${jobId}`, {
|
| 402 |
+
method: 'POST',
|
| 403 |
+
headers: { 'Content-Type': 'application/json' },
|
| 404 |
+
body: JSON.stringify({ action: 'approve' }),
|
| 405 |
+
})
|
| 406 |
+
const approveJson = await approveRes.json().catch(() => ({}))
|
| 407 |
+
if (!approveRes.ok) throw new Error(approveJson?.error || `Failed to approve job #${jobId}`)
|
| 408 |
+
|
| 409 |
+
const runRes = await fetch(`/api/super/provision-jobs/${jobId}/run`, { method: 'POST' })
|
| 410 |
+
const runJson = await runRes.json().catch(() => ({}))
|
| 411 |
+
if (!runRes.ok) throw new Error(runJson?.error || `Failed to run job #${jobId}`)
|
| 412 |
+
|
| 413 |
+
showFeedback(true, `Job #${jobId} approved and executed`)
|
| 414 |
+
await load()
|
| 415 |
+
await loadJobDetail(jobId)
|
| 416 |
+
} catch (e: any) {
|
| 417 |
+
showFeedback(false, e?.message || `Failed to approve/run job #${jobId}`)
|
| 418 |
+
await load()
|
| 419 |
+
await loadJobDetail(jobId)
|
| 420 |
+
} finally {
|
| 421 |
+
setBusyJobId(null)
|
| 422 |
+
setOpenActionMenu(null)
|
| 423 |
+
}
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
const openDecommissionDialog = (tenant: TenantRow) => {
|
| 427 |
setOpenActionMenu(null)
|
| 428 |
setDecommissionDialog({
|
|
|
|
| 912 |
View events
|
| 913 |
</button>
|
| 914 |
<button
|
| 915 |
+
onClick={() => Number(job.dry_run) === 1 ? approveAndRunJob(job.id) : setJobState(job.id, 'approve')}
|
| 916 |
disabled={busyJobId === job.id || !['queued', 'rejected', 'failed'].includes(job.status)}
|
| 917 |
className="w-full px-3 py-2 text-xs text-emerald-400 hover:bg-emerald-500/10 disabled:opacity-40"
|
| 918 |
>
|
| 919 |
+
{Number(job.dry_run) === 1 ? 'Approve + Run' : 'Approve'}
|
| 920 |
</button>
|
| 921 |
<button
|
| 922 |
onClick={() => setJobState(job.id, 'reject')}
|
src/lib/super-admin.ts
CHANGED
|
@@ -817,9 +817,14 @@ export async function executeProvisionJob(jobId: number, actor: string) {
|
|
| 817 |
jobId,
|
| 818 |
)
|
| 819 |
|
| 820 |
-
const completedTenantStatus =
|
| 821 |
-
|
| 822 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 823 |
db.prepare(`
|
| 824 |
UPDATE tenants
|
| 825 |
SET status = ?, updated_at = (unixepoch())
|
|
|
|
| 817 |
jobId,
|
| 818 |
)
|
| 819 |
|
| 820 |
+
const completedTenantStatus = (() => {
|
| 821 |
+
if (jobType === 'decommission') {
|
| 822 |
+
return dryRun ? previousTenantStatus : 'suspended'
|
| 823 |
+
}
|
| 824 |
+
// For bootstrap/update jobs, mark tenant active when the workflow completes,
|
| 825 |
+
// even in dry-run mode, so workspace lifecycle is not stuck in pending.
|
| 826 |
+
return 'active'
|
| 827 |
+
})()
|
| 828 |
db.prepare(`
|
| 829 |
UPDATE tenants
|
| 830 |
SET status = ?, updated_at = (unixepoch())
|