Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8e437f3a4 | ||
|
|
82dfb8f789 | ||
|
|
8224b06f9e | ||
|
|
69151362a0 | ||
|
|
24e83d5b74 | ||
|
|
3d81df0cad | ||
|
|
2481810906 | ||
|
|
03311debe6 | ||
|
|
e4adec9c11 | ||
|
|
d525229b42 | ||
|
|
73b23808cf | ||
|
|
4a14f50932 | ||
|
|
1bfe632174 | ||
|
|
265a80bb25 | ||
|
|
64efd3fb9f | ||
|
|
2e6425f5e6 | ||
|
|
1856aeba28 | ||
|
|
4e4a2ea4db | ||
|
|
4ccbf51976 | ||
|
|
0df48fbb1c | ||
|
|
25040070e5 | ||
|
|
edf6351d85 | ||
|
|
c3b4cc893b | ||
|
|
b83b67d234 | ||
|
|
efd61be64e | ||
|
|
da7909f422 | ||
|
|
14b2ce1c49 | ||
|
|
84d9408151 | ||
|
|
05c0571154 | ||
|
|
8790f0dca4 | ||
|
|
98af999304 | ||
|
|
0a996fa6d0 | ||
|
|
441e575661 | ||
|
|
a27118a428 | ||
|
|
01709431dc | ||
|
|
de0d413c0c | ||
|
|
b3ac10cae7 | ||
|
|
4e00f5a592 | ||
|
|
eff3f42ae1 | ||
|
|
dda873a633 | ||
|
|
9b2bd7f1b8 | ||
|
|
2dfe531fe0 | ||
|
|
72c2625121 | ||
|
|
58fd1a3ccc | ||
|
|
4f1a2f04d8 | ||
|
|
cedc795b82 | ||
|
|
ccf541a06b | ||
|
|
d5124df89e | ||
|
|
3eacc9c8a5 | ||
|
|
735672ccf1 | ||
|
|
1ebee4d6e9 | ||
|
|
24c0a95734 | ||
|
|
f88f655f5a | ||
|
|
a6b0918885 | ||
|
|
1d6fe690a4 | ||
|
|
e67c928eb7 | ||
|
|
46f8ea8712 | ||
|
|
b0eab52a2e | ||
|
|
8ce062c349 | ||
|
|
f768220a73 | ||
|
|
eab2e26510 | ||
|
|
b0ed56d423 | ||
|
|
36a064d03d | ||
|
|
2d01b8b7d7 | ||
|
|
fa632cdc6e | ||
|
|
177fb76e1b | ||
|
|
ff57084799 | ||
|
|
2dc5520928 | ||
|
|
7c11142a04 | ||
|
|
957a5b0e23 | ||
|
|
c773230a9b | ||
|
|
4f009539b0 | ||
|
|
bb90e0c9ba | ||
|
|
0e9c997d06 | ||
|
|
5ab247e996 | ||
|
|
d80717f3f0 | ||
|
|
24069439b0 | ||
|
|
4117f549cc | ||
|
|
2e24b24426 | ||
|
|
b71239f394 | ||
|
|
6e69f5bba3 | ||
|
|
1224335b66 | ||
|
|
e5b19bc594 | ||
|
|
89fcc6836d | ||
|
|
f9d4ab0074 | ||
|
|
6f68ccef28 | ||
|
|
8e687ef28e | ||
|
|
87db2dd4b8 | ||
|
|
aac1aed4c8 | ||
|
|
f0aea3dcac | ||
|
|
f10ab30f6d | ||
|
|
de431c6d84 | ||
|
|
3cd6d60e0b | ||
|
|
67b8c718fb | ||
|
|
26547f3e01 | ||
|
|
13b75a73c9 | ||
|
|
c6fceae724 | ||
|
|
068ec4c510 | ||
|
|
3a92459190 | ||
|
|
5ffeb3d16a | ||
|
|
de705380c9 | ||
|
|
e4f3315af3 | ||
|
|
07ab04e668 | ||
|
|
c5ce24ff78 | ||
|
|
bcb237158f | ||
|
|
f103daaafa | ||
|
|
168add47a3 | ||
|
|
423886dc54 | ||
|
|
4a9137387c | ||
|
|
2fc08f40d2 | ||
|
|
4a7fd7a098 | ||
|
|
19cd1200b4 | ||
|
|
333adbaf19 | ||
|
|
8051061fd7 | ||
|
|
8ae3f5e70f | ||
|
|
9ce328d155 | ||
|
|
2e463a2f30 | ||
|
|
422dc3f24d | ||
|
|
4b01ff9f8a | ||
|
|
456a4d9d3f | ||
|
|
02e9189f5f | ||
|
|
aff7d9c098 | ||
|
|
770926abd1 | ||
|
|
13f04d8acb | ||
|
|
49284fee15 | ||
|
|
db9ce7543e | ||
|
|
7a14c2d4a5 | ||
|
|
92948079a9 | ||
|
|
e406a3e82b | ||
|
|
c43b955db0 | ||
|
|
0321763e91 | ||
|
|
4bbbcd7d04 | ||
|
|
682ed861fa | ||
|
|
3a9045b97e | ||
|
|
4b9f6c319f | ||
|
|
6e2701bc8c | ||
|
|
ac3b4b17ae | ||
|
|
f87eb70c6a | ||
|
|
214cc6b737 | ||
|
|
40177f6318 | ||
|
|
d3dda09079 | ||
|
|
277e729f2c | ||
|
|
f778b9159b | ||
|
|
b6d6d2391d | ||
|
|
2c231fdf69 | ||
|
|
689751ffe6 | ||
|
|
fa93340c24 | ||
|
|
e9523a2f67 | ||
|
|
088be074f5 | ||
|
|
340fdd97e5 | ||
|
|
09ce4b391b | ||
|
|
7e6577a811 | ||
|
|
675faf0471 | ||
|
|
175ca9464a | ||
|
|
38241f0a08 | ||
|
|
0fe323b12f | ||
|
|
94187461c6 | ||
|
|
fa0eb91586 | ||
|
|
2f872ea08e | ||
|
|
ab2d30a05b | ||
|
|
d37ce07119 | ||
|
|
4bd6ffd083 | ||
|
|
94cfec8693 | ||
|
|
a4bb3f1725 | ||
|
|
931e6da0ab | ||
|
|
88454ec7d7 | ||
|
|
f0efb84abb | ||
|
|
62d3424a92 | ||
|
|
a3860c29cb | ||
|
|
b1961c6955 | ||
|
|
bb84bdc55a | ||
|
|
9733ff61a0 | ||
|
|
1b5243714e | ||
|
|
36459fdd76 | ||
|
|
ce11790a0d | ||
|
|
55431b39f6 | ||
|
|
46e1c955ba | ||
|
|
7b728cec6f | ||
|
|
0d17dd563f | ||
|
|
d556d634ca | ||
|
|
2844a1ed6d | ||
|
|
7b5e365d5e | ||
|
|
45fb382b07 | ||
|
|
8973774594 | ||
|
|
48c5b0c9cf | ||
|
|
1557a7cf3c | ||
|
|
131bc106af | ||
|
|
19ccc4003d | ||
|
|
b5fe9f217f | ||
|
|
f65d14f452 | ||
|
|
b92fba870c | ||
|
|
4deee377a7 | ||
|
|
fe42b07e2b | ||
|
|
9984649177 | ||
|
|
7d47edb649 | ||
|
|
fdb19fe0ee | ||
|
|
b9b50f9a70 | ||
|
|
c31b67572b | ||
|
|
a4e3b4368b | ||
|
|
93003c8cb8 | ||
|
|
fd058ea74d | ||
|
|
edc469fe29 | ||
|
|
a71c5359a5 | ||
|
|
9e3863cc4e | ||
|
|
d22ec04b5b | ||
|
|
0a65536a7f | ||
|
|
0631c813e2 | ||
|
|
eaebeba528 | ||
|
|
b36fd95356 | ||
|
|
52b1d1a4ea | ||
|
|
48f28ae7ff | ||
|
|
a7fc8303e9 | ||
|
|
a62866a93a | ||
|
|
42bbed9661 | ||
|
|
3c8de6da87 | ||
|
|
3a6f62b1d5 | ||
|
|
530e2683f0 | ||
|
|
9fedc6dd3d | ||
|
|
5e23d13771 | ||
|
|
09d5857fdd | ||
|
|
be309853c9 | ||
|
|
072edb36a8 | ||
|
|
10dd330e3c | ||
|
|
c514eb8bf3 | ||
|
|
e2c4ed6be3 | ||
|
|
56ee56af2e | ||
|
|
8626b4c1d4 | ||
|
|
0cc09e4015 | ||
|
|
674dc1f35d | ||
|
|
7e6b443e07 | ||
|
|
73ec184c23 | ||
|
|
bf41fbd202 | ||
|
|
04ab442c6d | ||
|
|
c02623a610 | ||
|
|
c69bf4c5ec | ||
|
|
575274786a | ||
|
|
7f4f6dae03 | ||
|
|
4eeb12c020 | ||
|
|
7d81b15b6e | ||
|
|
d6253ddc25 | ||
|
|
8cac67cdf7 | ||
|
|
5620e950ce | ||
|
|
83491bc9e3 | ||
|
|
eb83b8be09 | ||
|
|
bece4a20a1 | ||
|
|
cdc84c75e4 | ||
|
|
6be1871bb4 | ||
|
|
4686515de3 | ||
|
|
c39cc421a7 | ||
|
|
c1df7c23d1 | ||
|
|
9bae4052a3 | ||
|
|
0d115e0e0b | ||
|
|
8e78d1e629 | ||
|
|
13cc9db1ce | ||
|
|
3dcab09047 | ||
|
|
94992562fd | ||
|
|
6150ada5f8 | ||
|
|
8116ef0a5b | ||
|
|
0cd56b1196 | ||
|
|
3b3f36256c | ||
|
|
18b28edcbb | ||
|
|
6ea9503ab2 | ||
|
|
e0df37f359 | ||
|
|
c04a5fc3a4 | ||
|
|
f0b311fc43 | ||
|
|
2f005d2993 | ||
|
|
974052877d | ||
|
|
ae77ffe495 | ||
|
|
f65416f0d1 | ||
|
|
ffde4eaf60 | ||
|
|
c05d83132b | ||
|
|
dcd3cd5a98 | ||
|
|
9f107b6e60 | ||
|
|
15b10d9ed0 | ||
|
|
abf1cd1344 | ||
|
|
b5b52776bc | ||
|
|
0486c34bfd | ||
|
|
690092d564 | ||
|
|
115ea09ebb | ||
|
|
f3e1a393b5 | ||
|
|
206c72b5ef | ||
|
|
472811cc9a | ||
|
|
80552816b9 | ||
|
|
c1caf6f569 | ||
|
|
ddad65fe90 | ||
|
|
ff7b517491 | ||
|
|
65417433eb | ||
|
|
b7621fad31 | ||
|
|
82f78cb8f2 | ||
|
|
75f2ade1a1 | ||
|
|
3e91b05a42 | ||
|
|
ec1cb4106d | ||
|
|
62c6c65f1c | ||
|
|
c691591295 | ||
|
|
434acbcc13 | ||
|
|
1aae2fac30 | ||
|
|
1f9b3df3e6 | ||
|
|
eb4bae278b | ||
|
|
7908044a44 | ||
|
|
0ec1a5e63f | ||
|
|
114af559c7 | ||
|
|
5a2c4ca029 | ||
|
|
63e367d298 | ||
|
|
5e513daf17 | ||
|
|
3ec9319d77 | ||
|
|
b42eace21d | ||
|
|
3dfd6a88c7 | ||
|
|
7fa26e0ae3 | ||
|
|
1c8cdab4bf | ||
|
|
f49ca0d6fe | ||
|
|
12fb6fd809 | ||
|
|
dad65fd550 | ||
|
|
dc82e044bc | ||
|
|
7daa9effe0 | ||
|
|
950586135a | ||
|
|
c1ad800fc5 | ||
|
|
dc4fb5becc | ||
|
|
78ecef15e9 | ||
|
|
eab927f52a | ||
|
|
02c816647c | ||
|
|
d3f1069285 | ||
|
|
98bda62181 | ||
|
|
3f65db9984 | ||
|
|
df95c544d4 | ||
|
|
7df3c87892 | ||
|
|
53f12a42c5 | ||
|
|
1b514b5ea8 | ||
|
|
dd09be9dd0 | ||
|
|
b2b7f8a0c6 | ||
|
|
5b28ddf80a | ||
|
|
624f93a28a | ||
|
|
fe3da0c7a5 | ||
|
|
412098d0ed | ||
|
|
5ecce86ef1 | ||
|
|
247b230212 | ||
|
|
c012dda029 | ||
|
|
6616a0c9ce | ||
|
|
eab8c901cf | ||
|
|
21a9fdf45a | ||
|
|
1ed12d1101 | ||
|
|
8d7faf5312 | ||
|
|
af4101f060 | ||
|
|
d6d9c368e9 | ||
|
|
36de9791be | ||
|
|
eab17f8a3f | ||
|
|
552bd1cb18 | ||
|
|
4bcbada764 | ||
|
|
9fae0f9249 | ||
|
|
774e04816e | ||
|
|
ead1c40aac | ||
|
|
d245249385 | ||
|
|
37eefb4b35 | ||
|
|
3f5093f180 | ||
|
|
4c4232cc07 | ||
|
|
c4928e5546 | ||
|
|
67ae4598d9 | ||
|
|
b651890a3a | ||
|
|
caa7874bb3 | ||
|
|
53f33bd606 | ||
|
|
57e3fce95e |
No files matched your search
@@ -3,6 +3,7 @@ name: Build firmware
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -16,8 +17,14 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt update
|
||||
apt install make gcc sdcc xxd python-is-python3 libjson-c-dev -y
|
||||
- name: Check if machine.c can be compiled for all machines
|
||||
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 that every machine definition can be compiled
|
||||
run: make machine_check
|
||||
- name: Make project
|
||||
run: make MACHINE="KP_9000_6XHML_X2"
|
||||
- name: Make project (4x2/RTL8372)
|
||||
run: make CI=1 MACHINE="KP_9000_6XHML_X2"
|
||||
- name: Make project (8x1/RTL8373)
|
||||
run: make CI=1 MACHINE="KP_9000_9XHML_X_V3_1"
|
||||
- name: Make project with the health instrumentation
|
||||
run: make clean && make CI=1 MACHINE="KP_9000_6XHML_X2" HEALTH=1
|
||||
@@ -7,3 +7,7 @@ version.h
|
||||
tools/httpd_sim
|
||||
tools/injector
|
||||
tools/fileadder
|
||||
|
||||
# host-test stand-ins for the two generated headers
|
||||
!test/stub/html_data.h
|
||||
!test/stub/version.h
|
||||
@@ -4,10 +4,22 @@
|
||||
|
||||
## Added
|
||||
|
||||
- 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
|
||||
|
||||
## Fixed
|
||||
|
||||
- Config
|
||||
- A startup configuration line longer than the command buffer no longer stops the replay:
|
||||
the offending line is skipped and the following lines are still applied.
|
||||
- The web UI and the configuration upload now refuse a line the replay cannot take,
|
||||
instead of writing it to flash.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
- Config
|
||||
|
||||
@@ -7,8 +7,8 @@ RUN apt-get update && apt-get install -y \
|
||||
xxd \
|
||||
python3 \
|
||||
libjson-c-dev \
|
||||
golang-go \
|
||||
git \
|
||||
zlib1g-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# git safe.directory for mounted repos (Makefile uses git describe)
|
||||
|
||||
@@ -7,18 +7,32 @@ HTML_LOCATION = 262144
|
||||
ifeq ($(origin CC),default)
|
||||
CC = sdcc
|
||||
endif
|
||||
CC_FLAGS = -mmcs51 -I. -Ihttpd -Iuip
|
||||
CC_FLAGS = -mmcs51 -I. -Imachine -Ihttpd -Iuip
|
||||
ASM ?= sdas8051
|
||||
AFLAGS= -plosgff
|
||||
|
||||
SUBDIRS := tools
|
||||
SUBDIRSCLEAN=$(addsuffix clean,$(SUBDIRS))
|
||||
|
||||
ifeq ($(MACHINE),)
|
||||
MACHINE:= $(shell grep "^\s*#define MACHINE_" machine.h | sed "s/^\s*#define MACHINE_//")
|
||||
else
|
||||
# $MACHINE selects machine/$(MACHINE).c as the board definition and is passed
|
||||
# on as -DMACHINE_$(MACHINE), which the rest of the firmware tests with
|
||||
# #if defined(MACHINE_...), and as -DMACHINE_NAME, which machine.h uses to
|
||||
# include machine/$(MACHINE).h. Both take the same value, so they cannot drift.
|
||||
ifneq ($(MACHINE),)
|
||||
CC_FLAGS += -DMACHINE_$(MACHINE)
|
||||
CC_FLAGS += -DMACHINE_NAME=$(MACHINE)
|
||||
endif
|
||||
# Health instrumentation and the "health" console command: HEALTH=1 gmake ...
|
||||
ifneq ($(HEALTH),)
|
||||
CC_FLAGS += -DHEALTH
|
||||
endif
|
||||
|
||||
ifeq ($(CI),1)
|
||||
CC_FLAGS += --Werror
|
||||
endif
|
||||
|
||||
# The board definition being built: one file per MACHINE_* symbol in machine.h.
|
||||
MACHINE_SRC = machine/$(MACHINE).c
|
||||
|
||||
BUILDDIR = output/$(MACHINE)
|
||||
VERSION_HEADER := version.h
|
||||
@@ -43,24 +57,46 @@ BUILD_DATE := $(shell date -u -d @$(SOURCE_DATE_EPOCH) +"%Y-%m-%d %H:%M:%S" 2>/d
|
||||
|| date -u -r $(SOURCE_DATE_EPOCH) +"%Y-%m-%d %H:%M:%S")
|
||||
endif
|
||||
|
||||
all: create_build_dir $(VERSION_HEADER) $(SUBDIRS) $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin
|
||||
# $(MACHINE_SRC) is listed here so an unknown $MACHINE fails before anything else
|
||||
# is built, including the tools/ subdirectory.
|
||||
all: create_build_dir $(VERSION_HEADER) $(MACHINE_SRC) $(SUBDIRS) $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin
|
||||
|
||||
create_build_dir:
|
||||
mkdir -p "$(BUILDDIR)"
|
||||
mkdir -p "$(BUILDDIR)/uip"
|
||||
mkdir -p "$(BUILDDIR)/httpd"
|
||||
mkdir -p "$(BUILDDIR)/machine"
|
||||
mkdir -p "$(BUILDDIR)/crypto"
|
||||
|
||||
# Keep machine.c in first position to fail immediately on invalid $MACHINE value
|
||||
# Every machine is a pair machine/<MACHINE>.c + machine/<MACHINE>.h. Selecting
|
||||
# them by name means no #if/#elif chain, so an unknown or empty $MACHINE is
|
||||
# caught here instead of by a #error further down the build.
|
||||
$(MACHINE_SRC):
|
||||
@echo "ERROR: MACHINE='$(MACHINE)' has no definition (expected $(MACHINE_SRC))." >&2
|
||||
@echo " Every machine is one file pair under machine/, named after the" >&2
|
||||
@echo " MACHINE_* symbol it defines; see machine/*.c for the full list." >&2
|
||||
@echo " Example: make MACHINE=KP_9000_6XHML_X2" >&2
|
||||
@false
|
||||
|
||||
# Keep the per-machine definition in first position to fail immediately on an
|
||||
# invalid $MACHINE value
|
||||
SRCS = \
|
||||
machine.c \
|
||||
$(MACHINE_SRC) \
|
||||
machine_init.c \
|
||||
cmd_editor.c \
|
||||
cmd_parser.c \
|
||||
dhcp.c \
|
||||
html_data.c \
|
||||
rtlplayground.c \
|
||||
boot.c \
|
||||
sfp.c \
|
||||
dns.c \
|
||||
dns_cli.c \
|
||||
ntp.c \
|
||||
ntp_cli.c \
|
||||
syslog.c \
|
||||
udp_apps.c
|
||||
udp_apps.c \
|
||||
crypto/chacha20.c
|
||||
|
||||
# RTL837x
|
||||
SRCS += \
|
||||
@@ -69,10 +105,15 @@ SRCS += \
|
||||
rtl837x_igmp.c \
|
||||
rtl837x_init.c \
|
||||
rtl837x_leds.c \
|
||||
rtl837x_leds_dump.c \
|
||||
rtl837x_phy.c \
|
||||
rtl837x_pins.c\
|
||||
rtl837x_port.c \
|
||||
rtl837x_stp.c
|
||||
rtl837x_stp.c \
|
||||
rtl837x_lldp.c \
|
||||
rtl837x_storm.c \
|
||||
rtl837x_lacp.c \
|
||||
rtl837x_trap.c
|
||||
SRCS += \
|
||||
httpd/httpd.c \
|
||||
httpd/page_impl.c
|
||||
@@ -87,16 +128,29 @@ SRCS += \
|
||||
|
||||
OBJS = ${SRCS:%.c=$(BUILDDIR)/%.rel}
|
||||
DEPS := ${SRCS:%.c=$(BUILDDIR)/%.d}
|
||||
HTML := $(shell find html -name '*.js' -or -name '*.html' -or -name '*.svg')
|
||||
HTML := $(shell find html -name '*.js' -or -name '*.html' -or -name '*.svg' -or -name '*.css' -or -name '*.ico')
|
||||
|
||||
html_data.c html_data.h &: $(HTML) | tools
|
||||
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -b BANK1 -d html -p html_data
|
||||
# Minified copy of the web UI sources, used as the fileadder input.
|
||||
# The raw html/ sources stay untouched for development; the minified
|
||||
# copy is a build artifact under output/.
|
||||
HTML_MIN := output/html_min
|
||||
.PHONY: html_min
|
||||
html_min: $(HTML)
|
||||
rm -rf $(HTML_MIN)
|
||||
mkdir -p $(HTML_MIN)
|
||||
@for f in $(HTML); do python3 tools/minify.py $$f $(HTML_MIN)/$$(basename $$f) || exit 1; done
|
||||
|
||||
$(VERSION_HEADER):
|
||||
html_data.c html_data.h &: $(HTML) | tools html_min
|
||||
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -b BANK1 -z -d $(HTML_MIN) -p html_data
|
||||
|
||||
$(VERSION_HEADER): FORCE
|
||||
@printf '%s\n' "#ifndef VERSION_H" "#define VERSION_H" \
|
||||
"#define VERSION_SW \"$(VERSION_EXTENSION)\"" \
|
||||
"#define BUILD_DATE \"$(BUILD_DATE)\"" \
|
||||
"#endif" > $(VERSION_HEADER)
|
||||
"#endif" > $(VERSION_HEADER).tmp
|
||||
@cmp -s $(VERSION_HEADER).tmp $(VERSION_HEADER) \
|
||||
&& rm -f $(VERSION_HEADER).tmp \
|
||||
|| mv $(VERSION_HEADER).tmp $(VERSION_HEADER)
|
||||
|
||||
httpd: html_data.h
|
||||
|
||||
@@ -114,15 +168,25 @@ distclean: $(SUBDIRSCLEAN)
|
||||
$(SUBDIRSCLEAN):
|
||||
$(MAKE) -C $(@:clean=) clean
|
||||
|
||||
$(BUILDDIR)/%.rel: %.c | create_build_dir html_data.h
|
||||
# Objects depend on the flags they were built with, through a stamp file that
|
||||
# is rewritten only when CC_FLAGS actually changes.
|
||||
CCFLAGS_STAMP := $(BUILDDIR)/.ccflags
|
||||
|
||||
.PHONY: FORCE
|
||||
FORCE:
|
||||
|
||||
$(CCFLAGS_STAMP): FORCE | create_build_dir
|
||||
@echo '$(CC_FLAGS)' | cmp -s - $@ 2>/dev/null || echo '$(CC_FLAGS)' > $@
|
||||
|
||||
$(BUILDDIR)/%.rel: %.c $(CCFLAGS_STAMP) | create_build_dir html_data.h
|
||||
$(CC) -MMD $(CC_FLAGS) -o $@ -c $<
|
||||
|
||||
$(BUILDDIR)/%.rel: %.asm | create_build_dir
|
||||
$(BUILDDIR)/%.rel: %.asm $(CCFLAGS_STAMP) | create_build_dir
|
||||
${ASM} ${AFLAGS} -o $@ $<
|
||||
# mv -f $(addprefix $(basename $^), .lst .rel .sym) .
|
||||
|
||||
$(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtbank.rel $(BUILDDIR)/crc16.rel
|
||||
$(CC) $(CC_FLAGS) -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-r -o $@ $^
|
||||
$(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtbank.rel $(BUILDDIR)/crc16.rel $(BUILDDIR)/crypto/chacha_8051.rel
|
||||
$(CC) $(CC_FLAGS) --xram-size 48896 -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-bBANK3=0x34000 -Wl-r -o $@ $^
|
||||
|
||||
$(BUILDDIR)/rtlplayground.img: $(BUILDDIR)/rtlplayground.ihx
|
||||
objcopy --input-target=ihex -O binary $< $@
|
||||
@@ -132,21 +196,28 @@ $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin: $(BUILDDIR)/rtlplayground.i
|
||||
tools/output/imagebuilder -i $^ $@
|
||||
tools/output/fileadder -a $(DEFAULT_CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@
|
||||
tools/output/fileadder -a $(CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@
|
||||
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -d html -p html_data -b BANK1 $@
|
||||
tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -z -d $(HTML_MIN) -b BANK1 $@
|
||||
tools/output/crc_calculator -u $@
|
||||
ln -sf $(MACHINE)/rtlplayground-$(FILENAME_EXTENSION).bin output/rtlplayground.bin
|
||||
|
||||
.PHONY: clean distclean all $(SUBDIRS) $(SUBDIRSCLEAN) $(VERSION_HEADER) create_build_dir
|
||||
.PHONY: clean distclean all $(SUBDIRS) $(SUBDIRSCLEAN) create_build_dir
|
||||
|
||||
# Every machine/ definition is compiled, so a machine added to machine/ is
|
||||
# covered without registering it anywhere. The machine-specific defines are
|
||||
# filtered out of $(CC_FLAGS) and supplied per iteration instead, so this also
|
||||
# works as "make machine_check MACHINE=...".
|
||||
MACHINE_DEFS := $(sort $(patsubst machine/%.c,%,$(wildcard machine/*.c)))
|
||||
MACHINE_LESS_CC_FLAGS = $(filter-out -DMACHINE_%,$(CC_FLAGS))
|
||||
|
||||
.PHONY:
|
||||
machine_check:
|
||||
@mkdir -p $(BUILDDIR)/tmp
|
||||
@set -eo pipefail; \
|
||||
for MACHINE in `grep -E '^[[:space:]]*(//[[:space:]]*)?#define MACHINE_' machine.h | sed -E 's%^[[:space:]]*(//[[:space:]]*)?#define MACHINE_%%' | awk '{print $$1}' | sort -u`; \
|
||||
for MACHINE in $(MACHINE_DEFS); \
|
||||
do \
|
||||
echo "Checking $${MACHINE}"; \
|
||||
$(CC) $(CC_FLAGS) -DMACHINE_$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine.c; \
|
||||
$(CC) $(CC_FLAGS) -DMACHINE_$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine_init.c; \
|
||||
$(CC) $(MACHINE_LESS_CC_FLAGS) -DMACHINE_$${MACHINE} -DMACHINE_NAME=$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine/$${MACHINE}.c; \
|
||||
$(CC) $(MACHINE_LESS_CC_FLAGS) -DMACHINE_$${MACHINE} -DMACHINE_NAME=$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine_init.c; \
|
||||
done
|
||||
@rm -rf $(BUILDDIR)/tmp
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
A Playground for Firmware development for advanced user of RTL8372/RTL8373 based 2.5GBit Switches.
|
||||
|
||||
For each hardware configuration of these devices, there is usually a managed and an
|
||||
umanaged version sold, with mostly identical hardware. The aim is to provide management
|
||||
unmanaged version sold, with mostly identical hardware. The aim is to provide management
|
||||
features also for unmanaged devices with additional features such as Management VLAN,
|
||||
dhcp servers, multi-language support, IPv6 and TLS-encrypted web-pages. At present, however
|
||||
DHCP servers, multi-language support, IPv6 and TLS-encrypted web-pages. At present, however
|
||||
only the following features are provided:
|
||||
- A modern web-interface with mouse-over to display further information
|
||||
- A serial console interface to configure all features
|
||||
- IGMP to configure Multicast streaming
|
||||
- Port configuration showing detailed informtion about own and Link-partner advertised
|
||||
Speed settins and configuration of these settings on the local side
|
||||
- Port configuration showing detailed information about own and Link-partner advertised
|
||||
Speed settings and configuration of these settings on the local side
|
||||
- Per-port configuration of frame sizes (MTUs) for Jumbo-Frame support or limiting MTUs
|
||||
for particular devices
|
||||
- EEE (Energy Efficient Ethernet) can be configured per-port. Detailed information is
|
||||
@@ -25,7 +25,7 @@ only the following features are provided:
|
||||
- Firmware updates via the web
|
||||
- Installation as a firmware upgrade from the original web-interface
|
||||
|
||||
<img width="1420" height="623" alt="GUI" src="doc/images/gui.png" />
|
||||
<img width="1673" height="977" alt="GUI" src="doc/images/gui.png" />
|
||||
|
||||
While the firmware provides already considerable improvements over the original managed firmware,
|
||||
the firmware still lacks support for the proprietary loop prevention
|
||||
@@ -37,7 +37,7 @@ a backup of the original flash content via a SOIC clamp such as also used for BI
|
||||
backups and can re-install that firmware in case something is wrong. For this no soldering
|
||||
skills are necessary.
|
||||
|
||||
The firmware supports all hardware featues of devices with
|
||||
The firmware supports all hardware features of devices with
|
||||
- 4 2.5GBit ports + 2 SFP+ ports
|
||||
- 5 2.5GBIT + 1 SFP+ port
|
||||
- 8 2.5GBit + 1 SFP+ port
|
||||
@@ -59,7 +59,7 @@ device simulator is provided, which runs entirely under Linux as a local webserv
|
||||
Install the following particular build requisites (Debian 12/13), note that Ubuntu 24.04
|
||||
still has an older version of sdcc, but you will need sdcc version 4.5 for the code to compile:
|
||||
```
|
||||
sudo apt install make gcc sdcc xxd python-is-python3 libjson-c-dev
|
||||
sudo apt install make gcc sdcc xxd python-is-python3 libjson-c-dev zlib1g-dev
|
||||
```
|
||||
|
||||
<details>
|
||||
@@ -131,7 +131,7 @@ rtlplayground-v0.1.0-12c98ba-dirty-LIANGUO_ZX_SWTGW215AS.bin
|
||||
|
||||
> [!CAUTION]
|
||||
> This image can be flashed directly to the chip OR through the firmware update/upgrade
|
||||
> interface of RTLPlaygound interface
|
||||
> interface of RTLPlayground interface
|
||||
|
||||
## (2) Compiling for OEM running device with management options (web upgrade)
|
||||
|
||||
@@ -175,11 +175,11 @@ Header checksum is: 0x5a1
|
||||
## (3) Sandbox Usage with Ghidra (optional)
|
||||
|
||||
You can play with the image using ghidra or flash real Switch Hardware. For
|
||||
ghidra see this information about [Ghidra images](ghidra.md).
|
||||
ghidra see this information about [Ghidra images](doc/ghidra.md).
|
||||
|
||||
## (4) Installation through the Web interface (software way)
|
||||
|
||||
Managed switches (OEM firmware of RTLplaygroud firmware) can be upgraded via the web interface.
|
||||
Managed switches (OEM firmware of RTLplayground firmware) can be upgraded via the web interface.
|
||||
Unmanaged switch cannot be flashed this way (see 5).
|
||||
|
||||
Go to "Firmware update" tab, select the correct file.
|
||||
@@ -190,7 +190,7 @@ Go to "Firmware update" tab, select the correct file.
|
||||
|
||||
> [!CAUTION]
|
||||
> Check one more time that your device matches the machine type before flashing.
|
||||
> Be shure you have a backup of the original firmware before diving in RTLPlaygroung.
|
||||
> Be sure you have a backup of the original firmware before diving in RTLPlayground.
|
||||
|
||||
Finally, push the Upload File Button and you're done !
|
||||
|
||||
@@ -198,7 +198,7 @@ Finally, push the Upload File Button and you're done !
|
||||
## (5) Flashing the ROM directly (hardware way, but also only way to rescue)
|
||||
|
||||
This procedure is the only way to flash unmanaged switches, if the ROM chip is large enough.
|
||||
This is also the only way to unbrick your device if something went wroong.
|
||||
This is also the only way to unbrick your device if something went wrong.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> You need a SOIC-8 clip to flash the ROM chip directly onboard.
|
||||
@@ -218,7 +218,7 @@ This is also the only way to unbrick your device if something went wroong.
|
||||
- ERASE THE ROM (BLANK) !
|
||||
- Load the firmware into IMSProg.
|
||||
- Flash is to the ROM chip.
|
||||
- Disconect the clip from the ROM chip.
|
||||
- Disconnect the clip from the ROM chip.
|
||||
- You're done, ready for the first boot.
|
||||
|
||||
## (6) Connecting a serial interface (optional)
|
||||
@@ -233,7 +233,7 @@ When you power up the switch, the device will perform some examples and provide
|
||||
## (8) The web-interface
|
||||
|
||||
The web-interface can be reached under the [default 192.168.10.247](http://192.168.10.247) unless you
|
||||
specified an IP adress in the config.txt before compilation.
|
||||
specified an IP address in the config.txt before compilation.
|
||||
|
||||
> [!TIP]
|
||||
> The default password is `1234`.
|
||||
@@ -333,17 +333,18 @@ While in compilation part, you might write directly to config.txt file before ma
|
||||
nano config.txt
|
||||
```
|
||||
|
||||
If you want to modify settings after the flash is done, go to the Advanced Settings tab in System Settings
|
||||
If you want to modify settings after the flash is done, go to the System tab and find the Startup Configuration.
|
||||
|
||||
<img width="1085" height="646" alt="ADVANCED SETTINGS" src="doc/images/advanced_settings.png" />
|
||||
<img width="1673" height="978" alt="ADVANCED SETTINGS" src="doc/images/advanced_settings.png" />
|
||||
|
||||
```
|
||||
ip xxx.xxx.xxx.xxx = IP adress of the switch
|
||||
gw yyy.yyy.yyy.yyy = IP adress of the gateway
|
||||
ip xxx.xxx.xxx.xxx = IP address of the switch
|
||||
gw yyy.yyy.yyy.yyy = IP address of the gateway
|
||||
netmask zzz.zzz.zzz.zzz = Network mask of the switch
|
||||
port x name xxx = Name xxx the port number x
|
||||
port z 1g = Set 1g speed for port z
|
||||
igmp on/off = Turn IGMP on or off
|
||||
session xxxx = Web session timeout in seconds (default 200)
|
||||
```
|
||||
[To be continue]
|
||||
|
||||
@@ -358,5 +359,7 @@ The following documents give further documentation on specific features of the R
|
||||
- [IGMP (IP-MC streaming)](doc/igmp.md)
|
||||
- [SFP+ ports](doc/sfp.md)
|
||||
- [Trunking aka. port aggregation](doc/trunking.md)
|
||||
- [LACP (802.3ad link aggregation)](doc/lacp.md)
|
||||
- [VLAN](doc/vlan.md)
|
||||
- [Storm control](doc/storm_control.md)
|
||||
- [Modifications and Flash replacement](doc/mods.md)
|
||||
@@ -0,0 +1,479 @@
|
||||
#pragma codeseg BANK3
|
||||
#pragma constseg BANK3
|
||||
|
||||
#include <8051.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "rtl837x_sfr.h"
|
||||
#include "rtl837x_regs.h"
|
||||
#include "rtl837x_common.h"
|
||||
#include "rtl837x_flash.h"
|
||||
#include "rtl837x_pins.h"
|
||||
#include "rtl837x_phy.h"
|
||||
#include "rtl837x_port.h"
|
||||
#include "rtl837x_init.h"
|
||||
#include "machine.h"
|
||||
#include "phy.h"
|
||||
#include "boot.h"
|
||||
|
||||
extern __code const struct machine machine;
|
||||
extern __xdata struct machine_runtime machine_detected;
|
||||
extern __xdata uint8_t sfr_data[4];
|
||||
extern volatile __xdata uint32_t ticks;
|
||||
__xdata uint32_t rtl8224_release_tick;
|
||||
extern __xdata uint8_t tx_seq;
|
||||
extern __code const uint8_t * __code const hex;
|
||||
|
||||
|
||||
|
||||
void sds_config_mac(uint8_t sds, uint8_t mode) __banked
|
||||
{
|
||||
reg_read_m(RTL837X_REG_SDS_MODES);
|
||||
sfr_data[0] = 0;
|
||||
sfr_data[1] = 0;
|
||||
switch (sds) {
|
||||
case 0:
|
||||
sfr_mask_data(0, 0x1f, mode);
|
||||
break;
|
||||
case 1:
|
||||
sfr_mask_data(0, 0xe0, mode << 5);
|
||||
sfr_mask_data(1, 0x03, mode >> 3);
|
||||
break;
|
||||
case 2:
|
||||
sfr_mask_data(1, 0xfc, 0x02 << 2);
|
||||
}
|
||||
if (machine_detected.isRTL8373) // Set 3rd SERDES Mode to 0x2 for RTL8224
|
||||
sfr_mask_data(1, 0xfc, 0x02 << 2);
|
||||
else
|
||||
sfr_data[2] &= 0x03;
|
||||
reg_write_m(RTL837X_REG_SDS_MODES);
|
||||
print_string("\nRTL837X_REG_SDS_MODES: ");
|
||||
print_reg(RTL837X_REG_SDS_MODES);
|
||||
print_string("\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
void early_boot_handle_button(void) __banked
|
||||
{
|
||||
if (machine.reset_pin == GPIO_NA)
|
||||
return;
|
||||
|
||||
gpio_input_setup(machine.reset_pin);
|
||||
|
||||
// Debounce after init
|
||||
delay(100);
|
||||
// If the button is not already held at boot, continue normally.
|
||||
if (gpio_pin_test(machine.reset_pin))
|
||||
return;
|
||||
|
||||
set_sys_led_state(SYS_LED_FAST);
|
||||
print_string("\n[Reset button held at boot]\n");
|
||||
|
||||
if (gpio_pin_test(machine.reset_pin))
|
||||
return;
|
||||
|
||||
const __xdata uint32_t min_hold_ticks = 10UL * SYS_TICK_HZ;
|
||||
const __xdata uint32_t max_hold_ticks = 30UL * SYS_TICK_HZ;
|
||||
const __xdata uint32_t blink_ticks = SYS_TICK_HZ / 10; // 100 ms
|
||||
const __xdata uint32_t pause_ticks = SYS_TICK_HZ / 2; // 500 ms
|
||||
__xdata uint32_t start_ticks = ticks;
|
||||
__xdata uint32_t last_blink_step = start_ticks;
|
||||
__xdata uint8_t blink_step = 0;
|
||||
|
||||
set_sys_led_state(SYS_LED_ON);
|
||||
|
||||
while (!gpio_pin_test(machine.reset_pin)) {
|
||||
__xdata uint32_t held_ticks = ticks - start_ticks;
|
||||
|
||||
if (held_ticks > max_hold_ticks) {
|
||||
print_string("[Button held >30s at boot; continuing normal boot]\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Double blink pattern while button is held:
|
||||
// ON (100ms), OFF (100ms), ON (100ms), OFF (500ms)
|
||||
__xdata uint32_t step_ticks = (blink_step == 3) ? pause_ticks : blink_ticks;
|
||||
if ((ticks - last_blink_step) >= step_ticks) {
|
||||
blink_step = (blink_step + 1) & 0x3;
|
||||
set_sys_led_state((blink_step == 0 || blink_step == 2) ? SYS_LED_ON : SYS_LED_OFF);
|
||||
last_blink_step = ticks;
|
||||
}
|
||||
|
||||
PCON |= 1;
|
||||
}
|
||||
|
||||
set_sys_led_state(SYS_LED_ON);
|
||||
|
||||
if ((ticks - start_ticks) >= min_hold_ticks) {
|
||||
print_string("[Button held 10s-30s at boot; restoring default config]\n");
|
||||
set_sys_led_state(SYS_LED_FAST);
|
||||
flash_default_config();
|
||||
delay(3UL * SYS_TICK_HZ);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Configure the SerDes of the SoC for a particular mode
|
||||
* to connect to an SFP module or a PHY
|
||||
* Valid modes are SDS_10GR, SDS_QXGMII, SDS_HISGMII, SDS_HSG, SDS_SGMII and SDS_1000BX_FIBER
|
||||
* The SerDes ID may be 0 or 1 for RTL8272 and 0-2 for RTL8373
|
||||
* SDS_QXGMII is used for 10G Fiber, RTL8224 and RTL8261BE
|
||||
*/
|
||||
void sds_config(uint8_t sds, uint8_t mode) __banked
|
||||
{
|
||||
print_string("sds_config sds: "); print_byte(sds); print_string(", mode: "); print_byte(mode); write_char('\n');
|
||||
sds_config_mac(sds, mode);
|
||||
|
||||
if (mode == SDS_10GR || mode == SDS_QXGMII)
|
||||
sds_write_v(sds, 0x21, 0x10, 0x4480); // Q002110:6480
|
||||
else
|
||||
sds_write_v(sds, 0x21, 0x10, 0x6480); // Q002110:6480
|
||||
sds_write_v(sds, 0x21, 0x13, 0x0400); // Q002113:0400
|
||||
sds_write_v(sds, 0x21, 0x18, 0x6d02); // Q002118:6d02
|
||||
sds_write_v(sds, 0x21, 0x1b, 0x424e); // Q00211b:424e
|
||||
sds_write_v(sds, 0x21, 0x1d, 0x0002); // Q00211d:0002
|
||||
sds_write_v(sds, 0x36, 0x1c, 0x1390); // Q00361c:1390
|
||||
sds_write_v(sds, 0x36, 0x14, 0x003f); // Q003614:003f
|
||||
|
||||
uint8_t page = 0;
|
||||
uint16_t v = 0;
|
||||
|
||||
switch (mode) {
|
||||
case SDS_SGMII:
|
||||
case SDS_1000BX_FIBER:
|
||||
v = 0x0300;
|
||||
page = 0x24;
|
||||
break;
|
||||
case SDS_HISGMII:
|
||||
case SDS_HSG:
|
||||
v = 0x0200;
|
||||
page = 0x28;
|
||||
break;
|
||||
case SDS_10GR:
|
||||
case SDS_QXGMII:
|
||||
v = 0x0200;
|
||||
page = 0x2e;
|
||||
break;
|
||||
case SDS_100FX:
|
||||
v = 0x0200;
|
||||
page = 0x26;
|
||||
break;
|
||||
default:
|
||||
print_string("Error in SDS Mode\n");
|
||||
return;
|
||||
}
|
||||
sds_write_v(sds, 0x36, 0x10, v); // Q003610:0200
|
||||
|
||||
if (page == 0x2e) { // 10G Fiber / SDS_QXGMII
|
||||
sds_write_v(sds, page, 0x04, 0x0080); // Q012e04:0080
|
||||
sds_write_v(sds, page, 0x06, 0x0408); // Q012e06:0408
|
||||
sds_write_v(sds, page, 0x07, 0x020d); // Q012e07:020d
|
||||
sds_write_v(sds, page, 0x09, 0x0601); // Q012e09:0601
|
||||
sds_write_v(sds, page, 0x0b, 0x222c); // Q012e0b:222c
|
||||
sds_write_v(sds, page, 0x0c, 0xa217); // Q012e0c:a217
|
||||
sds_write_v(sds, page, 0x0d, 0xfe40); // Q012e0d:fe40
|
||||
sds_write_v(sds, page, 0x15, 0xf5c1); // Q012e15:f5c1
|
||||
} else {
|
||||
sds_write_v(sds, page, 0x04, 0x0080); // Q002804:0080
|
||||
sds_write_v(sds, page, 0x07, 0x1201); // Q002807:1201
|
||||
sds_write_v(sds, page, 0x09, 0x0601); // Q002809:0601
|
||||
sds_write_v(sds, page, 0x0b, 0x232c); // Q00280b:232c
|
||||
sds_write_v(sds, page, 0x0c, 0x9217); // Q00280c:9217
|
||||
sds_write_v(sds, page, 0x0f, 0x5b50); // Q00280f:5b50
|
||||
sds_write_v(sds, page, 0x15, 0xe7c1); // Q002815:e7f1 BUG !
|
||||
}
|
||||
|
||||
sds_write_v(sds, page, 0x16, 0x0443); // Q002816:0443 / Q012e16:0443
|
||||
sds_write_v(sds, page, 0x1d, 0xabb0); // Q00281d:abb0 / Q012e1d:abb0
|
||||
|
||||
sds_write_v(sds, 0x06, 0x12, 0x5078); // Q000612:5078
|
||||
sds_write_v(sds, 0x07, 0x06, 0x9401); // Q000706:9401
|
||||
sds_write_v(sds, 0x07, 0x08, 0x9401); // Q000708:9401
|
||||
sds_write_v(sds, 0x07, 0x0a, 0x9401); // Q00070a:9401
|
||||
sds_write_v(sds, 0x07, 0x0c, 0x9401); // Q00070c:9401
|
||||
sds_write_v(sds, 0x1f, 0x0b, 0x0003); // Q001f0b:0003
|
||||
sds_write_v(sds, 0x06, 0x03, 0xc45c); // Q000603:c45c
|
||||
|
||||
// RTL8261BE
|
||||
if (machine.n_10g && mode == SDS_QXGMII) {
|
||||
sds_write_v(sds, 0x06, 0x1f, 0x2100); // Q00061f:2100
|
||||
sds_write_v(sds, 0x07, 0x11, 0x054f); // Q000711:054f
|
||||
sds_write_v(sds, 0x20, 0x00, 0x0030); // Q002000:0030
|
||||
sds_write_v(sds, 0x20, 0x00, 0x0010); // Q002000:0010
|
||||
sds_write_v(sds, 0x20, 0x00, 0x0050); // Q002000:0050
|
||||
sds_write_v(sds, 0x20, 0x00, 0x00d0); // Q002000:00d0
|
||||
sds_write_v(sds, 0x20, 0x00, 0x0cd0); // Q002000:0cd0
|
||||
sds_write_v(sds, 0x20, 0x00, 0x04d0); // Q002000:04d0
|
||||
sds_write_v(sds, 0x20, 0x00, 0x04d0); // Q002000:04d0
|
||||
sds_write_v(sds, 0x20, 0x00, 0x0cd0); // Q002000:0cd0
|
||||
sds_write_v(sds, 0x20, 0x00, 0x00d0); // Q002000:00d0
|
||||
sds_write_v(sds, 0x20, 0x00, 0x00d0); // Q002000:00d0
|
||||
sds_write_v(sds, 0x20, 0x00, 0x0050); // Q002000:0050
|
||||
sds_write_v(sds, 0x20, 0x00, 0x0010); // Q002000:0010
|
||||
sds_write_v(sds, 0x20, 0x00, 0x0010); // Q002000:0010
|
||||
sds_write_v(sds, 0x20, 0x00, 0x0030); // Q002000:0030
|
||||
sds_write_v(sds, 0x20, 0x00, 0x0000); // Q002000:0000
|
||||
sds_write_v(sds, 0x1f, 0x00, 0x000b); // Q001f00:000b
|
||||
sds_write_v(sds, 0x1f, 0x00, 0x0000); // Q001f00:0000
|
||||
return;
|
||||
}
|
||||
if (mode != SDS_QXGMII)
|
||||
sds_write_v(sds, 0x06, 0x1f, 0x2100); // Q00061f:2100
|
||||
|
||||
if (mode == SDS_1000BX_FIBER) {
|
||||
sds_write_v(sds, 0x02, 0x04, 0x0020); // Q000204:0020
|
||||
sds_write_v(sds, 0x00, 0x02, 0x73d0); // Q000002:73d0
|
||||
sds_write_v(sds, 0x00, 0x04, 0x074d); // Q000004:074d
|
||||
sds_write_v(sds, 0x20, 0x04, 0x0000); // Q002000:0000
|
||||
sds_write_v(sds, 0x1f, 0x00, 0x0000); // Q001f00:0000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void rtl8224_enable(void) __banked
|
||||
{
|
||||
// Set Pin 4 low
|
||||
reg_bit_clear(RTL837X_REG_GPIO_32_63_OUTPUT, 4);
|
||||
// Configure Pin as output
|
||||
reg_bit_set(RTL837X_REG_GPIO_32_63_DIRECTION, 4);
|
||||
delay(4);
|
||||
// Set pin 4 high
|
||||
reg_bit_set(RTL837X_REG_GPIO_32_63_OUTPUT, 4);
|
||||
rtl8224_release_tick = ticks;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void nic_setup(void) __banked
|
||||
{
|
||||
// Enable NIC
|
||||
// r6040:00000100 R6040-00001100
|
||||
reg_bit_set(RTL837X_REG_HW_CONF, 0xc);
|
||||
|
||||
// This sets the size of the RX buffer, the filling level is in 0x7874
|
||||
// R7848-000004ff
|
||||
REG_SET(RTL837X_REG_NIC_RXBUFF_RX, 0x4ff);
|
||||
|
||||
// R7844-000007fe
|
||||
REG_SET(RTL837X_REG_NIC_BUFFSIZE_TX, 0x7fe);
|
||||
|
||||
// Configure NIC RX to receive various types of packets
|
||||
// RTL837X_REG_RX_CTRL: Set bits 24-31 to 0x4, clear bits 16/17
|
||||
reg_read_m(RTL837X_REG_RX_CTRL);
|
||||
sfr_mask_data(3, 0xff, 0x04);
|
||||
sfr_mask_data(2, 0x03, 0);
|
||||
reg_write_m(RTL837X_REG_RX_CTRL);
|
||||
|
||||
// Enable NIC TX (set bit 0)
|
||||
reg_bit_set(RTL837X_REG_TX_CTRL, 0);
|
||||
|
||||
// Enable NIC RX (set bit 0)
|
||||
reg_bit_set(RTL837X_REG_RX_CTRL, 0);
|
||||
|
||||
// Drop packets with invalid CRC
|
||||
reg_bit_clear(RTL837X_REG_RX_CTRL, 2);
|
||||
|
||||
// R603c-00000200
|
||||
// CPU-port is CPU-Tag aware (bit 9)
|
||||
REG_SET(RTL837X_REG_CPU_TAG_AWARE_PMASK, 0x200);
|
||||
|
||||
// Insert CPU-tag for internally received packets (bit 0), MODE is 0, i.e. ALL packets (bits 8-9)
|
||||
reg_read_m(RTL837X_REG_CPU_TAG);
|
||||
sfr_mask_data(0, 1, 1);
|
||||
sfr_mask_data(1, 3, 0);
|
||||
reg_write_m(RTL837X_REG_CPU_TAG);
|
||||
|
||||
// Force MAC mode of the CPU port (port 9)
|
||||
// r6368:00000194 R6368-00000197
|
||||
reg_read_m(RTL837X_REG_MAC_FORCE_MODE + 9 * 4);
|
||||
sfr_mask_data(0, 0, 3); // Set bits 0, 1: Force link
|
||||
reg_write_m(RTL837X_REG_MAC_FORCE_MODE+ 9 * 4);
|
||||
|
||||
// Sequence number of TX packets
|
||||
tx_seq = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void rtl8373_revision(void) __banked
|
||||
{
|
||||
reg_read_m(RTL837X_REG_CHIP_INFO);
|
||||
sfr_mask_data(2, 0x0a, 0x0a); // Enable reading version
|
||||
reg_write_m(RTL837X_REG_CHIP_INFO);
|
||||
delay(50);
|
||||
|
||||
reg_read_m(RTL837X_REG_CHIP_INFO);
|
||||
print_string("CPU revision: "); print_byte(sfr_data[2]); print_byte(sfr_data[2]); write_char('\n');
|
||||
sfr_mask_data(2, 0x0a, 0x00); // Enable reading version
|
||||
reg_write_m(RTL837X_REG_CHIP_INFO);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* The SoC manages Link-State for steering the LEDs and can set PHY-settings
|
||||
* automatically through Realtek's SMI (Simple Managagement) Interface, a
|
||||
* proprietary version of MDIO which for example allows for more PHYs on the same
|
||||
* bus.
|
||||
* Configure polling via SMI and the interface setup during boot.
|
||||
*/
|
||||
void init_smi(void) __banked
|
||||
{
|
||||
print_string("\ninit_switch called\n");
|
||||
|
||||
/* Set the SMI(i.e.I2C) type for PHY polling, 0b01 is 2.5/10G PHY. Disable (0b00) for the SFP-ports
|
||||
* which are at port 8 and additionally at port 3 for a dual SFP device
|
||||
*/
|
||||
|
||||
// Default: 0x00005555
|
||||
// Workaround for SDCC BUG 4070: SFR_DATA_U32 = 0x00005555;
|
||||
SFR_DATA_U16_UPPER = 0x0000;
|
||||
SFR_DATA_U16 = 0x5555;
|
||||
if (machine.n_10g == 2) {
|
||||
// 0x00015555, only change the bytes that differs from the default.
|
||||
SFR_DATA_16 = 0x01;
|
||||
} else if (machine.n_sfp == 2)
|
||||
// 0x00005515
|
||||
SFR_DATA_0 = 0x15;
|
||||
reg_write(RTL837X_REG_SMI_MAC_TYPE);
|
||||
|
||||
// Configure polling of all PHYs by the MAC to detect link-state changes
|
||||
// Default: 0x000000ff
|
||||
// Workaround for SDCC BUG 4070: SFR_DATA_U32 = 0x000000ff;
|
||||
SFR_DATA_U16_UPPER = 0x0000;
|
||||
SFR_DATA_U16 = 0x00ff;
|
||||
if (!machine_detected.isRTL8373) {
|
||||
if (machine.n_sfp == 2) {
|
||||
// 0x000000f0, only change the bytes that differs from the default.
|
||||
SFR_DATA_0 = 0xf0;
|
||||
} else {
|
||||
// 0x000001f8, only change the bytes that differs from the default.
|
||||
SFR_DATA_8 = 0x01;
|
||||
SFR_DATA_0 = 0xf8;
|
||||
}
|
||||
}
|
||||
reg_write(RTL837X_REG_SMI_PORT_POLLING);
|
||||
// Enable MDC
|
||||
reg_read_m(RTL837X_REG_SMI_CTRL);
|
||||
sfr_mask_data(1, 0, 0x70); // Set bits 12-14 to enable MDC for SMI0-SMI2
|
||||
reg_write_m(RTL837X_REG_SMI_CTRL);
|
||||
delay(50);
|
||||
|
||||
if (!machine_detected.isRTL8373) {
|
||||
// Change I2C addresses for SMI of the non-existent PHYs
|
||||
// r6450:000020e6 R6450-000000e6
|
||||
reg_read_m(RTL837X_REG_SMI_PORT6_9_ADDR);
|
||||
sfr_mask_data(1, 0x7c, 0);
|
||||
reg_write_m(RTL837X_REG_SMI_PORT6_9_ADDR);
|
||||
|
||||
// r644c:0a418820 R644c-0a400820
|
||||
reg_read_m(RTL837X_REG_SMI_PORT0_5_ADDR);
|
||||
sfr_mask_data(2, 0x0f, 0);
|
||||
sfr_mask_data(1, 0x80, 0);
|
||||
reg_write_m(RTL837X_REG_SMI_PORT0_5_ADDR);
|
||||
}
|
||||
|
||||
if (machine.n_10g == 2) {
|
||||
// Set address of second external PHY on port 8
|
||||
REG_SET(RTL837X_REG_SMI_PORT6_9_ADDR, 0x000040e6);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void setup_i2c(void) __banked
|
||||
{
|
||||
REG_SET(RTL837X_REG_I2C_MST_IF_CTRL, 0);
|
||||
// Configure SFP EEPROM address (0x50) as I2C device address
|
||||
// Configure SFP readings address (0x51) as I2C device address
|
||||
REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16), 0x50 >> 5, (0x50 << 3) & 0xff);
|
||||
|
||||
REG_SET(RTL837X_REG_I2C_CTRL2, 0);
|
||||
|
||||
// HW Control register, enable I2C depending on PIN configuration
|
||||
reg_read_m(RTL837X_PIN_MUX_1);
|
||||
for (uint8_t sfp = 0; sfp < machine.n_sfp; sfp++) {
|
||||
uint8_t i2c = machine.sfp_port[sfp].i2c;
|
||||
uint8_t scl_bus = (i2c >> RTL837X_REG_I2C_SCL_SHIFT) & RTL837X_REG_I2C_SCL_MASK;
|
||||
uint8_t sda_bus = (i2c >> RTL837X_REG_I2C_SDA_SHIFT) & RTL837X_REG_I2C_SDA_MASK;
|
||||
print_string("Configuring I2C for SFP idx="); print_byte(sfp); print_string(" SCL="); print_byte(scl_bus); print_string(", SDA="); print_byte(sda_bus); write_char('\n');
|
||||
// `& RTL837X_REG_I2C_SCL_MASK` is needed to silens a compiler warning 110.
|
||||
switch (scl_bus & RTL837X_REG_I2C_SCL_MASK) {
|
||||
case 3:
|
||||
// Bit 5-6 0b10 -> SCL (implies enabled SDA on bus 3)
|
||||
sfr_mask_data(0, 0x60, 0x40);
|
||||
break;
|
||||
case 2:
|
||||
// Bit 15-16 0b01 -> SCL
|
||||
sfr_mask_data(1, 0x80, 0x80);
|
||||
sfr_mask_data(2, 0x01, 0x00);
|
||||
break;
|
||||
case 1:
|
||||
// Bit 11-12 0b01 -> SCL
|
||||
sfr_mask_data(1, 0x18, 0x08);
|
||||
break;
|
||||
case 0:
|
||||
// Bit 7-8 0b01 -> SCL
|
||||
sfr_mask_data(0, 0x80, 0x80);
|
||||
sfr_mask_data(1, 0x01, 0x00);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (sda_bus) {
|
||||
case 4:
|
||||
// Bit 29 0b0 -> SDA
|
||||
sfr_mask_data(3, 0x20, 0x00);
|
||||
break;
|
||||
case 3:
|
||||
// Bit 5-6 0b10 -> SDA (implies enabled SCL on bus 3)
|
||||
sfr_mask_data(0, 0x60, 0x40);
|
||||
break;
|
||||
case 2:
|
||||
// Bit 17-18 0b01 -> SDA
|
||||
sfr_mask_data(2, 0x06, 0x02);
|
||||
break;
|
||||
case 1:
|
||||
// Bit 13-14 0b01 -> SDA
|
||||
sfr_mask_data(1, 0x60, 0x20);
|
||||
break;
|
||||
case 0:
|
||||
// Bit 9-10 0b01 -> SDA
|
||||
sfr_mask_data(1, 0x06, 0x02);
|
||||
break;
|
||||
}
|
||||
}
|
||||
reg_write_m(RTL837X_PIN_MUX_1);
|
||||
}
|
||||
|
||||
|
||||
/* Give the switch a name carrying the tail of its MAC, so several of them on
|
||||
* one network are distinguishable out of the box. Called after the startup
|
||||
* config has been replayed and returns at once if that config already set a
|
||||
* name, so a configured switch does no work for it (suggested in review).
|
||||
*
|
||||
* Written without a loop on purpose. Locals - counters and pointers alike -
|
||||
* land in the 8051's internal-RAM overlay, and on an image with LACP and STP
|
||||
* both enabled that overlay is exhausted: a loop here makes the linker fail
|
||||
* with "Could not get 8 consecutive bytes in internal RAM for area OSEG".
|
||||
* Moving the code into its own function does not help; the overlay is shared
|
||||
* across the whole image. Hoisting the locals to xdata does not help either,
|
||||
* because itohex() is inline and brings its own frame. */
|
||||
void set_hostname_default(void) __banked
|
||||
{
|
||||
if (hostname[0] != NUL)
|
||||
return;
|
||||
|
||||
strcpy((__xdata uint8_t *)hostname, "RTLPlayground-");
|
||||
hostname[14] = hex[uip_ethaddr.addr[3] >> 4];
|
||||
hostname[15] = hex[uip_ethaddr.addr[3] & 0xf];
|
||||
hostname[16] = hex[uip_ethaddr.addr[4] >> 4];
|
||||
hostname[17] = hex[uip_ethaddr.addr[4] & 0xf];
|
||||
hostname[18] = hex[uip_ethaddr.addr[5] >> 4];
|
||||
hostname[19] = hex[uip_ethaddr.addr[5] & 0xf];
|
||||
hostname[20] = NUL;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef BOOT_H
|
||||
#define BOOT_H
|
||||
|
||||
void sds_config_mac(uint8_t sds, uint8_t mode) __banked;
|
||||
void sds_config(uint8_t sds, uint8_t mode) __banked;
|
||||
void early_boot_handle_button(void) __banked;
|
||||
void rtl8224_enable(void) __banked;
|
||||
void nic_setup(void) __banked;
|
||||
void rtl8373_revision(void) __banked;
|
||||
void init_smi(void) __banked;
|
||||
void setup_i2c(void) __banked;
|
||||
void set_hostname_default(void) __banked;
|
||||
|
||||
#endif
|
||||
@@ -6,14 +6,42 @@
|
||||
#include "rtl837x_common.h"
|
||||
|
||||
extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
|
||||
extern __xdata uint8_t cmd_available;
|
||||
extern __bit __at(0x03) cmd_available;
|
||||
extern __xdata uint8_t err_status;
|
||||
|
||||
/* Health instrumentation, built with HEALTH=1. Without it the hooks below
|
||||
* compile to nothing and the "health" command does not exist. */
|
||||
#ifdef HEALTH
|
||||
/* Phase ids, in main-loop order */
|
||||
#define HEALTH_PH_LINK 0
|
||||
#define HEALTH_PH_SFP 1
|
||||
#define HEALTH_PH_RX 2
|
||||
#define HEALTH_PH_TX 3
|
||||
#define HEALTH_PH_STP 4
|
||||
#define HEALTH_PH_CMD 5
|
||||
#define HEALTH_PHASES 6
|
||||
#define HEALTH_LLDP 7
|
||||
|
||||
extern __xdata uint16_t health_rx_frames;
|
||||
#define health_rx_frame() health_rx_frames++
|
||||
|
||||
void health_loop_start(void) __banked; /* once per idle() pass, resets the phase baseline */
|
||||
void health_phase(uint8_t id) __banked; /* after each phase; accounts ticks since last checkpoint */
|
||||
void health_stack_paint(void) __banked; /* paint the unused stack area once at boot */
|
||||
void health_show(void) __banked; /* "health" CLI dump */
|
||||
#else
|
||||
#define health_rx_frame()
|
||||
#define health_loop_start()
|
||||
#define health_phase(id)
|
||||
#define health_stack_paint()
|
||||
#endif
|
||||
|
||||
void cmd_tokenize(void) __banked;
|
||||
void cmd_parser(void) __banked;
|
||||
void execute_config(void) __banked;
|
||||
void execute_commands(__xdata uint8_t *p) __banked;
|
||||
void print_ip(__xdata uint8_t *ptr);
|
||||
void print_ip(__xdata uint8_t *ptr) __banked;
|
||||
void print_mac(__xdata uint8_t *ptr) __banked;
|
||||
void print_sw_version(void) __banked;
|
||||
void clear_command_history(void) __banked;
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
;
|
||||
.globl _crc_value
|
||||
.globl _crc16
|
||||
.globl _crc16_bank1
|
||||
.globl __sdcc_banked_ret
|
||||
.equ BANK, 0x96
|
||||
; .equ DPS, 0x86
|
||||
; Variable in XMEM holding current CRC16 value, being updated
|
||||
@@ -18,7 +20,7 @@ _crc_value::
|
||||
.area HOME (CODE)
|
||||
.area CSEG (CODE)
|
||||
; .area BANK1 (CODE)
|
||||
_crc16:
|
||||
_crc16_bank1:
|
||||
mov BANK, #1
|
||||
push dph
|
||||
push dpl
|
||||
@@ -52,6 +54,10 @@ _crc16:
|
||||
|
||||
.area BANK1 (CODE)
|
||||
|
||||
_crc16:
|
||||
lcall _crc16_bank1
|
||||
ljmp __sdcc_banked_ret
|
||||
|
||||
crc16_table_l:
|
||||
.byte #0x00, #0xc1, #0x81, #0x40, #0x01, #0xc0, #0x80, #0x41
|
||||
.byte #0x01, #0xc0, #0x80, #0x41, #0x00, #0xc1, #0x81, #0x40
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef _CHACHA_H_
|
||||
#define _CHACHA_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct chacha20_t {
|
||||
uint8_t wstate[64]; // 64 bytes written here
|
||||
uint8_t constant[16]; // 128 bit constant
|
||||
uint8_t key[32]; // 256-bit secret key
|
||||
uint32_t cnt; // 32-bit block counter 1, 2.. Big Endian!
|
||||
uint8_t nonce[12]; // 96-bit nonce
|
||||
__xdata uint8_t *plaintext;
|
||||
uint16_t length;
|
||||
__xdata uint8_t *cyphertext;
|
||||
};
|
||||
|
||||
// Encrypt a plaintext with ChaCha20 as per RFC7539
|
||||
void chacha20_encrypt(void);
|
||||
|
||||
// Test ChaCha20 using the example from RFC7539
|
||||
void chacha20_test(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,103 @@
|
||||
#include "rtl837x_common.h"
|
||||
#include "chacha.h"
|
||||
|
||||
void chacha_20(void);
|
||||
void chacha_update(void);
|
||||
void chacha_count(void);
|
||||
|
||||
// generate a block of ChaCha20 keystream as per RFC7539
|
||||
|
||||
|
||||
__xdata struct chacha20_t __at(0xbf00) chacha20;
|
||||
|
||||
#ifdef CHACHA20_TEST
|
||||
__xdata uint8_t plaintext[256];
|
||||
__xdata uint8_t cyphertext[256];
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
void chacha20_print_block(void)
|
||||
{
|
||||
for (uint8_t i=0; i < 64; i++) {
|
||||
print_byte(*(uint8_t * __xdata)(chacha20.wstate + i));
|
||||
if (i%4 == 3)
|
||||
write_char(' ');
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void chacha20_encrypt(void)
|
||||
{
|
||||
__xdata uint8_t *p = chacha20.plaintext;
|
||||
|
||||
while (chacha20.length) {
|
||||
register uint8_t i;
|
||||
chacha_count();
|
||||
memcpy(chacha20.wstate, chacha20.wstate + 64, 64);
|
||||
#ifdef DEBUG
|
||||
chacha20_print_block(); write_char('\n');
|
||||
#endif
|
||||
chacha_20();
|
||||
chacha_update();
|
||||
#ifdef DEBUG
|
||||
chacha20_print_block(); write_char('\n');
|
||||
#endif
|
||||
for (i = 0; i < ((chacha20.length > 64) ? 64 : chacha20.length); i++)
|
||||
*chacha20.cyphertext++ = *p++ ^ chacha20.wstate[i];
|
||||
chacha20.length -= chacha20.length > 64 ? 64 : chacha20.length;
|
||||
#ifdef DEBUG
|
||||
print_string("\n round done\n");
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
#ifdef CHACHA20_TEST
|
||||
// Test the example of RFC 7539 Section 2.4.2
|
||||
void chacha20_test(void)
|
||||
{
|
||||
__code uint8_t chacha_c[16] =
|
||||
{ 0x61, 0x70, 0x78, 0x65, 0x33, 0x20, 0x64, 0x6e,
|
||||
0x79, 0x62, 0x2d, 0x32, 0x6b, 0x20, 0x65, 0x74 };
|
||||
|
||||
__code uint8_t key[32] =
|
||||
{ 0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04,
|
||||
0x0b, 0x0a, 0x09, 0x08, 0x0f, 0x0e, 0x0d, 0x0c,
|
||||
0x13, 0x12, 0x11, 0x10, 0x17, 0x16, 0x15, 0x14,
|
||||
0x1b, 0x1a, 0x19, 0x18, 0x1f, 0x1e, 0x1d, 0x1c };
|
||||
|
||||
__code uint8_t nonce[12] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
__code uint8_t sunscreen[] = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for " \
|
||||
"the future, sunscreen would be it.";
|
||||
|
||||
/*
|
||||
Ciphertext Sunscreen (from RFC 7539 Section 2.4.2):
|
||||
000 6e 2e 35 9a 25 68 f9 80 41 ba 07 28 dd 0d 69 81 n.5.%h..A..(..i.
|
||||
016 e9 7e 7a ec 1d 43 60 c2 0a 27 af cc fd 9f ae 0b .~z..C`..'......
|
||||
032 f9 1b 65 c5 52 47 33 ab 8f 59 3d ab cd 62 b3 57 ..e.RG3..Y=..b.W
|
||||
048 16 39 d6 24 e6 51 52 ab 8f 53 0c 35 9f 08 61 d8 .9.$.QR..S.5..a.
|
||||
064 07 ca 0d bf 50 0d 6a 61 56 a3 8e 08 8a 22 b6 5e ....P.jaV....".^
|
||||
080 52 bc 51 4d 16 cc f8 06 81 8c e9 1a b7 79 37 36 R.QM.........y76
|
||||
096 5a f9 0b bf 74 a3 5b e6 b4 0b 8e ed f2 78 5e 42 Z...t.[......x^B
|
||||
112 87 4d
|
||||
*/
|
||||
strcpy(plaintext, sunscreen);
|
||||
memcpyc(chacha20.constant, chacha_c, 16);
|
||||
memcpyc(chacha20.key, key, 32);
|
||||
chacha20.cnt = 0;
|
||||
memcpyc(chacha20.nonce, nonce, 12);
|
||||
chacha20.plaintext = plaintext;
|
||||
chacha20.length = strlen_x(plaintext);
|
||||
chacha20.cyphertext = cyphertext;
|
||||
|
||||
print_string("Encrypting...\n");
|
||||
chacha20_encrypt();
|
||||
print_string("Cypertext:\n");
|
||||
uint16_t len = strlen_x(plaintext);
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
print_byte(cyphertext[i]); write_char(' ');
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,455 @@
|
||||
; ChaCha Quater-Round implementation in Assembler
|
||||
.module chacha_8051
|
||||
; Global variables:
|
||||
.globl _chacha_20
|
||||
.globl _chacha_update
|
||||
.globl _chacha_count
|
||||
|
||||
;#define CHACHA_QR(A, B, C, D) { \
|
||||
; A += B; D ^= A; D = ROTL32(D, 16); \
|
||||
; C += D; B ^= C; B = ROTL32(B, 12); \
|
||||
; A += B; D ^= A; D = ROTL32(D, 8); \
|
||||
; C += D; B ^= C; B = ROTL32(B, 7); \
|
||||
;}
|
||||
|
||||
; r0 r1 r2 r3
|
||||
; CHACHA_QR( v[ 0], v[ 4], v[ 8], v[12] );
|
||||
|
||||
.area HOME (CODE)
|
||||
|
||||
store_32:
|
||||
ar7 = 0x07
|
||||
ar6 = 0x06
|
||||
ar5 = 0x05
|
||||
ar4 = 0x04
|
||||
ar3 = 0x03
|
||||
ar2 = 0x02
|
||||
ar1 = 0x01
|
||||
ar0 = 0x00
|
||||
|
||||
mov a, r7
|
||||
movx @dptr, a
|
||||
dec dpl
|
||||
mov a, r6
|
||||
movx @dptr, a
|
||||
dec dpl
|
||||
mov a, r5
|
||||
movx @dptr, a
|
||||
dec dpl
|
||||
mov a, r4
|
||||
movx @dptr, a
|
||||
ret
|
||||
|
||||
rol_b:
|
||||
clr c
|
||||
mov a, r7
|
||||
rlc a
|
||||
mov r7, a
|
||||
mov a, r6
|
||||
rlc a
|
||||
mov r6, a
|
||||
mov a, r5
|
||||
rlc a
|
||||
mov r5, a
|
||||
mov a, r4
|
||||
rlc a
|
||||
mov r4, a
|
||||
clr a
|
||||
addc a, r7
|
||||
mov r7, a
|
||||
djnz dpl, rol_b
|
||||
ret
|
||||
|
||||
|
||||
chacha_plus_xor:
|
||||
; Load A into registers r4-r7, A pointed to by r0
|
||||
mov dptr, #_chacha20
|
||||
mov dpl, r0
|
||||
movx a, @dptr
|
||||
mov r7, a
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
mov r6, a
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
mov r5, a
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
mov r4, a
|
||||
|
||||
; A += B, B pointed to by r1
|
||||
mov dpl, r1
|
||||
movx a, @dptr
|
||||
add a, r7
|
||||
mov r7, a
|
||||
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
addc a, r6
|
||||
mov r6, a
|
||||
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
addc a, r5
|
||||
mov r5, a
|
||||
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
addc a, r4
|
||||
mov r4, a
|
||||
|
||||
mov dpl, r0 ; Store A back
|
||||
mov a, r7
|
||||
movx @dptr, a
|
||||
dec dpl
|
||||
mov a, r6
|
||||
movx @dptr, a
|
||||
dec dpl
|
||||
mov a, r5
|
||||
movx @dptr, a
|
||||
dec dpl
|
||||
mov a, r4
|
||||
movx @dptr, a
|
||||
|
||||
; D ^= A
|
||||
mov dpl, r3
|
||||
movx a, @dptr
|
||||
xrl a, r7
|
||||
mov r7, a
|
||||
dec dpl
|
||||
|
||||
movx a, @dptr
|
||||
xrl a, r6
|
||||
mov r6, a
|
||||
dec dpl
|
||||
|
||||
movx a, @dptr
|
||||
xrl a, r5
|
||||
mov r5, a
|
||||
dec dpl
|
||||
|
||||
movx a, @dptr
|
||||
xrl a, r4
|
||||
mov r4, a
|
||||
dec dpl
|
||||
ret
|
||||
|
||||
chacha_qr:
|
||||
; QR Part: A += B; D ^= A; D = ROTL32(D, 16);
|
||||
acall chacha_plus_xor
|
||||
|
||||
; rotate left 16. D is r4, r5, r6, r7 -> r6, r7, r4, r5
|
||||
mov a, r6
|
||||
xch a, r4
|
||||
mov r6, a
|
||||
mov a, r7
|
||||
xch a, r5
|
||||
mov r7, a
|
||||
mov dpl, r3 ; Store D
|
||||
acall store_32
|
||||
|
||||
;QR Part: C += D; B ^= C; B = ROTL32(B, 12);
|
||||
; Swap A <-> C and D <-> B
|
||||
mov a, r0
|
||||
xch a, r2
|
||||
mov r0, a
|
||||
mov a, r1
|
||||
xch a, r3
|
||||
mov r1, a
|
||||
acall chacha_plus_xor
|
||||
|
||||
; rotate left 12. D is r4, r5, r6, r7 -> r5, r6, r7, r4
|
||||
mov a, r4
|
||||
xch a, r7
|
||||
xch a, r6
|
||||
xch a, r5
|
||||
mov r4, a
|
||||
|
||||
mov dpl, #4
|
||||
acall rol_b
|
||||
mov dpl, r3 ; Store D (being B)
|
||||
acall store_32
|
||||
|
||||
; Swap A <-> C and D <-> B
|
||||
mov a, r0
|
||||
xch a, r2
|
||||
mov r0, a
|
||||
mov a, r1
|
||||
xch a, r3
|
||||
mov r1, a
|
||||
|
||||
; QR Part A += B; D ^= A; D = ROTL32(D, 8);
|
||||
acall chacha_plus_xor
|
||||
mov a, r4
|
||||
xch a, r7
|
||||
xch a, r6
|
||||
xch a, r5
|
||||
mov r4, a
|
||||
mov dpl, r3 ; Store D
|
||||
acall store_32
|
||||
|
||||
; QR Part
|
||||
; C += D; B ^= C; B = ROTL32(B, 7);
|
||||
line4:
|
||||
; Swap A <-> C and D <-> B
|
||||
mov a, r0
|
||||
xch a, r2
|
||||
mov r0, a
|
||||
mov a, r1
|
||||
xch a, r3
|
||||
mov r1, a
|
||||
acall chacha_plus_xor
|
||||
|
||||
; Roll left 7 bits, start by rolling 8 bits left, then roll 1 to the right
|
||||
mov a, r4
|
||||
xch a, r7
|
||||
xch a, r6
|
||||
xch a, r5
|
||||
mov r4, a
|
||||
|
||||
clr c
|
||||
mov a, r4
|
||||
rrc a
|
||||
mov r4, a
|
||||
mov a, r5
|
||||
rrc a
|
||||
mov r5, a
|
||||
mov a, r6
|
||||
rrc a
|
||||
mov r6, a
|
||||
mov a, r7
|
||||
rrc a
|
||||
mov r7, a
|
||||
clr a
|
||||
rrc a
|
||||
add a, r4
|
||||
mov r4, a
|
||||
|
||||
; Swap A <-> C and D <-> B
|
||||
mov a, r0
|
||||
xch a, r2
|
||||
mov r0, a
|
||||
mov a, r1
|
||||
xch a, r3
|
||||
mov r1, a
|
||||
mov dpl, r1 ; Store B
|
||||
acall store_32
|
||||
ret
|
||||
|
||||
|
||||
;
|
||||
; Implementation of 20 ChaCha Rounds (10 Double Rounds)
|
||||
;
|
||||
_chacha_20:
|
||||
push acc
|
||||
push b
|
||||
push dpl
|
||||
push dph
|
||||
push ar7
|
||||
push ar6
|
||||
push ar5
|
||||
push ar4
|
||||
push ar3
|
||||
push ar2
|
||||
push ar1
|
||||
push ar0
|
||||
push psw
|
||||
|
||||
mov b, #10 ; 10 Double rounds
|
||||
chacha_20_loop:
|
||||
; CHACHA_QR( v[ 0], v[ 4], v[ 8], v[12] );
|
||||
mov r0, #3
|
||||
mov r1, #19
|
||||
mov r2, #35
|
||||
mov r3, #51
|
||||
acall chacha_qr
|
||||
|
||||
; CHACHA_QR( v[ 1], v[ 5], v[ 9], v[13] ); 7 23 39 55
|
||||
mov r0, #7
|
||||
mov r1, #23
|
||||
mov r2, #39
|
||||
mov r3, #55
|
||||
acall chacha_qr
|
||||
|
||||
; CHACHA_QR( v[ 2], v[ 6], v[10], v[14] ); 11 27 43 59
|
||||
mov r0, #11
|
||||
mov r1, #27
|
||||
mov r2, #43
|
||||
mov r3, #59
|
||||
acall chacha_qr
|
||||
|
||||
; CHACHA_QR( v[ 3], v[ 7], v[11], v[15] ); 15 31 47 63
|
||||
mov r0, #15
|
||||
mov r1, #31
|
||||
mov r2, #47
|
||||
mov r3, #63
|
||||
acall chacha_qr
|
||||
|
||||
; CHACHA_QR( v[ 0], v[ 5], v[10], v[15] ); 3 23 43 63
|
||||
mov r0, #3
|
||||
mov r1, #23
|
||||
mov r2, #43
|
||||
mov r3, #63
|
||||
acall chacha_qr
|
||||
|
||||
; CHACHA_QR( v[ 1], v[ 6], v[11], v[12] ); 7 27 47 51
|
||||
mov r0, #7
|
||||
mov r1, #27
|
||||
mov r2, #47
|
||||
mov r3, #51
|
||||
acall chacha_qr
|
||||
|
||||
; CHACHA_QR( v[ 2], v[ 7], v[ 8], v[13] ); 11 31 35 55
|
||||
mov r0, #11
|
||||
mov r1, #31
|
||||
mov r2, #35
|
||||
mov r3, #55
|
||||
acall chacha_qr
|
||||
|
||||
; CHACHA_QR( v[ 3], v[ 4], v[ 9], v[14] ); 15 19 39 59
|
||||
mov r0, #15
|
||||
mov r1, #19
|
||||
mov r2, #39
|
||||
mov r3, #59
|
||||
acall chacha_qr
|
||||
djnz b, chacha_20_loop
|
||||
|
||||
pop psw
|
||||
pop ar0
|
||||
pop ar1
|
||||
pop ar2
|
||||
pop ar3
|
||||
pop ar4
|
||||
pop ar5
|
||||
pop ar6
|
||||
pop ar7
|
||||
pop dph
|
||||
pop dpl
|
||||
pop b
|
||||
pop acc
|
||||
ret
|
||||
|
||||
;
|
||||
; Update ChaCHa state
|
||||
;
|
||||
_chacha_update:
|
||||
push acc
|
||||
push b
|
||||
push dpl
|
||||
push dph
|
||||
push ar4
|
||||
push ar3
|
||||
push ar2
|
||||
push ar1
|
||||
push ar0
|
||||
push psw
|
||||
|
||||
mov dptr, #_chacha20
|
||||
mov b, #16 ; 16 uint32
|
||||
update_loop:
|
||||
mov a, b ; multiply by 4 and subtract 1
|
||||
rl a
|
||||
rl a
|
||||
dec a
|
||||
; Load Working State variable into registers r0-r3
|
||||
mov dpl, a
|
||||
|
||||
orl a, #64 ; Save pointer to state
|
||||
mov r4, a
|
||||
|
||||
movx a, @dptr
|
||||
mov r3, a
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
mov r2, a
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
mov r1, a
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
mov r0, a
|
||||
|
||||
; Add to State variable
|
||||
mov a, r4
|
||||
mov dpl, a
|
||||
movx a, @dptr
|
||||
add a, r3
|
||||
mov r3, a
|
||||
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
addc a, r2
|
||||
mov r2, a
|
||||
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
addc a, r1
|
||||
mov r1, a
|
||||
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
addc a, r0
|
||||
mov r0, a
|
||||
|
||||
; Store back state variable in LSB, first sequence (serialized)
|
||||
mov a, r4
|
||||
xrl a, #64 ; Save pointer to working state
|
||||
mov dpl, a
|
||||
mov a, r0
|
||||
movx @dptr, a
|
||||
dec dpl
|
||||
mov a, r1
|
||||
movx @dptr, a
|
||||
dec dpl
|
||||
mov a, r2
|
||||
movx @dptr, a
|
||||
dec dpl
|
||||
mov a, r3
|
||||
movx @dptr, a
|
||||
|
||||
djnz b, update_loop
|
||||
|
||||
pop psw
|
||||
pop ar0
|
||||
pop ar1
|
||||
pop ar2
|
||||
pop ar3
|
||||
pop ar4
|
||||
pop dph
|
||||
pop dpl
|
||||
pop b
|
||||
pop acc
|
||||
ret
|
||||
|
||||
;
|
||||
; Increase counter in state
|
||||
;
|
||||
_chacha_count:
|
||||
push acc
|
||||
push dpl
|
||||
push dph
|
||||
mov dptr, #_chacha20
|
||||
mov dpl, #112 + 3
|
||||
movx a, @dptr
|
||||
inc a
|
||||
movx @dptr, a
|
||||
jnz chacha_count_done
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
inc a
|
||||
movx @dptr, a
|
||||
jnz chacha_count_done
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
inc a
|
||||
movx @dptr, a
|
||||
jnz chacha_count_done
|
||||
dec dpl
|
||||
movx a, @dptr
|
||||
inc a
|
||||
movx @dptr, a
|
||||
chacha_count_done:
|
||||
pop dph
|
||||
pop dpl
|
||||
pop acc
|
||||
ret
|
||||
@@ -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 {
|
||||
@@ -139,7 +139,6 @@ void dhcp_addopt_request_ip(void)
|
||||
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.current_ip[1];
|
||||
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.current_ip[2];
|
||||
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.current_ip[3];
|
||||
memcpy(&DHCP_OPT[dhcp_state.opt_ptr], uip_ethaddr.addr, 4);
|
||||
}
|
||||
|
||||
|
||||
@@ -151,13 +150,11 @@ void dhcp_addopt_server_id(void)
|
||||
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.server[1];
|
||||
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.server[2];
|
||||
DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.server[3];
|
||||
memcpy(&DHCP_OPT[dhcp_state.opt_ptr], uip_ethaddr.addr, 4);
|
||||
}
|
||||
|
||||
|
||||
void dhcp_send_discover(void)
|
||||
{
|
||||
print_string("dhcp_send_discover called\n");
|
||||
dhcp_prepare_request();
|
||||
|
||||
dhcp_state.opt_ptr = 0;
|
||||
@@ -193,7 +190,6 @@ void dhcp_send_discover(void)
|
||||
|
||||
void dhcp_send_request(void)
|
||||
{
|
||||
print_string("dhcp_send_request called\n");
|
||||
dhcp_prepare_request();
|
||||
|
||||
dhcp_state.opt_ptr = 0;
|
||||
@@ -232,19 +228,21 @@ void ip_opt(__xdata uint8_t * ip)
|
||||
{
|
||||
dhcp_state.opt_ptr++;
|
||||
uint8_t len = DHCP_OPT[dhcp_state.opt_ptr++];
|
||||
*ip++ = DHCP_OPT[dhcp_state.opt_ptr++];
|
||||
*ip++ = DHCP_OPT[dhcp_state.opt_ptr++];
|
||||
*ip++ = DHCP_OPT[dhcp_state.opt_ptr++];
|
||||
*ip++ = DHCP_OPT[dhcp_state.opt_ptr++];
|
||||
if (len >= 4)
|
||||
memcpy(ip, &DHCP_OPT[dhcp_state.opt_ptr], 4);
|
||||
// There may be more than one IP option, such as 2 DNS servers advertised
|
||||
dhcp_state.opt_ptr += len - 4;
|
||||
dhcp_state.opt_ptr += len;
|
||||
}
|
||||
|
||||
|
||||
void long_opt(void)
|
||||
uint8_t long_opt(void)
|
||||
{
|
||||
dhcp_state.opt_ptr++;
|
||||
dhcp_state.opt_ptr++;
|
||||
uint8_t len = DHCP_OPT[dhcp_state.opt_ptr++];
|
||||
if (len < 4) {
|
||||
dhcp_state.opt_ptr += len;
|
||||
return 0;
|
||||
}
|
||||
long_value = DHCP_OPT[dhcp_state.opt_ptr++];
|
||||
long_value <<= 8;
|
||||
long_value |= DHCP_OPT[dhcp_state.opt_ptr++];
|
||||
@@ -252,6 +250,8 @@ void long_opt(void)
|
||||
long_value |= DHCP_OPT[dhcp_state.opt_ptr++];
|
||||
long_value <<= 8;
|
||||
long_value |= DHCP_OPT[dhcp_state.opt_ptr++];
|
||||
dhcp_state.opt_ptr += len - 4;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -275,16 +275,16 @@ void parse_opts(void)
|
||||
ip_opt(&dhcp_state.broadcast[0]);
|
||||
break;
|
||||
case DHCP_LEASE:
|
||||
long_opt();
|
||||
dhcp_state.lease = long_value;
|
||||
if (long_opt())
|
||||
dhcp_state.lease = long_value;
|
||||
break;
|
||||
case DHCP_REBIND:
|
||||
long_opt();
|
||||
dhcp_state.rebind = long_value;
|
||||
if (long_opt())
|
||||
dhcp_state.rebind = long_value;
|
||||
break;
|
||||
case DHCP_RENEWAL:
|
||||
long_opt();
|
||||
dhcp_state.renewal = long_value;
|
||||
if (long_opt())
|
||||
dhcp_state.renewal = long_value;
|
||||
break;
|
||||
case DHCP_END:
|
||||
break;
|
||||
@@ -332,6 +332,18 @@ void parse_dhcp(void)
|
||||
uip_ipaddr(&uip_hostaddr, dhcp_state.current_ip[0], dhcp_state.current_ip[1], dhcp_state.current_ip[2], dhcp_state.current_ip[3]);
|
||||
uip_ipaddr(&uip_draddr, dhcp_state.router[0], dhcp_state.router[1], dhcp_state.router[2], dhcp_state.router[3]);
|
||||
uip_ipaddr(&uip_netmask, dhcp_state.subnet[0], dhcp_state.subnet[1], dhcp_state.subnet[2], dhcp_state.subnet[3]);
|
||||
/* T1 is optional. Without it the renewal time stays zero, which
|
||||
* makes dhcp_timer zero, and the callback then sends a REQUEST on
|
||||
* every pass through LEASING: client and server trade REQUEST and
|
||||
* ACK as fast as the round trip allows. RFC 2131 has the client
|
||||
* renew at half the lease when the server does not say, and if
|
||||
* there is no lease either, pick something rather than zero. Only
|
||||
* filled in when absent, so a server that asks for a short renewal
|
||||
* still gets it. */
|
||||
if (!dhcp_state.renewal)
|
||||
dhcp_state.renewal = dhcp_state.lease / 2;
|
||||
if (!dhcp_state.renewal)
|
||||
dhcp_state.renewal = 60;
|
||||
dhcp_state.state = DHCP_LEASING;
|
||||
dhcp_state.ticks = SYS_TICK_HZ;
|
||||
dhcp_state.dhcp_timer = dhcp_state.renewal > 0xffff ? 0xffff : dhcp_state.renewal;
|
||||
@@ -341,6 +353,12 @@ void parse_dhcp(void)
|
||||
|
||||
void dhcp_start(void) __banked
|
||||
{
|
||||
/* Starting twice would leave the first socket bound: uip_udp_new() takes
|
||||
* the next free slot while the old one keeps port 68, and dhcp_callback()
|
||||
* matches on that port rather than on the connection, so every leftover
|
||||
* runs the state machine too and the timers count down once per socket. */
|
||||
if (dhcp_state.state != DHCP_OFF)
|
||||
dhcp_stop();
|
||||
uip_ipaddr(server, 255,255,255,255);
|
||||
dhcp_state.conn = uip_udp_new(&server, HTONS(DHCPC_SERVER_PORT));
|
||||
dhcp_state.current_ip[0] = dhcp_state.current_ip[1] = dhcp_state.current_ip[2] = dhcp_state.current_ip[3] = 0;
|
||||
@@ -358,13 +376,11 @@ void dhcp_start(void) __banked
|
||||
*tid++ = SFR_DATA_8;
|
||||
*tid = SFR_DATA_0;
|
||||
dhcp_state.state = DHCP_START;
|
||||
print_string("dhcp_start done\n");
|
||||
}
|
||||
|
||||
|
||||
void dhcp_stop(void) __banked
|
||||
{
|
||||
print_string("dhcp_stop called\n");
|
||||
uip_udp_remove(dhcp_state.conn);
|
||||
dhcp_state.state = DHCP_OFF;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* DNS resolver for the RTL837x platform
|
||||
* This code is in the Public Domain
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "rtl837x_common.h"
|
||||
#include "cmd_parser.h"
|
||||
#include "uip/uip.h"
|
||||
#include "dhcp.h"
|
||||
#include "dns.h"
|
||||
|
||||
#pragma codeseg BANK3
|
||||
#pragma constseg BANK3
|
||||
|
||||
#define DNS_PORT 53
|
||||
#define DNS_TRIES 6
|
||||
#define DNS_TIMEOUT (2 * SYS_TICK_HZ)
|
||||
|
||||
extern __xdata struct dhcp_state dhcp_state;
|
||||
extern volatile __xdata uint32_t ticks;
|
||||
|
||||
__xdata struct dns_state dns_state;
|
||||
__xdata uip_ipaddr_t dns_ip;
|
||||
uint8_t * __xdata dns_cand[3];
|
||||
|
||||
|
||||
static uint8_t dns_used(__xdata uint8_t *a) __reentrant
|
||||
{
|
||||
return a[0] | a[1] | a[2] | a[3];
|
||||
}
|
||||
|
||||
|
||||
static uint8_t dns_name_is_ip(void) __reentrant
|
||||
{
|
||||
__xdata char *s = dns_state.name;
|
||||
uint8_t i, n;
|
||||
uint16_t v;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
v = 0;
|
||||
n = 0;
|
||||
while (*s >= '0' && *s <= '9') {
|
||||
v = v * 10 + (*s++ - '0');
|
||||
if (++n > 3 || v > 255)
|
||||
return 0;
|
||||
}
|
||||
if (!n)
|
||||
return 0;
|
||||
dns_state.addr[i] = v;
|
||||
if (i < 3 && *s++ != '.')
|
||||
return 0;
|
||||
}
|
||||
return !*s;
|
||||
}
|
||||
|
||||
|
||||
static void dns_next(void) __reentrant
|
||||
{
|
||||
uint8_t k = 0;
|
||||
|
||||
if (dns_state.conn) {
|
||||
uip_udp_remove(dns_state.conn);
|
||||
dns_state.conn = 0;
|
||||
}
|
||||
if (dns_used(dns_state.server[0]))
|
||||
dns_cand[k++] = dns_state.server[0];
|
||||
if (dns_used(dns_state.server[1]))
|
||||
dns_cand[k++] = dns_state.server[1];
|
||||
if (dns_used(dhcp_state.dns))
|
||||
dns_cand[k++] = dhcp_state.dns;
|
||||
if (!k || dns_state.tries >= DNS_TRIES) {
|
||||
dns_state.status = DNS_FAILED;
|
||||
if (dns_state.verbose)
|
||||
print_string(k ? "DNS: no answer\n" : "DNS: no server\n");
|
||||
return;
|
||||
}
|
||||
k = dns_state.tries % k;
|
||||
uip_ipaddr(dns_ip, dns_cand[k][0], dns_cand[k][1], dns_cand[k][2], dns_cand[k][3]);
|
||||
dns_state.conn = uip_udp_new(&dns_ip, HTONS(DNS_PORT));
|
||||
if (!dns_state.conn) {
|
||||
dns_state.status = DNS_FAILED;
|
||||
print_string("DNS: no free UDP connection\n");
|
||||
return;
|
||||
}
|
||||
dns_state.send = 1;
|
||||
}
|
||||
|
||||
|
||||
static void dns_query(void) __reentrant
|
||||
{
|
||||
__xdata uint8_t *p = (__xdata uint8_t *)uip_appdata;
|
||||
__xdata char *n = dns_state.name;
|
||||
__xdata uint8_t *label;
|
||||
uint16_t len = 12;
|
||||
|
||||
dns_state.id++;
|
||||
p[0] = dns_state.id >> 8;
|
||||
p[1] = dns_state.id;
|
||||
p[2] = 0x01;
|
||||
p[3] = 0x00;
|
||||
p[4] = 0;
|
||||
p[5] = 1;
|
||||
p[6] = p[7] = p[8] = p[9] = p[10] = p[11] = 0;
|
||||
while (*n) {
|
||||
label = p + len++;
|
||||
*label = 0;
|
||||
while (*n && *n != '.') {
|
||||
p[len++] = *n++;
|
||||
(*label)++;
|
||||
}
|
||||
if (*n)
|
||||
n++;
|
||||
}
|
||||
p[len++] = 0;
|
||||
p[len++] = 0;
|
||||
p[len++] = 1;
|
||||
p[len++] = 0;
|
||||
p[len++] = 1;
|
||||
uip_udp_send(len);
|
||||
}
|
||||
|
||||
|
||||
static uint16_t dns_skip_name(__xdata uint8_t *p, uint16_t i, uint16_t len) __reentrant
|
||||
{
|
||||
while (i < len) {
|
||||
if (!p[i])
|
||||
return i + 1;
|
||||
if ((p[i] & 0xc0) == 0xc0)
|
||||
return i + 2;
|
||||
i += p[i] + 1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
static void dns_answer(void) __reentrant
|
||||
{
|
||||
__xdata uint8_t *p = (__xdata uint8_t *)uip_appdata;
|
||||
uint16_t len = uip_datalen();
|
||||
uint16_t i, an;
|
||||
|
||||
if (dns_state.status != DNS_PENDING || len < 12 || !(p[2] & 0x80)
|
||||
|| p[0] != (uint8_t)(dns_state.id >> 8) || p[1] != (uint8_t)dns_state.id)
|
||||
return;
|
||||
an = ((uint16_t)p[6] << 8) | p[7];
|
||||
i = dns_skip_name(p, 12, len) + 4;
|
||||
while (!(p[3] & 0x0f) && an-- && i + 10 <= len) {
|
||||
i = dns_skip_name(p, i, len);
|
||||
if (i + 10 > len)
|
||||
break;
|
||||
if (!p[i] && p[i + 1] == 1 && !p[i + 8] && p[i + 9] == 4 && i + 14 <= len) {
|
||||
memcpy(dns_state.addr, p + i + 10, 4);
|
||||
dns_state.status = DNS_DONE;
|
||||
uip_udp_remove(dns_state.conn);
|
||||
dns_state.conn = 0;
|
||||
if (dns_state.verbose) {
|
||||
print_string("DNS: ");
|
||||
print_string_x(dns_state.name);
|
||||
print_string(" is ");
|
||||
print_ip(dns_state.addr);
|
||||
write_char('\n');
|
||||
}
|
||||
return;
|
||||
}
|
||||
i += 10 + (((uint16_t)p[i + 8] << 8) | p[i + 9]);
|
||||
}
|
||||
dns_state.tries = DNS_TRIES;
|
||||
dns_next();
|
||||
}
|
||||
|
||||
|
||||
void dns_init(void) __banked
|
||||
{
|
||||
memset(dns_state.server, 0, sizeof(dns_state.server));
|
||||
dns_state.name[0] = 0;
|
||||
dns_state.status = DNS_IDLE;
|
||||
dns_state.conn = 0;
|
||||
}
|
||||
|
||||
|
||||
void dns_lookup(void) __banked
|
||||
{
|
||||
if (dns_name_is_ip()) {
|
||||
dns_state.status = DNS_DONE;
|
||||
return;
|
||||
}
|
||||
dns_state.tries = 0;
|
||||
dns_state.status = DNS_PENDING;
|
||||
dns_next();
|
||||
}
|
||||
|
||||
|
||||
void dns_show(void) __banked
|
||||
{
|
||||
print_string("DNS servers ");
|
||||
print_ip(dns_state.server[0]);
|
||||
write_char(' ');
|
||||
print_ip(dns_state.server[1]);
|
||||
print_string(", from DHCP ");
|
||||
print_ip(dhcp_state.dns);
|
||||
write_char('\n');
|
||||
if (!dns_state.name[0])
|
||||
return;
|
||||
print_string("Last lookup ");
|
||||
print_string_x(dns_state.name);
|
||||
print_string(": ");
|
||||
if (dns_state.status == DNS_DONE)
|
||||
print_ip(dns_state.addr);
|
||||
else if (dns_state.status == DNS_PENDING)
|
||||
print_string("pending");
|
||||
else
|
||||
print_string("failed");
|
||||
write_char('\n');
|
||||
}
|
||||
|
||||
|
||||
void dns_callback(uint16_t lport) __banked __reentrant
|
||||
{
|
||||
if (!dns_state.conn || lport != dns_state.conn->lport)
|
||||
return;
|
||||
if (uip_newdata()) {
|
||||
dns_answer();
|
||||
return;
|
||||
}
|
||||
if (dns_state.status != DNS_PENDING)
|
||||
return;
|
||||
if (dns_state.send) {
|
||||
dns_state.send = 0;
|
||||
dns_state.sent = ticks;
|
||||
dns_query();
|
||||
return;
|
||||
}
|
||||
if (ticks - dns_state.sent >= DNS_TIMEOUT) {
|
||||
dns_state.tries++;
|
||||
dns_next();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef _DNS_H_
|
||||
#define _DNS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define DNS_NAME_LEN 48
|
||||
|
||||
#define DNS_IDLE 0
|
||||
#define DNS_PENDING 1
|
||||
#define DNS_DONE 2
|
||||
#define DNS_FAILED 3
|
||||
|
||||
struct uip_udp_conn;
|
||||
|
||||
struct dns_state {
|
||||
uint8_t server[2][4]; /* configured servers, 0.0.0.0 = unused */
|
||||
char name[DNS_NAME_LEN]; /* name of the current or last lookup */
|
||||
uint8_t addr[4]; /* address found by the last lookup */
|
||||
uint8_t status; /* DNS_* */
|
||||
uint8_t verbose; /* print the result of the lookup */
|
||||
uint8_t tries; /* queries sent for the current lookup */
|
||||
uint8_t send; /* a query is due on the connection */
|
||||
uint16_t id;
|
||||
uint32_t sent; /* ticks when the last query went out */
|
||||
struct uip_udp_conn *conn;
|
||||
};
|
||||
|
||||
extern __xdata struct dns_state dns_state;
|
||||
|
||||
void dns_init(void) __banked;
|
||||
void dns_lookup(void) __banked;
|
||||
void dns_show(void) __banked;
|
||||
void dns_callback(uint16_t lport) __banked __reentrant;
|
||||
void dns_parse(void) __banked __reentrant;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Command line interface of the DNS resolver for the RTL837x platform
|
||||
* This code is in the Public Domain
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "rtl837x_common.h"
|
||||
#include "cmd_parser.h"
|
||||
#include "dns.h"
|
||||
|
||||
#pragma codeseg BANK2
|
||||
#pragma constseg BANK2
|
||||
|
||||
extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
|
||||
extern __xdata uint8_t cmd_words_len;
|
||||
extern __xdata uint8_t cmd_words_b[15];
|
||||
extern __xdata uint8_t ip[4];
|
||||
uint8_t cmd_compare(uint8_t start, __code const uint8_t * cmd);
|
||||
uint8_t parse_ip(uint8_t idx);
|
||||
|
||||
|
||||
void dns_parse(void) __banked __reentrant
|
||||
{
|
||||
__xdata uint8_t *s;
|
||||
uint8_t n;
|
||||
|
||||
if (cmd_words_len < 2) {
|
||||
dns_show();
|
||||
return;
|
||||
}
|
||||
if (cmd_compare(1, "server") && cmd_words_len >= 3 && parse_ip(cmd_words_b[2])) {
|
||||
memcpy(dns_state.server[0], ip, 4);
|
||||
memset(dns_state.server[1], 0, 4);
|
||||
if (cmd_words_len >= 4) {
|
||||
if (!parse_ip(cmd_words_b[3]))
|
||||
goto err;
|
||||
memcpy(dns_state.server[1], ip, 4);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (cmd_compare(1, "lookup") && cmd_words_len >= 3) {
|
||||
if (dns_state.status == DNS_PENDING) {
|
||||
print_string("DNS: a lookup is already running\n");
|
||||
return;
|
||||
}
|
||||
s = &cmd_buffer[cmd_words_b[2]];
|
||||
for (n = 0; s[n] && s[n] != ' '; n++) {
|
||||
if (n == DNS_NAME_LEN - 1)
|
||||
goto err;
|
||||
dns_state.name[n] = s[n];
|
||||
}
|
||||
dns_state.name[n] = 0;
|
||||
dns_state.verbose = 1;
|
||||
dns_lookup();
|
||||
if (dns_state.status == DNS_DONE) {
|
||||
print_ip(dns_state.addr);
|
||||
write_char('\n');
|
||||
}
|
||||
return;
|
||||
}
|
||||
err:
|
||||
print_string("Error: dns [server <ip> [<ip>]|lookup <name>]\n");
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
### 2M-PCB23-V3.2
|
||||
|
||||
## Brands
|
||||
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|
||||
|---|---|---|---|---|---|
|
||||
| keepLINK | KP-9000-9XHML-X | Yes| 2M-PCB23-V3.2 | 2M (Winbond 25Q) | |
|
||||
|
||||
The original web interface reports this board as hardware version V3.2
|
||||
(original firmware V100.9.25, January 2026).
|
||||
|
||||
## PCB
|
||||
|
||||
<img src="photos/2M-PCB23-V3.2-managed/2M-PCB23-V3.2-top.jpeg" width="300" />
|
||||
|
||||
## Compared to V3.1
|
||||
|
||||
The component placement matches [2M-PCB23-V3.1](2M-PCB23-V3_1.md): same
|
||||
positions for the flash (U8), the T7/T8/T9/T10 headers, the SFP cage and its
|
||||
LED, and the unpopulated U7/U9 footprints. Build with
|
||||
`MACHINE=KP_9000_9XHML_X_V3_2`, which uses the V3.1 machine definition and
|
||||
only differs in the reported name.
|
||||
|
||||
## Tested
|
||||
|
||||
Installed from the original firmware through its web interface with
|
||||
`installer/output/rtlplayground_oem_upgrade.bin`, then:
|
||||
|
||||
- RJ45 ports link at 2.5G; LEDs: orange at 2.5G, green below
|
||||
- SFP port: module detected with vendor/model/serial and DOM readings
|
||||
(temperature, Vcc, TX bias, TX/RX power), link up at 10G
|
||||
- factory MAC read from flash (0x1FC000)
|
||||
- reset button (GPIO48): held for more than 10 s it restores the default config
|
||||
|
||||
## Flash access
|
||||
|
||||
Reading the flash in-circuit with a SOIC-8 clip did not work: the chip ID
|
||||
comes back as `EF FF FF` (only the manufacturer byte is right) and reads
|
||||
return 0xff, the same as on a V3.1 board. The switch chip is powered through
|
||||
the clip and keeps driving the SPI lines. Make sure a way back is acceptable
|
||||
before installing, or hold the switch chip in reset while reading.
|
||||
@@ -0,0 +1,32 @@
|
||||
# MokerLink POE-2G080110GS
|
||||
|
||||
The `POE-2G080110GS` is an unmanaged 8x2.5G RJ45 and 1xSFP+ PoE switch sold by Mokerlink. There is also a `POE-2G080110GSM` managed version of the switch which may be similar but this has not been verified on actual hardware.
|
||||
|
||||
## Brands
|
||||
| Brand | Type |Managed| PCB | Flash | Chip RTL |
|
||||
|--------|------------------|-------|------------------|-----------------|---------------|
|
||||
| Mokerlink | POE-2G080110GS | No | 2M-PCB51-ML-V1.1 | 4MB (W25Q32JV) | 8373N + 8224N |
|
||||
|
||||
## Hardware overview
|
||||
Front
|
||||
|
||||
<img src="photos/POE-2G080110GS/POE-2G080110GS-front.jpg" width="300" />
|
||||
|
||||
Label
|
||||
|
||||
<img src="photos/POE-2G080110GS/POE-2G080110GS-label.jpg" width="300" />
|
||||
|
||||
The stock firmware from a POE-2G080110GS device had a sha256sum of `4c280853465eaad80e1772075c0f2d29c11fb8c561547f06353d3a5443c388e9`.
|
||||
|
||||
### PCB
|
||||
|
||||
Top silkscreen is marked `2M-PCB51-ML-V1.1`. PoE is provided by RTL8238C. Flash chip is 4 MB Winbond 25Q32JV (U8).
|
||||
|
||||
<img src="photos/POE-2G080110GS/POE-2G080110GS-pcb.jpg" width="300" />
|
||||
|
||||
> [!CAUTION]
|
||||
> This device operates on mains voltage, which can be a dangerous or potentially lethal hazard. Even after the device is unplugged, high-voltage capacitors can remain charged afterward and may still shock you. Do not attempt to open/disassemble a mains powered device unless you understand how to do so safely. Also, opening the device may void your warranty.
|
||||
|
||||
## Notes
|
||||
|
||||
The stock firmware blinks both the 2.5G and 1G LEDs on activity, despite the front of the device indicating that each LED should be independent. The `POE_2G080110GS` machine config behaves as the front of the device indicates (and what seems more logical) -- each port LED turns on and blinks independently.
|
||||
@@ -0,0 +1,70 @@
|
||||
# F7008-2.5
|
||||
|
||||
Documentation for the unmanaged 8 + 1 switch sold as `7018-2.5G` by Guangnianwei
|
||||
(光年微), board marking `F7008-2.5`, PCB `F7008-2.5GPOE-V1.2`.
|
||||
|
||||
### Brands
|
||||
|
||||
| Brand | Type | Managed | PCB | Flash | Chip RTL |
|
||||
|-------|------|---------|-----|-------|----------|
|
||||
| Guangnianwei (光年微) | 7018-2.5G | No | F7008-2.5GPOE-V1.2 | W25Q64JVSSIQ | 8373N + 8224N |
|
||||
|
||||
### What works
|
||||
|
||||
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
|
||||
- SFP+ port with 1G/2.5G/10G modules
|
||||
- LEDs of the eight RJ45 ports
|
||||
|
||||
### Not verified
|
||||
|
||||
- The LED pads for the SFP+ cage are not mapped yet. The SFP+ lamps are left
|
||||
undriven, so an inserted module lights nothing.
|
||||
- The UART header footprint is present but unpopulated, and no boot log could be
|
||||
captured from it, so the baud rate is unknown. There are most likely
|
||||
unpopulated 0-Ohm resistors next to the header; bridging those should bring
|
||||
the console to life.
|
||||
|
||||
### Build target
|
||||
|
||||
```
|
||||
make MACHINE=F7008_2_5
|
||||
```
|
||||
|
||||
### LED wiring
|
||||
|
||||
Measured pad to lamp map (`led_mux` index -> lamp):
|
||||
|
||||
| Port | green lamp | yellow lamp |
|
||||
|------|-----------|-------------|
|
||||
| 1 | pad 0 | pad 1 |
|
||||
| 2 | pad 2 | pad 3 |
|
||||
| 3 | pad 4 | pad 5 |
|
||||
| 4 | pad 6 | pad 8 |
|
||||
| 5 | pad 9 | pad 10 |
|
||||
| 6 | pad 12 | pad 15 |
|
||||
| 7 | pad 18 | pad 21 |
|
||||
| 8 | pad 24 | pad 25 |
|
||||
|
||||
Pads 7, 11, 13, 14, 16, 17, 19, 20, 22, 23, 26 and 27 do not drive an RJ45 lamp.
|
||||
|
||||
The front panel is silkscreened `2.5G: Green / 10/100M: Yellow`, and the target
|
||||
follows it: the green lamp is on while the port is linked at 2.5G, the yellow
|
||||
lamp blinks while data is flowing.
|
||||
|
||||
### PCB overview
|
||||
|
||||
The board is marked `F7008-2.5GPOE-V1.2`.
|
||||
|
||||
Top side:
|
||||
|
||||
<img src="photos/F7008-2.5/PCB-top.jpg" width="600" />
|
||||
|
||||
Bottom side:
|
||||
|
||||
<img src="photos/F7008-2.5/PCB-bottom.jpg" width="600" />
|
||||
|
||||
### Hardware notes
|
||||
|
||||
- 4 x `RT4802SNE` magnetics
|
||||
- Unpopulated UART header footprint, next to the magnetics
|
||||
- Power input 12V 1A, 180 x 84 x 29 mm metal case
|
||||
@@ -0,0 +1,75 @@
|
||||
# Hasivo S1100WP-8GT-1SX-SE
|
||||
|
||||
RTL8373-based 8×2.5G PoE + 1×SFP switch.
|
||||
|
||||
### Label specifications
|
||||
|
||||
- **Manufacturer**: Hasivo
|
||||
- **Model**: S1100WP-8GT-1SX-SE
|
||||
- **Ports**:
|
||||
- 8 × RJ45: 10/100/1000/2500 Mbps with PoE
|
||||
- 1 × SFP: 1G / 2.5G / 10G
|
||||
- **Power input**: DC 52V 2.5A
|
||||
- **Stock firmware defaults**: IP 192.168.0.1, user/password `admin`/`admin`
|
||||
|
||||
### What works
|
||||
|
||||
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps (PoE is not configurable via RTLPlayground)
|
||||
- SFP port supporting 1G, 2.5G and 10G modules
|
||||
- LEDs: green (2.5G) and amber (1G/100M/10M) per copper port; combined link/act on the SFP port
|
||||
|
||||
### Photos
|
||||
|
||||
Front panel:
|
||||
|
||||
<img src="photos/HASIVO_S1100WP-8GT-1SX-SE/front.jpg" width="600" />
|
||||
|
||||
Label:
|
||||
|
||||
<img src="photos/HASIVO_S1100WP-8GT-1SX-SE/label.jpg" width="600" />
|
||||
|
||||
### PCB overview
|
||||
|
||||
Top side:
|
||||
|
||||
<img src="photos/HASIVO_S1100WP-8GT-1SX-SE/pcb_top.jpg" width="600" />
|
||||
|
||||
Bottom side:
|
||||
|
||||
<img src="photos/HASIVO_S1100WP-8GT-1SX-SE/pcb_bottom.jpg" width="600" />
|
||||
|
||||
### Port layout
|
||||
|
||||
| Logical port | Physical port | Type |
|
||||
|--------------|---------------|--------|
|
||||
| 1-8 | 1-8 | Copper |
|
||||
| 9 | 9 | SFP |
|
||||
|
||||
### LED configuration
|
||||
|
||||
Copper ports use LED SET0, the SFP port uses LED SET1.
|
||||
|
||||
| SET | LED0 | LED2 |
|
||||
|------|-----------------------------------------------|------------------------------------------------------|
|
||||
| SET0 | Green — lights on 2.5G link with activity | Amber — lights on 1G / 100M / 10M link with activity |
|
||||
| SET1 | All speeds — lights on any link with activity | — |
|
||||
|
||||
### SFP GPIO assignments
|
||||
|
||||
| SFP (logical 9) | pin_detect (ModAbs) | pin_los | pin_tx_disable | SerDes | I2C SDA | I2C SCL |
|
||||
|-----------------|---------------------|---------|----------------|--------|-----------------|---------------------|
|
||||
| SFP | GPIO30_ACL_BIT3_EN | GPIO37 | GPIO_NA | SDS1 | GPIO39_I2C_SDA4 | GPIO40_I2C_SCL3_MDC1 |
|
||||
|
||||
### Machine configuration
|
||||
|
||||
- `reset_pin`: GPIO54_ACL_BIT2_EN
|
||||
- High LEDs (pads 27-29): mux = `LED_27 | LED_28_SYS | LED_29`, enable = `LED_28_SYS | LED_29`
|
||||
|
||||
### PoE
|
||||
|
||||
The 8 copper ports support PoE powered by a DC 52V 2.5A input. Power management is handled by two HS104PTI chipsets over I2C (GPIO47_I2C_SDA0, GPIO46_I2C_SCL0):
|
||||
|
||||
* Chip 1 (I2C address 0x1A): 2.5G Ports 1–4
|
||||
* Chip 2 (I2C address 0x2A): 2.5G Ports 5–8
|
||||
|
||||
Note: The current implementation does not yet support PoE management. Consequently, PoE currently operates in standalone mode without software control.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,122 @@
|
||||
# ONT-S207CW-62TS-SE / Binardat 2G06-04210GSM / Binardat 2G06-04210GS
|
||||
|
||||
## Overview
|
||||
|
||||
ONT-S207CW-62TS-SE, Binardat 2G06-04210GSM (managed), and Binardat 2G06-04210GS (unmanaged) are **switches with identical hardware**:
|
||||
- **CPU:** RTL8372N (confirmed via serial: "Detecting CPU: RTL8372N")
|
||||
- **Flash:** GD25Q128E (16MB)
|
||||
- **Ports:** 4x 2.5G RJ45 + 2x 10G SFP+
|
||||
- **Console:** 115200 baud (RTLPlayground firmware) / 9600 baud (stock firmware)
|
||||
|
||||
**Note:** These devices are **RTL8372N-based** with **LAN ports mounted upside-down on the PCB**, requiring a custom configuration. The `MACHINE_ONT_S207CW_62TS_SE` definition uses a **custom configuration** with:
|
||||
- Corrected port mapping (physical ports 1-4 = RJ45, 5-6 = SFP)
|
||||
- Optimized LED sets for different port wiring (RJ45 Ports 1-2 have reversed LED wiring)
|
||||
- Custom LED mux configuration
|
||||
|
||||
## Device Photos
|
||||
|
||||
### Binardat 2G06-04210GSM
|
||||
|
||||
**Front View:**
|
||||
<img src="photos/ONT-S207CW-Binardat-2G06-04210GSM/binardat-2g06-04210gsm-front.jpg" width="400" />
|
||||
|
||||
**Back View:**
|
||||
<img src="photos/ONT-S207CW-Binardat-2G06-04210GSM/binardat-2g06-04210gsm-back.jpg" width="400" />
|
||||
|
||||
**PCB View:**
|
||||
<img src="photos/ONT-S207CW-Binardat-2G06-04210GSM/binardat-2g06-04210gsm-pcb.jpg" width="400" />
|
||||
|
||||
*Photos courtesy of [andreas5232](https://github.com/andreas5232) from [emesix/ONT-S207CW-62TS-SE#1](https://github.com/emesix/ONT-S207CW-62TS-SE/issues/1)*
|
||||
|
||||
## Hardware Specification
|
||||
|
||||
| Feature | Value |
|
||||
|---|---|
|
||||
| **CPU** | RTL8372N |
|
||||
| **Flash** | GD25Q128E (16MB) |
|
||||
| **RAM** | Integrated in RTL8372N |
|
||||
| **RJ45 Ports** | 4x 2.5GBase-T (Ports 1-4) |
|
||||
| **SFP+ Ports** | 2x 10G (Ports 5-6) |
|
||||
| **Console** | UART (115200 baud for RTLPlayground, 9600 baud for stock) |
|
||||
| **Stock Web UI IP** | 192.168.2.1 |
|
||||
| **Loader Mode IP** | 192.168.10.247 |
|
||||
|
||||
## Port Layout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
|
||||
│ │RJ45 │ │RJ45 │ │RJ45 │ │RJ45 │ │SFP+ │ │SFP+ │ │
|
||||
│ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 │ │ 6 │ │
|
||||
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
|
||||
│ │
|
||||
│ [System LEDs] │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## LED Behavior
|
||||
|
||||
### RJ45 Ports (Physical 1-4)
|
||||
|
||||
| LED Color | Speed |
|
||||
|---|---|
|
||||
| **Green** | 2.5G |
|
||||
| **Orange** | 1G / 100M / 10M |
|
||||
| **Off** | No link |
|
||||
|
||||
### SFP+ Ports (Physical 5-6)
|
||||
|
||||
| LED Color | Speed |
|
||||
|---|---|
|
||||
| **Green** | 10G |
|
||||
| **Orange** | 2.5G / 1G |
|
||||
| **Off** | No link / No SFP module |
|
||||
|
||||
## Port to Logical Mapping
|
||||
|
||||
Custom mapping for ONT-S207CW-62TS-SE:
|
||||
|
||||
| Physical Port | Logical Port | Type | LED Set |
|
||||
|---|---|---|---|
|
||||
| 1 | 7 | RJ45 | 2 |
|
||||
| 2 | 6 | RJ45 | 2 |
|
||||
| 3 | 5 | RJ45 | 0 |
|
||||
| 4 | 4 | RJ45 | 0 |
|
||||
| 5 | 3 | SFP+ (SDS0) | 3 |
|
||||
| 6 | 8 | SFP+ (SDS1) | 3 |
|
||||
|
||||
**Note:** Ports 1-2 (RJ45) have reversed LED wiring and use LED-Set 2. SFP Ports 5-6 use LED-Set 3 to account for wiring differences.
|
||||
|
||||
## Flashing Instructions
|
||||
|
||||
See the [README.md](../../README.md) for general flashing instructions.
|
||||
|
||||
## Stock Firmware Information
|
||||
|
||||
| Property | Value |
|
||||
|---|---|
|
||||
| **Default IP** | 192.168.2.1 (ONT) / 192.168.10.247 (Loader Mode) |
|
||||
| **Default Login** | admin/admin (may vary by OEM) |
|
||||
| **Console Baud** | 9600 (stock) / 115200 (RTLPlayground) |
|
||||
| **CPU String** | "RTL8372N" |
|
||||
| **Loader Mode IP** | 192.168.10.247 |
|
||||
|
||||
## Configuration Details
|
||||
|
||||
- **CPU:** RTL8372N
|
||||
- **SFP:** SDS0 on logical port 3 (GPIO37), SDS1 on logical port 8 (GPIO38)
|
||||
- **Port Mapping:** Physical 1-4 → Logical 7-4, Physical 5-6 → Logical 3,8
|
||||
- **LED Wiring:** RJ45 Ports 1-2 and SFP Port 6 have reversed LED pins (orange/green swapped)
|
||||
- **LED Sets:** Set 0 for RJ45 Ports 3-4, Set 1 unused, Set 2 for RJ45 Ports 1-2, Set 3 for SFP Ports 5-6
|
||||
|
||||
## Notes
|
||||
|
||||
- ONT-S207CW-62TS-SE, Binardat 2G06-04210GSM (managed), and Binardat 2G06-04210GS (unmanaged) are **RTL8372N-based** with **LAN ports mounted upside-down on the PCB**
|
||||
- Web UI shows correct physical port order (1-2-3-4 for RJ45, 5-6 for SFP)
|
||||
- Loader Mode always remains accessible at `192.168.10.247` for recovery
|
||||
- Serial console: 9600 baud (stock) → 115200 baud (RTLPlayground)
|
||||
|
||||
## References
|
||||
|
||||
- [GitHub Issue: emesix/ONT-S207CW-62TS-SE#1](https://github.com/emesix/ONT-S207CW-62TS-SE/issues/1) - Original photos and discussion
|
||||
- [PR: logicog/RTLPlayground#465](https://github.com/logicog/RTLPlayground/pull/465) - Feedback and improvements
|
||||
@@ -0,0 +1,53 @@
|
||||
# IG108
|
||||
|
||||
Following is documentation for unmanaged switch marked as `IG108`.
|
||||
|
||||
Original software is running UART on 9600 baud rate.
|
||||
|
||||
Using SPI clamp in-board is the only method for initial installation.
|
||||
|
||||
### Brands
|
||||
|
||||
| Brand | Type | Managed | PCB | Flash | Chip RTL |
|
||||
|---------|------------|---------|------------|-------------|---------------|
|
||||
| Goalake | IG108 | No | PB-2132 | cFeon Q32A | 8373N + 8224N |
|
||||
| Netcore | GS9 | No | PB-2132 | cFeon Q32A | 8373N + 8224N |
|
||||
|
||||
### What works
|
||||
|
||||
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
|
||||
- SFP port with 1G/2.5G/10G modules
|
||||
- LEDs
|
||||
|
||||
### PCB overview
|
||||
|
||||
**Board markings**
|
||||
|
||||
- Top silkscreen: PB-2132
|
||||
|
||||
Front Goalake:
|
||||
|
||||
<img src="photos/PB-2132/case.jpg" width="600" />
|
||||
|
||||
Label Goalake:
|
||||
|
||||
<img src="photos/PB-2132/label.jpg" width="600" />
|
||||
|
||||
Top side:
|
||||
|
||||
<img src="photos/PB-2132/PCB-top.jpg" width="600" />
|
||||
|
||||
Bottom:
|
||||
|
||||
<img src="photos/PB-2132/PCB-bottom.jpg" width="600" />
|
||||
|
||||
### Serial console
|
||||
|
||||
The PCB has unpopulated through-holes near the SFP Port, with description of each pin, works out of the box.
|
||||
|
||||
- **Settings**: 115200 baud / 8N1 / 3.3V TTL
|
||||
|
||||
### Power supply
|
||||
|
||||
Input power is delivered via barell plug, `12V 1A` adapter was provided.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# SWTG018AS-A V2.0
|
||||
## Brands
|
||||
| Brand | Type |Managed| PCB | Flash | Chip RTL |
|
||||
|--------|------------------|-------|------------------|-----------------|---------------|
|
||||
| Ampcom | SWTG018AS-A V2.0 | No | SWTG018AS-A V2.0 | 2MB | 8273N + 8224N |
|
||||
| Horaco | HC-SWTGW218AS-A | Yes | SWTG018AS-A V2.0 | 2MB(25Q16JVSIQ) | 8273N + 8224N |
|
||||
| Brand | Type |Managed| PCB | Flash | Chip RTL |
|
||||
|---------|------------------|-------|------------------|-----------------|---------------|
|
||||
| Ampcom | SWTG018AS-A V2.0 | No | SWTG018AS-A V2.0 | 2MB | 8273N + 8224N |
|
||||
| Horaco | HC-SWTGW218AS-A | Yes | SWTG018AS-A V2.0 | 2MB(25Q16JVSIQ) | 8273N + 8224N |
|
||||
| GoodTop | GT-ST018M | Yes | SWTG018AS-A V2.0 | ? | 8273N + 8224N |
|
||||
|
||||
|
||||
The following is a documentation for the unmanaged switch marked as `SWTG018AS-A V2.0`.
|
||||
|
||||
|
After Width: | Height: | Size: 480 KiB |
|
After Width: | Height: | Size: 519 KiB |
|
After Width: | Height: | Size: 529 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 273 KiB |
|
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 |
|
After Width: | Height: | Size: 546 KiB |
|
After Width: | Height: | Size: 478 KiB |
|
After Width: | Height: | Size: 639 KiB |
|
After Width: | Height: | Size: 907 KiB |
|
After Width: | Height: | Size: 923 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 934 KiB |
@@ -0,0 +1,23 @@
|
||||
# DNS
|
||||
|
||||
The switch can resolve host names to IPv4 addresses, for the NTP client and
|
||||
for anything else that is given a name instead of an address.
|
||||
|
||||
```
|
||||
dns server <ip> [<ip>] # one or two servers, 0.0.0.0 removes them
|
||||
dns lookup <name> # resolve a name and print the address
|
||||
dns # show the servers and the last lookup
|
||||
```
|
||||
|
||||
The answer to a lookup arrives after the command has returned, so it is
|
||||
printed on the serial console and in the syslog. In the console of the web
|
||||
interface, run `dns` afterwards to see it.
|
||||
|
||||
Without a configured server the resolver uses the one handed out by DHCP.
|
||||
Only address records are looked up; a query goes to the servers in turn,
|
||||
every two seconds, and gives up after six tries. A name that already is a
|
||||
dotted address is taken as it is, without a query.
|
||||
|
||||
The DNS servers are set in the Network card of the System page as well.
|
||||
Settings made there or on the console are kept only once the configuration is
|
||||
saved.
|
||||
@@ -0,0 +1,79 @@
|
||||
# The health command
|
||||
|
||||
`health` prints a one-page snapshot of the firmware's vital signs on the
|
||||
serial console. It exists for the situations where the switch misbehaves and
|
||||
every probe from the network measures the network instead of the device: the
|
||||
dump is taken from inside, costs one command, and works while HTTP does not.
|
||||
|
||||
```
|
||||
> health
|
||||
up 0000001f ticks 0x00001f4c loops 0x000016da rx 0x024e
|
||||
link max 01 slow 0x0000
|
||||
sfp max 74 slow 0x0001
|
||||
rx max 00 slow 0x0000
|
||||
tx max 00 slow 0x0000
|
||||
stp max 04 slow 0x0001
|
||||
cmd max 02 slow 0x0001
|
||||
sp 80 untouched 62
|
||||
tcp st 00 tmr 00 rtx 00 rto 00 len 0x0000 mss 0x05b4
|
||||
lport 0x0000 rport 0x0000 rip 0x0000 0x0000
|
||||
httpd left 0x0000 entry 00 stp 01 mvlan 0x0002
|
||||
```
|
||||
|
||||
The instrumentation is not part of a default build. It costs about 1.1 kB
|
||||
of bank 2, a few bytes of xdata and seven banked calls per main-loop pass,
|
||||
which is nothing a user needs unless they are chasing a problem, so it is
|
||||
switched on at build time:
|
||||
|
||||
```
|
||||
make MACHINE=SWTGW218AS HEALTH=1
|
||||
```
|
||||
|
||||
Without it the hooks compile to nothing and `health` is not a command.
|
||||
|
||||
All figures are raw hex. The counters are cumulative: take two dumps a known
|
||||
time apart and the differences give the rates.
|
||||
|
||||
## The first line
|
||||
|
||||
`up` is the seconds counter, `ticks` the system tick (200 per second),
|
||||
`loops` the number of main-loop passes and `rx` the frames handle_rx has
|
||||
taken off the NIC. A healthy loop completes one pass per tick, so the
|
||||
difference in `loops` tracks the difference in `ticks`; a loop that falls
|
||||
behind is being held up by one of the phases below. The `rx` rate separates
|
||||
a flooded CPU port from a stalled loop, which look identical from outside.
|
||||
|
||||
## Phases
|
||||
|
||||
Each line is one section of the main loop: the link poll, SFP handling,
|
||||
frame reception, TCP transmission, the STP timers and the command dispatch.
|
||||
`max` is the worst time that section has taken, in ticks of 5 ms; `slow`
|
||||
counts the passes that took two ticks or more. A misbehaving subsystem
|
||||
names itself here, which is the difference between knowing that the loop is
|
||||
slow and knowing why.
|
||||
|
||||
A dump of its own output takes the console some milliseconds, so running
|
||||
`health` repeatedly increments the `cmd` counters by itself. That is the
|
||||
instrument observing itself, not a fault.
|
||||
|
||||
## Stack
|
||||
|
||||
At boot the free stack area is painted with a pattern. `sp` is the live
|
||||
stack pointer; `untouched` counts the painted bytes still intact at the top,
|
||||
which is the closest thing to a high-water mark this hardware offers. A
|
||||
shrinking `untouched` across dumps means something is reaching deeper than
|
||||
anything before it.
|
||||
|
||||
## The TCP slot
|
||||
|
||||
The connection table is printed whole, one slot per pair of lines: TCP
|
||||
state (3 is ESTABLISHED, 0 closed), retransmission timer and counter, bytes
|
||||
in flight, negotiated MSS, then the ports and the peer address. With a
|
||||
single slot serving the entire web interface, one glance answers the
|
||||
question every management outage starts with: who is holding it. A slot
|
||||
showing state 3 with nothing in flight and a peer that no longer answers is
|
||||
a connection whose owner went away.
|
||||
|
||||
`httpd left` and `entry` describe the file transfer the web server believes
|
||||
it is in the middle of, and the trailing flags show whether STP is running
|
||||
and which VLAN carries management.
|
||||
@@ -69,18 +69,26 @@ The code currently provides the following functions:
|
||||
void igmp_setup(void) __banked;
|
||||
void igmp_enable(void) __banked;
|
||||
void igmp_router_port_set(uint16_t pmask) __banked;
|
||||
void igmp_packet_handler(void) __banked;
|
||||
uint8_t igmp_packet_handler(void) __banked;
|
||||
void igmp_show(void) __banked;
|
||||
```c
|
||||
`igmp_setup()` is called at boot-time and configures flooding of all IP-MC packets by
|
||||
default, as otherwise no IP-MC would be possible in the network.
|
||||
default, as otherwise no IP-MC would be possible in the network. `igmp off` calls it as
|
||||
well; groups learned while IGMP was on stay in the table, but are no longer looked up.
|
||||
|
||||
`igmp_enable()`starts IGMP which cause IGMP packets to be handled by the CPU and forwarding
|
||||
of IP-MC packets to be limited to only subscribed ports.
|
||||
|
||||
`igmp_router_port_set()`configures forwarding ports for IGMP messages.
|
||||
|
||||
`igmp_packet_handler()` implements handling of trapped IGMP packets by the CPU.
|
||||
`igmp_packet_handler()` implements handling of trapped IGMP packets by the CPU. It is called
|
||||
for frames to 01:00:5e:xx:xx:xx and returns 0 when the frame is not a trapped IGMP packet,
|
||||
so that it is passed on to the IP stack. IGMPv1/v2 reports (0x12, 0x16) join the group given
|
||||
in the IGMP header, IGMPv2 leaves (0x17) leave it, IGMPv3 reports use the first group record
|
||||
(type 4 joins, type 3 leaves). Trapped IGMP frames arrive without a VLAN tag.
|
||||
Reports for groups in 224.0.0.0/24 are ignored: the switch floods that block
|
||||
even with IGMP on, and an entry for a group such as 224.0.0.251 (mDNS) would
|
||||
otherwise limit it to the ports that reported it, as RFC 4541 warns against.
|
||||
|
||||
`igmp_show()` prints out the IGMP configuration on the CLI.
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 120 KiB |
@@ -73,13 +73,14 @@ without being flooded to the other ports. No bridge relays these frames:
|
||||
their addresses are in the set that 802.1D-2004 clause 7.12.6 forbids a
|
||||
bridge to forward, and what travels the network is the information, with
|
||||
every bridge regenerating BPDUs of its own on its designated ports. The reserved-multicast *trap* action
|
||||
cannot do that on this hardware, because its destination is an external CPU
|
||||
attached to a physical port, which these boards do not populate. The protocol
|
||||
modules therefore leave the reserved-multicast action at *forward* and constrain
|
||||
the egress with a static L2 multicast entry instead: the lookup hits the entry's
|
||||
own port mask rather than the VLAN flood mask. Verified on a SWTGW218AS both
|
||||
ways, with the CPU bit cleared, where delivery stops, and with the CPU bit alone,
|
||||
where nothing egresses.
|
||||
does that: it delivers to the port named in `EXT_CPU_CTRL` (0x6724), which
|
||||
`trap_init()` sets to 9, the CPU port, at startup; with the reset value 0xf it
|
||||
names no port and the trapped frame is dropped. STP traps its address while it
|
||||
runs (see stp.md). Where the action is left at *forward*, the egress can be
|
||||
constrained with a static L2 multicast entry instead: the lookup hits the
|
||||
entry's own port mask rather than the VLAN flood mask. Verified on a SWTGW218AS
|
||||
both ways, with the CPU bit cleared, where delivery stops, and with the CPU bit
|
||||
alone, where nothing egresses.
|
||||
|
||||
`port_l2mc_set()` writes one such entry. The SMI layout is the L2 multicast
|
||||
variant of the table entry:
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
# LACP (Link Aggregation Control Protocol, IEEE 802.3ad)
|
||||
|
||||
A static link aggregation group works only if both ends are configured for the
|
||||
same links, and nothing checks that they are. LACP lets the two ends agree on
|
||||
which links reach the same partner, aggregates only those, and takes a link out
|
||||
of the group when the partner stops answering.
|
||||
|
||||
The switch runs LACP in software on the 8051. LACP frames belong to the Slow
|
||||
Protocols, with destination `01:80:C2:00:00:02` and ethertype `0x8809`; the
|
||||
subtype of LACP frames is 1. The implementation has the Receive, Selection and
|
||||
Mux machines of 802.3ad clause 43 and does not aggregate links whose partners
|
||||
differ. Up to `LACP_NUM_LAGS` (4) groups can run LACP; the others stay static.
|
||||
|
||||
## Getting the frames to the CPU
|
||||
|
||||
While LACP runs, the reserved multicast action for `01:80:C2:00:00:02` is
|
||||
*trap*, the same mechanism STP uses for its BPDUs, see [stp.md](stp.md). A
|
||||
trapped LACPDU has no VLAN tag after the RTL tag, and `TRAP_RX_BODY()` gives the
|
||||
offset of the LACPDU in the received frame. When the last group leaves LACP,
|
||||
the action goes back to *drop*.
|
||||
|
||||
## What has to match across the members
|
||||
|
||||
802.3ad requires that every link in an aggregate be full duplex and run at the
|
||||
same speed. Beyond the standard, anything that changes how a frame is treated
|
||||
has to be identical across members, because the hash picks the member per frame
|
||||
and a difference would make the behaviour depend on that choice:
|
||||
|
||||
* must match: speed and duplex, VLAN membership and pvid, ingress filtering,
|
||||
port isolation
|
||||
* may differ: EEE, LED configuration, counters, cable diagnostics
|
||||
|
||||
None of this is enforced yet — `port_lag_members_set()` writes a member mask and
|
||||
a hash setting and validates nothing. See #377 for the discussion of whether a
|
||||
mismatch should be rejected or applied to the whole group.
|
||||
|
||||
## Protocol timing
|
||||
|
||||
The timer values are in units of the LACP timer, which steps every four ticks
|
||||
of the system clock, 50 times per second:
|
||||
|
||||
| Constant | Value | Time |
|
||||
|----------------------|-------|------|
|
||||
| `LACP_FAST_PERIODIC` | 50 | 1 s |
|
||||
| `LACP_SLOW_PERIODIC` | 1500 | 30 s |
|
||||
| `LACP_SHORT_TIMEOUT` | 200 | 4 s |
|
||||
|
||||
802.3ad specifies a fast periodic time of 1 s with a short timeout of 3 s, and a
|
||||
slow periodic time of 30 s with a long timeout of 90 s. The switch transmits at
|
||||
the rate the partner asks for with its TIMEOUT bit. Its own timeout is always
|
||||
the short one, one second above the standard, so a partner that stops sending
|
||||
while its link stays up leaves the trunk after 4 s.
|
||||
|
||||
The Partner block of a transmitted LACPDU repeats the partner's actor
|
||||
information as it was last received: system priority, system, key, port
|
||||
priority, port and state. A Linux partner sets our SYNC only when this block
|
||||
matches what it sent.
|
||||
|
||||
## LACP API
|
||||
|
||||
```
|
||||
void lacp_init(void) __banked;
|
||||
void lacp_off(void) __banked;
|
||||
void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked;
|
||||
void lacp_in(void) __banked;
|
||||
void lacp_timers(void) __banked;
|
||||
void lacp_show(void) __banked;
|
||||
```
|
||||
|
||||
`lacp_init()` has to run before the startup configuration replays, because xdata
|
||||
is not zeroed and the port-to-LAG map uses `0xff` as "no LAG"; a stray zero would
|
||||
make a port look like it belongs to LAG 0.
|
||||
|
||||
## LACP configuration on the serial console
|
||||
|
||||
A group is put under LACP by adding `lacp` to the `lag` command:
|
||||
|
||||
```
|
||||
lag 1 lacp 7 8 # ports 7 and 8 become LACP candidates of group 1
|
||||
lag 1 lacp off # group 1 leaves LACP, its trunk is cleared (so does "lag 1 lacp")
|
||||
lag 1 d # group 1 disabled, whether it ran LACP or not
|
||||
lag 1 7 8 # plain static aggregation, no protocol
|
||||
```
|
||||
|
||||
A group put under LACP gives up the static trunk it had: the hardware trunk is
|
||||
cleared and rebuilt from the ports that converge. The other way round, `lag 1 d`
|
||||
and a static member list take the group out of LACP before they touch the
|
||||
trunk. A port named in another LACP group moves to the new one. The protocol
|
||||
starts with the first group put under LACP and stops when the last one is taken
|
||||
out; there is no separate switch for it. Two commands act on the whole engine:
|
||||
|
||||
```
|
||||
lacp off # every group leaves LACP, protocol stopped
|
||||
lacp show
|
||||
```
|
||||
|
||||
`lacp show` prints the engine state, then one line per group with its candidate
|
||||
ports, the elected aggregator and the trunk members actually programmed into the
|
||||
hardware, then one line per port with the actor and partner state bytes and the
|
||||
receive-machine state.
|
||||
|
||||
## LACP configuration via the Web Interface
|
||||
|
||||
Each group on the LAG page has a Static/LACP mode next to its member ports;
|
||||
for an LACP group it shows the elected aggregator and the active members. Below
|
||||
the groups a table has one row per LACP port with its group, actor and partner
|
||||
state, receive state, LACPDU count and partner system. The page reads
|
||||
`/lacp.json` every three seconds while it is open. The member checkboxes of a
|
||||
static group come from the hardware registers in `/lag.json`.
|
||||
|
||||
## A test with a Linux 802.3ad bond
|
||||
|
||||
Two ports of the switch to two interfaces of one host:
|
||||
|
||||
```
|
||||
ip link add bond0 type bond mode 802.3ad
|
||||
ip link set enp1s0 down && ip link set enp1s0 master bond0
|
||||
ip link set enp2s0 down && ip link set enp2s0 master bond0
|
||||
ip link set bond0 up
|
||||
```
|
||||
|
||||
On the switch, with those two ports as the group:
|
||||
|
||||
```
|
||||
lag 1 lacp 7 8
|
||||
```
|
||||
|
||||
`lacp show` should then give both ports an actor state of `0x3f` (ACTIVITY,
|
||||
TIMEOUT, AGGREGATION, SYNC, COLLECTING and DISTRIBUTING), a partner state with
|
||||
SYNC set, and the partner's system MAC. `lag show` lists the trunk members as
|
||||
programmed in the hardware and should show both ports.
|
||||
|
||||
On the host, `/proc/net/bonding/bond0` should show the same `Aggregator ID` for
|
||||
both slaves; different IDs mean that each link is in a group of its own. Both
|
||||
churn states should read `none`.
|
||||
|
||||
Further checks:
|
||||
|
||||
* `tcpdump -i enp1s0 ether proto 0x8809` on each slave shows the LACPDUs of the
|
||||
switch and of that slave, never those of the other slave.
|
||||
* The host log has no `illegal loopback`.
|
||||
* The three reserved bytes after the actor state in a LACPDU from the switch
|
||||
are zero.
|
||||
|
||||
When one slave goes down, the group narrows to the remaining member, and the
|
||||
slave rejoins after it comes back.
|
||||
|
||||
## Known limits
|
||||
|
||||
* No Churn Detection machines and no Marker protocol.
|
||||
* Coupled Mux: collecting and distributing are enabled together.
|
||||
* One aggregator per group.
|
||||
* Selection compares the partner's system only, not its key: two aggregates
|
||||
that one partner system offers on the same group land in one trunk here.
|
||||
* Member compatibility is not validated; see #377.
|
||||
@@ -227,3 +227,7 @@ iperf Done.
|
||||
```
|
||||
As you can see, the total throughput was 4.71 GBit/sec which is close to the
|
||||
maximum possible with a single 5GBit link.
|
||||
|
||||
## LACP
|
||||
|
||||
Dynamic aggregation with LACP is described in [lacp.md](lacp.md).
|
||||
@@ -0,0 +1,33 @@
|
||||
# Time
|
||||
|
||||
The switch has no battery backed clock. It counts seconds from power on, and
|
||||
it can take the date and time from an NTP server once the network is up. The
|
||||
time is lost at every restart and set again at the first synchronisation.
|
||||
|
||||
```
|
||||
ntp server <name|ip> # default pool.ntp.org
|
||||
ntp interval <1-1440> # minutes between two synchronisations, default 60
|
||||
ntp timezone <+hh:mm> # offset from UTC, -12:00 to +14:00
|
||||
ntp dst off|eu|us # daylight saving rule
|
||||
ntp on
|
||||
ntp off
|
||||
ntp # settings and time
|
||||
time # local date and time
|
||||
```
|
||||
|
||||
The client speaks SNTP version 4 and synchronises at once when turned on,
|
||||
then after every interval. A server given by name is resolved through the DNS
|
||||
resolver before every synchronisation. An answer from an unsynchronised
|
||||
server is ignored, and a failed attempt is repeated after 30 seconds.
|
||||
|
||||
The local time is UTC plus the time zone offset, plus one hour while daylight
|
||||
saving time applies:
|
||||
|
||||
* `eu`: from 01:00 UTC on the last Sunday of March to 01:00 UTC on the last
|
||||
Sunday of October;
|
||||
* `us`: from 02:00 local time on the second Sunday of March to 02:00 local
|
||||
time on the first Sunday of November.
|
||||
|
||||
The same settings, with the current local time and the last synchronisation,
|
||||
are in the Time card of the System page. Settings made there or on the
|
||||
console are kept only once the configuration is saved.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Storm Control
|
||||
Storm control limits the rate at which a port admits broadcast, multicast,
|
||||
unknown unicast and unknown multicast frames. Frames of a type above its limit
|
||||
are dropped at ingress, so a loop or a misbehaving host cannot flood the rest
|
||||
of the network through that port. Known unicast traffic is not affected.
|
||||
|
||||
## Commands
|
||||
```
|
||||
storm <port> bcast|mcast|ucast|umcast <rate> pps
|
||||
storm <port> bcast|mcast|ucast|umcast <rate> kbps
|
||||
storm <port> bcast|mcast|ucast|umcast off
|
||||
storm
|
||||
```
|
||||
`ucast` is unicast to a MAC address the switch has not learned yet, and
|
||||
`umcast` is multicast without an entry in the address table. Multicast groups
|
||||
get their entries from IGMP snooping, so without `igmp on` all multicast is
|
||||
unknown and only the `umcast` limit applies to it; `mcast` covers the groups
|
||||
IGMP has learned. A limit in
|
||||
packets per second goes from 1 to 1048575, a limit in kbit/s from 1 to
|
||||
10000000. `storm` without arguments prints the limits of every port. The
|
||||
limits are kept in the configuration like any other setting, and the Bandwidth
|
||||
page of the web interface has a Storm control card to set them.
|
||||
|
||||
## Registers
|
||||
Each port and type has an enable bit and a meter index, and each limit uses
|
||||
one of the 64 shared meters of the switch. RTLPlayground gives every port and
|
||||
type its own meter, index `24 + port * 4 + type`, so one port's storm does not
|
||||
eat into the limit of another. Storm control uses meters 24 to 63 and leaves
|
||||
0 to 23 to the ACL. The limit of a meter is in `RTL837X_METER_RATE + 4 *
|
||||
meter`, and bit `meter % 32` of `RTL837X_METER_MODE + 4 * (meter / 32)`
|
||||
selects pps instead of kbit/s.
|
||||
```
|
||||
#define RTL837X_STORM_CTRL 0x54e4 /* + 4 * type, bit = port */
|
||||
#define RTL837X_STORM_MIDX 0x54f4 /* + 8 * type + 4 * (port / 5), 6 bits per port */
|
||||
#define RTL837X_METER_RATE 0x5cf0 /* + 4 * meter, 24 bits, kbit/s or pps */
|
||||
#define RTL837X_METER_MODE 0x5ef0 /* + 4 * (meter / 32), bit set = pps */
|
||||
```
|
||||
In kbit/s mode one step of the rate is 1 kbit/s. Measured with 100 and 1000 byte
|
||||
frames, the meter counts a frame with its FCS, but without preamble and
|
||||
interframe gap. Every meter has a bit that adds them, in `0x5f08 + 4 * (meter
|
||||
/ 32)`, which storm control leaves cleared. `IGBW_INC_IFG` and `EGBW_INC_IFG`
|
||||
are the matching bits of the bandwidth control.
|
||||
|
||||
An ingress bandwidth limit on the same port works on the same arriving frames
|
||||
as the storm limit rather than after it, and the two together pass far less
|
||||
than either one: a 1 Mbit/s `umcast` limit with a 10 Mbit/s ingress limit let
|
||||
0.12 to 0.16 Mbit/s of a 1.4 Gbit/s stream through, where each alone passed
|
||||
1.17 and 10.2 Mbit/s.
|
||||
|
||||
The type is 0 for broadcast, 1 for multicast, 2 for unknown unicast and 3 for
|
||||
unknown multicast. The burst size of a meter (0x5df0 + 4 * meter) counts packets
|
||||
in pps mode; its default of 0x2000 lets 8192 frames through before a pps limit
|
||||
bites, so a pps limit sets the burst to one second of traffic. A kbit/s limit
|
||||
puts back the default.
|
||||
@@ -35,28 +35,60 @@ BPDUs are addressed to `01:80:C2:00:00:00`, a reserved link-local group. The
|
||||
ASIC's Reserved-Multicast action for that address decides what happens to the
|
||||
frame.
|
||||
|
||||
Forwarding to the CPU port works normally: the 8051 sits behind an ordinary
|
||||
port of the internal switch and is an ordinary member of a forwarding mask.
|
||||
The *trap* action does not deliver to it. Its destination is an external CPU
|
||||
attached to a physical port (`cpuTag_externalCpuPort_set`, `EXT_CPU_CTRL` in
|
||||
the vendor SDK), which these boards do not populate. The ACL trap and
|
||||
redirect actions do not deliver to the 8051 either.
|
||||
While STP runs, the action is *trap*. A trap delivers to the port named in
|
||||
`EXT_CPU_CTRL` (0x6724), whose reset value 0xf names no port, and with that
|
||||
value the trapped frame is dropped at ingress. `trap_init()` sets it to 9, the
|
||||
port the 8051 sits behind, once at startup. The trap needs no per-VLAN L2
|
||||
entries, so it does not depend on which VLANs exist or on the PVIDs, and turning
|
||||
STP on no longer walks the whole VLAN table. BPDUs reach the CPU on a port that
|
||||
STP holds in blocking with either action: measured on a SWTGW218AS by sending
|
||||
BPDUs into a blocked port with forward and with trap, both ports stopped being
|
||||
edge and waited out the forward delay. The chip exempts this address from the
|
||||
ingress state check; the slow-protocols address used by LACP is not exempt.
|
||||
|
||||
Delivery therefore uses the *forward* action, constrained to the CPU port
|
||||
by a static L2 multicast entry (`port_l2mc_set()`), one per VLAN in use:
|
||||
A trapped frame reaches the CPU in the format it came in with: unlike a
|
||||
forwarded one it has no VLAN tag inserted after the RTL tag, and the RTL tag
|
||||
carries the trap reason. `stp_in()` reads the BPDU from the offset
|
||||
`TRAP_RX_BODY()` returns, which works for both.
|
||||
|
||||
* while STP runs, the entry's member mask is the CPU port only — BPDUs reach
|
||||
the CPU and are not flooded to other ports, as a participating bridge
|
||||
requires;
|
||||
* with STP off, the same entries are retargeted to all ports, restoring the
|
||||
transparency an unmanaged switch is expected to have, so a surrounding
|
||||
spanning tree can span *through* this device.
|
||||
With STP off, the action goes back to *forward* and a static L2 multicast
|
||||
entry per VLAN in use (`port_l2mc_set()`) targets all ports, restoring the
|
||||
transparency an unmanaged switch is expected to have, so a surrounding
|
||||
spanning tree can span *through* this device.
|
||||
|
||||
A BPDU delivered this way is an ordinary frame to the port's ingress logic
|
||||
and passes through its acceptable-frame-type filter. BPDUs are untagged, so a
|
||||
port set to admit tagged frames only (`ingress <port>t`) never delivers one
|
||||
to the CPU. `stp_setup()` prints a warning for every STP-enabled port in that
|
||||
state.
|
||||
The port's acceptable-frame-type filter treats the two actions differently.
|
||||
BPDUs are untagged, so a port set to admit tagged frames only
|
||||
(`ingress <port>t`) drops a forwarded BPDU before it reaches the CPU, while a
|
||||
trapped one passes. Both were measured on a SWTGW218AS with `ingress 3t`:
|
||||
LACPDUs, delivered by forward, stopped arriving on port 3, and BPDUs sent into
|
||||
the same port, delivered by trap, kept arriving. A port that admits tagged
|
||||
frames only therefore takes part in STP.
|
||||
|
||||
## Link aggregation
|
||||
|
||||
A LAG (Link Aggregation Group) is handled like an additional port by the STP
|
||||
protocol, with its own timers and states. The switch devices support up to 4
|
||||
LAGs, which are shown alongside the physical ports in the STP status. A group
|
||||
carries its own path cost, priority, edge and guard settings, and gets its own
|
||||
row on the Spanning Tree page.
|
||||
|
||||
```
|
||||
stp lag 1 cost 10000 # the group decides, not its members
|
||||
stp lag 1 edge off
|
||||
```
|
||||
|
||||
LAGs are configured via the `lag` command. Once a port is a member of a LAG it
|
||||
can no longer be configured individually for STP, so `stp port <n>` on a member
|
||||
tells you which group to configure instead. Membership is re-read from the
|
||||
aggregation registers once a second, so a group changing under LACP is picked
|
||||
up without any coordination between the two.
|
||||
|
||||
The switch hardware does not handle STP state for a LAG as a whole. State
|
||||
changes for the members have to be done by the firmware, updating every member
|
||||
port, which is possible using a single register write. BPDUs go out through the
|
||||
lowest member and carry the group's own port id. Losing one member of a live
|
||||
LAG is not a topology change; the logical port only goes down with its last
|
||||
link.
|
||||
|
||||
Port states live in `RTL837X_MSTP_STATES (0x5310)`, two bits per port:
|
||||
`00` disabled, `01` blocking, `10` learning, `11` forwarding. A port in
|
||||
|
||||
@@ -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`).
|
||||
@@ -6,6 +6,9 @@ The following devices have been tested and are fully working:
|
||||
| Ampcom | WAM902-SWTG018AS| No | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | | 8 + 1 |
|
||||
| Davuaz | Da-K6501W | No | [PCB-K0501W-V2.0](devices/K0501W_V2_0.md) | | 5 + 1 |
|
||||
| FOXNEO | FNS-1200P | No | [PCB-K0402W-U13-V2.0](devices/FNS-1200P.md) | 2M | 4 + 2 |
|
||||
| Goalake | IG108 | No | [PB-2132](devices/PB-2132.md) | 4M | 8 + 1 |
|
||||
| GoodTop | GT-ST018M | Yes | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | ? | 8 + 1 |
|
||||
| Hasivo | S1100WP_8GT_1SX_SE | Yes | [S1100WP_8GT_1SX_SE-V1.05](devices/HASIVO_S1100WP_8GT_1SX_SE.md) | 2M | 8 + 1 |
|
||||
| Hisource | Hi-K0402WS | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
|
||||
| Hisource | Hi-K0402WS | No | [PCB-K0402WS-V2.0](devices/PCB-K0402WS-V2.0.md) | 2M | 4 + 2 |
|
||||
| Hisource | Hi-K0801WS | No | [PCB-KO801W-V2.0](devices/HI-K0801WS.md) | | 8 + 1 |
|
||||
@@ -19,10 +22,16 @@ The following devices have been tested and are fully working:
|
||||
| Keeplink | KP-9000-6XH-X2 / KP-9000-6XHML-X2 | No/Yes | [2M-PCB43-V1.2 / V2.1](devices/KP-9000-6XH-X2.md) | | 4 + 2 |
|
||||
| keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V2.2](devices/2M-PCB23-V2_2.md) | 2M | 8 + 1 |
|
||||
| keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V3.1](devices/2M-PCB23-V3_1.md) | 2M | 8 + 1 |
|
||||
| keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V3.2](devices/2M-PCB23-V3_2.md) | 2M | 8 + 1 |
|
||||
| LIANGUO | SWTG024AS | No | [SWTG024AS-v2.0-17452](devices/SWTG024AS.md) | 0.5M | 4 + 2 |
|
||||
| Lianguo | ZX-SWTGW215AS | Yes | [PCB-SWTG115AS-V2.0 / PCB-SWTG115AS-V2.1](devices/SWTGW215AS.md) | 2M | 5 + 1 |
|
||||
| Lianguo | HYWS-SGT0108S | No | [2G5F_20G_V1.01](devices/HYWS-SGT0108S.md) | 0.5M | 8 + 1 |
|
||||
| Mokerlink| 2G040210GSM | Yes | [2M-PCB43-V1.1](devices/2M-PCB43-V1.1.md) | | 4 + 2 |
|
||||
| Mokerlink| ZX-SWTGW218AS | Yes | [SWTG118AS-V2.0-16029](devices/SWTGW218AS.md) | 2M | 8 + 1 |
|
||||
| MokerLink| POE-2G080110GS | No | [2M-PCB51-ML-V1.1](devices/2M-PCB51-ML-V1_1.md) | 4M | 8 + 1 |
|
||||
| Binardat | 2G06-04210GSM | No | [PCB-SWTG024AS-A-2.0.1](devices/ONT-S207CW-62TS-SE.md) | 0.5M | 4 + 2 |
|
||||
| ONT | ONT-S207CW-62TS-SE | No | [PCB-SWTG024AS-A-2.0.1](devices/ONT-S207CW-62TS-SE.md) | 0.5M | 4 + 2 |
|
||||
| Netcore | GS9 | No | [PB-2132](devices/PB-2132.md) | 4M | 8 + 1 |
|
||||
| Ruiying | RY-4GT-2SX | No | [FG-4GT-2SX_V2.0](devices/FG-4GT-2SX_V2.0.md) | 4M | 4 + 2 |
|
||||
| Ruiying | RY-8GT-1SX | No | [FG-8GT-1SX](devices/FG-8GT-1SX.md) | 1M | 8 + 1 |
|
||||
| SiscoLink | SL-G0402F | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
|
||||
@@ -32,6 +41,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 |
|
||||
| Guangnianwei | 7018-2.5G | No | [F7008-2.5GPOE-V1.2](devices/F7008-2.5.md) | 8M | 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
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# Compressing the Web UI Assets
|
||||
|
||||
The Web UI (all files under `html/`) is embedded into the flash image and
|
||||
served over HTTP. Two build-time steps keep it small: a safe minifier and
|
||||
gzip compression of the embedded files.
|
||||
|
||||
## How it works
|
||||
|
||||
```
|
||||
html/ --(minify.py)--> output/html_min/ --(fileadder -z)--> flash image
|
||||
```
|
||||
|
||||
- `tools/minify.py` removes comments (`<!-- -->`, `//`, `/* */`) and
|
||||
line-leading whitespace without touching string or regex literals, so
|
||||
the output is functionally identical to the input. Binary files
|
||||
(`.ico`) are copied through unchanged. The raw sources in `html/` stay
|
||||
untouched for development; the minified copy in `output/` is a pure
|
||||
build artifact. Comments and indentation are pure overhead for the
|
||||
served bytes, so minification alone already reduces the transfer size
|
||||
from 109,814 to 97,730 bytes, independent of gzip.
|
||||
- The Web UI is a single-page application: `index.html` holds all pages
|
||||
as sections (`#ports`, `#vlan`, ...) that the sidebar switches
|
||||
between via the URL hash. `login.html` stays separate because it is
|
||||
the authentication gate 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
|
||||
final image. Both invocations run on `output/html_min/`, so the table
|
||||
and the image always agree.
|
||||
- The httpd serves each file with `Content-Encoding: gzip` according to
|
||||
the `gzip` flag in its `f_data` entry. The 8051 only copies compressed
|
||||
bytes from flash to the socket; decompression happens in the browser.
|
||||
- Only the static files are compressed. The JSON API responses
|
||||
(`/information.json`, `/vlanlist`, ...) are generated on the fly and
|
||||
stay uncompressed.
|
||||
|
||||
## Measured effect
|
||||
|
||||
Build on any machine (the Web UI is machine-independent):
|
||||
|
||||
| | bytes |
|
||||
|---|---:|
|
||||
| raw sources | 109,814 |
|
||||
| minified (comments and indentation removed) | 97,730 |
|
||||
| gzip (flash + wire) | 25,302 (23.0 %) |
|
||||
|
||||
The minified assets are also what the browser receives when gzip support
|
||||
is missing or disabled, so the transfer size drops in both stages:
|
||||
109,814 → 97,730 bytes from minification, then → 25,302 bytes from gzip.
|
||||
|
||||
The embedded data block ends at `0x5b977` without compression and at
|
||||
`0x462d6` with it, freeing ~86 KB of the 512 KB image.
|
||||
|
||||
The consolidation helps at every step. The 17 scripts gzip to 23,302
|
||||
bytes as separate files but to 17,231 bytes as one `main.js` bundle,
|
||||
because the gzip dictionary spans all of them. The single-page layout
|
||||
then removes the per-page HTML and, more importantly, the repeated
|
||||
downloads: with the multi-page UI the browser fetched `main.js` on every
|
||||
navigation; the SPA fetches everything once and section switches are
|
||||
pure in-page JavaScript (measured on a real device: cold load ~450 ms,
|
||||
section switch ~60-160 ms, the only network traffic afterwards is the
|
||||
JSON polling of the visible section).
|
||||
|
||||
## Notes
|
||||
|
||||
- The files stay well below the `uint16_t` size limit of the file table
|
||||
(largest gzip output: 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.
|
||||
- `version.h` is now written by a single atomic `printf` (change from
|
||||
upstream) instead of five separate `echo`s, so a parallel build
|
||||
(`make -j`) can no longer read a half-written copy. It stays in
|
||||
`.PHONY` so it is regenerated on every build.
|
||||
|
||||
## Verification
|
||||
|
||||
Decompress what the firmware embeds and compare it with the minified
|
||||
sources:
|
||||
|
||||
```python
|
||||
import gzip, re
|
||||
h = open('html_data.h').read(); c = open('html_data.c').read()
|
||||
starts = dict((k, int(v, 16)) for k, v in
|
||||
re.findall(r'#define FDATA_START_(\w+) 0x([0-9a-f]+)', h))
|
||||
sizes = dict((k, int(v)) for k, v in
|
||||
re.findall(r'#define FDATA_SIZE_(\w+) (\d+)', h))
|
||||
entries = [(p, sizes[z], starts[s]) for p, s, z in
|
||||
re.findall(r'\{"/([^"]+)", FDATA_START_(\w+), '
|
||||
r'FDATA_SIZE_(\w+), \w+, 1\}', c)]
|
||||
d = open('output/rtlplayground.bin', 'rb').read()
|
||||
for name, size, start in entries:
|
||||
assert gzip.decompress(d[start:start+size]) == \
|
||||
open('output/html_min/' + name, 'rb').read()
|
||||
```
|
||||
|
||||
`tools/httpd_sim` serves the raw files from `html/` and does not exercise
|
||||
the gzip path; a browser session against a flashed switch (or `curl
|
||||
--compressed`) is the final check.
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="bw_title">Ingress and Egress Bandwidth</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="bw_heading">Ingress and Egress Bandwidth</h1>
|
||||
<table id="bwtable">
|
||||
<tr> <th> </th> <th colspan="3" data-i18n="bw_ingress"> Ingress </th> <th colspan="2" data-i18n="bw_egress">Egress</th> <th></th></tr>
|
||||
<tr> <th data-i18n="bw_col_port">Port</th> <th data-i18n="bw_col_limit">Limit</th> <th data-i18n="bw_col_bandwidth">Bandwidth [kBit/s]</th> <th data-i18n="bw_col_flow">Flow Control</th> <th data-i18n="bw_col_limit">Limit</th> <th data-i18n="bw_col_bandwidth">Bandwidth [kBit/s]</th> <th data-i18n="bw_col_apply">Apply</th></tr>
|
||||
</table>
|
||||
<script src="/bandwidth.js"></script>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,138 +0,0 @@
|
||||
const iLayout = '" type="number" maxlength="10" size="10" onfocus="inputFocus(';
|
||||
function createBW() {
|
||||
var tbl = document.getElementById('bwtable');
|
||||
const limit = '<input type="checkbox" id="limit_port" onchange="exec();">'
|
||||
if (tbl.rows.length <= 2 && numPorts) {
|
||||
console.log("CREATING TABLE ", tbl.rows.length);
|
||||
for (let i = 2; i < 2 + numPorts; i++) {
|
||||
const tr = tbl.insertRow();
|
||||
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i-1)));
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = limit.replaceAll("limit_port", "ilimit_port_" + i).replace("exec()", "iClicked(" + i + ")");
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = t('bw_unlimited');
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = limit.replaceAll("limit_port", "fc_port_" + i).replace("exec()", "document.getElementById('bwapply_" + i + "').disabled=false;");
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = limit.replaceAll("limit_port", "elimit_port_" + i).replace("exec()", "eClicked(" + i + ")");
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = t('bw_unlimited');
|
||||
var button = '<button type="button" id="bwapply_' + i + '" style="margin: 0 0 0 24px" onclick="applyBandwidth(' + i + ');">' + t('bw_col_apply') + '</button>';
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = button;
|
||||
document.getElementById("bwapply_" + i).disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function iClicked(i)
|
||||
{
|
||||
document.getElementById("bwapply_" + i).disabled=false;
|
||||
var tbl = document.getElementById('bwtable');
|
||||
var tr = tbl.rows[i];
|
||||
if (!document.getElementById("ilimit_port_" + i).checked) {
|
||||
tr.cells[2].innerHTML = t('bw_unlimited');
|
||||
document.getElementById("fc_port_" + i).disabled = true;
|
||||
document.getElementById("fc_port_" + i).checked = true;
|
||||
} else {
|
||||
tr.cells[2].innerHTML = '<input id="ibw_' + i + iLayout + i + ')" value="0"/>';
|
||||
document.getElementById("fc_port_" + i).disabled = false;
|
||||
document.getElementById("fc_port_" + i).checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
function eClicked(i)
|
||||
{
|
||||
document.getElementById("bwapply_" + i).disabled=false;
|
||||
var tbl = document.getElementById('bwtable');
|
||||
var tr = tbl.rows[i];
|
||||
if (!document.getElementById("elimit_port_" + i).checked) {
|
||||
tr.cells[5].innerHTML = t('bw_unlimited');
|
||||
} else {
|
||||
tr.cells[5].innerHTML = '<input id="ebw_' + i + iLayout + i + ')" value="0"/>';
|
||||
}
|
||||
}
|
||||
|
||||
function inputFocus(i)
|
||||
{
|
||||
document.getElementById("bwapply_" + i).disabled=false;
|
||||
}
|
||||
|
||||
async function doCMD(cmd)
|
||||
{
|
||||
console.log("Sending >" + cmd + "<");
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function applyBandwidth(i) {
|
||||
var tbl = document.getElementById('bwtable');
|
||||
var tr = tbl.rows[i];
|
||||
var cmd = "bw in " + (i-1) + " off";
|
||||
if (document.getElementById("ilimit_port_" + i).checked)
|
||||
cmd = 'bw in ' + (i-1) + ' ' + parseInt(document.getElementById("ibw_" + i).value).toString(16).padStart(4, "0");;
|
||||
doCMD(cmd);
|
||||
if (document.getElementById("ilimit_port_" + i).checked) {
|
||||
if (!document.getElementById("fc_port_" + i).checked)
|
||||
cmd = "bw in " + (i-1) + " drop";
|
||||
doCMD(cmd);
|
||||
}
|
||||
var cmd = "bw out " + (i-1) + " off";
|
||||
if (document.getElementById("elimit_port_" + i).checked)
|
||||
cmd = 'bw out ' + (i-1) + ' ' + parseInt(document.getElementById("ebw_" + i).value).toString(16).padStart(4, "0");;
|
||||
doCMD(cmd);
|
||||
}
|
||||
|
||||
function getBW() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
console.log("IN getBW ");
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("BW: ", JSON.stringify(s));
|
||||
var tbl = document.getElementById('bwtable');
|
||||
if (tbl.rows.length > 2 && numPorts) {
|
||||
for (let i = 2; i < 2 + numPorts; i++) {
|
||||
p = s[i-2];
|
||||
let n = p.portNum;
|
||||
let tr = tbl.rows[n+1];
|
||||
if (!document.getElementById("bwapply_" + (n+1)).disabled)
|
||||
continue;
|
||||
console.log("Table Update row: " + i + " portNum is " + n + ", pState is " + pState[i-2] + ", row number is " + (n+1));
|
||||
let iBW = parseInt(p.iBW,16) * 16; let eBW = parseInt(p.eBW,16) * 16;
|
||||
document.getElementById("ilimit_port_" + (n+1)).checked = p.iLimited;
|
||||
document.getElementById("elimit_port_" + (n+1)).checked = p.eLimited;
|
||||
if (!p.iLimited) {
|
||||
tr.cells[2].innerHTML = t('bw_unlimited');
|
||||
} else {
|
||||
tr.cells[2].innerHTML = '<input id="ibw_' + (n+1) + iLayout + (n+1) + ')" value="' + iBW +'"/>';
|
||||
}
|
||||
if (!p.eLimited) {
|
||||
tr.cells[5].innerHTML = t('bw_unlimited');
|
||||
} else {
|
||||
tr.cells[5].innerHTML = '<input id="ebw_' + (n+1) + iLayout + (n+1) + ')" value="' + eBW +'"/>';
|
||||
}
|
||||
document.getElementById("fc_port_" + (n+1)).checked = p.iFC==1?true:false;
|
||||
document.getElementById("fc_port_" + (n+1)).disabled = p.iLimited==1?false:true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/bandwidth.json", true);
|
||||
xhttp.timeout = 1500; sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
createBW();
|
||||
getBW();
|
||||
const interval = setInterval(update, 2000);
|
||||
});
|
||||
});
|
||||
@@ -1,138 +0,0 @@
|
||||
var configInterval = Number();
|
||||
var configuration = [];
|
||||
const conf_cmds = [
|
||||
/^ip\s+(\d{1,3}\.){3}\d{1,3}$/,
|
||||
/^ip\s+dhcp$/,
|
||||
/^gw\s+(\d{1,3}\.){3}\d{1,3}$/,
|
||||
/^netmask\s+(\d{1,3}\.){3}\d{1,3}$/,
|
||||
/^syslog\s+(on|off)$/,
|
||||
/^syslog\s+ip\s+(\d{1,3}\.){3}\d{1,3}$/,
|
||||
/^syslog\s+port\s+\d{1,5}$/,
|
||||
/^passwd\s+\S+$/,
|
||||
/^vlan\s+\d{1,4}\s+d$/,
|
||||
/^vlan\s+\d{1,4}\s+mgmt$/,
|
||||
/^vlan\s+\d{1,4}(\s+[a-zA-Z]\w*)?(\s+[1-9]t?)+$/,
|
||||
/^pvid\s+\d{1,2}\s+\d{1,4}$/,
|
||||
/^ingress(\s+\d{1,2}[tua])+$/,
|
||||
/^ingress\s+[tua]$/,
|
||||
/^port\s+\d{1,2}\s+(10m|100m|1g|2g5|5g|10g|auto|on|off)(\s+(half|full))?$/,
|
||||
/^port\s+\d{1,2}\s+name\s+\S+$/,
|
||||
/^eee(\s+\d{1,2})?\s+(on|off)$/,
|
||||
/^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/,
|
||||
/^lag\s+\d(\s+\d{1,2})+$/,
|
||||
/^laghash\s+\d(\s+\w+)+$/,
|
||||
/^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/,
|
||||
/^stp\s+(on|off)$/,
|
||||
/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d{1,2}$/,
|
||||
/^stp\s+version\s+(rstp|stp)$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+(on|off)$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+edge\s+(on|off|auto)$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+cost\s+\d{1,9}$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+prio\s+\d{1,3}$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+guard\s+(none|bpdu|root)$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+filter\s+(on|off)$/,
|
||||
/^stp\s+port\s+\d{1,2}\s+p2p\s+(auto|on|off)$/,
|
||||
/^igmp\s+(on|off)$/,
|
||||
/^mtu\s+\d{1,2}\s+\d+$/,
|
||||
/^bw\s+(in|out)\s+\d{1,2}\s+\S+$/,
|
||||
/^hostname\s+.{1,23}$/,
|
||||
];
|
||||
/* Commands that come in an on/off pair replace each other, which the list
|
||||
* below cannot express: it drops lines starting with the text it matched, and
|
||||
* "syslog off" does not start with "syslog on". Naming the stem separately
|
||||
* keeps the pair collapsed without widening the match to the whole family. */
|
||||
const conf_toggle = [
|
||||
/^(syslog)\s+(?:on|off)$/,
|
||||
];
|
||||
|
||||
const conf_overwrite = [
|
||||
/^ip\b/,
|
||||
/^gw\b/,
|
||||
/^netmask\b/,
|
||||
/^syslog\s+ip\b/,
|
||||
/^syslog\s+port\b/,
|
||||
/^syslog\s+(on|off)$/,
|
||||
/^passwd\b/,
|
||||
/^vlan\s+\d{1,4}\s+mgmt$/,
|
||||
/^vlan\s+\d{1,4}(?!\s+mgmt\b)/,
|
||||
/^pvid\s+\d{1,2}\b/,
|
||||
/^ingress\b/,
|
||||
/^port\s+\d{1,2}(?!\s+name\b)/,
|
||||
/^port\s+\d{1,2}\s+name\b/,
|
||||
/^eee\s+\d{1,2}\b/,
|
||||
/^eee\b/,
|
||||
/^mirror\b/,
|
||||
/^lag\s+\d+\b/,
|
||||
/^laghash\b/,
|
||||
/^isolate\s+\d{1,2}\b/,
|
||||
/^stp\s+(prio|hello|maxage|fwd|txhold|version)\b/,
|
||||
/^stp\s+port\s+\d{1,2}\s+(edge|cost|prio|guard|filter|p2p)\b/,
|
||||
/^igmp\b/,
|
||||
/^mtu\s+\d{1,2}\b/,
|
||||
/^bw\s+(in|out)\s+\d{1,2}\b/,
|
||||
/^hostname\b/,
|
||||
];
|
||||
|
||||
function parseConf(s){
|
||||
var a = s.split(/\r\n|\n/);
|
||||
for (var l = 0; l < a.length; l++) {
|
||||
var line = a[l].trim().replace(/\s+/g, ' ');
|
||||
if (!line.length) continue;
|
||||
const deleteMatch = line.match(/^vlan\s+(\d{1,4})\s+d$/);
|
||||
if (deleteMatch) {
|
||||
const prefix = "vlan " + deleteMatch[1] + " ";
|
||||
configuration = configuration.filter(c => !c.startsWith(prefix));
|
||||
continue;
|
||||
}
|
||||
console.log(l + ' --> ' + line);
|
||||
var ignore = true;
|
||||
for (const x of conf_cmds)
|
||||
if (x.test(line)) { ignore = false; break; }
|
||||
if (ignore) continue;
|
||||
for (const x of conf_toggle) {
|
||||
const t = line.match(x);
|
||||
if (t) {
|
||||
configuration = configuration.filter(item => item !== t[1] + " on" && item !== t[1] + " off");
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const x of conf_overwrite) {
|
||||
if (x.test(line)) {
|
||||
let m = line.match(x);
|
||||
let matchStr = m[0];
|
||||
configuration = configuration.filter(item =>
|
||||
!(item === matchStr || (item.startsWith(matchStr + " ") && !item.endsWith(" mgmt") && !item.startsWith(matchStr + " name "))));
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Only one management VLAN can be active, so drop any previous mgmt entry
|
||||
if (/^vlan\s+\d{1,4}\s+mgmt$/.test(line))
|
||||
configuration = configuration.filter(item => !/^vlan\s+\d{1,4}\s+mgmt$/.test(item));
|
||||
configuration.push(line);
|
||||
}
|
||||
console.log("Configuration now:");
|
||||
for (const x of configuration) { console.log(x); }
|
||||
}
|
||||
|
||||
async function fetchConfig() {
|
||||
try {
|
||||
const response = await fetch('/config');
|
||||
console.log("CONFIG: ", response);
|
||||
const t = await response.text();
|
||||
return t;
|
||||
} catch(err) {
|
||||
console.error("Error: ", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCmdLog() {
|
||||
try {
|
||||
const response = await fetch('/cmd_log');
|
||||
console.log("CMD-Log: ", response);
|
||||
const t = await response.text();
|
||||
return t;
|
||||
} catch(err) {
|
||||
console.error("Error: ", err);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="eee_title">EEE Configuration</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="eee_heading">EEE Status</h1>
|
||||
<table id="eeetable">
|
||||
<tr> <th> </th> <th colspan="3" data-i18n="eee_advertising"> Advertising </th> <th colspan="3" data-i18n="eee_partner">Link-Partner advertises</th> <th></th></tr>
|
||||
<tr> <th data-i18n="eee_port">Port</th> <th>2.5G</th> <th>1G</th> <th>100M</th> <th>2.5G</th> <th>1G</th> <th>100M</th> <th data-i18n="eee_active">Active?</th></tr>
|
||||
</table>
|
||||
<div>
|
||||
<input style="width:20%;" class="action" id="eee_enable" onclick="eeeSub(0, 1);" type="button" data-i18n="eee_enable" value="Enable EEE">
|
||||
<input style="width:20%;" class="action" id="eee_disable" onclick="eeeSub(0, 0);" type="button" data-i18n="eee_disable" value="Disable EEE">
|
||||
</div>
|
||||
<script src="/eee.js"></script>
|
||||
<script src="/eee_sub.js"></script>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,52 +0,0 @@
|
||||
function createEEE() {
|
||||
var tbl = document.getElementById('eeetable');
|
||||
if (tbl.rows.length <= 2 && numPorts) {
|
||||
console.log("CREATING TABLE ", tbl.rows.length);
|
||||
for (let i = 2; i < 2 + numPorts; i++) {
|
||||
console.log("Table row: " + i + "pState: " + pState[i-2]);
|
||||
const tr = tbl.insertRow();
|
||||
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i-1)));
|
||||
for (let j = 0; j < 7; j++) {
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(" "));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getEEE() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("EEE: ", JSON.stringify(s));
|
||||
var tbl = document.getElementById('eeetable');
|
||||
if (tbl.rows.length > 2 && numPorts) {
|
||||
for (let i = 2; i < 2 + numPorts; i++) {
|
||||
p = s[i-2];
|
||||
let n = p.portNum;
|
||||
console.log("Table Update row: " + i + " portNum is " + n + ", pState is " + pState[i-2]);
|
||||
let tr = tbl.rows[n+1];
|
||||
if (!p.isSFP) {
|
||||
let eee = parseInt(p.eee,2); let lp = parseInt(p.eee_lp,2);
|
||||
tr.cells[1].innerHTML = `${eee&4?t('eee_on'):t('eee_off')}`; tr.cells[2].innerHTML = `${eee&2?t('eee_on'):t('eee_off')}`; tr.cells[3].innerHTML = `${eee&1?t('eee_on'):t('eee_off')}`;
|
||||
tr.cells[4].innerHTML = `${lp&4?t('eee_on'):t('eee_off')}`; tr.cells[5].innerHTML = `${lp&2?t('eee_on'):t('eee_off')}`; tr.cells[6].innerHTML = `${lp&1?t('eee_on'):t('eee_off')}`;
|
||||
tr.cells[7].innerHTML = `${p.active}`;
|
||||
tr.classList.toggle('disabled', pState[i-2] < 0); tr.classList.toggle('isNOK', !p.active); tr.classList.toggle('isOK', p.active);
|
||||
}
|
||||
tr.classList.toggle('isSFP', p.isSFP);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/eee.json", true);
|
||||
xhttp.timeout = 1500; sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
createEEE();
|
||||
getEEE();
|
||||
const interval = setInterval(update, 2000);
|
||||
const iCount = setInterval(getEEE, 2000);
|
||||
});
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
async function eeeSub(port, enable) {
|
||||
var cmd = "eee ";
|
||||
if (enable)
|
||||
cmd = cmd + "on";
|
||||
else
|
||||
cmd = cmd + "off";
|
||||
console.log("eeeSub port " + port, ", value " + enable);
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,609 +0,0 @@
|
||||
var LANG = {
|
||||
en: {
|
||||
nav_overview: 'Overview',
|
||||
nav_port_config: 'Port Configuration',
|
||||
nav_port_stat: 'Port Statistics',
|
||||
nav_l2: 'L2 Configuration',
|
||||
nav_mirror: 'Mirroring',
|
||||
nav_lag: 'Link Aggregation',
|
||||
nav_eee: 'EEE',
|
||||
nav_bandwidth: 'Bandwidth Limits',
|
||||
nav_system: 'System Settings',
|
||||
nav_fw_update: 'Firmware Update',
|
||||
|
||||
port_name: 'Name',
|
||||
port_status: 'Status',
|
||||
port_not_enabled: 'Not enabled.',
|
||||
port_link_speed: 'Link speed',
|
||||
port_vendor: 'Vendor',
|
||||
port_model: 'Model',
|
||||
port_serial: 'Serial',
|
||||
port_temp: 'Temp',
|
||||
port_vcc: 'Vcc',
|
||||
port_tx_fault: 'TX-Fault',
|
||||
port_tx_disabled: 'TX-Disabled',
|
||||
port_tx_bias: 'TX-Bias',
|
||||
port_tx_power: 'TX-Power',
|
||||
port_rx_power: 'RX-Power',
|
||||
port_rx_los: 'RX-LOS',
|
||||
|
||||
speed_disabled: 'Disabled',
|
||||
speed_down: 'Down',
|
||||
|
||||
port_title: 'FreeSwitchOS Port Configuration',
|
||||
port_heading: 'Port Configuration',
|
||||
port_col_port: 'Port',
|
||||
port_col_name: 'Name',
|
||||
port_col_speed: 'Current Link Speed',
|
||||
port_col_set_speed: 'Set Speed',
|
||||
port_col_disabled: 'Disabled',
|
||||
port_col_devices: 'Connected devices',
|
||||
port_devices: 'devices',
|
||||
port_col_apply: 'Apply',
|
||||
port_mtu_heading: 'Configure Maximum Frame Size (MTU) forwarded at Port',
|
||||
port_auto: 'Auto',
|
||||
port_2500m: '2500MBit/Full',
|
||||
port_1000m: '1000MBit/Full',
|
||||
port_100m_f: '100MBit/Full',
|
||||
port_100m_h: '100MBit/Half',
|
||||
port_10m_f: '10MBit/Full',
|
||||
port_10m_h: '10MBit/Half',
|
||||
port_apply: 'Apply',
|
||||
|
||||
stat_title: 'FreeSwitchOS Port Statistics',
|
||||
stat_heading: 'Port Statistics',
|
||||
stat_detailed: 'Detailed Port Statistics',
|
||||
stat_close: 'Close',
|
||||
stat_col_port: 'Port',
|
||||
stat_col_name: 'Name',
|
||||
stat_col_link: 'link',
|
||||
stat_col_tx_good: 'TX Good',
|
||||
stat_col_tx_bad: 'TX Bad',
|
||||
stat_col_rx_good: 'RX Good',
|
||||
stat_col_rx_bad: 'RX Bad',
|
||||
stat_col_all: 'All Counters',
|
||||
stat_counter: 'Counter',
|
||||
stat_value: 'Value',
|
||||
stat_show: 'Show',
|
||||
|
||||
vlan_title: 'FreeSwitchOS VLAN Configuration',
|
||||
vlan_heading: 'VLAN Configuration',
|
||||
vlan_select: 'VLAN Select:',
|
||||
vlan_choose: '— VLAN Choose —',
|
||||
vlan_id: 'VLAN ID:',
|
||||
vlan_get_config: 'Get Configuration',
|
||||
vlan_name: 'VLAN Name:',
|
||||
vlan_tagged: 'Tagged Ports',
|
||||
vlan_untagged: 'Untagged Ports',
|
||||
vlan_select_all: 'Select all',
|
||||
vlan_pvid: 'Use as default VLAN for incoming traffic (PVID)',
|
||||
vlan_update: 'Update / Create',
|
||||
vlan_configured: 'Configured VLANs',
|
||||
vlan_col_name: 'Name',
|
||||
vlan_col_member: 'Member Ports',
|
||||
vlan_col_tagged: 'Tagged Ports',
|
||||
vlan_col_untagged: 'Untagged Ports',
|
||||
vlan_col_pvid: 'PVID Ports',
|
||||
vlan_col_delete: 'Delete',
|
||||
vlan_set_id_first: 'Set VLAN ID first',
|
||||
vlan_delete_confirm: 'Delete VLAN ',
|
||||
|
||||
lag_title: 'Link Aggregation Configuration',
|
||||
lag_heading: 'Link Aggregation Groups Configuration',
|
||||
lag_update: 'Update / Create',
|
||||
|
||||
mirror_title: 'Mirror Configuration',
|
||||
mirror_heading: 'Mirror Configuration',
|
||||
mirror_enabled: 'Enabled:',
|
||||
mirror_port: 'Mirroring Port:',
|
||||
mirror_tx: 'Mirrored Ports (TX)',
|
||||
mirror_rx: 'Mirrored Ports (RX)',
|
||||
mirror_update: 'Update / Create',
|
||||
mirror_disable: 'Disable Mirroring',
|
||||
mirror_set_port_first: 'Set Mirroring Port first',
|
||||
mirror_select_ports: 'Select Mirrored Ports',
|
||||
|
||||
eee_title: 'EEE Configuration',
|
||||
eee_heading: 'EEE Status',
|
||||
eee_advertising: 'Advertising',
|
||||
eee_partner: 'Link-Partner advertises',
|
||||
eee_port: 'Port',
|
||||
eee_active: 'Active?',
|
||||
eee_enable: 'Enable EEE',
|
||||
eee_disable: 'Disable EEE',
|
||||
eee_on: 'ON',
|
||||
eee_off: 'OFF',
|
||||
|
||||
l2_title: 'FreeSwitchOS L2 Configuration',
|
||||
l2_heading: 'L2 Configuration',
|
||||
l2_col_port: 'Port',
|
||||
l2_col_type: 'Type',
|
||||
l2_col_remove: 'Remove Entry',
|
||||
l2_shown: 'Shown:',
|
||||
l2_delete: 'Delete',
|
||||
l2_static: 'static',
|
||||
l2_learned: 'learned',
|
||||
|
||||
bw_title: 'Ingress and Egress Bandwidth',
|
||||
bw_heading: 'Ingress and Egress Bandwidth',
|
||||
bw_ingress: 'Ingress',
|
||||
bw_egress: 'Egress',
|
||||
bw_col_port: 'Port',
|
||||
bw_col_limit: 'Limit',
|
||||
bw_col_bandwidth: 'Bandwidth [kBit/s]',
|
||||
bw_col_flow: 'Flow Control',
|
||||
bw_col_apply: 'Apply',
|
||||
bw_unlimited: 'UNLIMITED',
|
||||
|
||||
sys_title: 'System Settings',
|
||||
sys_tab_system: 'System',
|
||||
sys_tab_advanced: 'Advanced',
|
||||
sys_tab_console: 'Console',
|
||||
sys_heading: 'System Settings',
|
||||
sys_ip: 'IP address:',
|
||||
sys_model: 'Model:',
|
||||
sys_hostname: 'Hostname:',
|
||||
sys_apply: 'Apply',
|
||||
sys_netmask: 'Netmask:',
|
||||
sys_gateway: 'Gateway:',
|
||||
sys_language: 'Language:',
|
||||
sys_mgmt_vlan: 'Management VLAN:',
|
||||
sys_mgmt_untagged: 'untagged',
|
||||
sys_mgmt_confirm: 'Move switch management to VLAN ',
|
||||
sys_mgmt_warn: 'The switch will start tagging its own traffic with that VLAN. If the port you are connected through does not carry it, this page becomes unreachable and the setting can only be undone over the console. Continue?',
|
||||
sys_ip_note: 'When updating the above settings, remember to point your browser to the new IP afterwards:',
|
||||
sys_update: 'Update Settings',
|
||||
sys_save_label: 'Save all current settings to Flash:',
|
||||
sys_save: 'Save Settings to Flash',
|
||||
sys_advanced: 'Advanced Settings',
|
||||
sys_startup_config: 'Startup configuration:',
|
||||
sys_startup_warn: 'Be careful when saving the directly edited startup configuration, you can lock yourself out:',
|
||||
sys_clear_config: 'Clear Startup Config',
|
||||
sys_save_startup: 'Save Startup Settings to Flash',
|
||||
sys_reset: 'Reset Switch',
|
||||
sys_console: 'Console Command',
|
||||
sys_enter_cmd: 'Enter command:',
|
||||
sys_send_cmd: 'Send Command',
|
||||
sys_console_warn: 'Be careful when entering console commands, you can lock yourself out!',
|
||||
sys_invalid_ip: 'Invalid ip:',
|
||||
sys_reset_confirm: 'Are you sure you want to reset the switch?',
|
||||
sys_resetting: 'Switch is resetting. Please wait and refresh the page.',
|
||||
|
||||
login_title: 'RTL Switch Login',
|
||||
login_heading: 'RTL Switch Login',
|
||||
login_wrong: 'Wrong password!',
|
||||
login_password: 'Password',
|
||||
login_login: 'Login',
|
||||
|
||||
index_title: 'FreeSwitchOS Main Page',
|
||||
index_heading: 'Switch Configuration',
|
||||
index_settings: 'Settings',
|
||||
|
||||
update_title: 'Firmware update',
|
||||
update_heading: 'Firmware Update',
|
||||
update_instruction: 'Choose a firmware update file to upload:',
|
||||
update_upload: 'Upload File',
|
||||
|
||||
common_port: 'Port ',
|
||||
common_pkts: ' pkts',
|
||||
},
|
||||
|
||||
ja: {
|
||||
nav_overview: '概要',
|
||||
nav_port_config: 'ポート設定',
|
||||
nav_port_stat: 'ポート統計',
|
||||
nav_l2: 'L2 設定',
|
||||
nav_mirror: 'ミラーリング',
|
||||
nav_lag: 'リンクアグリゲーション',
|
||||
nav_eee: 'EEE',
|
||||
nav_bandwidth: '帯域制限',
|
||||
nav_system: 'システム設定',
|
||||
nav_fw_update: 'ファームウェア更新',
|
||||
|
||||
port_name: '名前',
|
||||
port_status: '状態',
|
||||
port_not_enabled: '無効',
|
||||
port_link_speed: 'リンク速度',
|
||||
port_vendor: 'ベンダー',
|
||||
port_model: 'モデル',
|
||||
port_serial: 'シリアル',
|
||||
port_temp: '温度',
|
||||
port_vcc: '電圧',
|
||||
port_tx_fault: 'TX 障害',
|
||||
port_tx_disabled: 'TX 無効',
|
||||
port_tx_bias: 'TX バイアス',
|
||||
port_tx_power: 'TX 電力',
|
||||
port_rx_power: 'RX 電力',
|
||||
port_rx_los: 'RX 信号ロス',
|
||||
|
||||
speed_disabled: '無効',
|
||||
speed_down: 'リンクダウン',
|
||||
|
||||
port_title: 'FreeSwitchOS ポート設定',
|
||||
port_heading: 'ポート設定',
|
||||
port_col_port: 'ポート',
|
||||
port_col_name: '名前',
|
||||
port_col_speed: '現在のリンク速度',
|
||||
port_col_set_speed: '速度設定',
|
||||
port_col_disabled: '無効',
|
||||
port_col_devices: '接続デバイス',
|
||||
port_devices: 'デバイス',
|
||||
port_col_apply: '適用',
|
||||
port_mtu_heading: 'ポートの最大フレームサイズ (MTU) 設定',
|
||||
port_auto: '自動',
|
||||
port_2500m: '2500Mbps/全二重',
|
||||
port_1000m: '1000Mbps/全二重',
|
||||
port_100m_f: '100Mbps/全二重',
|
||||
port_100m_h: '100Mbps/半二重',
|
||||
port_10m_f: '10Mbps/全二重',
|
||||
port_10m_h: '10Mbps/半二重',
|
||||
port_apply: '適用',
|
||||
|
||||
stat_title: 'FreeSwitchOS ポート統計',
|
||||
stat_heading: 'ポート統計',
|
||||
stat_detailed: '詳細ポート統計',
|
||||
stat_close: '閉じる',
|
||||
stat_col_port: 'ポート',
|
||||
stat_col_name: '名前',
|
||||
stat_col_link: 'リンク',
|
||||
stat_col_tx_good: 'TX 正常',
|
||||
stat_col_tx_bad: 'TX 異常',
|
||||
stat_col_rx_good: 'RX 正常',
|
||||
stat_col_rx_bad: 'RX 異常',
|
||||
stat_col_all: '全カウンタ',
|
||||
stat_counter: 'カウンタ',
|
||||
stat_value: '値',
|
||||
stat_show: '表示',
|
||||
|
||||
vlan_title: 'FreeSwitchOS VLAN 設定',
|
||||
vlan_heading: 'VLAN 設定',
|
||||
vlan_select: 'VLAN 選択:',
|
||||
vlan_choose: '— VLAN 選択 —',
|
||||
vlan_id: 'VLAN ID:',
|
||||
vlan_get_config: '設定取得',
|
||||
vlan_name: 'VLAN 名:',
|
||||
vlan_tagged: 'タグ付きポート',
|
||||
vlan_untagged: 'タグ無しポート',
|
||||
vlan_select_all: 'すべて選択',
|
||||
vlan_pvid: '受信トラフィックのデフォルト VLAN (PVID)',
|
||||
vlan_update: '更新 / 作成',
|
||||
vlan_configured: '設定済み VLAN',
|
||||
vlan_col_name: '名前',
|
||||
vlan_col_member: 'メンバーポート',
|
||||
vlan_col_tagged: 'タグ付きポート',
|
||||
vlan_col_untagged: 'タグ無しポート',
|
||||
vlan_col_pvid: 'PVID ポート',
|
||||
vlan_col_delete: '削除',
|
||||
vlan_set_id_first: 'VLAN ID を先に設定してください',
|
||||
vlan_delete_confirm: 'VLAN 削除 ',
|
||||
|
||||
lag_title: 'リンクアグリゲーション設定',
|
||||
lag_heading: 'リンクアグリゲーショングループ設定',
|
||||
lag_update: '更新 / 作成',
|
||||
|
||||
mirror_title: 'ミラーリング設定',
|
||||
mirror_heading: 'ミラーリング設定',
|
||||
mirror_enabled: '有効:',
|
||||
mirror_port: 'ミラーポート:',
|
||||
mirror_tx: 'ミラー元ポート (TX)',
|
||||
mirror_rx: 'ミラー元ポート (RX)',
|
||||
mirror_update: '更新 / 作成',
|
||||
mirror_disable: 'ミラーリング無効化',
|
||||
mirror_set_port_first: 'ミラーポートを先に設定してください',
|
||||
mirror_select_ports: 'ミラー元ポートを選択してください',
|
||||
|
||||
eee_title: 'EEE 設定',
|
||||
eee_heading: 'EEE 状態',
|
||||
eee_advertising: 'EEE アドバタイジング',
|
||||
eee_partner: 'リンクパートナー広告',
|
||||
eee_port: 'ポート',
|
||||
eee_active: '有効?',
|
||||
eee_enable: 'EEE 有効化',
|
||||
eee_disable: 'EEE 無効化',
|
||||
eee_on: 'オン',
|
||||
eee_off: 'オフ',
|
||||
|
||||
l2_title: 'FreeSwitchOS L2 設定',
|
||||
l2_heading: 'L2 設定',
|
||||
l2_col_port: 'ポート',
|
||||
l2_col_type: 'タイプ',
|
||||
l2_col_remove: 'エントリ削除',
|
||||
l2_shown: 'Shown:',
|
||||
l2_delete: '削除',
|
||||
l2_static: '静的',
|
||||
l2_learned: '学習',
|
||||
|
||||
bw_title: '入力/出力帯域制限',
|
||||
bw_heading: '入力/出力帯域制限',
|
||||
bw_ingress: '入力',
|
||||
bw_egress: '出力',
|
||||
bw_col_port: 'ポート',
|
||||
bw_col_limit: '制限',
|
||||
bw_col_bandwidth: '帯域 [kbps]',
|
||||
bw_col_flow: 'フロー制御',
|
||||
bw_col_apply: '適用',
|
||||
bw_unlimited: '制限無し',
|
||||
|
||||
sys_title: 'システム設定',
|
||||
sys_tab_system: 'システム',
|
||||
sys_tab_advanced: '詳細設定',
|
||||
sys_tab_console: 'コンソール',
|
||||
sys_heading: 'システム設定',
|
||||
sys_ip: 'IP アドレス:',
|
||||
sys_model: 'モデル:',
|
||||
sys_hostname: 'ホスト名:',
|
||||
sys_apply: '適用',
|
||||
sys_netmask: 'ネットマスク:',
|
||||
sys_gateway: 'ゲートウェイ:',
|
||||
sys_language: '言語:',
|
||||
sys_mgmt_vlan: 'Management VLAN:',
|
||||
sys_mgmt_untagged: 'untagged',
|
||||
sys_mgmt_confirm: 'Move switch management to VLAN ',
|
||||
sys_mgmt_warn: 'The switch will start tagging its own traffic with that VLAN. If the port you are connected through does not carry it, this page becomes unreachable and the setting can only be undone over the console. Continue?',
|
||||
sys_ip_note: '上記設定を変更した場合は、ブラウザで新しい IP にアクセスしてください:',
|
||||
sys_update: '設定更新',
|
||||
sys_save_label: '現在の設定をフラッシュに保存:',
|
||||
sys_save: '設定をフラッシュに保存',
|
||||
sys_advanced: '詳細設定',
|
||||
sys_startup_config: '起動設定:',
|
||||
sys_startup_warn: '起動設定を直接編集する際は注意してください。ロックアウトされる可能性があります:',
|
||||
sys_clear_config: '起動設定クリア',
|
||||
sys_save_startup: '起動設定をフラッシュに保存',
|
||||
sys_reset: 'スイッチ再起動',
|
||||
sys_console: 'コンソールコマンド',
|
||||
sys_enter_cmd: 'コマンド入力:',
|
||||
sys_send_cmd: 'コマンド送信',
|
||||
sys_console_warn: 'コンソールコマンドは注意して入力してください。ロックアウトされる可能性があります!',
|
||||
sys_invalid_ip: '無効な IP: ',
|
||||
sys_reset_confirm: 'スイッチを再起動してもよろしいですか?',
|
||||
sys_resetting: 'スイッチを再起動中です。しばらく待ってからページをリロードしてください。',
|
||||
|
||||
login_title: 'RTL スイッチ ログイン',
|
||||
login_heading: 'RTL スイッチ ログイン',
|
||||
login_wrong: 'パスワードが違います!',
|
||||
login_password: 'パスワード',
|
||||
login_login: 'ログイン',
|
||||
|
||||
index_title: 'FreeSwitchOS メインページ',
|
||||
index_heading: 'スイッチ設定',
|
||||
index_settings: '設定',
|
||||
|
||||
update_title: 'ファームウェア更新',
|
||||
update_heading: 'ファームウェア更新',
|
||||
update_instruction: 'アップロードするファームウェアファイルを選択:',
|
||||
update_upload: 'ファイルをアップロード',
|
||||
|
||||
common_port: 'ポート ',
|
||||
common_pkts: ' pkts',
|
||||
},
|
||||
|
||||
zh: {
|
||||
nav_overview: '概览',
|
||||
nav_port_config: '端口配置',
|
||||
nav_port_stat: '端口统计',
|
||||
nav_l2: 'L2 配置',
|
||||
nav_mirror: '端口镜像',
|
||||
nav_lag: '链路聚合',
|
||||
nav_eee: 'EEE',
|
||||
nav_bandwidth: '带宽限制',
|
||||
nav_system: '系统设置',
|
||||
nav_fw_update: '固件升级',
|
||||
|
||||
port_name: '名称',
|
||||
port_status: '状态',
|
||||
port_not_enabled: '未启用。',
|
||||
port_link_speed: '链路速率',
|
||||
port_vendor: '厂商',
|
||||
port_model: '型号',
|
||||
port_serial: '序列号',
|
||||
port_temp: '温度',
|
||||
port_vcc: '供电电压',
|
||||
port_tx_fault: 'TX 故障',
|
||||
port_tx_disabled: 'TX 禁用',
|
||||
port_tx_bias: 'TX 偏置电流',
|
||||
port_tx_power: 'TX 光功率',
|
||||
port_rx_power: 'RX 光功率',
|
||||
port_rx_los: 'RX 信号丢失',
|
||||
|
||||
speed_disabled: '禁用',
|
||||
speed_down: '未连接',
|
||||
|
||||
port_title: 'FreeSwitchOS 端口配置',
|
||||
port_heading: '端口配置',
|
||||
port_col_port: '端口',
|
||||
port_col_name: '名称',
|
||||
port_col_speed: '当前链路速率',
|
||||
port_col_set_speed: '设置速率',
|
||||
port_col_disabled: '禁用',
|
||||
port_col_devices: '已连接设备',
|
||||
port_devices: '台设备',
|
||||
port_col_apply: '应用',
|
||||
port_mtu_heading: '配置端口转发的最大帧大小 (MTU)',
|
||||
port_auto: '自动',
|
||||
port_2500m: '2500Mbps/全双工',
|
||||
port_1000m: '1000Mbps/全双工',
|
||||
port_100m_f: '100Mbps/全双工',
|
||||
port_100m_h: '100Mbps/半双工',
|
||||
port_10m_f: '10Mbps/全双工',
|
||||
port_10m_h: '10Mbps/半双工',
|
||||
port_apply: '应用',
|
||||
|
||||
stat_title: 'FreeSwitchOS 端口统计',
|
||||
stat_heading: '端口统计',
|
||||
stat_detailed: '详细端口统计',
|
||||
stat_close: '关闭',
|
||||
stat_col_port: '端口',
|
||||
stat_col_name: '名称',
|
||||
stat_col_link: '链路',
|
||||
stat_col_tx_good: 'TX 正常包',
|
||||
stat_col_tx_bad: 'TX 错误包',
|
||||
stat_col_rx_good: 'RX 正常包',
|
||||
stat_col_rx_bad: 'RX 错误包',
|
||||
stat_col_all: '所有计数器',
|
||||
stat_counter: '计数器',
|
||||
stat_value: '值',
|
||||
stat_show: '查看',
|
||||
|
||||
vlan_title: 'FreeSwitchOS VLAN 配置',
|
||||
vlan_heading: 'VLAN 配置',
|
||||
vlan_select: 'VLAN 选择:',
|
||||
vlan_choose: '-- 请选择 VLAN --',
|
||||
vlan_id: 'VLAN ID:',
|
||||
vlan_get_config: '获取配置',
|
||||
vlan_name: 'VLAN 名称:',
|
||||
vlan_tagged: 'Tagged 端口',
|
||||
vlan_untagged: 'Untagged 端口',
|
||||
vlan_select_all: '全选',
|
||||
vlan_pvid: '作为入方向流量的默认 VLAN (PVID)',
|
||||
vlan_update: '更新 / 创建',
|
||||
vlan_configured: '已配置 VLAN',
|
||||
vlan_col_name: '名称',
|
||||
vlan_col_member: '成员端口',
|
||||
vlan_col_tagged: 'Tagged 端口',
|
||||
vlan_col_untagged: 'Untagged 端口',
|
||||
vlan_col_pvid: 'PVID 端口',
|
||||
vlan_col_delete: '删除',
|
||||
vlan_set_id_first: '请先设置 VLAN ID',
|
||||
vlan_delete_confirm: '删除 VLAN ',
|
||||
|
||||
lag_title: '链路聚合配置',
|
||||
lag_heading: '链路聚合组配置',
|
||||
lag_update: '更新 / 创建',
|
||||
|
||||
mirror_title: '端口镜像配置',
|
||||
mirror_heading: '端口镜像配置',
|
||||
mirror_enabled: '启用:',
|
||||
mirror_port: '镜像目的端口:',
|
||||
mirror_tx: '被镜像端口 (TX)',
|
||||
mirror_rx: '被镜像端口 (RX)',
|
||||
mirror_update: '更新 / 创建',
|
||||
mirror_disable: '禁用端口镜像',
|
||||
mirror_set_port_first: '请先设置镜像目的端口',
|
||||
mirror_select_ports: '请选择被镜像端口',
|
||||
|
||||
eee_title: 'EEE 配置',
|
||||
eee_heading: 'EEE 状态',
|
||||
eee_advertising: '本端通告',
|
||||
eee_partner: '链路伙伴通告',
|
||||
eee_port: '端口',
|
||||
eee_active: '已生效?',
|
||||
eee_enable: '启用 EEE',
|
||||
eee_disable: '禁用 EEE',
|
||||
eee_on: '开',
|
||||
eee_off: '关',
|
||||
|
||||
l2_title: 'FreeSwitchOS L2 配置',
|
||||
l2_heading: 'L2 配置',
|
||||
l2_col_port: '端口',
|
||||
l2_col_type: '类型',
|
||||
l2_col_remove: '删除条目',
|
||||
l2_shown: 'Shown:',
|
||||
l2_delete: '删除',
|
||||
l2_static: '静态',
|
||||
l2_learned: '动态学习',
|
||||
|
||||
bw_title: '入方向/出方向带宽限制',
|
||||
bw_heading: '入方向/出方向带宽限制',
|
||||
bw_ingress: '入方向',
|
||||
bw_egress: '出方向',
|
||||
bw_col_port: '端口',
|
||||
bw_col_limit: '限速',
|
||||
bw_col_bandwidth: '带宽 [kbit/s]',
|
||||
bw_col_flow: '流量控制',
|
||||
bw_col_apply: '应用',
|
||||
bw_unlimited: '不限速',
|
||||
|
||||
sys_title: '系统设置',
|
||||
sys_tab_system: '系统',
|
||||
sys_tab_advanced: '高级',
|
||||
sys_tab_console: '控制台',
|
||||
sys_heading: '系统设置',
|
||||
sys_ip: 'IP 地址:',
|
||||
sys_model: '型号:',
|
||||
sys_hostname: '主机名:',
|
||||
sys_apply: '应用',
|
||||
sys_netmask: '子网掩码:',
|
||||
sys_gateway: '网关:',
|
||||
sys_language: '语言:',
|
||||
sys_mgmt_vlan: 'Management VLAN:',
|
||||
sys_mgmt_untagged: 'untagged',
|
||||
sys_mgmt_confirm: 'Move switch management to VLAN ',
|
||||
sys_mgmt_warn: 'The switch will start tagging its own traffic with that VLAN. If the port you are connected through does not carry it, this page becomes unreachable and the setting can only be undone over the console. Continue?',
|
||||
sys_ip_note: '更新上述设置后,请使用新的 IP 地址重新访问管理界面:',
|
||||
sys_update: '更新设置',
|
||||
sys_save_label: '将当前全部设置保存到 Flash:',
|
||||
sys_save: '保存设置到 Flash',
|
||||
sys_advanced: '高级设置',
|
||||
sys_startup_config: '启动配置:',
|
||||
sys_startup_warn: '直接编辑启动配置时请谨慎,错误配置可能导致无法访问设备:',
|
||||
sys_clear_config: '清除启动配置',
|
||||
sys_save_startup: '保存启动配置到 Flash',
|
||||
sys_reset: '重启交换机',
|
||||
sys_console: '控制台命令',
|
||||
sys_enter_cmd: '输入命令:',
|
||||
sys_send_cmd: '发送命令',
|
||||
sys_console_warn: '输入控制台命令时请谨慎,错误命令可能导致无法访问设备!',
|
||||
sys_invalid_ip: '无效 IP: ',
|
||||
sys_reset_confirm: '确定要重启交换机吗?',
|
||||
sys_resetting: '交换机正在重启。请稍候并刷新页面。',
|
||||
|
||||
login_title: 'RTL 交换机登录',
|
||||
login_heading: 'RTL 交换机登录',
|
||||
login_wrong: '密码错误!',
|
||||
login_password: '密码',
|
||||
login_login: '登录',
|
||||
|
||||
index_title: 'FreeSwitchOS 主页',
|
||||
index_heading: '交换机配置',
|
||||
index_settings: '设置',
|
||||
|
||||
update_title: '固件升级',
|
||||
update_heading: '固件升级',
|
||||
update_instruction: '请选择要上传的固件文件:',
|
||||
update_upload: '上传文件',
|
||||
|
||||
common_port: '端口 ',
|
||||
common_pkts: ' 个包',
|
||||
}
|
||||
};
|
||||
|
||||
var rtlLang = (function() {
|
||||
var saved = localStorage.getItem('rtl_lang');
|
||||
if (saved && LANG[saved]) return saved;
|
||||
var browser = (navigator.language || navigator.userLanguage || 'en').substring(0, 2);
|
||||
return LANG[browser] ? browser : 'en';
|
||||
})();
|
||||
|
||||
function t(key) {
|
||||
return LANG[rtlLang][key] || LANG['en'][key] || key;
|
||||
}
|
||||
|
||||
function setLang(lang) {
|
||||
if (LANG[lang]) {
|
||||
localStorage.setItem('rtl_lang', lang);
|
||||
rtlLang = lang;
|
||||
document.querySelectorAll('[data-i18n]').forEach(function(el) {
|
||||
applyTranslation(el);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function applyTranslation(el) {
|
||||
var key = el.getAttribute('data-i18n');
|
||||
if (!key) return;
|
||||
if (el.tagName === 'INPUT' && (el.type === 'submit' || el.type === 'button')) {
|
||||
el.value = t(key);
|
||||
} else if (el.tagName === 'OPTION') {
|
||||
el.textContent = t(key);
|
||||
} else if (el.tagName === 'TITLE') {
|
||||
el.textContent = t(key);
|
||||
} else {
|
||||
el.innerHTML = t(key);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('[data-i18n]').forEach(function(el) {
|
||||
applyTranslation(el);
|
||||
});
|
||||
});
|
||||
@@ -1,32 +1,508 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/main_info.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<script>
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
const interval = setInterval(update, 2000);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="index_title">FreeSwitchOS Main Page</title>
|
||||
<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>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<script src="/navigation.js"></script>
|
||||
<ul id="navlist"></ul>
|
||||
<div id="navfoot"><span id="fver"></span></div>
|
||||
</nav>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
</section>
|
||||
|
||||
<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 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-lldp">
|
||||
<div class="card"><h2 data-i18n="lldp_title">LLDP</h2>
|
||||
<label class="row"><span class="lb" data-i18n="lldp_enabled">Enabled</span>
|
||||
<span class="switch"><input type="checkbox" id="lldp_en"><i></i></span>
|
||||
</label>
|
||||
<div class="scrollx"><table class="t" id="lldp_table">
|
||||
<thead><tr><th data-i18n="c_port">Port Number</th><th data-i18n="lldp_portname">Name</th><th data-i18n="lldp_enable">Send Frames</th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab" id="tab-isolate">
|
||||
<div class="card"><h2 data-i18n="iso_title">Port isolation</h2>
|
||||
<p class="small mut" style="margin-bottom:10px" data-i18n="iso_intro">A frame entering a port may only leave through the ports ticked in its row.</p>
|
||||
<div class="scrollx"><table class="t" id="isotbl" style="width:auto"><thead></thead><tbody></tbody></table></div>
|
||||
<p class="small mut" style="margin-top:6px" data-i18n="iso_legend">The cell of the port itself is always allowed.</p>
|
||||
<div style="display:flex;gap:12px;margin-top:10px">
|
||||
<button class="ctl pri" id="isoapply" data-i18n="c_apply">Apply</button>
|
||||
<button class="ctl" id="isoall" data-i18n="iso_all">Allow all</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab" id="tab-lag">
|
||||
<div id="lagwrap" class="grid2"></div>
|
||||
<div class="card" style="margin-top:14px"><h2 data-i18n="lacp_heading">LACP (802.3ad)</h2>
|
||||
<div class="scrollx"><table class="t" id="lacptbl">
|
||||
<thead><tr><th data-i18n="c_port">Port</th><th>LAG</th><th data-i18n="lacp_actor">Actor</th><th data-i18n="lacp_partner">Partner</th><th data-i18n="lacp_rxstate">RX state</th><th data-i18n="lacp_rx">LACPDUs received</th><th data-i18n="lacp_psys">Partner system</th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
</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>
|
||||
<div class="card"><h2><span data-i18n="sc_title">Storm control</span> <span class="hint" data-i18n="sc_h">per port and traffic type, empty = off</span></h2>
|
||||
<div class="scrollx"><table class="t" id="sctable">
|
||||
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="sc_bcast">Broadcast</th><th data-i18n="sc_mcast">Multicast</th><th data-i18n="sc_ucast">Unknown unicast</th><th data-i18n="sc_umcast">Unknown multicast</th><th></th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
<p class="small mut" style="margin-top:8px" data-i18n="sc_note">Frames of a type above its limit are dropped where they enter the switch.</p>
|
||||
</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>
|
||||
<label class="row"><span class="lb" data-i18n="sy_dns1">DNS server</span><input class="in" id="sy-dns1" size="15"></label>
|
||||
<label class="row"><span class="lb" data-i18n="sy_dns2">Second DNS</span><input class="in" id="sy-dns2" size="15"></label>
|
||||
<p class="small mut" id="sy-dnsdhcp"></p>
|
||||
<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="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>
|
||||
<label class="row" style="justify-content:space-between"><span data-i18n="sy_sesstmo">Session timeout (s)</span>
|
||||
<input class="in" id="sy-sesstmo" style="width:80px" type="number" min="1" max="65535"></label>
|
||||
<p class="small mut" data-i18n="sy_services_note">Service state reflects the startup config; runtime state is not readable.</p>
|
||||
</div>
|
||||
<div class="card" id="sy-ntpcard"><h2 data-i18n="sy_time">Time</h2>
|
||||
<label class="row" style="justify-content:space-between"><span data-i18n="sy_ntp">NTP</span>
|
||||
<span class="switch"><input type="checkbox" id="sy-ntp"><i></i></span></label>
|
||||
<label class="row"><span class="lb" data-i18n="sy_ntpsrv">NTP server</span><input class="in" id="sy-ntpsrv" maxlength="47" data-i18n-p="sy_ntpsrv_p"></label>
|
||||
<label class="row"><span class="lb" data-i18n="sy_ntpint">Poll interval [min]</span><input class="in sm" id="sy-ntpint" type="number" min="1" max="1440"></label>
|
||||
<label class="row"><span class="lb" data-i18n="sy_tz">Time zone</span><select class="in" id="sy-tz"></select></label>
|
||||
<label class="row"><span class="lb" data-i18n="sy_dst">Daylight saving</span><select class="in" id="sy-dst"><option value="off" data-i18n="c_offc">Off</option><option value="eu" data-i18n="sy_dst_eu">Europe</option><option value="us" data-i18n="sy_dst_us">United States</option></select></label>
|
||||
<div style="display:flex;gap:10px;margin-top:8px">
|
||||
<button class="ctl pri" id="sy-ntpapply" data-i18n="c_apply">Apply</button>
|
||||
<button class="ctl" id="sy-ntprefresh" data-i18n="c_refresh">Refresh</button>
|
||||
</div>
|
||||
<label class="row" style="margin-top:10px"><span class="lb" data-i18n="sy_now">Local time</span><span class="mono" id="sy-now">-</span></label>
|
||||
<label class="row"><span class="lb" data-i18n="sy_sync">Last synchronisation</span><span id="sy-sync">-</span></label>
|
||||
<label class="row"><span class="lb" data-i18n="sy_ntpaddr">Server address</span><span class="mono" id="sy-ntpaddr">-</span></label>
|
||||
<p class="small mut" style="margin-top:8px" data-i18n="sy_time_note">The switch has no battery backed clock.</p>
|
||||
</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="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="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="de">Deutsch</option>
|
||||
<option value="en">English</option>
|
||||
<option value="es">Español</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="zh">中文</option>
|
||||
|
||||
</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>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<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 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="l2_title">FreeSwitchOS L2 Configuration</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="l2_heading">L2 Configuration</h1>
|
||||
<p><span data-i18n="l2_shown">Shown:</span> <span id="l2count">-</span></p>
|
||||
<table id="l2table">
|
||||
<tr>
|
||||
<th><span class="l2sort" onclick="l2SortBy('port')"><span data-i18n="l2_col_port">Port</span><span id="l2a_port" class="l2arrow"></span></span><br>
|
||||
<input id="l2f_port" class="l2filter" oninput="l2FilterChanged()" size="5"></th>
|
||||
<th><span class="l2sort" onclick="l2SortBy('mac')">MAC<span id="l2a_mac" class="l2arrow"></span></span><br>
|
||||
<input id="l2f_mac" class="l2filter" oninput="l2FilterChanged()" size="14"></th>
|
||||
<th><span class="l2sort" onclick="l2SortBy('vlan')">VLAN<span id="l2a_vlan" class="l2arrow"></span></span><br>
|
||||
<input id="l2f_vlan" class="l2filter" oninput="l2FilterChanged()" size="5"></th>
|
||||
<th><span class="l2sort" onclick="l2SortBy('type')"><span data-i18n="l2_col_type">Type</span><span id="l2a_type" class="l2arrow"></span></span><br>
|
||||
<input id="l2f_type" class="l2filter" oninput="l2FilterChanged()" size="8"></th>
|
||||
<th data-i18n="l2_col_remove">Remove Entry</th></tr>
|
||||
<script src="/l2.js"></script>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
<script src="/navigation.js"></script>
|
||||
</html>
|
||||
@@ -1,178 +0,0 @@
|
||||
function fillStats() {
|
||||
var tbl = document.getElementById('statstable');
|
||||
if (!numPorts)
|
||||
return;
|
||||
if (tbl.rows.length > 1) {
|
||||
for (let i = 0; i < numPorts; i++) {
|
||||
console.log("Table Update row: " + i + " state " + pState[i] + " is " + linkS[pState[i] +1]);
|
||||
tbl.rows[i+1].cells[1].innerHTML = linkText(pState[i]+1);
|
||||
tbl.rows[i+1].cells[2].innerHTML = `${txG[i]}` + t('common_pkts');
|
||||
tbl.rows[i+1].cells[3].innerHTML = `${txB[i]}` + t('common_pkts');
|
||||
tbl.rows[i+1].cells[4].innerHTML = `${rxG[i]}` + t('common_pkts');
|
||||
tbl.rows[i+1].cells[5].innerHTML = `${rxB[i]}` + t('common_pkts');
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < numPorts; i++) {
|
||||
console.log("Table row: " + i);
|
||||
const tr = tbl.insertRow();
|
||||
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i+1)));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(linkText(pState[i]+1)));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(`${txG[i]}` + t('common_pkts')));
|
||||
td = tr.insertCell();td.appendChild(document.createTextNode(`${txB[i]}` + t('common_pkts')));
|
||||
td = tr.insertCell();td.appendChild(document.createTextNode(`${rxG[i]}` + t('common_pkts')));
|
||||
td = tr.insertCell();td.appendChild(document.createTextNode(`${rxB[i]}` + t('common_pkts')));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function l2CMP(a, b)
|
||||
{
|
||||
if (a.port < b.port)
|
||||
return -1;
|
||||
if (a.port > b.port)
|
||||
return 1;
|
||||
if (a.mac < b.mac)
|
||||
return -1;
|
||||
if (a.mac > b.mac)
|
||||
return 1;
|
||||
if (a.vlan < b.vlan)
|
||||
return -1;
|
||||
if (a.vlan > b.vlan)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function uniq(a) {
|
||||
return a.filter(function(item, pos, ary) {
|
||||
return !pos || item.idx != ary[pos - 1].idx;
|
||||
});
|
||||
}
|
||||
|
||||
function delL2(idx) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
if (JSON.parse(xhttp.responseText).result == 1) {
|
||||
l2All = l2All.filter(function(e) { return e.idx != idx; });
|
||||
renderL2();
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/l2_del.json?idx=" + idx, true);
|
||||
xhttp.timeout = 1500; xhttp.send();
|
||||
}
|
||||
|
||||
var l2All = [];
|
||||
const l2Cols = ['port', 'mac', 'vlan', 'type'];
|
||||
var l2SortCol = 'port';
|
||||
var l2SortDir = 1;
|
||||
|
||||
function l2Key(e, col) {
|
||||
if (col === 'port') {
|
||||
if (e.port === 'CPU') return Number.MAX_SAFE_INTEGER;
|
||||
if (e.lag) return 100 + e.lag;
|
||||
return Number(e.port);
|
||||
}
|
||||
if (col === 'vlan') return Number(e.vlan);
|
||||
return String(e[col]).toLowerCase();
|
||||
}
|
||||
|
||||
function l2SortBy(col) {
|
||||
l2SortDir = (col === l2SortCol) ? -l2SortDir : 1;
|
||||
l2SortCol = col;
|
||||
renderL2();
|
||||
}
|
||||
|
||||
function l2FilterChanged() { renderL2(); }
|
||||
|
||||
function renderL2() {
|
||||
var tbl = document.getElementById('l2table');
|
||||
if (!tbl) return;
|
||||
var f = {};
|
||||
l2Cols.forEach(function(c) {
|
||||
var el = document.getElementById('l2f_' + c);
|
||||
f[c] = el ? el.value.trim().toLowerCase() : '';
|
||||
});
|
||||
var rows = l2All.filter(function(e) {
|
||||
return l2Cols.every(function(c) {
|
||||
return !f[c] || String(e[c]).toLowerCase().indexOf(f[c]) !== -1;
|
||||
});
|
||||
});
|
||||
rows.sort(function(a, b) {
|
||||
var x = l2Key(a, l2SortCol), y = l2Key(b, l2SortCol);
|
||||
return (x < y ? -1 : x > y ? 1 : 0) * l2SortDir;
|
||||
});
|
||||
l2Cols.forEach(function(c) {
|
||||
var a = document.getElementById('l2a_' + c);
|
||||
if (a) a.textContent = (c === l2SortCol) ? (l2SortDir > 0 ? ' \u25b2' : ' \u25bc') : ' \u21c5';
|
||||
});
|
||||
paintL2(tbl, rows);
|
||||
var cnt = document.getElementById('l2count');
|
||||
if (cnt) cnt.textContent = rows.length + ' / ' + l2All.length;
|
||||
}
|
||||
|
||||
function fillL2(s)
|
||||
{
|
||||
var tbl = document.getElementById('l2table');
|
||||
if (!s.length)
|
||||
return;
|
||||
s.sort(l2CMP);
|
||||
s = uniq(s);
|
||||
l2All = s.map(function(e) {
|
||||
if (e.lag)
|
||||
e.port = 'LAG' + e.lag;
|
||||
return e;
|
||||
});
|
||||
renderL2();
|
||||
}
|
||||
|
||||
function delL2Button(e)
|
||||
{
|
||||
if (e.port == 'CPU')
|
||||
return '';
|
||||
return '<button type="button" onclick="delL2(' + e.idx + ');">' + t('l2_delete') + '</button>';
|
||||
}
|
||||
|
||||
function paintL2(tbl, s)
|
||||
{
|
||||
console.log("L2: ", JSON.stringify(s));
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
var e = s[i];
|
||||
console.log(i, e);
|
||||
if (tbl.rows[i+1]) {
|
||||
tbl.rows[i+1].cells[0].innerHTML = `${e.port}`;
|
||||
tbl.rows[i+1].cells[1].innerHTML = `${e.mac}`;
|
||||
tbl.rows[i+1].cells[2].innerHTML = `${e.vlan}`;
|
||||
tbl.rows[i+1].cells[3].innerHTML = `${e.type}`;
|
||||
tbl.rows[i+1].cells[4].innerHTML = delL2Button(e);
|
||||
} else {
|
||||
const tr = tbl.insertRow();
|
||||
let td = tr.insertCell(); td.innerHTML = `${e.port}`;
|
||||
td = tr.insertCell(); td.innerHTML = `${e.mac}`;
|
||||
td = tr.insertCell(); td.innerHTML = `${e.vlan}`;
|
||||
td = tr.insertCell(); td.innerHTML = `${e.type}`;
|
||||
td = tr.insertCell(); td.innerHTML = delL2Button(e);
|
||||
}
|
||||
}
|
||||
for (let i = tbl.rows.length - 1; i > s.length; i--)
|
||||
tbl.deleteRow(i);
|
||||
}
|
||||
|
||||
function getL2() {
|
||||
walkL2(function(entries, ok) {
|
||||
if (ok) {
|
||||
for (var i = 0; i < entries.length; i++)
|
||||
entries[i].type = entries[i].type == "s" ? t('l2_static') : t('l2_learned');
|
||||
fillL2(entries);
|
||||
}
|
||||
setTimeout(getL2, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
getL2();
|
||||
const interval = setInterval(update, 2000);
|
||||
});;
|
||||
});
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="lag_title">Link Aggregation Configuration</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="lag_heading">Link Aggregation Groups Configuration</h1>
|
||||
<h2>LAG 1 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub0" onclick="lagSub(0);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
|
||||
<div id="mLAG0"></div>
|
||||
<br />
|
||||
<h2>LAG 2 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub1" onclick="lagSub(1);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
|
||||
<div id="mLAG1"></div>
|
||||
<br />
|
||||
<h2>LAG 3 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub2" onclick="lagSub(2);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
|
||||
<div id="mLAG2"></div>
|
||||
<br />
|
||||
<h2>LAG 4 <input style="width:15%;margin-left: 3em;" class="action" id="l_sub3" onclick="lagSub(3);" type="button" data-i18n="lag_update" value="Update / Create"></h2>
|
||||
<div id="mLAG3"></div>
|
||||
<script src="/lag.js"></script>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,81 +0,0 @@
|
||||
var lagInterval = Number();
|
||||
|
||||
function lagForm() {
|
||||
if (!numPorts)
|
||||
return;
|
||||
for (let j=0; j < 4; j++) {
|
||||
var lag = "mLAG" + j
|
||||
console.log("Adding LAG " + lag)
|
||||
var m = document.getElementById(lag);
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
const d = document.createElement("div");
|
||||
d.classList.add("cbgroup");
|
||||
const l = document.createElement("label");
|
||||
l.innerHTML = "" + i;
|
||||
l.classList.add("cbgroup");
|
||||
const inp = document.createElement("input");
|
||||
inp.type = "checkbox"; inp.setAttribute("class","psel");
|
||||
inp.id = "p_" + lag + "_" + i;
|
||||
const o = document.createElement("img");
|
||||
if (pIsSFP[i - 1]) {
|
||||
o.src = "sfp.svg"; o.width ="60"; o.height ="60";
|
||||
} else {
|
||||
o.src = "port.svg"; o.width = "40"; o.height = "40";
|
||||
}
|
||||
l.appendChild(inp); l.appendChild(o);
|
||||
d.appendChild(l)
|
||||
m.appendChild(d);
|
||||
}
|
||||
}
|
||||
fetchLag();
|
||||
}
|
||||
|
||||
function setL(p, c){
|
||||
console.log("LAG setting: ", p, " to ", c);
|
||||
document.getElementById(p).checked=c;
|
||||
}
|
||||
|
||||
function fetchLag() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("LAG: ", JSON.stringify(s));
|
||||
for (let l = 0; l < 4; l++) {
|
||||
let members = parseInt(s[l].members, 2);
|
||||
let hash = parseInt(s[l].hash, 16);
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
let p = i - 1;
|
||||
if (numPorts < 9)
|
||||
p = physToLogPort[p];
|
||||
setL("p_mLAG"+l+"_"+i, members & (1<<p));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", `/lag.json`, true);
|
||||
sendXHTTP(xhttp);
|
||||
}
|
||||
async function lagSub(l) {
|
||||
var cmd = "lag " + (l + 1);
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
if (document.getElementById("p_mLAG"+l+"_"+i).checked)
|
||||
cmd = cmd + ` ${i}`;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
lagForm();
|
||||
const interval = setInterval(update, 2000);
|
||||
});
|
||||
});
|
||||
@@ -1,30 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<title data-i18n="login_title">RTL Switch Login</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="/i18n.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={
|
||||
de:["Switch-Anmeldung","RTLPlayground-Verwaltungsoberfläche","Passwort","Anmelden","Falsches Passwort, bitte erneut versuchen."],
|
||||
en:["Switch Login","RTLPlayground management interface","Password","Sign in","Wrong password, try again."],
|
||||
ja:["スイッチ ログイン","RTLPlayground 管理インターフェース","パスワード","ログイン","パスワードが違います。もう一度お試しください。"],
|
||||
zh:["交换机登录","RTLPlayground 管理界面","密码","登录","密码错误,请重试。"],
|
||||
es:["Inicio de sesión del switch","Interfaz de gestión de RTLPlayground","Contraseña","Iniciar sesión","Contraseña incorrecta, inténtalo de nuevo."],
|
||||
fr:["Utilisateur du commutateur","Interface de management RTLPlayground","Mot de passe","Se connecter","Mot de passe erronné, essayez à nouveau."]
|
||||
};
|
||||
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;
|
||||
// Name the tab after the switch: the hostname seen at the last login here,
|
||||
// otherwise the address, so several open switches can be told apart
|
||||
var host=null;
|
||||
try{host=localStorage.getItem("rtl_host");}catch(e){}
|
||||
document.title=(host||location.hostname)+" – "+s[0];
|
||||
document.getElementById("hn").textContent=host||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,350 +0,0 @@
|
||||
var txG = new BigInt64Array(10);
|
||||
var txB = new BigInt64Array(10);
|
||||
var rxG = new BigInt64Array(10);
|
||||
var rxB = new BigInt64Array(10);
|
||||
const linkS = [function(){return t('speed_disabled')}, function(){return t('speed_down')}, "10M", "100M", "1000M", "500M", "10G", "2.5G", "5G"];
|
||||
var pState = new Int8Array(10);
|
||||
var pIsSFP = new Int8Array(10);
|
||||
var pAdvertised = new Int8Array(10);
|
||||
var numPorts = 0;
|
||||
function linkText(idx) { var v = linkS[idx]; return typeof v === 'function' ? v() : v; }
|
||||
var logToPhysPort = new Int8Array(10);
|
||||
var physToLogPort = new Int8Array(10);
|
||||
var portNames = new Array(10);
|
||||
var currentRequests = [];
|
||||
var currentCallback;
|
||||
function drawPorts() {
|
||||
var f = document.getElementById('ports');
|
||||
console.log("DRAWING PORTS: ", numPorts);
|
||||
for (let i = 0; i < numPorts; i++) {
|
||||
console.log("DRAWING isSFP: ", pIsSFP[i]);
|
||||
const d = document.createElement("div");
|
||||
d.classList.add('tooltip');
|
||||
const s = document.createElement("span");
|
||||
s.classList.add("tooltiptext");
|
||||
s.innerHTML = t('common_port');
|
||||
s.id="tt_" + (i+1);
|
||||
const l = document.createElement("object");
|
||||
d.appendChild(l);
|
||||
d.appendChild(s);
|
||||
l.type = "image/svg+xml";
|
||||
if (!pIsSFP[i]) {
|
||||
l.data = "port.svg";
|
||||
l.width ="40";
|
||||
l.height ="40";
|
||||
} else {
|
||||
l.data = "sfp.svg";
|
||||
l.width = "60";
|
||||
l.height = "60";
|
||||
}
|
||||
l.id="port" + (i+1);
|
||||
f.appendChild(d);
|
||||
}
|
||||
}
|
||||
|
||||
function parseUint16(val) {
|
||||
return parseInt(val, 16) & 0xffff;
|
||||
}
|
||||
|
||||
function parseInt16(val) {
|
||||
let valInt = parseInt(val, 16);
|
||||
let num = valInt & 0x7fff;
|
||||
if (valInt & 0x8000) {
|
||||
return num - 0x8000;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
function applyCalibrationSlopeOffset(val, cal) {
|
||||
if (typeof cal !== 'string') {
|
||||
return val;
|
||||
}
|
||||
if (cal.startsWith("0x")) {
|
||||
cal = cal.substring(2);
|
||||
}
|
||||
if (cal.length != 8) {
|
||||
return val;
|
||||
}
|
||||
let slope = parseUint16(cal.substring(0, 4)) / 256;
|
||||
let offset = parseInt16(cal.substring(4, 8));
|
||||
return slope * val + offset;
|
||||
}
|
||||
|
||||
function applyRxPowerCalibration(val, cal) {
|
||||
if (typeof cal !== 'string') {
|
||||
return val;
|
||||
}
|
||||
if (cal.startsWith("0x")) {
|
||||
cal = cal.substring(2);
|
||||
}
|
||||
if (cal.length != 40) {
|
||||
return val;
|
||||
}
|
||||
let bytes = cal.match(/.{1,2}/g).map(function (x) { return parseInt(x, 16); });
|
||||
let view = new DataView(new Uint8Array(bytes).buffer);
|
||||
return view.getFloat32(0) * Math.pow(val, 4)
|
||||
+ view.getFloat32(4) * Math.pow(val, 3)
|
||||
+ view.getFloat32(8) * Math.pow(val, 2)
|
||||
+ view.getFloat32(12) * val
|
||||
+ view.getFloat32(16);
|
||||
}
|
||||
|
||||
function decodeSfpTemp(val, cal) {
|
||||
let temp = parseInt16(val);
|
||||
return applyCalibrationSlopeOffset(temp, cal) / 256;
|
||||
}
|
||||
|
||||
function decodeSfpVcc(val, cal) {
|
||||
let vcc = parseUint16(val);
|
||||
return applyCalibrationSlopeOffset(vcc, cal) / 10000;
|
||||
}
|
||||
|
||||
function decodeSfpTxBias(val, cal) {
|
||||
let bias = parseUint16(val);
|
||||
return applyCalibrationSlopeOffset(bias, cal) / 500;
|
||||
}
|
||||
|
||||
function decodeSfpTxPower(val, cal) {
|
||||
let txPower = parseUint16(val);
|
||||
return applyCalibrationSlopeOffset(txPower, cal) / 10000;
|
||||
}
|
||||
|
||||
function decodeSfpRxPower(val, cal) {
|
||||
let rxPower = parseUint16(val);
|
||||
return applyRxPowerCalibration(rxPower, cal) / 10000;
|
||||
}
|
||||
|
||||
function convertPowerTodBm(val) {
|
||||
return 10 * Math.log10(val);
|
||||
}
|
||||
|
||||
function update(callback) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
console.log("IN UPDATE ");
|
||||
if (this.readyState == 4 && this.status == 401)
|
||||
document.location = "/login.html"
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
if (!numPorts) {
|
||||
numPorts = s.length;
|
||||
for (let i = 0; i < s.length; i++)
|
||||
pIsSFP[s[i].portNum-1] = s[i].isSFP;
|
||||
drawPorts();
|
||||
}
|
||||
console.log("RES:", JSON.stringify(s));
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
p = s[i];
|
||||
let n = p.portNum;
|
||||
logToPhysPort[p.logPort] = n;
|
||||
physToLogPort[n-1] = p.logPort;
|
||||
portNames[p.logPort] = p.name;
|
||||
let pid = "port" + n;
|
||||
let ttid = "tt_" + n;
|
||||
n--;
|
||||
txG[n] = BigInt(p.txG); txB[n] = BigInt(p.txB); rxG[n] = BigInt(p.rxG); rxB[n] = BigInt(p.rxB);
|
||||
var psvg = document.getElementById(pid);
|
||||
var tt = document.getElementById(ttid);
|
||||
if (psvg == null || !psvg.contentDocument)
|
||||
continue;
|
||||
var bgs = psvg.contentDocument.getElementsByClassName("bg");
|
||||
var leds = psvg.contentDocument.getElementsByClassName("led");
|
||||
if (leds[0] == null || leds[0].style == null)
|
||||
continue;
|
||||
const portName = p.name || portNames[p.logPort] || '';
|
||||
var iHTML = "<table border=\"0\" class=\"tt_table\">";
|
||||
if (portName) iHTML += "<tr><td align=\"left\">" + t('port_name') + "</td><td>:</td><td>" + portName + "</td></tr>";
|
||||
if (p.enabled == 0) {
|
||||
pState[n] = -1;
|
||||
bgs[0].style.fill = "red";
|
||||
leds[0].style.fill = "black"; leds[1].style.fill = "black";
|
||||
psvg.style.opacity = 0.4;
|
||||
iHTML += "<tr><td align=\"left\">" + t('port_status') + "</td><td>:</td><td>" + t('port_not_enabled') + "</td></tr>";
|
||||
iHTML += "</table>";
|
||||
tt.innerHTML = iHTML;
|
||||
} else {
|
||||
psvg.style.opacity = 1.0;
|
||||
pState[n] = p.link;
|
||||
if (p.link == 5 || p.link == 7) {
|
||||
leds[0].style.fill = "green"; leds[1].style.fill = "blue";
|
||||
} else if (p.link == 4 || p.link == 6) {
|
||||
leds[0].style.fill = "green"; leds[1].style.fill = "orange";
|
||||
} else if (p.link == 1 || p.link == 2 || p.link == 3) {
|
||||
leds[0].style.fill = "green"; leds[1].style.fill = "green";
|
||||
} else {
|
||||
leds[0].style.fill = "black"; leds[1].style.fill = "black";
|
||||
psvg.style.opacity = 0.4
|
||||
}
|
||||
iHTML += "<tr><td align=\"left\">" + t('port_link_speed') + "</td><td>:</td><td>" + linkText(p.link + 1) + "</td></tr>";
|
||||
if (p.isSFP) {
|
||||
pAdvertised[n] = 0;
|
||||
const hasExtendedStatus = p.sfp_options & 0x40;
|
||||
iHTML += "<tr><td>" + t('port_vendor') + "</td><td>:</td><td>" + p.sfp_vendor + "</td></tr>";
|
||||
iHTML += "<tr><td>" + t('port_model') + "</td><td>:</td><td>" + p.sfp_model + "</td></tr>";
|
||||
iHTML += "<tr><td>" + t('port_serial') + "</td><td>:</td><td>" + p.sfp_serial + "</td></tr>";
|
||||
if (hasExtendedStatus) {
|
||||
let txPower = decodeSfpTxPower(p.sfp_txpower, p.sfp_txpower_cal);
|
||||
let txPowerdBm = convertPowerTodBm(txPower);
|
||||
let rxPower = decodeSfpRxPower(p.sfp_rxpower, p.sfp_rxpower_cal);
|
||||
let rxPowerdBm = convertPowerTodBm(rxPower);
|
||||
iHTML += "<tr><td>" + t('port_temp') + "</td><td>:</td><td>" + decodeSfpTemp(p.sfp_temp, p.sfp_temp_cal).toFixed(2) + " ℃</td></tr>";
|
||||
iHTML += "<tr><td>" + t('port_vcc') + "</td><td>:</td><td>" + decodeSfpVcc(p.sfp_vcc, p.sfp_vcc_cal).toFixed(2) + " V</td></tr>";
|
||||
iHTML += "<tr><td>" + t('port_tx_fault') + "</td><td>:</td><td>" + (Boolean(Number(p.sfp_state) & 0x4)) + "</td></tr>";
|
||||
iHTML += "<tr><td>" + t('port_tx_disabled') + "</td><td>:</td><td>" + (Boolean(Number(p.sfp_state) & 0x80)) + "</td></tr>";
|
||||
iHTML += "<tr><td>" + t('port_tx_bias') + "</td><td>:</td><td>" + decodeSfpTxBias(p.sfp_txbias, p.sfp_txbias_cal).toFixed(1) + " mA</td></tr>";
|
||||
iHTML += "<tr><td>" + t('port_tx_power') + "</td><td>:</td><td>" + txPower.toFixed(3) + " mW / " + txPowerdBm.toFixed(2) + " dBm</td></tr>";
|
||||
iHTML += "<tr><td>" + t('port_rx_power') + "</td><td>:</td><td>" + rxPower.toFixed(3) + " mW / " + rxPowerdBm.toFixed(2) + " dBm</td></tr>";
|
||||
}
|
||||
// Not all devices & modules have LOS pin...
|
||||
const rx_los_pin = p.sfp_los !== null ? Boolean(Number(p.sfp_los)) : null;
|
||||
const rx_los_module = hasExtendedStatus ? Boolean(Number(p.sfp_state) & 0x2) : null;
|
||||
if (rx_los_module !== null || rx_los_pin !== null) {
|
||||
iHTML += `<tr><td>` + t('port_rx_los') + `</td><td>:</td><td>${rxLosHTML(rx_los_pin, rx_los_module)}</td></tr>`;
|
||||
}
|
||||
} else {
|
||||
pAdvertised[n] = parseInt(p.adv, 2);
|
||||
};
|
||||
iHTML += "</table>";
|
||||
tt.innerHTML = iHTML;
|
||||
}}
|
||||
if (callback)
|
||||
callback();
|
||||
}};
|
||||
xhttp.open("GET", "/status.json", true);
|
||||
xhttp.timeout = 5000;
|
||||
sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
function rxLosHTML(pinStatus, moduleStatus) {
|
||||
if (moduleStatus !== null && pinStatus !== null && moduleStatus !== pinStatus) {
|
||||
return `pin=${pinStatus}<br/>mod=${moduleStatus}<br/>❗❗❗❗`;
|
||||
}
|
||||
|
||||
// Returns first non null value
|
||||
return moduleStatus ?? pinStatus;
|
||||
}
|
||||
|
||||
function callbackXHTTP()
|
||||
{
|
||||
x = currentRequests.shift();
|
||||
x.onreadystatechange = currentCallback;
|
||||
x.onreadystatechange();
|
||||
if (currentRequests.length === 0)
|
||||
return;
|
||||
x = currentRequests[0];
|
||||
currentCallback = x.onreadystatechange;
|
||||
x.onreadystatechange = callbackXHTTP;
|
||||
var retries = 10;
|
||||
while (retries) {
|
||||
try {
|
||||
setTimeout(() => {
|
||||
x.send();
|
||||
console.log("B1");
|
||||
}, 20);
|
||||
} catch (error) {
|
||||
retries--;
|
||||
setTimeout(() => {
|
||||
console.log(`Retry ${retries}/${maxRetries} failed: ${error.message}`);
|
||||
}, 200);
|
||||
if (retries < 1) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
console.log("B2");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function sendXHTTP(x)
|
||||
{
|
||||
console.log("sendXHTTP ", x);
|
||||
if (currentRequests.length === 0) {
|
||||
currentRequests.push(x);
|
||||
currentCallback = x.onreadystatechange;
|
||||
x.onreadystatechange = callbackXHTTP;
|
||||
var retries = 10;
|
||||
while (retries) {
|
||||
try {
|
||||
x.send();
|
||||
console.log("A1");
|
||||
} catch (error) {
|
||||
retries--;
|
||||
setTimeout(() => {
|
||||
console.log(`Retry ${retries}/${maxRetries} failed: ${error.message}`);
|
||||
}, 200);
|
||||
if (retries < 1) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
console.log("A2");
|
||||
return;
|
||||
}
|
||||
console.log("A3");
|
||||
return;
|
||||
}
|
||||
currentRequests.push(x);
|
||||
}
|
||||
|
||||
|
||||
function walkL2(onDone)
|
||||
{
|
||||
var entries = [];
|
||||
var idx = 0;
|
||||
var tries = 0;
|
||||
|
||||
function retry() {
|
||||
if (++tries < 3) {
|
||||
setTimeout(page, 1000);
|
||||
return;
|
||||
}
|
||||
onDone(entries, false);
|
||||
}
|
||||
|
||||
function page() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState != 4)
|
||||
return;
|
||||
if (this.status != 200) {
|
||||
retry();
|
||||
return;
|
||||
}
|
||||
var s;
|
||||
try {
|
||||
s = JSON.parse(xhttp.responseText);
|
||||
} catch (err) {
|
||||
retry();
|
||||
return;
|
||||
}
|
||||
tries = 0;
|
||||
s = s.map(function(e) {
|
||||
e.vlan = parseInt(e.vlan, 16);
|
||||
e.idx = parseInt(e.idx, 16);
|
||||
e.port = e.port == 9 ? 'CPU' : logToPhysPort[e.port];
|
||||
return e;
|
||||
});
|
||||
if (!s.length) {
|
||||
onDone(entries, true);
|
||||
return;
|
||||
}
|
||||
entries.push(...s);
|
||||
for (var i = entries.length - 1; i > 0; i--) {
|
||||
if (entries[0].idx == entries[i].idx) {
|
||||
onDone(entries, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (entries.length >= 4096) {
|
||||
onDone(entries, true);
|
||||
return;
|
||||
}
|
||||
idx = s[s.length - 1].idx + 1;
|
||||
setTimeout(page, 1000);
|
||||
};
|
||||
xhttp.open("GET", "/l2.json?idx=" + idx, true);
|
||||
xhttp.timeout = 1500;
|
||||
sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
page();
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
fetch('/information.json')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const tableBody = document.getElementById('infoTable').querySelector('tbody');
|
||||
|
||||
// Create table rows
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
const row = document.createElement('tr');
|
||||
const cellKey = document.createElement('td');
|
||||
const cellValue = document.createElement('td');
|
||||
|
||||
cellKey.textContent = key;
|
||||
cellValue.textContent = value;
|
||||
|
||||
row.appendChild(cellKey);
|
||||
row.appendChild(cellValue);
|
||||
tableBody.appendChild(row);
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching the data:', error));
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="mirror_title">Mirror Configuration</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="mirror_heading">Mirror Configuration</h1>
|
||||
<label class="tswitch"><span data-i18n="mirror_enabled">Enabled:</span> <input id="me" type="checkbox"></label><br/>
|
||||
<label for="mp"><span data-i18n="mirror_port">Mirroring Port:</span></label> <input type="number" id="mp" name="mp" min="1" max="9"/>
|
||||
<h2 data-i18n="mirror_tx">Mirrored Ports (TX)</h2>
|
||||
<div id="mPortsTX"></div>
|
||||
<br />
|
||||
<h2 data-i18n="mirror_rx">Mirrored Ports (RX)</h2>
|
||||
<div id="mPortsRX"></div>
|
||||
<br/> <input style="width:15%;" class="action" id="mirror_sub" onclick="mirrorSub();" type="button" data-i18n="mirror_update" value="Update / Create">
|
||||
<input style="width:15%;" class="action" id="mirror_del" onclick="mirrorDel();" type="button" data-i18n="mirror_disable" value="Disable Mirroring">
|
||||
<script src="/mirror.js"></script>
|
||||
<script src="/mirror_sub.js"></script>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,64 +0,0 @@
|
||||
var mirrorInterval = Number();
|
||||
const mirrors = ["mPortsTX", "mPortsRX"];
|
||||
|
||||
function mirrorForm() {
|
||||
if (!numPorts)
|
||||
return;
|
||||
for (let j=0; j < mirrors.length; j++) {
|
||||
console.log("Adding Mirror " + j)
|
||||
var m = document.getElementById(mirrors[j]);
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
const d = document.createElement("div");
|
||||
d.classList.add("cbgroup");
|
||||
const l = document.createElement("label");
|
||||
l.innerHTML = "" + i;
|
||||
l.classList.add("cbgroup");
|
||||
const inp = document.createElement("input");
|
||||
inp.type = "checkbox"; inp.setAttribute("class","psel");
|
||||
inp.id = mirrors[j] + i;
|
||||
const o = document.createElement("img");
|
||||
if (pIsSFP[i - 1]) {
|
||||
o.src = "sfp.svg"; o.width ="60"; o.height ="60";
|
||||
} else {
|
||||
o.src = "port.svg"; o.width = "40"; o.height = "40";
|
||||
}
|
||||
l.appendChild(inp); l.appendChild(o);
|
||||
d.appendChild(l)
|
||||
m.appendChild(d);
|
||||
}
|
||||
}
|
||||
fetchMirror();
|
||||
}
|
||||
|
||||
function setM(p, c){
|
||||
document.getElementById(p).checked=c;
|
||||
}
|
||||
|
||||
function fetchMirror() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("MIRROR: ", JSON.stringify(s));
|
||||
document.getElementById('me').checked = s.enabled;
|
||||
document.getElementById('mp').value = s.mPort;
|
||||
let m_tx = parseInt(s.mirror_tx, 2);
|
||||
let m_rx = parseInt(s.mirror_rx, 2);
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
let p = i - 1;
|
||||
if (numPorts < 9)
|
||||
p = physToLogPort[p];
|
||||
setM("mPortsTX"+i, m_tx&(1<<p)); setM("mPortsRX"+i, m_rx&(1<<p));
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", `/mirror.json`, true);
|
||||
sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
mirrorForm();
|
||||
const interval = setInterval(update, 2000);
|
||||
});
|
||||
});
|
||||
@@ -1,43 +0,0 @@
|
||||
async function mirrorSub() {
|
||||
var cmd = "mirror ";
|
||||
var mp=document.getElementById('mp').value
|
||||
if (!mp) {
|
||||
alert(t('mirror_set_port_first'));
|
||||
return;
|
||||
}
|
||||
document.getElementById(mirrors[0]+mp).checked=false;document.getElementById(mirrors[1]+mp).checked=false;
|
||||
cmd = cmd + mp;
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
if (document.getElementById(mirrors[0] + i).checked && document.getElementById(mirrors[1] + i).checked)
|
||||
cmd = cmd + ` ${i}`;
|
||||
else if (document.getElementById(mirrors[0] + i).checked)
|
||||
cmd = cmd + ` ${i}t`;
|
||||
else if (document.getElementById(mirrors[1] + i).checked)
|
||||
cmd = cmd + ` ${i}r`;
|
||||
}
|
||||
if (cmd.length < 10) {
|
||||
alert(t('mirror_select_ports'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
async function mirrorDel() {
|
||||
var cmd = "mirror off";
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
location.reload();
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
document.getElementById('sidebar').innerHTML =
|
||||
"<ul><li><a href='index.html' data-i18n='nav_overview'>Overview</a></li>"
|
||||
+ "<li><a href='ports.html' data-i18n='nav_port_config'>Port Configuration</a></li>"
|
||||
+ "<li><a href='stat.html' data-i18n='nav_port_stat'>Port Statistics</a></li>"
|
||||
+ "<li><a href='vlan.html' >VLAN</a></li>"
|
||||
+ "<li><a href='l2.html' data-i18n='nav_l2'>L2 Configuration</a></li>"
|
||||
+ "<li><a href='stp.html'>Spanning Tree</a></li>"
|
||||
+ "<li><a href='mirror.html' data-i18n='nav_mirror'>Mirroring</a></li>"
|
||||
+ "<li><a href='lag.html' data-i18n='nav_lag'>Link Aggregation</a></li>"
|
||||
+ "<li><a href='eee.html' data-i18n='nav_eee'>EEE</a></li>"
|
||||
+ "<li><a href='bandwidth.html' data-i18n='nav_bandwidth'>Bandwidth Limits</a></li>"
|
||||
+ "<li><a href='system.html' data-i18n='nav_system'>System Settings</a></li>"
|
||||
+ "<li><a href='update.html' data-i18n='nav_fw_update'>Firmware Update</a></li></ul>";
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var links = document.querySelectorAll('#sidebar a[data-i18n]');
|
||||
links.forEach(function(el) {
|
||||
var key = el.getAttribute('data-i18n');
|
||||
if (key) el.textContent = t(key);
|
||||
});
|
||||
});
|
||||
@@ -1,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,25 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="port_title">FreeSwitchOS Port Configuration</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="port_heading">Port Configuration</h1>
|
||||
<form id="vform" action="/vlan.html">
|
||||
<table id="speedtable">
|
||||
<tr> <th data-i18n="port_col_port">Port</th> <th data-i18n="port_col_name">Name</th> <th data-i18n="port_col_speed">Current Link Speed</th><th data-i18n="port_col_devices">Connected devices</th><th data-i18n="port_col_set_speed">Set Speed</th><th data-i18n="port_col_disabled">Disabled</th><th data-i18n="port_col_apply">Apply</th></tr>
|
||||
</table>
|
||||
<h2 style="margin-top:3em" data-i18n="port_mtu_heading">Configure Maximum Frame Size (MTU) forwarded at Port</h2>
|
||||
<table id="mtutable" style="margin-top:1em">
|
||||
</table>
|
||||
<script src="/ports.js"></script>
|
||||
</form>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,190 +0,0 @@
|
||||
var mtus = new Int16Array(10);
|
||||
var clicked = new Int8Array(10);
|
||||
function createPortTable() {
|
||||
var tbl = document.getElementById('speedtable');
|
||||
if (tbl.rows.length <= 2 && numPorts) {
|
||||
const sSelect = '<select name="speed_sel" id="speed_sel">'
|
||||
+ '<option value="auto">' + t('port_auto') + '</option>'
|
||||
+ '<option value="2g5">' + t('port_2500m') + '</option>'
|
||||
+ '<option value="1g">' + t('port_1000m') + '</option>'
|
||||
+ '<option value="100m full">' + t('port_100m_f') + '</option>'
|
||||
+ '<option value="100m half">' + t('port_100m_h') + '</option>'
|
||||
+ '<option value="10m full">' + t('port_10m_f') + '</option>'
|
||||
+ '<option value="10m half">' + t('port_10m_h') + '</option>'
|
||||
+ '</select>';
|
||||
const dSwitch = '<input type="checkbox" id="disable_port" onchange="portOnOff();">'
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
if (pIsSFP[i-1])
|
||||
continue;
|
||||
const tr = tbl.insertRow();
|
||||
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + i));
|
||||
let portName = portNames[physToLogPort[i-1]] || '';
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(portName));
|
||||
td = tr.insertCell(); td.innerHTML = linkText(pState[i-1] + 1);
|
||||
tr.insertCell(); // filled by devRender()
|
||||
td = tr.insertCell(); td.innerHTML = sSelect.replaceAll("speed_sel", "speed_sel_" + i);
|
||||
td = tr.insertCell(); td.innerHTML = dSwitch.replaceAll("disable_port", "disable_port_" + i)
|
||||
.replace("portOnOff()", "portOnOff(" + i + ")");
|
||||
var button = '<button type="button" style="margin: 0 0 0 24px" onclick="applySpeed(' + i + ');">' + t('port_apply') + '</button>';
|
||||
td = tr.insertCell();
|
||||
td.innerHTML = button;
|
||||
}
|
||||
}
|
||||
tbl = document.getElementById('mtutable');
|
||||
if (tbl.rows.length <= 2 && numPorts) {
|
||||
const mSelect = '<select name="mtu_sel" id="mtu_sel">'
|
||||
+ '<option value="16383">16383</option>'
|
||||
+ '<option value="1522">1522</option>'
|
||||
+ '<option value="1536">1536</option>'
|
||||
+ '<option value="1552">1552</option>'
|
||||
+ '<option value="9216">9216</option>'
|
||||
+ '</select>';
|
||||
var tr = tbl.insertRow();
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
let td = tr.insertCell();
|
||||
if (pIsSFP[i-1])
|
||||
td.innerHTML = '<object type="image/svg+xml" data="sfp.svg" width="60"></object>'
|
||||
else
|
||||
td.innerHTML = '<object type="image/svg+xml" data="port.svg" width="40"></object>'
|
||||
}
|
||||
tr = tbl.insertRow();
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
let td = tr.insertCell();
|
||||
td.innerHTML = mSelect.replaceAll("mtu_sel", "mtu_sel_" + i);
|
||||
}
|
||||
tr = tbl.insertRow();
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
let td = tr.insertCell();
|
||||
td.innerHTML = '<button type="button" style="margin: 0 0 0 24px" onclick="applyMTU(' + i + ');">' + t('port_apply') + '</button>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updatePortTable() {
|
||||
console.log("updatePortTable called");
|
||||
var tbl = document.getElementById('speedtable');
|
||||
if (tbl.rows.length <= 2 || !numPorts)
|
||||
return;
|
||||
for (let i = 1; i <= numPorts ; i++) {
|
||||
if (pIsSFP[i-1])
|
||||
continue;
|
||||
tbl.rows[i].cells[2].innerHTML = linkText(pState[i-1]+1);
|
||||
if (!clicked[i] && pState[i - 1] < 0) {
|
||||
document.getElementById('speed_sel_' + i).disabled = true;
|
||||
document.getElementById('disable_port_' + i).checked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function applySpeed(port) {
|
||||
var speed = document.getElementById('speed_sel_' + port).value;
|
||||
var disabled = document.getElementById('disable_port_' + port).checked;
|
||||
var cmd = "port " + port + " ";
|
||||
if (!disabled)
|
||||
cmd = cmd + speed;
|
||||
else
|
||||
cmd = cmd + "off";
|
||||
console.log("CMD: " + cmd);
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function portOnOff(p) {
|
||||
var disabled = document.getElementById('disable_port_' + p).checked;
|
||||
document.getElementById('speed_sel_' + p).disabled = disabled;
|
||||
clicked[p] = 1;
|
||||
}
|
||||
|
||||
async function applyMTU(port) {
|
||||
var mtu = document.getElementById('mtu_sel_' + port).value;
|
||||
var cmd = "mtu " + port + " " + mtu;
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('MTU Completed!', response);
|
||||
getMTUs();
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
function devRender(entries) {
|
||||
var tbl = document.getElementById('speedtable');
|
||||
if (tbl.rows.length <= 2 || !numPorts)
|
||||
return;
|
||||
var perPort = {};
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var e = entries[i];
|
||||
if (e.port == 'CPU')
|
||||
continue;
|
||||
if (!perPort[e.port])
|
||||
perPort[e.port] = [];
|
||||
if (perPort[e.port].indexOf(e.mac) < 0)
|
||||
perPort[e.port].push(e.mac);
|
||||
}
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
if (pIsSFP[i-1])
|
||||
continue;
|
||||
var cell = tbl.rows[i].cells[3];
|
||||
var macs = perPort[i] || [];
|
||||
if (macs.length == 1) {
|
||||
cell.textContent = macs[0];
|
||||
cell.title = '';
|
||||
} else if (macs.length > 1) {
|
||||
cell.textContent = macs.length + ' ' + t('port_devices');
|
||||
cell.title = macs.join('\n');
|
||||
} else {
|
||||
cell.textContent = '';
|
||||
cell.title = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function devWalk() {
|
||||
walkL2(function(entries, ok) {
|
||||
if (ok)
|
||||
devRender(entries);
|
||||
setTimeout(devWalk, 15000);
|
||||
});
|
||||
}
|
||||
|
||||
function getMTUs() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("MTUS: ", JSON.stringify(s));
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
p = s[i];
|
||||
let n = p.portNum;
|
||||
mtus[n] = parseInt(p.mtu, 16);
|
||||
var mtu = document.getElementById('mtu_sel_' + n);
|
||||
if (!mtu)
|
||||
continue;
|
||||
mtu.value = mtus[n];
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/mtu.json", true);
|
||||
xhttp.timeout = 1500; sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
createPortTable();
|
||||
updatePortTable();
|
||||
getMTUs()
|
||||
setTimeout(devWalk, 3000);
|
||||
const interval = setInterval(update, 2000);
|
||||
const updatePortTableInterval = setInterval(updatePortTable, 1000);
|
||||
});
|
||||
});
|
||||
@@ -1,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,48 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="stat_title">FreeSwitchOS Port Statistics</title>
|
||||
<style>
|
||||
.popup {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.popup-content {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
height: 80%
|
||||
}
|
||||
.popup .popup-content {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<div id="popup" class="popup">
|
||||
<div class="popup-content">
|
||||
<h2 data-i18n="stat_detailed">Detailed Port Statistics</h2>
|
||||
<div id="popup_text"></div>
|
||||
<button id="closePopup" class="action" data-i18n="stat_close">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<h1 data-i18n="stat_heading">Port Statistics</h1>
|
||||
<table id="statstable">
|
||||
<tr> <th data-i18n="stat_col_port">Port</th> <th data-i18n="stat_col_name">Name</th> <th data-i18n="stat_col_link">link</th> <th data-i18n="stat_col_tx_good">TX Good</th> <th data-i18n="stat_col_tx_bad">TX Bad</th> <th data-i18n="stat_col_rx_good">RX Good</th> <th data-i18n="stat_col_rx_bad">RX Bad</th> <th data-i18n="stat_col_all"> All Counters </th></tr>
|
||||
<script src="/stat.js"></script>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
<script src="/navigation.js"></script>
|
||||
</html>
|
||||
@@ -1,207 +0,0 @@
|
||||
const mib_counters = [
|
||||
"Interface in Octets", 8,
|
||||
"", 0,
|
||||
"Interface out Octets", 8,
|
||||
"", 0,
|
||||
"Interface in Unicast Pkts", 8,
|
||||
"", 0,
|
||||
"Interface in Multicast Pkts", 8,
|
||||
"", 0,
|
||||
"Interface in Broadcast Pkts", 8,
|
||||
"", 0,
|
||||
"Interface out Unicast Pkts", 8, // 10
|
||||
"", 0,
|
||||
"Interface out Multicast Pkts", 8,
|
||||
"", 0,
|
||||
"Interface out Broadcast Pkts", 8,
|
||||
"", 0,
|
||||
"Interface out discards", 4,
|
||||
"802.1d Tp Port in discards", 4,
|
||||
"802.3 Single collision frames", 4,
|
||||
"802.3 Multi collision frames", 4,
|
||||
"802.3 Deferred transmissions", 4, // 20
|
||||
"802.3 Late collisions", 4,
|
||||
"802.3 Excessive collisions", 4,
|
||||
"802.3 Symbol errors", 4,
|
||||
"802.3 Control in unknown opcodes", 4,
|
||||
"802.3 In Pause frames", 4,
|
||||
"802.3 Out Pause frames", 4,
|
||||
"Ether drop events", 4,
|
||||
"TX Ether Broadcast Pkts", 4,
|
||||
"TX Ether Multicast Pkts", 4,
|
||||
"TX Ether CRC Align errors", 4, // 30
|
||||
"RX Ether CRC Align errors", 4,
|
||||
"TX Ether Undersized Pkts", 4,
|
||||
"RX Ether Undersized Pkts", 4,
|
||||
"TX Ether Oversized Pkts", 4,
|
||||
"RX Ether Oversized Pkts", 4,
|
||||
"TX Ether Fragments", 4,
|
||||
"RX Ether fragments", 4,
|
||||
"TX Ether Jabbers", 4,
|
||||
"RX Ether Jabbers", 4,
|
||||
"TX Ether Collisions", 4, // 40
|
||||
"TX Ether Pkts 640 Octets", 4,
|
||||
"RX Ether Pkts 640 Octets", 4,
|
||||
"TX Ether 65-127 Octets", 4,
|
||||
"RX Ether 65-127 Octets", 4,
|
||||
"TX Ether Pkts 128-255 Octets", 4,
|
||||
"RX Ether Pkts 128-255 Octets", 4,
|
||||
"TX Ether Pkts 256-511 Octets", 4,
|
||||
"RX Ether Pkts 256-511 Octets", 4,
|
||||
"TX Ether Pkts 512-1023 Octets", 4,
|
||||
"RX Ether Pkts 512-1023 Octets", 4, // 50
|
||||
"TX Ether Pkts 1024-1518 Octets", 4,
|
||||
"RX Ether Pkts 1024-1518 Octets", 4,
|
||||
"", 4,
|
||||
"RX Ether Undersized Drop Pkts", 4, // 54
|
||||
"TX Ether Pkts >1518 Octets", 4,
|
||||
"RX Ether Pkts >1518 Octets", 4,
|
||||
"TX Ether Pkts too large", 4,
|
||||
"RX Ether Pkts too large", 4,
|
||||
"TX Ether Flexible Octets Set 1", 4,
|
||||
"RX Ether Flexible Octets Set 1", 4,// 60
|
||||
"TX Ether Flexible Octets CRC Set 1", 4,
|
||||
"RX Ether Flexible Octets CRC Set 1", 4,
|
||||
"TX Ether Flexible Octets Set 0", 4,
|
||||
"RX Ether Flexible Octets Set 0", 4,
|
||||
"TX Ether Flexible Octets CRC Set 0", 4,
|
||||
"RX Ether Flexible Octets CRC Set 0", 4,
|
||||
"Lenth Field Errors", 4,
|
||||
"False Carriers", 4,
|
||||
"Undersized Octets", 4,
|
||||
"Framing Errors", 4, // 70
|
||||
"", 4,
|
||||
"RX MAC Discards", 4, // 72
|
||||
"RX MAC IPG Short Drop", 4,
|
||||
"", 4,
|
||||
"802.1d TP Learned Entry Discards", 4, // 75
|
||||
"Egress Queue 7 Dropped Pkts", 4,
|
||||
"Egress Queue 6 Dropped Pkts", 4,
|
||||
"Egress Queue 5 Dropped Pkts", 4,
|
||||
"Egress Queue 4 Dropped Pkts", 4,
|
||||
"Egress Queue 3 Dropped Pkts", 4, // 80
|
||||
"Egress Queue 2 Dropped Pkts", 4,
|
||||
"Egress Queue 1 Dropped Pkts", 4,
|
||||
"Egress Queue 0 Dropped Pkts", 4,
|
||||
"Egress Queue 7 Out Pkts", 4,
|
||||
"Egress Queue 6 Out Pkts", 4,
|
||||
"Egress Queue 5 Out Pkts", 4,
|
||||
"Egress Queue 4 Out Pkts", 4,
|
||||
"Egress Queue 3 Out Pkts", 4,
|
||||
"Egress Queue 2 Out Pkts", 4,
|
||||
"Egress Queue 1 Out Pkts", 4, // 90
|
||||
"Egress Queue 0 Out Pkts", 4,
|
||||
"TX Good Counter", 8,
|
||||
"", 0,
|
||||
"RX Good Counter", 8,
|
||||
"", 0,
|
||||
"RX Error Counter", 4,
|
||||
"TX Error Counter", 4,
|
||||
"TX Good Counter PHY", 8,
|
||||
"", 0,
|
||||
"RX Good Counter PHY", 8, // 100
|
||||
"", 0,
|
||||
"RX Error Counter PHY", 4,
|
||||
"TX Error Counter PHY", 4
|
||||
];
|
||||
|
||||
|
||||
function getCounters(port) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
const popup = document.getElementById('popup');
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("Counters: ", JSON.stringify(s));
|
||||
const ptext = document.getElementById('popup_text');
|
||||
var tableHtml = "<table style='width:100%'> <tr> <th>" + t('stat_counter') + "</th> <th>" + t('stat_value') + "</th> <th>" + t('stat_counter') + "</th> <th>" + t('stat_value') + "</th></tr> <tr>";
|
||||
console.log("Counter 0: ", BigInt(s[0]).toString(), " length: ", s.length);
|
||||
var c = 0;
|
||||
for (i = 0; i < mib_counters.length; i += 4) {
|
||||
console.log(i, " ", mib_counters[i], ": ", mib_counters[i + 1]);
|
||||
if (mib_counters[i] == "" && mib_counters[i + 1] == 8) {
|
||||
console.log("c " + i + ": continue");
|
||||
continue;
|
||||
}
|
||||
var count = BigInt(s[i / 4]);
|
||||
if (mib_counters[i + 1] == 8) {
|
||||
tableHtml += "<td>" + mib_counters[i] + "</td><td>" + count.toString() + "</td>";
|
||||
c += 1;
|
||||
} else if (mib_counters[i + 1] == 4) {
|
||||
if (mib_counters[i] != "") {
|
||||
tableHtml += "<td>" + mib_counters[i] + "</td><td>" + (count >> 32n).toString() + "</td>";
|
||||
c += 1;
|
||||
}
|
||||
if (c == 2) {
|
||||
tableHtml += "</tr> <tr>";
|
||||
c = 0;
|
||||
}
|
||||
if (mib_counters[i + 2] != "") {
|
||||
tableHtml += "<td>" + mib_counters[i + 2] + "</td><td>" + (count & 4294967295n).toString() + "</td>";
|
||||
c += 1;
|
||||
}
|
||||
}
|
||||
if (c == 2) {
|
||||
tableHtml += "</tr> <tr>";
|
||||
c = 0;
|
||||
}
|
||||
}
|
||||
ptext.innerHTML = tableHtml + "</tr></table>";
|
||||
popup.style.display = 'flex';
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", "/counters.json?port=" + port, true);
|
||||
xhttp.timeout = 1500; sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
|
||||
function fillStats() {
|
||||
var tbl = document.getElementById('statstable');
|
||||
if (!numPorts)
|
||||
return;
|
||||
if (tbl.rows.length > 1) {
|
||||
for (let i = 0; i < numPorts; i++) {
|
||||
console.log("Table Update row: " + i + " state " + pState[i] + " is " + linkS[pState[i] + 1]);
|
||||
tbl.rows[i + 1].cells[2].innerHTML = linkText(pState[i] + 1);
|
||||
tbl.rows[i + 1].cells[3].innerHTML = `${txG[i]}` + t('common_pkts');
|
||||
tbl.rows[i + 1].cells[4].innerHTML = `${txB[i]}` + t('common_pkts');
|
||||
tbl.rows[i + 1].cells[5].innerHTML = `${rxG[i]}` + t('common_pkts');
|
||||
tbl.rows[i + 1].cells[6].innerHTML = `${rxB[i]}` + t('common_pkts');
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < numPorts; i++) {
|
||||
console.log("Table row: " + i);
|
||||
const tr = tbl.insertRow();
|
||||
let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + (i + 1)));
|
||||
let portName = portNames[physToLogPort[i]] || '';
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(portName));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(linkText(pState[i] + 1)));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(`${txG[i]}` + t('common_pkts')));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(`${txB[i]}` + t('common_pkts')));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(`${rxG[i]}` + t('common_pkts')));
|
||||
td = tr.insertCell(); td.appendChild(document.createTextNode(`${rxB[i]}` + t('common_pkts')));
|
||||
var button = '<button type="button" style="margin: 0 0 0 24px" onclick="getCounters(' + (i + 1) + ');">' + t('stat_show') + '</button>';
|
||||
td = tr.insertCell(); td.innerHTML = button;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const popup = document.getElementById('popup');
|
||||
const closePopup = document.getElementById('closePopup');
|
||||
closePopup.addEventListener('click', () => {
|
||||
popup.style.display = 'none';
|
||||
});
|
||||
window.addEventListener('click', (event) => {
|
||||
if (event.target === popup) {
|
||||
popup.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
update(() => {
|
||||
update();
|
||||
fillStats();
|
||||
const stat = setInterval(fillStats, 1000);
|
||||
const interval = setInterval(update, 2000);
|
||||
});
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title>FreeSwitchOS Spanning Tree</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1>Spanning Tree (RSTP)</h1>
|
||||
<h2>Mode: <select id="stpMode"><option value="off">Disabled</option><option value="on">Enabled</option></select>
|
||||
<input style="width:15%;margin-left: 3em;" class="action" id="stp_sub" onclick="stpSub();" type="button" value="Apply"></h2>
|
||||
<div id="stpStat" style="font-family:monospace;"></div>
|
||||
<h2>Bridge settings</h2>
|
||||
<table id="stpBridge">
|
||||
<tr>
|
||||
<th>Priority</th><th>Version</th><th>Hello [s]</th><th>Max age [s]</th><th>Fwd delay [s]</th><th>Tx hold</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><select id="bPrio"></select></td>
|
||||
<td><select id="bVer"><option value="rstp">RSTP</option><option value="stp">STP compat</option></select></td>
|
||||
<td><input id="bHello" type="number" min="1" max="10" style="width:4em"></td>
|
||||
<td><input id="bMaxage" type="number" min="6" max="40" style="width:4em"></td>
|
||||
<td><input id="bFwd" type="number" min="4" max="30" style="width:4em"></td>
|
||||
<td><input id="bTxhold" type="number" min="1" max="10" style="width:4em"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="font-size:small">Changes apply immediately. Edge ports skip the listen period; guard/filter act on received BPDUs.</p>
|
||||
<h2>Port configuration</h2>
|
||||
<table id="stpPortsTbl">
|
||||
<tr>
|
||||
<th>Port</th><th>State</th><th>Path Cost<br><span style="font-weight:normal;font-size:small">(0 = Auto)</span></th><th>Priority</th><th>Edge Port</th><th>BPDU Filter</th><th>Guard</th><th>Point-to-Point</th>
|
||||
</tr>
|
||||
</table>
|
||||
<h2>Port status</h2>
|
||||
<table id="stpStatTbl">
|
||||
<tr>
|
||||
<th>Port</th><th>Port State</th><th>Role</th><th>Designated Bridge</th><th>Designated Port ID</th><th>Designated Cost</th><th>Oper. Edge</th><th>Oper. P2P</th>
|
||||
</tr>
|
||||
</table>
|
||||
<script src="/stp.js"></script>
|
||||
</div>
|
||||
</body>
|
||||
<script src="/navigation.js"></script>
|
||||
</html>
|
||||
@@ -1,187 +0,0 @@
|
||||
|
||||
const STP_STATES = ["Disabled", "Blocking", "Learning", "Forwarding"];
|
||||
const STP_ROLES = ["-", "Root", "Designated", "Alternate"];
|
||||
|
||||
const PF_ENABLED = 1, PF_ADMEDGE = 2, PF_AUTOEDGE = 4, PF_BPDUGUARD = 8,
|
||||
PF_ROOTGUARD = 16, PF_FILTER = 32, PF_OPEREDGE = 64, PF_TRIPPED = 128;
|
||||
|
||||
var stpDirty = false;
|
||||
var stpRows = 0; // ports table built?
|
||||
|
||||
async function stpCmd(cmd) {
|
||||
stpDirty = true;
|
||||
try {
|
||||
await fetch('/cmd', { method: 'POST', body: cmd });
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
stpDirty = false;
|
||||
fetchStp();
|
||||
}
|
||||
|
||||
function sel(id, opts, onch) {
|
||||
const s = document.createElement("select");
|
||||
s.id = id;
|
||||
for (const [v, label] of opts) {
|
||||
const o = document.createElement("option");
|
||||
o.value = v; o.textContent = label;
|
||||
s.appendChild(o);
|
||||
}
|
||||
s.addEventListener("change", onch);
|
||||
return s;
|
||||
}
|
||||
|
||||
function num(id, min, max, onch) {
|
||||
const n = document.createElement("input");
|
||||
n.type = "number"; n.id = id; n.min = min; n.max = max; n.style.width = "4em";
|
||||
n.addEventListener("change", onch);
|
||||
return n;
|
||||
}
|
||||
|
||||
function buildPortsTable(ports) {
|
||||
const tbl = document.getElementById("stpPortsTbl");
|
||||
const stat = document.getElementById("stpStatTbl");
|
||||
for (const p of [...ports].sort((a, b) => a.p - b.p)) {
|
||||
const tr = tbl.insertRow();
|
||||
tr.insertCell().textContent = p.p; // Port
|
||||
tr.insertCell().appendChild(sel("en_" + p.p,
|
||||
[["on","Enable"],["off","Disable"]],
|
||||
e => stpCmd("stp port " + p.p + " " + e.target.value)));
|
||||
const pc = num("cost_" + p.p, 0, 200000000,
|
||||
e => stpCmd("stp port " + p.p + " cost " + e.target.value));
|
||||
pc.style.width = "7em";
|
||||
pc.title = "0 - 200000000 (0 = Auto)";
|
||||
tr.insertCell().appendChild(pc);
|
||||
const pr = sel("prio_" + p.p, [],
|
||||
e => stpCmd("stp port " + p.p + " prio " + e.target.value));
|
||||
for (let v = 0; v <= 240; v += 16) {
|
||||
const o = document.createElement("option");
|
||||
o.value = v; o.textContent = v + (v === 128 ? " (default)" : "");
|
||||
pr.appendChild(o);
|
||||
}
|
||||
tr.insertCell().appendChild(pr);
|
||||
tr.insertCell().appendChild(sel("edge_" + p.p,
|
||||
[["auto","Auto"],["on","Enable"],["off","Disable"]],
|
||||
e => stpCmd("stp port " + p.p + " edge " + e.target.value)));
|
||||
tr.insertCell().appendChild(sel("filt_" + p.p,
|
||||
[["off","Disable"],["on","Enable"]],
|
||||
e => stpCmd("stp port " + p.p + " filter " + e.target.value)));
|
||||
tr.insertCell().appendChild(sel("guard_" + p.p,
|
||||
[["none","None"],["bpdu","BPDU"],["root","Root"]],
|
||||
e => stpCmd("stp port " + p.p + " guard " + e.target.value)));
|
||||
tr.insertCell().appendChild(sel("p2p_" + p.p,
|
||||
[["auto","Auto"],["on","Enable"],["off","Disable"]],
|
||||
e => stpCmd("stp port " + p.p + " p2p " + e.target.value)));
|
||||
|
||||
const sr = stat.insertRow();
|
||||
sr.insertCell().textContent = p.p;
|
||||
for (const id of ["st","role","db","dp","dc","oe","op"])
|
||||
sr.insertCell().id = id + "_" + p.p;
|
||||
}
|
||||
stpRows = ports.length;
|
||||
}
|
||||
|
||||
function bridgeSelf(s) {
|
||||
return fmtBridgeId((s.prio * 4096).toString(16).padStart(4, "0") + s.myMac);
|
||||
}
|
||||
|
||||
function fmtBridgeId(h) {
|
||||
if (!h || h.length < 16) return "";
|
||||
const prio = parseInt(h.slice(0, 4), 16);
|
||||
const mac = h.slice(4).replace(/(..)(?=.)/g, "$1:");
|
||||
return prio + "-" + mac.toUpperCase();
|
||||
}
|
||||
|
||||
function fetchStp() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
if (!stpRows)
|
||||
buildPortsTable(s.ports);
|
||||
document.getElementById("stpStat").textContent = s.on
|
||||
? (s.weRoot
|
||||
? "This switch (" + bridgeSelf(s) + ") is the root bridge — topology changes: "
|
||||
+ parseInt(s.tc, 16)
|
||||
: "This switch: " + bridgeSelf(s)
|
||||
+ " — root bridge: " + fmtBridgeId(s.rootPrio + s.rootMac)
|
||||
+ " via port " + s.rootPort + " — path cost: " + parseInt(s.cost, 16)
|
||||
+ " — topology changes: " + parseInt(s.tc, 16))
|
||||
: "";
|
||||
for (const p of s.ports) {
|
||||
const trip = (p.f & PF_TRIPPED) ? " (guard!)" : "";
|
||||
document.getElementById("st_" + p.p).textContent =
|
||||
s.on ? STP_STATES[p.st] + trip : "-";
|
||||
document.getElementById("role_" + p.p).textContent =
|
||||
s.on ? STP_ROLES[p.role] : "-";
|
||||
document.getElementById("db_" + p.p).textContent = s.on ? fmtBridgeId(p.db) : "-";
|
||||
document.getElementById("dp_" + p.p).textContent =
|
||||
s.on ? (parseInt(p.dp.slice(0, 2), 16) + "-" + parseInt(p.dp.slice(2), 16)) : "-";
|
||||
document.getElementById("dc_" + p.p).textContent = s.on ? parseInt(p.dc, 16) : "-";
|
||||
document.getElementById("oe_" + p.p).textContent =
|
||||
s.on ? ((p.f & PF_OPEREDGE) ? "True" : "False") : "-";
|
||||
document.getElementById("op_" + p.p).textContent = s.on ? (p.p2 == 2 ? "False" : "True") : "-";
|
||||
}
|
||||
if (stpDirty) // an edit is in flight - do not revert controls
|
||||
return;
|
||||
document.getElementById("stpMode").value = s.on ? "on" : "off";
|
||||
document.getElementById("bPrio").value = s.prio;
|
||||
document.getElementById("bVer").value = s.rstp ? "rstp" : "stp";
|
||||
document.getElementById("bHello").value = s.hello;
|
||||
document.getElementById("bMaxage").value = s.maxage;
|
||||
document.getElementById("bFwd").value = s.fwd;
|
||||
document.getElementById("bTxhold").value = s.txhold;
|
||||
for (const p of s.ports) {
|
||||
document.getElementById("en_" + p.p).value = (p.f & PF_ENABLED) ? "on" : "off";
|
||||
document.getElementById("edge_" + p.p).value =
|
||||
(p.f & PF_ADMEDGE) ? "on" : ((p.f & PF_AUTOEDGE) ? "auto" : "off");
|
||||
document.getElementById("cost_" + p.p).value = parseInt(p.pc, 16);
|
||||
document.getElementById("prio_" + p.p).value = p.prio;
|
||||
document.getElementById("p2p_" + p.p).value = ["auto","on","off"][p.p2];
|
||||
document.getElementById("guard_" + p.p).value =
|
||||
(p.f & PF_BPDUGUARD) ? "bpdu" : ((p.f & PF_ROOTGUARD) ? "root" : "none");
|
||||
document.getElementById("filt_" + p.p).value = (p.f & PF_FILTER) ? "on" : "off";
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("GET", `/stp.json`, true);
|
||||
sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
async function stpSub() {
|
||||
const on = document.getElementById("stpMode").value === "on";
|
||||
document.getElementById("stpStat").textContent = on
|
||||
? "Enabling STP. The ports start blocked and take up to "
|
||||
+ (2 * document.getElementById("bFwd").value)
|
||||
+ " s to reach forwarding, and this page can stay silent until they do."
|
||||
: "Disabling STP.";
|
||||
await stpCmd(on ? "stp on" : "stp off");
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
const bp = document.getElementById("bPrio");
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const o = document.createElement("option");
|
||||
o.value = i; o.textContent = (i * 4096) + (i === 8 ? " (default)" : "");
|
||||
bp.appendChild(o);
|
||||
}
|
||||
bp.addEventListener("change", e => stpCmd("stp prio " + e.target.value));
|
||||
document.getElementById("bVer")
|
||||
.addEventListener("change", e => stpCmd("stp version " + e.target.value));
|
||||
document.getElementById("bHello")
|
||||
.addEventListener("change", e => stpCmd("stp hello " + e.target.value));
|
||||
document.getElementById("bMaxage")
|
||||
.addEventListener("change", e => stpCmd("stp maxage " + e.target.value));
|
||||
document.getElementById("bFwd")
|
||||
.addEventListener("change", e => stpCmd("stp fwd " + e.target.value));
|
||||
document.getElementById("bTxhold")
|
||||
.addEventListener("change", e => stpCmd("stp txhold " + e.target.value));
|
||||
document.getElementById("stpMode")
|
||||
.addEventListener("change", () => { stpDirty = true; });
|
||||
|
||||
update( () => {
|
||||
fetchStp();
|
||||
const interval = setInterval(update, 2000);
|
||||
const stpInt = setInterval(fetchStp, 2000);
|
||||
});
|
||||
});
|
||||
@@ -1,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 |
@@ -1,109 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="sys_title">System Settings</title>
|
||||
<style>
|
||||
.tab-bar { display: flex; border-bottom: 2px solid #226; margin-bottom: 0; margin-left: 16%; padding: 1px 16px; padding-bottom: 0; }
|
||||
.tab-btn { padding: 10px 20px; background-color: #ddf; border: none; cursor: pointer; font-size: 1em; border-radius: 8px 8px 0 0; margin-right: 4px; }
|
||||
.tab-btn:hover { background-color: #aaf; }
|
||||
.tab-btn.active { background-color: #aaf; font-weight: bold; border-bottom: 2px solid #aaf; margin-bottom: -2px; }
|
||||
.tab-content { display: none; }
|
||||
.tab-content.active { display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="tab-bar">
|
||||
<button class="tab-btn active" onclick="openTab(event, 'system-tab')" data-i18n="sys_tab_system">System</button>
|
||||
<button class="tab-btn" onclick="openTab(event, 'advanced-tab')" data-i18n="sys_tab_advanced">Advanced</button>
|
||||
<button class="tab-btn" onclick="openTab(event, 'console-tab')" data-i18n="sys_tab_console">Console</button>
|
||||
</div>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
|
||||
<div id="system-tab" class="tab-content active">
|
||||
<h1 data-i18n="sys_heading">System Settings</h1>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="hostname" data-i18n="sys_hostname">Hostname:</label></div>
|
||||
<div class="rcol"> <input id="hostname" type="text" maxlength="23" size="20"/>
|
||||
<button onclick="hostSub()" data-i18n="sys_apply">Apply</button></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label data-i18n="sys_model">Model:</label></div>
|
||||
<div class="rcol"><span id="model" class="rotext"></span></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="ip" data-i18n="sys_ip">IP address:</label></div>
|
||||
<div class="rcol"> <input id="ip" class="ip" type="text" minlength="7" maxlength="15" size="15"/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="netmask" data-i18n="sys_netmask">Netmask:</label></div>
|
||||
<div class="rcol"><input id="netmask" class="ip" type="text" minlength="7" maxlength="15" size="15"/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="gw" data-i18n="sys_gateway">Gateway:</label></div>
|
||||
<div class="rcol"><input id="gw" class="ip" type="text" minlength="7" maxlength="15" size="15"/></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label for="mgmtvlan" data-i18n="sys_mgmt_vlan">Management VLAN:</label></div>
|
||||
<div class="rcol"><select id="mgmtvlan" class="ip" onchange="mgmtVlanChanged()"></select></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="lcol"> <label data-i18n="sys_language">Language:</label></div>
|
||||
<div class="rcol">
|
||||
<select id="lang-select" onchange="changeLang()">
|
||||
<option value="en">English</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="zh">中文</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<span data-i18n="sys_ip_note">When updating the above settings, remember to point your browser to the new IP afterwards:</span><br/>
|
||||
<input style="width:40%;" class="action" id="ip_sub" onclick="ipSub();" type="button" data-i18n="sys_update" value="Update Settings"><br/>
|
||||
<br/>
|
||||
<span data-i18n="sys_save_label">Save all current settings to Flash:</span><br/>
|
||||
<input style="width:40%;" class="action" id="flash_sub" onclick="flashSave();" type="button" data-i18n="sys_save" value="Save Settings to Flash">
|
||||
</div>
|
||||
|
||||
<div id="advanced-tab" class="tab-content">
|
||||
<h1 data-i18n="sys_advanced">Advanced Settings</h1>
|
||||
<div class="lcol"> <label for="config_display" data-i18n="sys_startup_config">Startup configuration:</label></div>
|
||||
<textarea id="config_display" rows="8" cols="60"></textarea>
|
||||
<br/><br/>
|
||||
<span data-i18n="sys_startup_warn">Be careful when saving the directly edited startup configuration, you can lock yourself out:</span><br/>
|
||||
<input style="width:40%;" class="action" id="clear_config" onclick="clearConfig();" type="button" data-i18n="sys_clear_config" value="Clear Startup Config">
|
||||
<br/>
|
||||
<input style="width:40%;" class="action" id="flash_startup_sub" onclick="flashStartupSave();" type="button" data-i18n="sys_save_startup" value="Save Startup Settings to Flash">
|
||||
<br/>
|
||||
<input style="width:40%;" class="action" id="switch_reset" onclick="resetSwitch();" type="button" data-i18n="sys_reset" value="Reset Switch">
|
||||
</div>
|
||||
|
||||
<div id="console-tab" class="tab-content">
|
||||
<h1 data-i18n="sys_console">Console Command</h1>
|
||||
<label for="console_command" data-i18n="sys_enter_cmd">Enter command:</label>
|
||||
<input type="text" id="console_cmd" name="console_cmd" style="width:40%;">
|
||||
<input style="width:20%;" class="action" id="cmd_sub" onclick="cmdSub();" type="button" data-i18n="sys_send_cmd" value="Send Command"><br/>
|
||||
<br/><br/>
|
||||
<span data-i18n="sys_console_warn">Be careful when entering console commands, you can lock yourself out!</span><br/>
|
||||
<br/>
|
||||
<pre id="console_out" style="height:22em;overflow:auto;white-space:pre-wrap;border:1px solid;padding:0.5em;"></pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="/config.js"></script>
|
||||
<script src="/system.js"></script>
|
||||
<script src="/navigation.js"></script>
|
||||
<script>
|
||||
function openTab(evt, tabId) {
|
||||
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||||
document.getElementById(tabId).classList.add('active');
|
||||
evt.currentTarget.classList.add('active');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,222 +0,0 @@
|
||||
var systemInterval = Number();
|
||||
var isSaving = false;
|
||||
const ips = ["ip", "netmask", "gw"];
|
||||
|
||||
function changeLang() {
|
||||
var lang = document.getElementById('lang-select').value;
|
||||
setLang(lang);
|
||||
}
|
||||
|
||||
function checkIp(ip) {
|
||||
const ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||
if (!ipv4.test(ip)) {alert(t('sys_invalid_ip') + ip); return false };
|
||||
return true;
|
||||
}
|
||||
|
||||
async function ipSub() {
|
||||
for (let i=0;i<3;i++) {
|
||||
if (!checkIp(document.getElementById(ips[i]).value))
|
||||
return;
|
||||
}
|
||||
var cmd = '';
|
||||
for (let i=0; i<3;i++){
|
||||
cmd += ips[i]+' '+document.getElementById(ips[i]).value+'\n';
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
fetchIP();
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdSub() {
|
||||
const input = document.getElementById('console_cmd');
|
||||
const out = document.getElementById('console_out');
|
||||
const cmd = input.value;
|
||||
try {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: cmd
|
||||
});
|
||||
if (response.status == 401) {
|
||||
window.location.href = 'login.html';
|
||||
return;
|
||||
}
|
||||
let text = await response.text();
|
||||
if (text != "" && !text.endsWith("\n"))
|
||||
text += "\n";
|
||||
if (out.textContent.length > 20000)
|
||||
out.textContent = out.textContent.slice(-16000);
|
||||
out.textContent += "> " + cmd + "\n" + text;
|
||||
out.scrollTop = out.scrollHeight;
|
||||
input.value = "";
|
||||
} catch(err) {
|
||||
out.textContent += "> " + cmd + "\n" + err + "\n";
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function hostSub() {
|
||||
const h = document.getElementById("hostname").value;
|
||||
try { await fetch('/cmd', { method: 'POST', body: "hostname " + h }); }
|
||||
catch(err) { console.error(`Error: ${err}`); }
|
||||
fetchIP();
|
||||
}
|
||||
|
||||
|
||||
async function sendConfig(c) {
|
||||
if (isSaving) return;
|
||||
isSaving = true;
|
||||
clearInterval(systemInterval);
|
||||
const form = new FormData();
|
||||
form.append("MAX_FILE_SIZE", "4096");
|
||||
form.append("configuration", new Blob([c], {type: "application/octet-stream"}), "config.txt");
|
||||
try {
|
||||
const response = await fetch('/config', {
|
||||
method: 'POST',
|
||||
body: form
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
try {
|
||||
await fetch('/cmd_log_clear', { method: 'GET' });
|
||||
} catch(e) {}
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
} finally {
|
||||
isSaving = false;
|
||||
systemInterval = setInterval(fetchIP, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function flashSave() {
|
||||
configuration = [];
|
||||
const savedConfig = await fetchConfig();
|
||||
const cmdLog = await fetchCmdLog();
|
||||
if (savedConfig) parseConf(savedConfig);
|
||||
if (cmdLog) parseConf(cmdLog);
|
||||
const body = configuration.join('\n') + '\n';
|
||||
console.log("CONFIGURATION to save: ", body);
|
||||
await sendConfig(body);
|
||||
}
|
||||
|
||||
async function flashStartupSave() {
|
||||
var configContent = document.getElementById("config_display").value;
|
||||
console.log("CONFIGURATION to save: ", configContent);
|
||||
sendConfig(configContent);
|
||||
// Clear the command log 1 second after initiating the config save
|
||||
setTimeout(() => {
|
||||
fetch('/cmd_log_clear', { method: 'GET' })
|
||||
.then(response => console.log('Command log cleared', response))
|
||||
.catch(err => console.error('Error clearing command log:', err));
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function clearConfig() {
|
||||
document.getElementById("config_display").value = "";
|
||||
|
||||
// Validate and populate with current IP settings
|
||||
for (let i=0; i<3; i++) {
|
||||
if (!checkIp(document.getElementById(ips[i]).value))
|
||||
return;
|
||||
}
|
||||
|
||||
var configLines = "";
|
||||
for (let i=0; i<3; i++){
|
||||
var cmd = ips[i]+' '+document.getElementById(ips[i]).value;
|
||||
configLines += cmd + "\n";
|
||||
}
|
||||
|
||||
document.getElementById("config_display").value = configLines;
|
||||
}
|
||||
|
||||
function fetchIP() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("IP: ", s);
|
||||
document.getElementById("ip").value=s.ip_address;
|
||||
document.getElementById("netmask").value=s.ip_netmask;
|
||||
document.getElementById("gw").value=s.ip_gateway;
|
||||
document.getElementById("hostname").value=s.hostname;
|
||||
document.getElementById("model").textContent=s.hw_ver;
|
||||
loadMgmtVlan();
|
||||
clearInterval(systemInterval);
|
||||
// Fetch and populate the config textbox
|
||||
fetchConfig().then((configText) => {
|
||||
let fullConfig = configText;
|
||||
// Fetch and append cmd_log
|
||||
//return fetchCmdLog().then((cmdLogText) => {
|
||||
// if (cmdLogText) {
|
||||
// fullConfig = fullConfig + cmdLogText;
|
||||
// }
|
||||
document.getElementById("config_display").value = fullConfig;
|
||||
});
|
||||
};
|
||||
}
|
||||
xhttp.open("GET", `/information.json`, true);
|
||||
xhttp.send();
|
||||
}
|
||||
|
||||
function resetSwitch() {
|
||||
if (!confirm(t('sys_reset_confirm'))) {
|
||||
return;
|
||||
}
|
||||
fetch('/reset', { method: 'GET' }).catch(() => {});
|
||||
setTimeout(() => {
|
||||
alert(t('sys_resetting'));
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
var langSel = document.getElementById('lang-select');
|
||||
if (langSel) langSel.value = rtlLang;
|
||||
systemInterval = setInterval(fetchIP, 1000);
|
||||
});
|
||||
|
||||
|
||||
var mgmtVlanCurrent = 0;
|
||||
|
||||
function loadMgmtVlan() {
|
||||
var sel = document.getElementById('mgmtvlan');
|
||||
if (!sel) return;
|
||||
fetch('/vlanlist').then(function(r) { return r.json(); }).then(function(d) {
|
||||
var cur = d.mgmt || 0;
|
||||
var list = d.vlan || [];
|
||||
mgmtVlanCurrent = cur;
|
||||
sel.innerHTML = '';
|
||||
if (!cur) {
|
||||
var none = document.createElement('option');
|
||||
none.value = 0; none.disabled = true;
|
||||
none.textContent = t('sys_mgmt_untagged');
|
||||
sel.appendChild(none);
|
||||
}
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var o = document.createElement('option');
|
||||
o.value = list[i].id;
|
||||
o.textContent = list[i].name ? (list[i].id + ' (' + list[i].name + ')') : list[i].id;
|
||||
sel.appendChild(o);
|
||||
}
|
||||
sel.value = cur;
|
||||
}).catch(function(err) { console.error('VLAN list failed:', err); });
|
||||
}
|
||||
|
||||
function mgmtVlanChanged() {
|
||||
var sel = document.getElementById('mgmtvlan');
|
||||
var id = parseInt(sel.value, 10);
|
||||
if (!id || id === mgmtVlanCurrent) return;
|
||||
if (!confirm(t('sys_mgmt_confirm') + id + '.\n\n' + t('sys_mgmt_warn'))) {
|
||||
sel.value = mgmtVlanCurrent;
|
||||
return;
|
||||
}
|
||||
fetch('/cmd', { method: 'POST', body: 'vlan ' + id + ' mgmt' })
|
||||
.then(function() { mgmtVlanCurrent = id; })
|
||||
.catch(function(err) { console.error('Set management VLAN failed:', err); sel.value = mgmtVlanCurrent; });
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="update_title">Firmware update</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;width:40%;">
|
||||
<h1 data-i18n="update_heading">Firmware Update</h1>
|
||||
<form enctype="multipart/form-data" action="/upload" method="POST">
|
||||
<input type="hidden" name="MAX_FILE_SIZE" value="1000000" />
|
||||
<span data-i18n="update_instruction">Choose a firmware update file to upload:</span> <br/> <br/>
|
||||
<input name="uploadedfile" type="file" accept=".bin" /><br />
|
||||
<input style="margin-top:3em" type="submit" data-i18n="update_upload" value="Upload File" />
|
||||
</form>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,59 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<script src="/main.js"></script>
|
||||
<script src="/i18n.js"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title data-i18n="vlan_title">FreeSwitchOS VLAN Configuration</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="sidebar"></nav>
|
||||
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
|
||||
<div id="ports"></div>
|
||||
<h1 data-i18n="vlan_heading">VLAN Configuration</h1>
|
||||
<form id="vform" action="/vlan.html">
|
||||
<div>
|
||||
<label for="vlanSelect" data-i18n="vlan_select">VLAN Select:</label>
|
||||
<select id="vlanSelect" style="margin: 0 0 0 8px">
|
||||
<option value="" disabled selected data-i18n="vlan_choose">— VLAN Choose —</option>
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
<div>
|
||||
<label for="vid" data-i18n="vlan_id">VLAN ID:</label>
|
||||
<input type="number" min="1" max="4094" id="vid" name="vid">
|
||||
<button type="button" style="margin: 0 0 0 24px" onclick="fetchVLAN();" data-i18n="vlan_get_config">Get Configuration</button>
|
||||
</div>
|
||||
<br/><br/>
|
||||
<label for="vname" data-i18n="vlan_name">VLAN Name:</label>
|
||||
<input type="text" id="vname" name="vname"><br><br>
|
||||
<br/>
|
||||
<h2 data-i18n="vlan_tagged">Tagged Ports</h2>
|
||||
<div id="tPorts"><button type="button" style="transform: translateY(-100%);margin: 0 50px 0 0" onclick="utClicked(true);" data-i18n="vlan_select_all">Select all</button></div>
|
||||
<h2 data-i18n="vlan_untagged">Untagged Ports</h2>
|
||||
<div id="uPorts"><button type="button" style="transform: translateY(-100%); margin: 0 50px 0 0" onclick="utClicked(false);" data-i18n="vlan_select_all">Select all</button> </div>
|
||||
<h2 data-i18n="vlan_pvid">Use as default VLAN for incoming traffic (PVID)</h2>
|
||||
<div id="pPorts"><button type="button" style="transform: translateY(-100%); margin: 0 50px 0 0" onclick="pvClicked(true);" data-i18n="vlan_select_all">Select all</button> </div>
|
||||
<script src="/vlan.js"></script>
|
||||
<br/> <input style="width:40%;" class="action" id="vlan_sub" onclick="vlanSub();" type="button" data-i18n="vlan_update" value="Update / Create">
|
||||
<script src="/vlan_sub.js"></script>
|
||||
</form>
|
||||
<h2 data-i18n="vlan_configured">Configured VLANs</h2>
|
||||
<table id="vlanTable" style="width:90%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>VLAN</th>
|
||||
<th data-i18n="vlan_col_name">Name</th>
|
||||
<th data-i18n="vlan_col_member">Member Ports</th>
|
||||
<th data-i18n="vlan_col_tagged">Tagged Ports</th>
|
||||
<th data-i18n="vlan_col_untagged">Untagged Ports</th>
|
||||
<th data-i18n="vlan_col_pvid">PVID Ports</th>
|
||||
<th data-i18n="vlan_col_delete">Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="vlanTableBody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script src="/navigation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,211 +0,0 @@
|
||||
var vlanInterval = Number();
|
||||
|
||||
function vlanForm() {
|
||||
if (!numPorts)
|
||||
return;
|
||||
var t = document.getElementById('tPorts');
|
||||
var u = document.getElementById('uPorts');
|
||||
var p = document.getElementById('pPorts');
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
const d = document.createElement("div");
|
||||
d.classList.add("cbgroup");
|
||||
const l = document.createElement("label");
|
||||
l.innerHTML = "" + i;
|
||||
l.classList.add("cbgroup");
|
||||
const inp = document.createElement("input");
|
||||
inp.type = "checkbox"; inp.setAttribute("class","psel");
|
||||
inp.id = "tport" + i;
|
||||
inp.setAttribute('onclick', `setC("u", ${i}, false);`);
|
||||
const o = document.createElement("img");
|
||||
if (pIsSFP[i - 1]) {
|
||||
o.src = "sfp.svg"; o.width ="60"; o.height ="60";
|
||||
} else {
|
||||
o.src = "port.svg"; o.width = "40"; o.height = "40";
|
||||
}
|
||||
l.appendChild(inp); l.appendChild(o);
|
||||
d.appendChild(l)
|
||||
t.appendChild(d);
|
||||
var d2=d.cloneNode(true);
|
||||
d2.children[0].children[0].id = "uport" + i;
|
||||
d2.children[0].children[0].setAttribute('onclick', `setC("t", ${i}, false);`);
|
||||
u.appendChild(d2);
|
||||
var d3=d.cloneNode(true);
|
||||
d3.children[0].children[0].id = "pport" + i;
|
||||
d3.children[0].children[0].removeAttribute('onclick');
|
||||
p.appendChild(d3);
|
||||
}
|
||||
}
|
||||
|
||||
function setC(t, p, c){
|
||||
document.getElementById(t+'port'+p).checked=c;
|
||||
}
|
||||
|
||||
function utClicked(t){
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
setC('t', i, t); setC('u', i, !t);
|
||||
}
|
||||
}
|
||||
|
||||
function pvClicked(p){
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
setC('p', i, p);
|
||||
}
|
||||
}
|
||||
|
||||
function fetchVLAN() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
const s = JSON.parse(xhttp.responseText);
|
||||
console.log("VLAN: ", JSON.stringify(s));
|
||||
m = parseInt(s.members, 16);
|
||||
document.getElementById('vname').value = s.name;
|
||||
var members = m & 0x3FF;
|
||||
var untag = (m >> 10) & 0x3FF;
|
||||
var pvid = parseInt(s.pvid, 16);
|
||||
console.log("PVID: ", pvid);
|
||||
for (let p = 1; p <= numPorts; p++) {
|
||||
var bit = physToLogPort[p-1];
|
||||
var isMember = (members >> bit) & 1;
|
||||
var isUntag = (untag >> bit) & 1;
|
||||
setC('t', p, isMember && !isUntag);
|
||||
setC('u', p, isMember && isUntag);
|
||||
setC('p', p, (pvid >> bit) & 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
var v=document.getElementById('vid').value
|
||||
if (!v) {
|
||||
alert(t('vlan_set_id_first'));
|
||||
return;
|
||||
}
|
||||
xhttp.open("GET", `/vlan.json?vid=${v}`, true);
|
||||
sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
function portsToRange(mask, nPorts) {
|
||||
var parts = [];
|
||||
var start = -1, prev = -1;
|
||||
for (var p = 1; p <= nPorts; p++) {
|
||||
var bit = physToLogPort[p - 1];
|
||||
if ((mask >> bit) & 1) {
|
||||
if (start < 0) start = p;
|
||||
prev = p;
|
||||
} else {
|
||||
if (start >= 0) {
|
||||
parts.push(start === prev ? String(start) : start + '-' + prev);
|
||||
start = -1; prev = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start >= 0)
|
||||
parts.push(start === prev ? String(start) : start + '-' + prev);
|
||||
return parts.length ? parts.join(',') : '-';
|
||||
}
|
||||
|
||||
async function loadVlanTable() {
|
||||
var tbody = document.getElementById('vlanTableBody');
|
||||
if (!tbody) return;
|
||||
tbody.innerHTML = '';
|
||||
var resp;
|
||||
try { resp = await fetch('/vlanlist'); } catch(e) { return; }
|
||||
if (!resp.ok) return;
|
||||
var vlans = (await resp.json()).vlan || [];
|
||||
for (var i = 0; i < vlans.length; i++) {
|
||||
var v = vlans[i];
|
||||
var vresp;
|
||||
try { vresp = await fetch('/vlan.json?vid=' + v.id); } catch(e) { continue; }
|
||||
if (!vresp.ok) continue;
|
||||
var s = await vresp.json();
|
||||
var m = parseInt(s.members, 16);
|
||||
var members = m & 0x3FF;
|
||||
var untag = ((m >> 10) & 0x3FF) & members;
|
||||
var tagged = members & ~untag;
|
||||
var pvid = parseInt(s.pvid, 16) & 0x3FF;
|
||||
var tr = document.createElement('tr');
|
||||
var td, a, btn;
|
||||
td = document.createElement('td');
|
||||
a = document.createElement('a');
|
||||
a.href = '#';
|
||||
a.textContent = v.id;
|
||||
(function(vid) {
|
||||
a.onclick = function(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('vid').value = vid;
|
||||
fetchVLAN();
|
||||
};
|
||||
})(v.id);
|
||||
td.appendChild(a); tr.appendChild(td);
|
||||
td = document.createElement('td');
|
||||
td.textContent = v.name || ''; tr.appendChild(td);
|
||||
td = document.createElement('td');
|
||||
td.textContent = portsToRange(members, numPorts); tr.appendChild(td);
|
||||
td = document.createElement('td');
|
||||
td.textContent = portsToRange(tagged, numPorts); tr.appendChild(td);
|
||||
td = document.createElement('td');
|
||||
td.textContent = portsToRange(untag, numPorts); tr.appendChild(td);
|
||||
td = document.createElement('td');
|
||||
td.textContent = portsToRange(pvid, numPorts); tr.appendChild(td);
|
||||
td = document.createElement('td');
|
||||
if (v.id !== 1) {
|
||||
btn = document.createElement('button');
|
||||
btn.textContent = '✕';
|
||||
(function(vid) {
|
||||
btn.onclick = function() { deleteVlan(vid); };
|
||||
})(v.id);
|
||||
td.appendChild(btn);
|
||||
}
|
||||
tr.appendChild(td);
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteVlan(id) {
|
||||
if (!confirm(t('vlan_delete_confirm') + id + '?')) return;
|
||||
fetch('/cmd', { method: 'POST', body: 'vlan ' + id + ' d' })
|
||||
.then(function() { refreshVlanViews(); })
|
||||
.catch(function(err) { console.error('Delete failed:', err); });
|
||||
}
|
||||
|
||||
function refreshVlanViews() {
|
||||
loadVlanList();
|
||||
loadVlanTable();
|
||||
}
|
||||
|
||||
function loadVlanList() {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState !== 4) return;
|
||||
var sel = document.getElementById('vlanSelect');
|
||||
if (this.status !== 200) {
|
||||
sel.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
var vlans = JSON.parse(this.responseText).vlan || [];
|
||||
if (!vlans.length) {
|
||||
sel.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
sel.options.length = 1;
|
||||
for (var i = 0; i < vlans.length; i++) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = vlans[i].id;
|
||||
opt.text = vlans[i].name ? vlans[i].id + ' — ' + vlans[i].name : String(vlans[i].id);
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
};
|
||||
xhttp.open('GET', '/vlanlist', true);
|
||||
sendXHTTP(xhttp);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
update( () => {
|
||||
vlanForm();
|
||||
refreshVlanViews();
|
||||
document.getElementById('vlanSelect').onchange = function() {
|
||||
document.getElementById('vid').value = this.value;
|
||||
fetchVLAN();
|
||||
};
|
||||
const interval = setInterval(update, 2000);
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
async function vlanSub() {
|
||||
var commands = [];
|
||||
var cmd = "vlan ";
|
||||
var v=document.getElementById('vid').value
|
||||
if (!v) {
|
||||
alert(t('vlan_set_id_first'));
|
||||
return;
|
||||
}
|
||||
cmd = cmd + v;
|
||||
if (document.getElementById('vname').value)
|
||||
cmd = cmd + ' ' + document.getElementById('vname').value;
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
if (document.getElementById('tport' + i).checked)
|
||||
cmd = cmd + ` ${i}t`;
|
||||
else if (document.getElementById('uport' + i).checked)
|
||||
cmd = cmd + ` ${i}`;
|
||||
}
|
||||
commands.push(cmd);
|
||||
for (let i = 1; i <= numPorts; i++) {
|
||||
if (document.getElementById('pport' + i).checked)
|
||||
commands.push(`pvid ${i} ${v}`);
|
||||
}
|
||||
try {
|
||||
for (let c of commands) {
|
||||
const response = await fetch('/cmd', {
|
||||
method: 'POST',
|
||||
body: c
|
||||
});
|
||||
console.log('Completed!', response);
|
||||
}
|
||||
refreshVlanViews();
|
||||
} catch(err) {
|
||||
console.error(`Error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||