claude went crazy
This commit is contained in:
70
js/ui.js
70
js/ui.js
@@ -1,5 +1,5 @@
|
||||
// Module-level UI state
|
||||
let editingId = null;
|
||||
let editingVmid = null;
|
||||
let currentVmid = null;
|
||||
let toastTimer = null;
|
||||
|
||||
@@ -27,8 +27,8 @@ function fmtDateFull(d) {
|
||||
|
||||
// ── Dashboard ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function renderDashboard() {
|
||||
const all = getInstances();
|
||||
async function renderDashboard() {
|
||||
const all = await getInstances();
|
||||
document.getElementById('nav-count').textContent = `${all.length} instance${all.length !== 1 ? 's' : ''}`;
|
||||
|
||||
const states = {};
|
||||
@@ -39,18 +39,19 @@ function renderDashboard() {
|
||||
<div class="stat-cell"><div class="stat-label">deployed</div><div class="stat-value">${states['deployed'] || 0}</div></div>
|
||||
<div class="stat-cell"><div class="stat-label">testing</div><div class="stat-value amber">${states['testing'] || 0}</div></div>
|
||||
<div class="stat-cell"><div class="stat-label">degraded</div><div class="stat-value red">${states['degraded'] || 0}</div></div>
|
||||
<div class="stat-cell"><div class="stat-label">stacks</div><div class="stat-value">${getDistinctStacks().length}</div></div>
|
||||
<div class="stat-cell"><div class="stat-label">stacks</div><div class="stat-value">${(await getDistinctStacks()).length}</div></div>
|
||||
`;
|
||||
|
||||
populateStackFilter();
|
||||
filterInstances();
|
||||
await populateStackFilter();
|
||||
await filterInstances();
|
||||
}
|
||||
|
||||
function populateStackFilter() {
|
||||
async function populateStackFilter() {
|
||||
const select = document.getElementById('filter-stack');
|
||||
const current = select.value;
|
||||
select.innerHTML = '<option value="">all stacks</option>';
|
||||
getDistinctStacks().forEach(s => {
|
||||
const stacks = await getDistinctStacks();
|
||||
stacks.forEach(s => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = s;
|
||||
opt.textContent = s;
|
||||
@@ -59,11 +60,11 @@ function populateStackFilter() {
|
||||
});
|
||||
}
|
||||
|
||||
function filterInstances() {
|
||||
async function filterInstances() {
|
||||
const search = document.getElementById('search-input').value;
|
||||
const state = document.getElementById('filter-state').value;
|
||||
const stack = document.getElementById('filter-stack').value;
|
||||
const instances = getInstances({ search, state, stack });
|
||||
const instances = await getInstances({ search, state, stack });
|
||||
const grid = document.getElementById('instance-grid');
|
||||
|
||||
if (!instances.length) {
|
||||
@@ -76,7 +77,6 @@ function filterInstances() {
|
||||
`<div class="svc-dot ${inst[s] ? 'on' : ''}" title="${s}"></div>`
|
||||
).join('');
|
||||
const activeCount = CARD_SERVICES.filter(s => inst[s]).length;
|
||||
|
||||
return `
|
||||
<div class="instance-card state-${esc(inst.state)}" onclick="navigate('instance', ${inst.vmid})">
|
||||
<div class="card-top">
|
||||
@@ -100,8 +100,8 @@ function filterInstances() {
|
||||
|
||||
// ── Detail Page ───────────────────────────────────────────────────────────────
|
||||
|
||||
function renderDetailPage(vmid) {
|
||||
const inst = getInstance(vmid);
|
||||
async function renderDetailPage(vmid) {
|
||||
const inst = await getInstance(vmid);
|
||||
if (!inst) { navigate('dashboard'); return; }
|
||||
currentVmid = vmid;
|
||||
|
||||
@@ -109,7 +109,7 @@ function renderDetailPage(vmid) {
|
||||
document.getElementById('detail-name').textContent = inst.name;
|
||||
document.getElementById('detail-vmid-sub').textContent = inst.vmid;
|
||||
document.getElementById('detail-id-sub').textContent = inst.id;
|
||||
document.getElementById('detail-created-sub').textContent = fmtDate(inst.createdAt);
|
||||
document.getElementById('detail-created-sub').textContent = fmtDate(inst.created_at);
|
||||
|
||||
document.getElementById('detail-identity').innerHTML = `
|
||||
<div class="kv-row"><span class="kv-key">name</span><span class="kv-val highlight">${esc(inst.name)}</span></div>
|
||||
@@ -135,8 +135,8 @@ function renderDetailPage(vmid) {
|
||||
`).join('');
|
||||
|
||||
document.getElementById('detail-timestamps').innerHTML = `
|
||||
<div class="kv-row"><span class="kv-key">created</span><span class="kv-val">${fmtDateFull(inst.createdAt)}</span></div>
|
||||
<div class="kv-row"><span class="kv-key">updated</span><span class="kv-val">${fmtDateFull(inst.updatedAt)}</span></div>
|
||||
<div class="kv-row"><span class="kv-key">created</span><span class="kv-val">${fmtDateFull(inst.created_at)}</span></div>
|
||||
<div class="kv-row"><span class="kv-key">updated</span><span class="kv-val">${fmtDateFull(inst.updated_at)}</span></div>
|
||||
`;
|
||||
|
||||
document.getElementById('detail-edit-btn').onclick = () => openEditModal(inst.vmid);
|
||||
@@ -146,16 +146,16 @@ function renderDetailPage(vmid) {
|
||||
// ── Modal ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
function openNewModal() {
|
||||
editingId = null;
|
||||
editingVmid = null;
|
||||
document.getElementById('modal-title').textContent = 'new instance';
|
||||
clearForm();
|
||||
document.getElementById('instance-modal').classList.add('open');
|
||||
}
|
||||
|
||||
function openEditModal(vmid) {
|
||||
const inst = getInstance(vmid);
|
||||
async function openEditModal(vmid) {
|
||||
const inst = await getInstance(vmid);
|
||||
if (!inst) return;
|
||||
editingId = inst.id;
|
||||
editingVmid = inst.vmid;
|
||||
document.getElementById('modal-title').textContent = `edit / ${inst.name}`;
|
||||
document.getElementById('f-name').value = inst.name;
|
||||
document.getElementById('f-vmid').value = inst.vmid;
|
||||
@@ -186,19 +186,18 @@ function clearForm() {
|
||||
.forEach(id => { document.getElementById(id).checked = false; });
|
||||
}
|
||||
|
||||
function saveInstance() {
|
||||
async function saveInstance() {
|
||||
const name = document.getElementById('f-name').value.trim();
|
||||
const vmid = parseInt(document.getElementById('f-vmid').value, 10);
|
||||
const state = document.getElementById('f-state').value;
|
||||
const stack = document.getElementById('f-stack').value;
|
||||
const tip = document.getElementById('f-tailscale-ip').value.trim();
|
||||
|
||||
if (!name) { showToast('name is required', 'error'); return; }
|
||||
if (!vmid || vmid < 1) { showToast('a valid vmid is required', 'error'); return; }
|
||||
|
||||
const data = {
|
||||
name, state, stack, vmid,
|
||||
tailscale_ip: tip,
|
||||
tailscale_ip: document.getElementById('f-tailscale-ip').value.trim(),
|
||||
atlas: +document.getElementById('f-atlas').checked,
|
||||
argus: +document.getElementById('f-argus').checked,
|
||||
semaphore: +document.getElementById('f-semaphore').checked,
|
||||
@@ -208,20 +207,19 @@ function saveInstance() {
|
||||
hardware_acceleration: +document.getElementById('f-hardware-accel').checked,
|
||||
};
|
||||
|
||||
const result = editingId ? updateInstance(editingId, data) : createInstance(data);
|
||||
const result = editingVmid
|
||||
? await updateInstance(editingVmid, data)
|
||||
: await createInstance(data);
|
||||
|
||||
if (!result.ok) {
|
||||
showToast(result.error.includes('UNIQUE') ? 'vmid already exists' : 'error saving instance', 'error');
|
||||
return;
|
||||
}
|
||||
if (!result.ok) { showToast(result.error, 'error'); return; }
|
||||
|
||||
showToast(`${name} ${editingId ? 'updated' : 'created'}`, 'success');
|
||||
showToast(`${name} ${editingVmid ? 'updated' : 'created'}`, 'success');
|
||||
closeModal();
|
||||
|
||||
if (currentVmid && document.getElementById('page-detail').classList.contains('active')) {
|
||||
renderDetailPage(vmid);
|
||||
await renderDetailPage(vmid);
|
||||
} else {
|
||||
renderDashboard();
|
||||
await renderDashboard();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,11 +230,10 @@ function confirmDeleteDialog(inst) {
|
||||
showToast(`demote ${inst.name} to development before deleting`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('confirm-title').textContent = `delete ${inst.name}?`;
|
||||
document.getElementById('confirm-msg').textContent =
|
||||
`This will permanently remove instance "${inst.name}" (vmid: ${inst.vmid}) from Catalyst. This action cannot be undone.`;
|
||||
document.getElementById('confirm-ok').onclick = () => doDelete(inst.id, inst.name);
|
||||
document.getElementById('confirm-ok').onclick = () => doDelete(inst.vmid, inst.name);
|
||||
document.getElementById('confirm-overlay').classList.add('open');
|
||||
}
|
||||
|
||||
@@ -244,9 +241,9 @@ function closeConfirm() {
|
||||
document.getElementById('confirm-overlay').classList.remove('open');
|
||||
}
|
||||
|
||||
function doDelete(id, name) {
|
||||
deleteInstance(id);
|
||||
async function doDelete(vmid, name) {
|
||||
closeConfirm();
|
||||
await deleteInstance(vmid);
|
||||
showToast(`${name} deleted`, 'success');
|
||||
navigate('dashboard');
|
||||
}
|
||||
@@ -261,7 +258,7 @@ function showToast(msg, type = 'success') {
|
||||
toastTimer = setTimeout(() => t.classList.remove('show'), 3000);
|
||||
}
|
||||
|
||||
// ── Global keyboard handler ───────────────────────────────────────────────────
|
||||
// ── Keyboard / backdrop ───────────────────────────────────────────────────────
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key !== 'Escape') return;
|
||||
@@ -269,7 +266,6 @@ document.addEventListener('keydown', e => {
|
||||
if (document.getElementById('confirm-overlay').classList.contains('open')) { closeConfirm(); return; }
|
||||
});
|
||||
|
||||
// Close modals on backdrop click
|
||||
document.getElementById('instance-modal').addEventListener('click', e => {
|
||||
if (e.target === document.getElementById('instance-modal')) closeModal();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user