mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-08-30 14:52:51 +08:00
Merge pull request #233 from Erdnusschokolade/feature/vlan-overview
Add VLAN selector dropdown and overview table to vlan.html
This commit is contained in:
@@ -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; }
|
||||
|
||||
@@ -10,6 +10,13 @@
|
||||
<div id="ports"></div>
|
||||
<h1>VLAN Configuration</h1>
|
||||
<form id="vform" action="/vlan.html">
|
||||
<div>
|
||||
<label for="vlanSelect">VLAN auswählen:</label>
|
||||
<select id="vlanSelect" style="margin: 0 0 0 8px">
|
||||
<option value="" disabled selected>— VLAN wählen —</option>
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
<div>
|
||||
<label for="vid">VLAN ID:</label>
|
||||
<input type="number" min="1" max="4094" id="vid" name="vid">
|
||||
@@ -29,6 +36,22 @@
|
||||
<br/> <input style="width:40%;" class="action" id="vlan_sub" onclick="vlanSub();" type="button" value="Update / Create">
|
||||
<script src="/vlan_sub.js"></script>
|
||||
</form>
|
||||
<h2>Configured VLANs</h2>
|
||||
<table id="vlanTable" style="width:90%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>VLAN</th>
|
||||
<th>Name</th>
|
||||
<th>Member Ports</th>
|
||||
<th>Tagged Ports</th>
|
||||
<th>Untagged Ports</th>
|
||||
<th>PVID Ports</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="vlanTableBody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
|
||||
+120
@@ -83,9 +83,129 @@ 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) & members;
|
||||
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() {
|
||||
if (this.readyState !== 4) return;
|
||||
var sel = document.getElementById('vlanSelect');
|
||||
if (this.status !== 200) {
|
||||
sel.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
var vlans = JSON.parse(this.responseText);
|
||||
if (!vlans.length) {
|
||||
sel.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
sel.options.length = 1;
|
||||
for (var i = 0; i < vlans.length; i++) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = vlans[i].id;
|
||||
opt.text = vlans[i].name ? vlans[i].id + ' — ' + vlans[i].name : String(vlans[i].id);
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
};
|
||||
xhttp.open('GET', '/vlanlist', true);
|
||||
sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
vlanForm();
|
||||
refreshVlanViews();
|
||||
document.getElementById('vlanSelect').onchange = function() {
|
||||
document.getElementById('vid').value = this.value;
|
||||
fetchVLAN();
|
||||
};
|
||||
const interval = setInterval(update, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ async function vlanSub() {
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
}
|
||||
refreshVlanViews();
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
|
||||
@@ -682,6 +682,8 @@ void httpd_appcall(void)
|
||||
send_mtu();
|
||||
} else if (is_word(q, "/lag.json")) {
|
||||
send_lag();
|
||||
} else if (is_word(q, "/vlanlist")) {
|
||||
send_vlanlist();
|
||||
} else if (is_word(q, "/config")) {
|
||||
send_config();
|
||||
} else if (is_word(q, "/cmd_log")) {
|
||||
|
||||
@@ -97,6 +97,19 @@ void itoa_html(uint8_t v)
|
||||
char_to_html('0' + (v % 10));
|
||||
}
|
||||
|
||||
void itoa16_html(uint16_t v) /* sufficient for VLAN IDs (max 4094) */
|
||||
{
|
||||
uint8_t print_zeros = 0;
|
||||
uint8_t d;
|
||||
d = v / 1000;
|
||||
if (d) { char_to_html('0' + d); print_zeros = 1; }
|
||||
d = (v / 100) % 10;
|
||||
if (d || print_zeros) { char_to_html('0' + d); print_zeros = 1; }
|
||||
d = (v / 10) % 10;
|
||||
if (d || print_zeros) char_to_html('0' + d);
|
||||
char_to_html('0' + (v % 10));
|
||||
}
|
||||
|
||||
void string_to_html(__code char *s)
|
||||
{
|
||||
while (*s) char_to_html(*s++);
|
||||
@@ -825,3 +838,46 @@ void send_cmd_log(void)
|
||||
p = (p + 1) & CMD_HISTORY_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void send_vlanlist(void)
|
||||
{
|
||||
/* Worst case per entry: {"id":4094,"name":"<117-char name>"} = 138 bytes
|
||||
* (name bound: CMD_BUF_SIZE=128 minus command prefix); +1 for closing ']'.
|
||||
* At worst case ~18 VLANs fit; typical configs with short names fit many more. */
|
||||
__xdata uint16_t i;
|
||||
__xdata uint16_t n;
|
||||
uint8_t first = 1;
|
||||
|
||||
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
|
||||
char_to_html('[');
|
||||
|
||||
for (i = 1; i < 4095; i++) {
|
||||
if (vlan_get(i) < 0)
|
||||
continue;
|
||||
if (!(sfr_data[0] & 0x02)) /* bit 1: VLAN table entry valid */
|
||||
continue;
|
||||
|
||||
if (slen + 139 > TCP_OUTBUF_SIZE) /* 138 bytes worst-case entry + 1 byte for closing ']' */
|
||||
break;
|
||||
|
||||
if (!first)
|
||||
char_to_html(',');
|
||||
first = 0;
|
||||
|
||||
slen += strtox(outbuf + slen, "{\"id\":");
|
||||
itoa16_html(i);
|
||||
slen += strtox(outbuf + slen, ",\"name\":\"");
|
||||
|
||||
n = vlan_name(i);
|
||||
if (n != 0xffff) {
|
||||
while (vlan_names[n] && vlan_names[n] != ' ')
|
||||
char_to_html(vlan_names[n++]);
|
||||
}
|
||||
|
||||
slen += strtox(outbuf + slen, "\"}");
|
||||
|
||||
}
|
||||
|
||||
char_to_html(']');
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ void send_mtu(void);
|
||||
void send_config(void);
|
||||
void send_cmd_log(void);
|
||||
void send_lag(void);
|
||||
void send_vlanlist(void);
|
||||
|
||||
/* Convert only the lower nibble to ascii HEX char.
|
||||
For convenience the upper nibble is masked out.
|
||||
|
||||
Reference in New Issue
Block a user