export function show(panel) {
document.getElementById('panelStatus').style.display = panel === 'status' ? 'block' : 'none';
document.getElementById('panelConfigs').style.display = panel === 'configs' ? 'block' : 'none';
document.getElementById('panelLogs').style.display = panel === 'logs' ? 'block' : 'none';
document.getElementById('panelCommands').style.display = panel === 'commands' ? 'block' : 'none';
if (panel === 'status') loadStatus();
if (panel === 'configs') refreshConfigs();
if (panel === 'logs') loadLogs();
}
export async function api(path, opts) {
const res = await fetch(path, opts);
if (!res.ok) {
const text = await res.text();
throw new Error(res.status + ' ' + text);
}
return res;
}
export async function loadStatus() {
try {
const res = await api('/api/status');
const data = await res.json();
renderIfaces(data.Interfaces || []);
} catch (e) {
document.getElementById('ifaces').innerHTML = '
Error loading status: ' + e.message + '
';
}
}
export function renderIfaces(ifaces) {
const out = [];
if (ifaces.length === 0) {
out.push('No interfaces
');
}
for (const ifc of ifaces) {
const name = ifc.Name || 'n/a';
const typ = ifc.Type || '';
const state = ifc.OperationalState || ifc.AdministrativeState || '';
const mac = ifc.HardwareAddress ? arrayToMac(ifc.HardwareAddress) : '';
const addrs = (ifc.Addresses || []).map(a => ipFromArray(a)).join('
');
const routes = (ifc.Routes || []).map(r => routeToString(r)).join('
');
const dns = (ifc.DNS || []).map(d => ipFromArray(d.Address || d)).join('
');
out.push(``);
}
document.getElementById('ifaces').innerHTML = out.join('');
}
export function arrayToMac(a) {
if (!Array.isArray(a)) return '';
return a.map(x => ('0' + x.toString(16)).slice(-2)).join(':');
}
export function ipFromArray(obj) {
// obj can be { Family: number, Address: [..] } or {Address: [...]}
let arr = null;
if (Array.isArray(obj)) arr = obj;
else if (obj && Array.isArray(obj.Address)) arr = obj.Address;
else return '';
// detect IPv4 vs IPv6
if (arr.length === 4) return arr.join('.');
if (arr.length === 16) {
// IPv6 - format groups of 2 bytes
const parts = [];
for (let i = 0; i < 16; i += 2) {
parts.push(((arr[i] << 8) | arr[i + 1]).toString(16));
}
return parts.join(':').replace(/(:0+)+/, '::');
}
return JSON.stringify(arr);
}
export function routeToString(r) {
if (!r) return '';
const dest = r.Destination ? ipFromArray(r.Destination) : '';
const pref = r.Gateway ? ipFromArray(r.Gateway) : '';
return dest + (pref ? ' → ' + pref : '');
}
/* Configs editor */
export async function refreshConfigs() {
try {
const res = await api('/api/configs');
const data = await res.json();
const sel = document.getElementById('configSelect');
sel.innerHTML = '';
data.files.forEach(f => {
const opt = document.createElement('option');
opt.value = f; opt.textContent = f;
sel.appendChild(opt);
});
if (data.files.length > 0) {
loadConfig();
} else {
document.getElementById('cfgEditor').value = '';
}
} catch (e) {
alert('Failed to list configs: ' + e.message);
}
}
export async function loadConfig() {
const name = document.getElementById('configSelect').value;
if (!name) return;
try {
const res = await api('/api/config/' + encodeURIComponent(name));
const txt = await res.text();
document.getElementById('cfgEditor').value = txt;
document.getElementById('validateResult').textContent = '';
} catch (e) {
alert('Failed to load: ' + e.message);
}
}
export async function validateConfig() {
const name = document.getElementById('configSelect').value;
const content = document.getElementById('cfgEditor').value;
document.getElementById('validateResult').textContent = 'Validating...';
try {
const res = await api('/api/validate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, content }) });
const r = await res.json();
if (r.ok) {
document.getElementById('validateResult').textContent = 'OK';
} else {
document.getElementById('validateResult').textContent = 'Error: ' + (r.output || 'validation failed');
}
} catch (e) {
document.getElementById('validateResult').textContent = 'Error: ' + e.message;
}
}
export async function saveConfig() {
const name = document.getElementById('configSelect').value;
const content = document.getElementById('cfgEditor').value;
const restart = document.getElementById('restartAfterSave').checked;
if (!confirm('Save file ' + name + '? This will create a backup and (optionally) restart networkd.')) return;
try {
const res = await api('/api/save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, content, restart }) });
const r = await res.json();
alert('Saved: ' + (r.status || 'ok'));
} catch (e) {
alert('Save failed: ' + e.message);
}
}
/* Logs & commands */
export async function loadLogs() {
try {
const res = await api('/api/logs');
const txt = await res.text();
document.getElementById('logsArea').textContent = txt;
} catch (e) {
document.getElementById('logsArea').textContent = 'Error: ' + e.message;
}
}
export async function restartNetworkd() {
if (!confirm('Restart systemd-networkd? Active connections may be reset.')) return;
try {
const res = await api('/api/reload', { method: 'POST' });
const j = await res.json();
document.getElementById('cmdResult').textContent = JSON.stringify(j);
} catch (e) {
document.getElementById('cmdResult').textContent = 'Error: ' + e.message;
}
}
export async function rebootDevice() {
if (!confirm('Reboot device now?')) return;
try {
const res = await api('/api/reboot', { method: 'POST' });
const j = await res.json();
document.getElementById('cmdResult').textContent = JSON.stringify(j);
} catch (e) {
document.getElementById('cmdResult').textContent = 'Error: ' + e.message;
}
}