mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-10-29 17:54:14 +08:00
Compare commits
34
Commits
6488433584
...
d3f1069285
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3f1069285 | ||
|
|
98bda62181 | ||
|
|
3f65db9984 | ||
|
|
df95c544d4 | ||
|
|
7df3c87892 | ||
|
|
53f12a42c5 | ||
|
|
1b514b5ea8 | ||
|
|
dd09be9dd0 | ||
|
|
b2b7f8a0c6 | ||
|
|
5b28ddf80a | ||
|
|
fe3da0c7a5 | ||
|
|
412098d0ed | ||
|
|
5ecce86ef1 | ||
|
|
247b230212 | ||
|
|
c012dda029 | ||
|
|
eab8c901cf | ||
|
|
21a9fdf45a | ||
|
|
1ed12d1101 | ||
|
|
af4101f060 | ||
|
|
eab17f8a3f | ||
|
|
4bcbada764 | ||
|
|
9fae0f9249 | ||
|
|
774e04816e | ||
|
|
ead1c40aac | ||
|
|
d245249385 | ||
|
|
37eefb4b35 | ||
|
|
3f5093f180 | ||
|
|
4c4232cc07 | ||
|
|
c4928e5546 | ||
|
|
67ae4598d9 | ||
|
|
b651890a3a | ||
|
|
caa7874bb3 | ||
|
|
53f33bd606 | ||
|
|
57e3fce95e |
No files matched your search
@@ -4,6 +4,9 @@
|
||||
|
||||
## Added
|
||||
|
||||
- Web UI
|
||||
- Compress the embedded assets (minify + gzip) and switch to a single-page layout. #315
|
||||
|
||||
## Changed
|
||||
|
||||
## Fixed
|
||||
|
||||
@@ -7,7 +7,6 @@ RUN apt-get update && apt-get install -y \
|
||||
xxd \
|
||||
python3 \
|
||||
libjson-c-dev \
|
||||
golang-go \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
@@ -87,10 +87,20 @@ SRCS += \
|
||||
|
||||
OBJS = ${SRCS:%.c=$(BUILDDIR)/%.rel}
|
||||
DEPS := ${SRCS:%.c=$(BUILDDIR)/%.d}
|
||||
HTML := $(shell find html -name '*.js' -or -name '*.html' -or -name '*.svg')
|
||||
HTML := $(shell find html -name '*.js' -or -name '*.html' -or -name '*.svg' -or -name '*.css' -or -name '*.ico')
|
||||
|
||||
html_data.c html_data.h &: $(HTML) | tools
|
||||
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -b BANK1 -d html -p html_data
|
||||
# Minified copy of the web UI sources, used as the fileadder input.
|
||||
# The raw html/ sources stay untouched for development; the minified
|
||||
# copy is a build artifact under output/.
|
||||
HTML_MIN := output/html_min
|
||||
.PHONY: html_min
|
||||
html_min: $(HTML)
|
||||
rm -rf $(HTML_MIN)
|
||||
mkdir -p $(HTML_MIN)
|
||||
@for f in $(HTML); do python3 tools/minify.py $$f $(HTML_MIN)/$$(basename $$f) || exit 1; done
|
||||
|
||||
html_data.c html_data.h &: $(HTML) | tools html_min
|
||||
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -b BANK1 -z -d $(HTML_MIN) -p html_data
|
||||
|
||||
$(VERSION_HEADER):
|
||||
@printf '%s\n' "#ifndef VERSION_H" "#define VERSION_H" \
|
||||
@@ -132,7 +142,7 @@ $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin: $(BUILDDIR)/rtlplayground.i
|
||||
tools/output/imagebuilder -i $^ $@
|
||||
tools/output/fileadder -a $(DEFAULT_CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@
|
||||
tools/output/fileadder -a $(CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@
|
||||
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -d html -p html_data -b BANK1 $@
|
||||
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -z -d $(HTML_MIN) -p html_data -b BANK1 $@
|
||||
tools/output/crc_calculator -u $@
|
||||
ln -sf $(MACHINE)/rtlplayground-$(FILENAME_EXTENSION).bin output/rtlplayground.bin
|
||||
|
||||
|
||||
+19
-9
@@ -504,20 +504,30 @@ void parse_vlan(void)
|
||||
}
|
||||
|
||||
uint8_t w = 2;
|
||||
if (cmd_words_len > w && isletter(cmd_buffer[cmd_words_b[w]])) {
|
||||
uint8_t pos = cmd_words_b[w];
|
||||
if (cmd_words_len > w && isletter(cmd_buffer[pos])) {
|
||||
uint8_t i = 0;
|
||||
while (!cmd_is_space_or_nul(pos + i))
|
||||
i++;
|
||||
if (vlan_ptr + i > (VLAN_NAMES_SIZE - 5)) {
|
||||
print_string("VLAN name table full, name ignored\n");
|
||||
} else {
|
||||
vlan_name_remove(vlan_settings.vlan);
|
||||
vlan_names[vlan_ptr++] = hex[(vlan_settings.vlan >> 8) & 0xf];
|
||||
vlan_names[vlan_ptr++] = hex[(vlan_settings.vlan >> 4) & 0xf] ;
|
||||
vlan_names[vlan_ptr++] = hex[vlan_settings.vlan & 0xf];
|
||||
while(!cmd_is_space_or_nul(cmd_words_b[w] + i)) {
|
||||
write_char(cmd_buffer[cmd_words_b[w] + i]);
|
||||
vlan_names[vlan_ptr++] = cmd_buffer[cmd_words_b[w] + i++];
|
||||
__xdata uint8_t *vlan_str = &vlan_names[vlan_ptr];
|
||||
*vlan_str++ = hex[(vlan_settings.vlan >> 8) & 0xf];
|
||||
*vlan_str++ = hex[(vlan_settings.vlan >> 4) & 0xf] ;
|
||||
*vlan_str++ = hex[vlan_settings.vlan & 0xf];
|
||||
while(!cmd_is_space_or_nul(pos)) {
|
||||
uint8_t c = cmd_buffer[pos++];
|
||||
write_char(c);
|
||||
*vlan_str++ = c;
|
||||
}
|
||||
vlan_names[vlan_ptr++] = ' '; vlan_names[vlan_ptr] = NUL;
|
||||
w++;
|
||||
*vlan_str++ = ' '; *vlan_str = NUL;
|
||||
vlan_ptr = vlan_str - vlan_names;
|
||||
print_string("<\n");
|
||||
}
|
||||
w++;
|
||||
}
|
||||
|
||||
uint8_t ret;
|
||||
uint8_t idx;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
;
|
||||
.globl _crc_value
|
||||
.globl _crc16
|
||||
.globl _crc16_bank1
|
||||
.globl __sdcc_banked_ret
|
||||
.equ BANK, 0x96
|
||||
; .equ DPS, 0x86
|
||||
; Variable in XMEM holding current CRC16 value, being updated
|
||||
@@ -18,7 +20,7 @@ _crc_value::
|
||||
.area HOME (CODE)
|
||||
.area CSEG (CODE)
|
||||
; .area BANK1 (CODE)
|
||||
_crc16:
|
||||
_crc16_bank1:
|
||||
mov BANK, #1
|
||||
push dph
|
||||
push dpl
|
||||
@@ -52,6 +54,10 @@ _crc16:
|
||||
|
||||
.area BANK1 (CODE)
|
||||
|
||||
_crc16:
|
||||
lcall _crc16_bank1
|
||||
ljmp __sdcc_banked_ret
|
||||
|
||||
crc16_table_l:
|
||||
.byte #0x00, #0xc1, #0x81, #0x40, #0x01, #0xc0, #0x80, #0x41
|
||||
.byte #0x01, #0xc0, #0x80, #0x41, #0x00, #0xc1, #0x81, #0x40
|
||||
|
||||
@@ -332,6 +332,18 @@ void parse_dhcp(void)
|
||||
uip_ipaddr(&uip_hostaddr, dhcp_state.current_ip[0], dhcp_state.current_ip[1], dhcp_state.current_ip[2], dhcp_state.current_ip[3]);
|
||||
uip_ipaddr(&uip_draddr, dhcp_state.router[0], dhcp_state.router[1], dhcp_state.router[2], dhcp_state.router[3]);
|
||||
uip_ipaddr(&uip_netmask, dhcp_state.subnet[0], dhcp_state.subnet[1], dhcp_state.subnet[2], dhcp_state.subnet[3]);
|
||||
/* T1 is optional. Without it the renewal time stays zero, which
|
||||
* makes dhcp_timer zero, and the callback then sends a REQUEST on
|
||||
* every pass through LEASING: client and server trade REQUEST and
|
||||
* ACK as fast as the round trip allows. RFC 2131 has the client
|
||||
* renew at half the lease when the server does not say, and if
|
||||
* there is no lease either, pick something rather than zero. Only
|
||||
* filled in when absent, so a server that asks for a short renewal
|
||||
* still gets it. */
|
||||
if (!dhcp_state.renewal)
|
||||
dhcp_state.renewal = dhcp_state.lease / 2;
|
||||
if (!dhcp_state.renewal)
|
||||
dhcp_state.renewal = 60;
|
||||
dhcp_state.state = DHCP_LEASING;
|
||||
dhcp_state.ticks = SYS_TICK_HZ;
|
||||
dhcp_state.dhcp_timer = dhcp_state.renewal > 0xffff ? 0xffff : dhcp_state.renewal;
|
||||
@@ -341,6 +353,12 @@ void parse_dhcp(void)
|
||||
|
||||
void dhcp_start(void) __banked
|
||||
{
|
||||
/* Starting twice would leave the first socket bound: uip_udp_new() takes
|
||||
* the next free slot while the old one keeps port 68, and dhcp_callback()
|
||||
* matches on that port rather than on the connection, so every leftover
|
||||
* runs the state machine too and the timers count down once per socket. */
|
||||
if (dhcp_state.state != DHCP_OFF)
|
||||
dhcp_stop();
|
||||
uip_ipaddr(server, 255,255,255,255);
|
||||
dhcp_state.conn = uip_udp_new(&server, HTONS(DHCPC_SERVER_PORT));
|
||||
dhcp_state.current_ip[0] = dhcp_state.current_ip[1] = dhcp_state.current_ip[2] = dhcp_state.current_ip[3] = 0;
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# Compressing the Web UI Assets
|
||||
|
||||
The Web UI (all files under `html/`) is embedded into the flash image and
|
||||
served over HTTP. Two build-time steps keep it small: a safe minifier and
|
||||
gzip compression of the embedded files.
|
||||
|
||||
## How it works
|
||||
|
||||
```
|
||||
html/ --(minify.py)--> output/html_min/ --(fileadder -z)--> flash image
|
||||
```
|
||||
|
||||
- `tools/minify.py` removes comments (`<!-- -->`, `//`, `/* */`) and
|
||||
line-leading whitespace without touching string or regex literals, so
|
||||
the output is functionally identical to the input. Binary files
|
||||
(`.ico`) are copied through unchanged. The raw sources in `html/` stay
|
||||
untouched for development; the minified copy in `output/` is a pure
|
||||
build artifact. Comments and indentation are pure overhead for the
|
||||
served bytes, so minification alone already reduces the transfer size
|
||||
from 109,814 to 97,730 bytes, independent of gzip.
|
||||
- The Web UI is a single-page application: `index.html` holds all pages
|
||||
as sections (`#/ports`, `#/vlan`, ...) that the sidebar switches
|
||||
between via the URL hash. `login.html` stays separate because it is
|
||||
the authentication gate. Only these two pages plus `main.js`,
|
||||
`style.css`, the three SVG port images and the favicon are embedded.
|
||||
- All JavaScript (i18n, shared helpers, navigation and the per-section
|
||||
scripts) lives in one `html/main.js` bundle that the pages load as
|
||||
their only script, so the browser fetches it once and gzip can
|
||||
compress across all of it. A section's initialisation runs when the
|
||||
section is shown, its polling intervals run only while it is visible,
|
||||
and re-entering a section refetches the data.
|
||||
- `fileadder -z` gzip-compresses every file (zlib, gzip format,
|
||||
`Z_BEST_COMPRESSION`) when it generates the file table
|
||||
(`html_data.c`/`html_data.h`) and when it embeds the files into the
|
||||
final image. Both invocations run on `output/html_min/`, so the table
|
||||
and the image always agree.
|
||||
- The httpd serves each file with `Content-Encoding: gzip` according to
|
||||
the `gzip` flag in its `f_data` entry. The 8051 only copies compressed
|
||||
bytes from flash to the socket; decompression happens in the browser.
|
||||
- Only the static files are compressed. The JSON API responses
|
||||
(`/information.json`, `/vlanlist`, ...) are generated on the fly and
|
||||
stay uncompressed.
|
||||
|
||||
## Measured effect
|
||||
|
||||
Build on any machine (the Web UI is machine-independent):
|
||||
|
||||
| | bytes |
|
||||
|---|---:|
|
||||
| raw sources | 109,814 |
|
||||
| minified (comments and indentation removed) | 97,730 |
|
||||
| gzip (flash + wire) | 25,302 (23.0 %) |
|
||||
|
||||
The minified assets are also what the browser receives when gzip support
|
||||
is missing or disabled, so the transfer size drops in both stages:
|
||||
109,814 → 97,730 bytes from minification, then → 25,302 bytes from gzip.
|
||||
|
||||
The embedded data block ends at `0x5b977` without compression and at
|
||||
`0x462d6` with it, freeing ~86 KB of the 512 KB image.
|
||||
|
||||
The consolidation helps at every step. The 17 scripts gzip to 23,302
|
||||
bytes as separate files but to 17,231 bytes as one `main.js` bundle,
|
||||
because the gzip dictionary spans all of them. The single-page layout
|
||||
then removes the per-page HTML and, more importantly, the repeated
|
||||
downloads: with the multi-page UI the browser fetched `main.js` on every
|
||||
navigation; the SPA fetches everything once and section switches are
|
||||
pure in-page JavaScript (measured on a real device: cold load ~450 ms,
|
||||
section switch ~60-160 ms, the only network traffic afterwards is the
|
||||
JSON polling of the visible section).
|
||||
|
||||
## Notes
|
||||
|
||||
- The files stay well below the `uint16_t` size limit of the file table
|
||||
(largest gzip output: 17.2 KB for the merged `main.js`).
|
||||
- `fileadder` terminates the embedded files with a NUL directly after the
|
||||
content (previously at `data_read + 1`), so the `strlen()`-based size
|
||||
computation no longer depends on uninitialised buffer content.
|
||||
- `version.h` is now written by a single atomic `printf` (change from
|
||||
upstream) instead of five separate `echo`s, so a parallel build
|
||||
(`make -j`) can no longer read a half-written copy. It stays in
|
||||
`.PHONY` so it is regenerated on every build.
|
||||
|
||||
## Verification
|
||||
|
||||
Decompress what the firmware embeds and compare it with the minified
|
||||
sources:
|
||||
|
||||
```python
|
||||
import gzip, re
|
||||
h = open('html_data.h').read(); c = open('html_data.c').read()
|
||||
starts = dict((k, int(v, 16)) for k, v in
|
||||
re.findall(r'#define FDATA_START_(\w+) 0x([0-9a-f]+)', h))
|
||||
sizes = dict((k, int(v)) for k, v in
|
||||
re.findall(r'#define FDATA_SIZE_(\w+) (\d+)', h))
|
||||
entries = [(p, sizes[z], starts[s]) for p, s, z in
|
||||
re.findall(r'\{"/([^"]+)", FDATA_START_(\w+), '
|
||||
r'FDATA_SIZE_(\w+), \w+, 1\}', c)]
|
||||
d = open('output/rtlplayground.bin', 'rb').read()
|
||||
for name, size, start in entries:
|
||||
assert gzip.decompress(d[start:start+size]) == \
|
||||
open('output/html_min/' + name, 'rb').read()
|
||||
```
|
||||
|
||||
`tools/httpd_sim` serves the raw files from `html/` and does not exercise
|
||||
the gzip path; a browser session against a flashed switch (or `curl
|
||||
--compressed`) is the final check.
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="bw_title">Ingress and Egress Bandwidth</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="bw_heading">Ingress and Egress Bandwidth</h1>
|
||||
<table id="bwtable">
|
||||
<tr> <th> </th> <th colspan="3" data-i18n="bw_ingress"> Ingress </th> <th colspan="2" data-i18n="bw_egress">Egress</th> <th></th></tr>
|
||||
<tr> <th data-i18n="bw_col_port">Port</th> <th data-i18n="bw_col_limit">Limit</th> <th data-i18n="bw_col_bandwidth">Bandwidth [kBit/s]</th> <th data-i18n="bw_col_flow">Flow Control</th> <th data-i18n="bw_col_limit">Limit</th> <th data-i18n="bw_col_bandwidth">Bandwidth [kBit/s]</th> <th data-i18n="bw_col_apply">Apply</th></tr>
|
||||
</table>
|
||||
<script src="/bandwidth.js"></script>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,138 +0,0 @@
|
||||
const iLayout = '" type="number" maxlength="10" size="10" onfocus="inputFocus(';
|
||||
function createBW() {
|
||||
var tbl = document.getElementById('bwtable');
|
||||
const limit = '<input type="checkbox" id="limit_port" onchange="exec();">'
|
||||
if (tbl.rows.length <= 2 && numPorts) {
|
||||
console.log("CREATING TABLE ", tbl.rows.length);
|
||||
for (let i = 2; i < 2 + numPorts; i++) {
|
||||
const tr = tbl.insertRow();
|
||||
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i-1)));
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = limit.replaceAll("limit_port", "ilimit_port_" + i).replace("exec()", "iClicked(" + i + ")");
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = t('bw_unlimited');
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = limit.replaceAll("limit_port", "fc_port_" + i).replace("exec()", "document.getElementById('bwapply_" + i + "').disabled=false;");
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = limit.replaceAll("limit_port", "elimit_port_" + i).replace("exec()", "eClicked(" + i + ")");
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = t('bw_unlimited');
|
||||
var button = '<button type="button" id="bwapply_' + i + '" style="margin: 0 0 0 24px" onclick="applyBandwidth(' + i + ');">' + t('bw_col_apply') + '</button>';
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = button;
|
||||
document.getElementById("bwapply_" + i).disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function iClicked(i)
|
||||
{
|
||||
document.getElementById("bwapply_" + i).disabled=false;
|
||||
var tbl = document.getElementById('bwtable');
|
||||
var tr = tbl.rows[i];
|
||||
if (!document.getElementById("ilimit_port_" + i).checked) {
|
||||
tr.cells[2].innerHTML = t('bw_unlimited');
|
||||
document.getElementById("fc_port_" + i).disabled = true;
|
||||
document.getElementById("fc_port_" + i).checked = true;
|
||||
} else {
|
||||
tr.cells[2].innerHTML = '<input id="ibw_' + i + iLayout + i + ')" value="0"/>';
|
||||
document.getElementById("fc_port_" + i).disabled = false;
|
||||
document.getElementById("fc_port_" + i).checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
function eClicked(i)
|
||||
{
|
||||
document.getElementById("bwapply_" + i).disabled=false;
|
||||
var tbl = document.getElementById('bwtable');
|
||||
var tr = tbl.rows[i];
|
||||
if (!document.getElementById("elimit_port_" + i).checked) {
|
||||
tr.cells[5].innerHTML = t('bw_unlimited');
|
||||
} else {
|
||||
tr.cells[5].innerHTML = '<input id="ebw_' + i + iLayout + i + ')" value="0"/>';
|
||||
}
|
||||
}
|
||||
|
||||
function inputFocus(i)
|
||||
{
|
||||
document.getElementById("bwapply_" + i).disabled=false;
|
||||
}
|
||||
|
||||
async function doCMD(cmd)
|
||||
{
|
||||
console.log("Sending >" + cmd + "<");
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function applyBandwidth(i) {
|
||||
var tbl = document.getElementById('bwtable');
|
||||
var tr = tbl.rows[i];
|
||||
var cmd = "bw in " + (i-1) + " off";
|
||||
if (document.getElementById("ilimit_port_" + i).checked)
|
||||
cmd = 'bw in ' + (i-1) + ' ' + parseInt(document.getElementById("ibw_" + i).value).toString(16).padStart(4, "0");;
|
||||
doCMD(cmd);
|
||||
if (document.getElementById("ilimit_port_" + i).checked) {
|
||||
if (!document.getElementById("fc_port_" + i).checked)
|
||||
cmd = "bw in " + (i-1) + " drop";
|
||||
doCMD(cmd);
|
||||
}
|
||||
var cmd = "bw out " + (i-1) + " off";
|
||||
if (document.getElementById("elimit_port_" + i).checked)
|
||||
cmd = 'bw out ' + (i-1) + ' ' + parseInt(document.getElementById("ebw_" + i).value).toString(16).padStart(4, "0");;
|
||||
doCMD(cmd);
|
||||
}
|
||||
|
||||
function getBW() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
console.log("IN getBW ");
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("BW: ", JSON.stringify(s));
|
||||
var tbl = document.getElementById('bwtable');
|
||||
if (tbl.rows.length > 2 && numPorts) {
|
||||
for (let i = 2; i < 2 + numPorts; i++) {
|
||||
p = s[i-2];
|
||||
let n = p.portNum;
|
||||
let tr = tbl.rows[n+1];
|
||||
if (!document.getElementById("bwapply_" + (n+1)).disabled)
|
||||
continue;
|
||||
console.log("Table Update row: " + i + " portNum is " + n + ", pState is " + pState[i-2] + ", row number is " + (n+1));
|
||||
let iBW = parseInt(p.iBW,16) * 16; let eBW = parseInt(p.eBW,16) * 16;
|
||||
document.getElementById("ilimit_port_" + (n+1)).checked = p.iLimited;
|
||||
document.getElementById("elimit_port_" + (n+1)).checked = p.eLimited;
|
||||
if (!p.iLimited) {
|
||||
tr.cells[2].innerHTML = t('bw_unlimited');
|
||||
} else {
|
||||
tr.cells[2].innerHTML = '<input id="ibw_' + (n+1) + iLayout + (n+1) + ')" value="' + iBW +'"/>';
|
||||
}
|
||||
if (!p.eLimited) {
|
||||
tr.cells[5].innerHTML = t('bw_unlimited');
|
||||
} else {
|
||||
tr.cells[5].innerHTML = '<input id="ebw_' + (n+1) + iLayout + (n+1) + ')" value="' + eBW +'"/>';
|
||||
}
|
||||
document.getElementById("fc_port_" + (n+1)).checked = p.iFC==1?true:false;
|
||||
document.getElementById("fc_port_" + (n+1)).disabled = p.iLimited==1?false:true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/bandwidth.json", true);
|
||||
xhttp.timeout = 1500; sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
createBW();
|
||||
getBW();
|
||||
const interval = setInterval(update, 2000);
|
||||
});
|
||||
});
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
var configInterval = Number();
|
||||
var configuration = [];
|
||||
const conf_cmds = [
|
||||
/^ip\s+(\d{1,3}\.){3}\d{1,3}$/,
|
||||
/^ip\s+dhcp$/,
|
||||
/^gw\s+(\d{1,3}\.){3}\d{1,3}$/,
|
||||
/^netmask\s+(\d{1,3}\.){3}\d{1,3}$/,
|
||||
/^syslog\s+(on|off)$/,
|
||||
/^syslog\s+ip\s+(\d{1,3}\.){3}\d{1,3}$/,
|
||||
/^syslog\s+port\s+\d{1,5}$/,
|
||||
/^passwd\s+\S+$/,
|
||||
/^vlan\s+\d{1,4}\s+d$/,
|
||||
/^vlan\s+\d{1,4}\s+mgmt$/,
|
||||
/^vlan\s+\d{1,4}(\s+[a-zA-Z]\w*)?(\s+[1-9]t?)+$/,
|
||||
/^pvid\s+\d{1,2}\s+\d{1,4}$/,
|
||||
/^ingress(\s+\d{1,2}[tua])+$/,
|
||||
/^ingress\s+[tua]$/,
|
||||
/^port\s+\d{1,2}\s+(10m|100m|1g|2g5|5g|10g|auto|on|off)(\s+(half|full))?$/,
|
||||
/^port\s+\d{1,2}\s+name\s+\S+$/,
|
||||
/^eee(\s+\d{1,2})?\s+(on|off)$/,
|
||||
/^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/,
|
||||
/^lag\s+\d(\s+\d{1,2})+$/,
|
||||
/^laghash\s+\d(\s+\w+)+$/,
|
||||
/^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/,
|
||||
/^stp\s+(on|off)$/,
|
||||
/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d{1,2}$/,
|
||||
/^stp\s+version\s+(rstp|stp)$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+(on|off)$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+edge\s+(on|off|auto)$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+cost\s+\d{1,9}$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+prio\s+\d{1,3}$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+guard\s+(none|bpdu|root)$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+filter\s+(on|off)$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+p2p\s+(auto|on|off)$/,
|
||||
/^igmp\s+(on|off)$/,
|
||||
/^mtu\s+\d{1,2}\s+\d+$/,
|
||||
/^bw\s+(in|out)\s+\d{1,2}\s+\S+$/,
|
||||
/^hostname\s+.{1,23}$/,
|
||||
];
|
||||
/* Commands that come in an on/off pair replace each other, which the list
|
||||
* below cannot express: it drops lines starting with the text it matched, and
|
||||
* "syslog off" does not start with "syslog on". Naming the stem separately
|
||||
* keeps the pair collapsed without widening the match to the whole family. */
|
||||
const conf_toggle = [
|
||||
/^(syslog)\s+(?:on|off)$/,
|
||||
];
|
||||
|
||||
const conf_overwrite = [
|
||||
/^ip\b/,
|
||||
/^gw\b/,
|
||||
/^netmask\b/,
|
||||
/^syslog\s+ip\b/,
|
||||
/^syslog\s+port\b/,
|
||||
/^syslog\s+(on|off)$/,
|
||||
/^passwd\b/,
|
||||
/^vlan\s+\d{1,4}\s+mgmt$/,
|
||||
/^vlan\s+\d{1,4}(?!\s+mgmt\b)/,
|
||||
/^pvid\s+\d{1,2}\b/,
|
||||
/^ingress\b/,
|
||||
/^port\s+\d{1,2}(?!\s+name\b)/,
|
||||
/^port\s+\d{1,2}\s+name\b/,
|
||||
/^eee\s+\d{1,2}\b/,
|
||||
/^eee\b/,
|
||||
/^mirror\b/,
|
||||
/^lag\s+\d+\b/,
|
||||
/^laghash\b/,
|
||||
/^isolate\s+\d{1,2}\b/,
|
||||
/^stp\s+(prio|hello|maxage|fwd|txhold|version)\b/,
|
||||
/^stp\s+port\s+\d{1,2}\s+(edge|cost|prio|guard|filter|p2p)\b/,
|
||||
/^igmp\b/,
|
||||
/^mtu\s+\d{1,2}\b/,
|
||||
/^bw\s+(in|out)\s+\d{1,2}\b/,
|
||||
/^hostname\b/,
|
||||
];
|
||||
|
||||
function parseConf(s){
|
||||
var a = s.split(/\r\n|\n/);
|
||||
for (var l = 0; l < a.length; l++) {
|
||||
var line = a[l].trim().replace(/\s+/g, ' ');
|
||||
if (!line.length) continue;
|
||||
const deleteMatch = line.match(/^vlan\s+(\d{1,4})\s+d$/);
|
||||
if (deleteMatch) {
|
||||
const prefix = "vlan " + deleteMatch[1] + " ";
|
||||
configuration = configuration.filter(c => !c.startsWith(prefix));
|
||||
continue;
|
||||
}
|
||||
console.log(l + ' --> ' + line);
|
||||
var ignore = true;
|
||||
for (const x of conf_cmds)
|
||||
if (x.test(line)) { ignore = false; break; }
|
||||
if (ignore) continue;
|
||||
for (const x of conf_toggle) {
|
||||
const t = line.match(x);
|
||||
if (t) {
|
||||
configuration = configuration.filter(item => item !== t[1] + " on" && item !== t[1] + " off");
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const x of conf_overwrite) {
|
||||
if (x.test(line)) {
|
||||
let m = line.match(x);
|
||||
let matchStr = m[0];
|
||||
configuration = configuration.filter(item =>
|
||||
!(item === matchStr || (item.startsWith(matchStr + " ") && !item.endsWith(" mgmt") && !item.startsWith(matchStr + " name "))));
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Only one management VLAN can be active, so drop any previous mgmt entry
|
||||
if (/^vlan\s+\d{1,4}\s+mgmt$/.test(line))
|
||||
configuration = configuration.filter(item => !/^vlan\s+\d{1,4}\s+mgmt$/.test(item));
|
||||
configuration.push(line);
|
||||
}
|
||||
console.log("Configuration now:");
|
||||
for (const x of configuration) { console.log(x); }
|
||||
}
|
||||
|
||||
async function fetchConfig() {
|
||||
try {
|
||||
const response = await fetch('/config');
|
||||
console.log("CONFIG: ", response);
|
||||
const t = await response.text();
|
||||
return t;
|
||||
} catch(err) {
|
||||
console.error("Error: ", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCmdLog() {
|
||||
try {
|
||||
const response = await fetch('/cmd_log');
|
||||
console.log("CMD-Log: ", response);
|
||||
const t = await response.text();
|
||||
return t;
|
||||
} catch(err) {
|
||||
console.error("Error: ", err);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="eee_title">EEE Configuration</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="eee_heading">EEE Status</h1>
|
||||
<table id="eeetable">
|
||||
<tr> <th> </th> <th colspan="3" data-i18n="eee_advertising"> Advertising </th> <th colspan="3" data-i18n="eee_partner">Link-Partner advertises</th> <th></th></tr>
|
||||
<tr> <th data-i18n="eee_port">Port</th> <th>2.5G</th> <th>1G</th> <th>100M</th> <th>2.5G</th> <th>1G</th> <th>100M</th> <th data-i18n="eee_active">Active?</th></tr>
|
||||
</table>
|
||||
<div>
|
||||
<input style="width:20%;" class="action" id="eee_enable" onclick="eeeSub(0, 1);" type="button" data-i18n="eee_enable" value="Enable EEE">
|
||||
<input style="width:20%;" class="action" id="eee_disable" onclick="eeeSub(0, 0);" type="button" data-i18n="eee_disable" value="Disable EEE">
|
||||
</div>
|
||||
<script src="/eee.js"></script>
|
||||
<script src="/eee_sub.js"></script>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
function createEEE() {
|
||||
var tbl = document.getElementById('eeetable');
|
||||
if (tbl.rows.length <= 2 && numPorts) {
|
||||
console.log("CREATING TABLE ", tbl.rows.length);
|
||||
for (let i = 2; i < 2 + numPorts; i++) {
|
||||
console.log("Table row: " + i + "pState: " + pState[i-2]);
|
||||
const tr = tbl.insertRow();
|
||||
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i-1)));
|
||||
for (let j = 0; j < 7; j++) {
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(" "));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getEEE() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("EEE: ", JSON.stringify(s));
|
||||
var tbl = document.getElementById('eeetable');
|
||||
if (tbl.rows.length > 2 && numPorts) {
|
||||
for (let i = 2; i < 2 + numPorts; i++) {
|
||||
p = s[i-2];
|
||||
let n = p.portNum;
|
||||
console.log("Table Update row: " + i + " portNum is " + n + ", pState is " + pState[i-2]);
|
||||
let tr = tbl.rows[n+1];
|
||||
if (!p.isSFP) {
|
||||
let eee = parseInt(p.eee,2); let lp = parseInt(p.eee_lp,2);
|
||||
tr.cells[1].innerHTML = `${eee&4?t('eee_on'):t('eee_off')}`; tr.cells[2].innerHTML = `${eee&2?t('eee_on'):t('eee_off')}`; tr.cells[3].innerHTML = `${eee&1?t('eee_on'):t('eee_off')}`;
|
||||
tr.cells[4].innerHTML = `${lp&4?t('eee_on'):t('eee_off')}`; tr.cells[5].innerHTML = `${lp&2?t('eee_on'):t('eee_off')}`; tr.cells[6].innerHTML = `${lp&1?t('eee_on'):t('eee_off')}`;
|
||||
tr.cells[7].innerHTML = `${p.active}`;
|
||||
tr.classList.toggle('disabled', pState[i-2] < 0); tr.classList.toggle('isNOK', !p.active); tr.classList.toggle('isOK', p.active);
|
||||
}
|
||||
tr.classList.toggle('isSFP', p.isSFP);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/eee.json", true);
|
||||
xhttp.timeout = 1500; sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
createEEE();
|
||||
getEEE();
|
||||
const interval = setInterval(update, 2000);
|
||||
const iCount = setInterval(getEEE, 2000);
|
||||
});
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
async function eeeSub(port, enable) {
|
||||
var cmd = "eee ";
|
||||
if (enable)
|
||||
cmd = cmd + "on";
|
||||
else
|
||||
cmd = cmd + "off";
|
||||
console.log("eeeSub port " + port, ", value " + enable);
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
-609
@@ -1,609 +0,0 @@
|
||||
var LANG = {
|
||||
en: {
|
||||
nav_overview: 'Overview',
|
||||
nav_port_config: 'Port Configuration',
|
||||
nav_port_stat: 'Port Statistics',
|
||||
nav_l2: 'L2 Configuration',
|
||||
nav_mirror: 'Mirroring',
|
||||
nav_lag: 'Link Aggregation',
|
||||
nav_eee: 'EEE',
|
||||
nav_bandwidth: 'Bandwidth Limits',
|
||||
nav_system: 'System Settings',
|
||||
nav_fw_update: 'Firmware Update',
|
||||
|
||||
port_name: 'Name',
|
||||
port_status: 'Status',
|
||||
port_not_enabled: 'Not enabled.',
|
||||
port_link_speed: 'Link speed',
|
||||
port_vendor: 'Vendor',
|
||||
port_model: 'Model',
|
||||
port_serial: 'Serial',
|
||||
port_temp: 'Temp',
|
||||
port_vcc: 'Vcc',
|
||||
port_tx_fault: 'TX-Fault',
|
||||
port_tx_disabled: 'TX-Disabled',
|
||||
port_tx_bias: 'TX-Bias',
|
||||
port_tx_power: 'TX-Power',
|
||||
port_rx_power: 'RX-Power',
|
||||
port_rx_los: 'RX-LOS',
|
||||
|
||||
speed_disabled: 'Disabled',
|
||||
speed_down: 'Down',
|
||||
|
||||
port_title: 'FreeSwitchOS Port Configuration',
|
||||
port_heading: 'Port Configuration',
|
||||
port_col_port: 'Port',
|
||||
port_col_name: 'Name',
|
||||
port_col_speed: 'Current Link Speed',
|
||||
port_col_set_speed: 'Set Speed',
|
||||
port_col_disabled: 'Disabled',
|
||||
port_col_devices: 'Connected devices',
|
||||
port_devices: 'devices',
|
||||
port_col_apply: 'Apply',
|
||||
port_mtu_heading: 'Configure Maximum Frame Size (MTU) forwarded at Port',
|
||||
port_auto: 'Auto',
|
||||
port_2500m: '2500MBit/Full',
|
||||
port_1000m: '1000MBit/Full',
|
||||
port_100m_f: '100MBit/Full',
|
||||
port_100m_h: '100MBit/Half',
|
||||
port_10m_f: '10MBit/Full',
|
||||
port_10m_h: '10MBit/Half',
|
||||
port_apply: 'Apply',
|
||||
|
||||
stat_title: 'FreeSwitchOS Port Statistics',
|
||||
stat_heading: 'Port Statistics',
|
||||
stat_detailed: 'Detailed Port Statistics',
|
||||
stat_close: 'Close',
|
||||
stat_col_port: 'Port',
|
||||
stat_col_name: 'Name',
|
||||
stat_col_link: 'link',
|
||||
stat_col_tx_good: 'TX Good',
|
||||
stat_col_tx_bad: 'TX Bad',
|
||||
stat_col_rx_good: 'RX Good',
|
||||
stat_col_rx_bad: 'RX Bad',
|
||||
stat_col_all: 'All Counters',
|
||||
stat_counter: 'Counter',
|
||||
stat_value: 'Value',
|
||||
stat_show: 'Show',
|
||||
|
||||
vlan_title: 'FreeSwitchOS VLAN Configuration',
|
||||
vlan_heading: 'VLAN Configuration',
|
||||
vlan_select: 'VLAN Select:',
|
||||
vlan_choose: '— VLAN Choose —',
|
||||
vlan_id: 'VLAN ID:',
|
||||
vlan_get_config: 'Get Configuration',
|
||||
vlan_name: 'VLAN Name:',
|
||||
vlan_tagged: 'Tagged Ports',
|
||||
vlan_untagged: 'Untagged Ports',
|
||||
vlan_select_all: 'Select all',
|
||||
vlan_pvid: 'Use as default VLAN for incoming traffic (PVID)',
|
||||
vlan_update: 'Update / Create',
|
||||
vlan_configured: 'Configured VLANs',
|
||||
vlan_col_name: 'Name',
|
||||
vlan_col_member: 'Member Ports',
|
||||
vlan_col_tagged: 'Tagged Ports',
|
||||
vlan_col_untagged: 'Untagged Ports',
|
||||
vlan_col_pvid: 'PVID Ports',
|
||||
vlan_col_delete: 'Delete',
|
||||
vlan_set_id_first: 'Set VLAN ID first',
|
||||
vlan_delete_confirm: 'Delete VLAN ',
|
||||
|
||||
lag_title: 'Link Aggregation Configuration',
|
||||
lag_heading: 'Link Aggregation Groups Configuration',
|
||||
lag_update: 'Update / Create',
|
||||
|
||||
mirror_title: 'Mirror Configuration',
|
||||
mirror_heading: 'Mirror Configuration',
|
||||
mirror_enabled: 'Enabled:',
|
||||
mirror_port: 'Mirroring Port:',
|
||||
mirror_tx: 'Mirrored Ports (TX)',
|
||||
mirror_rx: 'Mirrored Ports (RX)',
|
||||
mirror_update: 'Update / Create',
|
||||
mirror_disable: 'Disable Mirroring',
|
||||
mirror_set_port_first: 'Set Mirroring Port first',
|
||||
mirror_select_ports: 'Select Mirrored Ports',
|
||||
|
||||
eee_title: 'EEE Configuration',
|
||||
eee_heading: 'EEE Status',
|
||||
eee_advertising: 'Advertising',
|
||||
eee_partner: 'Link-Partner advertises',
|
||||
eee_port: 'Port',
|
||||
eee_active: 'Active?',
|
||||
eee_enable: 'Enable EEE',
|
||||
eee_disable: 'Disable EEE',
|
||||
eee_on: 'ON',
|
||||
eee_off: 'OFF',
|
||||
|
||||
l2_title: 'FreeSwitchOS L2 Configuration',
|
||||
l2_heading: 'L2 Configuration',
|
||||
l2_col_port: 'Port',
|
||||
l2_col_type: 'Type',
|
||||
l2_col_remove: 'Remove Entry',
|
||||
l2_shown: 'Shown:',
|
||||
l2_delete: 'Delete',
|
||||
l2_static: 'static',
|
||||
l2_learned: 'learned',
|
||||
|
||||
bw_title: 'Ingress and Egress Bandwidth',
|
||||
bw_heading: 'Ingress and Egress Bandwidth',
|
||||
bw_ingress: 'Ingress',
|
||||
bw_egress: 'Egress',
|
||||
bw_col_port: 'Port',
|
||||
bw_col_limit: 'Limit',
|
||||
bw_col_bandwidth: 'Bandwidth [kBit/s]',
|
||||
bw_col_flow: 'Flow Control',
|
||||
bw_col_apply: 'Apply',
|
||||
bw_unlimited: 'UNLIMITED',
|
||||
|
||||
sys_title: 'System Settings',
|
||||
sys_tab_system: 'System',
|
||||
sys_tab_advanced: 'Advanced',
|
||||
sys_tab_console: 'Console',
|
||||
sys_heading: 'System Settings',
|
||||
sys_ip: 'IP address:',
|
||||
sys_model: 'Model:',
|
||||
sys_hostname: 'Hostname:',
|
||||
sys_apply: 'Apply',
|
||||
sys_netmask: 'Netmask:',
|
||||
sys_gateway: 'Gateway:',
|
||||
sys_language: 'Language:',
|
||||
sys_mgmt_vlan: 'Management VLAN:',
|
||||
sys_mgmt_untagged: 'untagged',
|
||||
sys_mgmt_confirm: 'Move switch management to VLAN ',
|
||||
sys_mgmt_warn: 'The switch will start tagging its own traffic with that VLAN. If the port you are connected through does not carry it, this page becomes unreachable and the setting can only be undone over the console. Continue?',
|
||||
sys_ip_note: 'When updating the above settings, remember to point your browser to the new IP afterwards:',
|
||||
sys_update: 'Update Settings',
|
||||
sys_save_label: 'Save all current settings to Flash:',
|
||||
sys_save: 'Save Settings to Flash',
|
||||
sys_advanced: 'Advanced Settings',
|
||||
sys_startup_config: 'Startup configuration:',
|
||||
sys_startup_warn: 'Be careful when saving the directly edited startup configuration, you can lock yourself out:',
|
||||
sys_clear_config: 'Clear Startup Config',
|
||||
sys_save_startup: 'Save Startup Settings to Flash',
|
||||
sys_reset: 'Reset Switch',
|
||||
sys_console: 'Console Command',
|
||||
sys_enter_cmd: 'Enter command:',
|
||||
sys_send_cmd: 'Send Command',
|
||||
sys_console_warn: 'Be careful when entering console commands, you can lock yourself out!',
|
||||
sys_invalid_ip: 'Invalid ip:',
|
||||
sys_reset_confirm: 'Are you sure you want to reset the switch?',
|
||||
sys_resetting: 'Switch is resetting. Please wait and refresh the page.',
|
||||
|
||||
login_title: 'RTL Switch Login',
|
||||
login_heading: 'RTL Switch Login',
|
||||
login_wrong: 'Wrong password!',
|
||||
login_password: 'Password',
|
||||
login_login: 'Login',
|
||||
|
||||
index_title: 'FreeSwitchOS Main Page',
|
||||
index_heading: 'Switch Configuration',
|
||||
index_settings: 'Settings',
|
||||
|
||||
update_title: 'Firmware update',
|
||||
update_heading: 'Firmware Update',
|
||||
update_instruction: 'Choose a firmware update file to upload:',
|
||||
update_upload: 'Upload File',
|
||||
|
||||
common_port: 'Port ',
|
||||
common_pkts: ' pkts',
|
||||
},
|
||||
|
||||
ja: {
|
||||
nav_overview: '概要',
|
||||
nav_port_config: 'ポート設定',
|
||||
nav_port_stat: 'ポート統計',
|
||||
nav_l2: 'L2 設定',
|
||||
nav_mirror: 'ミラーリング',
|
||||
nav_lag: 'リンクアグリゲーション',
|
||||
nav_eee: 'EEE',
|
||||
nav_bandwidth: '帯域制限',
|
||||
nav_system: 'システム設定',
|
||||
nav_fw_update: 'ファームウェア更新',
|
||||
|
||||
port_name: '名前',
|
||||
port_status: '状態',
|
||||
port_not_enabled: '無効',
|
||||
port_link_speed: 'リンク速度',
|
||||
port_vendor: 'ベンダー',
|
||||
port_model: 'モデル',
|
||||
port_serial: 'シリアル',
|
||||
port_temp: '温度',
|
||||
port_vcc: '電圧',
|
||||
port_tx_fault: 'TX 障害',
|
||||
port_tx_disabled: 'TX 無効',
|
||||
port_tx_bias: 'TX バイアス',
|
||||
port_tx_power: 'TX 電力',
|
||||
port_rx_power: 'RX 電力',
|
||||
port_rx_los: 'RX 信号ロス',
|
||||
|
||||
speed_disabled: '無効',
|
||||
speed_down: 'リンクダウン',
|
||||
|
||||
port_title: 'FreeSwitchOS ポート設定',
|
||||
port_heading: 'ポート設定',
|
||||
port_col_port: 'ポート',
|
||||
port_col_name: '名前',
|
||||
port_col_speed: '現在のリンク速度',
|
||||
port_col_set_speed: '速度設定',
|
||||
port_col_disabled: '無効',
|
||||
port_col_devices: '接続デバイス',
|
||||
port_devices: 'デバイス',
|
||||
port_col_apply: '適用',
|
||||
port_mtu_heading: 'ポートの最大フレームサイズ (MTU) 設定',
|
||||
port_auto: '自動',
|
||||
port_2500m: '2500Mbps/全二重',
|
||||
port_1000m: '1000Mbps/全二重',
|
||||
port_100m_f: '100Mbps/全二重',
|
||||
port_100m_h: '100Mbps/半二重',
|
||||
port_10m_f: '10Mbps/全二重',
|
||||
port_10m_h: '10Mbps/半二重',
|
||||
port_apply: '適用',
|
||||
|
||||
stat_title: 'FreeSwitchOS ポート統計',
|
||||
stat_heading: 'ポート統計',
|
||||
stat_detailed: '詳細ポート統計',
|
||||
stat_close: '閉じる',
|
||||
stat_col_port: 'ポート',
|
||||
stat_col_name: '名前',
|
||||
stat_col_link: 'リンク',
|
||||
stat_col_tx_good: 'TX 正常',
|
||||
stat_col_tx_bad: 'TX 異常',
|
||||
stat_col_rx_good: 'RX 正常',
|
||||
stat_col_rx_bad: 'RX 異常',
|
||||
stat_col_all: '全カウンタ',
|
||||
stat_counter: 'カウンタ',
|
||||
stat_value: '値',
|
||||
stat_show: '表示',
|
||||
|
||||
vlan_title: 'FreeSwitchOS VLAN 設定',
|
||||
vlan_heading: 'VLAN 設定',
|
||||
vlan_select: 'VLAN 選択:',
|
||||
vlan_choose: '— VLAN 選択 —',
|
||||
vlan_id: 'VLAN ID:',
|
||||
vlan_get_config: '設定取得',
|
||||
vlan_name: 'VLAN 名:',
|
||||
vlan_tagged: 'タグ付きポート',
|
||||
vlan_untagged: 'タグ無しポート',
|
||||
vlan_select_all: 'すべて選択',
|
||||
vlan_pvid: '受信トラフィックのデフォルト VLAN (PVID)',
|
||||
vlan_update: '更新 / 作成',
|
||||
vlan_configured: '設定済み VLAN',
|
||||
vlan_col_name: '名前',
|
||||
vlan_col_member: 'メンバーポート',
|
||||
vlan_col_tagged: 'タグ付きポート',
|
||||
vlan_col_untagged: 'タグ無しポート',
|
||||
vlan_col_pvid: 'PVID ポート',
|
||||
vlan_col_delete: '削除',
|
||||
vlan_set_id_first: 'VLAN ID を先に設定してください',
|
||||
vlan_delete_confirm: 'VLAN 削除 ',
|
||||
|
||||
lag_title: 'リンクアグリゲーション設定',
|
||||
lag_heading: 'リンクアグリゲーショングループ設定',
|
||||
lag_update: '更新 / 作成',
|
||||
|
||||
mirror_title: 'ミラーリング設定',
|
||||
mirror_heading: 'ミラーリング設定',
|
||||
mirror_enabled: '有効:',
|
||||
mirror_port: 'ミラーポート:',
|
||||
mirror_tx: 'ミラー元ポート (TX)',
|
||||
mirror_rx: 'ミラー元ポート (RX)',
|
||||
mirror_update: '更新 / 作成',
|
||||
mirror_disable: 'ミラーリング無効化',
|
||||
mirror_set_port_first: 'ミラーポートを先に設定してください',
|
||||
mirror_select_ports: 'ミラー元ポートを選択してください',
|
||||
|
||||
eee_title: 'EEE 設定',
|
||||
eee_heading: 'EEE 状態',
|
||||
eee_advertising: 'EEE アドバタイジング',
|
||||
eee_partner: 'リンクパートナー広告',
|
||||
eee_port: 'ポート',
|
||||
eee_active: '有効?',
|
||||
eee_enable: 'EEE 有効化',
|
||||
eee_disable: 'EEE 無効化',
|
||||
eee_on: 'オン',
|
||||
eee_off: 'オフ',
|
||||
|
||||
l2_title: 'FreeSwitchOS L2 設定',
|
||||
l2_heading: 'L2 設定',
|
||||
l2_col_port: 'ポート',
|
||||
l2_col_type: 'タイプ',
|
||||
l2_col_remove: 'エントリ削除',
|
||||
l2_shown: 'Shown:',
|
||||
l2_delete: '削除',
|
||||
l2_static: '静的',
|
||||
l2_learned: '学習',
|
||||
|
||||
bw_title: '入力/出力帯域制限',
|
||||
bw_heading: '入力/出力帯域制限',
|
||||
bw_ingress: '入力',
|
||||
bw_egress: '出力',
|
||||
bw_col_port: 'ポート',
|
||||
bw_col_limit: '制限',
|
||||
bw_col_bandwidth: '帯域 [kbps]',
|
||||
bw_col_flow: 'フロー制御',
|
||||
bw_col_apply: '適用',
|
||||
bw_unlimited: '制限無し',
|
||||
|
||||
sys_title: 'システム設定',
|
||||
sys_tab_system: 'システム',
|
||||
sys_tab_advanced: '詳細設定',
|
||||
sys_tab_console: 'コンソール',
|
||||
sys_heading: 'システム設定',
|
||||
sys_ip: 'IP アドレス:',
|
||||
sys_model: 'モデル:',
|
||||
sys_hostname: 'ホスト名:',
|
||||
sys_apply: '適用',
|
||||
sys_netmask: 'ネットマスク:',
|
||||
sys_gateway: 'ゲートウェイ:',
|
||||
sys_language: '言語:',
|
||||
sys_mgmt_vlan: 'Management VLAN:',
|
||||
sys_mgmt_untagged: 'untagged',
|
||||
sys_mgmt_confirm: 'Move switch management to VLAN ',
|
||||
sys_mgmt_warn: 'The switch will start tagging its own traffic with that VLAN. If the port you are connected through does not carry it, this page becomes unreachable and the setting can only be undone over the console. Continue?',
|
||||
sys_ip_note: '上記設定を変更した場合は、ブラウザで新しい IP にアクセスしてください:',
|
||||
sys_update: '設定更新',
|
||||
sys_save_label: '現在の設定をフラッシュに保存:',
|
||||
sys_save: '設定をフラッシュに保存',
|
||||
sys_advanced: '詳細設定',
|
||||
sys_startup_config: '起動設定:',
|
||||
sys_startup_warn: '起動設定を直接編集する際は注意してください。ロックアウトされる可能性があります:',
|
||||
sys_clear_config: '起動設定クリア',
|
||||
sys_save_startup: '起動設定をフラッシュに保存',
|
||||
sys_reset: 'スイッチ再起動',
|
||||
sys_console: 'コンソールコマンド',
|
||||
sys_enter_cmd: 'コマンド入力:',
|
||||
sys_send_cmd: 'コマンド送信',
|
||||
sys_console_warn: 'コンソールコマンドは注意して入力してください。ロックアウトされる可能性があります!',
|
||||
sys_invalid_ip: '無効な IP: ',
|
||||
sys_reset_confirm: 'スイッチを再起動してもよろしいですか?',
|
||||
sys_resetting: 'スイッチを再起動中です。しばらく待ってからページをリロードしてください。',
|
||||
|
||||
login_title: 'RTL スイッチ ログイン',
|
||||
login_heading: 'RTL スイッチ ログイン',
|
||||
login_wrong: 'パスワードが違います!',
|
||||
login_password: 'パスワード',
|
||||
login_login: 'ログイン',
|
||||
|
||||
index_title: 'FreeSwitchOS メインページ',
|
||||
index_heading: 'スイッチ設定',
|
||||
index_settings: '設定',
|
||||
|
||||
update_title: 'ファームウェア更新',
|
||||
update_heading: 'ファームウェア更新',
|
||||
update_instruction: 'アップロードするファームウェアファイルを選択:',
|
||||
update_upload: 'ファイルをアップロード',
|
||||
|
||||
common_port: 'ポート ',
|
||||
common_pkts: ' pkts',
|
||||
},
|
||||
|
||||
zh: {
|
||||
nav_overview: '概览',
|
||||
nav_port_config: '端口配置',
|
||||
nav_port_stat: '端口统计',
|
||||
nav_l2: 'L2 配置',
|
||||
nav_mirror: '端口镜像',
|
||||
nav_lag: '链路聚合',
|
||||
nav_eee: 'EEE',
|
||||
nav_bandwidth: '带宽限制',
|
||||
nav_system: '系统设置',
|
||||
nav_fw_update: '固件升级',
|
||||
|
||||
port_name: '名称',
|
||||
port_status: '状态',
|
||||
port_not_enabled: '未启用。',
|
||||
port_link_speed: '链路速率',
|
||||
port_vendor: '厂商',
|
||||
port_model: '型号',
|
||||
port_serial: '序列号',
|
||||
port_temp: '温度',
|
||||
port_vcc: '供电电压',
|
||||
port_tx_fault: 'TX 故障',
|
||||
port_tx_disabled: 'TX 禁用',
|
||||
port_tx_bias: 'TX 偏置电流',
|
||||
port_tx_power: 'TX 光功率',
|
||||
port_rx_power: 'RX 光功率',
|
||||
port_rx_los: 'RX 信号丢失',
|
||||
|
||||
speed_disabled: '禁用',
|
||||
speed_down: '未连接',
|
||||
|
||||
port_title: 'FreeSwitchOS 端口配置',
|
||||
port_heading: '端口配置',
|
||||
port_col_port: '端口',
|
||||
port_col_name: '名称',
|
||||
port_col_speed: '当前链路速率',
|
||||
port_col_set_speed: '设置速率',
|
||||
port_col_disabled: '禁用',
|
||||
port_col_devices: '已连接设备',
|
||||
port_devices: '台设备',
|
||||
port_col_apply: '应用',
|
||||
port_mtu_heading: '配置端口转发的最大帧大小 (MTU)',
|
||||
port_auto: '自动',
|
||||
port_2500m: '2500Mbps/全双工',
|
||||
port_1000m: '1000Mbps/全双工',
|
||||
port_100m_f: '100Mbps/全双工',
|
||||
port_100m_h: '100Mbps/半双工',
|
||||
port_10m_f: '10Mbps/全双工',
|
||||
port_10m_h: '10Mbps/半双工',
|
||||
port_apply: '应用',
|
||||
|
||||
stat_title: 'FreeSwitchOS 端口统计',
|
||||
stat_heading: '端口统计',
|
||||
stat_detailed: '详细端口统计',
|
||||
stat_close: '关闭',
|
||||
stat_col_port: '端口',
|
||||
stat_col_name: '名称',
|
||||
stat_col_link: '链路',
|
||||
stat_col_tx_good: 'TX 正常包',
|
||||
stat_col_tx_bad: 'TX 错误包',
|
||||
stat_col_rx_good: 'RX 正常包',
|
||||
stat_col_rx_bad: 'RX 错误包',
|
||||
stat_col_all: '所有计数器',
|
||||
stat_counter: '计数器',
|
||||
stat_value: '值',
|
||||
stat_show: '查看',
|
||||
|
||||
vlan_title: 'FreeSwitchOS VLAN 配置',
|
||||
vlan_heading: 'VLAN 配置',
|
||||
vlan_select: 'VLAN 选择:',
|
||||
vlan_choose: '-- 请选择 VLAN --',
|
||||
vlan_id: 'VLAN ID:',
|
||||
vlan_get_config: '获取配置',
|
||||
vlan_name: 'VLAN 名称:',
|
||||
vlan_tagged: 'Tagged 端口',
|
||||
vlan_untagged: 'Untagged 端口',
|
||||
vlan_select_all: '全选',
|
||||
vlan_pvid: '作为入方向流量的默认 VLAN (PVID)',
|
||||
vlan_update: '更新 / 创建',
|
||||
vlan_configured: '已配置 VLAN',
|
||||
vlan_col_name: '名称',
|
||||
vlan_col_member: '成员端口',
|
||||
vlan_col_tagged: 'Tagged 端口',
|
||||
vlan_col_untagged: 'Untagged 端口',
|
||||
vlan_col_pvid: 'PVID 端口',
|
||||
vlan_col_delete: '删除',
|
||||
vlan_set_id_first: '请先设置 VLAN ID',
|
||||
vlan_delete_confirm: '删除 VLAN ',
|
||||
|
||||
lag_title: '链路聚合配置',
|
||||
lag_heading: '链路聚合组配置',
|
||||
lag_update: '更新 / 创建',
|
||||
|
||||
mirror_title: '端口镜像配置',
|
||||
mirror_heading: '端口镜像配置',
|
||||
mirror_enabled: '启用:',
|
||||
mirror_port: '镜像目的端口:',
|
||||
mirror_tx: '被镜像端口 (TX)',
|
||||
mirror_rx: '被镜像端口 (RX)',
|
||||
mirror_update: '更新 / 创建',
|
||||
mirror_disable: '禁用端口镜像',
|
||||
mirror_set_port_first: '请先设置镜像目的端口',
|
||||
mirror_select_ports: '请选择被镜像端口',
|
||||
|
||||
eee_title: 'EEE 配置',
|
||||
eee_heading: 'EEE 状态',
|
||||
eee_advertising: '本端通告',
|
||||
eee_partner: '链路伙伴通告',
|
||||
eee_port: '端口',
|
||||
eee_active: '已生效?',
|
||||
eee_enable: '启用 EEE',
|
||||
eee_disable: '禁用 EEE',
|
||||
eee_on: '开',
|
||||
eee_off: '关',
|
||||
|
||||
l2_title: 'FreeSwitchOS L2 配置',
|
||||
l2_heading: 'L2 配置',
|
||||
l2_col_port: '端口',
|
||||
l2_col_type: '类型',
|
||||
l2_col_remove: '删除条目',
|
||||
l2_shown: 'Shown:',
|
||||
l2_delete: '删除',
|
||||
l2_static: '静态',
|
||||
l2_learned: '动态学习',
|
||||
|
||||
bw_title: '入方向/出方向带宽限制',
|
||||
bw_heading: '入方向/出方向带宽限制',
|
||||
bw_ingress: '入方向',
|
||||
bw_egress: '出方向',
|
||||
bw_col_port: '端口',
|
||||
bw_col_limit: '限速',
|
||||
bw_col_bandwidth: '带宽 [kbit/s]',
|
||||
bw_col_flow: '流量控制',
|
||||
bw_col_apply: '应用',
|
||||
bw_unlimited: '不限速',
|
||||
|
||||
sys_title: '系统设置',
|
||||
sys_tab_system: '系统',
|
||||
sys_tab_advanced: '高级',
|
||||
sys_tab_console: '控制台',
|
||||
sys_heading: '系统设置',
|
||||
sys_ip: 'IP 地址:',
|
||||
sys_model: '型号:',
|
||||
sys_hostname: '主机名:',
|
||||
sys_apply: '应用',
|
||||
sys_netmask: '子网掩码:',
|
||||
sys_gateway: '网关:',
|
||||
sys_language: '语言:',
|
||||
sys_mgmt_vlan: 'Management VLAN:',
|
||||
sys_mgmt_untagged: 'untagged',
|
||||
sys_mgmt_confirm: 'Move switch management to VLAN ',
|
||||
sys_mgmt_warn: 'The switch will start tagging its own traffic with that VLAN. If the port you are connected through does not carry it, this page becomes unreachable and the setting can only be undone over the console. Continue?',
|
||||
sys_ip_note: '更新上述设置后,请使用新的 IP 地址重新访问管理界面:',
|
||||
sys_update: '更新设置',
|
||||
sys_save_label: '将当前全部设置保存到 Flash:',
|
||||
sys_save: '保存设置到 Flash',
|
||||
sys_advanced: '高级设置',
|
||||
sys_startup_config: '启动配置:',
|
||||
sys_startup_warn: '直接编辑启动配置时请谨慎,错误配置可能导致无法访问设备:',
|
||||
sys_clear_config: '清除启动配置',
|
||||
sys_save_startup: '保存启动配置到 Flash',
|
||||
sys_reset: '重启交换机',
|
||||
sys_console: '控制台命令',
|
||||
sys_enter_cmd: '输入命令:',
|
||||
sys_send_cmd: '发送命令',
|
||||
sys_console_warn: '输入控制台命令时请谨慎,错误命令可能导致无法访问设备!',
|
||||
sys_invalid_ip: '无效 IP: ',
|
||||
sys_reset_confirm: '确定要重启交换机吗?',
|
||||
sys_resetting: '交换机正在重启。请稍候并刷新页面。',
|
||||
|
||||
login_title: 'RTL 交换机登录',
|
||||
login_heading: 'RTL 交换机登录',
|
||||
login_wrong: '密码错误!',
|
||||
login_password: '密码',
|
||||
login_login: '登录',
|
||||
|
||||
index_title: 'FreeSwitchOS 主页',
|
||||
index_heading: '交换机配置',
|
||||
index_settings: '设置',
|
||||
|
||||
update_title: '固件升级',
|
||||
update_heading: '固件升级',
|
||||
update_instruction: '请选择要上传的固件文件:',
|
||||
update_upload: '上传文件',
|
||||
|
||||
common_port: '端口 ',
|
||||
common_pkts: ' 个包',
|
||||
}
|
||||
};
|
||||
|
||||
var rtlLang = (function() {
|
||||
var saved = localStorage.getItem('rtl_lang');
|
||||
if (saved && LANG[saved]) return saved;
|
||||
var browser = (navigator.language || navigator.userLanguage || 'en').substring(0, 2);
|
||||
return LANG[browser] ? browser : 'en';
|
||||
})();
|
||||
|
||||
function t(key) {
|
||||
return LANG[rtlLang][key] || LANG['en'][key] || key;
|
||||
}
|
||||
|
||||
function setLang(lang) {
|
||||
if (LANG[lang]) {
|
||||
localStorage.setItem('rtl_lang', lang);
|
||||
rtlLang = lang;
|
||||
document.querySelectorAll('[data-i18n]').forEach(function(el) {
|
||||
applyTranslation(el);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function applyTranslation(el) {
|
||||
var key = el.getAttribute('data-i18n');
|
||||
if (!key) return;
|
||||
if (el.tagName === 'INPUT' && (el.type === 'submit' || el.type === 'button')) {
|
||||
el.value = t(key);
|
||||
} else if (el.tagName === 'OPTION') {
|
||||
el.textContent = t(key);
|
||||
} else if (el.tagName === 'TITLE') {
|
||||
el.textContent = t(key);
|
||||
} else {
|
||||
el.innerHTML = t(key);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('[data-i18n]').forEach(function(el) {
|
||||
applyTranslation(el);
|
||||
});
|
||||
});
|
||||
+291
-13
@@ -1,23 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/main_info.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<script>
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
const interval = setInterval(update, 2000);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="index_title">FreeSwitchOS Main Page</title>
|
||||
<style>
|
||||
.popup {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.popup-content {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
height: 80%
|
||||
}
|
||||
.popup .popup-content {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.tab-bar { display: flex; border-bottom: 2px solid #226; margin-bottom: 0; padding: 1px 16px; padding-bottom: 0; }
|
||||
.tab-btn { padding: 10px 20px; background-color: #ddf; border: none; cursor: pointer; font-size: 1em; border-radius: 8px 8px 0 0; margin-right: 4px; }
|
||||
.tab-btn:hover { background-color: #aaf; }
|
||||
.tab-btn.active { background-color: #aaf; font-weight: bold; border-bottom: 2px solid #aaf; margin-bottom: -2px; }
|
||||
.tab-content { display: none; }
|
||||
.tab-content.active { display: block; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<div id="ports" style="margin-left:16%;padding:1px 16px"></div>
|
||||
|
||||
<div class="page" id="page-overview" style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<h1 data-i18n="index_heading">Switch Configuration</h1>
|
||||
<table id="infoTable">
|
||||
<tr>
|
||||
@@ -27,6 +45,266 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
|
||||
<div class="page" id="page-ports" style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<h1 data-i18n="port_heading">Port Configuration</h1>
|
||||
<table id="speedtable">
|
||||
<tr> <th data-i18n="port_col_port">Port</th> <th data-i18n="port_col_name">Name</th> <th data-i18n="port_col_speed">Current Link Speed</th><th data-i18n="port_col_devices">Connected devices</th><th data-i18n="port_col_set_speed">Set Speed</th><th data-i18n="port_col_disabled">Disabled</th><th data-i18n="port_col_apply">Apply</th></tr>
|
||||
</table>
|
||||
<h2 style="margin-top:3em" data-i18n="port_mtu_heading">Configure Maximum Frame Size (MTU) forwarded at Port</h2>
|
||||
<table id="mtutable" style="margin-top:1em">
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="page" id="page-stat" style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="popup" class="popup">
|
||||
<div class="popup-content">
|
||||
<h2 data-i18n="stat_detailed">Detailed Port Statistics</h2>
|
||||
<div id="popup_text"></div>
|
||||
<button id="closePopup" class="action" data-i18n="stat_close">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<h1 data-i18n="stat_heading">Port Statistics</h1>
|
||||
<table id="statstable">
|
||||
<tr> <th data-i18n="stat_col_port">Port</th> <th data-i18n="stat_col_name">Name</th> <th data-i18n="stat_col_link">link</th> <th data-i18n="stat_col_tx_good">TX Good</th> <th data-i18n="stat_col_tx_bad">TX Bad</th> <th data-i18n="stat_col_rx_good">RX Good</th> <th data-i18n="stat_col_rx_bad">RX Bad</th> <th data-i18n="stat_col_all"> All Counters </th></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="page" id="page-vlan" style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<h1 data-i18n="vlan_heading">VLAN Configuration</h1>
|
||||
<form id="vform">
|
||||
<div>
|
||||
<label for="vlanSelect" data-i18n="vlan_select">VLAN Select:</label>
|
||||
<select id="vlanSelect" style="margin: 0 0 0 8px">
|
||||
<option value="" disabled selected data-i18n="vlan_choose">— VLAN Choose —</option>
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
<div>
|
||||
<label for="vid" data-i18n="vlan_id">VLAN ID:</label>
|
||||
<input type="number" min="1" max="4094" id="vid" name="vid">
|
||||
<button type="button" style="margin: 0 0 0 24px" onclick="fetchVLAN();" data-i18n="vlan_get_config">Get Configuration</button>
|
||||
</div>
|
||||
<br/><br/>
|
||||
<label for="vname" data-i18n="vlan_name">VLAN Name:</label>
|
||||
<input type="text" id="vname" name="vname"><br><br>
|
||||
<br/>
|
||||
<h2 data-i18n="vlan_tagged">Tagged Ports</h2>
|
||||
<div id="tPorts"><button type="button" style="transform: translateY(-100%);margin: 0 50px 0 0" onclick="utClicked(true);" data-i18n="vlan_select_all">Select all</button></div>
|
||||
<h2 data-i18n="vlan_untagged">Untagged Ports</h2>
|
||||
<div id="uPorts"><button type="button" style="transform: translateY(-100%); margin: 0 50px 0 0" onclick="utClicked(false);" data-i18n="vlan_select_all">Select all</button> </div>
|
||||
<h2 data-i18n="vlan_pvid">Use as default VLAN for incoming traffic (PVID)</h2>
|
||||
<div id="pPorts"><button type="button" style="transform: translateY(-100%); margin: 0 50px 0 0" onclick="pvClicked(true);" data-i18n="vlan_select_all">Select all</button> </div>
|
||||
<br/> <input style="width:40%;" class="action" id="vlan_sub" onclick="vlanSub();" type="button" data-i18n="vlan_update" value="Update / Create">
|
||||
</form>
|
||||
<h2 data-i18n="vlan_configured">Configured VLANs</h2>
|
||||
<table id="vlanTable" style="width:90%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>VLAN</th>
|
||||
<th data-i18n="vlan_col_name">Name</th>
|
||||
<th data-i18n="vlan_col_member">Member Ports</th>
|
||||
<th data-i18n="vlan_col_tagged">Tagged Ports</th>
|
||||
<th data-i18n="vlan_col_untagged">Untagged Ports</th>
|
||||
<th data-i18n="vlan_col_pvid">PVID Ports</th>
|
||||
<th data-i18n="vlan_col_delete">Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="vlanTableBody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="page" id="page-l2" style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<h1 data-i18n="l2_heading">L2 Configuration</h1>
|
||||
<p><span data-i18n="l2_shown">Shown:</span> <span id="l2count">-</span></p>
|
||||
<table id="l2table">
|
||||
<tr>
|
||||
<th><span class="l2sort" onclick="l2SortBy('port')"><span data-i18n="l2_col_port">Port</span><span id="l2a_port" class="l2arrow"></span></span><br>
|
||||
<input id="l2f_port" class="l2filter" oninput="l2FilterChanged()" size="5"></th>
|
||||
<th><span class="l2sort" onclick="l2SortBy('mac')">MAC<span id="l2a_mac" class="l2arrow"></span></span><br>
|
||||
<input id="l2f_mac" class="l2filter" oninput="l2FilterChanged()" size="14"></th>
|
||||
<th><span class="l2sort" onclick="l2SortBy('vlan')">VLAN<span id="l2a_vlan" class="l2arrow"></span></span><br>
|
||||
<input id="l2f_vlan" class="l2filter" oninput="l2FilterChanged()" size="5"></th>
|
||||
<th><span class="l2sort" onclick="l2SortBy('type')"><span data-i18n="l2_col_type">Type</span><span id="l2a_type" class="l2arrow"></span></span><br>
|
||||
<input id="l2f_type" class="l2filter" oninput="l2FilterChanged()" size="8"></th>
|
||||
<th data-i18n="l2_col_remove">Remove Entry</th></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="page" id="page-stp" style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<h1>Spanning Tree (RSTP)</h1>
|
||||
<h2>Mode: <select id="stpMode"><option value="off">Disabled</option><option value="on">Enabled</option></select>
|
||||
<input style="width:15%;margin-left: 3em;" class="action" id="stp_sub" onclick="stpSub();" type="button" value="Apply"></h2>
|
||||
<div id="stpStat" style="font-family:monospace;"></div>
|
||||
<h2>Bridge settings</h2>
|
||||
<table id="stpBridge">
|
||||
<tr>
|
||||
<th>Priority</th><th>Version</th><th>Hello [s]</th><th>Max age [s]</th><th>Fwd delay [s]</th><th>Tx hold</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><select id="bPrio"></select></td>
|
||||
<td><select id="bVer"><option value="rstp">RSTP</option><option value="stp">STP compat</option></select></td>
|
||||
<td><input id="bHello" type="number" min="1" max="10" style="width:4em"></td>
|
||||
<td><input id="bMaxage" type="number" min="6" max="40" style="width:4em"></td>
|
||||
<td><input id="bFwd" type="number" min="4" max="30" style="width:4em"></td>
|
||||
<td><input id="bTxhold" type="number" min="1" max="10" style="width:4em"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="font-size:small">Changes apply immediately. Edge ports skip the listen period; guard/filter act on received BPDUs.</p>
|
||||
<h2>Port configuration</h2>
|
||||
<table id="stpPortsTbl">
|
||||
<tr>
|
||||
<th>Port</th><th>State</th><th>Path Cost<br><span style="font-weight:normal;font-size:small">(0 = Auto)</span></th><th>Priority</th><th>Edge Port</th><th>BPDU Filter</th><th>Guard</th><th>Point-to-Point</th>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>Port status</h2>
|
||||
<table id="stpStatTbl">
|
||||
<tr>
|
||||
<th>Port</th><th>Port State</th><th>Role</th><th>Designated Bridge</th><th>Designated Port ID</th><th>Designated Cost</th><th>Oper. Edge</th><th>Oper. P2P</th>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="page" id="page-mirror" style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<h1 data-i18n="mirror_heading">Mirror Configuration</h1>
|
||||
<label class="tswitch"><span data-i18n="mirror_enabled">Enabled:</span> <input id="me" type="checkbox"></label><br/>
|
||||
<label for="mp"><span data-i18n="mirror_port">Mirroring Port:</span></label> <input type="number" id="mp" name="mp" min="1" max="9"/>
|
||||
<h2 data-i18n="mirror_tx">Mirrored Ports (TX)</h2>
|
||||
<div id="mPortsTX"></div>
|
||||
<br />
|
||||
<h2 data-i18n="mirror_rx">Mirrored Ports (RX)</h2>
|
||||
<div id="mPortsRX"></div>
|
||||
<br/> <input style="width:15%;" class="action" id="mirror_sub" onclick="mirrorSub();" type="button" data-i18n="mirror_update" value="Update / Create">
|
||||
<input style="width:15%;" class="action" id="mirror_del" onclick="mirrorDel();" type="button" data-i18n="mirror_disable" value="Disable Mirroring">
|
||||
</div>
|
||||
|
||||
<div class="page" id="page-lag" style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<h1 data-i18n="lag_heading">Link Aggregation Groups Configuration</h1>
|
||||
<h2>LAG 1 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub0" onclick="lagSub(0);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
|
||||
<div id="mLAG0"></div>
|
||||
<br />
|
||||
<h2>LAG 2 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub1" onclick="lagSub(1);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
|
||||
<div id="mLAG1"></div>
|
||||
<br />
|
||||
<h2>LAG 3 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub2" onclick="lagSub(2);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
|
||||
<div id="mLAG2"></div>
|
||||
<br />
|
||||
<h2>LAG 4 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub3" onclick="lagSub(3);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
|
||||
<div id="mLAG3"></div>
|
||||
</div>
|
||||
|
||||
<div class="page" id="page-eee" style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<h1 data-i18n="eee_heading">EEE Status</h1>
|
||||
<table id="eeetable">
|
||||
<tr> <th> </th> <th colspan="3" data-i18n="eee_advertising"> Advertising </th> <th colspan="3" data-i18n="eee_partner">Link-Partner advertises</th> <th></th></tr>
|
||||
<tr> <th data-i18n="eee_port">Port</th> <th>2.5G</th> <th>1G</th> <th>100M</th> <th>2.5G</th> <th>1G</th> <th>100M</th> <th data-i18n="eee_active">Active?</th></tr>
|
||||
</table>
|
||||
<div>
|
||||
<input style="width:20%;" class="action" id="eee_enable" onclick="eeeSub(0, 1);" type="button" data-i18n="eee_enable" value="Enable EEE">
|
||||
<input style="width:20%;" class="action" id="eee_disable" onclick="eeeSub(0, 0);" type="button" data-i18n="eee_disable" value="Disable EEE">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page" id="page-bandwidth" style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<h1 data-i18n="bw_heading">Ingress and Egress Bandwidth</h1>
|
||||
<table id="bwtable">
|
||||
<tr> <th> </th> <th colspan="3" data-i18n="bw_ingress"> Ingress </th> <th colspan="2" data-i18n="bw_egress">Egress</th> <th></th></tr>
|
||||
<tr> <th data-i18n="bw_col_port">Port</th> <th data-i18n="bw_col_limit">Limit</th> <th data-i18n="bw_col_bandwidth">Bandwidth [kBit/s]</th> <th data-i18n="bw_col_flow">Flow Control</th> <th data-i18n="bw_col_limit">Limit</th> <th data-i18n="bw_col_bandwidth">Bandwidth [kBit/s]</th> <th data-i18n="bw_col_apply">Apply</th></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="page" id="page-system" style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div class="tab-bar">
|
||||
<button class="tab-btn active" onclick="openTab(event, 'system-tab')" data-i18n="sys_tab_system">System</button>
|
||||
<button class="tab-btn" onclick="openTab(event, 'advanced-tab')" data-i18n="sys_tab_advanced">Advanced</button>
|
||||
<button class="tab-btn" onclick="openTab(event, 'console-tab')" data-i18n="sys_tab_console">Console</button>
|
||||
</div>
|
||||
<div id="system-tab" class="tab-content active">
|
||||
<h1 data-i18n="sys_heading">System Settings</h1>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="hostname" data-i18n="sys_hostname">Hostname:</label></div>
|
||||
<div class="rcol"> <input id="hostname" type="text" maxlength="23" size="20"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="ip" data-i18n="sys_ip">IP address:</label></div>
|
||||
<div class="rcol"> <input id="ip" type="text" size="15"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="netmask" data-i18n="sys_netmask">Netmask:</label></div>
|
||||
<div class="rcol"> <input id="netmask" type="text" size="15"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="gw" data-i18n="sys_gateway">Gateway:</label></div>
|
||||
<div class="rcol"> <input id="gw" type="text" size="15"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="model" data-i18n="sys_model">Model:</label></div>
|
||||
<div class="rcol"> <span id="model">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="lang-select" data-i18n="sys_language">Language:</label></div>
|
||||
<div class="rcol">
|
||||
<select id="lang-select" onchange="changeLang()">
|
||||
<option value="en">English</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="zh">中文</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="mgmtvlan" data-i18n="sys_mgmt_vlan">Management VLAN:</label></div>
|
||||
<div class="rcol">
|
||||
<select id="mgmtvlan" onchange="mgmtVlanChanged()">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<span data-i18n="sys_ip_note">When updating the above settings, remember to point your browser to the new IP afterwards:</span><br/>
|
||||
<input style="width:40%;" class="action" id="ip_sub" onclick="ipSub();" type="button" data-i18n="sys_update" value="Update Settings"><br/>
|
||||
<br/>
|
||||
<span data-i18n="sys_save_label">Save all current settings to Flash:</span><br/>
|
||||
<input style="width:40%;" class="action" id="flash_sub" onclick="flashSave();" type="button" data-i18n="sys_save" value="Save Settings to Flash">
|
||||
</div>
|
||||
|
||||
<div id="advanced-tab" class="tab-content">
|
||||
<h1 data-i18n="sys_advanced">Advanced Settings</h1>
|
||||
<div class="lcol"> <label for="config_display" data-i18n="sys_startup_config">Startup configuration:</label></div>
|
||||
<textarea id="config_display" rows="8" cols="60"></textarea>
|
||||
<br/><br/>
|
||||
<span data-i18n="sys_startup_warn">Be careful when saving the directly edited startup configuration, you can lock yourself out:</span><br/>
|
||||
<input style="width:40%;" class="action" id="clear_config" onclick="clearConfig();" type="button" data-i18n="sys_clear_config" value="Clear Startup Config">
|
||||
<br/>
|
||||
<input style="width:40%;" class="action" id="flash_startup_sub" onclick="flashStartupSave();" type="button" data-i18n="sys_save_startup" value="Save Startup Settings to Flash">
|
||||
<br/>
|
||||
<input style="width:40%;" class="action" id="switch_reset" onclick="resetSwitch();" type="button" data-i18n="sys_reset" value="Reset Switch">
|
||||
</div>
|
||||
|
||||
<div id="console-tab" class="tab-content">
|
||||
<h1 data-i18n="sys_console">Console Command</h1>
|
||||
<label for="console_command" data-i18n="sys_enter_cmd">Enter command:</label>
|
||||
<input type="text" id="console_cmd" name="console_cmd" style="width:40%;">
|
||||
<input style="width:20%;" class="action" id="cmd_sub" onclick="cmdSub();" type="button" data-i18n="sys_send_cmd" value="Send Command"><br/>
|
||||
<br/><br/>
|
||||
<span data-i18n="sys_console_warn">Be careful when entering console commands, you can lock yourself out!</span><br/>
|
||||
<br/>
|
||||
<pre id="console_out" style="height:22em;overflow:auto;white-space:pre-wrap;border:1px solid;padding:0.5em;"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page" id="page-update" style="margin-left:16%;padding:1px 16px;height:1000px;width:40%;">
|
||||
<h1 data-i18n="update_heading">Firmware Update</h1>
|
||||
<form enctype="multipart/form-data" action="/upload" method="POST">
|
||||
<input type="hidden" name="MAX_FILE_SIZE" value="1000000" />
|
||||
<span data-i18n="update_instruction">Choose a firmware update file to upload:</span> <br/> <br/>
|
||||
<input name="uploadedfile" type="file" accept=".bin" /><br />
|
||||
<input style="margin-top:3em" type="submit" data-i18n="update_upload" value="Upload File" />
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,30 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="l2_title">FreeSwitchOS L2 Configuration</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="l2_heading">L2 Configuration</h1>
|
||||
<p><span data-i18n="l2_shown">Shown:</span> <span id="l2count">-</span></p>
|
||||
<table id="l2table">
|
||||
<tr>
|
||||
<th><span class="l2sort" onclick="l2SortBy('port')"><span data-i18n="l2_col_port">Port</span><span id="l2a_port" class="l2arrow"></span></span><br>
|
||||
<input id="l2f_port" class="l2filter" oninput="l2FilterChanged()" size="5"></th>
|
||||
<th><span class="l2sort" onclick="l2SortBy('mac')">MAC<span id="l2a_mac" class="l2arrow"></span></span><br>
|
||||
<input id="l2f_mac" class="l2filter" oninput="l2FilterChanged()" size="14"></th>
|
||||
<th><span class="l2sort" onclick="l2SortBy('vlan')">VLAN<span id="l2a_vlan" class="l2arrow"></span></span><br>
|
||||
<input id="l2f_vlan" class="l2filter" oninput="l2FilterChanged()" size="5"></th>
|
||||
<th><span class="l2sort" onclick="l2SortBy('type')"><span data-i18n="l2_col_type">Type</span><span id="l2a_type" class="l2arrow"></span></span><br>
|
||||
<input id="l2f_type" class="l2filter" oninput="l2FilterChanged()" size="8"></th>
|
||||
<th data-i18n="l2_col_remove">Remove Entry</th></tr>
|
||||
<script src="/l2.js"></script>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
<script src="/navigation.js"></script>
|
||||
</html>
|
||||
-178
@@ -1,178 +0,0 @@
|
||||
function fillStats() {
|
||||
var tbl = document.getElementById('statstable');
|
||||
if (!numPorts)
|
||||
return;
|
||||
if (tbl.rows.length > 1) {
|
||||
for (let i = 0; i < numPorts; i++) {
|
||||
console.log("Table Update row: " + i + " state " + pState[i] + " is " + linkS[pState[i] +1]);
|
||||
tbl.rows[i+1].cells[1].innerHTML = linkText(pState[i]+1);
|
||||
tbl.rows[i+1].cells[2].innerHTML = `${txG[i]}` + t('common_pkts');
|
||||
tbl.rows[i+1].cells[3].innerHTML = `${txB[i]}` + t('common_pkts');
|
||||
tbl.rows[i+1].cells[4].innerHTML = `${rxG[i]}` + t('common_pkts');
|
||||
tbl.rows[i+1].cells[5].innerHTML = `${rxB[i]}` + t('common_pkts');
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < numPorts; i++) {
|
||||
console.log("Table row: " + i);
|
||||
const tr = tbl.insertRow();
|
||||
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i+1)));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(linkText(pState[i]+1)));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(`${txG[i]}` + t('common_pkts')));
|
||||
td = tr.insertCell();td.appendChild(document.createTextNode(`${txB[i]}` + t('common_pkts')));
|
||||
td = tr.insertCell();td.appendChild(document.createTextNode(`${rxG[i]}` + t('common_pkts')));
|
||||
td = tr.insertCell();td.appendChild(document.createTextNode(`${rxB[i]}` + t('common_pkts')));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function l2CMP(a, b)
|
||||
{
|
||||
if (a.port < b.port)
|
||||
return -1;
|
||||
if (a.port > b.port)
|
||||
return 1;
|
||||
if (a.mac < b.mac)
|
||||
return -1;
|
||||
if (a.mac > b.mac)
|
||||
return 1;
|
||||
if (a.vlan < b.vlan)
|
||||
return -1;
|
||||
if (a.vlan > b.vlan)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function uniq(a) {
|
||||
return a.filter(function(item, pos, ary) {
|
||||
return !pos || item.idx != ary[pos - 1].idx;
|
||||
});
|
||||
}
|
||||
|
||||
function delL2(idx) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
if (JSON.parse(xhttp.responseText).result == 1) {
|
||||
l2All = l2All.filter(function(e) { return e.idx != idx; });
|
||||
renderL2();
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/l2_del.json?idx=" + idx, true);
|
||||
xhttp.timeout = 1500; xhttp.send();
|
||||
}
|
||||
|
||||
var l2All = [];
|
||||
const l2Cols = ['port', 'mac', 'vlan', 'type'];
|
||||
var l2SortCol = 'port';
|
||||
var l2SortDir = 1;
|
||||
|
||||
function l2Key(e, col) {
|
||||
if (col === 'port') {
|
||||
if (e.port === 'CPU') return Number.MAX_SAFE_INTEGER;
|
||||
if (e.lag) return 100 + e.lag;
|
||||
return Number(e.port);
|
||||
}
|
||||
if (col === 'vlan') return Number(e.vlan);
|
||||
return String(e[col]).toLowerCase();
|
||||
}
|
||||
|
||||
function l2SortBy(col) {
|
||||
l2SortDir = (col === l2SortCol) ? -l2SortDir : 1;
|
||||
l2SortCol = col;
|
||||
renderL2();
|
||||
}
|
||||
|
||||
function l2FilterChanged() { renderL2(); }
|
||||
|
||||
function renderL2() {
|
||||
var tbl = document.getElementById('l2table');
|
||||
if (!tbl) return;
|
||||
var f = {};
|
||||
l2Cols.forEach(function(c) {
|
||||
var el = document.getElementById('l2f_' + c);
|
||||
f[c] = el ? el.value.trim().toLowerCase() : '';
|
||||
});
|
||||
var rows = l2All.filter(function(e) {
|
||||
return l2Cols.every(function(c) {
|
||||
return !f[c] || String(e[c]).toLowerCase().indexOf(f[c]) !== -1;
|
||||
});
|
||||
});
|
||||
rows.sort(function(a, b) {
|
||||
var x = l2Key(a, l2SortCol), y = l2Key(b, l2SortCol);
|
||||
return (x < y ? -1 : x > y ? 1 : 0) * l2SortDir;
|
||||
});
|
||||
l2Cols.forEach(function(c) {
|
||||
var a = document.getElementById('l2a_' + c);
|
||||
if (a) a.textContent = (c === l2SortCol) ? (l2SortDir > 0 ? ' \u25b2' : ' \u25bc') : ' \u21c5';
|
||||
});
|
||||
paintL2(tbl, rows);
|
||||
var cnt = document.getElementById('l2count');
|
||||
if (cnt) cnt.textContent = rows.length + ' / ' + l2All.length;
|
||||
}
|
||||
|
||||
function fillL2(s)
|
||||
{
|
||||
var tbl = document.getElementById('l2table');
|
||||
if (!s.length)
|
||||
return;
|
||||
s.sort(l2CMP);
|
||||
s = uniq(s);
|
||||
l2All = s.map(function(e) {
|
||||
if (e.lag)
|
||||
e.port = 'LAG' + e.lag;
|
||||
return e;
|
||||
});
|
||||
renderL2();
|
||||
}
|
||||
|
||||
function delL2Button(e)
|
||||
{
|
||||
if (e.port == 'CPU')
|
||||
return '';
|
||||
return '<button type="button" onclick="delL2(' + e.idx + ');">' + t('l2_delete') + '</button>';
|
||||
}
|
||||
|
||||
function paintL2(tbl, s)
|
||||
{
|
||||
console.log("L2: ", JSON.stringify(s));
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
var e = s[i];
|
||||
console.log(i, e);
|
||||
if (tbl.rows[i+1]) {
|
||||
tbl.rows[i+1].cells[0].innerHTML = `${e.port}`;
|
||||
tbl.rows[i+1].cells[1].innerHTML = `${e.mac}`;
|
||||
tbl.rows[i+1].cells[2].innerHTML = `${e.vlan}`;
|
||||
tbl.rows[i+1].cells[3].innerHTML = `${e.type}`;
|
||||
tbl.rows[i+1].cells[4].innerHTML = delL2Button(e);
|
||||
} else {
|
||||
const tr = tbl.insertRow();
|
||||
let td = tr.insertCell(); td.innerHTML = `${e.port}`;
|
||||
td = tr.insertCell(); td.innerHTML = `${e.mac}`;
|
||||
td = tr.insertCell(); td.innerHTML = `${e.vlan}`;
|
||||
td = tr.insertCell(); td.innerHTML = `${e.type}`;
|
||||
td = tr.insertCell(); td.innerHTML = delL2Button(e);
|
||||
}
|
||||
}
|
||||
for (let i = tbl.rows.length - 1; i > s.length; i--)
|
||||
tbl.deleteRow(i);
|
||||
}
|
||||
|
||||
function getL2() {
|
||||
walkL2(function(entries, ok) {
|
||||
if (ok) {
|
||||
for (var i = 0; i < entries.length; i++)
|
||||
entries[i].type = entries[i].type == "s" ? t('l2_static') : t('l2_learned');
|
||||
fillL2(entries);
|
||||
}
|
||||
setTimeout(getL2, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
getL2();
|
||||
const interval = setInterval(update, 2000);
|
||||
});;
|
||||
});
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="lag_title">Link Aggregation Configuration</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="lag_heading">Link Aggregation Groups Configuration</h1>
|
||||
<h2>LAG 1 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub0" onclick="lagSub(0);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
|
||||
<div id="mLAG0"></div>
|
||||
<br />
|
||||
<h2>LAG 2 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub1" onclick="lagSub(1);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
|
||||
<div id="mLAG1"></div>
|
||||
<br />
|
||||
<h2>LAG 3 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub2" onclick="lagSub(2);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
|
||||
<div id="mLAG2"></div>
|
||||
<br />
|
||||
<h2>LAG 4 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub3" onclick="lagSub(3);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
|
||||
<div id="mLAG3"></div>
|
||||
<script src="/lag.js"></script>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
var lagInterval = Number();
|
||||
|
||||
function lagForm() {
|
||||
if (!numPorts)
|
||||
return;
|
||||
for (let j=0; j < 4; j++) {
|
||||
var lag = "mLAG" + j
|
||||
console.log("Adding LAG " + lag)
|
||||
var m = document.getElementById(lag);
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
const d = document.createElement("div");
|
||||
d.classList.add("cbgroup");
|
||||
const l = document.createElement("label");
|
||||
l.innerHTML = "" + i;
|
||||
l.classList.add("cbgroup");
|
||||
const inp = document.createElement("input");
|
||||
inp.type = "checkbox"; inp.setAttribute("class","psel");
|
||||
inp.id = "p_" + lag + "_" + i;
|
||||
const o = document.createElement("img");
|
||||
if (pIsSFP[i - 1]) {
|
||||
o.src = "sfp.svg"; o.width ="60"; o.height ="60";
|
||||
} else {
|
||||
o.src = "port.svg"; o.width = "40"; o.height = "40";
|
||||
}
|
||||
l.appendChild(inp); l.appendChild(o);
|
||||
d.appendChild(l)
|
||||
m.appendChild(d);
|
||||
}
|
||||
}
|
||||
fetchLag();
|
||||
}
|
||||
|
||||
function setL(p, c){
|
||||
console.log("LAG setting: ", p, " to ", c);
|
||||
document.getElementById(p).checked=c;
|
||||
}
|
||||
|
||||
function fetchLag() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("LAG: ", JSON.stringify(s));
|
||||
for (let l = 0; l < 4; l++) {
|
||||
let members = parseInt(s[l].members, 2);
|
||||
let hash = parseInt(s[l].hash, 16);
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
let p = i - 1;
|
||||
if (numPorts < 9)
|
||||
p = physToLogPort[p];
|
||||
setL("p_mLAG"+l+"_"+i, members & (1<<p));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", `/lag.json`, true);
|
||||
sendXHTTP(xhttp);
|
||||
}
|
||||
async function lagSub(l) {
|
||||
var cmd = "lag " + (l + 1);
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
if (document.getElementById("p_mLAG"+l+"_"+i).checked)
|
||||
cmd = cmd + ` ${i}`;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
lagForm();
|
||||
const interval = setInterval(update, 2000);
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<title data-i18n="login_title">RTL Switch Login</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="/i18n.js"></script>
|
||||
<script src="/main.js"></script>
|
||||
<script>
|
||||
function removeNote() {
|
||||
document.getElementById("incorrect").innerHTML = "";
|
||||
|
||||
+2489
File diff suppressed because it is too large.
Load diff
@@ -1,22 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
fetch('/information.json')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const tableBody = document.getElementById('infoTable').querySelector('tbody');
|
||||
|
||||
// Create table rows
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
const row = document.createElement('tr');
|
||||
const cellKey = document.createElement('td');
|
||||
const cellValue = document.createElement('td');
|
||||
|
||||
cellKey.textContent = key;
|
||||
cellValue.textContent = value;
|
||||
|
||||
row.appendChild(cellKey);
|
||||
row.appendChild(cellValue);
|
||||
tableBody.appendChild(row);
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching the data:', error));
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="mirror_title">Mirror Configuration</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="mirror_heading">Mirror Configuration</h1>
|
||||
<label class="tswitch"><span data-i18n="mirror_enabled">Enabled:</span> <input id="me" type="checkbox"></label><br/>
|
||||
<label for="mp"><span data-i18n="mirror_port">Mirroring Port:</span></label> <input type="number" id="mp" name="mp" min="1" max="9"/>
|
||||
<h2 data-i18n="mirror_tx">Mirrored Ports (TX)</h2>
|
||||
<div id="mPortsTX"></div>
|
||||
<br />
|
||||
<h2 data-i18n="mirror_rx">Mirrored Ports (RX)</h2>
|
||||
<div id="mPortsRX"></div>
|
||||
<br/> <input style="width:15%;" class="action" id="mirror_sub" onclick="mirrorSub();" type="button" data-i18n="mirror_update" value="Update / Create">
|
||||
<input style="width:15%;" class="action" id="mirror_del" onclick="mirrorDel();" type="button" data-i18n="mirror_disable" value="Disable Mirroring">
|
||||
<script src="/mirror.js"></script>
|
||||
<script src="/mirror_sub.js"></script>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,64 +0,0 @@
|
||||
var mirrorInterval = Number();
|
||||
const mirrors = ["mPortsTX", "mPortsRX"];
|
||||
|
||||
function mirrorForm() {
|
||||
if (!numPorts)
|
||||
return;
|
||||
for (let j=0; j < mirrors.length; j++) {
|
||||
console.log("Adding Mirror " + j)
|
||||
var m = document.getElementById(mirrors[j]);
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
const d = document.createElement("div");
|
||||
d.classList.add("cbgroup");
|
||||
const l = document.createElement("label");
|
||||
l.innerHTML = "" + i;
|
||||
l.classList.add("cbgroup");
|
||||
const inp = document.createElement("input");
|
||||
inp.type = "checkbox"; inp.setAttribute("class","psel");
|
||||
inp.id = mirrors[j] + i;
|
||||
const o = document.createElement("img");
|
||||
if (pIsSFP[i - 1]) {
|
||||
o.src = "sfp.svg"; o.width ="60"; o.height ="60";
|
||||
} else {
|
||||
o.src = "port.svg"; o.width = "40"; o.height = "40";
|
||||
}
|
||||
l.appendChild(inp); l.appendChild(o);
|
||||
d.appendChild(l)
|
||||
m.appendChild(d);
|
||||
}
|
||||
}
|
||||
fetchMirror();
|
||||
}
|
||||
|
||||
function setM(p, c){
|
||||
document.getElementById(p).checked=c;
|
||||
}
|
||||
|
||||
function fetchMirror() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("MIRROR: ", JSON.stringify(s));
|
||||
document.getElementById('me').checked = s.enabled;
|
||||
document.getElementById('mp').value = s.mPort;
|
||||
let m_tx = parseInt(s.mirror_tx, 2);
|
||||
let m_rx = parseInt(s.mirror_rx, 2);
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
let p = i - 1;
|
||||
if (numPorts < 9)
|
||||
p = physToLogPort[p];
|
||||
setM("mPortsTX"+i, m_tx&(1<<p)); setM("mPortsRX"+i, m_rx&(1<<p));
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", `/mirror.json`, true);
|
||||
sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
mirrorForm();
|
||||
const interval = setInterval(update, 2000);
|
||||
});
|
||||
});
|
||||
@@ -1,43 +0,0 @@
|
||||
async function mirrorSub() {
|
||||
var cmd = "mirror ";
|
||||
var mp=document.getElementById('mp').value
|
||||
if (!mp) {
|
||||
alert(t('mirror_set_port_first'));
|
||||
return;
|
||||
}
|
||||
document.getElementById(mirrors[0]+mp).checked=false;document.getElementById(mirrors[1]+mp).checked=false;
|
||||
cmd = cmd + mp;
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
if (document.getElementById(mirrors[0] + i).checked && document.getElementById(mirrors[1] + i).checked)
|
||||
cmd = cmd + ` ${i}`;
|
||||
else if (document.getElementById(mirrors[0] + i).checked)
|
||||
cmd = cmd + ` ${i}t`;
|
||||
else if (document.getElementById(mirrors[1] + i).checked)
|
||||
cmd = cmd + ` ${i}r`;
|
||||
}
|
||||
if (cmd.length < 10) {
|
||||
alert(t('mirror_select_ports'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
async function mirrorDel() {
|
||||
var cmd = "mirror off";
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
location.reload();
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
document.getElementById('sidebar').innerHTML =
|
||||
"<ul><li><a href='index.html' data-i18n='nav_overview'>Overview</a></li>"
|
||||
+ "<li><a href='ports.html' data-i18n='nav_port_config'>Port Configuration</a></li>"
|
||||
+ "<li><a href='stat.html' data-i18n='nav_port_stat'>Port Statistics</a></li>"
|
||||
+ "<li><a href='vlan.html' >VLAN</a></li>"
|
||||
+ "<li><a href='l2.html' data-i18n='nav_l2'>L2 Configuration</a></li>"
|
||||
+ "<li><a href='stp.html'>Spanning Tree</a></li>"
|
||||
+ "<li><a href='mirror.html' data-i18n='nav_mirror'>Mirroring</a></li>"
|
||||
+ "<li><a href='lag.html' data-i18n='nav_lag'>Link Aggregation</a></li>"
|
||||
+ "<li><a href='eee.html' data-i18n='nav_eee'>EEE</a></li>"
|
||||
+ "<li><a href='bandwidth.html' data-i18n='nav_bandwidth'>Bandwidth Limits</a></li>"
|
||||
+ "<li><a href='system.html' data-i18n='nav_system'>System Settings</a></li>"
|
||||
+ "<li><a href='update.html' data-i18n='nav_fw_update'>Firmware Update</a></li></ul>";
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var links = document.querySelectorAll('#sidebar a[data-i18n]');
|
||||
links.forEach(function(el) {
|
||||
var key = el.getAttribute('data-i18n');
|
||||
if (key) el.textContent = t(key);
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="port_title">FreeSwitchOS Port Configuration</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="port_heading">Port Configuration</h1>
|
||||
<form id="vform" action="/vlan.html">
|
||||
<table id="speedtable">
|
||||
<tr> <th data-i18n="port_col_port">Port</th> <th data-i18n="port_col_name">Name</th> <th data-i18n="port_col_speed">Current Link Speed</th><th data-i18n="port_col_devices">Connected devices</th><th data-i18n="port_col_set_speed">Set Speed</th><th data-i18n="port_col_disabled">Disabled</th><th data-i18n="port_col_apply">Apply</th></tr>
|
||||
</table>
|
||||
<h2 style="margin-top:3em" data-i18n="port_mtu_heading">Configure Maximum Frame Size (MTU) forwarded at Port</h2>
|
||||
<table id="mtutable" style="margin-top:1em">
|
||||
</table>
|
||||
<script src="/ports.js"></script>
|
||||
</form>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
var mtus = new Int16Array(10);
|
||||
var clicked = new Int8Array(10);
|
||||
function createPortTable() {
|
||||
var tbl = document.getElementById('speedtable');
|
||||
if (tbl.rows.length <= 2 && numPorts) {
|
||||
const sSelect = '<select name="speed_sel" id="speed_sel">'
|
||||
+ '<option value="auto">' + t('port_auto') + '</option>'
|
||||
+ '<option value="2g5">' + t('port_2500m') + '</option>'
|
||||
+ '<option value="1g">' + t('port_1000m') + '</option>'
|
||||
+ '<option value="100m full">' + t('port_100m_f') + '</option>'
|
||||
+ '<option value="100m half">' + t('port_100m_h') + '</option>'
|
||||
+ '<option value="10m full">' + t('port_10m_f') + '</option>'
|
||||
+ '<option value="10m half">' + t('port_10m_h') + '</option>'
|
||||
+ '</select>';
|
||||
const dSwitch = '<input type="checkbox" id="disable_port" onchange="portOnOff();">'
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
if (pIsSFP[i-1])
|
||||
continue;
|
||||
const tr = tbl.insertRow();
|
||||
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + i));
|
||||
let portName = portNames[physToLogPort[i-1]] || '';
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(portName));
|
||||
td = tr.insertCell(); td.innerHTML = linkText(pState[i-1] + 1);
|
||||
tr.insertCell(); // filled by devRender()
|
||||
td = tr.insertCell(); td.innerHTML = sSelect.replaceAll("speed_sel", "speed_sel_" + i);
|
||||
td = tr.insertCell(); td.innerHTML = dSwitch.replaceAll("disable_port", "disable_port_" + i)
|
||||
.replace("portOnOff()", "portOnOff(" + i + ")");
|
||||
var button = '<button type="button" style="margin: 0 0 0 24px" onclick="applySpeed(' + i + ');">' + t('port_apply') + '</button>';
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = button;
|
||||
}
|
||||
}
|
||||
tbl = document.getElementById('mtutable');
|
||||
if (tbl.rows.length <= 2 && numPorts) {
|
||||
const mSelect = '<select name="mtu_sel" id="mtu_sel">'
|
||||
+ '<option value="16383">16383</option>'
|
||||
+ '<option value="1522">1522</option>'
|
||||
+ '<option value="1536">1536</option>'
|
||||
+ '<option value="1552">1552</option>'
|
||||
+ '<option value="9216">9216</option>'
|
||||
+ '</select>';
|
||||
var tr = tbl.insertRow();
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
let td = tr.insertCell();
|
||||
if (pIsSFP[i-1])
|
||||
td.innerHTML = '<object type="image/svg+xml" data="sfp.svg" width="60"></object>'
|
||||
else
|
||||
td.innerHTML = '<object type="image/svg+xml" data="port.svg" width="40"></object>'
|
||||
}
|
||||
tr = tbl.insertRow();
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
let td = tr.insertCell();
|
||||
td.innerHTML = mSelect.replaceAll("mtu_sel", "mtu_sel_" + i);
|
||||
}
|
||||
tr = tbl.insertRow();
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
let td = tr.insertCell();
|
||||
td.innerHTML = '<button type="button" style="margin: 0 0 0 24px" onclick="applyMTU(' + i + ');">' + t('port_apply') + '</button>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updatePortTable() {
|
||||
console.log("updatePortTable called");
|
||||
var tbl = document.getElementById('speedtable');
|
||||
if (tbl.rows.length <= 2 || !numPorts)
|
||||
return;
|
||||
for (let i = 1; i <= numPorts ; i++) {
|
||||
if (pIsSFP[i-1])
|
||||
continue;
|
||||
tbl.rows[i].cells[2].innerHTML = linkText(pState[i-1]+1);
|
||||
if (!clicked[i] && pState[i - 1] < 0) {
|
||||
document.getElementById('speed_sel_' + i).disabled = true;
|
||||
document.getElementById('disable_port_' + i).checked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function applySpeed(port) {
|
||||
var speed = document.getElementById('speed_sel_' + port).value;
|
||||
var disabled = document.getElementById('disable_port_' + port).checked;
|
||||
var cmd = "port " + port + " ";
|
||||
if (!disabled)
|
||||
cmd = cmd + speed;
|
||||
else
|
||||
cmd = cmd + "off";
|
||||
console.log("CMD: " + cmd);
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function portOnOff(p) {
|
||||
var disabled = document.getElementById('disable_port_' + p).checked;
|
||||
document.getElementById('speed_sel_' + p).disabled = disabled;
|
||||
clicked[p] = 1;
|
||||
}
|
||||
|
||||
async function applyMTU(port) {
|
||||
var mtu = document.getElementById('mtu_sel_' + port).value;
|
||||
var cmd = "mtu " + port + " " + mtu;
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('MTU Completed!', response);
|
||||
getMTUs();
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
function devRender(entries) {
|
||||
var tbl = document.getElementById('speedtable');
|
||||
if (tbl.rows.length <= 2 || !numPorts)
|
||||
return;
|
||||
var perPort = {};
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var e = entries[i];
|
||||
if (e.port == 'CPU')
|
||||
continue;
|
||||
if (!perPort[e.port])
|
||||
perPort[e.port] = [];
|
||||
if (perPort[e.port].indexOf(e.mac) < 0)
|
||||
perPort[e.port].push(e.mac);
|
||||
}
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
if (pIsSFP[i-1])
|
||||
continue;
|
||||
var cell = tbl.rows[i].cells[3];
|
||||
var macs = perPort[i] || [];
|
||||
if (macs.length == 1) {
|
||||
cell.textContent = macs[0];
|
||||
cell.title = '';
|
||||
} else if (macs.length > 1) {
|
||||
cell.textContent = macs.length + ' ' + t('port_devices');
|
||||
cell.title = macs.join('\n');
|
||||
} else {
|
||||
cell.textContent = '';
|
||||
cell.title = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function devWalk() {
|
||||
walkL2(function(entries, ok) {
|
||||
if (ok)
|
||||
devRender(entries);
|
||||
setTimeout(devWalk, 15000);
|
||||
});
|
||||
}
|
||||
|
||||
function getMTUs() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("MTUS: ", JSON.stringify(s));
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
p = s[i];
|
||||
let n = p.portNum;
|
||||
mtus[n] = parseInt(p.mtu, 16);
|
||||
var mtu = document.getElementById('mtu_sel_' + n);
|
||||
if (!mtu)
|
||||
continue;
|
||||
mtu.value = mtus[n];
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/mtu.json", true);
|
||||
xhttp.timeout = 1500; sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
createPortTable();
|
||||
updatePortTable();
|
||||
getMTUs()
|
||||
setTimeout(devWalk, 3000);
|
||||
const interval = setInterval(update, 2000);
|
||||
const updatePortTableInterval = setInterval(updatePortTable, 1000);
|
||||
});
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="stat_title">FreeSwitchOS Port Statistics</title>
|
||||
<style>
|
||||
.popup {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.popup-content {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
height: 80%
|
||||
}
|
||||
.popup .popup-content {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<div id="popup" class="popup">
|
||||
<div class="popup-content">
|
||||
<h2 data-i18n="stat_detailed">Detailed Port Statistics</h2>
|
||||
<div id="popup_text"></div>
|
||||
<button id="closePopup" class="action" data-i18n="stat_close">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<h1 data-i18n="stat_heading">Port Statistics</h1>
|
||||
<table id="statstable">
|
||||
<tr> <th data-i18n="stat_col_port">Port</th> <th data-i18n="stat_col_name">Name</th> <th data-i18n="stat_col_link">link</th> <th data-i18n="stat_col_tx_good">TX Good</th> <th data-i18n="stat_col_tx_bad">TX Bad</th> <th data-i18n="stat_col_rx_good">RX Good</th> <th data-i18n="stat_col_rx_bad">RX Bad</th> <th data-i18n="stat_col_all"> All Counters </th></tr>
|
||||
<script src="/stat.js"></script>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
<script src="/navigation.js"></script>
|
||||
</html>
|
||||
-207
@@ -1,207 +0,0 @@
|
||||
const mib_counters = [
|
||||
"Interface in Octets", 8,
|
||||
"", 0,
|
||||
"Interface out Octets", 8,
|
||||
"", 0,
|
||||
"Interface in Unicast Pkts", 8,
|
||||
"", 0,
|
||||
"Interface in Multicast Pkts", 8,
|
||||
"", 0,
|
||||
"Interface in Broadcast Pkts", 8,
|
||||
"", 0,
|
||||
"Interface out Unicast Pkts", 8, // 10
|
||||
"", 0,
|
||||
"Interface out Multicast Pkts", 8,
|
||||
"", 0,
|
||||
"Interface out Broadcast Pkts", 8,
|
||||
"", 0,
|
||||
"Interface out discards", 4,
|
||||
"802.1d Tp Port in discards", 4,
|
||||
"802.3 Single collision frames", 4,
|
||||
"802.3 Multi collision frames", 4,
|
||||
"802.3 Deferred transmissions", 4, // 20
|
||||
"802.3 Late collisions", 4,
|
||||
"802.3 Excessive collisions", 4,
|
||||
"802.3 Symbol errors", 4,
|
||||
"802.3 Control in unknown opcodes", 4,
|
||||
"802.3 In Pause frames", 4,
|
||||
"802.3 Out Pause frames", 4,
|
||||
"Ether drop events", 4,
|
||||
"TX Ether Broadcast Pkts", 4,
|
||||
"TX Ether Multicast Pkts", 4,
|
||||
"TX Ether CRC Align errors", 4, // 30
|
||||
"RX Ether CRC Align errors", 4,
|
||||
"TX Ether Undersized Pkts", 4,
|
||||
"RX Ether Undersized Pkts", 4,
|
||||
"TX Ether Oversized Pkts", 4,
|
||||
"RX Ether Oversized Pkts", 4,
|
||||
"TX Ether Fragments", 4,
|
||||
"RX Ether fragments", 4,
|
||||
"TX Ether Jabbers", 4,
|
||||
"RX Ether Jabbers", 4,
|
||||
"TX Ether Collisions", 4, // 40
|
||||
"TX Ether Pkts 640 Octets", 4,
|
||||
"RX Ether Pkts 640 Octets", 4,
|
||||
"TX Ether 65-127 Octets", 4,
|
||||
"RX Ether 65-127 Octets", 4,
|
||||
"TX Ether Pkts 128-255 Octets", 4,
|
||||
"RX Ether Pkts 128-255 Octets", 4,
|
||||
"TX Ether Pkts 256-511 Octets", 4,
|
||||
"RX Ether Pkts 256-511 Octets", 4,
|
||||
"TX Ether Pkts 512-1023 Octets", 4,
|
||||
"RX Ether Pkts 512-1023 Octets", 4, // 50
|
||||
"TX Ether Pkts 1024-1518 Octets", 4,
|
||||
"RX Ether Pkts 1024-1518 Octets", 4,
|
||||
"", 4,
|
||||
"RX Ether Undersized Drop Pkts", 4, // 54
|
||||
"TX Ether Pkts >1518 Octets", 4,
|
||||
"RX Ether Pkts >1518 Octets", 4,
|
||||
"TX Ether Pkts too large", 4,
|
||||
"RX Ether Pkts too large", 4,
|
||||
"TX Ether Flexible Octets Set 1", 4,
|
||||
"RX Ether Flexible Octets Set 1", 4,// 60
|
||||
"TX Ether Flexible Octets CRC Set 1", 4,
|
||||
"RX Ether Flexible Octets CRC Set 1", 4,
|
||||
"TX Ether Flexible Octets Set 0", 4,
|
||||
"RX Ether Flexible Octets Set 0", 4,
|
||||
"TX Ether Flexible Octets CRC Set 0", 4,
|
||||
"RX Ether Flexible Octets CRC Set 0", 4,
|
||||
"Lenth Field Errors", 4,
|
||||
"False Carriers", 4,
|
||||
"Undersized Octets", 4,
|
||||
"Framing Errors", 4, // 70
|
||||
"", 4,
|
||||
"RX MAC Discards", 4, // 72
|
||||
"RX MAC IPG Short Drop", 4,
|
||||
"", 4,
|
||||
"802.1d TP Learned Entry Discards", 4, // 75
|
||||
"Egress Queue 7 Dropped Pkts", 4,
|
||||
"Egress Queue 6 Dropped Pkts", 4,
|
||||
"Egress Queue 5 Dropped Pkts", 4,
|
||||
"Egress Queue 4 Dropped Pkts", 4,
|
||||
"Egress Queue 3 Dropped Pkts", 4, // 80
|
||||
"Egress Queue 2 Dropped Pkts", 4,
|
||||
"Egress Queue 1 Dropped Pkts", 4,
|
||||
"Egress Queue 0 Dropped Pkts", 4,
|
||||
"Egress Queue 7 Out Pkts", 4,
|
||||
"Egress Queue 6 Out Pkts", 4,
|
||||
"Egress Queue 5 Out Pkts", 4,
|
||||
"Egress Queue 4 Out Pkts", 4,
|
||||
"Egress Queue 3 Out Pkts", 4,
|
||||
"Egress Queue 2 Out Pkts", 4,
|
||||
"Egress Queue 1 Out Pkts", 4, // 90
|
||||
"Egress Queue 0 Out Pkts", 4,
|
||||
"TX Good Counter", 8,
|
||||
"", 0,
|
||||
"RX Good Counter", 8,
|
||||
"", 0,
|
||||
"RX Error Counter", 4,
|
||||
"TX Error Counter", 4,
|
||||
"TX Good Counter PHY", 8,
|
||||
"", 0,
|
||||
"RX Good Counter PHY", 8, // 100
|
||||
"", 0,
|
||||
"RX Error Counter PHY", 4,
|
||||
"TX Error Counter PHY", 4
|
||||
];
|
||||
|
||||
|
||||
function getCounters(port) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
const popup = document.getElementById('popup');
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("Counters: ", JSON.stringify(s));
|
||||
const ptext = document.getElementById('popup_text');
|
||||
var tableHtml = "<table style='width:100%'> <tr> <th>" + t('stat_counter') + "</th> <th>" + t('stat_value') + "</th> <th>" + t('stat_counter') + "</th> <th>" + t('stat_value') + "</th></tr> <tr>";
|
||||
console.log("Counter 0: ", BigInt(s[0]).toString(), " length: ", s.length);
|
||||
var c = 0;
|
||||
for (i = 0; i < mib_counters.length; i += 4) {
|
||||
console.log(i, " ", mib_counters[i], ": ", mib_counters[i + 1]);
|
||||
if (mib_counters[i] == "" && mib_counters[i + 1] == 8) {
|
||||
console.log("c " + i + ": continue");
|
||||
continue;
|
||||
}
|
||||
var count = BigInt(s[i / 4]);
|
||||
if (mib_counters[i + 1] == 8) {
|
||||
tableHtml += "<td>" + mib_counters[i] + "</td><td>" + count.toString() + "</td>";
|
||||
c += 1;
|
||||
} else if (mib_counters[i + 1] == 4) {
|
||||
if (mib_counters[i] != "") {
|
||||
tableHtml += "<td>" + mib_counters[i] + "</td><td>" + (count >> 32n).toString() + "</td>";
|
||||
c += 1;
|
||||
}
|
||||
if (c == 2) {
|
||||
tableHtml += "</tr> <tr>";
|
||||
c = 0;
|
||||
}
|
||||
if (mib_counters[i + 2] != "") {
|
||||
tableHtml += "<td>" + mib_counters[i + 2] + "</td><td>" + (count & 4294967295n).toString() + "</td>";
|
||||
c += 1;
|
||||
}
|
||||
}
|
||||
if (c == 2) {
|
||||
tableHtml += "</tr> <tr>";
|
||||
c = 0;
|
||||
}
|
||||
}
|
||||
ptext.innerHTML = tableHtml + "</tr></table>";
|
||||
popup.style.display = 'flex';
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/counters.json?port=" + port, true);
|
||||
xhttp.timeout = 1500; sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
|
||||
function fillStats() {
|
||||
var tbl = document.getElementById('statstable');
|
||||
if (!numPorts)
|
||||
return;
|
||||
if (tbl.rows.length > 1) {
|
||||
for (let i = 0; i < numPorts; i++) {
|
||||
console.log("Table Update row: " + i + " state " + pState[i] + " is " + linkS[pState[i] + 1]);
|
||||
tbl.rows[i + 1].cells[2].innerHTML = linkText(pState[i] + 1);
|
||||
tbl.rows[i + 1].cells[3].innerHTML = `${txG[i]}` + t('common_pkts');
|
||||
tbl.rows[i + 1].cells[4].innerHTML = `${txB[i]}` + t('common_pkts');
|
||||
tbl.rows[i + 1].cells[5].innerHTML = `${rxG[i]}` + t('common_pkts');
|
||||
tbl.rows[i + 1].cells[6].innerHTML = `${rxB[i]}` + t('common_pkts');
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < numPorts; i++) {
|
||||
console.log("Table row: " + i);
|
||||
const tr = tbl.insertRow();
|
||||
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i + 1)));
|
||||
let portName = portNames[physToLogPort[i]] || '';
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(portName));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(linkText(pState[i] + 1)));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(`${txG[i]}` + t('common_pkts')));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(`${txB[i]}` + t('common_pkts')));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(`${rxG[i]}` + t('common_pkts')));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(`${rxB[i]}` + t('common_pkts')));
|
||||
var button = '<button type="button" style="margin: 0 0 0 24px" onclick="getCounters(' + (i + 1) + ');">' + t('stat_show') + '</button>';
|
||||
td = tr.insertCell(); td.innerHTML = button;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const popup = document.getElementById('popup');
|
||||
const closePopup = document.getElementById('closePopup');
|
||||
closePopup.addEventListener('click', () => {
|
||||
popup.style.display = 'none';
|
||||
});
|
||||
window.addEventListener('click', (event) => {
|
||||
if (event.target === popup) {
|
||||
popup.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
update(() => {
|
||||
update();
|
||||
fillStats();
|
||||
const stat = setInterval(fillStats, 1000);
|
||||
const interval = setInterval(update, 2000);
|
||||
});
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title>FreeSwitchOS Spanning Tree</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1>Spanning Tree (RSTP)</h1>
|
||||
<h2>Mode: <select id="stpMode"><option value="off">Disabled</option><option value="on">Enabled</option></select>
|
||||
<input style="width:15%;margin-left: 3em;" class="action" id="stp_sub" onclick="stpSub();" type="button" value="Apply"></h2>
|
||||
<div id="stpStat" style="font-family:monospace;"></div>
|
||||
<h2>Bridge settings</h2>
|
||||
<table id="stpBridge">
|
||||
<tr>
|
||||
<th>Priority</th><th>Version</th><th>Hello [s]</th><th>Max age [s]</th><th>Fwd delay [s]</th><th>Tx hold</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><select id="bPrio"></select></td>
|
||||
<td><select id="bVer"><option value="rstp">RSTP</option><option value="stp">STP compat</option></select></td>
|
||||
<td><input id="bHello" type="number" min="1" max="10" style="width:4em"></td>
|
||||
<td><input id="bMaxage" type="number" min="6" max="40" style="width:4em"></td>
|
||||
<td><input id="bFwd" type="number" min="4" max="30" style="width:4em"></td>
|
||||
<td><input id="bTxhold" type="number" min="1" max="10" style="width:4em"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="font-size:small">Changes apply immediately. Edge ports skip the listen period; guard/filter act on received BPDUs.</p>
|
||||
<h2>Port configuration</h2>
|
||||
<table id="stpPortsTbl">
|
||||
<tr>
|
||||
<th>Port</th><th>State</th><th>Path Cost<br><span style="font-weight:normal;font-size:small">(0 = Auto)</span></th><th>Priority</th><th>Edge Port</th><th>BPDU Filter</th><th>Guard</th><th>Point-to-Point</th>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>Port status</h2>
|
||||
<table id="stpStatTbl">
|
||||
<tr>
|
||||
<th>Port</th><th>Port State</th><th>Role</th><th>Designated Bridge</th><th>Designated Port ID</th><th>Designated Cost</th><th>Oper. Edge</th><th>Oper. P2P</th>
|
||||
</tr>
|
||||
</table>
|
||||
<script src="/stp.js"></script>
|
||||
</div>
|
||||
</body>
|
||||
<script src="/navigation.js"></script>
|
||||
</html>
|
||||
-187
@@ -1,187 +0,0 @@
|
||||
|
||||
const STP_STATES = ["Disabled", "Blocking", "Learning", "Forwarding"];
|
||||
const STP_ROLES = ["-", "Root", "Designated", "Alternate"];
|
||||
|
||||
const PF_ENABLED = 1, PF_ADMEDGE = 2, PF_AUTOEDGE = 4, PF_BPDUGUARD = 8,
|
||||
PF_ROOTGUARD = 16, PF_FILTER = 32, PF_OPEREDGE = 64, PF_TRIPPED = 128;
|
||||
|
||||
var stpDirty = false;
|
||||
var stpRows = 0; // ports table built?
|
||||
|
||||
async function stpCmd(cmd) {
|
||||
stpDirty = true;
|
||||
try {
|
||||
await fetch('/cmd', { method: 'POST', body: cmd });
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
stpDirty = false;
|
||||
fetchStp();
|
||||
}
|
||||
|
||||
function sel(id, opts, onch) {
|
||||
const s = document.createElement("select");
|
||||
s.id = id;
|
||||
for (const [v, label] of opts) {
|
||||
const o = document.createElement("option");
|
||||
o.value = v; o.textContent = label;
|
||||
s.appendChild(o);
|
||||
}
|
||||
s.addEventListener("change", onch);
|
||||
return s;
|
||||
}
|
||||
|
||||
function num(id, min, max, onch) {
|
||||
const n = document.createElement("input");
|
||||
n.type = "number"; n.id = id; n.min = min; n.max = max; n.style.width = "4em";
|
||||
n.addEventListener("change", onch);
|
||||
return n;
|
||||
}
|
||||
|
||||
function buildPortsTable(ports) {
|
||||
const tbl = document.getElementById("stpPortsTbl");
|
||||
const stat = document.getElementById("stpStatTbl");
|
||||
for (const p of [...ports].sort((a, b) => a.p - b.p)) {
|
||||
const tr = tbl.insertRow();
|
||||
tr.insertCell().textContent = p.p; // Port
|
||||
tr.insertCell().appendChild(sel("en_" + p.p,
|
||||
[["on","Enable"],["off","Disable"]],
|
||||
e => stpCmd("stp port " + p.p + " " + e.target.value)));
|
||||
const pc = num("cost_" + p.p, 0, 200000000,
|
||||
e => stpCmd("stp port " + p.p + " cost " + e.target.value));
|
||||
pc.style.width = "7em";
|
||||
pc.title = "0 - 200000000 (0 = Auto)";
|
||||
tr.insertCell().appendChild(pc);
|
||||
const pr = sel("prio_" + p.p, [],
|
||||
e => stpCmd("stp port " + p.p + " prio " + e.target.value));
|
||||
for (let v = 0; v <= 240; v += 16) {
|
||||
const o = document.createElement("option");
|
||||
o.value = v; o.textContent = v + (v === 128 ? " (default)" : "");
|
||||
pr.appendChild(o);
|
||||
}
|
||||
tr.insertCell().appendChild(pr);
|
||||
tr.insertCell().appendChild(sel("edge_" + p.p,
|
||||
[["auto","Auto"],["on","Enable"],["off","Disable"]],
|
||||
e => stpCmd("stp port " + p.p + " edge " + e.target.value)));
|
||||
tr.insertCell().appendChild(sel("filt_" + p.p,
|
||||
[["off","Disable"],["on","Enable"]],
|
||||
e => stpCmd("stp port " + p.p + " filter " + e.target.value)));
|
||||
tr.insertCell().appendChild(sel("guard_" + p.p,
|
||||
[["none","None"],["bpdu","BPDU"],["root","Root"]],
|
||||
e => stpCmd("stp port " + p.p + " guard " + e.target.value)));
|
||||
tr.insertCell().appendChild(sel("p2p_" + p.p,
|
||||
[["auto","Auto"],["on","Enable"],["off","Disable"]],
|
||||
e => stpCmd("stp port " + p.p + " p2p " + e.target.value)));
|
||||
|
||||
const sr = stat.insertRow();
|
||||
sr.insertCell().textContent = p.p;
|
||||
for (const id of ["st","role","db","dp","dc","oe","op"])
|
||||
sr.insertCell().id = id + "_" + p.p;
|
||||
}
|
||||
stpRows = ports.length;
|
||||
}
|
||||
|
||||
function bridgeSelf(s) {
|
||||
return fmtBridgeId((s.prio * 4096).toString(16).padStart(4, "0") + s.myMac);
|
||||
}
|
||||
|
||||
function fmtBridgeId(h) {
|
||||
if (!h || h.length < 16) return "";
|
||||
const prio = parseInt(h.slice(0, 4), 16);
|
||||
const mac = h.slice(4).replace(/(..)(?=.)/g, "$1:");
|
||||
return prio + "-" + mac.toUpperCase();
|
||||
}
|
||||
|
||||
function fetchStp() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
if (!stpRows)
|
||||
buildPortsTable(s.ports);
|
||||
document.getElementById("stpStat").textContent = s.on
|
||||
? (s.weRoot
|
||||
? "This switch (" + bridgeSelf(s) + ") is the root bridge — topology changes: "
|
||||
+ parseInt(s.tc, 16)
|
||||
: "This switch: " + bridgeSelf(s)
|
||||
+ " — root bridge: " + fmtBridgeId(s.rootPrio + s.rootMac)
|
||||
+ " via port " + s.rootPort + " — path cost: " + parseInt(s.cost, 16)
|
||||
+ " — topology changes: " + parseInt(s.tc, 16))
|
||||
: "";
|
||||
for (const p of s.ports) {
|
||||
const trip = (p.f & PF_TRIPPED) ? " (guard!)" : "";
|
||||
document.getElementById("st_" + p.p).textContent =
|
||||
s.on ? STP_STATES[p.st] + trip : "-";
|
||||
document.getElementById("role_" + p.p).textContent =
|
||||
s.on ? STP_ROLES[p.role] : "-";
|
||||
document.getElementById("db_" + p.p).textContent = s.on ? fmtBridgeId(p.db) : "-";
|
||||
document.getElementById("dp_" + p.p).textContent =
|
||||
s.on ? (parseInt(p.dp.slice(0, 2), 16) + "-" + parseInt(p.dp.slice(2), 16)) : "-";
|
||||
document.getElementById("dc_" + p.p).textContent = s.on ? parseInt(p.dc, 16) : "-";
|
||||
document.getElementById("oe_" + p.p).textContent =
|
||||
s.on ? ((p.f & PF_OPEREDGE) ? "True" : "False") : "-";
|
||||
document.getElementById("op_" + p.p).textContent = s.on ? (p.p2 == 2 ? "False" : "True") : "-";
|
||||
}
|
||||
if (stpDirty) // an edit is in flight - do not revert controls
|
||||
return;
|
||||
document.getElementById("stpMode").value = s.on ? "on" : "off";
|
||||
document.getElementById("bPrio").value = s.prio;
|
||||
document.getElementById("bVer").value = s.rstp ? "rstp" : "stp";
|
||||
document.getElementById("bHello").value = s.hello;
|
||||
document.getElementById("bMaxage").value = s.maxage;
|
||||
document.getElementById("bFwd").value = s.fwd;
|
||||
document.getElementById("bTxhold").value = s.txhold;
|
||||
for (const p of s.ports) {
|
||||
document.getElementById("en_" + p.p).value = (p.f & PF_ENABLED) ? "on" : "off";
|
||||
document.getElementById("edge_" + p.p).value =
|
||||
(p.f & PF_ADMEDGE) ? "on" : ((p.f & PF_AUTOEDGE) ? "auto" : "off");
|
||||
document.getElementById("cost_" + p.p).value = parseInt(p.pc, 16);
|
||||
document.getElementById("prio_" + p.p).value = p.prio;
|
||||
document.getElementById("p2p_" + p.p).value = ["auto","on","off"][p.p2];
|
||||
document.getElementById("guard_" + p.p).value =
|
||||
(p.f & PF_BPDUGUARD) ? "bpdu" : ((p.f & PF_ROOTGUARD) ? "root" : "none");
|
||||
document.getElementById("filt_" + p.p).value = (p.f & PF_FILTER) ? "on" : "off";
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", `/stp.json`, true);
|
||||
sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
async function stpSub() {
|
||||
const on = document.getElementById("stpMode").value === "on";
|
||||
document.getElementById("stpStat").textContent = on
|
||||
? "Enabling STP. The ports start blocked and take up to "
|
||||
+ (2 * document.getElementById("bFwd").value)
|
||||
+ " s to reach forwarding, and this page can stay silent until they do."
|
||||
: "Disabling STP.";
|
||||
await stpCmd(on ? "stp on" : "stp off");
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
const bp = document.getElementById("bPrio");
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const o = document.createElement("option");
|
||||
o.value = i; o.textContent = (i * 4096) + (i === 8 ? " (default)" : "");
|
||||
bp.appendChild(o);
|
||||
}
|
||||
bp.addEventListener("change", e => stpCmd("stp prio " + e.target.value));
|
||||
document.getElementById("bVer")
|
||||
.addEventListener("change", e => stpCmd("stp version " + e.target.value));
|
||||
document.getElementById("bHello")
|
||||
.addEventListener("change", e => stpCmd("stp hello " + e.target.value));
|
||||
document.getElementById("bMaxage")
|
||||
.addEventListener("change", e => stpCmd("stp maxage " + e.target.value));
|
||||
document.getElementById("bFwd")
|
||||
.addEventListener("change", e => stpCmd("stp fwd " + e.target.value));
|
||||
document.getElementById("bTxhold")
|
||||
.addEventListener("change", e => stpCmd("stp txhold " + e.target.value));
|
||||
document.getElementById("stpMode")
|
||||
.addEventListener("change", () => { stpDirty = true; });
|
||||
|
||||
update( () => {
|
||||
fetchStp();
|
||||
const interval = setInterval(update, 2000);
|
||||
const stpInt = setInterval(fetchStp, 2000);
|
||||
});
|
||||
});
|
||||
@@ -1,109 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="sys_title">System Settings</title>
|
||||
<style>
|
||||
.tab-bar { display: flex; border-bottom: 2px solid #226; margin-bottom: 0; margin-left: 16%; padding: 1px 16px; padding-bottom: 0; }
|
||||
.tab-btn { padding: 10px 20px; background-color: #ddf; border: none; cursor: pointer; font-size: 1em; border-radius: 8px 8px 0 0; margin-right: 4px; }
|
||||
.tab-btn:hover { background-color: #aaf; }
|
||||
.tab-btn.active { background-color: #aaf; font-weight: bold; border-bottom: 2px solid #aaf; margin-bottom: -2px; }
|
||||
.tab-content { display: none; }
|
||||
.tab-content.active { display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="tab-bar">
|
||||
<button class="tab-btn active" onclick="openTab(event, 'system-tab')" data-i18n="sys_tab_system">System</button>
|
||||
<button class="tab-btn" onclick="openTab(event, 'advanced-tab')" data-i18n="sys_tab_advanced">Advanced</button>
|
||||
<button class="tab-btn" onclick="openTab(event, 'console-tab')" data-i18n="sys_tab_console">Console</button>
|
||||
</div>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
|
||||
<div id="system-tab" class="tab-content active">
|
||||
<h1 data-i18n="sys_heading">System Settings</h1>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="hostname" data-i18n="sys_hostname">Hostname:</label></div>
|
||||
<div class="rcol"> <input id="hostname" type="text" maxlength="23" size="20"/>
|
||||
<button onclick="hostSub()" data-i18n="sys_apply">Apply</button></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label data-i18n="sys_model">Model:</label></div>
|
||||
<div class="rcol"><span id="model" class="rotext"></span></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="ip" data-i18n="sys_ip">IP address:</label></div>
|
||||
<div class="rcol"> <input id="ip" class="ip" type="text" minlength="7" maxlength="15" size="15"/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="netmask" data-i18n="sys_netmask">Netmask:</label></div>
|
||||
<div class="rcol"><input id="netmask" class="ip" type="text" minlength="7" maxlength="15" size="15"/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="gw" data-i18n="sys_gateway">Gateway:</label></div>
|
||||
<div class="rcol"><input id="gw" class="ip" type="text" minlength="7" maxlength="15" size="15"/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="mgmtvlan" data-i18n="sys_mgmt_vlan">Management VLAN:</label></div>
|
||||
<div class="rcol"><select id="mgmtvlan" class="ip" onchange="mgmtVlanChanged()"></select></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label data-i18n="sys_language">Language:</label></div>
|
||||
<div class="rcol">
|
||||
<select id="lang-select" onchange="changeLang()">
|
||||
<option value="en">English</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="zh">中文</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<span data-i18n="sys_ip_note">When updating the above settings, remember to point your browser to the new IP afterwards:</span><br/>
|
||||
<input style="width:40%;" class="action" id="ip_sub" onclick="ipSub();" type="button" data-i18n="sys_update" value="Update Settings"><br/>
|
||||
<br/>
|
||||
<span data-i18n="sys_save_label">Save all current settings to Flash:</span><br/>
|
||||
<input style="width:40%;" class="action" id="flash_sub" onclick="flashSave();" type="button" data-i18n="sys_save" value="Save Settings to Flash">
|
||||
</div>
|
||||
|
||||
<div id="advanced-tab" class="tab-content">
|
||||
<h1 data-i18n="sys_advanced">Advanced Settings</h1>
|
||||
<div class="lcol"> <label for="config_display" data-i18n="sys_startup_config">Startup configuration:</label></div>
|
||||
<textarea id="config_display" rows="8" cols="60"></textarea>
|
||||
<br/><br/>
|
||||
<span data-i18n="sys_startup_warn">Be careful when saving the directly edited startup configuration, you can lock yourself out:</span><br/>
|
||||
<input style="width:40%;" class="action" id="clear_config" onclick="clearConfig();" type="button" data-i18n="sys_clear_config" value="Clear Startup Config">
|
||||
<br/>
|
||||
<input style="width:40%;" class="action" id="flash_startup_sub" onclick="flashStartupSave();" type="button" data-i18n="sys_save_startup" value="Save Startup Settings to Flash">
|
||||
<br/>
|
||||
<input style="width:40%;" class="action" id="switch_reset" onclick="resetSwitch();" type="button" data-i18n="sys_reset" value="Reset Switch">
|
||||
</div>
|
||||
|
||||
<div id="console-tab" class="tab-content">
|
||||
<h1 data-i18n="sys_console">Console Command</h1>
|
||||
<label for="console_command" data-i18n="sys_enter_cmd">Enter command:</label>
|
||||
<input type="text" id="console_cmd" name="console_cmd" style="width:40%;">
|
||||
<input style="width:20%;" class="action" id="cmd_sub" onclick="cmdSub();" type="button" data-i18n="sys_send_cmd" value="Send Command"><br/>
|
||||
<br/><br/>
|
||||
<span data-i18n="sys_console_warn">Be careful when entering console commands, you can lock yourself out!</span><br/>
|
||||
<br/>
|
||||
<pre id="console_out" style="height:22em;overflow:auto;white-space:pre-wrap;border:1px solid;padding:0.5em;"></pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="/config.js"></script>
|
||||
<script src="/system.js"></script>
|
||||
<script src="/navigation.js"></script>
|
||||
<script>
|
||||
function openTab(evt, tabId) {
|
||||
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||||
document.getElementById(tabId).classList.add('active');
|
||||
evt.currentTarget.classList.add('active');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
-222
@@ -1,222 +0,0 @@
|
||||
var systemInterval = Number();
|
||||
var isSaving = false;
|
||||
const ips = ["ip", "netmask", "gw"];
|
||||
|
||||
function changeLang() {
|
||||
var lang = document.getElementById('lang-select').value;
|
||||
setLang(lang);
|
||||
}
|
||||
|
||||
function checkIp(ip) {
|
||||
const ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||
if (!ipv4.test(ip)) {alert(t('sys_invalid_ip') + ip); return false };
|
||||
return true;
|
||||
}
|
||||
|
||||
async function ipSub() {
|
||||
for (let i=0;i<3;i++) {
|
||||
if (!checkIp(document.getElementById(ips[i]).value))
|
||||
return;
|
||||
}
|
||||
var cmd = '';
|
||||
for (let i=0; i<3;i++){
|
||||
cmd += ips[i]+' '+document.getElementById(ips[i]).value+'\n';
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
fetchIP();
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdSub() {
|
||||
const input = document.getElementById('console_cmd');
|
||||
const out = document.getElementById('console_out');
|
||||
const cmd = input.value;
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
if (response.status == 401) {
|
||||
window.location.href = 'login.html';
|
||||
return;
|
||||
}
|
||||
let text = await response.text();
|
||||
if (text != "" && !text.endsWith("\n"))
|
||||
text += "\n";
|
||||
if (out.textContent.length > 20000)
|
||||
out.textContent = out.textContent.slice(-16000);
|
||||
out.textContent += "> " + cmd + "\n" + text;
|
||||
out.scrollTop = out.scrollHeight;
|
||||
input.value = "";
|
||||
} catch(err) {
|
||||
out.textContent += "> " + cmd + "\n" + err + "\n";
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function hostSub() {
|
||||
const h = document.getElementById("hostname").value;
|
||||
try { await fetch('/cmd', { method: 'POST', body: "hostname " + h }); }
|
||||
catch(err) { console.error(`Error: ${err}`); }
|
||||
fetchIP();
|
||||
}
|
||||
|
||||
|
||||
async function sendConfig(c) {
|
||||
if (isSaving) return;
|
||||
isSaving = true;
|
||||
clearInterval(systemInterval);
|
||||
const form = new FormData();
|
||||
form.append("MAX_FILE_SIZE", "4096");
|
||||
form.append("configuration", new Blob([c], {type: "application/octet-stream"}), "config.txt");
|
||||
try {
|
||||
const response = await fetch('/config', {
|
||||
method: 'POST',
|
||||
body: form
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
try {
|
||||
await fetch('/cmd_log_clear', { method: 'GET' });
|
||||
} catch(e) {}
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
} finally {
|
||||
isSaving = false;
|
||||
systemInterval = setInterval(fetchIP, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function flashSave() {
|
||||
configuration = [];
|
||||
const savedConfig = await fetchConfig();
|
||||
const cmdLog = await fetchCmdLog();
|
||||
if (savedConfig) parseConf(savedConfig);
|
||||
if (cmdLog) parseConf(cmdLog);
|
||||
const body = configuration.join('\n') + '\n';
|
||||
console.log("CONFIGURATION to save: ", body);
|
||||
await sendConfig(body);
|
||||
}
|
||||
|
||||
async function flashStartupSave() {
|
||||
var configContent = document.getElementById("config_display").value;
|
||||
console.log("CONFIGURATION to save: ", configContent);
|
||||
sendConfig(configContent);
|
||||
// Clear the command log 1 second after initiating the config save
|
||||
setTimeout(() => {
|
||||
fetch('/cmd_log_clear', { method: 'GET' })
|
||||
.then(response => console.log('Command log cleared', response))
|
||||
.catch(err => console.error('Error clearing command log:', err));
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function clearConfig() {
|
||||
document.getElementById("config_display").value = "";
|
||||
|
||||
// Validate and populate with current IP settings
|
||||
for (let i=0; i<3; i++) {
|
||||
if (!checkIp(document.getElementById(ips[i]).value))
|
||||
return;
|
||||
}
|
||||
|
||||
var configLines = "";
|
||||
for (let i=0; i<3; i++){
|
||||
var cmd = ips[i]+' '+document.getElementById(ips[i]).value;
|
||||
configLines += cmd + "\n";
|
||||
}
|
||||
|
||||
document.getElementById("config_display").value = configLines;
|
||||
}
|
||||
|
||||
function fetchIP() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("IP: ", s);
|
||||
document.getElementById("ip").value=s.ip_address;
|
||||
document.getElementById("netmask").value=s.ip_netmask;
|
||||
document.getElementById("gw").value=s.ip_gateway;
|
||||
document.getElementById("hostname").value=s.hostname;
|
||||
document.getElementById("model").textContent=s.hw_ver;
|
||||
loadMgmtVlan();
|
||||
clearInterval(systemInterval);
|
||||
// Fetch and populate the config textbox
|
||||
fetchConfig().then((configText) => {
|
||||
let fullConfig = configText;
|
||||
// Fetch and append cmd_log
|
||||
//return fetchCmdLog().then((cmdLogText) => {
|
||||
// if (cmdLogText) {
|
||||
// fullConfig = fullConfig + cmdLogText;
|
||||
// }
|
||||
document.getElementById("config_display").value = fullConfig;
|
||||
});
|
||||
};
|
||||
}
|
||||
xhttp.open("GET", `/information.json`, true);
|
||||
xhttp.send();
|
||||
}
|
||||
|
||||
function resetSwitch() {
|
||||
if (!confirm(t('sys_reset_confirm'))) {
|
||||
return;
|
||||
}
|
||||
fetch('/reset', { method: 'GET' }).catch(() => {});
|
||||
setTimeout(() => {
|
||||
alert(t('sys_resetting'));
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
var langSel = document.getElementById('lang-select');
|
||||
if (langSel) langSel.value = rtlLang;
|
||||
systemInterval = setInterval(fetchIP, 1000);
|
||||
});
|
||||
|
||||
|
||||
var mgmtVlanCurrent = 0;
|
||||
|
||||
function loadMgmtVlan() {
|
||||
var sel = document.getElementById('mgmtvlan');
|
||||
if (!sel) return;
|
||||
fetch('/vlanlist').then(function(r) { return r.json(); }).then(function(d) {
|
||||
var cur = d.mgmt || 0;
|
||||
var list = d.vlan || [];
|
||||
mgmtVlanCurrent = cur;
|
||||
sel.innerHTML = '';
|
||||
if (!cur) {
|
||||
var none = document.createElement('option');
|
||||
none.value = 0; none.disabled = true;
|
||||
none.textContent = t('sys_mgmt_untagged');
|
||||
sel.appendChild(none);
|
||||
}
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var o = document.createElement('option');
|
||||
o.value = list[i].id;
|
||||
o.textContent = list[i].name ? (list[i].id + ' (' + list[i].name + ')') : list[i].id;
|
||||
sel.appendChild(o);
|
||||
}
|
||||
sel.value = cur;
|
||||
}).catch(function(err) { console.error('VLAN list failed:', err); });
|
||||
}
|
||||
|
||||
function mgmtVlanChanged() {
|
||||
var sel = document.getElementById('mgmtvlan');
|
||||
var id = parseInt(sel.value, 10);
|
||||
if (!id || id === mgmtVlanCurrent) return;
|
||||
if (!confirm(t('sys_mgmt_confirm') + id + '.\n\n' + t('sys_mgmt_warn'))) {
|
||||
sel.value = mgmtVlanCurrent;
|
||||
return;
|
||||
}
|
||||
fetch('/cmd', { method: 'POST', body: 'vlan ' + id + ' mgmt' })
|
||||
.then(function() { mgmtVlanCurrent = id; })
|
||||
.catch(function(err) { console.error('Set management VLAN failed:', err); sel.value = mgmtVlanCurrent; });
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="update_title">Firmware update</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;width:40%;">
|
||||
<h1 data-i18n="update_heading">Firmware Update</h1>
|
||||
<form enctype="multipart/form-data" action="/upload" method="POST">
|
||||
<input type="hidden" name="MAX_FILE_SIZE" value="1000000" />
|
||||
<span data-i18n="update_instruction">Choose a firmware update file to upload:</span> <br/> <br/>
|
||||
<input name="uploadedfile" type="file" accept=".bin" /><br />
|
||||
<input style="margin-top:3em" type="submit" data-i18n="update_upload" value="Upload File" />
|
||||
</form>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,59 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="vlan_title">FreeSwitchOS VLAN Configuration</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="vlan_heading">VLAN Configuration</h1>
|
||||
<form id="vform" action="/vlan.html">
|
||||
<div>
|
||||
<label for="vlanSelect" data-i18n="vlan_select">VLAN Select:</label>
|
||||
<select id="vlanSelect" style="margin: 0 0 0 8px">
|
||||
<option value="" disabled selected data-i18n="vlan_choose">— VLAN Choose —</option>
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
<div>
|
||||
<label for="vid" data-i18n="vlan_id">VLAN ID:</label>
|
||||
<input type="number" min="1" max="4094" id="vid" name="vid">
|
||||
<button type="button" style="margin: 0 0 0 24px" onclick="fetchVLAN();" data-i18n="vlan_get_config">Get Configuration</button>
|
||||
</div>
|
||||
<br/><br/>
|
||||
<label for="vname" data-i18n="vlan_name">VLAN Name:</label>
|
||||
<input type="text" id="vname" name="vname"><br><br>
|
||||
<br/>
|
||||
<h2 data-i18n="vlan_tagged">Tagged Ports</h2>
|
||||
<div id="tPorts"><button type="button" style="transform: translateY(-100%);margin: 0 50px 0 0" onclick="utClicked(true);" data-i18n="vlan_select_all">Select all</button></div>
|
||||
<h2 data-i18n="vlan_untagged">Untagged Ports</h2>
|
||||
<div id="uPorts"><button type="button" style="transform: translateY(-100%); margin: 0 50px 0 0" onclick="utClicked(false);" data-i18n="vlan_select_all">Select all</button> </div>
|
||||
<h2 data-i18n="vlan_pvid">Use as default VLAN for incoming traffic (PVID)</h2>
|
||||
<div id="pPorts"><button type="button" style="transform: translateY(-100%); margin: 0 50px 0 0" onclick="pvClicked(true);" data-i18n="vlan_select_all">Select all</button> </div>
|
||||
<script src="/vlan.js"></script>
|
||||
<br/> <input style="width:40%;" class="action" id="vlan_sub" onclick="vlanSub();" type="button" data-i18n="vlan_update" value="Update / Create">
|
||||
<script src="/vlan_sub.js"></script>
|
||||
</form>
|
||||
<h2 data-i18n="vlan_configured">Configured VLANs</h2>
|
||||
<table id="vlanTable" style="width:90%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>VLAN</th>
|
||||
<th data-i18n="vlan_col_name">Name</th>
|
||||
<th data-i18n="vlan_col_member">Member Ports</th>
|
||||
<th data-i18n="vlan_col_tagged">Tagged Ports</th>
|
||||
<th data-i18n="vlan_col_untagged">Untagged Ports</th>
|
||||
<th data-i18n="vlan_col_pvid">PVID Ports</th>
|
||||
<th data-i18n="vlan_col_delete">Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="vlanTableBody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
-211
@@ -1,211 +0,0 @@
|
||||
var vlanInterval = Number();
|
||||
|
||||
function vlanForm() {
|
||||
if (!numPorts)
|
||||
return;
|
||||
var t = document.getElementById('tPorts');
|
||||
var u = document.getElementById('uPorts');
|
||||
var p = document.getElementById('pPorts');
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
const d = document.createElement("div");
|
||||
d.classList.add("cbgroup");
|
||||
const l = document.createElement("label");
|
||||
l.innerHTML = "" + i;
|
||||
l.classList.add("cbgroup");
|
||||
const inp = document.createElement("input");
|
||||
inp.type = "checkbox"; inp.setAttribute("class","psel");
|
||||
inp.id = "tport" + i;
|
||||
inp.setAttribute('onclick', `setC("u", ${i}, false);`);
|
||||
const o = document.createElement("img");
|
||||
if (pIsSFP[i - 1]) {
|
||||
o.src = "sfp.svg"; o.width ="60"; o.height ="60";
|
||||
} else {
|
||||
o.src = "port.svg"; o.width = "40"; o.height = "40";
|
||||
}
|
||||
l.appendChild(inp); l.appendChild(o);
|
||||
d.appendChild(l)
|
||||
t.appendChild(d);
|
||||
var d2=d.cloneNode(true);
|
||||
d2.children[0].children[0].id = "uport" + i;
|
||||
d2.children[0].children[0].setAttribute('onclick', `setC("t", ${i}, false);`);
|
||||
u.appendChild(d2);
|
||||
var d3=d.cloneNode(true);
|
||||
d3.children[0].children[0].id = "pport" + i;
|
||||
d3.children[0].children[0].removeAttribute('onclick');
|
||||
p.appendChild(d3);
|
||||
}
|
||||
}
|
||||
|
||||
function setC(t, p, c){
|
||||
document.getElementById(t+'port'+p).checked=c;
|
||||
}
|
||||
|
||||
function utClicked(t){
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
setC('t', i, t); setC('u', i, !t);
|
||||
}
|
||||
}
|
||||
|
||||
function pvClicked(p){
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
setC('p', i, p);
|
||||
}
|
||||
}
|
||||
|
||||
function fetchVLAN() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("VLAN: ", JSON.stringify(s));
|
||||
m = parseInt(s.members, 16);
|
||||
document.getElementById('vname').value = s.name;
|
||||
var members = m & 0x3FF;
|
||||
var untag = (m >> 10) & 0x3FF;
|
||||
var pvid = parseInt(s.pvid, 16);
|
||||
console.log("PVID: ", pvid);
|
||||
for (let p = 1; p <= numPorts; p++) {
|
||||
var bit = physToLogPort[p-1];
|
||||
var isMember = (members >> bit) & 1;
|
||||
var isUntag = (untag >> bit) & 1;
|
||||
setC('t', p, isMember && !isUntag);
|
||||
setC('u', p, isMember && isUntag);
|
||||
setC('p', p, (pvid >> bit) & 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
var v=document.getElementById('vid').value
|
||||
if (!v) {
|
||||
alert(t('vlan_set_id_first'));
|
||||
return;
|
||||
}
|
||||
xhttp.open("GET", `/vlan.json?vid=${v}`, true);
|
||||
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()).vlan || [];
|
||||
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(t('vlan_delete_confirm') + 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).vlan || [];
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
async function vlanSub() {
|
||||
var commands = [];
|
||||
var cmd = "vlan ";
|
||||
var v=document.getElementById('vid').value
|
||||
if (!v) {
|
||||
alert(t('vlan_set_id_first'));
|
||||
return;
|
||||
}
|
||||
cmd = cmd + v;
|
||||
if (document.getElementById('vname').value)
|
||||
cmd = cmd + ' ' + document.getElementById('vname').value;
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
if (document.getElementById('tport' + i).checked)
|
||||
cmd = cmd + ` ${i}t`;
|
||||
else if (document.getElementById('uport' + i).checked)
|
||||
cmd = cmd + ` ${i}`;
|
||||
}
|
||||
commands.push(cmd);
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
if (document.getElementById('pport' + i).checked)
|
||||
commands.push(`pvid ${i} ${v}`);
|
||||
}
|
||||
try {
|
||||
for (let c of commands) {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: c
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
}
|
||||
refreshVlanViews();
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-7
@@ -89,7 +89,7 @@ __xdata uint32_t last_session_use;
|
||||
|
||||
extern __xdata uint16_t crc_value;
|
||||
__xdata uint16_t crc_final;
|
||||
void crc16(__xdata uint8_t *v) __naked;
|
||||
void crc16_bank1(__xdata uint8_t *v) __naked;
|
||||
|
||||
|
||||
inline uint8_t is_separator(uint8_t c)
|
||||
@@ -101,10 +101,11 @@ inline uint8_t is_separator(uint8_t c)
|
||||
void httpd_init(void) __banked
|
||||
{
|
||||
config_upload = 0; // xdata is not zeroed by the startup code
|
||||
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
|
||||
// Start listening to port 80
|
||||
uip_listen(HTONS(80));
|
||||
s->tstate = TSTATE_CLOSED;
|
||||
// Not through uip_conn: it only points at a connection while uIP is
|
||||
// handling one, and nothing has set it yet at init time.
|
||||
uip_conns[0].appstate.tstate = TSTATE_CLOSED;
|
||||
fw_reset_pending = 0; // xdata is not zeroed by the startup code
|
||||
}
|
||||
|
||||
@@ -517,7 +518,7 @@ uint8_t stream_upload(void)
|
||||
if (upload_settings.p[upload_settings.bptr] == boundary[bindex]) {
|
||||
if (!bindex)
|
||||
crc_final = crc_value;
|
||||
crc16(upload_settings.p + upload_settings.bptr);
|
||||
crc16_bank1(upload_settings.p + upload_settings.bptr);
|
||||
upload_settings.bptr++;
|
||||
bindex++;
|
||||
} else {
|
||||
@@ -526,7 +527,7 @@ uint8_t stream_upload(void)
|
||||
write_len += bindex;
|
||||
bindex = 0;
|
||||
}
|
||||
crc16(upload_settings.p + upload_settings.bptr);
|
||||
crc16_bank1(upload_settings.p + upload_settings.bptr);
|
||||
flash_buf[write_len++] = upload_settings.p[upload_settings.bptr++];
|
||||
if (write_len >= FLASH_PAGE_SIZE) {
|
||||
dbg_string("len: "); dbg_short(write_len); dbg_char(' ');
|
||||
@@ -1012,7 +1013,7 @@ void httpd_appcall(void)
|
||||
if (!authenticated && !(f_data[entry].start == FDATA_START_login_html
|
||||
|| f_data[entry].start == FDATA_START_port_svg
|
||||
|| f_data[entry].start == FDATA_START_sfp_svg
|
||||
|| f_data[entry].start == FDATA_START_i18n_js
|
||||
|| f_data[entry].start == FDATA_START_main_js
|
||||
|| f_data[entry].start == FDATA_START_style_css)) {
|
||||
send_to_login();
|
||||
goto do_send;
|
||||
@@ -1030,7 +1031,10 @@ void httpd_appcall(void)
|
||||
* response; without advertising it a browser reuses the socket
|
||||
* from its keep-alive pool and the next request hits the already
|
||||
* closed connection (a POST is then dropped without a retry). */
|
||||
slen += strtox(outbuf + slen, "; charset=UTF-8\r\nCache-Control: max-age=60, must-revalidate\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\nContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; form-action 'self'\r\n\r\n");
|
||||
slen += strtox(outbuf + slen, "; charset=UTF-8\r\n");
|
||||
if (f_data[entry].gzip)
|
||||
slen += strtox(outbuf + slen, "Content-Encoding: gzip\r\n");
|
||||
slen += strtox(outbuf + slen, "Cache-Control: max-age=60, must-revalidate\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\nContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; form-action 'self'\r\n\r\n");
|
||||
|
||||
len_left = f_data[entry].len;
|
||||
if (len_left > (TCP_OUTBUF_SIZE - slen)) {
|
||||
|
||||
+1
-3
@@ -299,11 +299,9 @@ void send_vlan(uint16_t vlan)
|
||||
bool send_counters(uint8_t phys_port)
|
||||
{
|
||||
uint8_t phys_port_idx = phys_port - 1;
|
||||
if (phys_port_idx > 8)
|
||||
if (phys_port_idx > (machine.max_port - machine.min_port))
|
||||
goto err;
|
||||
uint8_t log_port = machine.phys_to_log_port[phys_port_idx];
|
||||
if (log_port == 0)
|
||||
goto err;
|
||||
|
||||
dbg_string("send_counters called: "); dbg_byte(phys_port_idx); dbg_char('\n');
|
||||
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
|
||||
|
||||
+14
-10
@@ -332,21 +332,25 @@ void phy_set_speed(void) __banked
|
||||
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_CTRL, 0x2000); // Clear bit 12: No Autoneg, Set Extended Pages (bit 13)
|
||||
if (phy_settings.speed == PHY_SPEED_10M) {
|
||||
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_MGBASE_CTRL, 0x6001);
|
||||
if (!phy_settings.duplex)
|
||||
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_ADV, 0x1421);
|
||||
else if (phy_settings.duplex == 1)
|
||||
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_ADV, 0x1441);
|
||||
uint16_t adv10;
|
||||
if (phy_settings.duplex == PHY_DUPLEX_HALF)
|
||||
adv10 = 0x1421;
|
||||
else if (phy_settings.duplex == PHY_DUPLEX_FULL)
|
||||
adv10 = 0x1441;
|
||||
else
|
||||
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_ADV, 0x1461);
|
||||
adv10 = 0x1461;
|
||||
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_ADV, adv10);
|
||||
phy_modify(phy_settings.port, PHY_MMD31, PHY_MMD31_GBCR, 0x0200, 0x0000);
|
||||
} else if (phy_settings.speed == PHY_SPEED_100M) {
|
||||
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_MGBASE_CTRL, 0x6001);
|
||||
if (!phy_settings.duplex)
|
||||
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_ADV, 0x1481);
|
||||
if (phy_settings.duplex == 1)
|
||||
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_ADV, 0x1501);
|
||||
uint16_t adv100;
|
||||
if (phy_settings.duplex == PHY_DUPLEX_HALF)
|
||||
adv100 = 0x1481;
|
||||
else if (phy_settings.duplex == PHY_DUPLEX_FULL)
|
||||
adv100 = 0x1501;
|
||||
else
|
||||
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_ADV, 0x1581);
|
||||
adv100 = 0x1581;
|
||||
phy_write(phy_settings.port, PHY_MMD_AN, PHY_ANEG_ADV, adv100);
|
||||
phy_modify(phy_settings.port, PHY_MMD31, PHY_MMD31_GBCR, 0x0200, 0x0000);
|
||||
} else {
|
||||
// AN Advertisement Register (MMD 7.0x0010)
|
||||
|
||||
+4
-5
@@ -32,7 +32,7 @@ extern __xdata uint32_t flash_size;
|
||||
|
||||
extern __xdata uint16_t crc_value;
|
||||
__xdata struct machine_runtime machine_detected;
|
||||
void crc16(__xdata uint8_t *v) __naked;
|
||||
void crc16_bank1(__xdata uint8_t *v) __naked;
|
||||
void flash_default_config(void);
|
||||
void early_boot_handle_button(void);
|
||||
|
||||
@@ -515,8 +515,7 @@ void isr_ext1(void) __interrupt(2)
|
||||
*/
|
||||
void isr_ext2(void) __interrupt(8)
|
||||
{
|
||||
EXIF &= 0xef; // Clear IRQ flag (bit 7) in EXIF
|
||||
PCON |= 1; // Enter Idle mode until interrupt occurs
|
||||
EXIF &= 0xef; // Clear IRQ flag (bit 4) in EXIF
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -525,7 +524,7 @@ void isr_ext2(void) __interrupt(8)
|
||||
*/
|
||||
void isr_ext3(void) __interrupt(9)
|
||||
{
|
||||
EXIF &= 0xdf; // Clear IRQ flag (bit 6) in EXIF
|
||||
EXIF &= 0xdf; // Clear IRQ flag (bit 5) in EXIF
|
||||
}
|
||||
|
||||
// Timer2: handles system tick.
|
||||
@@ -2096,7 +2095,7 @@ void check_and_flash_update_image(void)
|
||||
flash_read_bulk(flash_buf);
|
||||
bptr = flash_buf;
|
||||
for (j = 0; j < FLASH_BUF_SIZE; j++) {
|
||||
crc16(bptr++);
|
||||
crc16_bank1(bptr++);
|
||||
}
|
||||
source += FLASH_BUF_SIZE;
|
||||
if (i%16 == 0) write_char('.');
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ $(BUILDDIR)/injector: injector.c
|
||||
gcc $^ $(CCFLAGS) $@
|
||||
|
||||
$(BUILDDIR)/fileadder: fileadder.c
|
||||
gcc $^ $(CCFLAGS) $@
|
||||
gcc $^ $(CCFLAGS) $@ -lz
|
||||
|
||||
$(BUILDDIR)/crc_calculator: crc_calculator.c
|
||||
gcc $^ $(CCFLAGS) $@
|
||||
|
||||
+58
-5
@@ -11,6 +11,7 @@
|
||||
#include <string.h>
|
||||
#include <argp.h>
|
||||
#include <stdbool.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#define OFFSET 2
|
||||
|
||||
@@ -33,6 +34,7 @@ int fbuf_p;
|
||||
int xbuf_p;
|
||||
bool addsDir = false;
|
||||
int callNum = 0;
|
||||
struct arguments arguments;
|
||||
|
||||
const char *argp_program_version = "fileadder 0.1";
|
||||
const char *argp_program_bug_address = "<git@logicog.de>";
|
||||
@@ -45,6 +47,7 @@ static struct argp_option options[] = {
|
||||
{ "address", 'a', "SIZE", 0, "Address where data is placed, default is 0x1000000 if option is used, otherwise 0x1fd000"},
|
||||
{ "prefix", 'p', "FILE", 0, "Prefix for header and index file generation"},
|
||||
{ "bank", 'b', "BANKNAME", 0, "Generate #pragma with given bank-name"},
|
||||
{ "gzip", 'z', 0, 0, "gzip-compress the added files"},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@@ -57,6 +60,7 @@ struct arguments {
|
||||
char *bank;
|
||||
bool overwrite;
|
||||
bool add_zero;
|
||||
bool gzip;
|
||||
};
|
||||
|
||||
|
||||
@@ -86,6 +90,9 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state)
|
||||
case 'b':
|
||||
arguments->bank = arg? arg : "BANK1";
|
||||
break;
|
||||
case 'z':
|
||||
arguments->gzip = true;
|
||||
break;
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
}
|
||||
@@ -118,6 +125,47 @@ int addfile(const char *name, int addr)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* gzip-compresses the data at buffer[addr] in place (gzip format, as
|
||||
* expected by the Content-Encoding: gzip header the httpd serves it
|
||||
* with). Returns the compressed size.
|
||||
*/
|
||||
int gzipBuffer(int addr, int len)
|
||||
{
|
||||
uLong bound = compressBound(len);
|
||||
unsigned char *out = malloc(bound);
|
||||
if (!out) {
|
||||
perror("malloc");
|
||||
return 0;
|
||||
}
|
||||
|
||||
z_stream zs;
|
||||
memset(&zs, 0, sizeof(zs));
|
||||
if (deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 8,
|
||||
Z_DEFAULT_STRATEGY) != Z_OK) {
|
||||
fprintf(stderr, "gzip: deflateInit2 failed\n");
|
||||
free(out);
|
||||
return 0;
|
||||
}
|
||||
zs.next_in = (Bytef *)&buffer[addr];
|
||||
zs.avail_in = len;
|
||||
zs.next_out = out;
|
||||
zs.avail_out = bound;
|
||||
if (deflate(&zs, Z_FINISH) != Z_STREAM_END) {
|
||||
fprintf(stderr, "gzip: deflate failed\n");
|
||||
deflateEnd(&zs);
|
||||
free(out);
|
||||
return 0;
|
||||
}
|
||||
int out_len = zs.total_out;
|
||||
deflateEnd(&zs);
|
||||
|
||||
memcpy(&buffer[addr], out, out_len);
|
||||
free(out);
|
||||
return out_len;
|
||||
}
|
||||
|
||||
|
||||
int hasSuffix(const char *str, const char *suffix)
|
||||
{
|
||||
if (!str || !suffix)
|
||||
@@ -164,9 +212,9 @@ int addidx(const char *name, int addr, int len)
|
||||
|
||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#define FDATA_START_%s 0x%x\n", s, addr);
|
||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#define FDATA_SIZE_%s %d\n", s, len);
|
||||
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, " {\"/%s\", FDATA_START_%s, FDATA_SIZE_%s, %s},\n", name, s, s, getMime(name));
|
||||
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, " {\"/%s\", FDATA_START_%s, FDATA_SIZE_%s, %s, %d},\n", name, s, s, getMime(name), arguments.gzip? 1 : 0);
|
||||
if (!strcmp(name, "index.html"))
|
||||
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, " {\"/\", FDATA_START_%s, FDATA_SIZE_%s, mime_HTML},\n", s, s);
|
||||
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, " {\"/\", FDATA_START_%s, FDATA_SIZE_%s, mime_HTML, %d},\n", s, s, arguments.gzip? 1 : 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -216,7 +264,6 @@ int replaceCalls(int pos)
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct arguments arguments;
|
||||
int arg_index;
|
||||
char tmpfilename[] = "image_XXXXXX";
|
||||
struct stat s;
|
||||
@@ -227,6 +274,7 @@ int main(int argc, char **argv)
|
||||
arguments.overwrite = true;
|
||||
arguments.prefix = 0;
|
||||
arguments.bank = NULL;
|
||||
arguments.gzip = false;
|
||||
|
||||
argp_parse(&argp, argc, argv, 0, &arg_index, &arguments);
|
||||
if (!arg_index)
|
||||
@@ -269,7 +317,7 @@ int main(int argc, char **argv)
|
||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "#include <stdint.h>\n\n");
|
||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p,
|
||||
"typedef enum mime_type_e {\n mime_HTML = 0,\n mime_SVG,\n mime_ICO,\n mime_PNG,\n mime_JS,\n mime_CSS,\n mime_TXT\n} mime_type_t;\n\n");
|
||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "struct f_data {\n __code char *file;\n uint32_t start;\n uint16_t len;\n mime_type_t mime;\n};\n\n");
|
||||
defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "struct f_data {\n __code char *file;\n uint32_t start;\n uint16_t len;\n mime_type_t mime;\n uint8_t gzip;\n};\n\n");
|
||||
// defbuf_p += snprintf(&dbuf[defbuf_p], DEF_SIZE - defbuf_p, "typedef uint16_t (* fcall_ptr)(void);\n\n");
|
||||
|
||||
ibuf_p += snprintf(&ibuf[ibuf_p], INDEX_SIZE - ibuf_p, "// This file is automatically generated, do not edit!\n\n");
|
||||
@@ -310,7 +358,10 @@ int main(int argc, char **argv)
|
||||
size_t data_read = addfile(pathbuffer, addr);
|
||||
if (data_read) {
|
||||
if (arguments.add_zero) {
|
||||
buffer[addr + data_read + 1] = '\0';
|
||||
/* NUL-terminate the data so the file table and the
|
||||
* strlen()-based size computation are deterministic
|
||||
* (the buffer past the file is otherwise uninitialised) */
|
||||
buffer[addr + data_read] = '\0';
|
||||
data_read++;
|
||||
}
|
||||
printf("Data inserted from %s at 0x%x, size: %ld\n", pathbuffer, addr, data_read);
|
||||
@@ -319,6 +370,8 @@ int main(int argc, char **argv)
|
||||
data_read = replaceCalls(addr);
|
||||
if (old_len > data_read)
|
||||
memset(buffer + addr + data_read, 0, old_len - data_read);
|
||||
if (arguments.gzip)
|
||||
data_read = gzipBuffer(addr, data_read);
|
||||
addidx(in_file->d_name, addr, data_read);
|
||||
addr += data_read;
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#define BUFFER_SIZE 24000
|
||||
#define BUFFER_SIZE 131072
|
||||
|
||||
struct Server {
|
||||
int domain;
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Safe minifier for the WebUI sources (HTML/JS).
|
||||
|
||||
Removes comments and line-leading whitespace without touching string or
|
||||
regex literals, so the output is functionally identical to the input.
|
||||
Used by the firmware Makefile before the files are embedded in the
|
||||
flash; the raw sources in html/ stay untouched for development.
|
||||
|
||||
Binary files (.ico) are copied through unchanged.
|
||||
|
||||
Usage: minify.py <src> <dst>
|
||||
"""
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
|
||||
def js_min(s):
|
||||
out = []
|
||||
i = 0
|
||||
n = len(s)
|
||||
prev = '\n'
|
||||
# Characters after which a '/' starts a regex literal rather than a
|
||||
# division: operator / delimiter / statement-start contexts.
|
||||
regex_start = set('([{=,:;!&|?+-*%^~<>')
|
||||
while i < n:
|
||||
c = s[i]
|
||||
if c == '"' or c == "'":
|
||||
j = i + 1
|
||||
while j < n and s[j] != c:
|
||||
if s[j] == '\\':
|
||||
j += 1
|
||||
j += 1
|
||||
out.append(s[i:j + 1])
|
||||
i = j + 1
|
||||
prev = c
|
||||
elif c == '/' and (i + 1 >= n or s[i + 1] not in '/=*') and \
|
||||
(prev in regex_start or prev == '\n'):
|
||||
# Regex literal (e.g. /^#\// or /https?:\/\/x/g): copy through
|
||||
# the closing '/', honouring escapes and character classes.
|
||||
j = i + 1
|
||||
in_class = False
|
||||
while j < n:
|
||||
ch = s[j]
|
||||
if ch == '\\':
|
||||
j += 2
|
||||
continue
|
||||
if ch == '[':
|
||||
in_class = True
|
||||
elif ch == ']':
|
||||
in_class = False
|
||||
elif ch == '/' and not in_class:
|
||||
break
|
||||
j += 1
|
||||
out.append(s[i:j + 1])
|
||||
i = j + 1
|
||||
prev = '/'
|
||||
elif c == '/' and i + 1 < n and s[i + 1] == '/':
|
||||
j = s.find('\n', i)
|
||||
i = j if j >= 0 else n
|
||||
elif c == '/' and i + 1 < n and s[i + 1] == '*':
|
||||
j = s.find('*/', i + 2)
|
||||
i = j + 2 if j >= 0 else n
|
||||
else:
|
||||
out.append(c)
|
||||
i += 1
|
||||
if c not in ' \t':
|
||||
prev = c
|
||||
lines = [l.strip() for l in ''.join(out).split('\n')]
|
||||
return '\n'.join(l for l in lines if l) + '\n'
|
||||
|
||||
|
||||
def html_min(s):
|
||||
s = re.sub(r'<!--.*?-->', '', s, flags=re.S)
|
||||
lines = [l.strip() for l in s.split('\n')]
|
||||
return '\n'.join(l for l in lines if l) + '\n'
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print('usage: minify.py <src> <dst>', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
src, dst = sys.argv[1], sys.argv[2]
|
||||
if src.endswith('.ico'):
|
||||
shutil.copyfile(src, dst)
|
||||
print('%s: copied %d bytes' % (src.split('/')[-1], len(open(src, 'rb').read())))
|
||||
return
|
||||
with open(src, 'r', encoding='utf-8', errors='replace') as f:
|
||||
s = f.read()
|
||||
if src.endswith('.js'):
|
||||
out = js_min(s)
|
||||
else:
|
||||
out = html_min(s)
|
||||
with open(dst, 'w', encoding='utf-8') as f:
|
||||
f.write(out)
|
||||
print('%s: %d -> %d bytes' % (src.split('/')[-1], len(s), len(out)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user