✨feature: Add editable hostname and operating system
Release / Build and Push Docker Image (push) Successful in 4m56s
Release / Build and Push Docker Image (push) Successful in 4m56s
This commit is contained in:
+86
-2
@@ -12,6 +12,8 @@ const scanProgressDetail = document.getElementById('scanProgressDetail');
|
||||
|
||||
let devices = [];
|
||||
let selectedDeviceId = null;
|
||||
let selectedDevice = null;
|
||||
let saveInFlight = false;
|
||||
|
||||
function setStatus(msg) {
|
||||
statusText.textContent = msg;
|
||||
@@ -128,19 +130,40 @@ function renderDeviceList() {
|
||||
}
|
||||
|
||||
function renderMachineInfo(d) {
|
||||
selectedDevice = d;
|
||||
machineInfoEl.classList.remove('empty');
|
||||
machineInfoEl.innerHTML = `
|
||||
<form id="machineInfoForm" class="machine-info-form">
|
||||
<div class="info-grid">
|
||||
<div class="info-card"><div class="label">Hostname</div><div class="value">${d.hostname || '-'}</div></div>
|
||||
<div class="info-card info-card-edit">
|
||||
<div class="label">Hostname</div>
|
||||
<input id="hostnameInput" class="inline-input" type="text" value="${escapeAttr(d.hostname || '')}" placeholder="${escapeAttr(d.detected_hostname || 'Hostname')}" />
|
||||
<div class="subvalue">Detected: ${escapeHtml(d.detected_hostname || '-')}</div>
|
||||
</div>
|
||||
<div class="info-card"><div class="label">IP Address</div><div class="value">${d.ip}</div></div>
|
||||
<div class="info-card"><div class="label">MAC Address</div><div class="value">${d.mac || '-'}</div></div>
|
||||
<div class="info-card"><div class="label">Vendor</div><div class="value">${d.vendor || '-'}</div></div>
|
||||
<div class="info-card"><div class="label">Operating System</div><div class="value">${d.os_name || '-'}</div></div>
|
||||
<div class="info-card info-card-edit">
|
||||
<div class="label">Operating System</div>
|
||||
<input id="osNameInput" class="inline-input" type="text" value="${escapeAttr(d.os_name || '')}" placeholder="${escapeAttr(d.detected_os_name || 'Operating system')}" />
|
||||
<div class="subvalue">Detected: ${escapeHtml(d.detected_os_name || '-')}</div>
|
||||
</div>
|
||||
<div class="info-card"><div class="label">Status</div><div class="value">${d.is_active ? 'Active' : 'Not Seen in Last Scan'}</div></div>
|
||||
<div class="info-card"><div class="label">First Seen</div><div class="value">${formatDate(d.first_seen)}</div></div>
|
||||
<div class="info-card"><div class="label">Last Seen</div><div class="value">${formatDate(d.last_seen)}</div></div>
|
||||
</div>
|
||||
<div class="machine-info-actions">
|
||||
<button id="saveDeviceBtn" type="submit" class="secondary-btn">Save Changes</button>
|
||||
<button id="resetDeviceBtn" type="button" class="ghost-btn">Reset to Detected</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
const form = document.getElementById('machineInfoForm');
|
||||
const resetBtn = document.getElementById('resetDeviceBtn');
|
||||
|
||||
form.addEventListener('submit', saveDeviceEdits);
|
||||
resetBtn.addEventListener('click', resetDeviceEdits);
|
||||
}
|
||||
|
||||
function renderPorts(ports) {
|
||||
@@ -200,6 +223,67 @@ async function loadDevice(deviceId) {
|
||||
renderPorts(d.ports || []);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
function escapeAttr(value) {
|
||||
return escapeHtml(value);
|
||||
}
|
||||
|
||||
async function saveDeviceEdits(event) {
|
||||
event.preventDefault();
|
||||
if (!selectedDevice || saveInFlight) return;
|
||||
|
||||
const hostname = document.getElementById('hostnameInput')?.value ?? '';
|
||||
const osName = document.getElementById('osNameInput')?.value ?? '';
|
||||
const saveBtn = document.getElementById('saveDeviceBtn');
|
||||
|
||||
saveInFlight = true;
|
||||
if (saveBtn) saveBtn.disabled = true;
|
||||
setStatus(`Saving edits for ${selectedDevice.ip}...`);
|
||||
|
||||
try {
|
||||
const updated = await api(`/api/devices/${selectedDevice.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ hostname, os_name: osName }),
|
||||
});
|
||||
selectedDevice = updated;
|
||||
updateDeviceInList(updated);
|
||||
renderMachineInfo(updated);
|
||||
setStatus(`Saved hostname and operating system for ${updated.ip}.`);
|
||||
} catch (err) {
|
||||
setStatus(`Failed saving device changes: ${err.message}`);
|
||||
} finally {
|
||||
saveInFlight = false;
|
||||
if (saveBtn) saveBtn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function resetDeviceEdits() {
|
||||
if (!selectedDevice) return;
|
||||
const hostnameInput = document.getElementById('hostnameInput');
|
||||
const osNameInput = document.getElementById('osNameInput');
|
||||
if (hostnameInput) hostnameInput.value = '';
|
||||
if (osNameInput) osNameInput.value = '';
|
||||
setStatus('Manual overrides cleared in the form. Save to apply.');
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
renderDeviceList();
|
||||
}
|
||||
|
||||
async function runScan() {
|
||||
const subnet = subnetInput.value.trim();
|
||||
if (!subnet) return;
|
||||
|
||||
@@ -81,6 +81,11 @@ button {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: wait;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.scan-progress-card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
@@ -206,6 +211,12 @@ button {
|
||||
background: rgba(255,255,255,0.02);
|
||||
}
|
||||
|
||||
.info-card-edit {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
@@ -218,6 +229,38 @@ button {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.subvalue {
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.inline-input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
background: rgba(10, 17, 24, 0.75);
|
||||
}
|
||||
|
||||
.machine-info-form {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.machine-info-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.secondary-btn {
|
||||
background: linear-gradient(180deg, var(--accent), var(--accent-2));
|
||||
}
|
||||
|
||||
.ghost-btn {
|
||||
border: 1px solid var(--border);
|
||||
background: rgba(255,255,255,0.03);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
details.port {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
@@ -263,4 +306,5 @@ summary {
|
||||
.toolbar { min-width: 0; }
|
||||
.controls { flex-direction: column; align-items: stretch; }
|
||||
input { min-width: 0; }
|
||||
.machine-info-actions { flex-direction: column; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user