This commit is contained in:
+78
-18
@@ -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 = `
|
||||
<div>${deviceTitle(d)}</div>
|
||||
<div class="meta">${d.os_name || 'OS unknown'} | ${d.is_active ? 'Active' : 'Missing'}</div>
|
||||
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) => `
|
||||
<li class="device-item ${selectedDeviceId === device.id ? 'active' : ''}" data-device-id="${device.id}">
|
||||
<div>${escapeHtml(deviceTitle(device))}</div>
|
||||
<div class="meta">${escapeHtml(device.os_name || 'OS unknown')} | ${device.is_active ? 'Active' : 'Missing'}</div>
|
||||
</li>
|
||||
`).join('')
|
||||
: '<li class="device-empty">No machines in this section.</li>';
|
||||
|
||||
details.innerHTML = `
|
||||
<summary class="device-section-summary">
|
||||
<span>${escapeHtml(title)}</span>
|
||||
<span class="device-section-count">${sectionDevices.length}</span>
|
||||
</summary>
|
||||
<ul class="device-section-list">${itemsHtml}</ul>
|
||||
`;
|
||||
li.addEventListener('click', () => {
|
||||
selectedDeviceId = d.id;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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); }
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
codex resume 019ccf04-af34-7883-a705-2802dd142306
|
||||
Reference in New Issue
Block a user