feat: single shared Spaces key, deactivate/reactivate customer, status badge for inactive
Some checks failed
Build & Release / build (push) Has been cancelled

This commit is contained in:
Ryan Moon
2026-04-03 20:21:51 -05:00
parent 78503f993d
commit fe62514515
4 changed files with 74 additions and 36 deletions

View File

@@ -345,6 +345,8 @@
<!-- Kebab dropdown (shared, repositioned via JS) -->
<div id="kebab-menu">
<div class="kebab-item" onclick="kebabAction('view')">View Details</div>
<div class="kebab-item" id="kebab-deactivate" onclick="kebabAction('deactivate')">Deactivate</div>
<div class="kebab-item" id="kebab-reactivate" onclick="kebabAction('reactivate')" style="display:none">Reactivate</div>
<div class="kebab-item danger" onclick="kebabAction('delete')">Delete</div>
<div class="kebab-item" onclick="kebabAction('record')">Remove Record Only</div>
</div>
@@ -466,6 +468,7 @@
}
function customerStatusBadge(r) {
if (r.status === 'inactive') return '<span class="badge badge-gray">Inactive</span>';
if (r.status === 'provisioning') return '<span class="badge badge-yellow">Provisioning</span>';
if (r.status === 'failed') {
const failedStep = Object.entries(r.steps || {}).find(([,v]) => v === 'failed');
@@ -500,7 +503,7 @@
<td>${expiry}</td>
<td style="color:#8b949e">${fmtDateTime(r.created_at)}</td>
<td style="color:#8b949e">${fmtDateTime(r.updated_at)}</td>
<td><button class="kebab-btn" onclick="openKebab(event,'${r.slug}')">⋮</button></td>
<td><button class="kebab-btn" onclick="openKebab(event,'${r.slug}','${r.status}')">⋮</button></td>
</tr>`;
}).join('');
}
@@ -552,9 +555,12 @@
// ── Kebab menu ────────────────────────────────────────────────────────────
function openKebab(event, slug) {
function openKebab(event, slug, status) {
event.stopPropagation();
kebabSlug = slug;
// Show deactivate or reactivate depending on status
document.getElementById('kebab-deactivate').style.display = status === 'inactive' ? 'none' : '';
document.getElementById('kebab-reactivate').style.display = status === 'inactive' ? '' : 'none';
const menu = document.getElementById('kebab-menu');
const rect = event.currentTarget.getBoundingClientRect();
menu.style.top = (rect.bottom + 4) + 'px';
@@ -572,6 +578,8 @@
closeKebab();
if (!slug) return;
if (action === 'view') openDetail(slug);
else if (action === 'deactivate') deactivate(slug);
else if (action === 'reactivate') reactivate(slug);
else if (action === 'delete') openDeleteDialog(slug);
else if (action === 'record') removeRecord(slug);
}
@@ -758,6 +766,20 @@
}
}
async function deactivate(slug) {
if (!confirm(`Deactivate "${slug}"?\n\nThis removes the deployment but keeps the database and storage. You can reactivate later.`)) return;
const res = await apiFetch(`/api/customers/${slug}/deactivate`, { method: 'POST' });
if (res.ok) { loadCustomers(); if (currentDetailSlug === slug) loadDetail(slug, false); }
else { const d = await res.json().catch(() => ({})); alert(d.message ?? 'Failed'); }
}
async function reactivate(slug) {
if (!confirm(`Reactivate "${slug}"?\n\nThis will redeploy the application.`)) return;
const res = await apiFetch(`/api/customers/${slug}/reactivate`, { method: 'POST' });
if (res.ok) { loadCustomers(); if (currentDetailSlug === slug) loadDetail(slug, false); }
else { const d = await res.json().catch(() => ({})); alert(d.message ?? 'Failed'); }
}
async function removeRecord(slug) {
if (!confirm(`Remove "${slug}" from the manager database only?\n\nThis will NOT delete the ArgoCD app or DO database.`)) return;
const res = await apiFetch(`/api/customers/${slug}/record`, { method: 'DELETE' });