nyk commited on
Commit
03a699e
·
unverified ·
2 Parent(s): 3c9931194350fd

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 = dryRun
821
- ? previousTenantStatus
822
- : (jobType === 'decommission' ? 'suspended' : 'active')
 
 
 
 
 
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())