Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bae4052a3 | ||
|
|
0d115e0e0b | ||
|
|
8e78d1e629 | ||
|
|
94992562fd | ||
|
|
6150ada5f8 | ||
|
|
8116ef0a5b | ||
|
|
0cd56b1196 | ||
|
|
3b3f36256c | ||
|
|
6ea9503ab2 | ||
|
|
e0df37f359 | ||
|
|
c04a5fc3a4 | ||
|
|
2f005d2993 | ||
|
|
f65416f0d1 | ||
|
|
c05d83132b | ||
|
|
dcd3cd5a98 | ||
|
|
9f107b6e60 | ||
|
|
15b10d9ed0 | ||
|
|
abf1cd1344 | ||
|
|
b5b52776bc | ||
|
|
0486c34bfd | ||
|
|
690092d564 | ||
|
|
115ea09ebb | ||
|
|
f3e1a393b5 | ||
|
|
206c72b5ef | ||
|
|
472811cc9a | ||
|
|
80552816b9 | ||
|
|
c1caf6f569 | ||
|
|
ddad65fe90 | ||
|
|
ff7b517491 | ||
|
|
65417433eb | ||
|
|
b7621fad31 | ||
|
|
82f78cb8f2 | ||
|
|
75f2ade1a1 | ||
|
|
3e91b05a42 | ||
|
|
ec1cb4106d | ||
|
|
62c6c65f1c | ||
|
|
c691591295 | ||
|
|
7908044a44 | ||
|
|
0ec1a5e63f | ||
|
|
114af559c7 | ||
|
|
3ec9319d77 | ||
|
|
8d7faf5312 |
No files matched your search
@@ -3,6 +3,7 @@ name: Build firmware
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -17,6 +18,8 @@ jobs:
|
||||
run: |
|
||||
apt update
|
||||
apt install make gcc sdcc xxd python-is-python3 libjson-c-dev zlib1g-dev -y
|
||||
- name: Run the host-side unit tests
|
||||
run: make -C test
|
||||
- name: Check if machine.c can be compiled for all machines
|
||||
run: make machine_check
|
||||
- name: Make project
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
- Web UI
|
||||
- Compress the embedded assets (minify + gzip) and switch to a single-page layout. #315
|
||||
- Replace the multi-page UI with a themed single-page app: light, dark and Selenized themes following the browser by default,
|
||||
English, Japanese and Chinese, save to flash merges the command log into the startup config and verifies the write,
|
||||
firmware images are checked in the browser before upload. #429
|
||||
|
||||
## Changed
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ __xdata uip_ipaddr_t server;
|
||||
#define DHCP_PARAM_DNS 6
|
||||
#define DHCP_END 255
|
||||
|
||||
#pragma codeseg BANK2
|
||||
#pragma codeseg BANK3
|
||||
#pragma constseg BANK2
|
||||
|
||||
struct dhcp_pkt {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Lianguo HYWS-SGT0108S
|
||||
|
||||
Following is documentation for unmanaged switch marked as `HYWS-SGT0108S`.
|
||||
|
||||
Original software is running UART on 9600 baud rate. Output is very minimal.
|
||||
|
||||
Using SPI clamp in-board is the only method for initial installation and update.
|
||||
|
||||
Stock flash chip is 512KB in size. Consider replacing for update through Web UI.
|
||||
|
||||
- Header for uart is clearly identified.
|
||||
- The red LED act as a powered-on LED.
|
||||
- There is no SYS LED.
|
||||
- There is a footprint for a Reset button. Unpopulated. Unclear if routed back to RTL8373.
|
||||
|
||||
### Label specifications
|
||||
|
||||
- **Name**: 2.5G Ethernet Switch 8+1
|
||||
- **Model**: HYWS-SGT0108S
|
||||
- **Ports**:
|
||||
- 8 × RJ45: 10/100/1000/2500 Mbps
|
||||
- 1 × SFP: 1000 / 2500 / 10000 Mbps
|
||||
|
||||
### What works
|
||||
|
||||
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
|
||||
- SFP port supporting 1G, 2.5G and 10G modules
|
||||
- RJ45 LEDs (Blue+White)
|
||||
- SFP Single LED (Blue)
|
||||
|
||||
### Assembled unit
|
||||
|
||||
Top side
|
||||
|
||||
<img src="photos/HYWS-SGT0108S-unmanaged\Assembled-top.jpg" width="300" />
|
||||
|
||||
Front
|
||||
|
||||
<img src="photos/HYWS-SGT0108S-unmanaged\Assembled-front.jpg" width="300" />
|
||||
|
||||
Bottom
|
||||
|
||||
<img src="photos/HYWS-SGT0108S-unmanaged\Assembled-bottom.jpg" width="300" />
|
||||
|
||||
### PCB overview
|
||||
|
||||
**Board markings**
|
||||
- Top silkscreen: 2G5F_20G_V1.01 / 2023-09-28
|
||||
|
||||
Top side
|
||||
|
||||
<img src="photos/HYWS-SGT0108S-unmanaged\PCB-top.jpg" width="300" />
|
||||
|
||||
Bottom
|
||||
|
||||
<img src="photos/HYWS-SGT0108S-unmanaged\PCB-bot.jpg" width="300" />
|
||||
|
||||
Original SPI Flash Chip
|
||||
|
||||
<img src="photos/HYWS-SGT0108S-unmanaged\og_spi_flash.jpg" width="300" />
|
||||
|
||||
## Power supply
|
||||
|
||||
Input power is delivered via barell plug, `12V 1A` adapter was provided.
|
||||
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 791 KiB |
|
After Width: | Height: | Size: 760 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
@@ -1,135 +1,107 @@
|
||||
# Supporting Multiple Languages in the Web UI
|
||||
|
||||
The firmware uses a client-side i18n approach
|
||||
all translations are stored in a single JavaScript dictionary embedded in the firmware.
|
||||
No server-side changes are needed.
|
||||
The firmware uses a client-side i18n approach: all translations are stored
|
||||
in one JavaScript dictionary embedded in the firmware. No server-side
|
||||
changes are needed.
|
||||
|
||||
## Architecture
|
||||
|
||||
All translation logic lives in `html/i18n.js`. The file contains:
|
||||
All translation logic lives at the top of `html/app.js`:
|
||||
|
||||
- A `LANG` object with one sub-object per language (`en`, `ja`, ...)
|
||||
- Language auto-detection (browser language → `localStorage` override)
|
||||
- `t(key)` — look up a translated string
|
||||
- `setLang(lang)` — switch language and update the page
|
||||
- `applyTranslation(el)` — apply translation to one DOM element
|
||||
- A `LANG` object with one sub-object per language (`en`, `ja`, `zh`)
|
||||
- Language auto-detection (browser language, overridden by the
|
||||
`rtl_lang` key in `localStorage`, which the language selector in the
|
||||
System section writes)
|
||||
- `t(key, vars)` looks up a translated string; `{name}` placeholders in
|
||||
the string are replaced from `vars`, e.g. `t("v_del_q", {n: vid})`
|
||||
- `i18nApply()` applies the dictionary to the static markup once at load
|
||||
|
||||
Translation keys are **flat strings** (no nesting). The English keys in `LANG.en` also serve as the fallback when a key is missing in another language.
|
||||
Translation keys are flat strings. The English entry is the fallback when
|
||||
a key is missing in another language, and the key itself is the fallback
|
||||
when it is missing everywhere.
|
||||
|
||||
Changing the language stores the choice and reloads the page, so every
|
||||
dynamically built table is rebuilt in the new language.
|
||||
|
||||
`login.html` is served before authentication and does not load `app.js`;
|
||||
it carries its own five strings per language in an inline table.
|
||||
|
||||
## How to Add a New Language
|
||||
|
||||
### 1. Add a dictionary entry in `html/i18n.js`
|
||||
### 1. Add a dictionary entry in `html/app.js`
|
||||
|
||||
Append a new sub-object to the `LANG` object. Every key from `LANG.en` must be present:
|
||||
Append a sub-object to `LANG`. Every key from `LANG.en` should be present:
|
||||
|
||||
```js
|
||||
var LANG = {
|
||||
en: {
|
||||
nav_overview: 'Overview',
|
||||
nav_port_config: 'Port Configuration',
|
||||
// ... all keys for English
|
||||
},
|
||||
ja: {
|
||||
nav_overview: '概要',
|
||||
nav_port_config: 'ポート設定',
|
||||
// ... all keys for Japanese
|
||||
},
|
||||
LANGCODE: { // ← add your language here
|
||||
nav_overview: '...',
|
||||
nav_port_config: '...',
|
||||
// ... translate every key
|
||||
},
|
||||
var LANG={
|
||||
en:{
|
||||
nav_dash:"Dashboard",
|
||||
...
|
||||
},
|
||||
LANGCODE:{ // add your language here
|
||||
nav_dash:"...",
|
||||
...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Add the language to the navigation sidebar
|
||||
### 2. Add the language to the selector in `html/index.html`
|
||||
|
||||
In `html/navigation.js`, add an `<option>` to the language selector:
|
||||
|
||||
```js
|
||||
+ "<option value='en'>English</option><option value='ja'>日本語</option>"
|
||||
```html
|
||||
<select class="ctl" id="langSel" data-i18n-t="sy_lang">
|
||||
<option value="en">English</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="zh">中文</option>
|
||||
<option value="LANGCODE">Native name</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
Replace with:
|
||||
### 3. Add the login strings in `html/login.html`
|
||||
|
||||
```js
|
||||
+ "<option value='en'>English</option><option value='ja'>日本語</option><option value='LANGCODE'>Native Name</option>"
|
||||
```
|
||||
The inline table there holds, per language: page title, subtitle,
|
||||
password label, button label, wrong-password message.
|
||||
|
||||
### 3. Verify auto-detection
|
||||
### 4. Verify auto-detection
|
||||
|
||||
The language detection code in `i18n.js` reads `navigator.language` and normalises it to the first two characters:
|
||||
|
||||
```js
|
||||
var browser = (navigator.language || navigator.userLanguage || 'en').substring(0, 2);
|
||||
return LANG[browser] ? browser : 'en';
|
||||
```
|
||||
|
||||
If the two-letter code matches a key in `LANG`, it will be auto-selected. No changes needed here.
|
||||
The detection normalises `navigator.language` to its first two
|
||||
characters and selects that language if `LANG` has it, otherwise English.
|
||||
|
||||
## Two Translation Mechanisms
|
||||
|
||||
### (A) `data-i18n` attribute (declarative — for HTML)
|
||||
|
||||
Add `data-i18n="key_name"` to any HTML element. The English text goes in the element content as a fallback:
|
||||
### (A) `data-i18n` attributes (declarative, for HTML)
|
||||
|
||||
```html
|
||||
<h1 data-i18n="port_heading">Port Configuration</h1>
|
||||
<input type="button" data-i18n="port_apply" value="Apply">
|
||||
<option data-i18n="port_auto">Auto</option>
|
||||
<title data-i18n="port_title">Port Configuration</title>
|
||||
<h2 data-i18n="pt_title">Port configuration</h2>
|
||||
<button class="ctl" data-i18n="c_apply" data-i18n-t="sy_dhcp_t">Apply</button>
|
||||
<input class="in" data-i18n-p="l2_filter">
|
||||
```
|
||||
|
||||
On page load, `applyTranslation()` sets:
|
||||
- `data-i18n` sets the element's text content
|
||||
- `data-i18n-t` sets the `title` attribute (tooltip)
|
||||
- `data-i18n-p` sets the `placeholder` attribute
|
||||
|
||||
- `el.value` for `<input type="submit|button">`
|
||||
- `el.textContent` for `<option>`, `<title>`
|
||||
- `el.innerHTML` for everything else
|
||||
The English text stays in the markup as the fallback and as the source
|
||||
of truth for what the key means.
|
||||
|
||||
### (B) `t('key')` call (imperative — for JavaScript strings)
|
||||
|
||||
When generating HTML or text in JavaScript, wrap translatable strings with `t()`:
|
||||
### (B) `t("key")` calls (imperative, for JavaScript)
|
||||
|
||||
```js
|
||||
td.appendChild(document.createTextNode(t('common_port') + i));
|
||||
td.innerHTML = t('port_auto');
|
||||
iHTML += "<tr><td>" + t('port_vendor') + "</td></tr>";
|
||||
tr.insertCell().textContent=t("c_port")+" "+p;
|
||||
toast(t("v_loaded",{n:vid}),"ok");
|
||||
```
|
||||
|
||||
### Special Case: Link Speed Display
|
||||
|
||||
The `linkS` array in `html/main.js` maps numeric link states to display strings. The first two entries (`speed_disabled`, `speed_down`) use `t()` for translation; the remaining entries are static literals (they are the same in all languages):
|
||||
|
||||
```js
|
||||
const linkS = [
|
||||
function(){return t('speed_disabled')},
|
||||
function(){return t('speed_down')},
|
||||
"10M", "100M", "1000M", "500M", "10G", "2.5G", "5G"
|
||||
];
|
||||
function linkText(idx) { var v = linkS[idx]; return typeof v === 'function' ? v() : v; }
|
||||
```
|
||||
|
||||
Always use `linkText(idx)` (not `linkS[idx]`) to read these values.
|
||||
Strings that are identical in every language (`10M`, `2.5G`, `MAC`,
|
||||
`VLAN`, `CPU`, `RSTP`) are literals, not dictionary entries.
|
||||
|
||||
## Size Considerations
|
||||
|
||||
- `html/i18n.js` is embedded in the firmware filesystem (~14 KB for two languages)
|
||||
- Each new language adds roughly the same number of bytes as the English dictionary (~3–4 KB)
|
||||
- The firmware binary is padded to 512 KiB, so a few extra KB do not change the flash footprint
|
||||
- Values that are identical in all languages should be inlined as literals rather than added to the dictionary (e.g., `"10M"`, `"2.5G"`, `"MAC"`, `"VLAN"`, `"CPU"`)
|
||||
- Each language adds roughly 3 KB to `app.js` before compression; the
|
||||
build gzips the file, so the flash cost of a language is closer to 1 KB
|
||||
- The largest embedded file must stay below 64 KB after compression
|
||||
(`uint16_t` length in the file table); `app.js` with three languages
|
||||
is about 29 KB compressed
|
||||
|
||||
## Build
|
||||
|
||||
No special flags are needed. The `html/` directory is embedded by `fileadder` during the build.
|
||||
|
||||
## Script Load Order
|
||||
|
||||
`i18n.js` must be loaded after `main.js` (which defines `t()`'s dependencies like `LANG`) but before any page-specific JS that calls `t()`:
|
||||
|
||||
```html
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<script src="/eee.js"></script> <!-- uses t() -->
|
||||
```
|
||||
|
||||
The `navigation.js` script is loaded last (bottom of `<body>`).
|
||||
No special flags are needed. `html/` is minified and embedded by the
|
||||
normal build (see `webui-compression.md`).
|
||||
@@ -33,6 +33,7 @@ The following devices have been tested and are fully working:
|
||||
| Xikestore| SKS3200M-4GPY2XF| Yes | [SWTG024AS-v1.0](devices/SWTG024AS.md) | | 4 + 2 |
|
||||
| XikeStor | SKS3200-8E1X | Yes | [SWTG118AS-V2.1-17462](devices/SWTGW218AS.md) | 2M | 8 + 1 |
|
||||
| Ztyuav | Z-QWYT0402 | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
|
||||
| Lianguo | HYWS-SGT0108S | No | [2G5F_20G_V1.01](devices/HYWS-SGT0108S.md) | 0.5M | 8 + 1 |
|
||||
|
||||
For KP-9000-6XH-X2 / KP-9000-6XHML-X2 / Mokerlink 2G040210GSM devices, select
|
||||
the machine target by PCB revision. The ML/non-ML or managed/unmanaged label
|
||||
|
||||
@@ -19,16 +19,17 @@ html/ --(minify.py)--> output/html_min/ --(fileadder -z)--> flash image
|
||||
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
|
||||
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.
|
||||
the authentication gate and is self-contained (its own styles and
|
||||
strings), so nothing else is served before login. Only these two
|
||||
pages plus `app.js` and the favicon are embedded; the stylesheet and
|
||||
the theme bootstrap are inline in `index.html`.
|
||||
- All JavaScript (translation table, shared helpers, navigation and the
|
||||
per-section code) lives in one `html/app.js` that the page loads as
|
||||
its only script, so the browser fetches it once and gzip can compress
|
||||
across all of it. A section starts its pollers when it is shown and
|
||||
stops them when it is left; 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
|
||||
@@ -71,7 +72,8 @@ 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`).
|
||||
(largest gzip output: about 29 KB for `app.js`, three languages
|
||||
included).
|
||||
- `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.
|
||||
|
||||
@@ -1,310 +1,449 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></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 id="ports" style="margin-left:16%;padding:1px 16px"></div>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Switch</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#f4f6f8;--panel:#ffffff;--panel2:#eaeef2;--bd:#d7dde4;--fg:#1a222c;--fg2:#5c6773;
|
||||
--ac:#0969da;--acfg:#ffffff;--ok:#158744;--bad:#cf222e;--warn:#9a6700;--cy:#1b7c83;
|
||||
--sel:rgba(9,105,218,.10);--shadow:0 2px 10px rgba(20,30,40,.07);
|
||||
--s10:#b58900;--s100:#b58900;--s1000:#159744;--s2g5:#0d9488;--s5g:#0d9488;--s10g:#2563eb;
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--bg:#0e1116;--panel:#161b22;--panel2:#1f262e;--bd:#2c333c;--fg:#e6edf3;--fg2:#8d96a0;
|
||||
--ac:#4c8ef7;--acfg:#ffffff;--ok:#3fb950;--bad:#f85149;--warn:#d29922;--cy:#39c5cf;
|
||||
--sel:rgba(76,142,247,.16);--shadow:0 2px 10px rgba(0,0,0,.35);
|
||||
--s10:#d29922;--s100:#d29922;--s1000:#3fb950;--s2g5:#2dd4bf;--s5g:#2dd4bf;--s10g:#58a6ff;
|
||||
}
|
||||
:root[data-theme="sel-dark"]{
|
||||
--bg:#103c48;--panel:#184956;--panel2:#2d5b69;--bd:#2d5b69;--fg:#adbcbc;--fg2:#72898f;
|
||||
--ac:#4695f7;--acfg:#ffffff;--ok:#75b938;--bad:#fa5750;--warn:#dbb32d;--cy:#41c7b9;
|
||||
--sel:rgba(70,149,247,.18);--shadow:0 2px 10px rgba(0,20,26,.35);
|
||||
--s10:#dbb32d;--s100:#dbb32d;--s1000:#75b938;--s2g5:#41c7b9;--s5g:#41c7b9;--s10g:#4695f7;
|
||||
}
|
||||
:root[data-theme="sel-light"]{
|
||||
--bg:#fbf3db;--panel:#ece3cc;--panel2:#d5cdb6;--bd:#d5cdb6;--fg:#53676d;--fg2:#909995;
|
||||
--ac:#0072d4;--acfg:#ffffff;--ok:#489100;--bad:#d2212d;--warn:#ad8900;--cy:#009c8f;
|
||||
--sel:rgba(0,114,212,.12);--shadow:0 2px 10px rgba(80,70,40,.12);
|
||||
--s10:#ad8900;--s100:#ad8900;--s1000:#489100;--s2g5:#009c8f;--s5g:#009c8f;--s10g:#0072d4;
|
||||
}
|
||||
*{box-sizing:border-box;margin:0}
|
||||
html,body{height:100%}
|
||||
body{
|
||||
font:14px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
|
||||
background:var(--bg);color:var(--fg);display:flex;
|
||||
}
|
||||
button,input,select,textarea{font:inherit;color:var(--fg)}
|
||||
a{color:var(--ac);text-decoration:none}
|
||||
#nav{
|
||||
width:200px;flex:none;background:var(--panel);border-right:1px solid var(--bd);
|
||||
display:flex;flex-direction:column;position:sticky;top:0;height:100vh;
|
||||
}
|
||||
#brand{display:flex;align-items:center;gap:9px;padding:16px 16px 12px;font-weight:650;font-size:15px}
|
||||
#brand svg{flex:none;stroke:var(--ac)}
|
||||
#brand span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
#navlist{list-style:none;padding:4px 8px;overflow-y:auto;flex:1}
|
||||
#navlist li{
|
||||
display:flex;align-items:center;gap:9px;padding:7px 10px;margin:1px 0;
|
||||
border-radius:7px;cursor:pointer;color:var(--fg2);font-weight:500;
|
||||
}
|
||||
#navlist li svg{flex:none;stroke:currentColor}
|
||||
#navlist li:hover{background:var(--panel2);color:var(--fg)}
|
||||
#navlist li.act{background:var(--sel);color:var(--ac)}
|
||||
#navfoot{padding:10px 16px;border-top:1px solid var(--bd);font-size:12px;color:var(--fg2)}
|
||||
#wrap{flex:1;min-width:0;display:flex;flex-direction:column}
|
||||
#top{
|
||||
display:flex;align-items:center;gap:10px;padding:10px 20px;
|
||||
background:var(--panel);border-bottom:1px solid var(--bd);position:sticky;top:0;z-index:5;
|
||||
}
|
||||
#burger{display:none;background:none;border:0;cursor:pointer;padding:4px}
|
||||
#ttitle{font-size:16px;font-weight:650;flex:1}
|
||||
#dirty{
|
||||
display:none;font-size:12px;font-weight:600;color:var(--warn);
|
||||
border:1px solid var(--warn);border-radius:20px;padding:2px 10px;
|
||||
}
|
||||
#dirty.show{display:inline-block}
|
||||
select.ctl,button.ctl{
|
||||
background:var(--panel2);border:1px solid var(--bd);border-radius:7px;
|
||||
padding:5px 9px;cursor:pointer;font-size:13px;white-space:nowrap;
|
||||
}
|
||||
button.pri{background:var(--ac);color:var(--acfg);border-color:transparent;font-weight:600}
|
||||
button.pri:hover,button.ctl:hover{filter:brightness(1.07)}
|
||||
button:disabled{opacity:.45;cursor:default;filter:none}
|
||||
main{padding:18px 20px 40px;max-width:1400px;width:100%}
|
||||
section.tab{display:none}
|
||||
section.tab.act{display:block}
|
||||
.card{
|
||||
background:var(--panel);border:1px solid var(--bd);border-radius:10px;
|
||||
box-shadow:var(--shadow);padding:16px 18px;margin-bottom:16px;
|
||||
}
|
||||
.card h2{font-size:14px;font-weight:650;margin-bottom:12px;color:var(--fg)}
|
||||
.card h2 .hint{font-weight:400;font-size:12px;color:var(--fg2);margin-left:8px}
|
||||
.grid2{display:grid;grid-template-columns:repeat(auto-fit,minmax(340px,1fr));gap:16px;align-items:start}
|
||||
.grid2 .card{margin-bottom:0}
|
||||
table.t{width:100%;border-collapse:collapse;font-size:13px}
|
||||
table.t th{
|
||||
text-align:left;font-weight:600;color:var(--fg2);font-size:12px;
|
||||
padding:6px 8px;border-bottom:1px solid var(--bd);white-space:nowrap;
|
||||
}
|
||||
table.t th[data-sort]{cursor:pointer;user-select:none}
|
||||
table.t td{padding:6px 8px;border-bottom:1px solid var(--bd);vertical-align:middle}
|
||||
table.t tr:last-child td{border-bottom:0}
|
||||
table.t tr:hover td{background:var(--sel)}
|
||||
.num{font-variant-numeric:tabular-nums;text-align:right}
|
||||
.scrollx{overflow-x:auto}
|
||||
input.in,select.in,textarea.in{
|
||||
background:var(--bg);border:1px solid var(--bd);border-radius:7px;padding:5px 8px;outline:none;
|
||||
}
|
||||
input.in:focus,select.in:focus,textarea.in:focus{border-color:var(--ac)}
|
||||
input.in.sm{width:7em}
|
||||
label.row{display:flex;align-items:center;gap:10px;margin:8px 0}
|
||||
label.row span.lb{width:110px;flex:none;color:var(--fg2);font-size:13px}
|
||||
.switch{position:relative;display:inline-block;width:34px;height:19px;flex:none}
|
||||
.switch input{opacity:0;width:0;height:0}
|
||||
.switch i{
|
||||
position:absolute;inset:0;background:var(--bd);border-radius:19px;transition:.15s;cursor:pointer;
|
||||
}
|
||||
.switch i:before{
|
||||
content:"";position:absolute;width:15px;height:15px;border-radius:50%;
|
||||
left:2px;top:2px;background:var(--panel);transition:.15s;
|
||||
}
|
||||
.switch input:checked+i{background:var(--ok)}
|
||||
.switch input:checked+i:before{transform:translateX(15px)}
|
||||
#strip{display:flex;gap:8px;flex-wrap:wrap;padding:4px 0 2px}
|
||||
.port{
|
||||
width:46px;border:2px solid var(--bd);border-radius:7px;padding:5px 0 3px;
|
||||
text-align:center;cursor:pointer;background:var(--panel2);position:relative;
|
||||
}
|
||||
.port.sfp{width:56px}
|
||||
.port .pn{font-weight:650;font-size:13px}
|
||||
.port .ps{font-size:10px;color:var(--fg2);display:block;line-height:1.2}
|
||||
.port .jack{
|
||||
display:block;width:18px;height:12px;margin:3px auto 2px;border-radius:2px;
|
||||
border:2px solid var(--fg2);border-top-width:5px;opacity:.55;
|
||||
}
|
||||
.port.sfp .jack{width:28px;height:10px;border-top-width:3px}
|
||||
.port.up{border-color:var(--pc);background:color-mix(in srgb,var(--pc) 10%,var(--panel2))}
|
||||
.port.up .jack{border-color:var(--pc);opacity:1}
|
||||
.port.up .ps{color:var(--pc);font-weight:600}
|
||||
.port.dis{opacity:.45;border-style:dashed}
|
||||
.badge{font-size:11px;font-weight:600;border-radius:10px;padding:1px 8px;background:var(--panel2);color:var(--fg2)}
|
||||
.badge.ok{color:var(--ok)}.badge.bad{color:var(--bad)}
|
||||
.seg{display:inline-flex;border:1px solid var(--bd);border-radius:7px;overflow:hidden}
|
||||
.seg button{
|
||||
background:var(--panel);border:0;padding:3px 8px;cursor:pointer;font-size:12px;color:var(--fg2);
|
||||
border-right:1px solid var(--bd);
|
||||
}
|
||||
.seg button:last-child{border-right:0}
|
||||
.seg button.on{background:var(--ac);color:var(--acfg)}
|
||||
#toasts{position:fixed;right:16px;bottom:16px;z-index:50;display:flex;flex-direction:column;gap:8px}
|
||||
.toast{
|
||||
background:var(--panel);border:1px solid var(--bd);border-left:4px solid var(--ac);
|
||||
border-radius:8px;padding:9px 14px;box-shadow:var(--shadow);font-size:13px;max-width:340px;
|
||||
animation:tin .18s ease-out;
|
||||
}
|
||||
.toast.ok{border-left-color:var(--ok)}.toast.err{border-left-color:var(--bad)}
|
||||
@keyframes tin{from{transform:translateY(8px);opacity:0}to{transform:none;opacity:1}}
|
||||
#mback{
|
||||
position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:40;display:none;
|
||||
align-items:flex-start;justify-content:center;padding:6vh 16px;
|
||||
}
|
||||
#mback.show{display:flex}
|
||||
#modal{
|
||||
background:var(--panel);border:1px solid var(--bd);border-radius:12px;box-shadow:var(--shadow);
|
||||
width:min(760px,96vw);max-height:86vh;display:flex;flex-direction:column;
|
||||
}
|
||||
#mhead{display:flex;align-items:center;padding:13px 18px;border-bottom:1px solid var(--bd)}
|
||||
#mtitle{font-weight:650;font-size:15px;flex:1}
|
||||
#mx{background:none;border:0;font-size:19px;cursor:pointer;color:var(--fg2);padding:0 2px}
|
||||
#mbody{padding:14px 18px;overflow:auto}
|
||||
#mfoot{padding:12px 18px;border-top:1px solid var(--bd);display:flex;gap:10px;justify-content:flex-end}
|
||||
#mfoot:empty{display:none}
|
||||
pre.cfg{
|
||||
background:var(--bg);border:1px solid var(--bd);border-radius:8px;padding:10px 12px;
|
||||
font:12px/1.5 ui-monospace,Consolas,monospace;white-space:pre-wrap;word-break:break-all;
|
||||
}
|
||||
textarea.cfg{
|
||||
width:100%;min-height:200px;background:var(--bg);border:1px solid var(--bd);border-radius:8px;
|
||||
padding:10px 12px;font:12px/1.5 ui-monospace,Consolas,monospace;resize:vertical;outline:none;
|
||||
}
|
||||
.mono{font-family:ui-monospace,Consolas,monospace;font-size:12px}
|
||||
.mut{color:var(--fg2)}
|
||||
.small{font-size:12px}
|
||||
.dangerzone{border-color:color-mix(in srgb,var(--bad) 40%,var(--bd))}
|
||||
button.danger{background:var(--bad);color:#fff;border-color:transparent}
|
||||
@media (max-width:820px){
|
||||
#nav{position:fixed;left:-210px;transition:left .18s;z-index:30;box-shadow:var(--shadow)}
|
||||
#nav.open{left:0}
|
||||
#burger{display:block}
|
||||
main{padding:14px 12px 40px}
|
||||
#top{padding:8px 10px;gap:7px}
|
||||
#ttitle{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
#dirty{padding:2px 7px}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
(function(){
|
||||
var pref;
|
||||
try{pref=localStorage.getItem("theme")||"auto";}catch(e){pref="auto";}
|
||||
var dark=window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
var t=pref;
|
||||
if(pref==="auto")t=dark?"dark":"light";
|
||||
if(pref==="auto-sel")t=dark?"sel-dark":"sel-light";
|
||||
document.documentElement.dataset.theme=t;
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<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>
|
||||
<th colspan="2" data-i18n="index_settings">Settings</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<nav id="nav">
|
||||
<div id="brand">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke-width="2">
|
||||
<rect x="2" y="7" width="20" height="10" rx="2"/><path d="M6 11v2M10 11v2M14 11v2M18 11v2"/>
|
||||
</svg>
|
||||
<span id="brandname">Switch</span>
|
||||
</div>
|
||||
<ul id="navlist"></ul>
|
||||
<div id="navfoot"><span id="fver"></span></div>
|
||||
</nav>
|
||||
|
||||
<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 id="wrap">
|
||||
<header id="top">
|
||||
<button id="burger" title="Menu">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--fg)" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
|
||||
</button>
|
||||
<span id="ttitle"></span>
|
||||
<span id="dirty" data-i18n="hdr_dirty" data-i18n-t="hdr_dirty_t">unsaved changes</span>
|
||||
<button class="ctl pri" id="saveBtn" data-i18n="hdr_save" data-i18n-t="hdr_save_t">Save to flash</button>
|
||||
</header>
|
||||
|
||||
<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>
|
||||
<main>
|
||||
<section class="tab act" id="tab-dash">
|
||||
<div class="card"><h2><span data-i18n="d_ports">Ports</span> <span class="hint" data-i18n="d_ports_h">click a port for details</span></h2>
|
||||
<div id="strip"></div>
|
||||
</div>
|
||||
<div class="grid2">
|
||||
<div class="card"><h2 data-i18n="d_system">System</h2><table class="t" id="sysinfo"></table></div>
|
||||
<div class="card"><h2><span data-i18n="d_traffic">Traffic</span> <span class="hint" data-i18n="d_traffic_h">packets/s, live</span></h2>
|
||||
<div class="scrollx"><table class="t" id="traffic">
|
||||
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="c_link">Link</th><th class="num" data-i18n="d_txpps">TX pps</th><th class="num" data-i18n="d_rxpps">RX pps</th><th class="num" data-i18n="d_txbad">TX bad</th><th class="num" data-i18n="d_rxbad">RX bad</th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
</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>
|
||||
</section>
|
||||
|
||||
<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">
|
||||
<section class="tab" id="tab-ports">
|
||||
<div class="card"><h2 data-i18n="pt_title">Port configuration</h2>
|
||||
<div class="scrollx"><table class="t" id="ptable">
|
||||
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="c_name">Name</th><th data-i18n="c_link">Link</th><th data-i18n="pt_conn">Connected</th><th data-i18n="pt_speed">Speed</th><th data-i18n="c_enabled">Enabled</th><th data-i18n="pt_mtu">MTU</th><th></th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
<p class="small mut" style="margin-top:8px" data-i18n="pt_sfp_note">SFP speed is set per slot and is not saved in the startup config.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
<section class="tab" id="tab-stp">
|
||||
<div class="card"><h2><span data-i18n="stp_title">Spanning tree</span> <span class="hint" data-i18n="stp_h">RSTP, 802.1w</span></h2>
|
||||
<label class="row"><span class="lb" data-i18n="c_enabled">Enabled</span>
|
||||
<span class="switch"><input type="checkbox" id="stpen"><i></i></span></label>
|
||||
<p class="small mono" id="stpids">-</p>
|
||||
<div id="stpbridge" style="display:flex;gap:14px;flex-wrap:wrap;align-items:flex-end;margin-top:10px">
|
||||
<label><span class="small mut" data-i18n="stp_version">Version</span><br><select class="in" id="stpver"><option value="rstp">RSTP</option><option value="stp" data-i18n="stp_v_stp">STP compatible</option></select></label>
|
||||
<label><span class="small mut" data-i18n="stp_prio">Bridge priority</span><br><select class="in" id="stpprio"></select></label>
|
||||
<label><span class="small mut" data-i18n="stp_hello">Hello [s]</span><br><input class="in sm" id="stphello" type="number" min="1" max="10"></label>
|
||||
<label><span class="small mut" data-i18n="stp_maxage">Max age [s]</span><br><input class="in sm" id="stpmaxage" type="number" min="6" max="40"></label>
|
||||
<label><span class="small mut" data-i18n="stp_fwd">Forward delay [s]</span><br><input class="in sm" id="stpfwd" type="number" min="4" max="30"></label>
|
||||
<label><span class="small mut" data-i18n="stp_txhold">TX hold</span><br><input class="in sm" id="stptxhold" type="number" min="1" max="10"></label>
|
||||
</div>
|
||||
<div style="margin-top:10px"><button class="ctl pri" id="stpbapply" data-i18n="stp_bridge_apply">Apply bridge settings</button></div>
|
||||
<p class="small mut" style="margin-top:8px" data-i18n="stp_prio_note">Lower priority wins the root election (default 8 = 32768). Settings apply immediately; use Save to flash to keep them.</p>
|
||||
</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 class="card"><h2 data-i18n="stp_portcfg">Port configuration</h2>
|
||||
<div class="scrollx"><table class="t" id="stpcfg">
|
||||
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="c_enabled">Enabled</th><th data-i18n="stp_edge">Edge</th><th data-i18n="stp_pcost">Path cost</th><th data-i18n="stp_pprio">Priority</th><th data-i18n="stp_guard">Guard</th><th data-i18n="stp_filter">BPDU filter</th><th data-i18n="stp_p2p">Point-to-point</th><th></th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
<p class="small mut" style="margin-top:8px" data-i18n="stp_port_note">Edge ports forward immediately and cause no topology change; Auto treats a port as edge after 3 s without BPDUs, and any received BPDU revokes it. Path cost 0 = automatic.</p>
|
||||
</div>
|
||||
<div class="card"><h2 data-i18n="stp_status">Port status</h2>
|
||||
<div class="scrollx"><table class="t" id="stpstat">
|
||||
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="stp_role">Role</th><th data-i18n="c_state">State</th><th data-i18n="stp_db">Designated bridge</th><th data-i18n="stp_dp">Designated port</th><th data-i18n="stp_dc">Designated cost</th><th data-i18n="stp_oedge">Oper. edge</th><th data-i18n="stp_op2p">Oper. P2P</th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab" id="tab-stats">
|
||||
<div class="card"><h2><span data-i18n="st_title">Port statistics</span> <span class="hint" data-i18n="st_h">totals since boot</span></h2>
|
||||
<div class="scrollx"><table class="t" id="stable">
|
||||
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="c_name">Name</th><th data-i18n="c_link">Link</th><th class="num" data-i18n="st_txg">TX good</th><th class="num" data-i18n="st_txb">TX bad</th><th class="num" data-i18n="st_rxg">RX good</th><th class="num" data-i18n="st_rxb">RX bad</th><th></th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab" id="tab-vlan">
|
||||
<div class="card"><h2><span data-i18n="v_title">VLANs</span> <span class="hint"><span data-i18n="v_mgmt">Management VLAN</span>: <span id="vmgmtcur">-</span></span></h2>
|
||||
<div class="scrollx"><table class="t" id="vtable">
|
||||
<thead><tr><th data-i18n="v_vid">VID</th><th data-i18n="c_name">Name</th><th data-i18n="v_members">Members</th><th data-i18n="v_tagged">Tagged</th><th data-i18n="v_untagged">Untagged</th><th data-i18n="v_pvid_on">PVID on</th><th></th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
<p class="small mut" id="vempty" style="display:none;margin-top:6px" data-i18n="v_empty">No VLANs configured.</p>
|
||||
</div>
|
||||
<div class="card"><h2 data-i18n="v_editor">VLAN editor</h2>
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px">
|
||||
<label><span data-i18n="v_vid">VID</span> <input class="in sm" id="vvid" type="number" min="1" max="4094"></label>
|
||||
<label><span data-i18n="c_name">Name</span> <input class="in" id="vname" size="12" maxlength="15" data-i18n-p="v_optional"></label>
|
||||
<button class="ctl" id="vload" data-i18n="c_load">Load</button>
|
||||
<button class="ctl pri" id="vapply" data-i18n="c_apply">Apply</button>
|
||||
<button class="ctl" id="vmgmt" data-i18n="v_setmgmt" data-i18n-t="v_setmgmt_t">Set as mgmt VLAN</button>
|
||||
</div>
|
||||
<div class="scrollx"><table class="t" id="vedit"><tbody></tbody></table></div>
|
||||
<p class="small mut" style="margin-top:6px" data-i18n="v_legend">U = untagged member, T = tagged member, - = not a member. PVID assigns this VLAN to untagged ingress traffic.</p>
|
||||
</div>
|
||||
<div class="card"><h2><span data-i18n="v_ingress">Ingress filtering</span> <span class="hint" data-i18n="v_ingress_h">write-only: the state is not readable from the switch</span></h2>
|
||||
<div class="scrollx"><table class="t" id="ingress"><tbody></tbody></table></div>
|
||||
<div style="margin-top:10px"><button class="ctl pri" id="ingapply" data-i18n="v_ing_apply">Apply ingress modes</button></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab" id="tab-l2">
|
||||
<div class="card"><h2><span data-i18n="l2_title">MAC address table</span>
|
||||
<span class="hint"><input class="in" id="l2filter" data-i18n-p="l2_filter" size="14"></span></h2>
|
||||
<div style="display:flex;gap:10px;margin-bottom:10px">
|
||||
<button class="ctl" id="l2refresh" data-i18n="c_refresh">Refresh</button>
|
||||
<button class="ctl" id="l2flush" data-i18n="l2_flush">Flush learned entries</button>
|
||||
<span class="small mut" id="l2count" style="align-self:center"></span>
|
||||
</div>
|
||||
<div class="scrollx"><table class="t" id="l2table">
|
||||
<thead><tr><th data-sort="pport"><span data-i18n="c_port">Port</span><span class="arrow"></span></th><th data-sort="mac">MAC<span class="arrow"></span></th><th data-sort="vlan">VLAN<span class="arrow"></span></th><th data-sort="stat"><span data-i18n="c_type">Type</span><span class="arrow"></span></th><th></th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab" id="tab-mirror">
|
||||
<div class="card"><h2 data-i18n="m_title">Port mirroring</h2>
|
||||
<div style="display:flex;gap:12px;align-items:center;margin-bottom:10px;flex-wrap:wrap">
|
||||
<span class="badge" id="mstate">off</span>
|
||||
<label><span data-i18n="m_monitor">Monitor port</span> <select class="in" id="mport"></select></label>
|
||||
<button class="ctl pri" id="mapply" data-i18n="c_apply">Apply</button>
|
||||
<button class="ctl" id="moff" data-i18n="c_disable">Disable</button>
|
||||
</div>
|
||||
<div class="scrollx"><table class="t" id="mtable"><tbody></tbody></table></div>
|
||||
<p class="small mut" style="margin-top:6px" data-i18n="m_note">Both = mirror RX and TX of the port to the monitor port.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab" id="tab-lag">
|
||||
<div id="lagwrap" class="grid2"></div>
|
||||
<p class="small mut" style="margin-top:10px" data-i18n="lag_note">A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.</p>
|
||||
</section>
|
||||
|
||||
<section class="tab" id="tab-eee">
|
||||
<div class="card"><h2 data-i18n="e_title">Energy Efficient Ethernet</h2>
|
||||
<div class="scrollx"><table class="t" id="etable">
|
||||
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="e_adv">Advertised</th><th data-i18n="e_lp">Link partner</th><th data-i18n="e_active">Active</th><th data-i18n="e_enable">Enable</th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
<p class="small mut" style="margin-top:8px" data-i18n="e_note">Advertised and link-partner flags per speed: 100M, 1G, 2.5G. SFP ports do not support EEE.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab" id="tab-bw">
|
||||
<div class="card"><h2><span data-i18n="bw_title">Bandwidth limits</span> <span class="hint" data-i18n="bw_h">Mbit/s, 0.016-10000</span></h2>
|
||||
<div class="scrollx"><table class="t" id="btable">
|
||||
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="bw_in">Ingress limit</th><th>Mbit/s</th><th data-i18n="bw_exceed">When exceeded</th><th data-i18n="bw_out">Egress limit</th><th>Mbit/s</th><th></th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab" id="tab-system">
|
||||
<div class="grid2">
|
||||
<div class="card"><h2 data-i18n="sy_network">Network</h2>
|
||||
<label class="row"><span class="lb" data-i18n="i_host">Hostname</span><input class="in" id="sy-host" maxlength="23"></label>
|
||||
<label class="row"><span class="lb" data-i18n="i_ip">IP address</span><input class="in" id="sy-ip" size="15"></label>
|
||||
<label class="row"><span class="lb" data-i18n="i_mask">Netmask</span><input class="in" id="sy-mask" size="15"></label>
|
||||
<label class="row"><span class="lb" data-i18n="i_gw">Gateway</span><input class="in" id="sy-gw" size="15"></label>
|
||||
<div style="display:flex;gap:10px;margin-top:12px">
|
||||
<button class="ctl pri" id="sy-apply" data-i18n="c_apply">Apply</button>
|
||||
<button class="ctl" id="sy-dhcp" data-i18n="sy_dhcp" data-i18n-t="sy_dhcp_t">Use DHCP</button>
|
||||
</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 class="card"><h2 data-i18n="sy_services">Services</h2>
|
||||
<label class="row" style="justify-content:space-between"><span data-i18n="sy_igmp">IGMP snooping</span>
|
||||
<span class="switch"><input type="checkbox" id="sy-igmp"><i></i></span></label>
|
||||
<label class="row" style="justify-content:space-between"><span data-i18n="i_syslog">Syslog</span>
|
||||
<span class="switch"><input type="checkbox" id="sy-syslog"><i></i></span></label>
|
||||
<div class="row" style="display:flex;gap:8px;align-items:center;margin:-2px 0 10px">
|
||||
<span class="small mut" data-i18n="sy_server">Server</span>
|
||||
<input class="in" id="sy-sysip" style="flex:1;min-width:0;width:0" data-i18n-p="sy_sysip">
|
||||
<span class="small mut" data-i18n="sy_port">Port</span>
|
||||
<input class="in" id="sy-sysport" style="width:64px" type="number" min="1" max="65535"></div>
|
||||
<p class="small mut" data-i18n="sy_services_note">Service state reflects the startup config; runtime state is not readable.</p>
|
||||
</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 class="card"><h2 data-i18n="sy_password">Admin password</h2>
|
||||
<label class="row"><span class="lb" data-i18n="sy_newpw">New password</span><input class="in" id="sy-pw1" type="password" maxlength="20"></label>
|
||||
<label class="row"><span class="lb" data-i18n="sy_repeat">Repeat</span><input class="in" id="sy-pw2" type="password" maxlength="20"></label>
|
||||
<button class="ctl pri" id="sy-pwapply" style="margin-top:8px" data-i18n="sy_pwapply">Change password</button>
|
||||
<p class="small mut" style="margin-top:8px" data-i18n="sy_pw_note">Takes effect immediately; save to flash to keep it after reboot.</p>
|
||||
</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 class="card"><h2 data-i18n="sy_console">Console</h2>
|
||||
<div style="display:flex;gap:8px">
|
||||
<input class="in" id="sy-cmd" style="flex:1" data-i18n-p="sy_cmd" spellcheck="false">
|
||||
<button class="ctl pri" id="sy-send" data-i18n="sy_send">Send</button>
|
||||
</div>
|
||||
<pre class="cfg" id="sy-cout" style="margin-top:10px;min-height:70px;max-height:180px;overflow:auto"></pre>
|
||||
</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()">
|
||||
<div class="card"><h2 data-i18n="sy_display">Display</h2>
|
||||
<label class="row"><span class="lb" data-i18n="sy_lang">Language</span>
|
||||
<select class="in" id="langSel">
|
||||
<option value="en">English</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="zh">中文</option>
|
||||
</select>
|
||||
</div>
|
||||
</select></label>
|
||||
<label class="row"><span class="lb" data-i18n="sy_theme">Theme</span>
|
||||
<select class="in" id="themeSel">
|
||||
<option value="auto" data-i18n="th_auto">System</option>
|
||||
<option value="auto-sel" data-i18n="th_auto_sel">System (Selenized)</option>
|
||||
<option value="light" data-i18n="th_light">Light</option>
|
||||
<option value="dark" data-i18n="th_dark">Dark</option>
|
||||
<option value="sel-light" data-i18n="th_sel_light">Selenized Light</option>
|
||||
<option value="sel-dark" data-i18n="th_sel_dark">Selenized Dark</option>
|
||||
</select></label>
|
||||
<p class="small mut" data-i18n="sy_display_note">Stored in this browser only.</p>
|
||||
</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>
|
||||
<div class="card" style="margin-top:16px"><h2><span data-i18n="sy_startup">Startup configuration</span>
|
||||
<span class="hint"><span id="cfgbytes"></span>, <span data-i18n="sy_replayed">replayed on every boot</span></span></h2>
|
||||
<textarea class="cfg" id="cfgedit" spellcheck="false"></textarea>
|
||||
<div style="display:flex;gap:10px;margin-top:10px;flex-wrap:wrap">
|
||||
<button class="ctl" id="cfgreload" data-i18n="sy_reload">Reload from flash</button>
|
||||
<button class="ctl pri" id="cfgwrite" data-i18n="sy_write">Write to flash</button>
|
||||
<span class="small mut" style="align-self:center" data-i18n="sy_unknown_note">Unknown lines are highlighted before writing.</span>
|
||||
</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 class="card dangerzone" style="margin-top:16px"><h2 data-i18n="sy_maint">Maintenance</h2>
|
||||
<button class="ctl danger" id="sy-reboot" data-i18n="sy_reboot">Reboot switch</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
<section class="tab" id="tab-fw">
|
||||
<div class="card"><h2 data-i18n="fw_title">Firmware update</h2>
|
||||
<p class="small mut" style="margin-bottom:12px" data-i18n="fw_intro">Upload an RTLPlayground image (512 KiB .bin). The image is staged to flash and CRC-checked; on success the switch reboots itself, re-verifies the image and applies it. The startup configuration is preserved.</p>
|
||||
<input type="file" id="fwfile" accept=".bin">
|
||||
<div id="fwinfo" class="small" style="margin:10px 0"></div>
|
||||
<div style="display:flex;gap:10px;align-items:center">
|
||||
<button class="ctl pri" id="fwup" disabled data-i18n="fw_upload">Upload</button>
|
||||
<progress id="fwprog" max="100" value="0" style="display:none;width:220px"></progress>
|
||||
<span class="small" id="fwstat"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</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>
|
||||
<div id="toasts"></div>
|
||||
<div id="mback"><div id="modal">
|
||||
<div id="mhead"><span id="mtitle"></span><button id="mx">✕</button></div>
|
||||
<div id="mbody"></div><div id="mfoot"></div>
|
||||
</div></div>
|
||||
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,30 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<title data-i18n="login_title">RTL Switch Login</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="/main.js"></script>
|
||||
<script>
|
||||
function removeNote() {
|
||||
document.getElementById("incorrect").innerHTML = "";
|
||||
}
|
||||
window.addEventListener("load", function() {
|
||||
if (document.referrer.endsWith("login.html"))
|
||||
document.getElementById("incorrect").innerHTML = t('login_wrong');
|
||||
});
|
||||
</script>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Switch Login</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#f4f6f8;--panel:#ffffff;--bd:#d7dde4;--fg:#1a222c;--fg2:#5c6773;
|
||||
--ac:#0969da;--acfg:#ffffff;--bad:#cf222e;
|
||||
}
|
||||
:root[data-theme="dark"]{
|
||||
--bg:#0e1116;--panel:#161b22;--bd:#2c333c;--fg:#e6edf3;--fg2:#8d96a0;
|
||||
--ac:#4c8ef7;--acfg:#ffffff;--bad:#f85149;
|
||||
}
|
||||
:root[data-theme="sel-dark"]{
|
||||
--bg:#103c48;--panel:#184956;--bd:#2d5b69;--fg:#adbcbc;--fg2:#72898f;
|
||||
--ac:#4695f7;--acfg:#ffffff;--bad:#fa5750;
|
||||
}
|
||||
:root[data-theme="sel-light"]{
|
||||
--bg:#fbf3db;--panel:#ece3cc;--bd:#d5cdb6;--fg:#53676d;--fg2:#909995;
|
||||
--ac:#0072d4;--acfg:#ffffff;--bad:#d2212d;
|
||||
}
|
||||
*{box-sizing:border-box;margin:0}
|
||||
body{
|
||||
font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
|
||||
background:var(--bg);color:var(--fg);
|
||||
min-height:100vh;display:flex;align-items:center;justify-content:center;
|
||||
}
|
||||
.card{
|
||||
background:var(--panel);border:1px solid var(--bd);border-radius:12px;
|
||||
padding:36px 32px;width:min(360px,92vw);
|
||||
box-shadow:0 8px 30px rgba(0,0,0,.12);
|
||||
}
|
||||
.logo{display:flex;align-items:center;gap:10px;margin-bottom:6px}
|
||||
h1{font-size:19px;font-weight:650}
|
||||
p.sub{color:var(--fg2);font-size:13px;margin-bottom:24px}
|
||||
label{display:block;font-size:13px;color:var(--fg2);margin-bottom:6px}
|
||||
input[type=password]{
|
||||
width:100%;padding:10px 12px;font-size:15px;color:var(--fg);
|
||||
background:var(--bg);border:1px solid var(--bd);border-radius:8px;outline:none;
|
||||
}
|
||||
input[type=password]:focus{border-color:var(--ac)}
|
||||
button{
|
||||
width:100%;margin-top:18px;padding:10px;font-size:15px;font-weight:600;
|
||||
color:var(--acfg);background:var(--ac);border:0;border-radius:8px;cursor:pointer;
|
||||
}
|
||||
button:hover{filter:brightness(1.1)}
|
||||
#err{color:var(--bad);font-size:13px;min-height:20px;margin-top:14px;text-align:center}
|
||||
</style>
|
||||
<script>
|
||||
(function(){
|
||||
var pref;
|
||||
try{pref=localStorage.getItem("theme")||"auto";}catch(e){pref="auto";}
|
||||
var dark=window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
var t=pref;
|
||||
if(pref==="auto")t=dark?"dark":"light";
|
||||
if(pref==="auto-sel")t=dark?"sel-dark":"sel-light";
|
||||
document.documentElement.dataset.theme=t;
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="login_page">
|
||||
<div class = "center">
|
||||
<h1 data-i18n="login_heading"> RTL Switch Login</h1>
|
||||
<form method="post" action="login">
|
||||
<div class="txt_field">
|
||||
<input name="pwd" type="password" autocomplete="current-password" onclick="removeNote()" required />
|
||||
<span></span>
|
||||
<label data-i18n="login_password">Password</label>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="logo">
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="var(--ac)" stroke-width="2">
|
||||
<rect x="2" y="7" width="20" height="10" rx="2"/>
|
||||
<path d="M6 11v2M10 11v2M14 11v2M18 11v2"/>
|
||||
</svg>
|
||||
<h1 id="hn">Switch Login</h1>
|
||||
</div>
|
||||
<input type="submit" data-i18n="login_login" value="Login"/>
|
||||
<h3 id="incorrect" style="margin-top: 5em;"></h3>
|
||||
<p class="sub" id="sub">RTLPlayground management interface</p>
|
||||
<form method="post" action="login">
|
||||
<label for="pwd" id="lp">Password</label>
|
||||
<input id="pwd" name="pwd" type="password" autocomplete="current-password" autofocus required
|
||||
oninput="document.getElementById('err').textContent=''">
|
||||
<button type="submit" id="lb">Sign in</button>
|
||||
<div id="err"></div>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
var L={
|
||||
en:["Switch Login","RTLPlayground management interface","Password","Sign in","Wrong password, try again."],
|
||||
ja:["スイッチ ログイン","RTLPlayground 管理インターフェース","パスワード","ログイン","パスワードが違います。もう一度お試しください。"],
|
||||
zh:["交换机登录","RTLPlayground 管理界面","密码","登录","密码错误,请重试。"]
|
||||
};
|
||||
var l=null;
|
||||
try{l=localStorage.getItem("rtl_lang");}catch(e){}
|
||||
if(!L[l])l=(navigator.language||"en").slice(0,2);
|
||||
var s=L[l]||L.en;
|
||||
document.title=s[0];
|
||||
document.getElementById("hn").textContent=s[0];
|
||||
document.getElementById("sub").textContent=s[1];
|
||||
document.getElementById("lp").textContent=s[2];
|
||||
document.getElementById("lb").textContent=s[3];
|
||||
if(document.referrer.endsWith("login.html"))document.getElementById("err").textContent=s[4];
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="layer1" transform="translate(-314.07 -485.06)">
|
||||
<g id="g1" transform="translate(256.16 417.12)">
|
||||
<rect id="r1" style="stroke:#0a0a0a;stroke-linecap:square;stroke-width:4;fill:#ffffff" rx="4.514" ry="4.514" height="90.625" width="117.88" y="69.93" x="59.896"/>
|
||||
<g id="g2"> <g id="g3">
|
||||
<path class="bg" id="rect3787"
|
||||
d="m73.958 75.66h89.41c2.3083 0 4.1667 1.8583 4.1667 4.1667l0.00028 52.653h-21.742v9.414h-8.694l0.00004 12.586h-36.871l-0.00004-12.586h-8.694v-9.414h-21.742l-0.000336-52.653c-0.000011-2.3083 1.8583-4.1667 4.1667-4.1667z"
|
||||
style="stroke:#0a0a0a;stroke-linecap:square;stroke-width:2.5;fill:#dcdcdc"/>
|
||||
<g id="g4" transform="translate(-0.368)" style="stroke:#191919;stroke-linecap:square;fill:#666666">
|
||||
<rect id="r2" height="22.786" width="3.9062" y="75.747" x="80.556"/>
|
||||
<rect id="r3" height="22.786" width="3.9062" y="75.747" x="153.6" />
|
||||
<rect id="r4" height="22.786" width="3.9062" y="75.747" x="132.73"/>
|
||||
<rect id="r5" height="22.786" width="3.9062" y="75.747" x="122.3"/>
|
||||
<rect id="r6" height="22.786" width="3.9062" y="75.747" x="111.86"/>
|
||||
<rect id="r7" height="22.786" width="3.9062" y="75.747" x="101.43"/>
|
||||
<rect id="r8" height="22.786" width="3.9062" y="75.747" x="90.99"/>
|
||||
<rect id="r9" height="22.786" width="3.9062" y="75.747" x="143.17"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect class="led" id="r10" style="stroke:#0a0a0a;stroke-linecap:square;stroke-width:2;fill:#1dfe0a" height="13.89" width="17.1" y="137.03" x="69.542"/>
|
||||
<rect class="led" id="r11" style="stroke:#0a0a0a;stroke-linecap:square;stroke-width:2;fill:#ffd10a" height="13.89" width="17.1" y="137.03" x="150.68"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="layer1" transform="translate(-41.68,-55.95)">
|
||||
<g id="g1" transform="translate(-177.45,-12.07)">
|
||||
<g id="g2" transform="matrix(1.46,0,0,1.44,51.31,-317.4)">
|
||||
<path id="p1"
|
||||
d="m 117.94442,272.67478 h 7.64646 c 0.21062,0 0.38018,0.16649 0.38018,0.3733 v 4.69239 c 0,0.2068 -0.16956,0.3733 -0.38018,0.3733 h -2.1027 v 0.31956 h -3.44117 v -0.31956 h -2.1027 c -0.21062,0 -0.38018,-0.16649 -0.38018,-0.3733 v -4.69239 c 0,-0.20681 0.16956,-0.3733 0.38018,-0.3733 z"
|
||||
style="fill:#ffffff;stroke:#000000;stroke-width:0.4"/>
|
||||
<path class="bg" id="p2"
|
||||
d="m 118.36775,273.15632 h 6.79794 c 0.18725,0 0.338,0.14695 0.338,0.32946 v 4.14153 c 0,0.18252 -0.15074,0.32946 -0.338,0.32946 h -6.79794 c -0.18724,0 -0.338,-0.14695 -0.338,-0.32946 v -4.14153 c 0,-0.18252 0.15074,-0.32946 0.338,-0.32946 z"
|
||||
style="fill:#000000;stroke-width:0.26" />
|
||||
<path d="m 118.65,273.53 h 6.24 v 4.05 h -6.24 z" style="fill:#1a1a1a;stroke-width:0.26" id="p3" />
|
||||
<path d="m 118.86,276.61 h 5.82" style="fill:#1a1a1a;stroke:#000000;stroke-width:0.26" id="p4" />
|
||||
<path d="m 124.05,274.25 h 1.39 v 0.72 h -1.39 z" style="fill:#1a1a1a;stroke-width:0.26" id="p5" />
|
||||
<path d="m 124.05,276 h 1.39 v 0.72 h -1.39 z" style="fill:#1a1a1a;stroke-width:0.26" id="p6" />
|
||||
<path transform="matrix(0.26,0,0,0.26,116.9,272.11)" d="M 7.3648613,7.0152205 H 29.369861" style="opacity:0.61257;fill:#4d4d4d;stroke:#4d4d4d;filter:url(#filter17794-0)" id="p7" />
|
||||
</g>
|
||||
<g id="g3" style="opacity:1;fill:#37d733;" transform="matrix(0.93,0,0,0.92,62.18,-43.11)">
|
||||
<circle class="led" id="c1" cx="176.86" cy="125.7" r="0.93"/>
|
||||
<circle class="led" id="c2" r="0.93" cy="125.7" cx="180.84"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,172 +0,0 @@
|
||||
h1, h2, h3 {
|
||||
color: #226;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 12%;
|
||||
height: calc(100% - 50px);
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
li a {
|
||||
background-color: #aaf;
|
||||
display: block;
|
||||
color: #000;
|
||||
padding: 8px 16px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Change the link color on hover */
|
||||
li a:hover {
|
||||
background-color: #226;
|
||||
color: white;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid #226;
|
||||
padding: 8px;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
input[type=submit] { padding: 8px 16px;background-color:#aaf;color:#000; margin-bottom: 2em;border-radius: 15px;width: 100%;}
|
||||
input[type=submit]:hover { background-color: #226; color: white;}
|
||||
input[type=file] { padding: 8px 16px;background-color:#aaf;color:#000; margin-bottom: 2em;border-radius: 15px;width: 60%;}
|
||||
input[type=file]:hover { background-color: #226; color: white;}
|
||||
b
|
||||
button {padding: 8px; background-color:#99f; color:#000;border-radius: 15px;}
|
||||
button:hover { background-color: #226; color: white;}
|
||||
.action{padding: 8px 16px;margin-top: 2em;margin-right: 3em; background-color: #aaf;color: #000;border-radius: 15px; width: 100%;}
|
||||
.action:hover { background-color: #226; color: white;}
|
||||
|
||||
/* Port selection inputs */
|
||||
.psel {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-top: 1em;
|
||||
}
|
||||
.psel + img {
|
||||
cursor: pointer;
|
||||
opacity: 0.4;
|
||||
padding: 8px;
|
||||
}
|
||||
.psel:checked + img {
|
||||
/* outline: 2px solid #f00;*/
|
||||
opacity: 1.0;
|
||||
}
|
||||
object, img {
|
||||
margin: 0.5em 0.5em;
|
||||
}
|
||||
.cbgroup {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
.cbgroup label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.disabled{ opacity: .4; background-color: #f88; color: #000}
|
||||
.isSFP{ opacity: .4; background-color: #660;}
|
||||
.isNOK{ color: #900;}
|
||||
.isOK{ color: #090;}
|
||||
.ip{padding:8px 16px;margin-bottom: 1em;margin-left: 1em}
|
||||
.rotext{display:inline-block;padding:8px 16px;margin-bottom: 1em;margin-left: 1em}
|
||||
.row {display: flex;}
|
||||
.rcol {flex: 90%;}
|
||||
.lcol {flex: 10%;}
|
||||
|
||||
/* Login page */
|
||||
.login_page {margin: 0; padding: 0; background: #aaf; height: 100vh; overflow: hidden;}
|
||||
.center{
|
||||
position: absolute; top: 50%; left: 50%;
|
||||
transform: translate(-50%, -40% );
|
||||
width: 500px; height: 400px;
|
||||
background: white;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.center h1 {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid silver;
|
||||
}
|
||||
|
||||
.center form {
|
||||
padding: 0 60px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
form .txt_field{
|
||||
position: relative;
|
||||
border-bottom: 2px solid #adadad;
|
||||
margin: 30px 0;
|
||||
}
|
||||
.txt_field input { width: 100%; padding: 0 5px; height: 40px; font-size: 16px; border: none;
|
||||
background: none;
|
||||
outline: none;}
|
||||
.txt_field label { position: absolute; top: 50%; left: 5px; color:#adadad; transform: translateY(-50%);
|
||||
font-size: 16px; pointer-events: none;transition: .5s; }
|
||||
.txt_field span::before{ content:''; position: absolute; top: 40px; left:0; width: 100%; height: 2px; background: #aaf;}
|
||||
.txt_field input:focus ~ label,
|
||||
.txt_field input:valid ~ label { top: -5px; color: #aaf; }
|
||||
|
||||
.tooltip {
|
||||
position: relative; display: inline-block; cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.tooltiptext {
|
||||
visibility: hidden;
|
||||
width: 260px;
|
||||
background-color: #226;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 0;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 80%;
|
||||
left: 50%;
|
||||
margin-left: -130px;
|
||||
}
|
||||
|
||||
.tooltiptext::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent black transparent;
|
||||
}
|
||||
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
.tt_table {
|
||||
width: 100%; font-size:90%;
|
||||
}
|
||||
.tt_table td {
|
||||
text-align: left;
|
||||
}
|
||||
select { text-align-last: right; font-family: monospace}
|
||||
option { direction: rtl; font-family: sans-serif}
|
||||
#vlanTable td { text-align: left; }
|
||||
|
||||
.l2sort{cursor:pointer;user-select:none}
|
||||
.l2sort:hover{text-decoration:underline}
|
||||
.l2arrow{opacity:0.55;font-size:0.85em}
|
||||
.l2filter{width:100%;box-sizing:border-box;font-weight:normal;font-size:0.9em}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" version="1.1" viewBox="0 0 577 434.38">
|
||||
<defs>
|
||||
<radialGradient id="rG39" gradientUnits="userSpaceOnUse" cy="676.79" cx="508.65" gradientTransform="matrix(1,0,0,0.5704,-2,274.75)" r="86.402">
|
||||
<stop stop-color="#000000" offset="0"/>
|
||||
<stop stop-color="#000000" stop-opacity="0" offset="1"/>
|
||||
</radialGradient>
|
||||
<filter id="f39" color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur stdDeviation="2.8"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<g transform="translate(-101.235,-272.06)">
|
||||
<path d="m420.75,672.71v-18.688l82.055-42.01,89.748,35.306v18.414l-93.012,43.848-78.792-36.871z" transform="matrix(3.115,0,0,3.115,-1188.5,-1524.8)" filter="url(#f39)" fill="url(#rG39)"/>
|
||||
<path style="color:#000000;" fill="#3a68b0" d="M143.37,387.98,384.43,501.62,630.32,385.7,389.26,272.06z"/>
|
||||
<path style="color:#000000;" fill="#5d9ce1" d="M384.43,501.62v141.21l245.89-115.92v-141.21z"/>
|
||||
<path style="color:#000000;" fill="#1b3a88" d="M143.37,387.98,384.43,501.62v141.21l-241.06-113.64z"/>
|
||||
<path fill-opacity="0.7" fill="#f2fdff" d="m422.47,315.78-38.514,5.6218-38.514,5.6218,16.367,5.3307-46.555,22.107,30.52,9.9405,46.555-22.107,16.367,5.3308,6.8691-15.929,6.9054-15.917z"/>
|
||||
<path fill-opacity="0.7" fill="#f2fdff" d="m269.31,421.78,6.8691-15.929,6.8691-15.929,16.367,5.3308,46.555-22.107,30.52,9.9405-46.555,22.107,16.367,5.3307-38.514,5.6218-38.477,5.6336z"/>
|
||||
<path fill-opacity="0.7" fill="#f2fdff" d="m507.05,352.03-38.514,5.6218-38.514,5.6218,16.367,5.3307-46.555,22.107,30.52,9.9405,46.555-22.107,16.367,5.3308,6.8691-15.929,6.9054-15.917z"/>
|
||||
<path fill-opacity="0.7" fill="#f2fdff" d="m353.9,458.03,6.8691-15.929,6.8691-15.929,16.367,5.3308,46.555-22.107,30.52,9.9405-46.555,22.107,16.367,5.3307-38.514,5.6218-38.477,5.6336z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -76,7 +76,6 @@ __xdata uint8_t fw_reset_pending;
|
||||
__xdata char session_id[SESSION_ID_LENGTH + 1];
|
||||
__xdata uint8_t authenticated;
|
||||
__xdata uint32_t now;
|
||||
__xdata uint8_t * __xdata timeptr;
|
||||
__xdata uint32_t last_session_use;
|
||||
|
||||
#define TSTATE_NONE 0
|
||||
@@ -341,13 +340,14 @@ __xdata uint8_t *scan_header(__xdata uint8_t * __xdata p)
|
||||
|
||||
read_reg_timer(&now);
|
||||
|
||||
if (session) {
|
||||
if (session && session_id[0]) {
|
||||
if (now - last_session_use > SESSION_TIMEOUT) {
|
||||
dbg_string("Session expired\n");
|
||||
} else {
|
||||
if (is_word_x(session, session_id))
|
||||
if (is_word_x(session, session_id)) {
|
||||
authenticated = 1;
|
||||
else
|
||||
last_session_use = now;
|
||||
} else
|
||||
dbg_string("Invalid session cookie!\n");
|
||||
}
|
||||
}
|
||||
@@ -866,7 +866,6 @@ void httpd_appcall(void)
|
||||
o_idx += uip_mss();
|
||||
} else {
|
||||
slen = 0;
|
||||
o_idx += slen;
|
||||
}
|
||||
|
||||
s->tstate = TSTATE_ACKED;
|
||||
@@ -887,7 +886,8 @@ void httpd_appcall(void)
|
||||
flash_region.addr = cont_addr;
|
||||
flash_region.len = slen;
|
||||
flash_read_bulk(outbuf);
|
||||
uip_send(outbuf, slen);
|
||||
o_idx = 0;
|
||||
uip_send(outbuf + o_idx, slen);
|
||||
cont_len -= slen;
|
||||
cont_addr += slen;
|
||||
s->tstate = TSTATE_TX;
|
||||
@@ -1011,18 +1011,10 @@ void httpd_appcall(void)
|
||||
} else {
|
||||
dbg_string("Have entry, authenticated: "); dbg_byte(authenticated); dbg_char('\n');
|
||||
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_main_js
|
||||
|| f_data[entry].start == FDATA_START_style_css)) {
|
||||
|| f_data[entry].start == FDATA_START_favicon_ico)) {
|
||||
send_to_login();
|
||||
goto do_send;
|
||||
}
|
||||
// A web-page is actively accessed, we can reset session time-out
|
||||
reg_read_m(RTL837X_REG_SEC_COUNTER);
|
||||
timeptr = (uint8_t*)&last_session_use; // last_session_use is Little endian
|
||||
timeptr[0] = sfr_data[3]; timeptr[1] = sfr_data[2]; timeptr[2] = sfr_data[1]; timeptr[3] = sfr_data[0];
|
||||
|
||||
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nContent-Type: ");
|
||||
slen += strtox(outbuf + slen, mime_strings[f_data[entry].mime]);
|
||||
/* 'unsafe-inline' is needed for the inline onclick handlers
|
||||
|
||||
@@ -1145,6 +1145,44 @@ __code const struct machine machine = {
|
||||
},
|
||||
};
|
||||
|
||||
#elif defined MACHINE_LIANGUO_HYWS_SGT0108S
|
||||
__code const struct machine machine = {
|
||||
.machine_name = "Lianguo HYWS-SGT0108S",
|
||||
.isRTL8373 = 1,
|
||||
.min_port = 0,
|
||||
.max_port = 8,
|
||||
.n_sfp = 1,
|
||||
.log_to_phys_port = {2, 1, 4, 3, 5, 6, 7, 8, 9},
|
||||
.phys_to_log_port = {1, 0, 3, 2, 4, 5, 6, 7, 8},
|
||||
.is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
|
||||
.sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN,
|
||||
.sfp_port[0].pin_los = GPIO37,
|
||||
.sfp_port[0].pin_tx_disable = GPIO_NA,
|
||||
.sfp_port[0].sds = 1,
|
||||
.sfp_port[0].i2c = {
|
||||
.sda = GPIO39_I2C_SDA4,
|
||||
.scl = GPIO40_I2C_SCL3_MDC1
|
||||
},
|
||||
|
||||
.reset_pin = GPIO_NA,
|
||||
.high_leds = { .mux = 0, .enable = 0 },
|
||||
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1 },
|
||||
.led_sets = {
|
||||
{
|
||||
LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // RJ45 Blue LEDs
|
||||
LEDS_2G5 | LEDS_LINK, // RJ45 White LEDs
|
||||
0,
|
||||
0
|
||||
},
|
||||
{
|
||||
LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // SFP blue LED?
|
||||
0,
|
||||
0,
|
||||
0
|
||||
},
|
||||
},
|
||||
};
|
||||
#elif defined MACHINE_POE_2G080110GS
|
||||
__code const struct machine machine = {
|
||||
.machine_name = "MokerLink POE-2G080110GS",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
// #define MACHINE_SWTG024AS_V2_0
|
||||
// #define MACHINE_FG_4GT_2SX_V2_0
|
||||
// #define MACHINE_FG_8GT_1SX
|
||||
// #define MACHINE_LIANGUO_HYWS_SGT0108S
|
||||
// #define MACHINE_POE_2G080110GS
|
||||
|
||||
typedef struct {
|
||||
@@ -74,7 +75,7 @@ struct sfp_port
|
||||
i2c_bus_t i2c;
|
||||
};
|
||||
|
||||
typedef struct machine {
|
||||
struct machine {
|
||||
char machine_name[30];
|
||||
uint8_t isRTL8373;
|
||||
// Lowest logical port number
|
||||
@@ -102,7 +103,7 @@ typedef struct machine {
|
||||
uint32_t mac_flash_offset;
|
||||
};
|
||||
|
||||
typedef struct machine_runtime
|
||||
struct machine_runtime
|
||||
{
|
||||
uint8_t isRTL8373 : 1;
|
||||
uint8_t isN : 1;
|
||||
|
||||
@@ -158,7 +158,6 @@ void reg_write_m(uint16_t reg_addr);
|
||||
void sds_read(uint8_t sds_id, uint8_t page, uint8_t reg);
|
||||
void sds_write_v(uint8_t sds_id, uint8_t page, uint8_t reg, uint16_t v);
|
||||
void delay(uint16_t t);
|
||||
void sleep(uint16_t t);
|
||||
void write_char_no_syslog(char c);
|
||||
void write_char(char c);
|
||||
void print_reg(uint16_t reg);
|
||||
@@ -170,15 +169,22 @@ uint8_t reg_bit_test(uint16_t reg_addr, char bit);
|
||||
void sfr_mask_data(uint8_t n, uint8_t mask, uint8_t set);
|
||||
void sfr_set_zero(void);
|
||||
void reset_chip(void);
|
||||
/* Firmware implementations that shadow libc names. Host unit-test builds
|
||||
* (RTLP_HOST_TEST) hide these prototypes so they don't clash with glibc;
|
||||
* argument order matches libc, so on-host callers transparently use the
|
||||
* C library. See test/. */
|
||||
#ifndef RTLP_HOST_TEST
|
||||
void sleep(uint16_t t);
|
||||
void memcpy(__xdata void * __xdata dst, __xdata const void * __xdata src, uint16_t len);
|
||||
void memcpyc(__xdata uint8_t *dst, __code uint8_t *src, uint16_t len);
|
||||
void memset(__xdata uint8_t *dst, __xdata uint8_t v, uint8_t len);
|
||||
int memcmp(__xdata const void *a, __xdata const void *b, uint16_t len);
|
||||
uint16_t strlen(__code const char *s);
|
||||
uint16_t strlen_x(__xdata const char *s);
|
||||
uint16_t strtox(__xdata uint8_t *dst, __code const char *s);
|
||||
uint16_t strcpy(__xdata uint8_t *dst, const char *s);
|
||||
char strcmp(__xdata const uint8_t *a, __code const uint8_t *b);
|
||||
#endif
|
||||
void memcpyc(__xdata uint8_t *dst, __code uint8_t *src, uint16_t len);
|
||||
uint16_t strlen_x(__xdata const char *s);
|
||||
uint16_t strtox(__xdata uint8_t *dst, __code const char *s);
|
||||
bool strstart(__xdata const uint8_t *a, __code const uint8_t *b);
|
||||
bool strstart_x(__xdata const uint8_t *a, __xdata const uint8_t *b);
|
||||
void tcpip_output(void);
|
||||
|
||||
@@ -28,11 +28,16 @@ __xdata uint8_t flash_capacity_code;
|
||||
#define CMD_READ_JEDEC_ID 0x9f
|
||||
#define CMD_FREAD_DIO 0xbb
|
||||
|
||||
#define STATUS_REG_BUSY_MASK 0x01
|
||||
#define STATUS_REG_WEL_MASK 0x02
|
||||
|
||||
/*
|
||||
* Configure Memory Managed IO
|
||||
*/
|
||||
void flash_configure_mmio(void)
|
||||
static void flash_configure_mmio(void)
|
||||
{
|
||||
while(SFR_FLASH_EXEC_BUSY);
|
||||
|
||||
// Set configuration for MMIO access by controller
|
||||
if (dio_enabled) {
|
||||
SFR_FLASH_MODEB = 0x18;
|
||||
@@ -46,6 +51,54 @@ void flash_configure_mmio(void)
|
||||
SFR_FLASH_DUMMYCYCLES = 8;
|
||||
}
|
||||
|
||||
static void flash_configure_sio(void)
|
||||
{
|
||||
while(SFR_FLASH_EXEC_BUSY);
|
||||
|
||||
// Set configuration for SIO access by controller
|
||||
SFR_FLASH_MODEB = 0x0;
|
||||
SFR_FLASH_DUMMYCYCLES = 0;
|
||||
}
|
||||
|
||||
static uint8_t flash_read_status(void)
|
||||
{
|
||||
uint8_t status;
|
||||
uint8_t old_cmd_r;
|
||||
uint8_t old_tconf;
|
||||
|
||||
while(SFR_FLASH_EXEC_BUSY);
|
||||
|
||||
// SAVE SPI peripheral state at entry
|
||||
old_cmd_r = SFR_FLASH_CMD_R;
|
||||
old_tconf = SFR_FLASH_TCONF;
|
||||
|
||||
SFR_FLASH_TCONF = 0x11;
|
||||
SFR_FLASH_CMD_R = CMD_READ_STATUS;
|
||||
|
||||
SFR_FLASH_EXEC_GO = 1;
|
||||
while(SFR_FLASH_EXEC_BUSY);
|
||||
status = SFR_FLASH_DATA0;
|
||||
|
||||
// RESTORE SPI peripheral state
|
||||
SFR_FLASH_CMD_R = old_cmd_r;
|
||||
SFR_FLASH_TCONF = old_tconf;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void flash_write_enable(void)
|
||||
{
|
||||
while (flash_read_status() & STATUS_REG_BUSY_MASK);
|
||||
while(SFR_FLASH_EXEC_BUSY);
|
||||
|
||||
SFR_FLASH_TCONF = 0x18;
|
||||
SFR_FLASH_CMD = CMD_WRITE_ENABLE;
|
||||
|
||||
SFR_FLASH_EXEC_GO = 1;
|
||||
while (SFR_FLASH_EXEC_BUSY);
|
||||
while (!(flash_read_status() & STATUS_REG_WEL_MASK));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initializes the flash controller for programmed control
|
||||
@@ -82,30 +135,12 @@ void flash_init(uint8_t enable_dio)
|
||||
flash_configure_mmio();
|
||||
}
|
||||
|
||||
|
||||
uint8_t flash_read_status(void)
|
||||
{
|
||||
// Test Controller Busy (we might call this directly after executing a command)
|
||||
while(SFR_FLASH_EXEC_BUSY);
|
||||
|
||||
// setup status read command
|
||||
SFR_FLASH_TCONF = 0x11;
|
||||
SFR_FLASH_CMD_R = CMD_READ_STATUS;
|
||||
|
||||
// execute and wait for controller done
|
||||
SFR_FLASH_EXEC_GO = 1;
|
||||
while(SFR_FLASH_EXEC_BUSY);
|
||||
|
||||
return SFR_FLASH_DATA0;
|
||||
}
|
||||
|
||||
|
||||
void flash_read_uid(void)
|
||||
{
|
||||
while (flash_read_status() & 0x1);
|
||||
flash_configure_sio();
|
||||
while (flash_read_status() & STATUS_REG_BUSY_MASK);
|
||||
|
||||
// Set slow read mode for UID
|
||||
SFR_FLASH_MODEB = 0x0;
|
||||
SFR_FLASH_CMD_R = CMD_READ_UNIQUE_ID;
|
||||
SFR_FLASH_DUMMYCYCLES = 8;
|
||||
|
||||
@@ -152,12 +187,12 @@ __code char* get_flash_size_str(void)
|
||||
|
||||
void flash_read_jedecid(void)
|
||||
{
|
||||
while (flash_read_status() & 0x1);
|
||||
flash_configure_sio();
|
||||
|
||||
while (flash_read_status() & STATUS_REG_BUSY_MASK);
|
||||
|
||||
// Set read mode for JEDEC ID
|
||||
SFR_FLASH_MODEB = 0x0;
|
||||
SFR_FLASH_CMD_R = CMD_READ_JEDEC_ID;
|
||||
SFR_FLASH_DUMMYCYCLES = 0;
|
||||
|
||||
// Transfer 3 bytes back
|
||||
SFR_FLASH_TCONF = 0x13;
|
||||
@@ -180,53 +215,18 @@ void flash_read_jedecid(void)
|
||||
}
|
||||
|
||||
|
||||
void flash_write_enable(void)
|
||||
{
|
||||
short status;
|
||||
|
||||
// Wait until busy bit clear
|
||||
do {
|
||||
status = flash_read_status();
|
||||
} while (status & 0x1);
|
||||
|
||||
SFR_FLASH_TCONF = 0x18;
|
||||
SFR_FLASH_CMD = CMD_WRITE_ENABLE;
|
||||
|
||||
/* The following makes sure that the PAGE_PROGRAM command,
|
||||
* where the data to be written follows the command word directly
|
||||
* works properly
|
||||
*/
|
||||
SFR_FLASH_DUMMYCYCLES = 0;
|
||||
SFR_FLASH_MODEB = 0;
|
||||
|
||||
SFR_FLASH_EXEC_GO = 1;
|
||||
// Wait for write status enabled
|
||||
do {
|
||||
status = flash_read_status();
|
||||
} while (!(status & 0x2));
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads bulk data of length len from the flash memory starging at address src
|
||||
* and writes the data into a buffer pointed to by dst in XMEM
|
||||
*/
|
||||
void flash_read_bulk(__xdata uint8_t *dst)
|
||||
{
|
||||
short status;
|
||||
do {
|
||||
status = flash_read_status();
|
||||
} while (status & 0x1);
|
||||
if (!flash_region.len)
|
||||
return;
|
||||
|
||||
// Set fast read mode
|
||||
if (dio_enabled) {
|
||||
SFR_FLASH_MODEB = 0x18;
|
||||
SFR_FLASH_CMD_R = CMD_FREAD_DIO;
|
||||
SFR_FLASH_DUMMYCYCLES = 4;
|
||||
} else {
|
||||
SFR_FLASH_MODEB = 0x0;
|
||||
SFR_FLASH_CMD_R = CMD_FREAD; // Fast read
|
||||
SFR_FLASH_DUMMYCYCLES = 8; // Add 8 dummy clocks
|
||||
}
|
||||
flash_configure_sio();
|
||||
while (flash_read_status() & STATUS_REG_BUSY_MASK);
|
||||
flash_configure_mmio();
|
||||
|
||||
|
||||
// Read 4 bytes
|
||||
@@ -260,12 +260,15 @@ void flash_read_bulk(__xdata uint8_t *dst)
|
||||
|
||||
void flash_read_security(void)
|
||||
{
|
||||
while (flash_read_status() & 0x1);
|
||||
if (!flash_region.len)
|
||||
return;
|
||||
|
||||
flash_configure_sio();
|
||||
|
||||
while (flash_read_status() & STATUS_REG_BUSY_MASK);
|
||||
|
||||
// Set slow read mode
|
||||
SFR_FLASH_MODEB = 0x0;
|
||||
SFR_FLASH_CMD_R = CMD_READ_SECURITY_REGS; // read security register
|
||||
SFR_FLASH_DUMMYCYCLES = 8; // Add 8 dummy clocks as for fast read
|
||||
|
||||
// Transfer 4 bytes (command + 3byte address)
|
||||
SFR_FLASH_TCONF = 4;
|
||||
@@ -298,6 +301,7 @@ void flash_read_security(void)
|
||||
|
||||
void flash_sector_erase(void)
|
||||
{
|
||||
flash_configure_sio();
|
||||
flash_write_enable();
|
||||
SFR_FLASH_TCONF = 8;
|
||||
SFR_FLASH_CMD = CMD_SECTOR_ERASE;
|
||||
@@ -307,7 +311,7 @@ void flash_sector_erase(void)
|
||||
SFR_FLASH_ADDR0 = flash_region.addr;
|
||||
|
||||
SFR_FLASH_EXEC_GO = 1;
|
||||
while (flash_read_status() & 0x1);
|
||||
while (flash_read_status() & STATUS_REG_BUSY_MASK);
|
||||
|
||||
flash_configure_mmio();
|
||||
}
|
||||
@@ -315,33 +319,39 @@ void flash_sector_erase(void)
|
||||
|
||||
void flash_write_bytes(__xdata uint8_t *ptr)
|
||||
{
|
||||
// write_char('\n'); write_char('>'); print_long(flash_region.addr); write_char(':'); print_short(flash_region.len); write_char('-'); print_byte(*ptr); // write_char('\n');
|
||||
while(1) {
|
||||
flash_write_enable();
|
||||
SFR_FLASH_CMD = CMD_PAGE_PROGRAM;
|
||||
SFR_FLASH_TCONF = 0x40 | 8 | 4; // Bytes written is 4, 8 enables write, 0x40 is unknown
|
||||
flash_configure_sio();
|
||||
|
||||
while(1) {
|
||||
flash_write_enable();
|
||||
SFR_FLASH_CMD = CMD_PAGE_PROGRAM;
|
||||
|
||||
// Last transfer?
|
||||
if (flash_region.len < 5) {
|
||||
SFR_FLASH_TCONF = 8 | flash_region.len;
|
||||
}
|
||||
if (flash_region.len < 5) {
|
||||
SFR_FLASH_TCONF = 8 | flash_region.len;
|
||||
} else {
|
||||
SFR_FLASH_TCONF = 0x40 | 8 | 4; // Bytes written is 4, 8 enables write, 0x40 is unknown
|
||||
}
|
||||
|
||||
SFR_FLASH_ADDR16 = flash_region.addr >> 16;
|
||||
SFR_FLASH_ADDR8 = flash_region.addr >> 8;
|
||||
SFR_FLASH_ADDR0 = flash_region.addr;
|
||||
SFR_FLASH_DATA0 = *ptr++;
|
||||
SFR_FLASH_DATA8 = *ptr++;
|
||||
SFR_FLASH_DATA16 = *ptr++;
|
||||
SFR_FLASH_DATA24 = *ptr++;
|
||||
SFR_FLASH_ADDR16 = flash_region.addr >> 16;
|
||||
SFR_FLASH_ADDR8 = flash_region.addr >> 8;
|
||||
SFR_FLASH_ADDR0 = flash_region.addr;
|
||||
|
||||
// Execute transfer, we wait for completion at top of loop
|
||||
SFR_FLASH_EXEC_GO = 1;
|
||||
// Safely load only the valid bytes remaining in the buffer
|
||||
SFR_FLASH_DATA0 = *ptr++;
|
||||
if (flash_region.len > 1) SFR_FLASH_DATA8 = *ptr++;
|
||||
if (flash_region.len > 2) SFR_FLASH_DATA16 = *ptr++;
|
||||
if (flash_region.len > 3) SFR_FLASH_DATA24 = *ptr++;
|
||||
|
||||
if (flash_region.len < 5)
|
||||
break;
|
||||
// Execute transfer, wait for completion at top of loop
|
||||
SFR_FLASH_EXEC_GO = 1;
|
||||
|
||||
flash_region.len -= 4;
|
||||
flash_region.addr += 4;
|
||||
};
|
||||
while (flash_read_status() & 0x1);
|
||||
flash_configure_mmio();
|
||||
if (flash_region.len < 5)
|
||||
break;
|
||||
|
||||
flash_region.len -= 4;
|
||||
flash_region.addr += 4;
|
||||
};
|
||||
|
||||
while (flash_read_status() & STATUS_REG_BUSY_MASK);
|
||||
flash_configure_mmio();
|
||||
}
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
void flash_init(uint8_t enable_dio);
|
||||
void flash_read_uid(void);
|
||||
void flash_write_enable(void);
|
||||
void flash_dump(uint8_t len);
|
||||
void flash_read_jedecid(void);
|
||||
void flash_read_security(void);
|
||||
|
||||
@@ -996,7 +996,7 @@ void stp_parse(void) __banked __reentrant
|
||||
if (cmd_words_len < 3)
|
||||
goto err;
|
||||
|
||||
if (cmd_compare(1, "ent") || cmd_compare(1, "lag")) {
|
||||
if (cmd_compare(1, "port") || cmd_compare(1, "lag")) {
|
||||
stp_lag_map();
|
||||
if (cmd_words_len < 4)
|
||||
goto err;
|
||||
@@ -1134,5 +1134,5 @@ void stp_parse(void) __banked __reentrant
|
||||
}
|
||||
return;
|
||||
err:
|
||||
print_string("Error: stp on|off|status | prio <0-15> | hello <1-10> | maxage <6-40> | fwd <4-30> | txhold <1-10> | version rstp|stp | ent <1-9>|lag <1-4> on|off|edge|cost|prio|guard|filter ...\n");
|
||||
print_string("Error: stp on|off|status | prio <0-15> | hello <1-10> | maxage <6-40> | fwd <4-30> | txhold <1-10> | version rstp|stp | port <1-9>|lag <1-4> on|off|edge|cost|prio|guard|filter ...\n");
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
# Host unit-test harness for RTLPlayground firmware logic.
|
||||
#
|
||||
# Compiles selected firmware translation units with host gcc + AddressSanitizer,
|
||||
# force-including sdcc_shim.h to erase 8051 keywords, and links them against
|
||||
# mocked hardware edges (support.c). No SDCC, no flashing, no hardware.
|
||||
#
|
||||
# make build + run all tests
|
||||
# make run run tests
|
||||
# make clean
|
||||
|
||||
CC = gcc
|
||||
SRCROOT = ..
|
||||
# -include shim first so __xdata/__code/__banked vanish before any firmware header.
|
||||
CFLAGS = -std=gnu11 -g -O1 -Wall -Wextra -Wno-unused-parameter -Wno-unknown-pragmas \
|
||||
-DRTLP_HOST_TEST \
|
||||
-fsanitize=address,undefined -fno-omit-frame-pointer \
|
||||
-include sdcc_shim.h -I. -I$(SRCROOT) -I$(SRCROOT)/httpd -I$(SRCROOT)/uip
|
||||
ifeq ($(findstring clang,$(shell $(CC) --version 2>/dev/null)),)
|
||||
CFLAGS += -Wno-builtin-declaration-mismatch
|
||||
endif
|
||||
LDFLAGS = -fsanitize=address,undefined
|
||||
|
||||
BUILDDIR = build
|
||||
|
||||
# One binary per subsystem under test.
|
||||
TESTS = $(BUILDDIR)/test_cmd_editor
|
||||
|
||||
all: run
|
||||
|
||||
$(BUILDDIR):
|
||||
mkdir -p $(BUILDDIR)
|
||||
|
||||
# cmd_editor.c is compiled UNMODIFIED from the firmware tree.
|
||||
$(BUILDDIR)/test_cmd_editor: test_cmd_editor.c support.c $(SRCROOT)/cmd_editor.c | $(BUILDDIR)
|
||||
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
|
||||
|
||||
run: $(TESTS)
|
||||
@fail=0; for t in $(TESTS); do \
|
||||
echo "=== $$t ==="; \
|
||||
$$t || fail=1; \
|
||||
echo; \
|
||||
done; \
|
||||
if [ $$fail -ne 0 ]; then echo "TESTS FAILED"; exit 1; else echo "ALL TESTS PASSED"; fi
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)
|
||||
|
||||
.PHONY: all run clean
|
||||
@@ -0,0 +1,49 @@
|
||||
# Host unit-test harness
|
||||
|
||||
Compile and test individual firmware translation units on the build host with
|
||||
**gcc + AddressSanitizer + UBSan** — no SDCC, no flashing, no hardware. This is
|
||||
the fast inner loop for the logic-level bugs in `../../NOTES/08-findings.md`
|
||||
(parser, line editor, HTTP header, DHCP): edit → `make` → see red/green in
|
||||
seconds instead of a minutes-long flash-and-reboot cycle.
|
||||
|
||||
## Run
|
||||
```
|
||||
cd test
|
||||
make # build + run all tests (ASan/UBSan on)
|
||||
make clean
|
||||
```
|
||||
Exit status is non-zero if any check fails, a sanitizer trips, or the watchdog
|
||||
fires — so it drops straight into CI.
|
||||
|
||||
## How it works
|
||||
- **`sdcc_shim.h`** (force-included) erases SDCC 8051 keywords (`__xdata`,
|
||||
`__code`, `__banked`, …) so firmware sources compile under host gcc. It changes
|
||||
no logic — the code under test is byte-for-byte the firmware source.
|
||||
- **`support.c` / `support.h`** mock the hardware edges: the 16-byte serial ring
|
||||
(`sbuf`), the command/history buffers, and the character-output sink
|
||||
(`write_char` etc.). Buffers are sized **exactly** as on target, so ASan
|
||||
redzones catch the same off-by-one overflows the 8051 hits.
|
||||
- Builds define **`RTLP_HOST_TEST`**, which hides the firmware's libc-named
|
||||
prototypes (`memset`/`strlen`/…) in `rtl837x_common.h` so they don't clash with
|
||||
glibc. Argument order matches libc, so on-host callers transparently use the C
|
||||
library. This guard is compiled out of normal firmware builds — zero on-target
|
||||
effect.
|
||||
- A **SIGALRM watchdog** (`watchdog_arm`) turns an infinite-loop bug into a fast
|
||||
test failure instead of a hung runner.
|
||||
|
||||
## Current coverage
|
||||
| Test binary | TU under test | Findings exercised |
|
||||
|-------------|---------------|--------------------|
|
||||
| `test_cmd_editor` | `cmd_editor.c` | **C4** — full-line hang + `cmd_buffer` 1-byte overflow; basic entry & backspace regressions |
|
||||
|
||||
## Adding a test for another module
|
||||
1. Write `test_<module>.c` with `main()` driving the module's entry points and
|
||||
`CHECK(cond, msg)` assertions.
|
||||
2. Add any missing mocked symbols to `support.c` (only what the linker asks for).
|
||||
3. Add a target in the `Makefile` listing `test_<module>.c support.c ../<module>.c`.
|
||||
4. If the module calls a firmware libc-name (`memcpy`/`strcpy`/…), it will use the
|
||||
C library on host (compatible arg order); provide a `fw_*`-style mock only if
|
||||
the semantics differ in a way the test cares about.
|
||||
|
||||
Good next targets (pure logic, high finding density): `cmd_parser.c` (C6/C8),
|
||||
the HTTP header parser in `httpd/httpd.c` (S1), `dhcp.c` (S5).
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* sdcc_shim.h — neutralize SDCC 8051 keywords so firmware translation units
|
||||
* compile under host gcc/clang for off-target unit testing.
|
||||
*
|
||||
* Force-included (gcc -include) ahead of every firmware source under test.
|
||||
* It only erases storage/qualifier keywords and the banked-call attribute;
|
||||
* it does NOT change any logic, so the code under test is byte-for-byte the
|
||||
* firmware source. Hardware/register access is provided by mocks (support.c),
|
||||
* never by this file.
|
||||
*/
|
||||
#ifndef SDCC_SHIM_H
|
||||
#define SDCC_SHIM_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/* Memory-space qualifiers → nothing (host has one flat address space). */
|
||||
#define __xdata
|
||||
#define __code
|
||||
#define __pdata
|
||||
#define __idata
|
||||
#define __data
|
||||
/* bool, not unsigned char: SDCC's bit types normalize any assigned value
|
||||
* to 0/1, which _Bool reproduces exactly. */
|
||||
#define __bit bool
|
||||
#define __sbit bool
|
||||
|
||||
/* NOTE: the firmware ships its own 8051 memcpy/memset/strlen/strcpy/sleep with
|
||||
* incompatible signatures (rtl837x_common.h). Host TUs must therefore build with
|
||||
* -fno-builtin (so gcc doesn't auto-declare the builtins) and must NOT include
|
||||
* <string.h>/<unistd.h>. Test code uses the t_* helpers in support.h instead. */
|
||||
|
||||
/* Function attributes → nothing (no banking / no 8051 calling conventions). */
|
||||
#define __banked
|
||||
#define __reentrant
|
||||
#define __naked
|
||||
#define __using(n)
|
||||
#define __interrupt(n)
|
||||
#define __at(addr)
|
||||
|
||||
#endif /* SDCC_SHIM_H */
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* support.c — mock hardware edges + test environment for the host harness.
|
||||
*
|
||||
* Everything the firmware expects the platform/other-TUs to provide, but which
|
||||
* has no business touching real hardware in a unit test, lives here: the serial
|
||||
* ring, the command/history buffers, the character-output sink, and stubs for
|
||||
* the print helpers. The buffers are sized EXACTLY as on target so that
|
||||
* AddressSanitizer redzones catch the same off-by-one overflows the 8051 hits.
|
||||
*/
|
||||
#include "sdcc_shim.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
/* Declared by hand instead of <unistd.h>, whose sleep() prototype clashes with
|
||||
* the firmware's own sleep() declaration in rtl837x_common.h. */
|
||||
extern unsigned int alarm(unsigned int);
|
||||
extern void _exit(int);
|
||||
#include "rtl837x_common.h" /* SBUF_SIZE, CMD_BUF_SIZE, CMD_HISTORY_SIZE, decls */
|
||||
#include "support.h"
|
||||
|
||||
/* --- symbols the editor/parser reference (extern in the firmware headers) --- */
|
||||
__xdata volatile uint8_t sbuf_ptr;
|
||||
__xdata uint8_t sbuf[SBUF_SIZE];
|
||||
uint8_t cmd_buffer[CMD_BUF_SIZE]; /* ASan-instrumented: OOB caught */
|
||||
uint8_t cmd_available;
|
||||
uint8_t err_status;
|
||||
uint8_t cmd_history[CMD_HISTORY_SIZE];
|
||||
uint16_t cmd_history_ptr;
|
||||
|
||||
/* editor-internal read pointer (defined in cmd_editor.c, external linkage) */
|
||||
extern __xdata uint8_t l;
|
||||
|
||||
/* the editor entry points under test */
|
||||
void cmd_editor_init(void);
|
||||
void cmd_edit(void);
|
||||
|
||||
/* ------------------------------- output sink ------------------------------- */
|
||||
char out_buf[8192];
|
||||
size_t out_len;
|
||||
|
||||
void out_reset(void) { out_len = 0; out_buf[0] = 0; }
|
||||
|
||||
static void out_putc(char c) {
|
||||
if (out_len < sizeof(out_buf) - 1) { out_buf[out_len++] = c; out_buf[out_len] = 0; }
|
||||
}
|
||||
|
||||
/* --- mocked print helpers (signatures match rtl837x_common.h post-shim) ---- */
|
||||
void write_char(char c) { out_putc(c); }
|
||||
void print_string(char *p) { while (*p) out_putc(*p++); }
|
||||
void print_string_x(char *p) { while (*p) out_putc(*p++); }
|
||||
void print_cmd_prompt(void) { print_string((char *)"> "); }
|
||||
void itoa(uint8_t v) { /* firmware prints a decimal number */
|
||||
char t[4]; int n = 0;
|
||||
if (!v) { out_putc('0'); return; }
|
||||
while (v) { t[n++] = '0' + v % 10; v /= 10; }
|
||||
while (n) out_putc(t[--n]);
|
||||
}
|
||||
|
||||
/* ------------------------------ environment -------------------------------- */
|
||||
void env_reset(void) {
|
||||
sbuf_ptr = 0;
|
||||
t_memset(sbuf, 0, sizeof(sbuf));
|
||||
t_memset(cmd_buffer, 0, sizeof(cmd_buffer));
|
||||
t_memset(cmd_history, 0, sizeof(cmd_history));
|
||||
cmd_history_ptr = 0;
|
||||
cmd_available = 0;
|
||||
err_status = 0;
|
||||
out_reset();
|
||||
cmd_editor_init(); /* sets l = sbuf_ptr, clears cursor/len */
|
||||
}
|
||||
|
||||
/* Push one byte into the ring, draining first if it is full (leave 1 slot). */
|
||||
static void ring_push(uint8_t b) {
|
||||
while (((sbuf_ptr + 1) & SBUF_MASK) == l)
|
||||
cmd_edit(); /* drain; under the C4 bug this spins */
|
||||
sbuf[sbuf_ptr] = b;
|
||||
sbuf_ptr = (sbuf_ptr + 1) & SBUF_MASK;
|
||||
}
|
||||
|
||||
void feed_n(const char *s, size_t n) {
|
||||
for (size_t i = 0; i < n; i++) ring_push((uint8_t)s[i]);
|
||||
cmd_edit();
|
||||
}
|
||||
void feed(const char *s) { feed_n(s, t_strlen(s)); }
|
||||
|
||||
/* -------------------------------- watchdog --------------------------------- */
|
||||
static const char *wd_what;
|
||||
static void wd_fire(int sig) {
|
||||
(void)sig;
|
||||
/* async-signal-unsafe write is acceptable: we abort immediately after. */
|
||||
fprintf(stderr, "\n HANG DETECTED (watchdog): %s\n", wd_what ? wd_what : "?");
|
||||
_exit(2);
|
||||
}
|
||||
void watchdog_arm(unsigned seconds, const char *what) {
|
||||
wd_what = what;
|
||||
signal(SIGALRM, wd_fire);
|
||||
alarm(seconds);
|
||||
}
|
||||
void watchdog_disarm(void) { alarm(0); }
|
||||
|
||||
/* -------------------------------- framework -------------------------------- */
|
||||
int tests_run, tests_failed;
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* support.h — test harness API for off-target firmware unit tests.
|
||||
*
|
||||
* Provides the mock environment (serial ring, command buffers, output capture)
|
||||
* plus a tiny assertion framework. The firmware sources under test are compiled
|
||||
* unmodified against this; only the hardware edges are mocked here.
|
||||
*/
|
||||
#ifndef TEST_SUPPORT_H
|
||||
#define TEST_SUPPORT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* Distinct-named string helpers so host code needn't include <string.h>, which
|
||||
* would clash with the firmware's own memset/strlen/strcpy prototypes in
|
||||
* rtl837x_common.h (different 8051 signatures). */
|
||||
static inline void t_memset(void *d, int c, size_t n) {
|
||||
unsigned char *p = d; while (n--) *p++ = (unsigned char)c;
|
||||
}
|
||||
static inline size_t t_strlen(const char *s) { const char *p = s; while (*p) p++; return (size_t)(p - s); }
|
||||
static inline int t_strcmp(const char *a, const char *b) {
|
||||
while (*a && *a == *b) { a++; b++; } return (unsigned char)*a - (unsigned char)*b;
|
||||
}
|
||||
|
||||
/* ---- captured terminal output (what the firmware would send over serial) ---- */
|
||||
extern char out_buf[8192];
|
||||
extern size_t out_len;
|
||||
void out_reset(void);
|
||||
|
||||
/* ---- editor/parser environment reset ---- */
|
||||
void env_reset(void); /* clears ring, buffers, editor state */
|
||||
|
||||
/* ---- drive the serial input ring exactly like the RX ISR would ----
|
||||
* Pushes bytes into the 16-byte sbuf ring, draining via cmd_edit() whenever the
|
||||
* ring would overflow, then a final drain. Mirrors real ISR→editor handoff.
|
||||
*/
|
||||
void feed(const char *s); /* NUL-terminated */
|
||||
void feed_n(const char *s, size_t n); /* explicit length (for embedded NULs) */
|
||||
|
||||
/* ---- watchdog: arm before calling into code that could spin (C4 hang) ---- */
|
||||
void watchdog_arm(unsigned seconds, const char *what);
|
||||
void watchdog_disarm(void);
|
||||
|
||||
/* ---- minimal test framework ---- */
|
||||
extern int tests_run, tests_failed;
|
||||
#define CHECK(cond, msg) do { \
|
||||
tests_run++; \
|
||||
if (!(cond)) { tests_failed++; \
|
||||
printf(" FAIL: %s (%s:%d)\n", (msg), __FILE__, __LINE__); \
|
||||
} else { printf(" ok: %s\n", (msg)); } \
|
||||
} while (0)
|
||||
|
||||
#endif /* TEST_SUPPORT_H */
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* test_cmd_editor.c — host unit tests for cmd_editor.c (the serial line editor).
|
||||
*
|
||||
* Focus: finding C4 (08-findings.md) — a full input line both HANGS the editor
|
||||
* (continue without advancing the ring read pointer) and OVERFLOWS cmd_buffer by
|
||||
* one byte (guard off-by-one lets cmd_line_len reach CMD_BUF_SIZE).
|
||||
*
|
||||
* These tests are RED against the current source and GREEN after the fix, with
|
||||
* no hardware and no flash cycle. The hang is caught by a SIGALRM watchdog; the
|
||||
* overflow is caught by AddressSanitizer redzones on cmd_buffer[].
|
||||
*/
|
||||
#include "sdcc_shim.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "rtl837x_common.h" /* CMD_BUF_SIZE */
|
||||
#include "support.h"
|
||||
|
||||
/* editor state (globals in cmd_editor.c, external linkage) */
|
||||
extern __xdata uint8_t cmd_line_len;
|
||||
extern uint8_t cmd_buffer[];
|
||||
extern uint8_t cmd_available;
|
||||
|
||||
static void test_basic_line(void) {
|
||||
printf("[test] basic line entry + Enter\n");
|
||||
env_reset();
|
||||
feed("vlan 199\n");
|
||||
CHECK(cmd_available == 1, "Enter marks a command available");
|
||||
CHECK(t_strcmp((char *)cmd_buffer, "vlan 199") == 0, "cmd_buffer holds the typed line");
|
||||
}
|
||||
|
||||
static void test_backspace(void) {
|
||||
printf("[test] backspace edits the line\n");
|
||||
env_reset();
|
||||
feed("helo");
|
||||
feed("\b"); /* delete the 'o' -> wait, cursor at end: removes last char */
|
||||
feed("lo\n");
|
||||
CHECK(t_strcmp((char *)cmd_buffer, "hello") == 0, "backspace then retype yields 'hello'");
|
||||
}
|
||||
|
||||
/* C4 part 1: a line that fills the buffer must not spin forever. */
|
||||
static void test_full_line_no_hang(void) {
|
||||
char big[200];
|
||||
printf("[test] C4: overlong line does not hang the editor\n");
|
||||
env_reset();
|
||||
t_memset(big, 'a', sizeof(big));
|
||||
watchdog_arm(5, "C4 full-line hang (editor spun without advancing ring ptr)");
|
||||
feed_n(big, sizeof(big)); /* 200 printable chars, no newline */
|
||||
watchdog_disarm();
|
||||
/* Reaching here means no hang. With the fix the line caps one short of full. */
|
||||
CHECK(cmd_line_len == CMD_BUF_SIZE - 1,
|
||||
"line length caps at CMD_BUF_SIZE-1 (room reserved for NUL)");
|
||||
}
|
||||
|
||||
/* C4 part 2: filling the buffer then Enter must not write cmd_buffer[CMD_BUF_SIZE]. */
|
||||
static void test_full_line_no_overflow(void) {
|
||||
char full[CMD_BUF_SIZE]; /* 128 printable chars, exactly buffer size */
|
||||
printf("[test] C4: full line + Enter does not overflow cmd_buffer\n");
|
||||
env_reset();
|
||||
t_memset(full, 'b', sizeof(full));
|
||||
watchdog_arm(5, "C4 overflow-path hang");
|
||||
feed_n(full, sizeof(full));
|
||||
feed("\n"); /* under the bug, writes cmd_buffer[128] -> ASan trap */
|
||||
watchdog_disarm();
|
||||
CHECK(1, "128-char line + Enter completes without ASan overflow");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("== cmd_editor host tests ==\n");
|
||||
test_basic_line();
|
||||
test_backspace();
|
||||
test_full_line_no_hang();
|
||||
test_full_line_no_overflow();
|
||||
printf("\n%d checks, %d failed\n", tests_run, tests_failed);
|
||||
return tests_failed ? 1 : 0;
|
||||
}
|
||||
@@ -724,7 +724,7 @@ void launch(struct Server *server)
|
||||
send_counters(new_socket, port);
|
||||
goto done;
|
||||
}
|
||||
if (!authenticated && !(!strncmp(&buffer[4], "/login.html", 11) || !strncmp(&buffer[4], "/style.css", 10))) {
|
||||
if (!authenticated && !(!strncmp(&buffer[4], "/login.html", 11) || !strncmp(&buffer[4], "/favicon.ico", 12))) {
|
||||
send_to_login(new_socket);
|
||||
goto done;
|
||||
}
|
||||
|
||||