diff --git a/html/style.css b/html/style.css index 581737a..84d3a2e 100644 --- a/html/style.css +++ b/html/style.css @@ -163,3 +163,4 @@ margin: 30px 0; } select { text-align-last: right; font-family: monospace} option { direction: rtl; font-family: sans-serif} +#vlanTable td { text-align: left; } diff --git a/html/vlan.html b/html/vlan.html index 353e20e..b2ffa55 100644 --- a/html/vlan.html +++ b/html/vlan.html @@ -36,6 +36,22 @@
+

Configured VLANs

+ + + + + + + + + + + + + + +
VLANNameMember PortsTagged PortsUntagged PortsPVID PortsDelete
diff --git a/html/vlan.js b/html/vlan.js index 803db36..245b7e6 100644 --- a/html/vlan.js +++ b/html/vlan.js @@ -83,6 +83,95 @@ function fetchVLAN() { sendXHTTP(xhttp); } +function portsToRange(mask, nPorts) { + var parts = []; + var start = -1, prev = -1; + for (var p = 1; p <= nPorts; p++) { + var bit = physToLogPort[p - 1]; + if ((mask >> bit) & 1) { + if (start < 0) start = p; + prev = p; + } else { + if (start >= 0) { + parts.push(start === prev ? String(start) : start + '-' + prev); + start = -1; prev = -1; + } + } + } + if (start >= 0) + parts.push(start === prev ? String(start) : start + '-' + prev); + return parts.length ? parts.join(',') : '-'; +} + +async function loadVlanTable() { + var tbody = document.getElementById('vlanTableBody'); + if (!tbody) return; + tbody.innerHTML = ''; + var resp; + try { resp = await fetch('/vlanlist'); } catch(e) { return; } + if (!resp.ok) return; + var vlans = await resp.json(); + for (var i = 0; i < vlans.length; i++) { + var v = vlans[i]; + var vresp; + try { vresp = await fetch('/vlan.json?vid=' + v.id); } catch(e) { continue; } + if (!vresp.ok) continue; + var s = await vresp.json(); + var m = parseInt(s.members, 16); + var members = m & 0x3FF; + var untag = (m >> 10) & 0x3FF; + var tagged = members & ~untag; + var pvid = parseInt(s.pvid, 16) & 0x3FF; + var tr = document.createElement('tr'); + var td, a, btn; + td = document.createElement('td'); + a = document.createElement('a'); + a.href = '#'; + a.textContent = v.id; + (function(vid) { + a.onclick = function(e) { + e.preventDefault(); + document.getElementById('vid').value = vid; + fetchVLAN(); + }; + })(v.id); + td.appendChild(a); tr.appendChild(td); + td = document.createElement('td'); + td.textContent = v.name || ''; tr.appendChild(td); + td = document.createElement('td'); + td.textContent = portsToRange(members, numPorts); tr.appendChild(td); + td = document.createElement('td'); + td.textContent = portsToRange(tagged, numPorts); tr.appendChild(td); + td = document.createElement('td'); + td.textContent = portsToRange(untag, numPorts); tr.appendChild(td); + td = document.createElement('td'); + td.textContent = portsToRange(pvid, numPorts); tr.appendChild(td); + td = document.createElement('td'); + if (v.id !== 1) { + btn = document.createElement('button'); + btn.textContent = '✕'; + (function(vid) { + btn.onclick = function() { deleteVlan(vid); }; + })(v.id); + td.appendChild(btn); + } + tr.appendChild(td); + tbody.appendChild(tr); + } +} + +function deleteVlan(id) { + if (!confirm('Delete VLAN ' + id + '?')) return; + fetch('/cmd', { method: 'POST', body: 'vlan ' + id + ' d' }) + .then(function() { refreshVlanViews(); }) + .catch(function(err) { console.error('Delete failed:', err); }); +} + +function refreshVlanViews() { + loadVlanList(); + loadVlanTable(); +} + function loadVlanList() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { @@ -112,7 +201,7 @@ function loadVlanList() { window.addEventListener("load", function() { update( () => { vlanForm(); - loadVlanList(); + refreshVlanViews(); document.getElementById('vlanSelect').onchange = function() { document.getElementById('vid').value = this.value; fetchVLAN(); diff --git a/html/vlan_sub.js b/html/vlan_sub.js index 974dfd5..2816951 100644 --- a/html/vlan_sub.js +++ b/html/vlan_sub.js @@ -28,7 +28,7 @@ async function vlanSub() { }); console.log('Completed!', response); } - loadVlanList(); + refreshVlanViews(); } catch(err) { console.error(`Error: ${err}`); }