diff --git a/app/static/app.js b/app/static/app.js index 9a0ae89..8c32d59 100644 --- a/app/static/app.js +++ b/app/static/app.js @@ -78,6 +78,31 @@ function deviceTitle(d) { return d.hostname ? `${d.hostname} (${d.ip})` : d.ip; } +function parseIPv4(ip) { + if (typeof ip !== 'string') return null; + const parts = ip.trim().split('.'); + if (parts.length !== 4) return null; + const nums = parts.map((p) => Number(p)); + if (nums.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return null; + return nums; +} + +function compareIpNumeric(a, b) { + const pa = parseIPv4(a.ip); + const pb = parseIPv4(b.ip); + + if (pa && pb) { + for (let i = 0; i < 4; i += 1) { + if (pa[i] !== pb[i]) return pa[i] - pb[i]; + } + return 0; + } + + if (pa) return -1; + if (pb) return 1; + return String(a.ip || '').localeCompare(String(b.ip || '')); +} + function renderDeviceList() { deviceListEl.innerHTML = ''; @@ -153,6 +178,10 @@ 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); + }); renderDeviceList(); if (!selectedDeviceId && devices.length) { diff --git a/app/static/styles.css b/app/static/styles.css index e33af31..d23c44c 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -179,11 +179,15 @@ button { .right-stack { display: grid; - grid-template-rows: 1fr 1fr; + grid-template-rows: auto 1fr; gap: 12px; min-height: 0; } +.detail-pane { + align-self: start; +} + .machine-info, .ports-list { padding: 14px; overflow: auto; @@ -252,8 +256,8 @@ summary { } .right-stack { - grid-template-rows: 1fr 1fr; - min-height: 500px; + grid-template-rows: auto minmax(260px, 1fr); + min-height: 0; } .toolbar { min-width: 0; }