From 98a424b929de1696af0553ed7dd1a913dfca3cdb Mon Sep 17 00:00:00 2001 From: Keith Solomon Date: Sat, 18 Apr 2026 12:20:33 -0500 Subject: [PATCH] Update sidebar --- app/static/app.js | 98 ++++++++++++++++++++++++++++++++++--------- app/static/styles.css | 55 ++++++++++++++++++++++++ codex-session.bat | 1 + 3 files changed, 135 insertions(+), 19 deletions(-) create mode 100644 codex-session.bat diff --git a/app/static/app.js b/app/static/app.js index c368079..df078a6 100644 --- a/app/static/app.js +++ b/app/static/app.js @@ -14,6 +14,8 @@ let devices = []; let selectedDeviceId = null; let selectedDevice = null; let saveInFlight = false; +let activeSectionOpen = true; +let inactiveSectionOpen = false; function setStatus(msg) { statusText.textContent = msg; @@ -105,6 +107,21 @@ function compareIpNumeric(a, b) { return String(a.ip || '').localeCompare(String(b.ip || '')); } +function compareDevices(a, b) { + const aName = String(a.hostname || '').trim().toLocaleLowerCase(); + const bName = String(b.hostname || '').trim().toLocaleLowerCase(); + + if (aName && bName) { + const byName = aName.localeCompare(bName); + if (byName !== 0) return byName; + return compareIpNumeric(a, b); + } + + if (aName) return -1; + if (bName) return 1; + return compareIpNumeric(a, b); +} + function renderDeviceList() { deviceListEl.innerHTML = ''; @@ -113,20 +130,69 @@ function renderDeviceList() { return; } - devices.forEach((d) => { - const li = document.createElement('li'); - li.className = `device-item ${selectedDeviceId === d.id ? 'active' : ''}`; - li.innerHTML = ` -
${deviceTitle(d)}
-
${d.os_name || 'OS unknown'} | ${d.is_active ? 'Active' : 'Missing'}
- `; - li.addEventListener('click', () => { - selectedDeviceId = d.id; + const activeDevices = devices.filter((device) => device.is_active); + const inactiveDevices = devices.filter((device) => !device.is_active); + + deviceListEl.appendChild(createSection('Active Machines', activeDevices, activeSectionOpen, true)); + deviceListEl.appendChild(createSection('Inactive Machines', inactiveDevices, inactiveSectionOpen, false)); + attachDeviceListHandlers(); +} + +function createSection(title, sectionDevices, isOpen, isActiveSection) { + const sectionItem = document.createElement('li'); + sectionItem.className = 'device-section-item'; + + const details = document.createElement('details'); + details.className = 'device-section'; + details.dataset.section = isActiveSection ? 'active' : 'inactive'; + details.open = isOpen; + + const itemsHtml = sectionDevices.length + ? sectionDevices.map((device) => ` +
  • +
    ${escapeHtml(deviceTitle(device))}
    +
    ${escapeHtml(device.os_name || 'OS unknown')} | ${device.is_active ? 'Active' : 'Missing'}
    +
  • + `).join('') + : '
  • No machines in this section.
  • '; + + details.innerHTML = ` + + ${escapeHtml(title)} + ${sectionDevices.length} + + + `; + + sectionItem.appendChild(details); + return sectionItem; +} + +function attachDeviceListHandlers() { + deviceListEl.querySelectorAll('.device-item[data-device-id]').forEach((item) => { + item.addEventListener('click', () => { + const deviceId = Number(item.dataset.deviceId); + if (!deviceId) return; + selectedDeviceId = deviceId; renderDeviceList(); - loadDevice(d.id); + loadDevice(deviceId); }); - deviceListEl.appendChild(li); }); + + const activeSection = deviceListEl.querySelector('.device-section[data-section="active"]'); + const inactiveSection = deviceListEl.querySelector('.device-section[data-section="inactive"]'); + + if (activeSection) { + activeSection.addEventListener('toggle', () => { + activeSectionOpen = activeSection.open; + }); + } + + if (inactiveSection) { + inactiveSection.addEventListener('toggle', () => { + inactiveSectionOpen = inactiveSection.open; + }); + } } function renderMachineInfo(d) { @@ -201,10 +267,7 @@ Headers:\n${headers} async function loadDevices() { devices = await api('/api/devices'); - devices.sort((a, b) => { - if (a.is_active !== b.is_active) return b.is_active - a.is_active; - return compareIpNumeric(a, b); - }); + devices.sort(compareDevices); renderDeviceList(); if (!selectedDeviceId && devices.length) { @@ -277,10 +340,7 @@ function resetDeviceEdits() { function updateDeviceInList(updatedDevice) { devices = devices.map((device) => (device.id === updatedDevice.id ? { ...device, ...updatedDevice } : device)); - devices.sort((a, b) => { - if (a.is_active !== b.is_active) return b.is_active - a.is_active; - return compareIpNumeric(a, b); - }); + devices.sort(compareDevices); renderDeviceList(); } diff --git a/app/static/styles.css b/app/static/styles.css index 1980e3c..5956198 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -164,6 +164,51 @@ button:disabled { overflow: auto; } +.device-section-item { + list-style: none; + margin-bottom: 8px; +} + +.device-section { + border: 1px solid var(--border); + border-radius: 12px; + background: rgba(255,255,255,0.015); + overflow: hidden; +} + +.device-section-summary { + display: flex; + align-items: center; + justify-content: space-between; + padding: 11px 12px; + cursor: pointer; + font-weight: 600; + list-style: none; + user-select: none; +} + +.device-section-summary::-webkit-details-marker { + display: none; +} + +.device-section-count { + min-width: 28px; + padding: 2px 8px; + border: 1px solid var(--border); + border-radius: 999px; + background: rgba(255,255,255,0.03); + color: var(--muted); + font-size: 0.78rem; + text-align: center; +} + +.device-section-list { + list-style: none; + margin: 0; + padding: 0 6px 6px; + border-top: 1px solid rgba(255,255,255,0.04); +} + .device-item { border: 1px solid transparent; border-radius: 10px; @@ -173,6 +218,16 @@ button:disabled { cursor: pointer; } +.device-section-list .device-item:first-child { + margin-top: 6px; +} + +.device-empty { + padding: 12px 10px; + color: var(--muted); + font-size: 0.84rem; +} + .device-item:hover { border-color: #33516b; } .device-item.active { border-color: var(--accent); background: rgba(93,196,255,0.08); } diff --git a/codex-session.bat b/codex-session.bat new file mode 100644 index 0000000..631ba06 --- /dev/null +++ b/codex-session.bat @@ -0,0 +1 @@ +codex resume 019ccf04-af34-7883-a705-2802dd142306