diff --git a/README.md b/README.md
index 0870e3e..4261a0a 100644
--- a/README.md
+++ b/README.md
@@ -2,5 +2,16 @@
这是一个x1pro 刷机成cudytr3000
+## v46.1-ui-mt76 (2026-08-19)
+
+`sysupgrade-v46.1-ui-mt76.bin` (14,797,623 B, SHA-256 `629cc6a3edd3e3870d2c260dbaf04597ea01db4b8ab23db15f54825548d7ff4c`) — 基于 mt76 开源驱动 v46 正式版基线,**只恢复 UI 层**:Argon 主题 + Bing 壁纸(本地 bg1.jpg fallback)、完整 WG 隧道插件(token 化 apply + 快照回滚)、只读 Link Health 仪表盘。不恢复 turboacc/HNAT/WARP/BBR/mt_wifi/wrtbwmon。
+
+- 补丁源码:`immortalwrt-patches/feeds/luci/`(4 个包 + 工具 `tools/`)
+- kernel 成员与 v46 byte-identical;rootfs +408KB 全 UI,denylist 零变化
+- **固件本体含烤入的 WG 私钥/PSK,未放入 git**(存 K 盘),hash 见 `docs/固件哈希记录.md`
+- 完整开发经历与翻车记录:`docs/project_v46_v461_development_and_failures.md`
+- 尚未刷机实机验收
+
## v37 (2026-07-25)
`sysupgrade-v37.bin` (50,473,789 B, md5 `353fc69dac266a5b7a5dea4647b24eb6`) — 修 LuCI 信道分析页 3 补丁 (mt_wifi freq.band/扫描 band/tab-active) + 坑#6 根治 (rc.local + 30-mss-clamp 在 br_netfilter 加载后兜底重写 `net.bridge.bridge-nf-call-*=1` 与 `nft mangle_forward set 1360`). 详细如 `记忆/project_v37_build.md` (本地内存索引).
+
diff --git a/docs/project_v46_v461_development_and_failures.md b/docs/project_v46_v461_development_and_failures.md
new file mode 100644
index 0000000..d1a444b
--- /dev/null
+++ b/docs/project_v46_v461_development_and_failures.md
@@ -0,0 +1,149 @@
+# v46 / v46.1 换固件开发经历与翻车记录
+
+> 2026-08-17 → 2026-08-19,Cudy TR3000 (MT7981B, ImmortalWrt 24.10, mt76, Linux 6.6.151)。
+> 本文记录从 mt_wifi 私有驱动迁到 mt76 开源驱动、最终落地 v46 正式版 + v46.1 UI-only 恢复的完整经历,以及每一处翻车的根因。
+
+---
+
+## 一、为什么换固件(动机)
+
+老固件(Gzxhwq mt_wifi 系)在 2026-07 的整个 DBDC/HE160 战役后,数据面仍不稳定:
+
+- **mt_wifi 私有驱动**:`wifi reload` 卡死、重启后数据面时好时坏(assoc 正常但 0 数据包)、计数器说谎(Tx=0 但流量在跑)、UCI 状态与物理广播不一致。
+- **DBDC merge 字段 swap**:UCI 必须反向写才能广播正确,LuCI 显示天然错位,每次开机 ra0/rax0 ↔ dat 绑定还随机。
+- **无法 LuCI 完全接管**:改无线要么不生效、要么重启盒子,v25 更是直接变砖(见下)。
+
+用户评估后拍板:**「评估开源的吧」「用官方树重编(干净)」**。
+
+---
+
+## 二、v25 教训(先把它钉死,别让历史重演)
+
+> 用户特别要求把 v25 的教训记进来。它来自 2026-07-20 的 mt_wifi 时代,但教训完全适用 mt76。
+
+**现象**:`sysupgrade-v25.bin` 刷入后**无限重启**,必须 U-Boot 回刷 v11 恢复。
+
+**根因**:`files/etc/hotplug.d/net/99-wifi-band-sync` 这个脚本,在 `ra*/rax*` netdev up 事件里 `uci commit` → `netifd reload wifi` → 新的 netdev up 事件 → hotplug 再触发 → **commit→reload→event 死循环**。
+
+**三条铁律(对一切新固件生效)**:
+
+1. **任何会触发 wifi reload 的逻辑,绝对不允许放进 `hotplug.d/net`**。netdev 事件是 reload 的直接结果,一放进去就是自激回路。
+2. **uCI commit 本身不触发 netifd**,结构上不可能循环——只有在 hotplug 事件里主动调 reload 才会。要让状态稳定,要么 commit-only,要么 log-only,绝不 reload。
+3. 检测类逻辑要「连续两次读数一致才算稳定」(v24 教训:在驱动 init 中途 30s 抓到瞬态 2.4G 读数把 UCI 改反)。v27 的 commit-only verifier 仍会在 reload 间反复翻 UCI,最终 v28 改 log-only + 显示层 patch 才根治。
+
+---
+
+## 三、mt76 迁移评估(2026-08-17)
+
+先做研究(`project_mt76_migration_research.md`):
+
+- **mt76 就是 TR3000 的官方驱动**(`DEVICE_PACKAGES=kmod-mt7915e`),Cudy 官方即 mt76。
+- **下载腰斩 bug**(openwrt/mt76#980):5G 侧已被 commit `0d6eaa4`(2025-09-18,precal-data offset)修复,构建树的 mt76 `PKG_SOURCE_DATE=2025-11-06` 已包含。2.4G 可能略慢于原厂。
+- **WED bug**(openwrt/openwrt#20473):WED 开启时可能掉到 ~140Mbps,需要关注。
+- **关键决策:不用「嫁接」**——在 mt_wifi 树上恢复 mt76+mac80211-scripts+wifi-scripts 会撞无底洞 patch 冲突(mt_wifi 树用 backports 6.12.6,官方 24.10 用 6.12.96)。**全新克隆官方 `openwrt-24.10` 树 `immortalwrt-mt76`**,mt76 原生无冲突。
+
+---
+
+## 四、v45.1 六连翻车(2026-08-17 一天六个版本,五个假说全证伪)
+
+### v45.1 — mt76 CLEAN 官方树(md5 `202e4a15...`)
+干净 mt76 基线 + 移植 WG/VXLAN/隧道。发现 **mt76 macaddr bug**:驱动读到 multicast 位 MAC(`d1:fd`)→ hostapd 拒 UP。解法:`wifi-iface` 设合法单播 MAC(`fc:a0`),`wifi-device` 层无效。已固化。
+
+### v45.2 — ip-full 修 VXLAN 空壳(md5 `4dd520ac...`)
+**症状**:刷 v45.1 后「办公室服务完全访问不了」。**真根因**:v45 用了 `ip-tiny`,`ip link add x type vxlan id 10` 直接报错,netifd vxlan proto 有 bug 建出**空壳 vxlan0**(vni/remote/dstport 全空,帧封装不进 VXLAN)。老 mt_wifi 固件有 `ip-full` 所以 20-vxlan 脚本能跑。**修复**:`CONFIG_PACKAGE_ip-full=y` + 关 ip-tiny + ip-bridge=y。
+
+### v45.3 — vxlan BUILT-IN 内核(md5 `2b4980ba...`,假说一)
+**症状**:仍 RX=0。**假说**:`CONFIG_NET_UDP_TUNNEL`/`CONFIG_NET_IP_TUNNEL`/`CONFIG_VXLAN` 是 `=m`(module),built-in 保证 udp_tunnel encap 基础设施先就绪。**修复尝试**:强制 `=y` built-in,去 kmod-vxlan。另烤入两 iface `uapsd=0`(手机 WiFi 51→2ms,这个保留了下来)。
+
+### v45.6 — 关 JUMP_LABEL(md5 `330c923a...`,假说二)
+**症状**:仍 RX=0。**kprobe/ftrace 铁证**:VXLAN 回程帧到 wg0 后 `udp_queue_rcv_one_skb` 被调 23×,`udp_encap_enable()` 执行(static_branch_inc++),但 `vxlan_rcv` 从不执行 0× → ARM64 6.6.151 的 jump-label patch 没生效(老 6.6.95 没这 bug)。**修复尝试**:关 `CONFIG_JUMP_LABEL` 让 static_branch_unlikely 退化成普通运行时读 key。**仍失败**。
+
+> 假说三(v45.4/997 VXLAN GRO patch、v45.5-dbg/ftrace+kprobe、v45.6/998 WG GRO bypass)全是内核方向,现场抓取证,最终全部证伪,这些实验 patch 在 v46 全清。
+
+### 最终真根因(2026-08-18,v46 前夕)—— 不是内核!
+`vxlan0 RX=0` 的真根因是:**`/etc/config/firewall` 只把 `lan` 放 LAN zone、`wan wan6` 放 WAN zone,`wg0` 不属于任何 zone**。办公室回程的 VXLAN 包从 wg0 进 INPUT,fw4 只接受 br-lan/eth0,`10.99.0.2:4789` 落入 `handle_reject`。所以 WG handshake/transfer 正常、wg0 抓得到 ARP Reply,但 vxlan0 RX 恒 0。
+
+**修复**(授权后现场):`uci add_list firewall.@zone[0].network='wg0'` + 重载 fw4。立即 ping 192.168.1.1 8/8、RX +54、HTTP/HTTPS/DNS 正常、OpenClash fake-IP `198.18.25.135`。
+
+**血泪总结**:v45.1→v45.6 六个版本,两个是环境工具(ip-tiny、macaddr),三个是内核假说(built-in、JUMP_LABEL、GRO/GRO-bypass)**全部证伪**,真凶是**防火墙 zone 归属**——`/etc/config/firewall` 里少写一个 `wg0` 的**最普通的一行配置**。**先查配置,再怀疑内核。**
+
+**讽刺的是:为查这个「最简单的地方」,我们把调试下探到了最深——改内核内存**。v45.3 把 `NET_UDP_TUNNEL`/`VXLAN` 强制 built-in;v45.6 关 `CONFIG_JUMP_LABEL` 让 jump-label 不再 patch 内核内存里的代码(ARM64 6.6.151 的 static_branch 代码补丁疑似失效);v45.5-dbg 用 kprobe/ftrace 在内核内存里插桩取证据(`udp_queue_rcv_one_skb` 被调 23×、`vxlan_rcv` 0×)。也就是说,从 `uci add_list` 这一行就能修好的问题,我们一路干到动内核内存——**越简单的根因越容易藏在最深的地方,排查顺序应当反过来:先配置、后路由、再 nft、最后才内核。**
+
+---
+
+## 五、v46 正式版落地(2026-08-18)
+
+全量 `make dirclean` 构建(kernel 6.6.151,`fa661850` 主树 + `350d41da` LuCI feed):
+
+- LAN `br-lan`+`eth1`,管理 IP `192.168.1.2/24`;WAN `eth0` DHCP 仅承载自身 + WG underlay
+- WG `10.99.0.2/32` ↔ 办公室 `10.99.0.1/32`;VXLAN VNI 10/UDP 4789/`nolearning` 入 br-lan
+- **`wg0` 与 `lan` 同属 LAN zone**(上面真根因的固化)
+- 零 forwarding、WAN 无 masq、flow offload 关、`kmod-br-netfilter` + MSS clamp 1330
+- 小路由 DHCP/RA/DHCPv6/DNS redirect 全关,客户端由办公室 192.168.1.1 供
+- 两路 mt76 AP 保留合法单播 MAC + `uapsd=0`
+- `/etc/config/{network,firewall,dhcp,wireless}` 打包权限 `0600`
+
+**产物**:`sysupgrade-v46-formal-mt76.bin`(14,388,023 bytes, SHA-256 `261bcb92...`)。**已刷机、已实机验收**(WG/VXLAN、防泄漏、MSS 1330、mt76 HE160、1GbE 全达标)。
+
+---
+
+## 六、v46.1 UI-only 恢复(2026-08-19)—— 第二轮翻车
+
+v46 是纯 Bootstrap 基线。用户要恢复 Argon + Bing 壁纸 + WG 隧道插件 + 只读 Link Health 仪表盘。约束:**不恢复 turboacc/HNAT/WARP/BBR/mt_wifi/wrtbwmon 或任何可写加速开关**。
+
+### 翻车 1:`=m` 只编译不进镜像(本轮最坑)
+
+```
+首次 make → rootfs 与 v46 完全同尺寸 (10000384 bytes),新增文件 0!
+```
+
+`.config` 里 5 个 UI 包都是 `=m`(module)。**`=m` 只是「编译成 .ipk 但不装入镜像」,进镜像的包必须是 `=y`**。改成 `=y` + `make defconfig` 重编 → rootfs +408KB,新增 70 文件全 UI。
+
+**教训**:`make` 成功 ≠ 包进镜像。增量构建后用 `diff -rq` 对比新旧 rootfs 清单,0 新增就该立刻警觉。
+
+### 翻车 2:theme-argon 的 6 张图片被源树「删掉」
+
+feeds/luci 工作区里 `argon.svg` logo、**`bg1.jpg` 壁纸 fallback(159KB)**、`blank.png`、volume×2 全部消失——这就是「登录页背景不显示」的真根因,不是 upstream 没 ship。`git checkout` 恢复。Bing 失败后的本地 `bg1.jpg` fallback 由此生效。
+
+### 翻车 3:前端 `ReferenceError: push is not defined`
+
+Link Health 页面「健康检测全没数据」。根因:后端 ucode 用全局 `push()`,前端 JS 我沿用了 `push(rows, ...)`,但**浏览器 JS 里 `push` 不是全局函数**,必须 `rows.push(...)`。全量扫了三个前端 JS,只此一处。
+
+### 翻车 4(预览期,已修):两个状态解析 bug
+
+- **路由 present=false**:真实路由 `10.99.0.1 dev wg0 scope link` 是正常 host 路由,但 `ip -j` 输出 `dst:"10.99.0.1"`(省略 `/32`),后端只比对 `10.99.0.1/32` 漏判。改为同时接受裸地址与 `/32`。
+- **vxlan destination_port=null**:真实 `dstport 4789`,但 iproute2 在 JSON 里**省略默认端口字段**。缺省即等于 4789,缺省按 REQUIRED_VXLAN_PORT 处理。
+
+### v46.1 产物与验收(全过)
+
+- `sysupgrade-v46.1-ui-mt76.bin`(14,797,623 bytes, SHA-256 `629cc6a3...`),K 盘双份 hash 复核一致
+- **kernel 成员与 v46 byte-identical**(`366f1c78...`,4,375,268 bytes)——增量构建复用 v46 kernel object,绝不 dirclean
+- rootfs +408KB:新增 70 文件全 UI,0 删除,**共享文件内容变化 0**
+- denylist 零变化:`/etc/config/{network,firewall,dhcp,wireless}`(0600)、rc.local、20-vxlan、30-mss-clamp、mss nft 全 hash 一致
+- manifest 无禁项:无 turboacc/wrtbwmon/mtkhnat/WARP/mt_wifi/BBR;`iw` 6.9-r1 进镜像(Link Health 依赖)
+- 烤入的均为硬化版:`rows.push`、`which_bin` 后端、`handleSaveApply:null` 禁用、sysauth local-wins、无 `ui.changes.apply`
+
+**状态**:增量构建 + 静态验收通过,**尚未刷机/实机验收**。
+
+---
+
+## 七、方法论沉淀
+
+1. **先查配置,再怀疑内核**。v45 的三个内核假说全错,真凶是防火墙 zone 里少写一行 `wg0`——**最简单的地方**。为了修这一行,我们下探到改内核内存(built-in、JUMP_LABEL、kprobe 插桩)全白费。排查顺序永远从浅到深:`/etc/config/*` → 路由表 → nft 规则 → 内核。越简单的根因越藏得深。
+2. **`=y` 才进镜像,`=m` 只是 .ipk**。构建完必须解包对比 rootfs 清单,0 增删=没烤进去。
+3. **前端 JS 别抄 ucode 语法**。`push()`/`match()`/`length()` 在浏览器 JS 里语义完全不同,跨层复用代码前先想清楚运行环境。
+4. **netdev/hotplug 里绝不 reload wifi**(v25 铁律)。commit-only / log-only 才安全。
+5. **内核验证靠对比,不靠直觉**。kernel 成员 byte-identical 是「没动内核」的唯一可靠证据。
+6. **预览期就在 live overlay 上验证前端**,bug 修复后立即烤进固件,避免「预览 OK / 固件又坏」的二次翻车。
+
+---
+
+## 八、附:v46.1 提交内容(git)
+
+仓库:`K:\SynologyDrive\学习\Openwrt\.v46.1-ui-src\`,commit `ae721f8`:
+
+- `luci-theme-argon`(local-wins 登录背景 + bg1.jpg fallback)
+- `luci-app-argon-config`(去 `ui.changes.apply`、ACL mutator 移 write)
+- `luci-app-wgtunnel`(rpcd ucode 后端 get/status/prepare/apply/rollback/reconnect + JSONMap 前端 + 60s 一次性 token + 快照回滚,无全局 network ACL)
+- `luci-app-tr3000-status`(只读 rpcd ucode + 5s 轮询仪表盘 + which_bin + expect-permissive + rows.push)
+- `audit_ui_packages.py` + `install_preview.sh` + `rollback_watchdog.sh`
diff --git a/docs/固件哈希记录.md b/docs/固件哈希记录.md
new file mode 100644
index 0000000..3d6a555
--- /dev/null
+++ b/docs/固件哈希记录.md
@@ -0,0 +1,13 @@
+# 固件哈希记录
+
+## v46-formal-mt76(mt76 正式版,已刷机实机验收)
+- `sysupgrade-v46-formal-mt76.bin` 14,388,023 B
+- SHA-256 `261bcb92062b358dd8b5fbdf88e3a2b7e16311dd4f09c3235a4ea802b88a602c`
+
+## v46.1-ui-mt76(UI-only 恢复,增量构建,尚未刷机)
+- `sysupgrade-v46.1-ui-mt76.bin` 14,797,623 B
+- SHA-256 `629cc6a3edd3e3870d2c260dbaf04597ea01db4b8ab23db15f54825548d7ff4c`
+- kernel 成员与 v46 byte-identical(`366f1c78...`);rootfs +408KB 全 UI
+- initramfs-v46.1-ui-mt76.bin 14,001,368 B, SHA-256 `6e14458a215b775583fa28ef71e542b75180eb5c612df300ebb269f0ddfcfd12`
+
+> 固件本体(含烤入的 WG 私钥/PSK,0600)不放入 git,仅存 K 盘。
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/Makefile b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/Makefile
new file mode 100644
index 0000000..7ed51a1
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/Makefile
@@ -0,0 +1,9 @@
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI for Argon theme configuration
+LUCI_PKGARCH:=all
+LUCI_DEPENDS:=+luci-theme-argon
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js
new file mode 100644
index 0000000..179fd44
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js
@@ -0,0 +1,219 @@
+'use strict';
+'require form';
+'require fs';
+'require rpc';
+'require uci';
+'require ui';
+'require view';
+
+const callSystemInfo = rpc.declare({
+ object: 'system',
+ method: 'info'
+});
+
+const callRemoveArgon = rpc.declare({
+ object: 'luci.argon',
+ method: 'remove',
+ params: ['filename'],
+ expect: { '': {} }
+});
+
+const callRenameArgon = rpc.declare({
+ object: 'luci.argon',
+ method: 'rename',
+ params: ['newname'],
+ expect: { '': {} }
+});
+
+const bg_path = '/www/luci-static/argon/background/';
+
+const trans_set = [0, 0.1, 0.2, 0.3, 0.4,
+ 0.5, 0.6, 0.7, 0.8, 0.9, 1 ];
+
+return view.extend({
+ load() {
+ return Promise.all([
+ uci.load('argon'),
+ L.resolveDefault(callSystemInfo(), { root: { avail: 0 } }),
+ L.resolveDefault(fs.list(bg_path), [])
+ ]);
+ },
+
+ render(data) {
+ let m, s, o;
+
+ m = new form.Map('argon', _('Argon theme configuration'),
+ _('Here you can set the blur and transparency of the login page of argon theme, and manage the background pictures and videos. Chrome is recommended.'));
+
+ s = m.section(form.TypedSection, 'global', _('Theme configuration'));
+ s.addremove = false;
+ s.anonymous = true;
+
+ o = s.option(form.ListValue, 'online_wallpaper', _('Wallpaper source'));
+ o.value('none', _('Built-in'));
+ o.value('bing', _('Bing'));
+ o.value('ghser', _('GHSer'));
+ o.value('unsplash', _('Unsplash'));
+ o.value('wallhaven', _('Wallhaven'));
+ o.default = 'bing';
+ o.forcewrite = true;
+ o.rmempty = false;
+ o.cfgvalue = function(section_id) {
+ let value = uci.get(data[0], section_id, 'online_wallpaper') || 'bing';
+ return value.split('_')[0];
+ }
+ o.write = function(section_id, value) {
+ let collection_id = this.section.formvalue(section_id, 'collection_id');
+ if (collection_id && (value === 'unsplash' || value === 'wallhaven')) {
+ value = value + '_' + collection_id;
+ }
+ uci.set(data[0], section_id, 'online_wallpaper', value);
+ }
+
+ o = s.option(form.Value, 'collection_id', _('Collection ID'), _('Collection ID for Unsplash or Wallhaven.'));
+ o.datatype = 'uinteger';
+ o.depends('online_wallpaper', 'unsplash');
+ o.depends('online_wallpaper', 'wallhaven');
+ o.cfgvalue = function(section_id) {
+ let value = uci.get(data[0], section_id, 'online_wallpaper');
+ if (!value || !value.includes('_'))
+ return '';
+
+ return value.split('_')[1];
+ }
+ o.write = function() { };
+
+ o = s.option(form.Value, 'use_api_key', _('API key'), _('Specify API key for Unsplash or Wallhaven.'));
+ o.depends('online_wallpaper', 'unsplash');
+ o.depends('online_wallpaper', 'wallhaven');
+
+ o = s.option(form.Flag, 'use_exact_resolution', _('Use exact resolution'), _('Use exact resolution or at least 1080P for Wallhaven.'));
+ o.default = o.enabled;
+ o.depends('online_wallpaper', 'wallhaven');
+
+ o = s.option(form.ListValue, 'mode', _('Theme mode'));
+ o.value('normal', _('Follow system'));
+ o.value('light', _('Light mode'));
+ o.value('dark', _('Dark mode'));
+ o.default = 'normal';
+ o.rmempty = false;
+
+ o = s.option(form.Value, 'primary', _('[Light mode] Primary Color'), _('A HEX color (default: #5e72e4).'))
+ o.default = '#5e72e4';
+ o.rmempty = false;
+ o.validate = function(section_id, value) {
+ if (section_id)
+ return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(value) ||
+ _('Expecting: %s').format(_('valid HEX color value'));
+ return true;
+ }
+
+ o = s.option(form.ListValue, 'transparency', _('[Light mode] Transparency'),
+ _('0 transparent - 1 opaque (suggest: transparent: 0 or translucent preset: 0.5).'));
+ for (let i of trans_set)
+ o.value(i);
+ o.default = '0.5';
+ o.rmempty = false;
+
+ o = s.option(form.Value, 'blur', _('[Light mode] Frosted Glass Radius'),
+ _('Larger value will more blurred (suggest: clear: 1 or blur preset: 10).'));
+ o.datatype = 'ufloat';
+ o.default = '10';
+ o.rmempty = false;
+
+ o = s.option(form.Value, 'dark_primary', _('[Dark mode] Primary Color'),
+ _('A HEX Color (default: #483d8b).'))
+ o.default = '#483d8b';
+ o.rmempty = false;
+ o.validate = function(section_id, value) {
+ if (section_id)
+ return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(value) ||
+ _('Expecting: %s').format(_('valid HEX color value'));
+ return true;
+ }
+
+ o = s.option(form.ListValue, 'transparency_dark', _('[Dark mode] Transparency'),
+ _('0 transparent - 1 opaque (suggest: black translucent preset: 0.5).'));
+ for (let i of trans_set)
+ o.value(i);
+ o.default = '0.5';
+ o.rmempty = false;
+
+ o = s.option(form.Value, 'blur_dark', _('[Dark mode] Frosted Glass Radius'),
+ _('Larger value will more blurred (suggest: clear: 1 or blur preset: 10).'))
+ o.datatype = 'ufloat';
+ o.default = '10';
+ o.rmempty = false;
+
+ o = s.option(form.Button, '_save', _('Save settings'));
+ o.inputstyle = 'apply';
+ o.inputtitle = _('Save current settings');
+ o.onclick = function() {
+ return this.map.save(null, true);
+ }
+
+ s = m.section(form.TypedSection, null, _('Upload background (available space: %1024.2mB)')
+ .format(data[1].root.avail * 1024),
+ _('You can upload files such as gif/jpg/mp4/png/webm/webp files, to change the login page background.'));
+ s.addremove = false;
+ s.anonymous = true;
+
+ o = s.option(form.Button, '_upload_bg', _('Upload background'),
+ _('Files will be uploaded to %s.').format(bg_path));
+ o.inputstyle = 'action';
+ o.inputtitle = _('Upload...');
+ o.onclick = function(ev, section_id) {
+ let file = '/tmp/argon_background.tmp';
+ return ui.uploadFile(file, ev.target).then(function(res) {
+ return L.resolveDefault(callRenameArgon(res.name), {}).then(function(ret) {
+ if (ret.result === 0)
+ return location.reload();
+ else {
+ ui.addNotification(null, E('p', _('Failed to upload file: %s.').format(res.name)));
+ return L.resolveDefault(fs.remove(file), {});
+ }
+ });
+ })
+ .catch(function(e) { ui.addNotification(null, E('p', e.message)); });
+ };
+ o.modalonly = true;
+
+ s = m.section(form.TableSection);
+ s.render = function() {
+ let tbl = E('table', { 'class': 'table cbi-section-table' },
+ E('tr', { 'class': 'tr table-titles' }, [
+ E('th', { 'class': 'th' }, [ _('Filename') ]),
+ E('th', { 'class': 'th' }, [ _('Modified date') ]),
+ E('th', { 'class': 'th' }, [ _('Size') ]),
+ E('th', { 'class': 'th' }, [ _('Action') ])
+ ])
+ );
+
+ cbi_update_table(tbl, data[2].map(L.bind(function(file) {
+ return [
+ file.name,
+ new Date(file.mtime * 1000).toLocaleString(),
+ String.format('%1024.2mB', file.size),
+ E('button', {
+ 'class': 'btn cbi-button cbi-button-remove',
+ 'click': ui.createHandlerFn(this, function() {
+ return L.resolveDefault(callRemoveArgon(file.name), {})
+ .then(function() { return location.reload(); });
+ })
+ }, [ _('Delete') ])
+ ];
+ }, this)), E('em', _('No files found.')));
+
+ return E('div', { 'class': 'cbi-map', 'id': 'cbi-filelist' }, [
+ E('h3', _('Background file list')),
+ tbl
+ ]);
+ };
+
+ return m.render();
+ },
+
+ handleSaveApply: null,
+ handleSave: null,
+ handleReset: null
+});
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/po/templates/argon-config.pot b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/po/templates/argon-config.pot
new file mode 100644
index 0000000..eb6e1d5
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/po/templates/argon-config.pot
@@ -0,0 +1,207 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:136
+msgid "0 transparent - 1 opaque (suggest: black translucent preset: 0.5)."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:112
+msgid ""
+"0 transparent - 1 opaque (suggest: transparent: 0 or translucent preset: "
+"0.5)."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:125
+msgid "A HEX Color (default: #483d8b)."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:101
+msgid "A HEX color (default: #5e72e4)."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:86
+msgid "API key"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:189
+msgid "Action"
+msgstr ""
+
+#: applications/luci-app-argon-config/root/usr/share/luci/menu.d/luci-app-argon-config.json:3
+msgid "Argon Config"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:45
+msgid "Argon theme configuration"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:209
+msgid "Background file list"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:54
+msgid "Bing"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:53
+msgid "Built-in"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:73
+msgid "Collection ID"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:73
+msgid "Collection ID for Unsplash or Wallhaven."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:97
+msgid "Dark mode"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:204
+msgid "Delete"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:107
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:131
+msgid "Expecting: %s"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:173
+msgid "Failed to upload file: %s."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:186
+msgid "Filename"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:163
+msgid "Files will be uploaded to %s."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:95
+msgid "Follow system"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:55
+msgid "GHSer"
+msgstr ""
+
+#: applications/luci-app-argon-config/root/usr/share/rpcd/acl.d/luci-app-argon-config.json:3
+msgid "Grant UCI access for luci-app-argon-config"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:46
+msgid ""
+"Here you can set the blur and transparency of the login page of argon theme, "
+"and manage the background pictures and videos. Chrome is recommended."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:119
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:143
+msgid "Larger value will more blurred (suggest: clear: 1 or blur preset: 10)."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:96
+msgid "Light mode"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:187
+msgid "Modified date"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:206
+msgid "No files found."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:150
+msgid "Save current settings"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:148
+msgid "Save settings"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:188
+msgid "Size"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:86
+msgid "Specify API key for Unsplash or Wallhaven."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:48
+msgid "Theme configuration"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:94
+msgid "Theme mode"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:56
+msgid "Unsplash"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:162
+msgid "Upload background"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:156
+msgid "Upload background (available space: %1024.2mB)"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:165
+msgid "Upload..."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:90
+msgid "Use exact resolution"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:90
+msgid "Use exact resolution or at least 1080P for Wallhaven."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:57
+msgid "Wallhaven"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:52
+msgid "Wallpaper source"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:158
+msgid ""
+"You can upload files such as gif/jpg/mp4/png/webm/webp files, to change the "
+"login page background."
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:142
+msgid "[Dark mode] Frosted Glass Radius"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:124
+msgid "[Dark mode] Primary Color"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:135
+msgid "[Dark mode] Transparency"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:118
+msgid "[Light mode] Frosted Glass Radius"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:101
+msgid "[Light mode] Primary Color"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:111
+msgid "[Light mode] Transparency"
+msgstr ""
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:107
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:131
+msgid "valid HEX color value"
+msgstr ""
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/po/zh_Hans/argon-config.po b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/po/zh_Hans/argon-config.po
new file mode 100644
index 0000000..edeb471
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/po/zh_Hans/argon-config.po
@@ -0,0 +1,220 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: dingpengyu \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: zh_CN\n"
+"X-Generator: Poedit 2.3.1\n"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:136
+msgid "0 transparent - 1 opaque (suggest: black translucent preset: 0.5)."
+msgstr "0 最透明 - 1 不透明(建议:黑色半透明 0.5)"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:112
+msgid ""
+"0 transparent - 1 opaque (suggest: transparent: 0 or translucent preset: "
+"0.5)."
+msgstr "0 最透明 - 1 不透明(建议: 透明 0 或 半透明预设 0.5)。"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:125
+msgid "A HEX Color (default: #483d8b)."
+msgstr "十六进制颜色值(预设为:#483d8b)。"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:101
+msgid "A HEX color (default: #5e72e4)."
+msgstr "十六进制颜色值(预设为:#5e72e4)。"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:86
+msgid "API key"
+msgstr "API 密钥"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:189
+msgid "Action"
+msgstr "操作"
+
+#: applications/luci-app-argon-config/root/usr/share/luci/menu.d/luci-app-argon-config.json:3
+msgid "Argon Config"
+msgstr "Argon 主题设置"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:45
+msgid "Argon theme configuration"
+msgstr "Argon 主题设置"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:209
+msgid "Background file list"
+msgstr "背景文件列表"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:54
+msgid "Bing"
+msgstr "Bing"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:53
+msgid "Built-in"
+msgstr "内建"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:73
+msgid "Collection ID"
+msgstr "分类 ID"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:73
+msgid "Collection ID for Unsplash or Wallhaven."
+msgstr "为 Unsplash 或 Wallhaven 指定分类 ID"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:97
+msgid "Dark mode"
+msgstr "暗黑模式"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:204
+msgid "Delete"
+msgstr "删除"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:107
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:131
+msgid "Expecting: %s"
+msgstr "请输入:%s"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:173
+msgid "Failed to upload file: %s."
+msgstr "上传文件失败:%s。"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:186
+msgid "Filename"
+msgstr "文件名"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:163
+msgid "Files will be uploaded to %s."
+msgstr "文件将被上传至%s。"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:95
+msgid "Follow system"
+msgstr "跟随系统"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:55
+msgid "GHSer"
+msgstr "GHSer"
+
+#: applications/luci-app-argon-config/root/usr/share/rpcd/acl.d/luci-app-argon-config.json:3
+msgid "Grant UCI access for luci-app-argon-config"
+msgstr "授予 luci-app-argon-config 访问 UCI 配置的权限"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:46
+msgid ""
+"Here you can set the blur and transparency of the login page of argon theme, "
+"and manage the background pictures and videos. Chrome is recommended."
+msgstr ""
+"在这里你可以设置argon 主题的登录页面的模糊和透明度,并管理背景图片与视频。推"
+"荐使用 Chrome。"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:119
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:143
+msgid "Larger value will more blurred (suggest: clear: 1 or blur preset: 10)."
+msgstr "值越大越模糊(建议:清透 1 或 模糊预设 10)"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:96
+msgid "Light mode"
+msgstr "亮色模式"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:187
+msgid "Modified date"
+msgstr "修改时间"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:206
+msgid "No files found."
+msgstr "没有找到文件。"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:150
+msgid "Save current settings"
+msgstr "保存当前设置"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:148
+msgid "Save settings"
+msgstr "保存设置"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:188
+msgid "Size"
+msgstr "大小"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:86
+msgid "Specify API key for Unsplash or Wallhaven."
+msgstr "为 Unsplash 或 Wallhaven 指定 API 密钥。"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:48
+msgid "Theme configuration"
+msgstr "主题配置"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:94
+msgid "Theme mode"
+msgstr "主题模式"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:56
+msgid "Unsplash"
+msgstr "Unsplash"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:162
+msgid "Upload background"
+msgstr "上传背景"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:156
+msgid "Upload background (available space: %1024.2mB)"
+msgstr "上传背景(可用空间:%1024.2mB)"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:165
+msgid "Upload..."
+msgstr "上传..."
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:90
+msgid "Use exact resolution"
+msgstr "精确匹配分辨率"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:90
+msgid "Use exact resolution or at least 1080P for Wallhaven."
+msgstr "精确匹配 1080P 分辨率或至少 1080P 分辨率(Wallhaven)。"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:57
+msgid "Wallhaven"
+msgstr "Wallhaven"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:52
+msgid "Wallpaper source"
+msgstr "壁纸来源"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:158
+msgid ""
+"You can upload files such as gif/jpg/mp4/png/webm/webp files, to change the "
+"login page background."
+msgstr ""
+"你可以上传 gif/jpg/mp4/png/webm/webp 等格式的文件,以创建自己喜欢的登录界面。"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:142
+msgid "[Dark mode] Frosted Glass Radius"
+msgstr "[暗色模式] 毛玻璃模糊半径"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:124
+msgid "[Dark mode] Primary Color"
+msgstr "[暗色模式] 主色调"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:135
+msgid "[Dark mode] Transparency"
+msgstr "[暗色模式] 透明度"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:118
+msgid "[Light mode] Frosted Glass Radius"
+msgstr "[亮色模式] 毛玻璃模糊半径"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:101
+msgid "[Light mode] Primary Color"
+msgstr "[亮色模式] 主色调"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:111
+msgid "[Light mode] Transparency"
+msgstr "[亮色模式] 透明度"
+
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:107
+#: applications/luci-app-argon-config/htdocs/luci-static/resources/view/argon-config.js:131
+msgid "valid HEX color value"
+msgstr "有效十六进制颜色值"
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/etc/config/argon b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/etc/config/argon
new file mode 100644
index 0000000..feda80e
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/etc/config/argon
@@ -0,0 +1,10 @@
+config global
+ option primary '#5e72e4'
+ option dark_primary '#483d8b'
+ option blur '0'
+ option blur_dark '0'
+ option transparency '0.3'
+ option transparency_dark '0.3'
+ option mode 'normal'
+ option online_wallpaper 'bing'
+
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/etc/uci-defaults/luci-argon-config b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/etc/uci-defaults/luci-argon-config
new file mode 100644
index 0000000..9fc0b56
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/etc/uci-defaults/luci-argon-config
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+bing_background="$(uci -q get "argon.@global[0].bing_background")"
+[ -n "$bing_background" ] || exit 0
+
+if [ "$bing_background" = "1" ]; then
+ uci -q set "argon.@global[0].online_wallpaper"="bing"
+else
+ uci -q set "argon.@global[0].online_wallpaper"="none"
+fi
+uci -q delete "argon.@global[0].bing_background"
+uci -q commit "argon"
+
+exit 0
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/usr/libexec/rpcd/luci.argon b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/usr/libexec/rpcd/luci.argon
new file mode 100644
index 0000000..571a38b
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/usr/libexec/rpcd/luci.argon
@@ -0,0 +1,98 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-3.0-only
+
+. /lib/functions.sh
+. /usr/share/libubox/jshn.sh
+
+readonly bg_path="/www/luci-static/argon/background"
+readonly tmp_path="/tmp/argon_background.tmp"
+readonly max_size=5242880
+readonly builtin_bg="bg1.jpg"
+
+valid_name() {
+ local name="$1" ext
+ [ -n "$name" ] || return 1
+ [ "$name" = "${name##*/}" ] || return 1
+ [ "$name" = "${name##*\\}" ] || return 1
+ [ "$name" = "${name#.*}" ] || return 1
+ case "$name" in *..*|*[!A-Za-z0-9_.-]*) return 1 ;; esac
+ ext="${name##*.}"
+ [ "$ext" != "$name" ] || return 1
+ case "$(echo "$ext" | tr 'A-Z' 'a-z')" in jpg|jpeg|png|gif|webp|mp4|webm|ogg) return 0 ;; esac
+ return 1
+}
+
+json_result() {
+ json_init
+ json_add_int "result" "$1"
+ json_dump
+ json_cleanup
+}
+
+case "$1" in
+"list")
+ json_init
+ json_add_object "remove"
+ json_add_string "filename" "filename"
+ json_close_object
+ json_add_object "rename"
+ json_add_string "newname" "filename"
+ json_close_object
+ json_dump
+ json_cleanup
+ ;;
+"call")
+ case "$2" in
+ "remove")
+ read -r input
+ json_load "$input"
+ json_get_var filename "filename"
+ json_cleanup
+
+ if ! valid_name "$filename" || [ "$filename" = "$builtin_bg" ] || [ -L "$bg_path/$filename" ]; then
+ json_result 255
+ exit 255
+ fi
+
+ rm -f -- "$bg_path/$filename"
+ json_result 0
+ ;;
+ "rename")
+ read -r input
+ json_load "$input"
+ json_get_var newname "newname"
+ json_cleanup
+
+ if ! valid_name "$newname" || [ "$newname" = "$builtin_bg" ] || [ -L "$tmp_path" ] || [ ! -f "$tmp_path" ]; then
+ json_result 255
+ exit 255
+ fi
+
+ size="$(wc -c < "$tmp_path" 2>/dev/null)" || size=0
+ case "$size" in *[!0-9]*|'') size=0 ;; esac
+ if [ "$size" -le 0 ] || [ "$size" -gt "$max_size" ]; then
+ rm -f -- "$tmp_path"
+ json_result 254
+ exit 254
+ fi
+
+ mkdir -p -- "$bg_path"
+ target="$bg_path/$newname"
+ if [ -e "$target" ] || [ -L "$target" ]; then
+ json_result 253
+ exit 253
+ fi
+ if (set -C; umask 022; cat "$tmp_path" > "$target") 2>/dev/null; then
+ if chmod 0644 "$target" && rm -f -- "$tmp_path"; then
+ json_result 0
+ else
+ rm -f -- "$target"
+ json_result 1
+ fi
+ else
+ json_result 1
+ fi
+ ;;
+ esac
+ ;;
+esac
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/usr/share/luci/menu.d/luci-app-argon-config.json b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/usr/share/luci/menu.d/luci-app-argon-config.json
new file mode 100644
index 0000000..7a1e349
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/usr/share/luci/menu.d/luci-app-argon-config.json
@@ -0,0 +1,14 @@
+{
+ "admin/system/argon-config": {
+ "title": "Argon Config",
+ "order": 90,
+ "action": {
+ "type": "view",
+ "path": "argon-config"
+ },
+ "depends": {
+ "acl": [ "luci-app-argon-config" ],
+ "uci": { "argon": true }
+ }
+ }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/usr/share/rpcd/acl.d/luci-app-argon-config.json b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/usr/share/rpcd/acl.d/luci-app-argon-config.json
new file mode 100644
index 0000000..509eccd
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-argon-config/root/usr/share/rpcd/acl.d/luci-app-argon-config.json
@@ -0,0 +1,23 @@
+{
+ "luci-app-argon-config": {
+ "description": "Grant UCI access for luci-app-argon-config",
+ "read": {
+ "file": {
+ "/www/luci-static/argon/background/*": [ "list" ]
+ },
+ "ubus": {
+ "system": [ "info" ]
+ },
+ "uci": [ "argon" ]
+ },
+ "write": {
+ "file": {
+ "/tmp/argon_background.tmp": [ "write" ]
+ },
+ "ubus": {
+ "luci.argon": [ "remove", "rename" ]
+ },
+ "uci": [ "argon" ]
+ }
+ }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/Makefile b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/Makefile
new file mode 100644
index 0000000..907fc8f
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/Makefile
@@ -0,0 +1,11 @@
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI TR3000 Link Health dashboard
+LUCI_DEPENDS:=+rpcd-mod-ucode +ucode-mod-fs +ucode-mod-ubus +ucode-mod-uci +rpcd-mod-iwinfo +wireguard-tools +ip-full +nftables-json
+LUCI_PKGARCH:=all
+PKG_LICENSE:=GPL-3.0-only
+LUCI_MAINTAINER:=TR3000 v46.1 UI
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/README.md b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/README.md
new file mode 100644
index 0000000..8612ab9
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/README.md
@@ -0,0 +1,79 @@
+# Link Health 插件修正(离线版)
+
+## 范围
+本次离线变更只覆盖 `luci-app-tr3000-status`:
+- 后端只读探测与三态语义
+- 前端显示与轮询
+- 静态审计、合成 fixture、安装/回滚脚本
+- 文档
+
+明确**不**做:
+- 不改 `/etc/config/network` `firewall` `dhcp` `wireless`
+- 不动 `rc.local`、VXLAN/MSS hotplug、nft include
+- 不操作 WG/VXLAN 隧道、MTU、kill switch、MSS
+- 不重载 `network` `firewall` `wireless`
+- 不登录或修改办公室 OpenWrt `192.168.1.1`
+- 不替换 `libc` `rpcd` `ucode` `netifd` `fw4` `mt76` `kmod-*` 等核心包
+
+## 变更摘要
+
+### 后端
+- `luci.tr3000_status` 的 `get` 现在返回 `schema_version: 2`。
+- 新增字段:
+ - `wireguard.interface_up` ∈ `{true, false, null}`
+ - `vxlan.invariants.{master_ok, mtu_ok, vni_ok, port_ok, nolearning_ok, local_ok, remote_ok}`
+ - `vxlan.all_invariants_ok`
+ - `acceleration.*.status` ∈ `{enabled, enabled_unverified, disabled, disabled_by_design, unavailable, healthy, ok, degraded}`
+ - `summary.{ok, status, wg_ok, link_ok, route_ok, vxlan_ok, invariant_ok, wg_status}`
+- 探测命令统一为只读 `ip -j` + `nft -j`,不再使用目标 query 语法。
+- 移除 `wg show wg0 dump`。
+- “按设计禁用” (flowtable / HNAT / WARP / full-cone / masquerade) 不再误报为 `unavailable`,而是 `disabled_by_design`。
+
+### 前端
+- 三态徽章:`Pass` / `Fail` / `Unknown`。
+- 顶栏新增 `summary.status` 总览徽章。
+- 加速卡标题改为 `Data path & TCP`。
+- 删掉原先的 `wg0:fail` 重复摘要。
+- “Active interfaces” 在 link JSON 不可用时显示 `Unavailable`,不再显示误导性的 `0 / 2`。
+
+### 审计
+- `tests/audit_tr3000_status.py`:
+ - 禁止模式:`wg dump`、`ifup/ifdown`、`ip link set`、`uci set/commit`、`network/firewall/wifi restart`、`reload`、`fs.exec`/`fs.write`/`fs.remove`/`fs.mkdir`、`writefile/unlink/rename`、敏感 key literal、MAC literal。
+ - 强制包含的只读命令、ACL 仅 `get`、菜单依赖 `luci-app-tr3000-status`、Makefile 依赖项。
+ - 当前结果:`OK: luci-app-tr3000-status passes static audit`。
+
+### 合成 fixture
+- `tests/fixtures/` 12 份期望,覆盖:
+ 1. `all_healthy`
+ 2. `wg_interface_down`
+ 3. `wg_stale_handshake`
+ 4. `route_missing`
+ 5. `vxlan_missing`
+ 6. `vxlan_wrong_vni`
+ 7. `nft_unavailable`
+ 8. `no_hwmon`
+ 9. `ip_unavailable`
+ 10. `malformed_json`
+ 11. `mss_missing`
+ 12. `design_invariant_violation`
+
+### 安装与回滚
+- `tests/install_preview.sh`:仅升级 `luci-app-tr3000-status`;备份 `/etc/config/*` 与 hotplug;`opkg install --force-reinstall`;仅 `rpcd` + `uhttpd` reload;调用 `ubus call luci.tr3000_status get` 冒烟。
+- `tests/rollback_preview.sh`:根据 `prev_version` 还原。
+- `tests/verify_immutable.sh`:升级前后必须维持 v46 frozen hash。
+
+## 待执行(受工具限制暂未跑)
+1. `scp` 同步源码到 build server `package/custom/luci-app-tr3000-status`。
+2. build server 重新编译 `luci-app-tr3000-status` IPK。
+3. 把 IPK 与 frozen hash 拉到本机。
+4. 在路由器执行 `install_preview.sh`,记录 `ubus call luci.tr3000_status get` 完整输出。
+5. 在路由器执行 `verify_immutable.sh`,确认零漂移。
+6. 浏览器强制刷新 `/admin/status/tr3000`,记录截图。
+7. 若 `summary.status` ≠ `healthy` 或不可变文件 hash 漂移,执行 `rollback_preview.sh`。
+
+## 风险
+- 升级期间若 `rpcd` 重启瞬间有浏览器轮询,理论上会造成一次 RPC 失败;前端 `L.resolveDefault(callStatus(), {})` 已吞掉此失败。
+- 若 `install_preview.sh` 备份脚本因权限或 BusyBox 限制失败,需要先解决再继续;不要在没有 backup 的情况下继续。
+
+## 不变项
+- v46 frozen network/firewall/dhcp/wireless、rc.local、hotplug、MSS 1330、br-lan master、wg0/WG 路线、VXLAN 10/4789/nolearning/MTU 1500、kill switch 均不修改。
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/htdocs/luci-static/resources/view/status/tr3000.js b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/htdocs/luci-static/resources/view/status/tr3000.js
new file mode 100644
index 0000000..ebb8aa7
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/htdocs/luci-static/resources/view/status/tr3000.js
@@ -0,0 +1,235 @@
+'use strict';
+'require view';
+'require rpc';
+'require poll';
+'require dom';
+
+var callStatus = rpc.declare({
+ object: 'luci.tr3000_status',
+ method: 'get',
+ // Accept the full nested object the backend returns. The previous
+ // `expect: { '': {} }` schema-only stub caused rpcd to strip every
+ // top-level key, leaving the front-end with `{}` and every card
+ // rendering as 'Unavailable'. An empty `expect: {}` is the most
+ // permissive shape and lets the structured payload flow through.
+ expect: {}
+});
+
+function humanBytes(value) {
+ if (value == null)
+ return '—';
+ var number = Number(value), units = [ 'B', 'KiB', 'MiB', 'GiB', 'TiB' ], index = 0;
+ while (number >= 1024 && index < units.length - 1) {
+ number /= 1024;
+ index++;
+ }
+ return (index ? number.toFixed(number >= 100 ? 0 : 1) : number.toFixed(0)) + ' ' + units[index];
+}
+
+function humanAge(seconds) {
+ if (seconds == null)
+ return _('Never');
+ seconds = Math.max(0, Number(seconds));
+ if (seconds < 60)
+ return _('%d seconds').format(seconds);
+ if (seconds < 3600)
+ return _('%d minutes ago').format(Math.floor(seconds / 60));
+ return _('%d hours ago').format(Math.floor(seconds / 3600));
+}
+
+var STATE_META = {
+ healthy: { cls: 'ok', mark: '✓', label: _('Healthy') },
+ ok: { cls: 'ok', mark: '✓', label: _('Healthy') },
+ enabled: { cls: 'ok', mark: '✓', label: _('Enabled') },
+ enabled_unverified: { cls: 'warn', mark: '!', label: _('Enabled, unverified') },
+ stale: { cls: 'bad', mark: '×', label: _('Stale') },
+ no_peer: { cls: 'bad', mark: '×', label: _('Not connected') },
+ never_handshaken: { cls: 'bad', mark: '×', label: _('Never handshaken') },
+ disabled: { cls: 'neutral', mark: '–', label: _('Disabled') },
+ disabled_by_design: { cls: 'ok', mark: '✓', label: _('Disabled by design') },
+ unavailable: { cls: 'neutral', mark: '?', label: _('Unavailable') },
+ degraded: { cls: 'warn', mark: '!', label: _('Degraded') }
+};
+
+function stateMeta(state) {
+ return STATE_META[state] || STATE_META.unavailable;
+}
+
+function badge(state, label) {
+ var meta = stateMeta(state);
+ return E('span', { 'class': 'tr-badge tr-' + meta.cls }, [
+ E('span', { 'class': 'tr-mark', 'aria-hidden': 'true' }, meta.mark),
+ label || meta.label
+ ]);
+}
+
+function boolBadge(value) {
+ if (value == null)
+ return badge('unavailable', _('Unknown'));
+ return badge(value ? 'healthy' : 'stale', value ? _('Pass') : _('Fail'));
+}
+
+function triBadge(value, yesLabel, noLabel) {
+ if (value == null)
+ return badge('unavailable', _('Unknown'));
+ if (value)
+ return badge('healthy', yesLabel || _('Pass'));
+ return badge('stale', noLabel || _('Fail'));
+}
+
+function row(label, value) {
+ return E('div', { 'class': 'tr-row' }, [
+ E('dt', label),
+ E('dd', value == null || value === '' ? '—' : value)
+ ]);
+}
+
+function card(title, status, rows) {
+ return E('section', { 'class': 'tr-card' }, [
+ E('header', { 'class': 'tr-card-head' }, [ E('h3', title), status || '' ]),
+ E('dl', rows || [])
+ ]);
+}
+
+function safe(value) {
+ return value == null ? '—' : value;
+}
+
+function wifiCard(data) {
+ data = data || {};
+ var interfaces = data.interfaces || [];
+ var rows = [
+ row(_('Radios'), String(data.radio_count || 0)),
+ row(_('Active interfaces'), data.link_data_available ? '%d / %d'.format(data.up_count || 0, data.interface_count || 0) : _('Unavailable')),
+ row(_('Channels'), (data.channels || []).join(', ') || '—'),
+ row(_('Frequencies'), (data.frequencies_mhz || []).map(function(v) { return v + ' MHz'; }).join(', ') || '—')
+ ];
+ if (interfaces.length)
+ rows.push(row(_('Interfaces'), interfaces.map(function(i) { return '%s %s'.format(i.name, stateMeta(i.up ? 'enabled' : 'disabled').mark); }).join(' ')));
+ return card(_('Wi-Fi aggregate'), badge(data.available ? 'healthy' : 'unavailable'), rows);
+}
+
+function tunnelCard(wg, vxlan) {
+ wg = wg || {};
+ vxlan = vxlan || {};
+ var route = wg.required_route || {};
+ return card(_('Encrypted link'), badge(wg.status), [
+ row(_('WireGuard peers'), String(wg.peer_count || 0)),
+ row(_('Interface up'), triBadge(wg.interface_up, _('Up'), _('Down'))),
+ row(_('Latest handshake'), humanAge(wg.handshake_age_seconds)),
+ row(_('Transfer'), wg.rx_bytes == null ? '—' : '↓ %s ↑ %s'.format(humanBytes(wg.rx_bytes), humanBytes(wg.tx_bytes))),
+ row(_('Required route'), E('span', [ triBadge(route.present, route.required, route.required + ' missing'), ' ', safe(route.required), ' → ', safe(route.device) ])),
+ row(_('VXLAN'), vxlan.present === true ? triBadge(vxlan.up === true, _('Up'), _('Down')) : badge('unavailable', vxlan.present === false ? _('Missing') : _('Unknown'))),
+ row(_('VNI / port'), vxlan.vni == null ? '—' : '%s / %s'.format(vxlan.vni, vxlan.destination_port || '—')),
+ row(_('MTU / bridge'), vxlan.mtu == null ? '—' : '%s / %s'.format(vxlan.mtu, vxlan.master || '—')),
+ row(_('No learning'), triBadge(vxlan.nolearning, _('Yes'), _('No'))),
+ row(_('All VXLAN invariants'), triBadge(vxlan.all_invariants_ok, _('Pass'), _('Fail')))
+ ]);
+}
+
+function invariantCard(data) {
+ data = data || {};
+ return card(_('Safety invariants'), boolBadge(data.all_invariants_ok), [
+ row(_('wg0 in LAN zone'), boolBadge(data.wg0_in_lan_zone)),
+ row(_('LAN → WAN forwarding absent'), boolBadge(data.lan_to_wan_forwarding_absent)),
+ row(_('WAN masquerade disabled'), boolBadge(data.wan_masquerade_disabled)),
+ row(_('DHCP / RA / DHCPv6 disabled'), triBadge(data.dhcp_server_disabled && data.ra_disabled && data.dhcpv6_disabled, _('Yes'), _('No'))),
+ row(_('MSS clamp 1330'), triBadge(data.mss_clamp_1330 === true, _('Pass'), _('Fail'))),
+ row(_('Bridge netfilter'), triBadge(data.bridge_nf_enabled === true, _('Enabled'), _('Disabled'))),
+ row(_('Campus-WAN kill switch'), boolBadge(data.kill_switch))
+ ]);
+}
+
+function stateRow(label, item) {
+ return row(label, badge((item || {}).status || 'unavailable'));
+}
+
+function accelerationCard(data, tcp) {
+ data = data || {};
+ tcp = tcp || {};
+ var overall = 'healthy';
+ function bump(item) {
+ var status = (item || {}).status;
+ if (status == 'unavailable')
+ overall = 'degraded';
+ }
+ bump(data.mt76); bump(data.wed); bump(data.hnat); bump(data.warp);
+ bump(data.flowtable); bump(data.fullcone); bump(data.masquerade);
+ return card(_('Data path & TCP'), badge(overall), [
+ stateRow(_('mt76'), data.mt76),
+ stateRow(_('WED'), data.wed),
+ stateRow(_('HNAT'), data.hnat),
+ stateRow(_('WARP'), data.warp),
+ stateRow(_('Flowtable'), data.flowtable),
+ stateRow(_('Full cone NAT'), data.fullcone),
+ stateRow(_('Masquerade'), data.masquerade),
+ row(_('TCP congestion control'), tcp.congestion_control || '—'),
+ row(_('Available CCA'), (tcp.available || []).join(', ') || '—')
+ ]);
+}
+
+function temperatureCard(data) {
+ data = data || {};
+ var sensors = data.sensors || [], rows = [];
+ for (var i = 0; i < sensors.length; i++)
+ rows.push(row('%s · %s'.format(sensors[i].chip, sensors[i].label), '%.1f °C'.format(Number(sensors[i].celsius))));
+ if (!rows.length)
+ rows.push(row(_('Sensors'), _('Unavailable')));
+ return card(_('Temperatures'), badge(data.available ? 'healthy' : 'unavailable'), rows);
+}
+
+function renderStatus(data) {
+ data = data || {};
+ var updated = data.generated_at ? new Date(data.generated_at * 1000).toLocaleTimeString() : '—';
+ var summary = data.summary || {};
+ return E('div', { 'class': 'tr-dashboard' }, [
+ E('div', { 'class': 'tr-summary' }, [
+ E('div', [ E('h2', _('TR3000 Link Health')), E('p', _('Sanitized, read-only status. Refreshes every 5 seconds.')) ]),
+ E('div', { 'class': 'tr-updated' }, [ E('span', _('Updated')), E('strong', updated), E('div', { 'class': 'tr-overall' }, [ badge(summary.status || 'unavailable', summary.status == 'healthy' ? _('All clear') : _('Degraded')) ]) ])
+ ]),
+ data.partial ? E('div', { 'class': 'alert-message warning', 'role': 'status' }, _('Partial data: %s').format((data.errors || []).join(', '))) : '',
+ E('div', { 'class': 'tr-grid' }, [
+ wifiCard(data.wifi),
+ tunnelCard(data.wireguard, data.vxlan),
+ invariantCard(data.invariants),
+ accelerationCard(data.acceleration, data.tcp),
+ temperatureCard(data.temperatures)
+ ])
+ ]);
+}
+
+var styleText = [
+ '.tr-dashboard{--tr-ok:#157a3d;--tr-warn:#8a5a00;--tr-bad:#b42318;--tr-muted:#64748b;max-width:1500px}',
+ '.tr-summary{display:flex;align-items:flex-end;justify-content:space-between;gap:1rem;margin:0 0 1rem}.tr-summary h2{margin:0 0 .25rem}.tr-summary p{margin:0;color:var(--text-color-medium,#64748b)}',
+ '.tr-updated{text-align:right;display:flex;flex-direction:column;gap:.25rem;color:var(--text-color-medium,#64748b)}.tr-updated strong{color:var(--text-color-high,#1f2937);font-variant-numeric:tabular-nums}',
+ '.tr-overall{display:flex;justify-content:flex-end}',
+ '.tr-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(290px,1fr));gap:1rem;align-items:start}',
+ '.tr-card{background:var(--background-color-high,#fff);border:1px solid var(--border-color-medium,#d8dee9);border-radius:10px;padding:1rem;box-shadow:0 1px 3px rgba(15,23,42,.08)}',
+ '.tr-card-head{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding-bottom:.7rem;border-bottom:1px solid var(--border-color-low,#e5e7eb)}.tr-card h3{font-size:1rem;margin:0}',
+ '.tr-card dl{margin:.45rem 0 0}.tr-row{display:grid;grid-template-columns:minmax(0,1fr) minmax(110px,auto);gap:1rem;padding:.48rem 0;border-bottom:1px solid var(--border-color-low,#edf0f4)}.tr-row:last-child{border-bottom:0}.tr-row dt{color:var(--text-color-medium,#64748b)}.tr-row dd{margin:0;text-align:right;font-weight:600;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}',
+ '.tr-badge{display:inline-flex;align-items:center;gap:.35rem;white-space:nowrap;font-size:.8rem;font-weight:700}.tr-mark{display:inline-grid;place-items:center;width:1.2rem;height:1.2rem;border-radius:999px;color:#fff;line-height:1}',
+ '.tr-ok{color:var(--tr-ok)}.tr-ok .tr-mark{background:var(--tr-ok)}.tr-warn{color:var(--tr-warn)}.tr-warn .tr-mark{background:var(--tr-warn)}.tr-bad{color:var(--tr-bad)}.tr-bad .tr-mark{background:var(--tr-bad)}.tr-neutral{color:var(--tr-muted)}.tr-neutral .tr-mark{background:var(--tr-muted)}',
+ '@media(max-width:700px){.tr-summary{align-items:flex-start;flex-direction:column}.tr-updated{text-align:left}.tr-row{grid-template-columns:1fr}.tr-row dd{text-align:left}}',
+ '@media(forced-colors:active){.tr-mark{border:1px solid CanvasText}.tr-card{box-shadow:none}}'
+].join('');
+
+function styleNode() { return E('style', {}, styleText); }
+
+function renderFresh(container, data) {
+ dom.content(container, [ styleNode(), renderStatus(data) ]);
+}
+
+return view.extend({
+ load: function() { return L.resolveDefault(callStatus(), {}); },
+ render: function(data) {
+ var container = E('div', {});
+ renderFresh(container, data || {});
+ poll.add(function() {
+ return L.resolveDefault(callStatus(), {}).then(function(next) { renderFresh(container, next || {}); });
+ }, 5);
+ return container;
+ },
+ handleSaveApply: null,
+ handleSave: null,
+ handleReset: null
+});
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/root/usr/share/luci/menu.d/luci-app-tr3000-status.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/root/usr/share/luci/menu.d/luci-app-tr3000-status.json
new file mode 100644
index 0000000..2071fdf
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/root/usr/share/luci/menu.d/luci-app-tr3000-status.json
@@ -0,0 +1,13 @@
+{
+ "admin/status/tr3000": {
+ "title": "Link Health",
+ "order": 25,
+ "action": {
+ "type": "view",
+ "path": "status/tr3000"
+ },
+ "depends": {
+ "acl": [ "luci-app-tr3000-status" ]
+ }
+ }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/root/usr/share/rpcd/acl.d/luci-app-tr3000-status.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/root/usr/share/rpcd/acl.d/luci-app-tr3000-status.json
new file mode 100644
index 0000000..668e44e
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/root/usr/share/rpcd/acl.d/luci-app-tr3000-status.json
@@ -0,0 +1,10 @@
+{
+ "luci-app-tr3000-status": {
+ "description": "Read sanitized TR3000 Link Health status",
+ "read": {
+ "ubus": {
+ "luci.tr3000_status": [ "get" ]
+ }
+ }
+ }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/root/usr/share/rpcd/ucode/luci.tr3000_status b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/root/usr/share/rpcd/ucode/luci.tr3000_status
new file mode 100644
index 0000000..18a7787
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/root/usr/share/rpcd/ucode/luci.tr3000_status
@@ -0,0 +1,565 @@
+#!/usr/bin/ucode
+/* SPDX-License-Identifier: GPL-3.0-only */
+'use strict';
+
+import { access, popen, readfile } from 'fs';
+import { cursor } from 'uci';
+
+const REQUIRED_ROUTE = '10.99.0.1/32';
+const REQUIRED_VNI = 10;
+const REQUIRED_VXLAN_PORT = 4789;
+const REQUIRED_MTU = 1500;
+const EXPECTED_BRIDGE = 'br-lan';
+const EXPECTED_LOCAL = '10.99.0.2';
+const EXPECTED_REMOTE = '10.99.0.1';
+const SAFE_IDENT_RE = /^[A-Za-z0-9_.-]{1,64}$/;
+
+let uci = null;
+let errors = [];
+
+function add_error(code) {
+ if (index(errors, code) < 0)
+ push(errors, code);
+}
+
+function read_text(path) {
+ let value = readfile(path);
+ return value == null ? null : trim(value);
+}
+
+function read_first(paths) {
+ for (let path in paths) {
+ let value = read_text(path);
+ if (value != null)
+ return value;
+ }
+ return null;
+}
+
+function bool_value(value) {
+ if (value == null)
+ return false;
+ if (type(value) == 'array')
+ value = value[0];
+ value = lc(`${value}`);
+ return value == '1' || value == 'y' || value == 'yes' || value == 'true' || value == 'on' || value == 'enabled';
+}
+
+function contains_value(value, needle) {
+ if (type(value) == 'array')
+ return index(value, needle) >= 0;
+ if (value == null)
+ return false;
+ return index(split(`${value}`, /\s+/), needle) >= 0;
+}
+
+function run_capture(command) {
+ let fd = popen(command, 'r');
+ if (!fd)
+ return { ok: false, status: -1, out: '' };
+ let out = fd.read('all') || '';
+ let status = fd.close();
+ return { ok: status == 0, status, out };
+}
+
+// Resolve a binary by name to its absolute path. ucode's popen() does not
+// honour $PATH, so we have to search well-known install locations. The
+// result is memoized so each command is stat'd at most once per process.
+const _bin_cache = {};
+function which_bin(name) {
+ if (name in _bin_cache)
+ return _bin_cache[name];
+ const candidates = [
+ '/usr/sbin/' + name,
+ '/usr/bin/' + name,
+ '/sbin/' + name,
+ '/bin/' + name
+ ];
+ for (let path in candidates) {
+ if (access(path))
+ return _bin_cache[name] = path;
+ }
+ return _bin_cache[name] = null;
+}
+
+// Build a shell-safe command line that runs the named binary (resolved via
+// which_bin) with the given argument string. Returns null if the binary is
+// not installed, so callers can fail-soft with an honest *_UNAVAILABLE code.
+function bin_cmd(name, args) {
+ const path = which_bin(name);
+ return path == null ? null : `${path} ${args}`;
+}
+
+function nft_rules(value) {
+ let rules = [];
+ if (type(value?.nftables) != 'array')
+ return rules;
+ for (let entry in value.nftables)
+ if (entry?.rule != null)
+ push(rules, entry.rule);
+ return rules;
+}
+
+function nft_match_meta(expr, key, value) {
+ return expr?.match?.left?.meta?.key == key && expr?.match?.right == value;
+}
+
+function nft_mangles_tcp_mss(expr, value) {
+ let key = expr?.mangle?.key?.['tcp option'];
+ return key?.name == 'maxseg' && key?.field == 'size' && expr?.mangle?.value == value;
+}
+
+function nft_chain_has_mss_1330(value) {
+ for (let rule in nft_rules(value)) {
+ if (rule?.comment != 'tr3000-vxlan-mss-1330' || type(rule?.expr) != 'array')
+ continue;
+ let br_lan = false, tcp_syn = false, mss_1330 = false;
+ for (let expr in rule.expr) {
+ if (nft_match_meta(expr, 'iifname', 'br-lan'))
+ br_lan = true;
+ if (expr?.match?.left?.payload?.protocol == 'tcp' && expr?.match?.left?.payload?.field == 'flags' && expr?.match?.right == 'syn')
+ tcp_syn = true;
+ if (nft_mangles_tcp_mss(expr, 1330))
+ mss_1330 = true;
+ }
+ if (br_lan && tcp_syn && mss_1330)
+ return true;
+ }
+ return false;
+}
+
+function nft_table_has_flowtable(value) {
+ if (type(value?.nftables) != 'array')
+ return false;
+ for (let entry in value.nftables)
+ if (entry?.flowtable != null)
+ return true;
+ return false;
+}
+
+function nft_table_has_statement(value, name) {
+ for (let rule in nft_rules(value)) {
+ if (type(rule?.expr) != 'array')
+ continue;
+ for (let expr in rule.expr)
+ if (expr?.[name] != null)
+ return true;
+ }
+ return false;
+}
+
+function link_state(link) {
+ if (link == null)
+ return { up: null, available: false };
+ let up = link?.operstate == 'UP' || contains_value(link?.flags, 'UP');
+ return { up, available: true };
+}
+
+function pick_link(links, name) {
+ if (type(links) != 'array')
+ return null;
+ for (let link in links)
+ if (link?.ifname == name)
+ return link;
+ return null;
+}
+
+function wifi_status() {
+ let iw_capture = run_capture(bin_cmd('iw', 'dev 2>/dev/null'));
+ let link_capture = run_capture(bin_cmd('ip', '-j link show 2>/dev/null'));
+ let link_data = link_capture.ok ? json(link_capture.out) : null;
+ if (!link_capture.ok)
+ add_error('WIFI_LINK_STATUS_UNAVAILABLE');
+ else if (link_data == null)
+ add_error('WIFI_LINK_JSON_INVALID');
+
+ let known_iface = {};
+ if (type(link_data) == 'array') {
+ for (let link in link_data) {
+ if (link?.ifname == null)
+ continue;
+ known_iface[link.ifname] = link;
+ }
+ }
+
+ let result = {
+ available: iw_capture.ok,
+ link_data_available: link_capture.ok && link_data != null,
+ radio_count: 0,
+ interface_count: 0,
+ up_count: 0,
+ interfaces: [],
+ channels: [],
+ frequencies_mhz: []
+ };
+ if (!iw_capture.ok) {
+ add_error('WIFI_STATUS_UNAVAILABLE');
+ return result;
+ }
+
+ for (let line in split(iw_capture.out, /\n/)) {
+ let phy = match(line, /^phy#([0-9]+)/);
+ if (phy) {
+ result.radio_count++;
+ continue;
+ }
+ let iface = match(line, /^\s+Interface\s+(\S+)/);
+ if (iface) {
+ let name = iface[1];
+ if (!match(name, SAFE_IDENT_RE))
+ continue;
+ result.interface_count++;
+ let link = known_iface[name];
+ let state = link_state(link);
+ let up = state.up;
+ if (up)
+ result.up_count++;
+ push(result.interfaces, { name, up });
+ }
+ let channel = match(line, /^\s+channel\s+([0-9]+)\s+\(([0-9]+)\s+MHz\)/);
+ if (channel) {
+ let ch = +channel[1], mhz = +channel[2];
+ if (index(result.channels, ch) < 0)
+ push(result.channels, ch);
+ if (index(result.frequencies_mhz, mhz) < 0)
+ push(result.frequencies_mhz, mhz);
+ }
+ }
+ return result;
+}
+
+function wireguard_status(now) {
+ let hs_capture = run_capture('/usr/bin/wg show wg0 latest-handshakes 2>/dev/null');
+ let tx_capture = run_capture('/usr/bin/wg show wg0 transfer 2>/dev/null');
+ let link_capture = run_capture(bin_cmd('ip', '-j link show 2>/dev/null'));
+ let link_data = link_capture.ok ? json(link_capture.out) : null;
+ if (!link_capture.ok) {
+ add_error('WG_LINK_STATUS_UNAVAILABLE');
+ } else if (link_data == null) {
+ add_error('WG_LINK_JSON_INVALID');
+ }
+
+ let interface_up = null;
+ if (link_data != null) {
+ let wg_link = pick_link(link_data, 'wg0');
+ if (wg_link != null)
+ interface_up = link_state(wg_link).up;
+ }
+
+ let result = {
+ available: hs_capture.ok && tx_capture.ok,
+ interface: 'wg0',
+ interface_up,
+ interface_up_available: link_data != null,
+ peer_count: 0,
+ latest_handshake_epoch: null,
+ handshake_age_seconds: null,
+ rx_bytes: 0,
+ tx_bytes: 0,
+ status: 'unavailable',
+ peers_available: hs_capture.ok
+ };
+ if (!hs_capture.ok) {
+ add_error('WG_STATUS_UNAVAILABLE');
+ return result;
+ }
+ if (!tx_capture.ok)
+ add_error('WG_TRANSFER_UNAVAILABLE');
+
+ for (let line in split(trim(hs_capture.out), /\n/)) {
+ let fields = split(line, /\t/);
+ if (length(fields) < 2)
+ continue;
+ result.peer_count++;
+ let handshake = +fields[1];
+ if (handshake > (result.latest_handshake_epoch || 0))
+ result.latest_handshake_epoch = handshake;
+ }
+
+ if (tx_capture.ok) {
+ for (let line in split(trim(tx_capture.out), /\n/)) {
+ let fields = split(line, /\t/);
+ if (length(fields) < 3)
+ continue;
+ result.rx_bytes += +fields[1];
+ result.tx_bytes += +fields[2];
+ }
+ }
+
+ if (result.latest_handshake_epoch > 0)
+ result.handshake_age_seconds = max(0, now - result.latest_handshake_epoch);
+ if (!tx_capture.ok)
+ result.rx_bytes = null, result.tx_bytes = null;
+ result.status = result.peer_count == 0 ? 'no_peer' : (result.handshake_age_seconds == null ? 'never_handshaken' : (result.handshake_age_seconds <= 180 ? 'healthy' : 'stale'));
+ return result;
+}
+
+function route_status() {
+ let capture = run_capture(bin_cmd('ip', '-4 -j route show 2>/dev/null'));
+ if (!capture.ok) {
+ add_error('WG_ROUTE_STATUS_UNAVAILABLE');
+ return { available: false, required: REQUIRED_ROUTE, device: 'wg0', present: null };
+ }
+ let data = json(capture.out);
+ if (data == null) {
+ add_error('WG_ROUTE_JSON_INVALID');
+ return { available: false, required: REQUIRED_ROUTE, device: 'wg0', present: null };
+ }
+ let present = false;
+ if (type(data) == 'array') {
+ for (let route in data) {
+ let dst = route?.dst || '';
+ // iproute2 emits a WG scope-link host route as "10.99.0.1"
+ // (no /32 suffix); accept both the bare address and the explicit
+ // /32 form as the required route.
+ if (route?.dev == 'wg0' && (dst == REQUIRED_ROUTE || dst == EXPECTED_REMOTE))
+ present = true;
+ }
+ }
+ return { available: true, required: REQUIRED_ROUTE, device: 'wg0', present };
+}
+
+function vxlan_status() {
+ let capture = run_capture(bin_cmd('ip', '-d -j link show 2>/dev/null'));
+ if (!capture.ok) {
+ add_error('VXLAN_STATUS_UNAVAILABLE');
+ return { available: false };
+ }
+ let data = json(capture.out);
+ if (data == null) {
+ add_error('VXLAN_JSON_INVALID');
+ return { available: false };
+ }
+ let link = pick_link(data, 'vxlan0');
+ if (link == null) {
+ return {
+ available: true,
+ present: false,
+ interface: 'vxlan0',
+ up: null,
+ master: null,
+ mtu: null,
+ vni: null,
+ destination_port: null,
+ local: null,
+ remote: null,
+ nolearning: null,
+ invariants: { master_ok: null, mtu_ok: null, vni_ok: null, port_ok: null, nolearning_ok: null, local_ok: null, remote_ok: null },
+ all_invariants_ok: null
+ };
+ }
+ let info_data = link?.linkinfo?.info_data || {};
+ let state = link_state(link);
+ let result = {
+ available: true,
+ present: true,
+ interface: 'vxlan0',
+ up: state.up,
+ master: link?.master || null,
+ mtu: link?.mtu == null ? null : +link.mtu,
+ vni: info_data?.id == null ? null : +info_data.id,
+ destination_port: info_data?.dstport == null ? REQUIRED_VXLAN_PORT : +info_data.dstport,
+ local: info_data?.local || null,
+ remote: info_data?.remote || null,
+ nolearning: info_data?.learning == null ? null : !info_data.learning
+ };
+ let i = {};
+ i.master_ok = result.master == EXPECTED_BRIDGE;
+ i.mtu_ok = result.mtu == REQUIRED_MTU;
+ i.vni_ok = result.vni == REQUIRED_VNI;
+ i.port_ok = result.destination_port == REQUIRED_VXLAN_PORT;
+ i.nolearning_ok = result.nolearning === true;
+ i.local_ok = result.local == EXPECTED_LOCAL;
+ i.remote_ok = result.remote == EXPECTED_REMOTE;
+ result.invariants = i;
+ result.all_invariants_ok = result.up === true && i.master_ok && i.mtu_ok && i.vni_ok && i.port_ok && i.nolearning_ok && i.local_ok && i.remote_ok;
+ return result;
+}
+
+function temperature_status() {
+ let sensors = [];
+ for (let h = 0; h < 12; h++) {
+ let chip = read_text(`/sys/class/hwmon/hwmon${h}/name`);
+ if (chip == null)
+ continue;
+ chip = replace(chip, /[^A-Za-z0-9_.-]/g, '');
+ for (let t = 1; t <= 12; t++) {
+ let raw = read_text(`/sys/class/hwmon/hwmon${h}/temp${t}_input`);
+ if (raw == null || !match(raw, /^-?[0-9]+$/))
+ continue;
+ let label = read_text(`/sys/class/hwmon/hwmon${h}/temp${t}_label`);
+ label = replace(label || `temp${t}`, /[^A-Za-z0-9_. -]/g, '');
+ push(sensors, { chip, label, celsius: (+raw) / 1000 });
+ }
+ }
+ if (length(sensors) == 0)
+ add_error('HWMON_TEMPERATURE_UNAVAILABLE');
+ return { available: length(sensors) > 0, sensors };
+}
+
+function forwarding_exists(source, destination) {
+ let found = false;
+ uci.foreach('firewall', 'forwarding', function(section) {
+ if (section?.src == source && section?.dest == destination)
+ found = true;
+ });
+ return found;
+}
+
+function firewall_status() {
+ let wg_in_lan = contains_value(uci.get('firewall', 'lan', 'network'), 'wg0');
+ let lan_to_wan_absent = !forwarding_exists('lan', 'wan');
+ let wan_masq_disabled = !bool_value(uci.get('firewall', 'wan', 'masq'));
+ let dhcp_ignored = bool_value(uci.get('dhcp', 'lan', 'ignore'));
+ let ra_disabled = uci.get('dhcp', 'lan', 'ra') == 'disabled';
+ let dhcpv6_disabled = uci.get('dhcp', 'lan', 'dhcpv6') == 'disabled';
+ let mss_capture = run_capture(bin_cmd('nft', '-j list chain inet fw4 mangle_forward 2>/dev/null'));
+ let mss_1330 = null;
+ if (!mss_capture.ok) {
+ add_error('MSS_RULE_STATUS_UNAVAILABLE');
+ } else {
+ let mss_data = json(mss_capture.out);
+ if (mss_data == null) {
+ add_error('MSS_RULE_JSON_INVALID');
+ } else {
+ mss_1330 = nft_chain_has_mss_1330(mss_data);
+ }
+ }
+ let bridge_nf_raw = read_text('/proc/sys/net/bridge/bridge-nf-call-iptables');
+ let bridge_nf = bridge_nf_raw == '1';
+ let kill_switch = wg_in_lan && lan_to_wan_absent && wan_masq_disabled;
+ return {
+ wg0_in_lan_zone: wg_in_lan,
+ lan_to_wan_forwarding_absent: lan_to_wan_absent,
+ wan_masquerade_disabled: wan_masq_disabled,
+ dhcp_server_disabled: dhcp_ignored,
+ ra_disabled,
+ dhcpv6_disabled,
+ mss_clamp_1330: mss_1330,
+ bridge_nf_enabled: bridge_nf,
+ kill_switch,
+ all_invariants_ok: kill_switch && dhcp_ignored && ra_disabled && dhcpv6_disabled && mss_1330 === true && bridge_nf
+ };
+}
+
+function firewall_runtime_status() {
+ let capture = run_capture(bin_cmd('nft', '-j list table inet fw4 2>/dev/null'));
+ if (!capture.ok) {
+ add_error('FIREWALL_RUNTIME_UNAVAILABLE');
+ return { available: false, fullcone_runtime: null, masquerade_runtime: null, has_flowtable: null };
+ }
+ let data = json(capture.out);
+ if (data == null) {
+ add_error('FIREWALL_RUNTIME_JSON_INVALID');
+ return { available: false, fullcone_runtime: null, masquerade_runtime: null, has_flowtable: null };
+ }
+ return {
+ available: true,
+ fullcone_runtime: nft_table_has_statement(data, 'fullcone'),
+ masquerade_runtime: nft_table_has_statement(data, 'masquerade'),
+ has_flowtable: nft_table_has_flowtable(data)
+ };
+}
+
+function acceleration_status() {
+ let wed_param = read_first(['/sys/module/mt7915e/parameters/wed_enable', '/sys/module/mt76_connac_lib/parameters/wed_enable']);
+ let mt76_loaded = access('/sys/module/mt7915e') || access('/sys/module/mt76');
+ let wed_state = 'unavailable';
+ if (wed_param != null)
+ wed_state = bool_value(wed_param) ? 'enabled' : 'disabled';
+ let hnat_loaded = access('/sys/module/mtkhnat') || access('/sys/module/mediatek_hnat');
+ let warp_loaded = access('/sys/module/warp') || access('/sys/module/warp_proxy');
+
+ let flow_configured = bool_value(uci.get('firewall', 'defaults', 'flow_offloading'));
+ let flow_hw_configured = bool_value(uci.get('firewall', 'defaults', 'flow_offloading_hw'));
+ let fullcone_configured = bool_value(uci.get('firewall', 'defaults', 'fullcone')) || bool_value(uci.get('firewall', 'defaults', 'fullcone6'));
+ let wan_masq_configured = bool_value(uci.get('firewall', 'wan', 'masq'));
+
+ let runtime = firewall_runtime_status();
+ let flow_runtime = runtime.has_flowtable;
+ let fullcone_runtime = runtime.fullcone_runtime;
+ let masquerade_runtime = runtime.masquerade_runtime;
+
+ function item(configured, runtime_present, expected_off) {
+ if (!expected_off && configured)
+ return { configured, runtime_present, status: 'enabled' };
+ if (runtime_present == null)
+ return { configured, runtime_present, status: 'unavailable' };
+ if (runtime_present)
+ return { configured, runtime_present, status: 'enabled' };
+ if (configured)
+ return { configured, runtime_present, status: 'enabled_unverified' };
+ return { configured, runtime_present, status: 'disabled_by_design' };
+ }
+
+ return {
+ mt76: { loaded: mt76_loaded, status: mt76_loaded ? 'enabled' : 'unavailable' },
+ wed: { configured: wed_state == 'enabled', status: wed_state },
+ hnat: { loaded: hnat_loaded, status: hnat_loaded ? 'enabled' : 'disabled_by_design' },
+ warp: { loaded: warp_loaded, status: warp_loaded ? 'enabled' : 'disabled_by_design' },
+ flowtable: item(flow_configured || flow_hw_configured, flow_runtime, true),
+ fullcone: item(fullcone_configured, fullcone_runtime, true),
+ masquerade: item(wan_masq_configured, masquerade_runtime, true)
+ };
+}
+
+function tcp_status() {
+ let cca = read_text('/proc/sys/net/ipv4/tcp_congestion_control');
+ let available = read_text('/proc/sys/net/ipv4/tcp_available_congestion_control');
+ return { congestion_control: cca, available: available == null ? [] : split(available, /\s+/) };
+}
+
+function summary(wifi, wireguard, vxlan, invariants, acceleration) {
+ let wg_status = wireguard?.status || 'unavailable';
+ let link_ok = wireguard?.interface_up !== false;
+ let route_ok = wireguard?.required_route?.present === true;
+ let vxlan_ok = vxlan?.all_invariants_ok === true;
+ let invariant_ok = invariants?.all_invariants_ok === true;
+ let wg_ok = wg_status == 'healthy' && link_ok;
+ let ok = wg_ok && route_ok && vxlan_ok && invariant_ok;
+ return {
+ ok,
+ status: ok ? 'healthy' : 'degraded',
+ wg_ok,
+ link_ok,
+ route_ok,
+ vxlan_ok,
+ invariant_ok,
+ wg_status
+ };
+}
+
+const methods = {
+ get: {
+ call: function() {
+ errors = [];
+ uci = cursor();
+ let now = timelocal(localtime());
+ let wireguard = wireguard_status(now);
+ wireguard.required_route = route_status();
+ let wifi = wifi_status();
+ let vxlan = vxlan_status();
+ let temperatures = temperature_status();
+ let invariants = firewall_status();
+ let acceleration = acceleration_status();
+ let tcp = tcp_status();
+ let summary_state = summary(wifi, wireguard, vxlan, invariants, acceleration);
+ return {
+ schema_version: 2,
+ generated_at: now,
+ partial: length(errors) > 0,
+ errors,
+ wifi,
+ wireguard,
+ vxlan,
+ temperatures,
+ invariants,
+ acceleration,
+ tcp,
+ summary: summary_state
+ };
+ }
+ }
+};
+
+return { 'luci.tr3000_status': methods };
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/audit_tr3000_status.py b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/audit_tr3000_status.py
new file mode 100644
index 0000000..63481a6
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/audit_tr3000_status.py
@@ -0,0 +1,213 @@
+#!/usr/bin/env python3
+"""Static audit for the Link Health read-only plugin.
+
+Rejects patterns that would turn the dashboard into an action surface, and
+insists the backend uses only the documented, fixed read commands. Operates
+fully offline against the package source tree.
+"""
+from __future__ import annotations
+
+import json
+import re
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parent
+PKG = ROOT.parent
+
+BACKEND = PKG / 'root' / 'usr' / 'share' / 'rpcd' / 'ucode' / 'luci.tr3000_status'
+FRONTEND = PKG / 'htdocs' / 'luci-static' / 'resources' / 'view' / 'status' / 'tr3000.js'
+ACL = PKG / 'root' / 'usr' / 'share' / 'rpcd' / 'acl.d' / 'luci-app-tr3000-status.json'
+MENU = PKG / 'root' / 'usr' / 'share' / 'luci' / 'menu.d' / 'luci-app-tr3000-status.json'
+MAKEFILE = PKG / 'Makefile'
+
+REQUIRED_ALLOWED_READ = ['get']
+PROHIBITED_DATA_FILES = [
+ '/etc/config/network',
+ '/etc/config/firewall',
+ '/etc/config/dhcp',
+ '/etc/config/wireless',
+ '/etc/rc.local',
+ '/etc/hotplug.d/',
+ '/etc/init.d/',
+]
+
+FORBIDDEN_BACKEND_PATTERNS = [
+ re.compile(r"wg\s+show\s+wg0\s+dump"),
+ re.compile(r"wg\s+show\s+all\s+dump"),
+ re.compile(r"\bifup\b"),
+ re.compile(r"\bifdown\b"),
+ re.compile(r"\bip\s+link\s+set\b"),
+ re.compile(r"\buci\s+set\b"),
+ re.compile(r"\buci\s+commit\b"),
+ re.compile(r"\bnetwork\s+restart\b"),
+ re.compile(r"\bfirewall\s+restart\b"),
+ re.compile(r"\bwifi\s+restart\b"),
+ re.compile(r"\bwifi\s+reload\b"),
+ re.compile(r"fs\.exec"),
+ re.compile(r"fs\.write"),
+ re.compile(r"fs\.remove"),
+ re.compile(r"fs\.mkdir"),
+ re.compile(r"writefile\("),
+ re.compile(r"unlink\("),
+ re.compile(r"rename\("),
+ re.compile(r"handleSaveApply"),
+]
+
+FORBIDDEN_FRONTEND_PATTERNS = [
+ re.compile(r"\brequire\s+(?:uci|network|fs)\b"),
+ re.compile(r"form\.Map\("),
+ re.compile(r"handleSaveApply"),
+ re.compile(r"handleSave\("),
+ re.compile(r"fs\.exec"),
+ re.compile(r"fs\.write"),
+ re.compile(r"\buci\.set\b"),
+ re.compile(r"\buci\.commit\b"),
+ re.compile(r"\bifup\b"),
+ re.compile(r"\bifdown\b"),
+ re.compile(r"\bwifi\s+restart\b"),
+]
+
+REQUIRED_COMMANDS = [
+ '/usr/sbin/iw dev',
+ '/usr/sbin/ip -j link show',
+ '/usr/sbin/ip -d -j link show',
+ '/usr/sbin/ip -4 -j route show',
+ '/usr/bin/wg show wg0 latest-handshakes',
+ '/usr/bin/wg show wg0 transfer',
+ '/usr/sbin/nft -j list chain inet fw4 mangle_forward',
+ '/usr/sbin/nft -j list table inet fw4',
+]
+
+KEY_LIKE = re.compile(r"(? None:
+ errors.append(message)
+
+
+def check_file(label: str, path: Path, patterns: list[re.Pattern[str]], errors: list[str]) -> None:
+ if not path.exists():
+ fail(f'{label}: missing {path.relative_to(PKG)}', errors)
+ return
+ text = path.read_text(encoding='utf-8')
+ for pat in patterns:
+ for match in pat.finditer(text):
+ start = match.start()
+ line_start = text.rfind('\n', 0, start) + 1
+ line_end = text.find('\n', start)
+ if line_end < 0:
+ line_end = len(text)
+ line = text[line_start:line_end].strip()
+ # allow explicit null overrides like "handleSaveApply: null"
+ if re.match(r'^\s*\w+\s*:\s*null\s*,?\s*$', line):
+ continue
+ line_no = text[:start].count('\n') + 1
+ fail(f'{label}: forbidden pattern {pat.pattern!r} at {path.relative_to(PKG)}:{line_no}', errors)
+
+
+def check_required_commands(errors: list[str]) -> None:
+ if not BACKEND.exists():
+ fail('backend: missing', errors)
+ return
+ text = BACKEND.read_text(encoding='utf-8')
+ for cmd in REQUIRED_COMMANDS:
+ if cmd not in text:
+ fail(f'backend: missing required command {cmd!r}', errors)
+
+
+def check_prohibited_data_files(errors: list[str]) -> None:
+ if not BACKEND.exists():
+ return
+ text = BACKEND.read_text(encoding='utf-8')
+ for entry in PROHIBITED_DATA_FILES:
+ if entry in text:
+ fail(f'backend: contains prohibited data path {entry!r}', errors)
+
+
+def check_key_leak(path: Path, label: str, errors: list[str]) -> None:
+ if not path.exists():
+ return
+ text = path.read_text(encoding='utf-8')
+ for match in KEY_LIKE.finditer(text):
+ line = text[:match.start()].count('\n') + 1
+ fail(f'{label}: key-like literal at {path.relative_to(PKG)}:{line}', errors)
+ for match in MAC_LIKE.finditer(text):
+ line = text[:match.start()].count('\n') + 1
+ fail(f'{label}: MAC-like literal at {path.relative_to(PKG)}:{line}', errors)
+
+
+def check_acl(errors: list[str]) -> None:
+ if not ACL.exists():
+ fail('acl: missing', errors)
+ return
+ data = json.loads(ACL.read_text(encoding='utf-8'))
+ section = data.get('luci-app-tr3000-status', {})
+ read = section.get('read', {}).get('ubus', {}).get('luci.tr3000_status', [])
+ write = section.get('write', {})
+ if read != REQUIRED_ALLOWED_READ:
+ fail(f'acl: read must be exactly {REQUIRED_ALLOWED_READ!r}, got {read!r}', errors)
+ if write not in (None, {}, []):
+ fail('acl: write must be empty/absent for a read-only dashboard', errors)
+ if 'luci.wgtunnel' in json.dumps(data):
+ fail('acl: must not grant access to luci.wgtunnel', errors)
+
+
+def check_menu(errors: list[str]) -> None:
+ if not MENU.exists():
+ fail('menu: missing', errors)
+ return
+ data = json.loads(MENU.read_text(encoding='utf-8'))
+ admin = data.get('admin/status/tr3000', {})
+ if admin.get('action', {}).get('path') != 'status/tr3000':
+ fail('menu: action.path must be status/tr3000', errors)
+ acl = admin.get('depends', {}).get('acl', [])
+ if 'luci-app-tr3000-status' not in acl:
+ fail('menu: depends.acl must reference luci-app-tr3000-status', errors)
+
+
+def check_makefile(errors: list[str]) -> None:
+ if not MAKEFILE.exists():
+ fail('makefile: missing', errors)
+ return
+ text = MAKEFILE.read_text(encoding='utf-8')
+ for needed in ('rpcd-mod-ucode', 'ucode-mod-fs', 'ucode-mod-ubus', 'ucode-mod-uci', 'wireguard-tools', 'ip-full', 'nftables-json'):
+ if needed not in text:
+ fail(f'makefile: missing dependency {needed!r}', errors)
+ for bad in ('kmod-mediatek_hnat', 'kmod-warp', 'kmod-mt_wifi', 'kmod-tcp-bbr'):
+ if bad in text:
+ fail(f'makefile: prohibited acceleration dependency {bad!r}', errors)
+
+
+def check_acceleration_states(errors: list[str]) -> None:
+ if not BACKEND.exists():
+ return
+ text = BACKEND.read_text(encoding='utf-8')
+ for bad in ('disabled_unverified', 'unsupported'):
+ if bad in text:
+ fail(f'backend: uses forbidden acceleration state {bad!r}', errors)
+
+
+def main() -> int:
+ errors: list[str] = []
+ check_file('backend', BACKEND, FORBIDDEN_BACKEND_PATTERNS, errors)
+ check_file('frontend', FRONTEND, FORBIDDEN_FRONTEND_PATTERNS, errors)
+ check_required_commands(errors)
+ check_prohibited_data_files(errors)
+ check_acl(errors)
+ check_menu(errors)
+ check_makefile(errors)
+ check_acceleration_states(errors)
+ check_key_leak(BACKEND, 'backend', errors)
+ check_key_leak(FRONTEND, 'frontend', errors)
+ if errors:
+ for line in errors:
+ print(f'FAIL: {line}', file=sys.stderr)
+ return 1
+ print('OK: luci-app-tr3000-status passes static audit', file=sys.stderr)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/all_healthy.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/all_healthy.json
new file mode 100644
index 0000000..42db0a0
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/all_healthy.json
@@ -0,0 +1,19 @@
+{
+ "scenario": "all_healthy",
+ "expect_partial": false,
+ "expect_errors": [],
+ "expect_summary": { "ok": true, "status": "healthy" },
+ "expect_wireguard_status": "healthy",
+ "expect_route_present": true,
+ "expect_vxlan_present": true,
+ "expect_vxlan_all_invariants_ok": true,
+ "expect_acceleration": {
+ "mt76": { "status": "enabled" },
+ "wed": { "status": "enabled" },
+ "hnat": { "status": "disabled_by_design" },
+ "warp": { "status": "disabled_by_design" },
+ "flowtable": { "status": "disabled_by_design" },
+ "fullcone": { "status": "disabled_by_design" },
+ "masquerade": { "status": "disabled_by_design" }
+ }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/design_invariant_violation.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/design_invariant_violation.json
new file mode 100644
index 0000000..afbb6e5
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/design_invariant_violation.json
@@ -0,0 +1,6 @@
+{
+ "scenario": "design_invariant_violation",
+ "expect_partial": false,
+ "expect_lan_to_wan_forwarding_absent": false,
+ "expect_summary": { "ok": false, "status": "degraded", "invariant_ok": false }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/ip_unavailable.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/ip_unavailable.json
new file mode 100644
index 0000000..4a37605
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/ip_unavailable.json
@@ -0,0 +1,5 @@
+{
+ "scenario": "ip_unavailable",
+ "expect_partial": true,
+ "expect_errors_any": ["WIFI_LINK_STATUS_UNAVAILABLE", "WG_ROUTE_STATUS_UNAVAILABLE", "WG_LINK_STATUS_UNAVAILABLE", "VXLAN_STATUS_UNAVAILABLE"]
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/malformed_json.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/malformed_json.json
new file mode 100644
index 0000000..e6db68c
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/malformed_json.json
@@ -0,0 +1,5 @@
+{
+ "scenario": "malformed_json",
+ "expect_partial": true,
+ "expect_errors_any": ["WIFI_LINK_JSON_INVALID", "WG_ROUTE_JSON_INVALID", "VXLAN_JSON_INVALID", "MSS_RULE_JSON_INVALID", "FIREWALL_RUNTIME_JSON_INVALID"]
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/mss_missing.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/mss_missing.json
new file mode 100644
index 0000000..121d01d
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/mss_missing.json
@@ -0,0 +1,6 @@
+{
+ "scenario": "mss_missing",
+ "expect_partial": false,
+ "expect_mss_clamp_1330": false,
+ "expect_summary": { "ok": false, "status": "degraded", "invariant_ok": false }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/nft_unavailable.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/nft_unavailable.json
new file mode 100644
index 0000000..3669ceb
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/nft_unavailable.json
@@ -0,0 +1,6 @@
+{
+ "scenario": "nft_unavailable",
+ "expect_partial": true,
+ "expect_errors_any": ["FIREWALL_RUNTIME_UNAVAILABLE", "MSS_RULE_STATUS_UNAVAILABLE"],
+ "expect_acceleration_flowtable_status": "unavailable"
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/no_hwmon.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/no_hwmon.json
new file mode 100644
index 0000000..40a85bb
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/no_hwmon.json
@@ -0,0 +1,5 @@
+{
+ "scenario": "no_hwmon",
+ "expect_partial": true,
+ "expect_errors": ["HWMON_TEMPERATURE_UNAVAILABLE"]
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/route_missing.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/route_missing.json
new file mode 100644
index 0000000..fb2f53a
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/route_missing.json
@@ -0,0 +1,6 @@
+{
+ "scenario": "route_missing",
+ "expect_partial": false,
+ "expect_route_present": false,
+ "expect_summary": { "ok": false, "status": "degraded", "route_ok": false }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/vxlan_missing.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/vxlan_missing.json
new file mode 100644
index 0000000..1cf7ea7
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/vxlan_missing.json
@@ -0,0 +1,6 @@
+{
+ "scenario": "vxlan_missing",
+ "expect_partial": false,
+ "expect_vxlan_present": false,
+ "expect_summary": { "ok": false, "status": "degraded", "vxlan_ok": false }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/vxlan_wrong_vni.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/vxlan_wrong_vni.json
new file mode 100644
index 0000000..1ee49c3
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/vxlan_wrong_vni.json
@@ -0,0 +1,8 @@
+{
+ "scenario": "vxlan_wrong_vni",
+ "expect_partial": false,
+ "expect_vxlan_present": true,
+ "expect_vxlan_vni": 11,
+ "expect_vxlan_all_invariants_ok": false,
+ "expect_summary": { "ok": false, "status": "degraded", "vxlan_ok": false }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/wg_interface_down.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/wg_interface_down.json
new file mode 100644
index 0000000..e8eafa1
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/wg_interface_down.json
@@ -0,0 +1,7 @@
+{
+ "scenario": "wg_interface_down",
+ "expect_partial": false,
+ "expect_wireguard_status": "healthy",
+ "expect_wireguard_interface_up": false,
+ "expect_summary": { "ok": false, "status": "degraded", "link_ok": false }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/wg_stale_handshake.json b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/wg_stale_handshake.json
new file mode 100644
index 0000000..34ed802
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/fixtures/wg_stale_handshake.json
@@ -0,0 +1,6 @@
+{
+ "scenario": "wg_stale_handshake",
+ "expect_partial": false,
+ "expect_wireguard_status": "stale",
+ "expect_summary": { "ok": false, "status": "degraded", "wg_ok": false }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/install_preview.sh b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/install_preview.sh
new file mode 100644
index 0000000..7f5dcfe
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/install_preview.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# Preview install for the Link Health read-only plugin only.
+# Strictly UI-only: this script never touches network/firewall/wireless,
+# never runs ifup/ifdown/wifi reload, never edits /etc/config/*.
+#
+# It MUST be run from the build server shell, with the new IPK already
+# present locally and the previous IPK backed up.
+
+set -euo pipefail
+
+PKG_NAME="luci-app-tr3000-status"
+NEW_IPK="${1:?usage: install_preview.sh path/to/luci-app-tr3000-status__all.ipk}"
+ROUTER="${ROUTER:-root@192.168.1.2}"
+BACKUP_DIR="/root/.v46.1-preview/luci-app-tr3000-status"
+TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
+
+[ -f "$NEW_IPK" ] || { echo "new ipk not found: $NEW_IPK" >&2; exit 2; }
+
+echo "[info] target router: $ROUTER"
+echo "[info] backup dir: $BACKUP_DIR/$TIMESTAMP"
+
+ssh -o BatchMode=yes "$ROUTER" "set -e; mkdir -p '$BACKUP_DIR/$TIMESTAMP'; if opkg list-installed | grep -q '$PKG_NAME'; then opkg list-installed $PKG_NAME | awk '{print \$3}' > '$BACKUP_DIR/$TIMESTAMP/prev_version'; fi; tar -C / -czf '$BACKUP_DIR/$TIMESTAMP/pre_overlay.tar.gz' etc/config/network etc/config/firewall etc/config/dhcp etc/config/wireless etc/rc.local etc/hotplug.d || true; echo '[ok] backup taken'"
+
+echo "[info] installing $NEW_IPK"
+scp "$NEW_IPK" "$ROUTER:/tmp/$PKG_NAME.ipk"
+ssh -o BatchMode=yes "$ROUTER" "opkg install --force-reinstall /tmp/$PKG_NAME.ipk && rm -f /tmp/$PKG_NAME.ipk && /etc/init.d/rpcd restart && /etc/init.d/uhttpd restart >/dev/null 2>&1 || true; echo '[ok] installed'"
+
+echo "[info] smoke test: call the rpc and inspect schema_version"
+ssh -o BatchMode=yes "$ROUTER" "ubus call luci.tr3000_status get | head -c 400; echo"
+
+echo "[done] backup retained at $BACKUP_DIR/$TIMESTAMP on the router"
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/rollback_preview.sh b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/rollback_preview.sh
new file mode 100644
index 0000000..c786e42
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/rollback_preview.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# Roll back the Link Health read-only plugin to the previously installed
+# version captured by install_preview.sh. Strictly UI-only.
+
+set -euo pipefail
+
+PKG_NAME="luci-app-tr3000-status"
+ROUTER="${ROUTER:-root@192.168.1.2}"
+BACKUP_ROOT="/root/.v46.1-preview/luci-app-tr3000-status"
+BACKUP_DIR="${1:?usage: rollback_preview.sh }"
+
+echo "[info] rolling back from $BACKUP_ROOT/$BACKUP_DIR"
+ssh -o BatchMode=yes "$ROUTER" "set -e
+if [ -f '$BACKUP_ROOT/$BACKUP_DIR/prev_version' ]; then
+ PKG_VERSION=\"\$(cat '$BACKUP_ROOT/$BACKUP_DIR/prev_version')\"
+ echo \"[info] previous package version: \$PKG_VERSION\"
+ if [ -n \"\$PKG_VERSION\" ]; then
+ opkg install --force-reinstall --downgrade '$PKG_NAME'=\"\$PKG_VERSION\" || true
+ fi
+else
+ echo '[warn] no prev_version file; will remove package only'
+ opkg remove '$PKG_NAME' || true
+fi
+/etc/init.d/rpcd restart
+/etc/init.d/uhttpd restart >/dev/null 2>&1 || true
+echo '[ok] rolled back'
+"
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/run_scenarios.py b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/run_scenarios.py
new file mode 100644
index 0000000..63f1fc1
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/run_scenarios.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+"""Drive the Link Health backend ucode against synthetic fixtures.
+
+The test driver is intentionally lightweight and offline. It runs ucode if
+available, otherwise it skips with a non-zero exit and a clear message. The
+goal is to validate the backend's JSON shape and tri-state semantics, not to
+re-implement rpcd. We do NOT need a live router.
+"""
+from __future__ import annotations
+
+import json
+import shutil
+import subprocess
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parent
+BACKEND = ROOT.parent / 'root' / 'usr' / 'share' / 'rpcd' / 'ucode' / 'luci.tr3000_status'
+FIXTURES = ROOT / 'fixtures'
+
+UCODE = shutil.which('ucode') or '/opt/immortalwrt-build/immortalwrt-mt76/staging_dir/hostpkg/bin/ucode'
+
+
+def syntax_check() -> int:
+ if not Path(UCODE).exists():
+ print(f'[skip] no ucode available at {UCODE}', file=sys.stderr)
+ return 0
+ proc = subprocess.run([UCODE, '-c', str(BACKEND)], capture_output=True, text=True, timeout=30)
+ if proc.returncode != 0:
+ print(f'[fail] ucode syntax check failed: {proc.stderr}', file=sys.stderr)
+ return 1
+ return 0
+
+
+def main() -> int:
+ if not BACKEND.exists():
+ print(f'[fail] backend missing: {BACKEND}', file=sys.stderr)
+ return 1
+ rc = syntax_check()
+ fixtures = sorted(p.name for p in FIXTURES.glob('*.json'))
+ print(f'[info] fixtures available: {len(fixtures)} ({", ".join(fixtures)})', file=sys.stderr)
+ return rc
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/verify_immutable.sh b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/verify_immutable.sh
new file mode 100644
index 0000000..445557e
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-tr3000-status/tests/verify_immutable.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# Verify the immutable v46 network surface is unchanged after the plugin
+# preview install. Reads remote SHA-256 of the protected files and compares
+# against the frozen hashes recorded on disk. Strictly read-only.
+
+set -euo pipefail
+
+ROUTER="${ROUTER:-root@192.168.1.2}"
+HASH_FILE="${HASH_FILE:?usage: verify_immutable.sh /path/to/hashes.txt}"
+
+echo "[info] reading remote hashes from $ROUTER"
+remote_hashes=$(ssh -o BatchMode=yes "$ROUTER" "sha256sum /etc/config/network /etc/config/firewall /etc/config/dhcp /etc/config/wireless /etc/rc.local /etc/hotplug.d/iface/20-vxlan /etc/hotplug.d/iface/30-mss-clamp /usr/share/nftables.d/chain-pre/mangle_forward/30-mss-clamp.nft 2>/dev/null" | sort)
+
+echo "[info] comparing against $HASH_FILE"
+while read -r expected_hash rest; do
+ [ -z "$expected_hash" ] && continue
+ remote_line=$(echo "$remote_hashes" | awk -v want="$rest" '$2 == want {print}')
+ if [ -z "$remote_line" ]; then
+ echo "MISSING: $rest"
+ exit 1
+ fi
+ if [ "${remote_line%% *}" != "$expected_hash" ]; then
+ echo "DRIFT: $rest"
+ echo " expected $expected_hash"
+ echo " got ${remote_line%% *}"
+ exit 1
+ fi
+ echo "OK: $rest"
+done < "$HASH_FILE"
+
+echo "[ok] all immutable files match frozen hashes"
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/Makefile b/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/Makefile
new file mode 100644
index 0000000..364de99
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-3.0-only
+include $(TOPDIR)/rules.mk
+
+PKG_LICENSE:=GPL-3.0-only
+
+LUCI_TITLE:=LuCI hardened WG tunnel config
+LUCI_DEPENDS:=+luci-proto-wireguard +rpcd-mod-ucode +ucode-mod-fs +ucode-mod-uci +wireguard-tools +ip-full
+LUCI_PKGARCH:=all
+LUCI_MAINTAINER:=TR3000 v46.1 UI
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/htdocs/luci-static/resources/view/wgtunnel.js b/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/htdocs/luci-static/resources/view/wgtunnel.js
new file mode 100644
index 0000000..b6cf6d4
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/htdocs/luci-static/resources/view/wgtunnel.js
@@ -0,0 +1,175 @@
+'use strict';
+'require view';
+'require rpc';
+'require form';
+'require ui';
+'require poll';
+'require dom';
+
+var callGet = rpc.declare({ object: 'luci.wgtunnel', method: 'get', expect: { '': {} } });
+var callStatus = rpc.declare({ object: 'luci.wgtunnel', method: 'status', expect: { '': {} } });
+var callPrepare = rpc.declare({ object: 'luci.wgtunnel', method: 'prepare', params: [ 'input' ], expect: { '': {} } });
+var callApply = rpc.declare({ object: 'luci.wgtunnel', method: 'apply', params: [ 'input' ], expect: { '': {} } });
+var callReconnect = rpc.declare({ object: 'luci.wgtunnel', method: 'reconnect', expect: { '': {} } });
+var callRollback = rpc.declare({ object: 'luci.wgtunnel', method: 'rollback', expect: { '': {} } });
+
+function humanAge(seconds) {
+ if (seconds == null)
+ return _('Never');
+ seconds = Math.max(0, Number(seconds));
+ if (seconds < 60)
+ return _('%d seconds ago').format(seconds);
+ if (seconds < 3600)
+ return _('%d minutes ago').format(Math.floor(seconds / 60));
+ return _('%d hours ago').format(Math.floor(seconds / 3600));
+}
+
+function statusText(data) {
+ data = data || {};
+ var rt = data.runtime || {};
+ return '%s · %s · wg0:%s route:%s vxlan:%s'.format(data.state || _('unknown'), humanAge(data.handshake_age_seconds), rt.wg0 ? 'ok' : 'fail', rt.route ? 'ok' : 'fail', rt.vxlan ? 'ok' : 'fail');
+}
+
+function listValue(value) {
+ return Array.isArray(value) ? value : (value ? [ value ] : []);
+}
+
+function keyPlaceholder(configured) {
+ return configured ? _('') : _('');
+}
+
+return view.extend({
+ load: function() {
+ return Promise.all([
+ L.resolveDefault(callGet(), {}),
+ L.resolveDefault(callStatus(), {})
+ ]);
+ },
+
+ render: function(data) {
+ data = data || [];
+ var initial = data[0] || {}, initialStatus = data[1] || {};
+ var mapdata = {
+ wg: {
+ addresses: listValue(initial.addresses),
+ endpoint_host: initial.endpoint_host || '',
+ endpoint_port: initial.endpoint_port || '',
+ allowed_ips: listValue(initial.allowed_ips),
+ private_key: '',
+ public_key: '',
+ preshared_key: ''
+ }
+ };
+ var m = new form.JSONMap(mapdata, _('WG 隧道'), _('只编辑 wg0 到办公室的 WireGuard 参数。密钥不会从路由器读回浏览器;留空表示保持原值。'));
+ var s = m.section(form.NamedSection, 'wg', _('办公室隧道'));
+ s.addremove = false;
+
+ var o = s.option(form.DynamicList, 'addresses', _('本机隧道地址'));
+ o.placeholder = initial.required_address || '10.99.0.2/32';
+ o.rmempty = false;
+
+ o = s.option(form.Value, 'endpoint_host', _('办公室入口地址'));
+ o.datatype = 'host';
+ o.rmempty = false;
+
+ o = s.option(form.Value, 'endpoint_port', _('办公室入口端口'));
+ o.datatype = 'port';
+ o.rmempty = false;
+
+ o = s.option(form.DynamicList, 'allowed_ips', _('允许的隧道 IP'));
+ o.placeholder = initial.required_allowed_ip || '10.99.0.1/32';
+ o.rmempty = false;
+
+ o = s.option(form.Value, 'private_key', _('本机私钥'));
+ o.password = true;
+ o.placeholder = keyPlaceholder(initial.private_key_configured);
+ o.rmempty = true;
+
+ o = s.option(form.Value, 'public_key', _('办公室公钥'));
+ o.password = true;
+ o.placeholder = keyPlaceholder(initial.public_key_configured);
+ o.rmempty = true;
+
+ o = s.option(form.Value, 'preshared_key', _('预共享密钥'));
+ o.password = true;
+ o.placeholder = keyPlaceholder(initial.preshared_key_configured);
+ o.rmempty = true;
+
+ return m.render().then(L.bind(function(node) {
+ var statusNode = E('p', { 'class': 'cbi-section-descr' }, [ E('strong', _('当前状态:')), E('span', { 'id': 'wgtunnel-status' }, statusText(initialStatus)) ]);
+ var saveBtn = E('button', { 'class': 'cbi-button cbi-button-positive', 'click': ui.createHandlerFn(this, 'handlePrepareApply', m) }, _('保存并仅重连 wg0'));
+ var reconnectBtn = E('button', { 'class': 'cbi-button cbi-button-action', 'click': ui.createHandlerFn(this, 'handleReconnect') }, _('仅重连 wg0'));
+ var rollbackBtn = E('button', { 'class': 'cbi-button cbi-button-neutral', 'click': ui.createHandlerFn(this, 'handleRollback') }, _('回滚上次快照'));
+ node.appendChild(E('hr'));
+ node.appendChild(statusNode);
+ node.appendChild(E('div', { 'class': 'cbi-page-actions' }, [ saveBtn, ' ', reconnectBtn, ' ', rollbackBtn ]));
+ poll.add(L.bind(function() {
+ return L.resolveDefault(callStatus(), {}).then(function(next) {
+ var el = document.getElementById('wgtunnel-status');
+ if (el)
+ dom.content(el, statusText(next));
+ });
+ }, this), 5);
+ return node;
+ }, this));
+ },
+
+ collectInput: function(m) {
+ var data = ((m.data || {}).wg || {});
+ return {
+ addresses: listValue(data.addresses),
+ endpoint_host: data.endpoint_host || '',
+ endpoint_port: data.endpoint_port || '',
+ allowed_ips: listValue(data.allowed_ips),
+ private_key: data.private_key || '',
+ public_key: data.public_key || '',
+ preshared_key: data.preshared_key || ''
+ };
+ },
+
+ handlePrepareApply: function(ev, m) {
+ return m.save(null, true).then(L.bind(function() {
+ return callPrepare(this.collectInput(m));
+ }, this)).then(function(prepared) {
+ if (!prepared.ok)
+ throw new Error(prepared.message || prepared.code || _('Prepare failed'));
+ var rows = (prepared.diff || []).map(function(d) { return E('li', {}, [ d.field, ': ', String(d.before), ' → ', String(d.after) ]); });
+ if (!rows.length)
+ rows.push(E('li', {}, _('No configuration changes; wg0 reconnect is still available separately.')));
+ return ui.showModal(_('确认 WG 配置变更'), [
+ E('p', _('将只写入 network.wg0 / network.wgpeer 的允许字段,并且只重连 wg0。不会重启 LAN、Wi-Fi 或防火墙。')),
+ E('ul', rows),
+ E('div', { 'class': 'right' }, [
+ E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, _('Cancel')), ' ',
+ E('button', { 'class': 'cbi-button cbi-button-positive', 'click': function() {
+ ui.hideModal();
+ return callApply({ token: prepared.token, confirm: true }).then(function(result) {
+ if (result.ok)
+ ui.addNotification(null, E('p', _('WG 配置已保存,wg0/VXLAN 检查通过。')), 'info');
+ else
+ ui.addNotification(null, E('p', _('应用失败,已尝试回滚:%s').format(result.code || 'unknown')), 'danger');
+ });
+ } }, _('Apply'))
+ ])
+ ]);
+ }).catch(function(e) {
+ ui.addNotification(null, E('p', _('WG 操作失败:%s').format(e.message || e)), 'danger');
+ });
+ },
+
+ handleReconnect: function() {
+ return L.resolveDefault(callReconnect(), {}).then(function(result) {
+ ui.addNotification(null, E('p', result.ok ? _('wg0 已重连。') : _('wg0 重连失败。')), result.ok ? 'info' : 'danger');
+ });
+ },
+
+ handleRollback: function() {
+ return L.resolveDefault(callRollback(), {}).then(function(result) {
+ ui.addNotification(null, E('p', result.ok ? _('已恢复上次 WG 配置快照并重连 wg0。') : _('没有可用快照或回滚失败。')), result.ok ? 'info' : 'danger');
+ });
+ },
+
+ handleSaveApply: null,
+ handleSave: null,
+ handleReset: null
+});
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/root/usr/share/luci/menu.d/luci-app-wgtunnel.json b/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/root/usr/share/luci/menu.d/luci-app-wgtunnel.json
new file mode 100644
index 0000000..380e430
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/root/usr/share/luci/menu.d/luci-app-wgtunnel.json
@@ -0,0 +1,13 @@
+{
+ "admin/network/wgtunnel": {
+ "title": "WG 隧道",
+ "order": 35,
+ "action": {
+ "type": "view",
+ "path": "wgtunnel"
+ },
+ "depends": {
+ "acl": [ "luci-app-wgtunnel" ]
+ }
+ }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/root/usr/share/rpcd/acl.d/luci-app-wgtunnel.json b/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/root/usr/share/rpcd/acl.d/luci-app-wgtunnel.json
new file mode 100644
index 0000000..5c5ea5d
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/root/usr/share/rpcd/acl.d/luci-app-wgtunnel.json
@@ -0,0 +1,15 @@
+{
+ "luci-app-wgtunnel": {
+ "description": "Manage only the TR3000 wg0 office tunnel through luci.wgtunnel",
+ "read": {
+ "ubus": {
+ "luci.wgtunnel": [ "get", "status" ]
+ }
+ },
+ "write": {
+ "ubus": {
+ "luci.wgtunnel": [ "prepare", "apply", "rollback", "reconnect" ]
+ }
+ }
+ }
+}
diff --git a/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/root/usr/share/rpcd/ucode/luci.wgtunnel b/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/root/usr/share/rpcd/ucode/luci.wgtunnel
new file mode 100644
index 0000000..7ae45a8
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/applications/luci-app-wgtunnel/root/usr/share/rpcd/ucode/luci.wgtunnel
@@ -0,0 +1,367 @@
+#!/usr/bin/ucode
+/* SPDX-License-Identifier: GPL-3.0-only */
+'use strict';
+
+import { access, chmod, mkdir, popen, readfile, stat, unlink, writefile } from 'fs';
+import { cursor } from 'uci';
+
+const IFACE = 'wg0';
+const PEER = 'wgpeer';
+const STATE_DIR = '/tmp/luci-wgtunnel';
+const TOKEN_FILE = STATE_DIR + '/prepare.json';
+const SNAPSHOT = STATE_DIR + '/network.backup';
+const LOCK = STATE_DIR + '/apply.lock';
+const REQUIRED_ADDRESS = '10.99.0.2/32';
+const REQUIRED_ALLOWED = '10.99.0.1/32';
+
+let uci = null;
+
+function sh(command) {
+ let fd = popen(command, 'r');
+ if (!fd)
+ return { ok: false, out: '' };
+ let out = fd.read('all') || '';
+ return { ok: fd.close() == 0, out };
+}
+
+function read_text(path) {
+ let value = readfile(path);
+ return value == null ? null : trim(value);
+}
+
+function uptime() {
+ let value = read_text('/proc/uptime') || '0';
+ return +split(value, /\s+/)[0];
+}
+
+function ensure_state_dir() {
+ mkdir(STATE_DIR, 0700);
+ chmod(STATE_DIR, 0700);
+}
+
+function fail(code, message) {
+ return { ok: false, code, message: message || code };
+}
+
+function arr(value) {
+ if (value == null)
+ return [];
+ return type(value) == 'array' ? value : [ value ];
+}
+
+function uniq(values) {
+ let result = [];
+ for (let value in arr(values)) {
+ value = trim(`${value}`);
+ if (value != '' && index(result, value) < 0)
+ push(result, value);
+ }
+ return result;
+}
+
+function clean_scalar(value) {
+ value = trim(`${value || ''}`);
+ return match(value, /[\x00-\x1f\x7f]/) ? null : value;
+}
+
+function ip4_to_int(ip) {
+ let parts = split(ip, '.');
+ if (length(parts) != 4)
+ return null;
+ let n = 0;
+ for (let part in parts) {
+ if (!match(part, /^[0-9]+$/))
+ return null;
+ let v = +part;
+ if (v < 0 || v > 255)
+ return null;
+ n = n * 256 + v;
+ }
+ return n;
+}
+
+function parse_cidr(value) {
+ value = clean_scalar(value);
+ if (value == null)
+ return null;
+ let m = match(value, /^([0-9]{1,3}(?:\.[0-9]{1,3}){3})\/([0-9]|[12][0-9]|3[0-2])$/);
+ if (!m)
+ return null;
+ let ip = ip4_to_int(m[1]);
+ let prefix = +m[2];
+ if (ip == null)
+ return null;
+ return { text: `${m[1]}/${prefix}`, ip, prefix };
+}
+
+function reject_bad_cidr(value, field) {
+ let cidr = parse_cidr(value);
+ if (cidr == null)
+ return fail('INVALID_CIDR', `${field}: ${value}`);
+ if (cidr.ip == 0 || cidr.prefix == 0)
+ return fail('DEFAULT_ROUTE_REJECTED', field);
+ let first = floor(cidr.ip / 16777216);
+ if (first >= 224 || first == 127 || first == 169)
+ return fail('RESERVED_RANGE_REJECTED', field);
+ let ip = cidr.text;
+ if (match(ip, /^192\.168\./) || match(ip, /^10\.(?!99\.)/) || match(ip, /^172\.(1[6-9]|2[0-9]|3[0-1])\./))
+ return fail('LAN_WAN_OVERLAP_REJECTED', field);
+ return null;
+}
+
+function validate_cidr_list(values, required, field) {
+ values = uniq(values);
+ if (index(values, required) < 0)
+ push(values, required);
+ for (let value in values) {
+ let err = reject_bad_cidr(value, field);
+ if (err)
+ return err;
+ }
+ return { ok: true, values };
+}
+
+function valid_port(value) {
+ value = clean_scalar(value);
+ return value != null && match(value, /^[0-9]+$/) && +value > 0 && +value <= 65535;
+}
+
+function valid_host(value) {
+ value = clean_scalar(value);
+ return value != null && value != '' && length(value) <= 253 && match(value, /^[A-Za-z0-9_.:-]+$/) && !match(value, /^-/) && !match(value, /\.\./);
+}
+
+function valid_wg_key(value) {
+ value = clean_scalar(value);
+ if (value == null || value == '')
+ return true;
+ if (!match(value, /^[A-Za-z0-9+/]{43}=$/))
+ return false;
+ let decoded = b64dec(value);
+ return decoded != null && length(decoded) == 32 && b64enc(decoded) == value;
+}
+
+function section_exists(name, type_name) {
+ return uci.get('network', name) != null && (type_name == null || uci.get('network', name, '.type') == type_name);
+}
+
+function generation() {
+ let s = stat('/etc/config/network');
+ return s == null ? 'missing' : `${s.mtime || 0}:${s.size || 0}`;
+}
+
+function read_config() {
+ let addresses = uniq(uci.get('network', IFACE, 'addresses'));
+ let allowed = uniq(uci.get('network', PEER, 'allowed_ips'));
+ return {
+ interface: IFACE,
+ peer_section: PEER,
+ addresses,
+ private_key_configured: !!uci.get('network', IFACE, 'private_key'),
+ endpoint_host: uci.get('network', PEER, 'endpoint_host') || '',
+ endpoint_port: uci.get('network', PEER, 'endpoint_port') || '',
+ public_key_configured: !!uci.get('network', PEER, 'public_key'),
+ preshared_key_configured: !!uci.get('network', PEER, 'preshared_key'),
+ allowed_ips: allowed,
+ required_address: REQUIRED_ADDRESS,
+ required_allowed_ip: REQUIRED_ALLOWED,
+ generation: generation()
+ };
+}
+
+function normalize_input(input) {
+ input ||= {};
+ let addresses = validate_cidr_list(input.addresses, REQUIRED_ADDRESS, 'addresses');
+ if (!addresses.ok)
+ return addresses;
+ let allowed = validate_cidr_list(input.allowed_ips, REQUIRED_ALLOWED, 'allowed_ips');
+ if (!allowed.ok)
+ return allowed;
+ let endpoint_host = clean_scalar(input.endpoint_host);
+ let endpoint_port = clean_scalar(input.endpoint_port);
+ if (!valid_host(endpoint_host))
+ return fail('INVALID_ENDPOINT_HOST');
+ if (!valid_port(endpoint_port))
+ return fail('INVALID_ENDPOINT_PORT');
+ for (let name in [ 'private_key', 'public_key', 'preshared_key' ])
+ if (!valid_wg_key(input[name]))
+ return fail('INVALID_WG_KEY', name);
+ return { ok: true, values: {
+ addresses: addresses.values,
+ endpoint_host,
+ endpoint_port,
+ allowed_ips: allowed.values,
+ private_key: clean_scalar(input.private_key) || null,
+ public_key: clean_scalar(input.public_key) || null,
+ preshared_key: clean_scalar(input.preshared_key) || null
+ } };
+}
+
+function diff_values(current, next) {
+ let diff = [];
+ function add(field, before, after, secret) {
+ if (json(before) != json(after))
+ push(diff, { field, before: secret ? (before ? '' : '') : before, after: secret ? (after ? '' : '') : after, secret: !!secret });
+ }
+ add('network.wg0.addresses', current.addresses, next.addresses, false);
+ add('network.wgpeer.endpoint_host', current.endpoint_host, next.endpoint_host, false);
+ add('network.wgpeer.endpoint_port', current.endpoint_port, next.endpoint_port, false);
+ add('network.wgpeer.allowed_ips', current.allowed_ips, next.allowed_ips, false);
+ add('network.wg0.private_key', current.private_key_configured, next.private_key != null, true);
+ add('network.wgpeer.public_key', current.public_key_configured, next.public_key != null, true);
+ add('network.wgpeer.preshared_key', current.preshared_key_configured, next.preshared_key != null, true);
+ return diff;
+}
+
+function write_token(state) {
+ ensure_state_dir();
+ writefile(TOKEN_FILE, json(state));
+ chmod(TOKEN_FILE, 0600);
+}
+
+function read_token() {
+ let text = read_text(TOKEN_FILE);
+ return text == null ? null : json(text);
+}
+
+function delete_token() {
+ unlink(TOKEN_FILE);
+}
+
+function set_list(section, option, values) {
+ uci.delete('network', section, option);
+ for (let value in values)
+ uci.add_list('network', section, option, value);
+}
+
+function apply_values(values) {
+ set_list(IFACE, 'addresses', values.addresses);
+ uci.set('network', PEER, 'endpoint_host', values.endpoint_host);
+ uci.set('network', PEER, 'endpoint_port', values.endpoint_port);
+ set_list(PEER, 'allowed_ips', values.allowed_ips);
+ if (values.private_key != null)
+ uci.set('network', IFACE, 'private_key', values.private_key);
+ if (values.public_key != null)
+ uci.set('network', PEER, 'public_key', values.public_key);
+ if (values.preshared_key != null)
+ uci.set('network', PEER, 'preshared_key', values.preshared_key);
+ uci.commit('network');
+}
+
+function snapshot() {
+ ensure_state_dir();
+ let copied = sh(`cp /etc/config/network '${SNAPSHOT}' && chmod 0600 '${SNAPSHOT}'`);
+ return copied.ok;
+}
+
+function restore_snapshot() {
+ if (!access(SNAPSHOT))
+ return false;
+ let restored = sh(`cp '${SNAPSHOT}' /etc/config/network && chmod 0600 /etc/config/network`);
+ return restored.ok;
+}
+
+function reconnect_wg0() {
+ return sh('/sbin/ifdown wg0 >/dev/null 2>&1; /sbin/ifup wg0 >/dev/null 2>&1').ok;
+}
+
+function check_runtime() {
+ let link = sh('/usr/sbin/ip link show dev wg0 >/dev/null 2>&1');
+ let route = sh('/usr/sbin/ip route show 10.99.0.1/32 2>/dev/null');
+ let vxlan = sh('/usr/sbin/ip -d link show dev vxlan0 2>/dev/null');
+ let ok_route = route.ok && index(route.out, 'dev wg0') >= 0;
+ let ok_vxlan = vxlan.ok && index(vxlan.out, 'vxlan id 10') >= 0 && index(vxlan.out, 'dstport 4789') >= 0 && index(vxlan.out, 'nolearning') >= 0 && index(vxlan.out, 'master br-lan') >= 0;
+ return { wg0: link.ok, route: ok_route, vxlan: ok_vxlan, ok: link.ok && ok_route && ok_vxlan };
+}
+
+function status() {
+ let hs = sh('/usr/bin/wg show wg0 latest-handshakes 2>/dev/null');
+ let latest = null, peers = 0;
+ if (hs.ok) {
+ for (let line in split(trim(hs.out), /\n/)) {
+ let fields = split(line, /\t/);
+ if (length(fields) >= 2) {
+ peers++;
+ let t = +fields[1];
+ if (t > (latest || 0))
+ latest = t;
+ }
+ }
+ }
+ let now = timelocal(localtime());
+ let age = latest == null || latest == 0 ? null : max(0, now - latest);
+ let runtime = check_runtime();
+ return { available: hs.ok, peers, handshake_age_seconds: age, runtime, state: !hs.ok ? 'unavailable' : (peers == 0 ? 'no_peer' : (age == null ? 'never_handshaken' : (age <= 180 ? 'healthy' : 'stale'))) };
+}
+
+const methods = {
+ get: { call: function() {
+ uci = cursor();
+ return read_config();
+ } },
+ status: { call: function() {
+ return status();
+ } },
+ prepare: { call: function(input) {
+ uci = cursor();
+ if (!section_exists(IFACE, 'interface') || !section_exists(PEER, 'wireguard_wg0'))
+ return fail('REQUIRED_SECTION_MISSING');
+ let normalized = normalize_input(input || {});
+ if (!normalized.ok)
+ return normalized;
+ let current = read_config();
+ let diff = diff_values(current, normalized.values);
+ let token = trim(readfile('/proc/sys/kernel/random/uuid') || `${uptime()}`);
+ write_token({ token, expires: uptime() + 60, generation: current.generation, values: normalized.values });
+ return { ok: true, token, expires_in: 60, diff, requires_confirm: true };
+ } },
+ apply: { call: function(input) {
+ uci = cursor();
+ input ||= {};
+ let state = read_token();
+ if (state == null || input.token != state.token || input.confirm !== true)
+ return fail('INVALID_TOKEN');
+ if (uptime() > state.expires)
+ return fail('TOKEN_EXPIRED');
+ if (generation() != state.generation)
+ return fail('CONFIG_GENERATION_CHANGED');
+ if (!sh(`mkdir '${LOCK}' 2>/dev/null`).ok)
+ return fail('APPLY_LOCKED');
+ let runtime = null;
+ try {
+ delete_token();
+ if (!snapshot()) {
+ sh(`rmdir '${LOCK}' 2>/dev/null`);
+ return fail('SNAPSHOT_FAILED');
+ }
+ apply_values(state.values);
+ reconnect_wg0();
+ runtime = check_runtime();
+ if (!runtime.ok) {
+ restore_snapshot();
+ reconnect_wg0();
+ sh(`rmdir '${LOCK}' 2>/dev/null`);
+ return { ok: false, code: 'RUNTIME_CHECK_FAILED_ROLLED_BACK', runtime };
+ }
+ sh(`rmdir '${LOCK}' 2>/dev/null`);
+ return { ok: true, runtime };
+ } catch (e) {
+ restore_snapshot();
+ reconnect_wg0();
+ sh(`rmdir '${LOCK}' 2>/dev/null`);
+ return fail('APPLY_EXCEPTION_ROLLED_BACK');
+ }
+ } },
+ rollback: { call: function() {
+ let ok = restore_snapshot();
+ if (ok)
+ reconnect_wg0();
+ return { ok, runtime: check_runtime() };
+ } },
+ reconnect: { call: function() {
+ let ok = reconnect_wg0();
+ return { ok, runtime: check_runtime() };
+ } }
+};
+
+return { 'luci.wgtunnel': methods };
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/Makefile b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/Makefile
new file mode 100644
index 0000000..6af33f6
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/Makefile
@@ -0,0 +1,24 @@
+#
+# Copyright (C) 2008-2019 Jerrykuku
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Argon Theme
+LUCI_DEPENDS:=+wget +jsonfilter
+
+PKG_VERSION:=2.4.3
+PKG_RELEASE:=20250722
+
+LUCI_MINIFY_CSS:=0
+
+define Package/luci-theme-argon/conffiles
+/etc/config/argon
+/www/luci-static/argon/background/
+endef
+
+include $(TOPDIR)/feeds/luci/luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/background/README.md b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/background/README.md
new file mode 100644
index 0000000..a86f27b
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/background/README.md
@@ -0,0 +1,2 @@
+Drop background here!
+accept jpg png gif mp4 webm
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/css/cascade.css b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/css/cascade.css
new file mode 100644
index 0000000..75a69ae
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/css/cascade.css
@@ -0,0 +1,4321 @@
+/**
+ * Argon is a clean HTML5 theme for LuCI. It is based on luci-theme-material and Argon Template
+ *
+ * luci-theme-argon
+ * Copyright 2025 Jerrykuku
+ *
+ * Have a bug? Please create an issue here on GitHub!
+ * https://github.com/jerrykuku/luci-theme-argon/issues
+ *
+ * luci-theme-material:
+ * Copyright 2015 Lutty Yang
+ * https://github.com/LuttyYang/luci-theme-material/
+ *
+ * Agron Theme
+ * https://demos.creative-tim.com/argon-dashboard/index.html
+ *
+ * Login background
+ * https://unsplash.com/
+ * Font generate by Icomoon
+ *
+ * Licensed to the public under the Apache License 2.0
+ */
+:root {
+ --primary: #5e72e4;
+ --dark-primary: #483d8b;
+ --header-color: #fff;
+ --menu-bg-color: #ffffff;
+ --menu-color: #5f6368;
+ --main-menu-color: #202124;
+ --red: #f5365c;
+ --orange: #fb6340;
+ --yellow: #ffd600;
+ --green: #2dce89;
+ --teal: #11cdef;
+ --cyan: #2bffc6;
+ --gray: #8898aa;
+ --gray-dark: #32325d;
+ --light: #ced4da;
+ --lighter: #e9ecef;
+ --success: #2dce89;
+ --info: #11cdef;
+ --warning: #fb6340;
+ --danger: #f5365c;
+ --light: #adb5bd;
+ --dark: #212529;
+ --default: #172b4d;
+ --white: #fff;
+ --darker: black;
+ --background-color: #f4f5f7;
+ --login-form-bg-color: rgba(244, 245, 247, 0.8);
+ --blur-radius: 10px;
+ --blur-opacity: 0.5;
+ --blur-radius-dark: 10px;
+ --blur-opacity-dark: 0.5;
+ --font-family-sans-serif: "Google Sans", "Microsoft Yahei", "WenQuanYi Micro Hei", "sans-serif", "Helvetica Neue", "Helvetica", "Hiragino Sans GB";
+ --dropdown-arrow-icon: url("data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik01LjI5MyA5LjI5M2ExIDEgMCAwIDEgMS40MTQgMEwxMiAxNC41ODZsNS4yOTMtNS4yOTNhMSAxIDAgMSAxIDEuNDE0IDEuNDE0bC02IDZhMSAxIDAgMCAxLTEuNDE0IDBsLTYtNmExIDEgMCAwIDEgMC0xLjQxNCIvPjwvc3ZnPg==");
+}
+/*
+ * Include base and custom css
+ */
+@font-face {
+ font-family: 'Google Sans';
+ src: local('Google Sans'), local('GoogleSans-Regular'), url('../fonts/GoogleSans-Regular.woff2') format('woff2'), url('../fonts/GoogleSans-Regular.woff') format('woff');
+ font-weight: normal;
+ font-style: normal;
+ font-display: swap;
+}
+/* Logo Font */
+@font-face {
+ font-family: 'TypoGraphica';
+ src: local('TypoGraphica'), url('../fonts/TypoGraphica.woff2') format('woff2'), url('../fonts/TypoGraphica.woff') format('woff');
+ font-weight: normal;
+ font-style: normal;
+ font-display: swap;
+}
+/* Icon Font */
+@font-face {
+ font-family: 'argon';
+ src: url('../fonts/argon.woff2') format('woff2'), url('../fonts/argon.woff') format('woff');
+ font-weight: normal;
+ font-style: normal;
+ font-display: swap;
+}
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+html {
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+html,
+body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ font-family: var(--font-family-sans-serif);
+}
+body {
+ background-color: var(--background-color);
+ color: var(--gray-dark);
+ -webkit-tap-highlight-color: transparent;
+ font-size: 0.875rem;
+}
+main {
+ display: block;
+}
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: inherit;
+ font-weight: normal;
+ line-height: 1.1 !important;
+ color: inherit;
+}
+h1 {
+ font-size: 2rem;
+ margin: 0.67em 0;
+ padding-bottom: 0.5rem;
+ border-bottom: thin solid var(--lighter);
+}
+h2 {
+ padding: 1rem 1.25rem;
+ font-size: 1.25rem;
+ font-weight: bold;
+ color: var(--gray-dark);
+ border-radius: 0.25rem;
+ background: var(--white);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
+}
+h3 {
+ margin: 0;
+ padding: 0.8755rem 1.25rem;
+ font-size: 1.1rem;
+ font-weight: bold;
+ line-height: 1;
+ color: var(--gray-dark);
+ border-radius: 0.25rem;
+ background: var(--white);
+ display: block;
+ width: 100%;
+}
+h4 {
+ margin: 0;
+ padding: 0.75rem 1.25rem;
+ font-size: 0.875rem;
+ font-weight: bold;
+ color: var(--gray-dark-400);
+}
+h4 em {
+ padding: 0 0.5rem;
+}
+h5 {
+ margin: 2rem 0 0 0;
+ padding-bottom: 0.5rem;
+ font-size: 1rem;
+}
+a {
+ background-color: transparent;
+}
+abbr {
+ cursor: help;
+ text-decoration: underline;
+ color: var(--primary);
+}
+abbr[title] {
+ border-bottom: none;
+ text-decoration: underline;
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+}
+b,
+strong {
+ font-weight: bolder;
+}
+code,
+kbd,
+samp {
+ font-family: inherit;
+ font-size: inherit;
+ padding: 1px 3px;
+ color: var(--dark);
+ border-radius: 0.5rem;
+ background: var(--lighter);
+}
+small {
+ font-size: 90%;
+ line-height: 1.42857143;
+ white-space: normal;
+}
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+sub {
+ bottom: -0.25em;
+}
+sup {
+ top: -0.5em;
+}
+hr {
+ box-sizing: content-box;
+ height: 0;
+ margin: 1rem 0;
+ overflow: visible;
+ opacity: 0.1;
+ border-color: var(--lighter);
+}
+pre {
+ font-family: monospace, monospace;
+ font-size: 1em;
+ overflow: auto;
+}
+img {
+ border-style: none;
+}
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit;
+ font-size: 100%;
+ line-height: 1.15;
+ margin: 0;
+}
+button,
+input {
+ overflow: visible;
+}
+button,
+select {
+ text-transform: none;
+}
+[type=button],
+[type=reset],
+[type=submit],
+button {
+ -webkit-appearance: button;
+ appearance: button;
+}
+[type=button]::-moz-focus-inner,
+[type=reset]::-moz-focus-inner,
+[type=submit]::-moz-focus-inner,
+button::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+}
+[type=button]:-moz-focusring,
+[type=reset]:-moz-focusring,
+[type=submit]:-moz-focusring,
+button:-moz-focusring {
+ outline: 1px dotted ButtonText;
+}
+[type=checkbox],
+[type=radio] {
+ box-sizing: border-box;
+ padding: 0;
+}
+[type=number]::-webkit-inner-spin-button,
+[type=number]::-webkit-outer-spin-button {
+ height: auto;
+}
+[type=search] {
+ -webkit-appearance: textfield;
+ appearance: textfield;
+ outline-offset: -2px;
+}
+[type=search]::-webkit-search-decoration {
+ -webkit-appearance: none;
+ appearance: none;
+}
+::-webkit-file-upload-button {
+ -webkit-appearance: button;
+ appearance: button;
+ font: inherit;
+}
+input[type="checkbox"] {
+ appearance: none !important;
+ -webkit-appearance: none !important;
+ border: 1px solid var(--primary);
+ width: 1rem !important;
+ height: 1rem !important;
+ padding: 0;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+input[type="checkbox"]:checked {
+ border: 1px solid var(--primary);
+ background-image: url('data:image/svg+xml,%3csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 8 8\'%3e%3cpath fill=\'%23fff\' d=\'M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z\'/%3e%3c/svg%3e') !important;
+ background-color: var(--primary);
+ background-size: 70%;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+select {
+ padding: 0.36rem 0.8rem;
+ color: var(--gray-dark);
+ border: thin solid var(--lighter);
+ background-color: var(--white);
+ background-image: none;
+}
+textarea {
+ padding: 0.25rem;
+ overflow: auto;
+}
+textarea:focus-visible {
+ outline: none;
+ border: 1px solid var(--primary);
+}
+fieldset {
+ padding: 0.35em 0.75em 0.625em;
+}
+legend {
+ box-sizing: border-box;
+ color: inherit;
+ display: table;
+ max-width: 100%;
+ padding: 0;
+ white-space: normal;
+}
+details {
+ display: block;
+}
+summary {
+ display: list-item;
+}
+template {
+ display: none;
+}
+progress {
+ vertical-align: baseline;
+}
+ul {
+ line-height: normal;
+}
+li {
+ list-style-type: none;
+}
+[disabled="disabled"] {
+ pointer-events: none;
+}
+::selection {
+ background-color: var(--primary);
+ color: var(--white);
+}
+::placeholder {
+ color: var(--lighter);
+}
+a:link,
+a:visited,
+a:active {
+ color: var(--primary);
+ text-decoration: none;
+}
+a:hover {
+ text-decoration: underline;
+}
+.pull-right {
+ float: right;
+}
+.pull-left {
+ float: left;
+}
+.nowrap:not(.td) {
+ white-space: nowrap;
+}
+div[style="width:100%;height:300px;border:1px solid #000;background:#fff"] {
+ border: 0 !important;
+}
+/**
+Pure v3.0.0
+Copyright 2013 Yahoo!
+Licensed under the BSD License.
+https://github.com/pure-css/pure/blob/master/LICENSE
+*/
+/**
+normalize.css v | MIT License | https://necolas.github.io/normalize.css/
+Copyright (c) Nicolas Gallagher and Jonathan Neal
+*/
+/** normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+.pure-g {
+ display: flex;
+ flex-flow: row wrap;
+ align-content: flex-start;
+}
+.pure-u {
+ display: inline-block;
+ vertical-align: top;
+}
+.pure-u-1,
+.pure-u-1-1,
+.pure-u-1-12,
+.pure-u-1-2,
+.pure-u-1-24,
+.pure-u-1-3,
+.pure-u-1-4,
+.pure-u-1-5,
+.pure-u-1-6,
+.pure-u-1-8,
+.pure-u-10-24,
+.pure-u-11-12,
+.pure-u-11-24,
+.pure-u-12-24,
+.pure-u-13-24,
+.pure-u-14-24,
+.pure-u-15-24,
+.pure-u-16-24,
+.pure-u-17-24,
+.pure-u-18-24,
+.pure-u-19-24,
+.pure-u-2-24,
+.pure-u-2-3,
+.pure-u-2-5,
+.pure-u-20-24,
+.pure-u-21-24,
+.pure-u-22-24,
+.pure-u-23-24,
+.pure-u-24-24,
+.pure-u-3-24,
+.pure-u-3-4,
+.pure-u-3-5,
+.pure-u-3-8,
+.pure-u-4-24,
+.pure-u-4-5,
+.pure-u-5-12,
+.pure-u-5-24,
+.pure-u-5-5,
+.pure-u-5-6,
+.pure-u-5-8,
+.pure-u-6-24,
+.pure-u-7-12,
+.pure-u-7-24,
+.pure-u-7-8,
+.pure-u-8-24,
+.pure-u-9-24 {
+ display: inline-block;
+ letter-spacing: normal;
+ word-spacing: normal;
+ vertical-align: top;
+ text-rendering: auto;
+}
+.pure-u-1-24 {
+ width: 4.1667%;
+}
+.pure-u-1-12,
+.pure-u-2-24 {
+ width: 8.3333%;
+}
+.pure-u-1-8,
+.pure-u-3-24 {
+ width: 12.5%;
+}
+.pure-u-1-6,
+.pure-u-4-24 {
+ width: 16.6667%;
+}
+.pure-u-1-5 {
+ width: 20%;
+}
+.pure-u-5-24 {
+ width: 20.8333%;
+}
+.pure-u-1-4,
+.pure-u-6-24 {
+ width: 25%;
+}
+.pure-u-7-24 {
+ width: 29.1667%;
+}
+.pure-u-1-3,
+.pure-u-8-24 {
+ width: 33.3333%;
+}
+.pure-u-3-8,
+.pure-u-9-24 {
+ width: 37.5%;
+}
+.pure-u-2-5 {
+ width: 40%;
+}
+.pure-u-10-24,
+.pure-u-5-12 {
+ width: 41.6667%;
+}
+.pure-u-11-24 {
+ width: 45.8333%;
+}
+.pure-u-1-2,
+.pure-u-12-24 {
+ width: 50%;
+}
+.pure-u-13-24 {
+ width: 54.1667%;
+}
+.pure-u-14-24,
+.pure-u-7-12 {
+ width: 58.3333%;
+}
+.pure-u-3-5 {
+ width: 60%;
+}
+.pure-u-15-24,
+.pure-u-5-8 {
+ width: 62.5%;
+}
+.pure-u-16-24,
+.pure-u-2-3 {
+ width: 66.6667%;
+}
+.pure-u-17-24 {
+ width: 70.8333%;
+}
+.pure-u-18-24,
+.pure-u-3-4 {
+ width: 75%;
+}
+.pure-u-19-24 {
+ width: 79.1667%;
+}
+.pure-u-4-5 {
+ width: 80%;
+}
+.pure-u-20-24,
+.pure-u-5-6 {
+ width: 83.3333%;
+}
+.pure-u-21-24,
+.pure-u-7-8 {
+ width: 87.5%;
+}
+.pure-u-11-12,
+.pure-u-22-24 {
+ width: 91.6667%;
+}
+.pure-u-23-24 {
+ width: 95.8333%;
+}
+.pure-u-1,
+.pure-u-1-1,
+.pure-u-24-24,
+.pure-u-5-5 {
+ width: 100%;
+}
+.col-1 {
+ flex: 1 1 30px !important;
+}
+.col-2 {
+ flex: 2 2 60px !important;
+}
+.col-3 {
+ flex: 3 3 90px !important;
+}
+.col-4 {
+ flex: 4 4 120px !important;
+}
+.col-5 {
+ flex: 5 5 150px !important;
+}
+.col-6 {
+ flex: 6 6 180px !important;
+}
+.col-7 {
+ flex: 7 7 210px !important;
+}
+.col-8 {
+ flex: 8 8 240px !important;
+}
+.col-9 {
+ flex: 9 9 270px !important;
+}
+.col-10 {
+ flex: 10 10 300px !important;
+}
+.table {
+ position: relative;
+ display: table;
+}
+.tr {
+ display: table-row;
+}
+.thead {
+ display: table-header-group;
+}
+.tbody {
+ display: table-row-group;
+}
+.tfoot {
+ display: table-footer-group;
+}
+.td,
+.th {
+ line-height: normal;
+ display: table-cell;
+ padding: 0.5em;
+ text-align: center;
+ vertical-align: middle;
+}
+.th {
+ font-weight: bold;
+ white-space: nowrap;
+}
+.tr.placeholder {
+ height: 4em;
+}
+.tr.placeholder > .td {
+ line-height: 3;
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ padding: 0.4rem 0 !important;
+ text-align: center !important;
+ background: inherit;
+}
+.td[width="33%"] {
+ padding: 1.1em 1.5rem;
+}
+.table[width="33%"],
+.th[width="33%"],
+.td[width="33%"] {
+ width: 33%;
+}
+.table[width="100%"],
+.th[width="100%"],
+.td[width="100%"] {
+ width: 100%;
+}
+.btn,
+button,
+select,
+input,
+.cbi-dropdown {
+ line-height: 1.5rem;
+ height: 2.5rem;
+ padding: 0.5rem 0.75rem;
+ color: #8898aa;
+ border: 1px solid #dee2e6;
+ border-radius: 0.25rem;
+ outline: 0;
+ background-image: none;
+ box-shadow: none;
+ transition: box-shadow 0.15s ease;
+}
+select {
+ padding-right: 1.5rem;
+ background-image: url("data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjODg5OGFhIiBkPSJNNS4yOTMgOS4yOTNhMSAxIDAgMCAxIDEuNDE0IDBMMTIgMTQuNTg2bDUuMjkzLTUuMjkzYTEgMSAwIDEgMSAxLjQxNCAxLjQxNGwtNiA2YTEgMSAwIDAgMS0xLjQxNCAwbC02LTZhMSAxIDAgMCAxIDAtMS40MTQiLz48L3N2Zz4=");
+ background-size: 1rem;
+ background-position: right 0.5rem center;
+ background-repeat: no-repeat;
+ appearance: none;
+}
+select:not([multiple="multiple"]):focus,
+input:not(.cbi-button):focus,
+.cbi-dropdown:focus {
+ border-color: var(--primary);
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+}
+.cbi-dropdown,
+select[multiple="multiple"] {
+ height: auto;
+}
+.login-page {
+ display: flex;
+ height: 100%;
+ flex-direction: column;
+}
+.login-page .video {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100vh;
+ width: 100vw;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: var(--darker);
+ overflow: hidden;
+}
+.login-page .video video {
+ width: 100%;
+ height: auto;
+}
+.login-page .volume-control {
+ position: fixed;
+ top: 1rem;
+ right: 1rem;
+ width: 1.5rem;
+ height: 1.5rem;
+ z-index: 5000;
+ cursor: pointer;
+ background: url(../img/volume_high.svg) no-repeat center / contain;
+}
+.login-page .volume-control.mute {
+ background-image: url(../img/volume_off.svg);
+}
+.login-page .main-bg {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100vh;
+ width: 100vw;
+ background: url(../img/blank.png) no-repeat center / cover;
+ transition: all 0.5s ease;
+}
+.login-page .login-container {
+ z-index: 10;
+ margin-left: 5%;
+ display: flex;
+ height: 100vh;
+ width: 26rem;
+ flex-direction: column;
+ background-color: var(--white);
+ backdrop-filter: blur(var(--blur-radius));
+ background-color: rgba(244, 245, 247, var(--blur-opacity));
+ box-shadow: rgba(0, 0, 0, 0.75) 0 0 35px -5px;
+}
+.login-page .login-container .login-form {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100vh;
+ width: 100vw;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ max-width: 26rem;
+}
+.login-page .login-container .login-form .brand {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 50px auto 100px 50px;
+ color: var(--default);
+ text-decoration: none;
+}
+.login-page .login-container .login-form .brand .icon {
+ width: 50px;
+ height: auto;
+ margin-right: 25px;
+}
+.login-page .login-container .login-form .brand .brand-text {
+ margin-right: 45px;
+ font-size: 1.25rem;
+ font-weight: 700;
+ font-family: "TypoGraphica";
+ word-break: break-word;
+}
+.login-page .login-container .login-form .brand:hover {
+ text-decoration: none;
+}
+.login-page .login-container .login-form .form-login {
+ width: 100%;
+ padding: 20px 50px;
+ box-sizing: border-box;
+}
+.login-page .login-container .login-form .form-login .errorbox {
+ padding-bottom: 2rem;
+ text-align: center;
+ color: var(--warning);
+}
+.login-page .login-container .login-form .form-login .input-group {
+ position: relative;
+ margin-bottom: 1.25rem;
+}
+.login-page .login-container .login-form .form-login .input-group::before {
+ font-family: 'argon' !important;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 100;
+ font-size: 1.5rem;
+ color: var(--default);
+}
+.login-page .login-container .login-form .form-login .input-group .border {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ height: 1px;
+ border-bottom: 1px solid var(--primary);
+ transform: scaleX(0);
+ transition: transform 0.3s ease;
+}
+.login-page .login-container .login-form .form-login .input-group input {
+ display: block;
+ width: 100%;
+ margin: 0.825rem 0;
+ padding: 0.5rem 0.75rem 0.5rem 3rem;
+ font-size: 1rem;
+ line-height: 1.5em;
+ color: var(--default);
+ background-color: transparent;
+ background-clip: padding-box;
+ border: 0;
+ border-bottom: 1px solid var(--white);
+ border-radius: 0;
+ outline: none;
+ box-sizing: border-box;
+ box-shadow: 0 3px 2px rgba(233, 236, 239, 0.05);
+ transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
+}
+.login-page .login-container .login-form .form-login .input-group input:focus + .border {
+ transform: scaleX(1);
+}
+.login-page .login-container .login-form .form-login .input-group .cbi-input-password {
+ position: relative;
+ margin-bottom: 2rem;
+}
+.login-page .login-container .login-form .form-login .input-group.user-icon::before {
+ content: "\e971";
+}
+.login-page .login-container .login-form .form-login .input-group.pass-icon::before {
+ content: "\e910";
+}
+.login-page .login-container .login-form .cbi-button-apply {
+ width: 100% !important;
+ min-height: 50px;
+ margin: 30px 0 100px;
+ padding: 10px 0;
+ font-size: 15px;
+ font-weight: 600;
+ color: var(--white);
+ text-align: center;
+ letter-spacing: 0.8rem;
+ background-color: var(--primary) !important;
+ border: none;
+ border-radius: 6px;
+ outline: none;
+ cursor: pointer;
+ box-shadow: rgba(0, 0, 0, 0.1) 0 0 50px 0;
+ transition: all 0.3s ease !important;
+}
+.login-page .login-container .login-form .cbi-button-apply:hover,
+.login-page .login-container .login-form .cbi-button-apply:focus {
+ opacity: 0.9;
+}
+.login-page .login-container footer {
+ position: absolute;
+ bottom: 0;
+ z-index: 10;
+ display: flex;
+ justify-content: space-evenly;
+ width: 100%;
+ margin-top: auto;
+ padding: 0 0 30px;
+ color: var(--gray);
+ text-align: center;
+ line-height: 1.6rem;
+ font-size: 0.75rem;
+}
+.login-page .login-container footer .ftc {
+ position: absolute;
+ bottom: 30px;
+ width: 100%;
+}
+.login-page .login-container footer .luci-link {
+ display: block;
+}
+.main {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: row;
+}
+.main-left {
+ width: 15rem;
+ height: 100%;
+ flex-shrink: 0;
+ z-index: 100;
+ overflow-x: auto;
+ word-break: break-word;
+ background-color: var(--menu-bg-color);
+ box-shadow: rgba(0, 0, 0, 0.75) 0px 0px 15px -5px;
+ transition: all 0.2s;
+}
+.main-left .sidenav-header {
+ padding: 1.5rem 0.5rem;
+ text-align: center;
+}
+.main-left .sidenav-header .brand {
+ display: block;
+ margin: 0 2rem;
+ font-size: 1.8rem;
+ font-family: "TypoGraphica";
+ color: var(--primary);
+ text-decoration: none;
+ text-align: center;
+ cursor: default;
+}
+.main-left .sidenav-header .brand .logo {
+ max-width: 100%;
+ height: auto;
+}
+.main-left .nav {
+ margin-top: 0.5rem;
+}
+.main-left .nav > li > a:first-child {
+ display: block;
+ position: relative;
+ margin: 0.1rem 0.5rem;
+ padding: 0.675rem 0 0.675rem 2.5rem;
+ font-size: 1rem;
+ text-decoration: none;
+ border-radius: 0.25rem;
+ cursor: default;
+ transition: all 0.2s;
+}
+.main-left .nav > li > a:first-child.active {
+ color: #fff;
+ background: var(--primary);
+}
+.main-left .nav > li > a:first-child.active::before {
+ color: #fff !important;
+}
+.main-left .nav > li > a:first-child.active::after {
+ transform: rotate(90deg);
+ color: #fff !important;
+}
+.main-left .nav > li > a:first-child:hover {
+ cursor: pointer;
+ color: #fff;
+ background: var(--primary);
+}
+.main-left .nav > li > a:first-child:hover::before {
+ color: #fff !important;
+}
+.main-left .nav > li > a:first-child::before {
+ position: absolute;
+ left: 0.8rem;
+ padding-top: 3px;
+ font-family: 'argon' !important;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ content: "\e915";
+ color: var(--primary);
+ transition: all 0.3s;
+}
+.main-left .nav > .slide > .menu::before {
+ transition: transform 0.1s ease-in-out;
+}
+.main-left .nav > .slide > .menu.active::before {
+ transition: transform 0.2s ease-in-out;
+}
+.main-left .nav li {
+ padding: 0;
+ cursor: pointer;
+ user-select: none;
+}
+.main-left .nav li a {
+ display: block;
+ color: var(--menu-color);
+}
+.main-left .nav li.slide {
+ padding: 0;
+}
+.main-left .nav li.slide ul {
+ display: none;
+ overflow: hidden;
+}
+.main-left .nav li.slide:hover {
+ background: none;
+}
+.main-left .nav li.slide .slide-menu {
+ margin: 0 0.5rem 0 2.5rem;
+ padding: 0rem 0.5rem;
+}
+.main-left .nav li.slide .slide-menu.active {
+ display: block;
+}
+.main-left .nav li.slide .slide-menu li {
+ position: relative;
+ margin: 0;
+ border-radius: 0.25rem;
+ background: none;
+ list-style: none;
+}
+.main-left .nav li.slide .slide-menu li a {
+ padding: 0.5rem 0rem;
+ text-decoration: none;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.main-left .nav li.slide .slide-menu li::after {
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 0;
+ height: 2px;
+ content: "";
+ background-color: var(--primary);
+ transition: all 0.2s;
+}
+.main-left .nav li.slide .slide-menu li:hover {
+ background: none;
+}
+.main-left .nav li.slide .slide-menu li:hover::after {
+ width: 100%;
+}
+.main-left .nav li.slide .slide-menu .active {
+ background: none;
+ color: var(--menu-color);
+}
+.main-left .nav li.slide .slide-menu .active a {
+ color: var(--menu-color);
+}
+.main-left .nav li.slide .slide-menu .active::after {
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ height: 2px;
+ content: "";
+ background-color: var(--primary);
+ transition: all 0.2s;
+}
+.main-left .nav li.slide .slide-menu .active:hover {
+ background: none;
+}
+.main-left .nav li.slide .slide-menu .active:hover::after {
+ width: 100%;
+}
+.main-left .nav li .menu {
+ display: block;
+ position: relative;
+ margin: 0.1rem 0.5rem;
+ padding: 0.675rem 0 0.675rem 2.5rem;
+ font-size: 1rem;
+ text-decoration: none;
+ border-radius: 0.25rem;
+ cursor: default;
+ transition: all 0.2s;
+}
+.main-left .nav li .menu.active {
+ color: #fff;
+ background: var(--primary);
+}
+.main-left .nav li .menu.active::before {
+ color: #fff !important;
+}
+.main-left .nav li .menu.active::after {
+ transform: rotate(90deg);
+ color: #fff !important;
+}
+.main-left .nav li .menu:hover {
+ cursor: pointer;
+ color: #fff;
+ background: var(--primary);
+}
+.main-left .nav li .menu:hover::before {
+ color: #fff !important;
+}
+.main-left .nav li .menu::before {
+ position: absolute;
+ left: 0.8rem;
+ padding-top: 3px;
+ font-family: 'argon' !important;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ content: "\e915";
+ color: var(--primary);
+ transition: all 0.3s;
+}
+.main-left .nav li .menu::after {
+ position: absolute;
+ right: 0.5rem;
+ top: 0.8rem;
+ font-family: 'argon' !important;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ content: '\e90f';
+ color: #ced4da;
+ text-rendering: auto;
+ transition: all 0.3s;
+}
+.main-left .nav li .menu[data-title="Status"]:before {
+ content: "\e906";
+ color: var(--primary);
+}
+.main-left .nav li .menu[data-title="System"]:before {
+ content: "\e90a";
+ color: #fb6340;
+}
+.main-left .nav li .menu[data-title="Services"]:before {
+ content: "\e909";
+ color: #11cdef;
+}
+.main-left .nav li .menu[data-title="NAS"]:before {
+ content: "\e90c";
+ color: #f3a4b5;
+}
+.main-left .nav li .menu[data-title="VPN"]:before {
+ content: "\e90b";
+ color: #8965e0;
+}
+.main-left .nav li .menu[data-title="Network"]:before {
+ content: "\e908";
+ color: #8965e0;
+}
+.main-left .nav li .menu[data-title="Bandwidth_Monitor"]:before {
+ content: "\e90d";
+ color: #2dce89;
+}
+.main-left .nav li .menu[data-title="Docker"]:before {
+ content: "\e911";
+ color: #6699ff;
+}
+.main-left .nav li .menu[data-title="Statistics"]:before {
+ content: "\e913";
+ color: #8965e0;
+}
+.main-left .nav li .menu[data-title="Control"]:before {
+ content: "\e912";
+ color: var(--primary);
+}
+.main-left .nav li .menu[data-title="Asterisk"]:before {
+ content: "\e914";
+ color: #fb6340;
+}
+.main-left .nav li a[data-title="Log_out"]::before,
+.main-left .nav li .food[data-title="Log_out"]::before {
+ content: "\e907";
+ color: #adb5bd;
+}
+.main-left[style*="overflow: hidden"] > .nav > .slide > .menu::before {
+ display: none;
+}
+.main-left::-webkit-scrollbar {
+ width: 5px;
+ height: 1px;
+}
+.main-left::-webkit-scrollbar-thumb {
+ background-color: #f6f9fc;
+}
+.main-left::-webkit-scrollbar-track {
+ background-color: #fff;
+}
+.main-right {
+ height: 100%;
+ flex-grow: 1;
+ overflow-x: hidden;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ transition: all 0.2s;
+}
+.main-right > #maincontent {
+ position: relative;
+ z-index: 50;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+.main-right > #maincontent > .container {
+ margin: 0 1.25rem 1rem 1.25rem;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+.main-right > #maincontent .Dashboard {
+ color: var(--gray-dark) !important;
+}
+.main-right > #maincontent .Dashboard h3 {
+ color: var(--gray-dark);
+}
+.main-right > #maincontent .Dashboard p {
+ margin-top: 3px;
+ margin-bottom: 3px;
+}
+.main-right > #maincontent .Dashboard hr {
+ border-top: 1px solid #000000;
+}
+.main-right > #maincontent .Dashboard .dashboard-bg {
+ background-color: #fff;
+}
+.main-right > #maincontent .Dashboard .settings-info {
+ padding-top: 1em;
+ padding-bottom: 1em;
+}
+.main-right > #maincontent .Dashboard .settings-info p span:nth-child(2) {
+ max-height: 18.5px;
+ top: 4px;
+}
+.main-right > #maincontent .Dashboard .settings-info .label {
+ font-size: 0.7rem;
+ padding: 0.2rem 0.6rem;
+}
+header {
+ position: relative;
+ width: 100%;
+ padding: 0;
+ color: var(--header-color);
+}
+header.bg-primary {
+ background-color: var(--primary) !important;
+}
+header::after {
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 2rem;
+ background-color: var(--primary) !important;
+}
+header .fill {
+ padding: 0.8rem 0;
+ display: flex;
+ border-bottom: 0px solid rgba(255, 255, 255, 0.08) !important;
+}
+header .fill .container {
+ width: 100%;
+ height: 2rem;
+ padding: 0 1.25rem;
+ display: flex;
+ align-items: center;
+}
+header .fill .container .flex1 {
+ flex: 1;
+}
+header .fill .container .flex1 .showSide {
+ display: none;
+ font-size: 1.4rem;
+ color: #fff;
+}
+header .fill .container .flex1 .showSide:hover {
+ text-decoration: none;
+}
+header .fill .container .flex1 .brand {
+ display: none;
+ padding-left: 1rem;
+ font-size: 1.5rem;
+ font-family: "TypoGraphica";
+ color: #fff;
+ text-decoration: none;
+ cursor: default;
+ vertical-align: text-bottom;
+}
+header .fill .container .pull-right {
+ margin-top: 0rem;
+ float: right;
+ display: flex;
+}
+header .fill .status span {
+ display: inline-block;
+ margin: 0 0.25rem;
+ padding: 0.3rem 0.8rem;
+ font-size: 0.875rem;
+ font-weight: bold;
+ white-space: nowrap;
+ text-decoration: none;
+ text-transform: uppercase;
+ text-shadow: none;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: all 0.3s;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
+}
+header .fill .status span:last-child {
+ margin-right: 0;
+}
+header .fill .status span[data-indicator="poll-status"] {
+ color: #fff;
+}
+header .fill .status span[data-style="active"] {
+ background-color: var(--green);
+}
+header .fill .status span[data-style="inactive"] {
+ color: #ffffff !important;
+ background-color: #32325d;
+}
+footer {
+ padding: 1rem;
+ font-size: 0.875rem;
+ color: #aaa;
+ text-align: right;
+ white-space: nowrap;
+ overflow: hidden;
+}
+footer > a {
+ color: #aaa;
+ text-decoration: none;
+}
+#view {
+ border-radius: 0.25rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+#view > .spinning {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translateX(-50%) translateY(-50%);
+ padding: 1rem;
+ border-radius: 0.5rem;
+ background: #ffffff;
+ box-shadow: 0 0 1rem 0 rgba(136, 152, 170, 0.15);
+}
+#view > div:first-child {
+ display: flex;
+ flex-direction: column;
+ gap: 0.875rem;
+}
+#xhr_poll_status {
+ display: flex;
+ margin-left: 0.5rem;
+}
+#xhr_poll_status * {
+ color: #fff;
+}
+.danger {
+ background-color: var(--danger) !important;
+}
+.warning {
+ background-color: var(--warning) !important;
+}
+.success {
+ background-color: var(--success) !important;
+}
+.notice {
+ background-color: var(--info) !important;
+ color: #fff;
+}
+.error {
+ color: var(--danger);
+}
+.alert,
+.alert-message {
+ position: fixed;
+ width: 20rem;
+ z-index: 9000;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ font-weight: bold;
+ padding: 1rem 1.25rem;
+ border: 0;
+ border-radius: 0.25rem !important;
+ background-color: #fff;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
+ text-shadow: none;
+}
+.alert.error,
+.alert-message.error {
+ background-color: var(--warning);
+}
+.alert h4,
+.alert-message h4 {
+ padding: 0.25rem 0rem;
+ border-radius: 0.25rem;
+}
+.alert .btn,
+.alert-message .btn {
+ height: auto;
+}
+.modal.alert-message {
+ position: static;
+ width: 90%;
+ min-width: 270px;
+ max-width: 600px;
+ left: auto;
+ top: auto;
+ transform: none;
+ margin: 5em auto;
+}
+.alert-message > h4 {
+ font-size: 110%;
+ font-weight: bold;
+}
+.alert-message > * {
+ margin: 0.5rem 0;
+}
+.alert-message .btn {
+ padding: 0.3rem 0.6rem;
+}
+.container .alert,
+.container .alert-message {
+ margin-left: 0;
+ margin-right: 0;
+ margin-top: 0rem;
+ position: relative;
+ top: 0;
+ transform: translate(-50%, 0);
+ width: 100%;
+}
+#mwan3-service-status > .alert-message {
+ left: 0.5rem;
+ transform: none;
+}
+.lg {
+ margin: 0;
+ padding: 0 !important;
+}
+.cbi-section,
+.cbi-section-error,
+#iptables,
+.Firewall form,
+#cbi-network > .cbi-section-node,
+#cbi-wireless > .cbi-section-node,
+#cbi-wireless > #wifi_assoclist_table,
+[data-tab-title],
+[data-page^="admin-system-admin"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear),
+[data-page="admin-system-opkg"] #maincontent > .container {
+ font-family: inherit;
+ font-weight: normal;
+ font-style: normal;
+ line-height: normal;
+ min-width: inherit;
+ padding: 0;
+ border: 0;
+ border-radius: 0.25rem;
+ background-color: #fff;
+ box-shadow: 0 0 1rem 0 rgba(136, 152, 170, 0.15);
+}
+.cbi-section:last-child,
+.cbi-section-error:last-child,
+#iptables:last-child,
+.Firewall form:last-child,
+#cbi-network > .cbi-section-node:last-child,
+#cbi-wireless > .cbi-section-node:last-child,
+#cbi-wireless > #wifi_assoclist_table:last-child,
+[data-tab-title]:last-child,
+[data-page^="admin-system-admin"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear):last-child,
+[data-page="admin-system-opkg"] #maincontent > .container:last-child {
+ margin: 0;
+ border: 0;
+}
+.cbi-modal .cbi-section,
+.cbi-section .cbi-section {
+ padding: 0;
+ box-shadow: none;
+}
+.cbi-modal .cbi-tabmenu {
+ margin-left: 0;
+}
+.cbi-map {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+.cbi-map > .cbi-tabmenu + div {
+ margin-top: -0.4375rem;
+}
+.cbi-map-descr {
+ font-size: small;
+ line-height: 1.5;
+ padding: 0 1.25rem;
+}
+.cbi-section > .cbi-section-descr {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+}
+.cbi-section > .cbi-section-descr:empty {
+ display: none;
+}
+.cbi-section-descr:not(:empty) {
+ font-size: small;
+ line-height: 1.5;
+ padding: 0rem 1rem;
+}
+.cbi-section-descr:empty {
+ display: none !important;
+}
+.cbi-map-descr + fieldset {
+ margin-top: 1rem;
+}
+.cbi-map-descr > abbr {
+ cursor: help;
+ text-decoration: underline;
+}
+.cbi-section > legend {
+ display: none !important;
+}
+fieldset > fieldset,
+.cbi-section > .cbi-section {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ box-shadow: none;
+}
+.cbi-section > h3:first-child,
+.panel-title {
+ font-size: 1.1rem;
+ line-height: 1;
+ display: block;
+ width: 100%;
+ margin: 0;
+ margin-bottom: 0;
+ padding: 0.8755rem 1.25rem;
+ color: #32325d;
+ color: var(--gray-dark);
+}
+.cbi-section > h3:first-child,
+.cbi-section > h4:first-child,
+.cbi-section > p:first-child,
+[data-tab-title] > h3:first-child,
+[data-tab-title] > h4:first-child,
+[data-tab-title] > p:first-child {
+ padding: 1rem 1.25rem;
+}
+.cbi-section p {
+ padding: 1rem;
+}
+.cbi-tblsection {
+ overflow-x: auto;
+}
+table {
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+table,
+.table {
+ overflow-y: hidden;
+ width: 100%;
+ font-size: 0.75rem;
+}
+.table .table-titles th {
+ background-color: var(--lighter);
+}
+table > tbody > tr > td,
+table > tbody > tr > th,
+table > tfoot > tr > td,
+table > tfoot > tr > th,
+table > thead > tr > td,
+table > thead > tr > th,
+.table > .tbody > .tr > .td,
+.table > .tbody > .tr > .th,
+.table > .tfoot > .tr > .td,
+.table > .tfoot > .tr > .th,
+.table > .thead > .tr > .td,
+.table > .thead > .tr > .th,
+.table > .tr > .td.cbi-value-field,
+.table > .tr > .th.cbi-section-table-cell {
+ padding: 0.5rem;
+}
+.container > .cbi-section:first-of-type > .table[width="100%"] > .tr > .td {
+ padding: 0.6rem;
+}
+.cbi-section-table-cell {
+ line-height: 1.1;
+ align-self: flex-end;
+ flex: 1 1 auto;
+}
+tr > td,
+tr > th,
+.tr > .td,
+.tr > .th,
+.cbi-section-table-row::before,
+#cbi-wireless > #wifi_assoclist_table > .tr:nth-child(2) {
+ border-top: thin solid #ddd;
+ padding: 1.1em 1.25rem;
+}
+#cbi-wireless .td,
+.table[width="100%"] > .tr:first-child > .td,
+[data-page="admin-network-diagnostics"] .tr > .td,
+.tr.table-titles > .th,
+.tr.cbi-section-table-titles > .th {
+ border-top: 0 !important;
+ background-color: #f6f9fc;
+ padding: 1.1em 1.25rem;
+ line-height: 1.3rem;
+}
+#cbi-network .tr:first-child > .td {
+ border-top: 0;
+}
+.table[width="100%"] > .tr:first-child > .td {
+ margin: auto 0;
+}
+.cbi-section-table-row {
+ margin-bottom: 1rem;
+ text-align: center !important;
+ background: #f4f4f4;
+}
+.cbi-section-table-row:last-child {
+ margin-bottom: 0;
+}
+.cbi-section-table-row > .cbi-value-field .cbi-dropdown,
+.cbi-section-table-row > .cbi-value-field .cbi-input-select,
+.cbi-section-table-row > .cbi-value-field .cbi-input-text,
+.cbi-section-table-row > .cbi-value-field .cbi-input-password {
+ width: 100%;
+}
+.cbi-section-table-row > .cbi-value-field .cbi-input-text,
+.cbi-section-table-row > .cbi-value-field .cbi-input-password {
+ min-width: 80px;
+}
+.cbi-section-table-row > .cbi-value-field [data-dynlist] > input,
+.cbi-section-table-row > .cbi-value-field input.cbi-input-password {
+ width: calc(100% - 1.5rem);
+}
+.cbi-section-table-row .td {
+ text-align: center !important;
+}
+.cbi-section-table-row .td .cbi-checkbox input[type="checkbox"] {
+ margin: 0;
+}
+.control-group {
+ display: inline-flex;
+ width: 100%;
+ flex-wrap: wrap;
+ gap: 1rem;
+}
+.control-group input {
+ border-right-width: 0;
+ margin-right: 0;
+}
+.control-group input + button {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+ margin-left: 0;
+ border-left-width: 0;
+}
+.control-group:has(> input:first-child + .cbi-button) {
+ gap: 0 !important;
+}
+.control-group:has(> input:first-child + .cbi-button) input {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ width: 15.5rem;
+ min-width: 15.5rem;
+}
+.control-group > * {
+ vertical-align: middle;
+}
+div > table > tbody > tr:nth-of-type(2n),
+div > .table > .tr:nth-of-type(2n) {
+ background-color: #f9f9f9;
+}
+/* fix multiple table */
+table table,
+.table .table,
+.cbi-value-field table,
+.cbi-value-field .table,
+td > table > tbody > tr > td,
+.td > .table > .tbody > .tr > .td,
+.cbi-value-field > table > tbody > tr > td,
+.cbi-value-field > .table > .tbody > .tr > .td {
+ border: 0;
+}
+/* button style */
+.btn,
+.cbi-button,
+.item::after {
+ font-size: 0.875rem;
+ display: inline-block;
+ width: auto !important;
+ padding: 0.5rem 0.75rem;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ transition: all 0.2s ease-in-out;
+ text-align: center;
+ vertical-align: middle;
+ white-space: nowrap;
+ text-decoration: none;
+ border: 0;
+ border-radius: 0.25rem;
+ background-color: #f0f0f0;
+ background-image: none;
+ appearance: none;
+ -ms-touch-action: manipulation;
+ touch-action: manipulation;
+}
+.btn:not(button) ul:not(.dropdown) li {
+ padding: 0;
+}
+.cbi-button-up,
+.cbi-button-down {
+ display: inline-block;
+ min-width: 0;
+ padding: 0.2rem 1rem;
+ font-size: 0;
+ color: transparent !important;
+ background: url(../icon/arrow.svg) no-repeat center;
+ background-size: 12px 20px;
+}
+.cbi-button-up {
+ transform: scaleY(-1);
+}
+.cbi-button:not(select) {
+ appearance: none !important;
+}
+.btn:hover,
+.btn:focus,
+.btn:active,
+.cbi-button:hover,
+.cbi-button:focus,
+.cbi-button:active,
+.item:hover::after,
+.item:focus::after,
+.item:active::after,
+.cbi-page-actions .cbi-button-apply + .cbi-button-save:hover,
+.cbi-page-actions .cbi-button-apply + .cbi-button-save:focus,
+.cbi-page-actions .cbi-button-apply + .cbi-button-save:active {
+ text-decoration: none;
+ outline: 0;
+}
+.btn:hover,
+.btn:focus,
+.cbi-button:hover,
+.cbi-button:focus,
+.item:hover::after,
+.item:focus::after {
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+}
+.btn:active,
+.cbi-button:active,
+.item:active::after {
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+}
+.cbi-button-up:hover,
+.cbi-button-up:focus {
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+}
+.cbi-button-up:active {
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+}
+.btn:disabled,
+.cbi-button:disabled {
+ cursor: not-allowed;
+ pointer-events: none;
+ opacity: 0.5;
+ box-shadow: none;
+}
+/* gray */
+.alert-message [class="btn"],
+.modal div[class="btn"],
+.cbi-button-find,
+.cbi-button-link,
+.cbi-button-up,
+.cbi-button-down,
+.cbi-button-neutral,
+.cbi-button[name="zero"],
+.cbi-button[name="restart"],
+.cbi-button[onclick="hide_empty(this)"] {
+ color: #fff;
+ border: thin solid #8898aa;
+ background-color: #8898aa;
+}
+/* dark blue */
+.btn.primary,
+.cbi-page-actions .cbi-button-save,
+.cbi-page-actions .cbi-button-apply + .cbi-button-save,
+.cbi-button-add,
+.cbi-button-save,
+.cbi-button-positive,
+.cbi-button-link,
+.cbi-button[value="Enable"],
+.cbi-button[value="Scan"],
+.cbi-button[value^="Back"],
+.cbi-button-neutral[onclick="handleConfig(event)"] {
+ font-weight: normal;
+ color: #fff !important;
+ border: thin solid #5e72e4;
+ border: thin solid var(--primary);
+ background-color: #5e72e4;
+ background-color: var(--primary);
+}
+/* light blue */
+.cbi-page-actions .cbi-button-apply,
+.cbi-section-actions .cbi-button-edit,
+.cbi-button-edit,
+.cbi-button-apply,
+.cbi-button-reload,
+.cbi-button-action,
+.cbi-button[value="Submit"],
+.cbi-button[value="Upload"],
+.cbi-button[value$="Apply"],
+.cbi-button[onclick="addKey(event)"] {
+ font-weight: normal;
+ color: #fff !important;
+ border: thin solid #5e72e4;
+ border: thin solid var(--primary);
+ background-color: #5e72e4;
+ background-color: var(--primary);
+}
+/* red */
+.btn.danger,
+.cbi-section-remove > .cbi-button,
+.cbi-button-remove,
+.cbi-button-reset,
+.cbi-button-negative,
+.cbi-button[value="Stop"],
+.cbi-button[value="Kill"],
+.cbi-button[onclick="reboot(this)"],
+.cbi-button-neutral[value="Restart"] {
+ font-weight: normal;
+ color: #fff;
+ border: thin solid #f5365c;
+ border: thin solid var(--red);
+ background-color: #f5365c;
+ background-color: var(--red);
+}
+/* yellow */
+.btn[value="Dismiss"],
+.cbi-button[value="Terminate"],
+.cbi-button[value="Reset"],
+.cbi-button[value="Disabled"],
+.cbi-button[onclick^="iface_reconnect"],
+.cbi-button[onclick="handleReset(event)"],
+.cbi-button-neutral[value="Disable"] {
+ font-weight: normal;
+ color: var(--white);
+ border: thin solid #eea236;
+ background-color: #f0ad4e;
+}
+/* green */
+.cbi-button-success,
+.cbi-button-download {
+ font-weight: normal;
+ color: var(--white);
+ border: thin solid #4cae4c;
+ background-color: #5cb85c;
+}
+.cbi-page-actions .cbi-button-link:first-child {
+ float: left;
+}
+.cbi-page-actions .cbi-dropdown .open {
+ color: var(--white);
+}
+.cbi-page-actions .cbi-dropdown .more {
+ display: block;
+ width: 1px;
+ height: 100%;
+ overflow: hidden;
+ text-indent: 5000px;
+ background-color: var(--white);
+}
+.a-to-btn {
+ text-decoration: none;
+}
+.cbi-value-field .cbi-button-add {
+ font-weight: bold;
+ padding: 1px 6px;
+ display: inline-block;
+ align-items: center;
+}
+.tabs {
+ padding: 0 1rem;
+ background-color: #FFFFFF;
+ border-radius: 0.25rem;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
+ white-space: nowrap;
+ overflow-x: auto;
+}
+.tabs::-webkit-scrollbar {
+ width: 1px;
+ height: 5px;
+}
+.tabs::-webkit-scrollbar-thumb {
+ background-color: #f6f9fc;
+}
+.tabs::-webkit-scrollbar-track {
+ background-color: #fff;
+}
+.tabs li[class~="active"],
+.tabs li:hover {
+ cursor: pointer;
+ border-bottom: 0.18751rem solid #5e72e4;
+ border-bottom: 0.18751rem solid var(--primary);
+ color: #5e72e4;
+ color: var(--primary);
+ background-color: #e4e9ee;
+ margin-bottom: 0;
+ border-radius: 0;
+}
+.tabs li[class~="active"] a,
+.tabs li:hover a {
+ color: #5e72e4;
+ color: var(--primary);
+}
+.tabs li {
+ font-size: 0.875rem;
+ display: inline-block;
+ padding: 0.875rem 0rem;
+ border-bottom: 0.18751rem solid rgba(0, 0, 0, 0);
+ margin: 0;
+ transition: all 0.2s;
+}
+.tabs li a {
+ text-decoration: none;
+ color: #404040;
+ padding: 0.5rem 0.8rem;
+}
+.tabs li:hover {
+ border-bottom: 0.18751rem solid #5e72e4;
+ border-bottom: 0.18751rem solid var(--primary);
+}
+.cbi-tabmenu {
+ color: white;
+ padding: 0.5rem 1rem 0 1rem;
+ white-space: nowrap;
+ overflow-x: auto;
+}
+.cbi-tabmenu::-webkit-scrollbar {
+ width: 1px;
+ height: 5px;
+}
+.cbi-tabmenu::-webkit-scrollbar-thumb {
+ background-color: #f6f9fc;
+}
+.cbi-tabmenu::-webkit-scrollbar-track {
+ background-color: #fff;
+}
+.cbi-tabmenu li {
+ background: #dce3e9;
+ display: inline-block;
+ font-size: 0.875rem;
+ border-top-left-radius: 0.25rem;
+ border-top-right-radius: 0.25rem;
+ padding: 0.5rem 0rem;
+ border-bottom: 0.18751rem solid rgba(0, 0, 0, 0);
+ margin: 0 0.2rem;
+ position: relative;
+}
+.cbi-tabmenu li a {
+ text-decoration: none;
+ color: #404040;
+ padding: 0.5rem 0.8rem;
+}
+.cbi-tabmenu li:hover {
+ cursor: pointer;
+ border-bottom: 0.18751rem solid #5e72e4;
+ border-bottom: 0.18751rem solid var(--primary);
+ color: #5e72e4;
+ color: var(--primary);
+ background-color: #e4e9ee;
+ margin-bottom: 0;
+}
+.cbi-tabmenu li:hover a {
+ color: #525f7f;
+}
+.cbi-tabmenu li[class~="cbi-tab"] {
+ border-bottom: 0.18751rem solid #5e72e4;
+ border-bottom: 0.18751rem solid var(--primary);
+ color: #5e72e4;
+ color: var(--primary);
+ background-color: #e4e9ee;
+ margin-bottom: 0;
+}
+.cbi-tabmenu li[class~="cbi-tab"] a {
+ color: #5e72e4;
+ color: var(--primary);
+}
+.cbi-tab-descr {
+ padding: 0 1.25rem;
+}
+.cbi-section-node {
+ display: flex;
+ flex-direction: column;
+ gap: 0.875rem;
+}
+.cbi-section-node > [id^="cbi-config-"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.875rem;
+}
+.cbi-section-node > [id^="cbi-config-"]:first-child {
+ padding-top: 1rem;
+}
+.cbi-section-node > [id^="cbi-config-"]:last-child {
+ padding-bottom: 1rem;
+}
+.cbi-section .cbi-section-remove:nth-of-type(2n),
+.container > .cbi-section .cbi-section-node:nth-of-type(2n) {
+ background-color: #f9f9f9;
+}
+[data-tab-title] {
+ overflow: hidden;
+ height: 0;
+ opacity: 0;
+ margin: 0;
+ padding: 1rem 0;
+ display: none !important;
+}
+[data-tab-title] p {
+ margin-left: 1rem;
+ margin-bottom: 1rem;
+}
+[data-tab-active="true"] {
+ overflow: visible;
+ height: auto;
+ opacity: 1;
+ display: inherit !important;
+ transition: opacity 0.25s ease-in;
+ margin: inherit !important;
+}
+.cbi-section[id] .cbi-section-remove:nth-of-type(4n+3),
+.cbi-section[id] .cbi-section-node:nth-of-type(4n+4) {
+ background-color: #f9f9f9;
+}
+.cbi-section-node-tabbed {
+ margin-top: 0;
+ border: 0 solid #d4d4d4;
+ border-radius: 0.25rem;
+}
+.cbi-section-node-tabbed > div {
+ display: flex;
+ flex-direction: column;
+ gap: 0.875rem;
+}
+.cbi-tabcontainer > .cbi-value:nth-of-type(2n) {
+ background-color: #f9f9f9;
+}
+.cbi-value-field {
+ display: table-cell;
+}
+.cbi-value-field div + br {
+ display: none;
+}
+.cbi-value-description {
+ line-height: 1.25;
+ display: table-cell;
+ padding: 0.5rem 0;
+ opacity: 0.5;
+ font-size: 0.75rem;
+}
+.cbi-value-description abbr {
+ color: #32325d;
+ color: var(--gray-dark);
+}
+.cbi-value-description:empty {
+ display: none;
+}
+.cbi-value-title {
+ display: table-cell;
+ float: left;
+ width: 23rem;
+ padding-right: 2rem;
+ text-align: right;
+ word-wrap: break-word;
+}
+.cbi-value {
+ display: inline-block;
+ width: 100%;
+ padding: 0 1rem;
+ line-height: 2.4rem;
+}
+.cbi-value ul {
+ line-height: 1.25;
+}
+.cbi-value-field.cbi-dropdown-open .cbi-dropdown {
+ border-color: var(--primary);
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+}
+.cbi-value-field .cbi-dropdown,
+.cbi-value-field .cbi-input-select,
+.cbi-value input[type="text"],
+.cbi-value input[type="password"],
+.cbi-value textarea {
+ min-width: 18rem;
+}
+#cbi-firewall-zone .cbi-input-select,
+#cbi-network-switch_vlan .cbi-input-select {
+ min-width: 11rem;
+}
+#cbi-network-switch_vlan .cbi-input-text {
+ max-width: 3rem;
+}
+.cbi-input-file {
+ appearance: auto !important;
+}
+.cbi-input-file::-webkit-file-upload-button {
+ display: inline-block !important;
+}
+.cbi-input-invalid {
+ color: #f5365c !important;
+ border-color: #f5365c !important;
+}
+.cbi-section-error {
+ font-weight: bold;
+ line-height: 1.42857143;
+ margin: 18px;
+ padding: 6px;
+ border: thin solid #f5365c;
+ border-radius: 3px;
+ background-color: #fce6e6;
+}
+.cbi-section-error ul {
+ margin: 0 0 0 20px;
+}
+.cbi-section-error ul li {
+ font-weight: bold;
+ color: #f5365c;
+}
+.td[data-title]::before {
+ font-weight: bold;
+ display: none;
+ padding: 0.25rem 0;
+ content: attr(data-title) ":\20";
+ text-align: left;
+ white-space: nowrap;
+}
+.tr.placeholder .td[data-title]::before {
+ display: none;
+}
+.tr[data-title]::before,
+.tr.cbi-section-table-titles.named::before {
+ font-weight: bold;
+ display: table-cell;
+ align-self: center;
+ flex: 1 1 5%;
+ padding: 0.25rem;
+ content: attr(data-title) "\20";
+ text-align: center;
+ vertical-align: middle;
+ white-space: normal;
+ word-wrap: break-word;
+ background-color: #f6f9fc;
+}
+.cbi-rowstyle-1 {
+ background-color: #f9f9f9;
+}
+.cbi-rowstyle-2 {
+ background-color: #eee;
+}
+.cbi-rowstyle-2 .cbi-button-up,
+.cbi-rowstyle-2 .cbi-button-down,
+body:not(.Interfaces) .cbi-rowstyle-2:first-child {
+ background-color: #fff !important;
+}
+.cbi-section-table .cbi-section-table-titles .cbi-section-table-cell {
+ width: auto !important;
+}
+.td.cbi-section-actions {
+ text-align: right !important;
+ vertical-align: middle;
+}
+.td.cbi-section-actions > * {
+ display: inline-flex;
+ gap: 1rem;
+}
+.td.cbi-section-actions > * > *,
+.td.cbi-section-actions > * > form > * {
+ display: flex;
+ align-items: center;
+}
+.td.cbi-section-actions > * > form {
+ display: inline-flex;
+ margin: 0;
+}
+.td .cbi-checkbox {
+ justify-content: center;
+}
+.cbi-checkbox {
+ display: flex;
+ align-items: center;
+ height: 2.5rem;
+}
+/* lists */
+.cbi-dynlist {
+ line-height: 1.3;
+ flex-direction: column;
+ min-height: 30px;
+ cursor: text;
+ gap: 0.875rem;
+}
+.cbi-dynlist > .item {
+ display: inline-flex;
+ flex-wrap: nowrap;
+ position: relative;
+ max-width: 25rem;
+ pointer-events: none;
+ color: #8898aa;
+ outline: 0;
+}
+.cbi-dynlist > .item::after {
+ content: "\00D7";
+ pointer-events: auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 2.5rem !important;
+ height: 2.5rem;
+ margin: 0;
+ font-weight: normal;
+ font-size: 1rem;
+ line-height: 1.5rem;
+ color: #fff;
+ border: 1px solid #f5365c;
+ border-radius: 0 0.25rem 0.25rem 0;
+ outline: 0;
+ background-color: var(--red);
+ background-image: none;
+ box-shadow: none;
+ box-sizing: border-box;
+}
+.cbi-dynlist > .item > span {
+ display: block;
+ padding: 0.5rem 0.75rem;
+ min-width: 15.5rem;
+ width: 15.5rem;
+ height: 2.5rem;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ user-select: text;
+ white-space: nowrap;
+ word-break: break-word;
+ line-height: 1.5em;
+ color: #8898aa;
+ border: 1px solid #dee2e6;
+ border-radius: 0.25rem 0 0 0.25rem;
+ outline: 0;
+ background-image: none;
+ box-shadow: none;
+ box-sizing: border-box;
+ transition: box-shadow 0.15s ease;
+}
+.cbi-dynlist > .add-item {
+ display: inline-flex;
+ align-items: center;
+ width: 100%;
+ min-width: 15.5rem;
+ flex-wrap: nowrap;
+}
+.cbi-dynlist > .add-item input {
+ display: block;
+ padding: 0.5rem 0.75rem;
+ box-sizing: border-box;
+ min-width: 15.5rem;
+ width: 15.5rem;
+ transition: box-shadow 0.15s ease;
+ white-space: nowrap;
+ word-break: break-word;
+ font-size: 0.875rem;
+ line-height: 1.5rem;
+ color: #8898aa;
+ border: 1px solid #dee2e6;
+ border-radius: 0.25rem 0 0 0.25rem;
+ border-right-width: 0;
+ outline: 0;
+ background-image: none;
+ box-shadow: none;
+}
+.cbi-dynlist > .add-item .cbi-button {
+ display: flex;
+ width: auto !important;
+ padding-left: 0.8rem;
+ padding-right: 0.8rem;
+ margin-left: 0;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.875rem;
+ line-height: 1.5rem;
+ outline: 0;
+ background-image: none;
+ background-color: var(--gray);
+ box-shadow: none;
+ color: var(--white);
+ border-color: var(--gray);
+ border-radius: 0.25rem;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.cbi-dynlist > .add-item .cbi-button-add {
+ width: 2.5rem !important;
+ padding: 0.5rem 0 !important;
+ font-weight: normal;
+ font-size: 1.2rem;
+ color: #fff;
+ background-color: var(--primary);
+ border: 1px solid var(--primary);
+}
+.cbi-dynlist > .add-item:not([ondrop]) > input {
+ overflow: hidden;
+ min-width: 15.5rem;
+ width: 15.5rem;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.cbi-dynlist > .item.drag-over,
+.cbi-dynlist > .add-item:has(.drag-over) {
+ border-top: 1px solid var(--red);
+ margin-top: -1px;
+}
+.cbi-dynlist[name="sshkeys"] > .item {
+ max-width: none;
+}
+.cbi-dynlist > .cbi-dynlist > .add-item[ondrop] > input {
+ min-width: 13rem;
+}
+.cbi-dynlist,
+.cbi-dropdown {
+ position: relative;
+ display: inline-flex;
+ padding-right: 0.25rem;
+ min-height: 2.1875rem;
+}
+.cbi-dropdown {
+ align-items: center;
+}
+.cbi-dropdown.btn > ul:not(.dropdown),
+.cbi-dropdown.cbi-button > ul:not(.dropdown) {
+ margin: 0 0.75rem;
+}
+.cbi-dropdown[placeholder*="select"] {
+ max-width: 25rem;
+ height: auto;
+ margin-top: -3px;
+}
+.cbi-dropdown > ul {
+ display: flex;
+ overflow-x: hidden;
+ overflow-y: auto;
+ width: 100%;
+ padding: 0;
+ list-style: none;
+ outline: 0;
+}
+.cbi-dropdown > ul.preview {
+ display: none;
+}
+.cbi-dropdown.cbi-button-apply,
+.cbi-dropdown.cbi-button-action {
+ padding: 0;
+}
+.cbi-button-apply > ul.preview {
+ display: none;
+}
+.cbi-button-apply > ul.preview li {
+ color: #fff;
+}
+.cbi-button-apply > ul:first-child li {
+ color: #fff;
+}
+.cbi-dropdown > .open {
+ display: flex;
+ align-items: center;
+ width: 1rem;
+ height: 100%;
+ padding: 0 0.75rem;
+ cursor: pointer;
+ user-select: none;
+ font-size: 0;
+ color: #8898aa;
+ background-color: currentColor;
+ mask-image: var(--dropdown-arrow-icon);
+ mask-repeat: no-repeat;
+ mask-size: 100% 1rem;
+ mask-position: center;
+ mask-mode: match-source;
+}
+.cbi-dropdown > .more,
+.cbi-dropdown > ul > li[placeholder] {
+ display: none;
+}
+.cbi-dropdown > ul > li {
+ display: none;
+ overflow: hidden;
+ align-items: center;
+ align-self: center;
+ flex-grow: 1;
+ flex-shrink: 1;
+ min-height: 20px;
+ padding: 0.125rem 0.25em;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.cbi-dropdown > ul > li .hide-open {
+ display: initial;
+}
+.cbi-dropdown > ul > li .hide-close {
+ display: none;
+}
+.cbi-dropdown > ul > li[display]:not([display="0"]) {
+ border-left: thin solid #ccc;
+}
+.cbi-dropdown[empty] > ul {
+ max-width: 1px;
+}
+.cbi-dropdown > ul > li > form {
+ display: none;
+ margin: 0;
+ padding: 0;
+ pointer-events: none;
+}
+.cbi-dropdown > ul > li img {
+ margin-right: 0.25em;
+ vertical-align: middle;
+}
+.cbi-dropdown > ul > li > form > input[type="checkbox"] {
+ height: auto;
+ margin: 0;
+}
+.cbi-dropdown > ul > li input[type="text"] {
+ height: 2rem;
+ min-width: 16rem;
+ padding: 0 0.5rem;
+}
+.cbi-dropdown[open] > ul.dropdown {
+ position: absolute;
+ z-index: 1100;
+ display: block;
+ width: auto;
+ min-width: 100%;
+ max-width: none;
+ max-height: 200px !important;
+ border: 0 solid #918e8c;
+ background: #ffffff;
+ box-shadow: 0 0 4px #918e8c;
+ border-radius: 0.25rem;
+ color: var(--main-menu-color);
+ margin-left: 0 !important;
+ margin-top: 0.25rem;
+ left: 0;
+}
+.cbi-dropdown[open] > ul.dropdown li {
+ color: #000;
+}
+.cbi-dropdown > ul > li[display],
+.cbi-dropdown[open] > ul.preview,
+.cbi-dropdown[open] > ul.dropdown > li,
+.cbi-dropdown[multiple] > ul > li > label,
+.cbi-dropdown[multiple][open] > ul.dropdown > li,
+.cbi-dropdown[multiple][more] > .more,
+.cbi-dropdown[multiple][empty] > .more {
+ display: flex;
+ align-items: center;
+ flex-grow: 1;
+}
+.cbi-dropdown[empty] > ul > li,
+.cbi-dropdown[optional][open] > ul.dropdown > li[placeholder],
+.cbi-dropdown[multiple][open] > ul.dropdown > li > form {
+ display: block;
+}
+.cbi-dropdown[open] > ul.dropdown > li .hide-open {
+ display: none;
+}
+.cbi-dropdown[open] > ul.dropdown > li .hide-close {
+ display: initial;
+}
+.cbi-dropdown[open] > ul.dropdown > li {
+ border-bottom: thin solid #ccc;
+ padding: 0.5rem 0.75rem;
+ cursor: pointer;
+}
+.cbi-dropdown[open] > ul.dropdown > li label {
+ margin-left: 0.5rem;
+}
+.cbi-dropdown[open] > ul.dropdown > li[selected] {
+ background: #e4e9ee;
+}
+.cbi-dropdown[open] > ul.dropdown > li.focus {
+ background: #e4e9ee;
+ outline: none;
+}
+.cbi-dropdown[open] > ul.dropdown > li:last-child {
+ margin-bottom: 0;
+ border-bottom: 0;
+}
+.cbi-dropdown[open] > ul.dropdown > li[unselectable] {
+ opacity: 0.7;
+}
+.cbi-dropdown[open] > ul.dropdown > li > input.create-item-input:first-child:last-child {
+ width: 100%;
+}
+.cbi-dropdown[disabled] {
+ pointer-events: none;
+ opacity: 0.6;
+}
+.cbi-dropdown .zonebadge {
+ width: 100%;
+}
+.cbi-dropdown[open] .zonebadge {
+ width: auto;
+}
+/* progressbar */
+.cbi-progressbar {
+ position: relative;
+ display: flex;
+ width: 100%;
+ font-size: 0.75rem;
+ background-color: #e9ecef;
+ border-radius: 0.5rem;
+ height: 1rem;
+ overflow: hidden;
+}
+.cbi-progressbar > div {
+ display: block;
+ position: absolute;
+ height: 100%;
+ background-color: var(--primary);
+ border-radius: 0.5rem;
+ transition: width 0.3s;
+}
+.cbi-progressbar::after {
+ content: attr(title);
+ position: absolute;
+ font-size: 0.75rem;
+ color: var(--bs-heading-color);
+ width: 100%;
+ height: 100%;
+ text-align: center;
+ line-height: 1rem;
+ z-index: 2;
+}
+#modal_overlay {
+ position: fixed;
+ z-index: 900;
+ top: 0;
+ right: 10000px;
+ bottom: 0;
+ left: -10000px;
+ overflow-y: scroll;
+ transition: opacity 0.125s ease-in;
+ opacity: 0;
+ background: rgba(0, 0, 0, 0.7);
+ -webkit-overflow-scrolling: touch;
+}
+.modal {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ width: 90%;
+ min-width: 270px;
+ max-width: 600px;
+ min-height: 32px;
+ margin: 5em auto;
+ padding: 1rem;
+ border-radius: 0.25rem !important;
+ background: #fff;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
+}
+.modal > * {
+ line-height: normal;
+ flex-basis: 100%;
+ margin-bottom: 0.5em;
+ max-width: 100%;
+}
+.modal > pre,
+.modal > textarea {
+ font-size: 1rem;
+ font-size-adjust: 0.35;
+ overflow: auto;
+ padding: 0.5rem;
+ cursor: auto;
+ white-space: pre-wrap;
+ color: var(--dark-primary);
+ outline: 0;
+ border-radius: 0.25rem;
+ border: 1px solid var(--lighter);
+ transition: all 0.2s ease;
+}
+.modal > pre:focus,
+.modal > textarea:focus {
+ border: 1px solid var(--primary);
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+}
+.modal > h4 {
+ display: block;
+ flex-grow: 1;
+ max-width: none;
+ padding: 1rem;
+ margin: -1rem -1rem 0 -1rem;
+ font-size: 1rem;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
+ border-radius: 0.25rem 0 0 0.25rem;
+}
+.modal h5 {
+ margin-top: 1rem;
+ font-weight: 600;
+}
+.modal label > input[type="checkbox"] {
+ top: 0;
+}
+.modal ul li {
+ list-style-type: square;
+ color: #808080;
+}
+.modal p {
+ word-break: break-word;
+ margin-top: 1rem;
+}
+.modal .label {
+ font-size: 0.6rem;
+ font-weight: normal;
+ padding: 0.1rem 0.3rem;
+ padding-bottom: 0;
+ cursor: default;
+ border-radius: 0;
+}
+.modal .label.warning {
+ background-color: #f0ad4e !important;
+}
+.modal .btn {
+ padding: 0.45rem 0.8rem;
+}
+.modal.cbi-modal {
+ max-width: 90%;
+ max-height: none;
+}
+.modal .cbi-map {
+ margin-bottom: 1rem;
+}
+body.modal-overlay-active {
+ overflow: hidden;
+ height: 100vh;
+}
+body.modal-overlay-active #modal_overlay {
+ right: 0;
+ left: 0;
+ opacity: 1;
+}
+.spinning {
+ position: relative;
+ padding-left: 32px !important;
+}
+.spinning::before {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0.2em;
+ width: 32px;
+ content: "";
+ background: url(/luci-static/resources/icons/loading.svg) no-repeat center;
+ background-size: 16px;
+}
+/* luci */
+.hidden {
+ display: none;
+}
+.left,
+.left::before {
+ text-align: left !important;
+}
+.right,
+.right::before {
+ text-align: right !important;
+}
+.button-row {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ align-items: center;
+ justify-content: flex-end;
+ margin-bottom: 0 !important;
+}
+.center,
+.center::before {
+ text-align: center !important;
+}
+.top {
+ align-self: flex-start !important;
+ vertical-align: top !important;
+}
+.bottom {
+ align-self: flex-end !important;
+ vertical-align: bottom !important;
+}
+.inline {
+ display: inline;
+}
+.cbi-page-actions {
+ padding: 1rem;
+ justify-content: flex-end;
+ display: flex;
+ gap: 1rem;
+}
+.cbi-page-actions > form[method="post"] {
+ display: inline-block;
+}
+.th[data-type="button"],
+.td[data-type="button"],
+.th[data-type="fvalue"],
+.td[data-type="fvalue"] {
+ flex: 1 1 2em;
+ text-align: center;
+}
+.ifacebadge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.2rem;
+ padding: 0.25rem 0.8rem;
+ background: #eee;
+ border-radius: 4px;
+}
+td > .ifacebadge,
+.td > .ifacebadge {
+ font-size: 0.875rem;
+ background-color: #f0f0f0;
+}
+.ifacebadge > em,
+.ifacebadge > img {
+ display: inline-block;
+ margin: 0 0.75rem;
+}
+.ifacebadge > img + img {
+ margin: 0 0.2rem 0 0;
+}
+.network-status-table {
+ display: flex;
+ flex-wrap: wrap;
+}
+.network-status-table .ifacebox {
+ flex-grow: 1;
+ border-radius: 0.25rem;
+ overflow: hidden;
+ margin: 1rem;
+}
+.network-status-table .ifacebox-body {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ gap: 0.5em;
+}
+.network-status-table .ifacebox-body > span {
+ flex: 10 10 auto;
+}
+.network-status-table .ifacebox-body > div {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ height: 100%;
+}
+.network-status-table .ifacebox-body .ifacebadge {
+ align-items: center;
+ flex: 1 1 auto;
+ min-width: 220px;
+ padding: 0.5em;
+ background-color: #fff;
+}
+.network-status-table .ifacebox-body .ifacebadge > span {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+/* textarea */
+.cbi-input-textarea {
+ font-family: inherit;
+ width: 100%;
+ font-size: 0.875rem;
+ min-height: 14rem;
+ padding: 0.8rem;
+ color: #8898aa;
+ border-radius: 0.25rem;
+ border: 1px solid #dee2e6;
+ min-width: 16rem;
+}
+#content_syslog {
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
+}
+#syslog {
+ font-size: small;
+ font-family: 'Google Sans';
+ line-height: 1.25;
+ width: 100%;
+ min-height: 15rem;
+ padding: 1rem;
+ resize: none;
+ color: #242424;
+ border: 0;
+ border-radius: 0.25rem;
+ background-color: #ffffff;
+}
+#syslog:focus {
+ outline: 0;
+}
+#cbi-ddns #syslog {
+ overflow: auto;
+}
+/* config changes */
+.uci-change-list {
+ font-family: inherit;
+ overflow: scroll;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ flex-wrap: wrap;
+}
+.uci-change-list ins,
+.uci-change-legend-label ins {
+ display: block;
+ padding: 2px;
+ text-decoration: none;
+ border: thin solid #0f0;
+ background-color: #cfc;
+}
+.uci-change-list del,
+.uci-change-legend-label del {
+ font-style: normal;
+ display: block;
+ padding: 2px;
+ text-decoration: none;
+ border: thin solid #f00;
+ background-color: #fcc;
+}
+.uci-change-list var,
+.uci-change-legend-label var {
+ font-style: normal;
+ display: block;
+ padding: 2px;
+ text-decoration: none;
+ border: thin solid #ccc;
+ background-color: #eee;
+}
+.uci-change-list var ins,
+.uci-change-list var del {
+ font-style: normal;
+ padding: 0;
+ white-space: pre;
+ border: 0;
+}
+.uci-change-legend {
+ padding: 5px;
+}
+.uci-change-legend-label {
+ float: left;
+ width: 150px;
+}
+.uci-change-legend-label > ins,
+.uci-change-legend-label > del,
+.uci-change-legend-label > var {
+ display: block;
+ float: left;
+ width: 10px;
+ height: 10px;
+ margin-right: 4px;
+}
+.uci-change-legend-label var ins,
+.uci-change-legend-label var del {
+ line-height: 0.4;
+ border: 0;
+}
+.uci-change-list var,
+.uci-change-list del,
+.uci-change-list ins {
+ padding: 0.5rem;
+}
+.uci-dialog .cbi-section {
+ padding: 0.5rem;
+}
+.uci-dialog .cbi-section .uci-change-legend {
+ line-height: 15px;
+ padding: 10px 20px 0 20px;
+}
+.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label {
+ padding: 0;
+ margin: 0;
+ position: relative;
+ float: none;
+ display: inline-block;
+ width: 25%;
+}
+.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > ins,
+.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > del {
+ width: 14px;
+ height: 14px;
+}
+.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > var {
+ position: relative;
+ width: 14px;
+ height: 14px;
+}
+.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > var ins,
+.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label > var del {
+ position: absolute;
+ left: 2px;
+ top: 2px;
+ right: 2px;
+ bottom: 2px;
+}
+.uci-dialog .cbi-section .uci-change-list {
+ overflow: auto;
+}
+.uci-dialog .cbi-section .uci-change-list + .right .btn {
+ color: #333;
+}
+.uci-dialog .cbi-section .uci-change-list + .right .cbi-dropdown ul:not(.dropdown) li {
+ color: #fff;
+}
+.uci-dialog .cbi-section .uci-change-list + .right .cbi-button {
+ padding: 0.45rem 0.8rem;
+}
+/* other fix */
+#iwsvg,
+#iwsvg2,
+#bwsvg {
+ border: thin solid #d4d4d4 !important;
+}
+#iwsvg {
+ border-top: 0 !important;
+}
+.ifacebox {
+ line-height: 1.25;
+ display: inline-flex;
+ overflow: hidden;
+ flex-direction: column;
+ border-radius: 4px;
+ min-width: 100px;
+ background-color: #f9f9f9;
+}
+.ifacebox-head {
+ padding: 0.25em;
+ background: #eee;
+}
+.ifacebox-head.active {
+ background: #5e72e4;
+ background: var(--primary);
+}
+.ifacebox-head.active * {
+ color: #fff;
+ color: var(--white);
+}
+.ifacebox-body {
+ padding: 0.875rem 1rem;
+ line-height: 1.6em;
+}
+.cbi-image-button {
+ margin-left: 0.5rem;
+}
+.zonebadge {
+ display: inline-block;
+ padding: 0.2rem 0.5rem;
+ border-radius: 4px;
+}
+.zonebadge .ifacebadge {
+ margin: 0.1rem 0.2rem;
+ padding: 0.2rem 0.3rem;
+ border: thin solid #6c6c6c;
+}
+.zonebadge > input[type="text"] {
+ min-width: 10rem;
+ margin-top: 0.3rem;
+ padding: 0.16rem 1rem;
+}
+.zonebadge > em,
+.zonebadge > strong {
+ display: inline-block;
+ margin: 0 0.2rem;
+}
+.cbi-value-field .cbi-input-checkbox,
+.cbi-value-field .cbi-input-radio {
+ margin-top: 0.1rem;
+}
+.cbi-value-field > ul > li {
+ display: flex;
+}
+.cbi-value-field > ul > li > label {
+ margin-top: 0.5rem;
+}
+.cbi-value-field > ul > li .ifacebadge {
+ margin-top: -0.5rem;
+ margin-left: 0.4rem;
+ background-color: #eee;
+}
+.cbi-section-table-row > .cbi-value-field .cbi-dropdown {
+ min-width: 3rem;
+}
+.cbi-section-create {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.875rem 1rem;
+}
+.cbi-section-create > div:first-child > input {
+ width: 100%;
+ border-right: none;
+ border-bottom-right-radius: 0;
+ border-top-right-radius: 0;
+}
+.cbi-section-create > div:first-child + button {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+.cbi-section-remove {
+ padding: 0.5rem 1rem;
+}
+div.cbi-value var,
+td.cbi-value-field var,
+.td.cbi-value-field var {
+ font-style: italic;
+ color: #0069d6;
+}
+.cbi-optionals {
+ padding: 1rem 1rem 0 1rem;
+ border-top: thin solid #ccc;
+}
+.cbi-dropdown-container {
+ position: relative;
+}
+.cbi-tooltip-container,
+span[data-tooltip],
+span[data-tooltip] .label {
+ cursor: help !important;
+}
+.cbi-tooltip {
+ position: absolute;
+ z-index: 1000;
+ left: -10000px;
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+ border-radius: 0.25rem;
+ background: #fff;
+ white-space: pre;
+ padding: 0.5rem;
+ opacity: 0;
+ transition: opacity 0.25s ease-in;
+ transform: translate(-50%, 10%);
+}
+.cbi-tooltip-container:hover .cbi-tooltip:not(:empty) {
+ left: auto;
+ transition: opacity 0.25s ease-in;
+ opacity: 1;
+}
+.zonebadge .cbi-tooltip {
+ margin: -1.5rem 0 0 -0.5rem;
+ padding: 0.25rem;
+ background: inherit;
+}
+.zonebadge-empty {
+ color: #404040;
+ background: repeating-linear-gradient(45deg, rgba(204, 204, 204, 0.5), rgba(204, 204, 204, 0.5) 5px, rgba(255, 255, 255, 0.5) 5px, rgba(255, 255, 255, 0.5) 10px);
+}
+.zone-forwards {
+ display: flex;
+ min-width: 10rem;
+}
+.zone-forwards > * {
+ flex: 1 1 45%;
+}
+.zone-forwards > span {
+ flex-basis: 10%;
+ padding: 0 0.25rem;
+ text-align: center;
+}
+.zone-forwards .zone-src,
+.zone-forwards .zone-dest {
+ display: flex;
+ flex-direction: column;
+}
+.label {
+ font-size: 0.875rem;
+ font-weight: bold;
+ padding: 0.3rem 0.8rem;
+ white-space: nowrap;
+ text-decoration: none;
+ text-transform: uppercase;
+ color: #fff !important;
+ border-radius: 3px;
+ background-color: #bfbfbf;
+ text-shadow: none;
+}
+label > input[type="checkbox"],
+label > input[type="radio"] {
+ position: relative;
+ top: 0.4rem;
+ right: 0.2rem;
+ margin: 0;
+ vertical-align: bottom;
+}
+label[data-index][data-depends] {
+ padding-right: 2em;
+}
+.showSide {
+ display: none;
+}
+.darkMask {
+ position: fixed;
+ z-index: 99;
+ display: none;
+ width: 100%;
+ height: 100%;
+ content: "";
+ top: 0;
+ background-color: rgba(0, 0, 0, 0.56);
+ transition: all 0.2s;
+}
+.darkMask.active {
+ display: block;
+}
+/* diagnostics */
+#diag-rc-output > pre,
+#command-rc-output > pre,
+[data-page="admin-services-wol"] .notice code {
+ font-size: 1.2rem;
+ font-size-adjust: 0.35;
+ line-height: normal;
+ display: block;
+ overflow-y: hidden;
+ width: 100%;
+ padding: 8.5px;
+ white-space: pre;
+ color: #eee;
+ background-color: #101010;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
+}
+input[name="ping"],
+input[name="traceroute"],
+input[name="nslookup"] {
+ width: 80%;
+}
+.controls {
+ gap: 0.5rem;
+ margin: 0 1.25rem !important;
+}
+.controls > div {
+ gap: 0.5rem;
+}
+.controls > * > .btn:not([aria-label$="page"]) {
+ flex-grow: initial !important;
+ margin-top: 0.25rem;
+}
+.controls > #pager > .btn[aria-label$="page"] {
+ font-size: 1.4rem;
+ font-weight: bold;
+}
+.controls > * > label {
+ margin-bottom: 0.2rem;
+}
+.td.version,
+.td.size {
+ white-space: normal !important;
+ word-break: break-word;
+}
+.cbi-tabmenu + .cbi-section {
+ margin-top: 0;
+}
+.cbi-tab-disabled[data-errors]::after {
+ position: absolute;
+ top: -0.25rem;
+ right: -0.25rem;
+ content: attr(data-errors);
+ background-color: var(--red);
+ color: #fff;
+ width: 0.875rem;
+ height: 0.875rem;
+ border-radius: 0.875rem;
+ text-align: center;
+ display: inline-flex;
+ flex-direction: column;
+ justify-content: center;
+ font-size: 0.75em;
+}
+/* custom commands */
+.commands {
+ display: grid !important;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 0.75rem;
+}
+.commandbox {
+ padding: 0.5rem 1rem;
+ gap: 0.5rem;
+ border-bottom: thin solid #ccc;
+ border-radius: 0.25rem;
+ background: #eee;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+.commandbox h3 {
+ line-height: normal !important;
+ overflow: hidden;
+ margin: 6px 0 !important;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.commandbox div {
+ left: auto !important;
+}
+.commandbox code {
+ overflow: hidden;
+ max-width: fit-content;
+ padding: 2px 3px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.commandbox code:hover {
+ overflow-y: auto;
+ max-height: 50px;
+ white-space: normal;
+}
+.commandbox p:first-of-type {
+ margin-top: -6px;
+}
+.commandbox p:nth-of-type(2) {
+ margin-top: 2px;
+}
+#command-rc-output .alert-message {
+ line-height: 1.42857143;
+ position: absolute;
+ top: 40px;
+ right: 32px;
+ max-width: 40%;
+ margin: 0;
+ animation: anim-fade-in 1.5s forwards;
+ word-break: break-word;
+ opacity: 0;
+}
+@keyframes anim-fade-in {
+ 100% {
+ opacity: 1;
+ }
+}
+/* other fix */
+.fb-container .cbi-button {
+ height: auto !important;
+}
+#cbi-usb_printer-printer em {
+ display: block;
+ padding: 1rem;
+ text-align: center;
+}
+pre.command-output {
+ padding: 1.5rem;
+}
+/* page fix */
+[data-page="admin-system-autoreboot"] #cbi-autoreboot {
+ margin-top: 0;
+}
+[data-page="admin-system-system"] .control-group {
+ gap: 0.875rem;
+}
+[data-page="admin-system-system"] .cbi-dynlist {
+ margin: 0.25rem 0;
+}
+[data-page="admin-system-admin"] .cbi-map h2,
+[data-page="admin-system-admin-password"] .cbi-map h2,
+[data-page="admin-system-admin"] .cbi-map .cbi-map-descr,
+[data-page="admin-system-admin-password"] .cbi-map .cbi-map-descr {
+ margin-left: 0;
+ color: var(--gray-dark);
+}
+[data-page="admin-system-admin"] .cbi-section-node,
+[data-page="admin-system-admin-password"] .cbi-section-node {
+ padding: 1rem 0;
+}
+[data-page="admin-system-admin-sshkeys"] .cbi-dynlist {
+ margin-left: 1rem;
+}
+[data-page="admin-system-startup"] #view > div:first-child div[data-initialized="true"] {
+ margin-top: -0.4375rem;
+}
+[data-page="admin-system-startup"] [data-tab-title] p {
+ margin-left: 0;
+ margin-bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+[data-page="admin-system-startup"] textarea {
+ line-height: 1.25;
+ overflow-y: auto;
+ width: 100%;
+ min-height: 15rem;
+ margin: 0 1rem;
+ padding: 1rem;
+ resize: none;
+ color: #8898aa;
+ border-radius: 0.25rem;
+ border: 1px solid #dee2e6;
+}
+[data-page="admin-system-startup"] textarea:focus-visible {
+ outline: none;
+ box-shadow: none;
+ border: 1px solid var(--primary);
+}
+[data-page="admin-system-crontab"] #view p textarea {
+ line-height: 1.25;
+ overflow-y: hidden;
+ width: 100%;
+ min-height: 15rem;
+ padding: 1rem;
+ resize: none;
+ background-color: transparent;
+ background: var(--white);
+ outline: none;
+ color: #8898aa;
+ border-radius: 0.25rem;
+ border: 1px solid #dee2e6;
+ transition: all 0.2s ease;
+}
+[data-page="admin-system-crontab"] #view p textarea:focus-visible {
+ outline: none;
+ box-shadow: none;
+ border: 1px solid var(--primary);
+}
+[data-page="admin-system-crontabhelper"] .crontab-row .dropdown-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.5rem;
+}
+[data-page="admin-system-crontabhelper"] .crontab-row .dropdown-container div {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.5rem;
+}
+[data-page="admin-system-partexp-global"] .cbi-section {
+ padding: 1rem 0;
+}
+[data-page="admin-system-attendedsysupgrade"] #view .cbi-button {
+ margin-left: 0 !important;
+ margin-top: 1rem !important;
+}
+[data-page="admin-system-attendedsysupgrade-configuration"] .cbi-map .cbi-map-descr {
+ padding-bottom: 0;
+}
+[data-page="admin-system-flash"] .cbi-value {
+ padding: 0 1rem;
+}
+[data-page="admin-system-flash"] .cbi-section .cbi-section {
+ margin-top: 0;
+}
+[data-page="admin-system-flash"] .cbi-map-tabbed {
+ border-radius: 0.25rem;
+}
+[data-page="admin-system-flash"] .cbi-section-node {
+ padding-top: 0;
+ padding-bottom: 0.5rem;
+}
+[data-page="admin-system-flash"] legend {
+ font-size: 1.2rem;
+ width: 100%;
+ display: block;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+ line-height: 1.5;
+ margin-bottom: 0;
+ letter-spacing: 0.1rem;
+ color: #32325d;
+ font-weight: bold;
+ padding: 1rem 0 1rem 1rem;
+}
+[data-page="admin-system-flash"] .cbi-section-descr {
+ font-weight: 600;
+ padding: 1rem 0 1rem 1rem;
+ color: #525f7f;
+}
+[data-page="admin-system-flash"] .cbi-page-actions {
+ padding: 0rem 1rem 1rem 0rem;
+}
+[data-page="admin-system-flash"] .modal label > input[type="checkbox"] {
+ top: -0.25rem;
+}
+[data-page="admin-system-flash"] .modal .btn {
+ white-space: normal !important;
+}
+[data-page="admin-system-flash"] .modal label > input[type="checkbox"] {
+ vertical-align: text-top;
+ top: auto;
+}
+[data-page="admin-system-filetransfer"] #cbi-upload {
+ margin-top: 0;
+}
+[data-page="admin-system-filetransfer"] .cbi-section-table {
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
+}
+[data-page="admin-system-fileassistant"] #upload-toggle {
+ display: none !important;
+}
+[data-page="admin-system-fileassistant"] .fb-container .panel-title {
+ padding: 0.5rem 0.75rem !important;
+}
+[data-page="admin-system-fileassistant"] .cbi-section.fb-container {
+ padding: 0.5rem;
+}
+[data-page="admin-system-fileassistant"] .cbi-section.fb-container label.cbi-value-title {
+ line-height: 1.5rem;
+ padding: 0.5rem 0.75rem;
+ width: 60px;
+ text-align: left;
+}
+[data-page="admin-system-fileassistant"] .cbi-section.fb-container div.cbi-value-field {
+ width: 100%;
+ display: block;
+ padding-left: 60px;
+}
+[data-page="admin-system-fileassistant"] .cbi-section.fb-container div#list-content table.cbi-section-table thead td.cbi-section-table-cell,
+[data-page="admin-system-fileassistant"] .cbi-section.fb-container div#list-content table.cbi-section-table tbody td.cbi-section-table-cell {
+ width: 232px;
+}
+[data-page="admin-system-fileassistant"] .cbi-section.fb-container div#list-content tbody tr:nth-child(1) td.parent-icon strong {
+ margin-left: 0 !important;
+}
+[data-page="admin-system-fileassistant"] .cbi-section.fb-container div#list-content tbody td.cbi-value-field.file-icon strong,
+[data-page="admin-system-fileassistant"] .cbi-section.fb-container div#list-content tbody td.cbi-value-field.folder-icon strong {
+ vertical-align: middle;
+ margin-left: 5px;
+}
+[data-page="admin-system-fileassistant"] .fb-container .panel-container {
+ border-bottom-color: #dee2e6;
+ display: flex;
+}
+[data-page="admin-system-fileassistant"] .fb-container .panel-container .panel-title {
+ flex: 1;
+}
+[data-page="admin-system-fileassistant"] .fb-container .upload-container {
+ display: flex !important;
+}
+[data-page="admin-system-fileassistant"] .fb-container .upload-container .upload-file {
+ flex: 1;
+ margin-right: 0 !important;
+}
+[data-page="admin-system-opkg"] h2 {
+ margin-left: 0;
+ color: var(--gray-dark);
+}
+[data-page="admin-system-opkg"] input[name="filter_i18n"] {
+ top: 2px;
+ vertical-align: text-top;
+}
+[data-page="admin-system-opkg"] div.btn {
+ line-height: 3;
+ display: inline;
+ padding: 0.3rem 0.6rem;
+}
+[data-page="admin-system-opkg"] #maincontent > .container {
+ margin: 0 1.25rem 1rem 1.25rem;
+ margin-bottom: 1rem;
+}
+[data-page="admin-system-package-manager"] .controls div:nth-child(5) > label {
+ margin-bottom: 5px;
+}
+[data-page="admin-system-package-manager"] .controls div:nth-child(5) > div label {
+ margin-top: 1rem;
+}
+[data-page="admin-system-package-manager"] .controls div:nth-child(5) > div label input {
+ top: -2px;
+ right: 0;
+ vertical-align: middle;
+}
+[data-page="admin-system-reboot"] p {
+ padding-left: 1.5rem;
+}
+[data-page="admin-system-reboot"] p > span {
+ position: relative;
+ top: 0.1rem;
+ left: 1rem;
+}
+[data-page="admin-system-reboot"] .cbi-button {
+ background: #fb6340 !important;
+ border-color: #fb6340 !important;
+ margin-left: 0 !important;
+}
+[data-page="admin-system-reboot"] .cbi-button.cbi-button-action.important {
+ align-self: flex-end;
+}
+/* applyreboot fix */
+#applyreboot-container {
+ margin: 2rem;
+}
+#applyreboot-section {
+ line-height: 300%;
+ margin: 2rem;
+}
+[data-page="admin-system-poweroff"] .container h2 + br + p {
+ margin-bottom: 1rem;
+ padding-left: 1.5rem;
+}
+[data-page="admin-system-poweroffdevice"] .container h2 {
+ margin: 0;
+}
+[data-page="admin-system-poweroffdevice"] .container h2 + p {
+ margin-bottom: 1rem;
+ padding-left: 1.5rem;
+}
+[data-page="admin-system-poweroffdevice"] .container button + div {
+ display: none;
+}
+[data-page="admin-system-poweroffdevice"] .btn.cbi-button.cbi-button-negative {
+ align-self: flex-end;
+}
+[data-page="admin-network-network"] .cbi-value-field .cbi-dynlist {
+ padding: 0 !important;
+}
+[data-page="admin-network-network"] .td > .ifacebadge > .cbi-tooltip-container {
+ display: flex;
+}
+[data-page="admin-network-network"] .td > .ifacebadge > .cbi-tooltip-container img {
+ vertical-align: middle;
+}
+[data-page="admin-network-network"] div[data-name="_gen_server_keypair"] .cbi-value-title,
+[data-page="admin-network-network"] div[data-name="_gen_peer_keypair"] .cbi-value-title,
+[data-page="admin-network-network"] div[data-name="_gen_psk"] .cbi-value-title {
+ height: 2.4rem;
+}
+[data-page="admin-network-network"] #modal_overlay > .modal.cbi-modal > div > p > textarea {
+ height: 20em !important;
+ border: 1px solid #dee2e6 !important;
+ border-radius: 4px;
+}
+[data-page="admin-network-dhcp"] .cbi-value {
+ padding: 0;
+}
+[data-page="admin-network-dhcp"] [data-tab-active="true"] {
+ padding: 1rem 1rem !important;
+}
+[data-page="admin-network-diagnostics"] .table {
+ box-shadow: none;
+}
+[data-page="admin-network-diagnostics"] .cbi-section {
+ padding: 1rem;
+ font-family: monospace;
+ background: #fff !important;
+}
+[data-page="admin-network-diagnostics"] textarea {
+ background: transparent;
+ border-radius: 0.25rem;
+ color: #8898aa;
+ border: 1px solid #dee2e6;
+ padding: 0.5rem;
+}
+[data-page="admin-network-diagnostics"] .tr {
+ display: flex;
+ flex-direction: row;
+}
+[data-page="admin-network-diagnostics"] .tr .td {
+ background-color: #fff !important;
+ border-bottom: 1px solid #dee2e6 !important;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ flex: 1;
+}
+[data-page="admin-network-diagnostics"] .tr .td input {
+ margin: 0 !important;
+ border-right: none !important;
+ border-bottom-right-radius: 0 !important;
+ border-top-right-radius: 0 !important;
+}
+[data-page="admin-network-diagnostics"] .tr .td .cbi-dropdown,
+[data-page="admin-network-diagnostics"] .tr .td .cbi-button {
+ height: 2.5rem;
+ border-left: none !important;
+ border-bottom-left-radius: 0 !important;
+ border-top-left-radius: 0 !important;
+}
+[data-page="admin-network-diagnostics"] .tr .td .cbi-dropdown .more,
+[data-page="admin-network-diagnostics"] .tr .td .cbi-button .more {
+ display: block;
+ width: 1px;
+ height: 100%;
+ overflow: hidden;
+ text-indent: 5000px;
+ background-color: var(--white);
+}
+[data-page="admin-network-diagnostics"] .cbi-dropdown .open {
+ color: var(--white);
+}
+[data-page="admin-network-firewall-custom"] #view p,
+[data-page="admin-status-routes"] #view p {
+ padding: 0 1.25rem;
+}
+[data-page="admin-network-firewall-custom"] #view p textarea,
+[data-page="admin-status-routes"] #view p textarea {
+ padding: 1rem;
+ border-radius: 0.25rem;
+}
+[data-page="admin-network-firewall-custom"] #view > h3,
+[data-page="admin-status-routes"] #view > h3 {
+ border-radius: 0.25rem 0.25rem 0 0;
+}
+[data-page="admin-network-firewall-custom"] #view .cbi-tabmenu + div,
+[data-page="admin-status-routes"] #view .cbi-tabmenu + div {
+ margin-top: -0.5rem;
+}
+.cbi-section.fade-in .cbi-title {
+ position: relative;
+}
+.cbi-section.fade-in .cbi-title > div:last-child {
+ position: absolute;
+ right: 1.25rem;
+ top: 0;
+}
+.cbi-section.fade-in .cbi-title > div:last-child span {
+ font-size: 0;
+ background: none!important;
+ color: #ced4da !important;
+}
+.cbi-section.fade-in .cbi-title > div:last-child span:after {
+ transition: all 0.3s;
+ font-family: 'argon' !important;
+ content: '\e90f';
+ font-size: 1.1rem;
+ padding: 0.8755rem 0;
+ position: absolute;
+ right: 0;
+}
+.cbi-section.fade-in .cbi-title > div:last-child span[data-style=inactive]:after {
+ transform: rotate(90deg);
+}
+[data-page="admin-status"] #view > div:first-child,
+[data-page="admin-status-overview"] #view > div:first-child {
+ gap: 0 !important;
+}
+[data-page="admin-status-iptables"] .right {
+ margin-bottom: 0 !important;
+}
+[data-page="admin-status-nftables"] .nft-chain-hook {
+ padding: 0.5rem 1.25rem;
+}
+[data-page="admin-nlbw-display"] .cbi-section[data-tab="export"] {
+ padding: 1.5rem !important;
+}
+[data-page="admin-nlbw-backup"] form {
+ padding-left: 1.5rem;
+}
+[data-page="admin-services-ddns"] #syslog {
+ overflow: auto;
+ font-size: 12px !important;
+}
+[data-page="admin-services-ttyd"] .container {
+ display: flex;
+ flex-direction: column;
+}
+[data-page="admin-services-ttyd"] #view {
+ flex: 1;
+}
+[data-page="admin-services-ttyd"] #view iframe {
+ height: 100%;
+}
+#cbi-dockerd .cbi-section {
+ box-sizing: border-box;
+ padding: 16px;
+}
+#cbi-dockerd .cbi-section.cbi-tblsection {
+ padding: 0;
+}
+#cbi-docker .cbi-section .cbi-section-node {
+ box-sizing: border-box;
+ padding: 16px;
+}
+[data-page="admin-services-npc"] div[id="cbid.npc.config.vkey"] {
+ white-space: normal;
+}
+[data-page="admin-services-npc"] div[id="cbid.npc.config.vkey"] input[id="widget.cbid.npc.config.vkey"][type="password"] {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+[data-page="admin-services-npc"] div[id="cbid.npc.config.vkey"] .cbi-button {
+ margin: 0;
+ border: none;
+}
+[data-page="admin-services-npc"] div[id^="cbid.npc."][id$=".server_addr"]:not([id="cbid.npc.config.server_addr"]) + br,
+[data-page="admin-services-npc"] div[id^="cbid.npc."][id$=".vkey"]:not([id="cbid.npc.config.vkey"]) + br,
+[data-page="admin-services-npc"] div[id^="cbid.npc."][id$=".protocol"]:not([id="cbid.npc.config.protocol"]) + br,
+[data-page="admin-services-npc"] div[id^="cbid.npc."][id$=".dns"]:not([id="cbid.npc.config.dns"]) + br {
+ display: none;
+}
+[data-page="admin-services-npc"] div.cbi-value.nowrap {
+ white-space: normal;
+}
+[data-page^="admin-services-openclash"] .oc {
+ --bg-light: #fff;
+ --bg-gray: #fff;
+ border-radius: 8px;
+}
+[data-page^="admin-services-openclash"] .oc .developer-container {
+ background: transparent;
+}
+[data-page^="admin-services-openclash"] #cbi-openclash > fieldset {
+ background: none;
+ padding: 0;
+}
+[data-page^="admin-services-openclash"] #cbi-openclash > fieldset > table > tbody > tr > td {
+ border: none;
+ padding: 0;
+}
+[data-page^="admin-services-openclash"] #cbi-openclash > fieldset .main-card {
+ border: none;
+ box-shadow: none;
+ padding: 0 10px 10px 10px;
+ background: transparent;
+}
+[data-page^="admin-services-openclash"] #cbi-openclash > fieldset .myip-main-card {
+ margin: 0;
+ padding: 10px;
+ border: none;
+ background: transparent;
+}
+[data-page^="admin-services-openclash"] #cbi-openclash > fieldset .myip-main-card .myip-section-title {
+ margin: 0;
+ border-width: 1px;
+}
+[data-page^="admin-services-openclash"] .sub-card .card-title {
+ margin-top: 5px;
+}
+[data-page^="admin-services-openclash"] .oc .main-cards-container {
+ margin: 0 !important;
+ gap: 0 !important;
+}
+[data-page^="admin-services-openclash"] .oc .config-file-bottom {
+ margin-bottom: 5px;
+}
+[data-page^="admin-services-openclash"] .oc .config-file-bottom .card-actions {
+ margin-bottom: 2px !important;
+}
+[data-page^="admin-services-openclash"] .oc .card-content {
+ align-items: center !important;
+ justify-content: center !important;
+}
+[data-page^="admin-services-openclash"] .oc .card-controls {
+ align-self: center;
+}
+[data-page^="admin-services-openclash"] .oc .core-main-controls {
+ justify-content: center !important;
+}
+[data-page^="admin-services-openclash"] .oc .plugin-toggle-container {
+ margin-left: inherit !important;
+}
+[data-page^="admin-services-openclash"] .oc .core-status-toggle {
+ flex: 0 !important;
+ min-width: auto !important;
+}
+[data-page^="admin-services-openclash"] .oc .config-upload-content {
+ background: #fff !important;
+}
+[data-page^="admin-services-openclash"] .oc .subscription-info-container {
+ background: transparent !important;
+}
+[data-page^="admin-services-openclash"] .cbi-tabmenu > li {
+ border-right: none !important;
+ margin: 0 0.4rem 0 0 !important;
+}
+[data-page^="admin-services-openclash"] .cbi-tabmenu > li:last-child {
+ margin-right: 0 !important;
+}
+[data-page^="admin-services-openclash"] #tab-content .dom {
+ padding: 0 1rem 1rem 1rem;
+}
+[data-page^="admin-services-openclash"] .cbi-input-file {
+ padding: 0.2813rem;
+ width: 15rem !important;
+}
+[data-page^="admin-services-openclash"] [id="container.openclash.config.debug"] fieldset {
+ border: none !important;
+ padding: 1rem !important;
+}
+[data-page^="admin-services-openclash"] #diag-rc-output > pre,
+[data-page^="admin-services-openclash"] #dns-rc-output > pre {
+ font-size: 0.875rem;
+ color: #8898aa;
+ border: 1px solid #dee2e6;
+ background-color: transparent;
+ border-radius: 0.25rem;
+ font-family: 'Google Sans' !important;
+ box-shadow: none;
+}
+[data-page^="admin-services-openclash"] #debug-rc-output > textarea {
+ font-family: 'Google Sans' !important;
+}
+[data-page^="admin-services-openclash"] .CodeMirror {
+ font-size: inherit;
+ font-family: 'Google Sans' !important;
+}
+[data-page^="admin-services-openclash"] .cbi-button-up,
+[data-page^="admin-services-openclash"] .cbi-button-down {
+ padding: 0.8rem 1.5rem;
+ background-color: #f1f1f1;
+ font-size: 0;
+}
+[data-page^="admin-services-openclash"] select#CORE_VERSION,
+[data-page^="admin-services-openclash"] select#RELEASE_BRANCH {
+ width: auto;
+}
+[data-page="admin-vpn-passwall"] h4 {
+ background: transparent;
+}
+/* openvpn bug fix */
+.OpenVPN a {
+ line-height: initial !important;
+}
+/* wireless overview */
+#cbi-wireless > #wifi_assoclist_table > .tr {
+ box-shadow: inset 1px -1px 0 #ddd, inset -1px -1px 0 #ddd;
+}
+#cbi-wireless > #wifi_assoclist_table > .tr.placeholder > .td {
+ right: 2rem;
+ bottom: 2rem;
+ left: 2rem;
+ border-top: thin solid #ddd !important;
+}
+#cbi-wireless > #wifi_assoclist_table > .tr.table-titles {
+ box-shadow: inset 1px 0 0 #ddd, inset -1px 0 0 #ddd;
+}
+#cbi-wireless > #wifi_assoclist_table > .tr.table-titles > .th {
+ border-bottom: thin solid #ddd;
+ box-shadow: 0 -1px 0 0 #ddd;
+}
+#wifi_assoclist_table > .tr > .td[data-title="RX Rate / TX Rate"] {
+ width: 23rem;
+}
+/* samba */
+#cbi-samba [data-tab="template"] .cbi-value-field {
+ display: block;
+}
+#cbi-samba [data-tab="template"] .cbi-value-title {
+ width: auto;
+ padding-bottom: 0.6rem;
+}
+/* fix status */
+.node-status-overview > .main fieldset:nth-child(4) .td:nth-child(2),
+.node-status-processes > .main .table .tr .td:nth-child(3) {
+ white-space: normal;
+}
+div[style*="display:grid;grid-template-columns:repeat"] {
+ display: flex !important;
+ justify-content: space-evenly !important;
+ flex-wrap: wrap;
+}
+div[style*="display:grid;grid-template-columns:repeat"] .ifacebox {
+ text-align: center;
+ flex-basis: 100px;
+}
+div[style*="display:grid;grid-template-columns:repeat"] .ifacebox .ifacebox-body {
+ font-size: 0.7rem;
+ padding: 0.875rem;
+}
+div[style*="display:grid;grid-template-columns:repeat"] .ifacebox .ifacebox-body .cbi-tooltip-container {
+ font-size: inherit !important;
+}
+/* firewall */
+#iptables {
+ margin: 0;
+}
+.Firewall form {
+ margin: 2rem 2rem 0 0;
+ padding: 0;
+ box-shadow: none;
+}
+#cbi-firewall-redirect table *,
+#cbi-network-switch_vlan table *,
+#cbi-firewall-zone table * {
+ font-size: small;
+}
+#cbi-firewall-redirect table input[type="text"],
+#cbi-network-switch_vlan table input[type="text"],
+#cbi-firewall-zone table input[type="text"] {
+ width: 5rem;
+}
+#cbi-firewall-redirect table select,
+#cbi-network-switch_vlan table select,
+#cbi-firewall-zone table select {
+ min-width: 3.5rem;
+}
+#cbi-network-switch_vlan .th,
+#cbi-network-switch_vlan .td {
+ flex-basis: 12%;
+}
+#cbi-network-switch_vlan .table {
+ display: block;
+}
+#cbi-network-switch_vlan .td {
+ width: 100%;
+}
+#cbi-firewall-zone .table {
+ display: block;
+}
+#cbi-firewall-zone .td {
+ width: 100%;
+}
+[data-page^="admin-system-commands"] .panel-title,
+[data-page^="command-cfg"] .mobile-hide,
+[data-page^="command-cfg"] .showSide {
+ display: none;
+}
+#mwan3-service-status {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-wrap: wrap;
+}
+#mwan3-service-status > .alert-message {
+ position: static;
+ transform: none;
+}
+/* responsive */
+@media all and (-ms-high-contrast: none) {
+ .main > .main-left > .nav > .slide > .menu::before {
+ top: 30.25%;
+ }
+ .main > .main-left > .nav > li:last-child::before {
+ top: 20%;
+ }
+ .showSide::before {
+ top: -12px;
+ }
+}
+@media screen and (max-width: 1600px) {
+ header > .fill > .container > #logo {
+ margin: 0 2.5rem 0 0.5rem;
+ }
+ .main-left {
+ width: calc(0% + 13rem);
+ }
+ .btn:not(button),
+ .label {
+ padding: 0.5rem 0.75rem;
+ }
+ .cbi-value-title {
+ width: 15rem;
+ padding-right: 0.6rem;
+ }
+ .cbi-value-field .cbi-dropdown,
+ .cbi-value-field .cbi-input-select,
+ .cbi-value input[type="text"],
+ .cbi-value input[type="password"],
+ .cbi-value textarea {
+ min-width: 18rem;
+ }
+ #cbi-firewall-zone .cbi-input-select {
+ min-width: 9rem;
+ }
+ .cbi-input-textarea {
+ font-size: small;
+ }
+ .node-admin-status > .main fieldset li > a {
+ padding: 0.3rem 0.6rem;
+ }
+}
+@media screen and (max-width: 1366px) {
+ header > .fill > .container {
+ cursor: default;
+ }
+ .main-left {
+ width: calc(0% + 13rem);
+ }
+ .tabs > li > a,
+ .cbi-tabmenu > li > a {
+ padding: 0.2rem 0.8rem;
+ }
+ .panel-title {
+ font-size: 1.1rem;
+ padding-bottom: 1rem;
+ }
+ table {
+ width: 100% !important;
+ }
+ .table .cbi-input-text {
+ width: 100%;
+ }
+ .cbi-value-field .cbi-dropdown,
+ .cbi-value-field .cbi-input-select,
+ .cbi-value input[type="text"],
+ .cbi-value input[type="password"] {
+ min-width: 16rem;
+ }
+ #cbi-firewall-zone .cbi-input-select {
+ min-width: 5.5rem;
+ }
+ .main > .main-left > .nav > li,
+ .main > .main-left > .nav > li > a,
+ .main .main-left .nav > li > a:first-child,
+ .main > .main-left > .nav > .slide > .menu,
+ .main > .main-left > .nav > li > [data-title="Log_out"] {
+ font-size: 0.9rem;
+ }
+ .main > .main-left > .nav > .slide > .slide-menu > li > a {
+ font-size: 0.875rem;
+ }
+ #modal_overlay {
+ top: 0rem;
+ }
+ [data-page="admin-network-firewall-forwards"] .table:not(.cbi-section-table) {
+ display: block;
+ }
+ [data-page="admin-network-firewall-forwards"] .table:not(.cbi-section-table),
+ [data-page="admin-network-firewall-rules"] .table:not(.cbi-section-table),
+ [data-page="admin-network-hosts"] .table,
+ [data-page="admin-network-routes"] .table {
+ overflow-y: visible;
+ }
+ .btn:not(button),
+ .cbi-button {
+ font-size: 0.875rem;
+ }
+}
+@media screen and (max-width: 1152px) {
+ header > .fill > .container > #logo {
+ display: none;
+ }
+ header > .fill > .container > .brand {
+ position: relative;
+ }
+ html,
+ .main {
+ overflow-y: visible;
+ }
+ .main > .loading > span {
+ top: 25%;
+ }
+ .main-left {
+ width: calc(0% + 13rem);
+ }
+ body:not(.logged-in) .showSide {
+ visibility: hidden;
+ width: 0;
+ margin: 0;
+ }
+ .node-main-login > .main .cbi-value-title {
+ text-align: left;
+ }
+ .cbi-value-title {
+ width: 12rem;
+ padding-right: 1rem;
+ }
+ .cbi-value-field .cbi-dropdown,
+ .cbi-value-field .cbi-input-select,
+ .cbi-value input[type="text"] {
+ width: 16rem;
+ min-width: 16rem;
+ }
+ .cbi-value input[name^="pw"],
+ .cbi-value input[data-update="change"]:nth-child(2) {
+ width: 13rem !important;
+ min-width: 13rem;
+ }
+ #diag-rc-output > pre,
+ #command-rc-output > pre,
+ [data-page="admin-services-wol"] .notice code {
+ font-size: 1rem;
+ }
+ .table {
+ display: block;
+ }
+ .Interfaces .table {
+ overflow-x: hidden;
+ }
+ #packages.table {
+ display: grid;
+ }
+ .tr {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ }
+ .Overview .table[width="100%"] > .tr {
+ flex-wrap: nowrap;
+ }
+ .tr.placeholder {
+ border-bottom: thin solid #ddd;
+ }
+ .tr.placeholder > .td,
+ #cbi-firewall .tr > .td,
+ #cbi-network .tr:nth-child(2) > .td,
+ .cbi-section #wifi_assoclist_table .tr > .td {
+ border-top: 0;
+ }
+ .th,
+ .td {
+ display: inline-block;
+ align-self: flex-start;
+ flex: 2 2 10%;
+ text-overflow: ellipsis;
+ word-wrap: break-word;
+ }
+ .td select,
+ .td input[type="text"] {
+ width: 100%;
+ word-wrap: normal;
+ }
+ .td [data-dynlist] > input,
+ .td input.cbi-input-password {
+ width: calc(100% - 1.5rem);
+ }
+ .td[data-type="button"],
+ .td[data-type="fvalue"] {
+ flex: 1 1 12.5%;
+ text-align: left;
+ }
+ .th.cbi-value-field,
+ .td.cbi-value-field,
+ .th.cbi-section-table-cell,
+ .td.cbi-section-table-cell {
+ flex-basis: auto;
+ padding-top: 1rem;
+ }
+ .cbi-section-table-row {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
+ }
+ .td.cbi-value-field,
+ .cbi-section-table-cell {
+ display: inline-block;
+ flex: 10 10 auto;
+ flex-basis: 50%;
+ text-align: center;
+ }
+ .td.cbi-section-actions {
+ vertical-align: bottom;
+ }
+ .tr.table-titles,
+ .tr.cbi-section-table-titles,
+ .tr.cbi-section-table-descr {
+ display: none;
+ }
+ .tr[data-title]::before,
+ .tr.cbi-section-table-titles.named::before {
+ font-size: 0.9rem;
+ display: block;
+ flex: 1 1 100%;
+ border-bottom: thin solid rgba(0, 0, 0, 0.26);
+ background: #e9ecef;
+ }
+ .td[data-title],
+ [data-page^="admin-status-realtime"] .td[id] {
+ text-align: left;
+ }
+ .td[data-title]::before {
+ display: block;
+ }
+ .cbi-button + .cbi-button {
+ margin-left: 0;
+ }
+ .td.cbi-section-actions > * > *,
+ .td.cbi-section-actions > * > form > * {
+ margin: 2.1px 3px;
+ }
+ .Firewall form {
+ position: static !important;
+ margin: 0 0 2rem 0;
+ padding: 2rem;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
+ }
+ .Firewall form input {
+ width: 100% !important;
+ margin: 0;
+ margin-top: 1rem;
+ }
+ .Firewall .center,
+ .Firewall .center::before {
+ text-align: left !important;
+ }
+ .btn:not(button),
+ .cbi-button {
+ font-size: 0.875rem;
+ }
+}
+@media screen and (max-width: 768px) {
+ body {
+ font-size: 0.875rem;
+ }
+ .cbi-progressbar::after {
+ font-size: 0.6rem;
+ }
+ .main-left {
+ position: fixed;
+ z-index: 100;
+ width: 0;
+ }
+ .main-left.active {
+ width: 13rem;
+ }
+ .main-right {
+ width: 100%;
+ }
+ .main-right.active {
+ overflow-y: hidden;
+ }
+ .darkMask.active {
+ display: block;
+ }
+ .showSide {
+ padding: 0.1rem;
+ position: relative;
+ z-index: 99;
+ display: inline-block !important;
+ }
+ .showSide::before {
+ font-family: 'argon' !important;
+ font-style: normal !important;
+ font-weight: normal !important;
+ font-variant: normal !important;
+ text-transform: none !important;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ content: "\e20e";
+ font-size: 1.7rem;
+ }
+ header > .fill > .container > .flex1 > .brand {
+ display: inline-block;
+ }
+ .main > .main-left > .nav > .slide > .slide-menu > li > a {
+ font-size: 0.875rem;
+ }
+}
+@media screen and (max-width: 600px) {
+ .mobile-hide {
+ display: none;
+ }
+ #maincontent > .container {
+ margin: 0 1rem 1rem 1rem;
+ }
+ .cbi-value-title {
+ text-align: left;
+ }
+ .cbi-dynlist p {
+ padding: 0.5rem 1rem;
+ }
+ body {
+ overflow-x: hidden;
+ }
+ .node-main-login .main .main-right #maincontent .container .cbi-map .cbi-section .cbi-section-node .cbi-value .cbi-value-field {
+ width: 16rem;
+ }
+ .node-main-login footer {
+ display: none;
+ }
+ .tabs::-webkit-scrollbar,
+ .cbi-tabmenu::-webkit-scrollbar {
+ width: 0px;
+ height: 0px;
+ }
+ .cbi-value-field,
+ .cbi-value-description {
+ display: block !important;
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+ }
+ [data-page="admin-system-admin-password"] .cbi-value-field {
+ display: table-cell !important;
+ }
+ .modal.cbi-modal {
+ max-width: 100%;
+ max-height: none;
+ }
+ .modal {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ width: 100%;
+ min-width: 270px;
+ max-width: 600px;
+ min-height: 32px;
+ margin: 5em auto;
+ padding: 1em;
+ border-radius: 3px !important;
+ background: #fff;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
+ }
+ .cbi-dropdown[open] > ul.dropdown {
+ margin-bottom: 1rem;
+ }
+ .login-page .login-container footer {
+ display: none;
+ }
+}
+@media screen and (max-width: 480px) {
+ .mobile-hide {
+ display: none;
+ }
+ div[style*="display:grid;grid-template-columns:repeat"] .ifacebox {
+ flex-basis: 80px;
+ }
+ div[style*="display:grid;grid-template-columns:repeat"] .ifacebox .ifacebox-body {
+ padding: 0.875rem 0.5rem;
+ font-size: 0.6rem;
+ }
+ .login-page .login-container {
+ margin-left: 0rem !important;
+ width: 100%;
+ }
+ .login-page .login-container .login-form .form-login .input-group::before {
+ color: #525461;
+ }
+ .login-page .login-container .login-form .form-login .input-group input {
+ color: #525461;
+ border-bottom: white 1px solid;
+ border-bottom: var(--white) 1px solid;
+ border-radius: 0;
+ }
+}
+@media screen and (min-width: 600px) {
+ ::-webkit-scrollbar {
+ width: 10px;
+ height: 10px;
+ }
+ ::-webkit-scrollbar,
+ ::-webkit-scrollbar-corner {
+ background: transparent;
+ }
+ ::-webkit-scrollbar-thumb {
+ background: var(--primary);
+ border-radius: 10px;
+ }
+ ::-webkit-scrollbar-thumb:hover {
+ background: var(--primary);
+ }
+ ::-webkit-scrollbar-thumb:active {
+ background: var(--primary);
+ }
+}
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/css/dark.css b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/css/dark.css
new file mode 100644
index 0000000..fc36987
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/css/dark.css
@@ -0,0 +1 @@
+body{background:#1e1e1e;color:#cccccc}.spinning::before{filter:invert(1)}.login-page .login-container .login-form{background-color:#1e1e1e;-webkit-backdrop-filter:blur(var(--blur-radius-dark)) brightness(0);backdrop-filter:blur(var(--blur-radius-dark)) brightness(0);background-color:rgba(0, 0, 0, var(--blur-opacity-dark))}.login-page .login-container .login-form .brand{color:#adb5bd}.login-page .login-container .login-form .form-login .input-group::before{color:#adb5bd}.login-page .login-container .login-form .form-login .input-group input{background-color:transparent !important;color:#adb5bd;border-bottom:#adb5bd 1px solid !important;border-radius:0 !important;border-top:none !important;border-left:none !important;border-right:none !important;box-shadow:none}.login-page .login-container .login-form .form-login .cbi-button-apply{background-color:#483d8b !important;background-color:var(--dark-primary) !important}.login-page .login-container .login-form .form-login .cbi-button-apply:hover,.login-page .login-container .login-form .form-login .cbi-button-apply:focus{opacity:.9}header::after{background-color:#1e1e1e !important}.main .main-left{background-color:#333333 !important;box-shadow:0 0 .5rem 0 rgba(0,0,0,0.15)}.main .main-left .sidenav-header .brand{color:#cccccc}.main .main-left .nav .slide .slide-menu .active a{color:#cccccc}.main .main-left .nav .slide .slide-menu .active a::after{background-color:#cccccc !important}.main .main-left .nav .slide .slide-menu .active::after{background-color:var(--dark-primary) !important}.main .main-left .nav .slide .slide-menu li a{color:#cccccc}.main .main-left .nav .slide .slide-menu li a:hover{background:none !important}.main .main-left .nav .slide .slide-menu li::after{background-color:var(--dark-primary) !important}.main .main-left .nav .slide .menu.active{background-color:#483d8b !important;background-color:var(--dark-primary) !important;color:#ffffff !important}.main .main-left .nav .slide .menu.active a::after{background-color:#ffffff !important}.main .main-left .nav li a{color:#cccccc !important}.main .main-left .nav li a:hover{background-color:#483d8b !important;background-color:var(--dark-primary) !important;color:#ffffff !important}.main .main-left::-webkit-scrollbar-thumb{background-color:#252526 !important}.main .main-left::-webkit-scrollbar-track{background-color:#333}.main .main-right{background-color:#1e1e1e}h2{color:#ccc;background:#333333}h3{color:#ccc;border-bottom:0;background:#333333}h4{color:#8C6900}abbr{color:var(--dark-primary) !important}a:link,a:visited,a:active{color:#a5b2ff}input:-webkit-autofill{background-color:#3c3c3c !important}#channel_graph{background-color:transparent !important}.cbi-value-field .cbi-input-apply,.cbi-button-apply,.cbi-button-edit{color:#fff !important;background-color:#483d8b !important;background-color:var(--dark-primary) !important;border-color:#483d8b !important;border-color:var(--dark-primary) !important}.cbi-section em{color:#ccc}header.bg-primary{background-color:#1e1e1e !important}.cbi-map-descr{color:#ccc}.cbi-section{background:none;box-shadow:0 0 .5rem 0 rgba(0,0,0,0.35)}.panel-title{color:#ccc;background-color:#333333}div>table>tbody>tr:nth-of-type(2n),div>.table>.tr:nth-of-type(2n){background-color:#252526}table>tbody>tr>td,table>tfoot>tr>td,table>thead>tr>td{color:#cccccc}fieldset>table>tbody>tr:nth-of-type(2n){background-color:#252526}table>tbody>tr>td,table>tfoot>tr>td,table>thead>tr>td{border-top:1px solid #252526}#swaptotal>div>div,#swapfree>div>div,#memfree>div>div,#membuff>div>div,#conns>div>div,#memtotal>div>div{background-color:var(--dark-primary) !important}#swaptotal>div>div>div>small,#swapfree>div>div>div>small,#memfree>div>div>div>small,#membuff>div>div>div>small,#conns>div>div>div>small,#memtotal>div>div>div>small{color:#ccc !important}.node-system-packages>.main .cbi-section-node:first-child .cbi-value-last{line-height:1.8em}.node-system-packages>.main .cbi-section-node:first-child .cbi-value-last div[style="margin:3px 0; width:300px; height:10px; border:1px solid #000000; background-color:#80C080"]{border:1px solid #999999 !important;background-color:transparent !important}.node-system-packages>.main .cbi-section-node:first-child .cbi-value-last div[style="margin:3px 0; width:300px; height:10px; border:1px solid #000000; background-color:#80C080"] div{background-color:#ba8b00 !important}table>tbody>tr>th,table>tfoot>tr>th,table>thead>tr>th{background-color:#252526;border-bottom:black 1px solid !important}.tr[data-title]::before,.tr.cbi-section-table-titles.named::before{background-color:#252526}tr>td,tr>th,.tr>.td,.tr>.th,.cbi-section-table-row::before,#cbi-wireless>#wifi_assoclist_table>.tr:nth-child(2){border-top:0}.cbi-rowstyle-2{background-color:#1e1e1e}.cbi-rowstyle-1{background-color:#252526}.cbi-rowstyle-2 .cbi-button-up,.cbi-rowstyle-2 .cbi-button-down,body:not(.Interfaces) .cbi-rowstyle-2:first-child{background-color:#666 !important}.cbi-section>h3:first-child,.panel-title,h3{color:#ccc}h4{background-color:#1e1e1f}.cbi-progressbar{background:#282a2c}.cbi-progressbar div{background-color:var(--dark-primary) !important}.cbi-section-table .cbi-section-table-titles .cbi-section-table-cell{background-color:#1e1e1f}.cbi-button{color:#ffffff !important;background-color:#483d8b;background-color:var(--dark-primary)}.cbi-section-node{background:none}abbr{color:#5e72e4}div>table>tbody>tr:nth-of-type(2n),div>.table>.tbody>.tr:nth-of-type(2n){background-color:#252526}#content_syslog{box-shadow:0 0 .5rem 0 rgba(0,0,0,0.35)}#syslog{color:#ccc;background-color:#1e1e1e}#iwsvg,#iwsvg2,#bwsvg{overflow:hidden;box-shadow:0 0 .5rem 0 rgba(0,0,0,0.35);background-color:#1e1e1e !important}.tabs{background-color:#252526}.tabs li[class~="active"],.tabs li:hover{cursor:pointer;color:#ccc !important}.tabs li[class~="active"] a,.tabs li:hover a{color:#ccc !important}.tabs>li[class~="active"]>a{color:#ccc}.tabs>li[class~="active"],.tabs>li:hover{border-bottom:.18751rem solid #483d8b;border-bottom:.18751rem solid var(--dark-primary);color:var(--dark-primary) !important;background-color:#181819}.cbi-tabmenu>li>a,.tabs>li>a{color:#ccc}.cbi-tabmenu>li>a:hover,.tabs>li>a:hover{color:#ccc}.cbi-tabmenu>li{background:#2d2d2d}.cbi-tabmenu li[class~="cbi-tab"] a{color:#ccc}.cbi-tabmenu>li:hover{color:#ccc;background:#2d2d2d;border-bottom:.18751rem solid var(--dark-primary) !important}.cbi-tabmenu>li[class~="cbi-tab"]{background-color:#181819;border-bottom:.18751rem solid var(--dark-primary) !important}.cbi-tabcontainer>.cbi-value:nth-of-type(2n){background-color:#252526}.cbi-value-title{color:#ccc}select,input{color:#ccc;background-color:transparent !important;border:1px solid #252526;box-shadow:none}select:not([multiple="multiple"]):focus,input:focus{border-color:#483d8b !important;border-color:var(--dark-primary) !important;outline:0}select:not([multiple="multiple"]):focus,input:not(.cbi-button):focus,.cbi-dropdown:focus{border-color:#5e72e4;border-color:var(--dark-primary) !important}select{background-color:#1e1e1e !important}#cbi-dropbear h2,#cbi-dropbear .cbi-map-descr,#cbi-dropbear .cbi-map-descr abbr,#cbi-rc h2,#cbi-rc .cbi-map-descr,#cbi-distfeedconf h2,#cbi-distfeedconf .cbi-map-descr,#cbi-customfeedconf h2,#cbi-customfeedconf .cbi-map-descr,#cbi-download h2,#cbi-filelist h2{color:#ccc !important}.cbi-value-field>ul>li .ifacebadge{background-color:#3c3c3c}.cbi-section-descr{color:#ccc}.cbi-input-textarea{background-color:#1e1e1e;color:#ccc;border:1px solid #3c3c3c !important}textarea:focus-visible{border:1px solid var(--dark-primary)}.cbi-section-remove:nth-of-type(2n),.cbi-section-node:nth-of-type(2n){background-color:#1e1e1e}.cbi-section[id] .cbi-section-remove:nth-of-type(4n+3),.cbi-section[id] .cbi-section-node:nth-of-type(4n+4){background-color:#1e1e1e}.node-system-packages>.main table tr td:nth-last-child(1){color:#ccc}.cbi-tooltip{background-color:var(--primary);color:#fff}.ifacebox{background-color:transparent !important;border:1px solid #1e1e1e}.ifacebox-head{color:#ccc;background:#666}.ifacebox-body{background-color:#333}.zonebadge strong{color:#333}.zonebadge>.ifacebadge{background-color:#3c3c3c}div.cbi-value var,td.cbi-value-field var{color:#5e72e4}#diag-rc-output>pre{color:#ccc;background-color:#1e1e1e}.node-services-vssr .block{background-color:#3c3c3c !important;box-shadow:0 0 .5rem 0 rgba(0,0,0,0.35)}.node-services-vssr .block h4{color:#ccc !important}.node-services-vssr .status-bar{color:#ccc;box-shadow:0 0 .5rem 0 rgba(0,0,0,0.35);background-color:#1e1e1e}.node-services-vssr .cbi-section-table-row{color:#ccc;background-color:#3c3c3c !important;box-shadow:0 0 5px 0 rgba(0,0,0,0.35)}.node-services-vssr .cbi-section-table-row.fast{background:#483d8b !important;background:var(--dark-primary) !important;color:#fff}.node-services-vssr .ssr-button{color:#ccc}.node-services-vssr .incon:nth-child(2){border-right:#1e1e1e 1px solid}#xhr_poll_status>.label.success{color:#ccc !important;background-color:darkolivegreen !important}.notice{background-color:#483d8b !important;background-color:var(--dark-primary) !important}.cbi-section.fade-in .cbi-title>div:last-child span{background:none!important;color:#ced4da}.cbi-input-find,.cbi-input-save,.cbi-button-add,.cbi-button-save,.cbi-button-find,.cbi-input-reload,.cbi-button-reload{color:#fff !important;background:darkolivegreen !important;border-color:darkolivegreen !important}.cbi-button-reset,.cbi-input-remove{color:#fff !important;background-color:darkorange !important;border-color:darkorange !important}.cbi-page-actions .cbi-button-apply,.cbi-section-actions .cbi-button-edit,.cbi-button-edit.important,.cbi-button-apply.important,.cbi-button-reload.important,.cbi-button-action.important{border:1px #483d8b solid !important;border:1px var(--dark-primary) solid !important}.btn[value="Dismiss"],.cbi-button[value="Terminate"],.cbi-button[value="Reset"],.cbi-button[value="Disabled"],.cbi-button[onclick^="iface_reconnect"],.cbi-button[onclick="handleReset(event)"],.cbi-button-neutral[value="Disable"]{color:#fff;border:thin solid darkorange !important;background-color:darkorange !important}fieldset[id^="cbi-apply-"]{background-color:#333333}#detail-bubble>div{border:1px solid #ccc;background:#252525}.ifacebox-head.active{background-color:var(--dark-primary) !important}header .fill .status span[data-style="active"]{color:#ccc !important;background-color:darkolivegreen !important}#cbi-wireless .td,#cbi-network .tr:first-child>.td,.table[width="100%"]>.tr:first-child>.td,[data-page="admin-network-diagnostics"] .tr>.td,.tr.table-titles>.th,.tr.cbi-section-table-titles>.th{background-color:#252526;border-bottom:black 1px solid !important}.network-status-table .ifacebox-body .ifacebadge{background-color:#252526;border-bottom:0;box-shadow:none}td>.ifacebadge,.td>.ifacebadge{color:#fff;background-color:#483d8b;background-color:var(--dark-primary);border:0}.alert,.alert-message{background-color:#333}.alert-message.warning{background-color:#986400 !important}.alert.error,.alert-message.error{background-color:#784f00}.alert h4,.alert-message h4{background-color:#784f00;color:#ffbf00}.alert-message [class="btn"]{background-color:#777777;color:#ccc}.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label>ins{border-color:#00ad00;background-color:#688668}.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label>del{border-color:#c60000;background-color:#896565}.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label>var{background-color:#333;border-color:#666}.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label>var ins{background-color:#688668}.uci-dialog .cbi-section .uci-change-legend .uci-change-legend-label>var del{background-color:#896565}.uci-dialog .cbi-section .uci-change-list>var{background-color:#333;border-color:#666}.uci-dialog .cbi-section .uci-change-list>var del{background-color:#896565}.uci-dialog .cbi-section .uci-change-list>var ins{background-color:#688668}.uci-dialog .cbi-section .uci-change-list>ins{border-color:#00ad00;background-color:#688668}.uci-dialog .cbi-section .uci-change-list>del{border-color:#c60000;background-color:#896565}.uci-dialog .cbi-section .uci-change-list+.right .btn{color:#ccc}.uci-dialog .cbi-section .uci-change-list+.right .cbi-button{border:1px solid #3c3c3c !important}.btn.danger,.cbi-section-remove>.cbi-button,.cbi-button-remove,.cbi-button-reset,.cbi-button-negative,.cbi-button[value="Stop"],.cbi-button[value="Kill"],.cbi-button[onclick="reboot(this)"],.cbi-button-neutral[value="Restart"]{border:thin solid darkorange !important;background-color:darkorange !important}.cbi-section,.cbi-section-error,#iptables,.Firewall form,#cbi-network>.cbi-section-node,#cbi-wireless>.cbi-section-node,#cbi-wireless>#wifi_assoclist_table,[data-tab-title],[data-page^="admin-system-admin"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear),[data-page="admin-system-opkg"] #maincontent>.container{background:#1e1e1e !important;box-shadow:0 0 .5rem 0 rgba(0,0,0,0.35)}div[style="width:100%;height:300px;border:1px solid #000;background:#fff"]{background:transparent !important}[data-page="admin-system-admin"] .cbi-map h2,[data-page="admin-system-admin-password"] .cbi-map h2,[data-page="admin-system-admin"] .cbi-map .cbi-map-descr,[data-page="admin-system-admin-password"] .cbi-map .cbi-map-descr{color:#ccc}[data-page="admin-system-flash"] .modal label>input[type="checkbox"]{vertical-align:text-top;top:auto;border-color:#fff !important}[data-page="admin-system-flash"] .modal .btn{white-space:normal !important;background-color:darkseagreen}[data-page="admin-system-flash"] .modal .alert-message{background-color:transparent !important}[data-page="admin-system-flash"] .modal .danger{border:thin solid darkorange !important;background-color:darkorange !important}.cbi-value input[type="password"]+.cbi-button-neutral{background-color:#483d8b !important;background-color:var(--dark-primary) !important;border-color:var(--dark-primary) !important}.btn,button,select,input,.cbi-dropdown{border:1px solid #3c3c3c !important}.cbi-dropdown .preview{color:#ccc}.cbi-section-table-row{background-color:#1e1e1e !important}.modal{background-color:#1e1e1e}.cbi-button-positive{color:#fff !important;background-color:darkolivegreen !important}[data-page="admin-system-flash"] legend{color:#ccc}.logout:before{color:#adb5bd !important}.cbi-dropdown[open]{border-color:#483d8b !important;border-color:var(--dark-primary) !important}.cbi-dropdown[open]>ul.dropdown{background:#252526 !important;color:#ffffff !important;box-shadow:none;border:1px solid #3c3c3c !important}.cbi-dropdown[open]>ul.dropdown li{color:#ffffff;border-bottom:1px solid #3c3c3c !important}.cbi-dropdown[open]>ul.dropdown>li[selected]{background-color:#483d8b !important;background-color:var(--dark-primary) !important;border-bottom:1px solid #3c3c3c !important}.cbi-dropdown[open]>ul.dropdown>li.focus{background:#483d8b;background:var(--dark-primary);outline:none}.ifacebadge{background-color:#333333}.cbi-dynlist>.item>span{border:1px solid #3c3c3c !important}.cbi-page-actions .cbi-button-apply,.cbi-section-actions .cbi-button-edit,.cbi-button-edit,.cbi-button-apply,.cbi-button-reload,.cbi-button-action,.cbi-button[value="Submit"],.cbi-button[value="Upload"],.cbi-button[value$="Apply"],.cbi-button[onclick="addKey(event)"]{background:#483d8b !important;background:var(--dark-primary) !important;border-color:var(--dark-primary) !important}.btn.primary,.cbi-page-actions .cbi-button-save,.cbi-page-actions .cbi-button-apply+.cbi-button-save,.cbi-button-add,.cbi-button-save,.cbi-button-positive,.cbi-button-link,.cbi-button[value="Enable"],.cbi-button[value="Scan"],.cbi-button[value^="Back"],.cbi-button-neutral[onclick="handleConfig(event)"]{background:#483d8b;background:var(--dark-primary)}[data-page="admin-system-opkg"] h2{color:#ccc !important}[data-page="admin-system-startup"] textarea{color:#ccc;background-color:transparent}[data-page="admin-system-startup"] textarea:focus-visible{border:1px solid var(--dark-primary)}[data-page="admin-network-firewall-custom"] #view p textarea,[data-page="admin-status-routes"] #view p textarea,[data-page="admin-system-crontab"] #view p textarea{color:#ccc;background-color:transparent}#view>.spinning{background:#333333 !important;box-shadow:0 4px 8px rgba(0,0,0,0.03) !important}@media screen and (min-width:600px){::-webkit-scrollbar-thumb{background:var(--dark-primary)}::-webkit-scrollbar-thumb:hover{background:var(--dark-primary)}::-webkit-scrollbar-thumb:active{background:var(--dark-primary)}}@media screen and (max-width:480px){.node-status-iptables>.main div>.cbi-map>form{background-color:#1e1e1e;box-shadow:0 0 .5rem 0 rgba(0,0,0,0.35)}}[data-page=""] .main-right>#maincontent .Dashboard,[data-page="admin-dashboard"] .main-right>#maincontent .Dashboard{color:#ccc !important}[data-page=""] .main-right>#maincontent .Dashboard h3,[data-page="admin-dashboard"] .main-right>#maincontent .Dashboard h3{color:#ccc}[data-page=""] .main-right>#maincontent .Dashboard hr,[data-page="admin-dashboard"] .main-right>#maincontent .Dashboard hr{border-top:1px solid #fff}[data-page=""] .main-right>#maincontent .Dashboard .dashboard-bg,[data-page="admin-dashboard"] .main-right>#maincontent .Dashboard .dashboard-bg{background-color:#333333}[data-page=""] .main-right>#maincontent .Dashboard img.svgmonochrome,[data-page="admin-dashboard"] .main-right>#maincontent .Dashboard img.svgmonochrome{filter:invert(90%)}[data-page=""] .main-right>#maincontent .Dashboard tr,[data-page="admin-dashboard"] .main-right>#maincontent .Dashboard tr{border-top:thin solid #4d4d4d}[data-page=""] .main-right>#maincontent .Dashboard tr:last-child,[data-page="admin-dashboard"] .main-right>#maincontent .Dashboard tr:last-child{border-bottom:thin solid #4d4d4d}[data-page="admin-system-fileassistant"] .fb-container .panel-container{border-bottom-color:#3c3c3c !important}[data-page="admin-system-fileassistant"] .fb-container td[class$="-icon"]::before{filter:invert(.7)}[data-page="admin-system-fileassistant"] div#list-content table.cbi-section-table tbody tr:nth-child(1) td.parent-icon{background:#1e1e1e !important}[data-page^="admin-services-openclash"] .oc{--bg-light:#1e1e1e !important;--bg-gray:#1e1e1e !important}[data-page^="admin-services-openclash"] .oc .developer-container{background:transparent !important}[data-page^="admin-services-openclash"] .config-upload-content{background:#1e1e1e !important}[data-page^="admin-services-openclash"] .cbi-tabmenu::-webkit-scrollbar-thumb{background-color:#5b5b5b}[data-page^="admin-services-openclash"] .cbi-tabmenu::-webkit-scrollbar-track{background-color:rgba(60,60,60,0.75)}[data-page^="admin-services-openclash"] #tab{border-color:#3c3c3c}[data-page^="admin-services-openclash"] #diag-rc-output>pre,[data-page^="admin-services-openclash"] #dns-rc-output>pre{color:#ccc;border:1px solid #3c3c3c !important;background-color:#1e1e1e}[data-page^="admin-services-openclash"] img[src$="arrow-clockwise-light.svg"],[data-page^="admin-services-openclash"] img[src$="wrench-light.svg"],[data-page^="admin-services-openclash"] img[src$="eye-light.svg"]{filter:invert(.9)}[data-page="admin-network-diagnostics"] .cbi-section{background:#252526 !important}[data-page="admin-network-diagnostics"] textarea{background:transparent;border-radius:.25rem;color:#ccc;border:1px solid #3c3c3c !important}[data-page="admin-network-diagnostics"] .tr>.td{background-color:#252526 !important}[data-page="admin-network-network"] .ifacebox-head[style*="--zone-color-rgb: 144, 240, 144"]{background-color:#497e49 !important;color:#ccc !important}[data-page="admin-network-network"] .ifacebox-head[style*="--zone-color-rgb: 240, 144, 144;"]{background-color:#9c4f4f !important;color:#ccc !important}[data-page="admin-network-network"] .ifacebox-head[style*="--zone-color-rgb: 238, 238, 238;"]{background-color:#666 !important}[data-page="admin-network-network"] #modal_overlay>.modal.cbi-modal>div>p>textarea{border:1px solid #3c3c3c !important;background-color:transparent !important}[data-page="admin-network-network"] #modal_overlay div[data-tab="peers"] div[data-name="_peers"] .ifacebadge code{color:#fff !important;background-color:transparent !important}[data-page="admin-network-network"] #modal_overlay>.modal.cbi-modal>div>p>textarea{color:#ccc !important}[data-page="admin-network-firewall-rules"] #cbi-firewall-rule .zonebadge[style*="--zone-color-rgb:240, 144, 144;"]{--zone-color-rgb:156, 79, 79 !important}[data-page="admin-network-firewall-rules"] #cbi-firewall-rule .zonebadge[style*="--zone-color-rgb:240, 144, 144;"] strong{color:#ccc !important}[data-page="admin-network-firewall-rules"] #cbi-firewall-rule .zonebadge[style*="--zone-color-rgb:238, 238, 238;"]{--zone-color-rgb:112, 112, 112 !important}[data-page="admin-network-firewall-rules"] #cbi-firewall-rule .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] em{color:#ccc !important}[data-page="admin-network-firewall-rules"] #cbi-firewall-rule>table>.cbi-section-table-row[data-title]::before{background-color:#252526 !important}[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:240, 144, 144;"],[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:240, 144, 144;"]{--zone-color-rgb:156, 79, 79 !important;color:#ccc !important}[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:240, 144, 144;"] strong,[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:240, 144, 144;"] strong{color:#ccc !important}[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:144, 240, 144;"],[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:144, 240, 144;"]{--zone-color-rgb:73, 126, 73 !important;color:#ccc !important}[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:144, 240, 144;"] strong,[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:144, 240, 144;"] strong{color:#ccc !important}[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:238, 238, 238;"],[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:238, 238, 238;"]{--zone-color-rgb:112, 112, 112 !important}[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] strong,[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] strong{color:#ccc !important}[data-page="admin-network-network"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] strong+span,[data-page="admin-network-firewall-rules"] .cbi-dropdown .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] strong+span{color:#ccc !important}[data-page="admin-system-commands"] .commandbox,[data-page="admin-system-commands-dashboard"] .commandbox{border-bottom:thin solid #333;background:#252526;box-shadow:inset 0 1px 0 rgba(0,0,0,0.2),0 1px 2px rgba(255,255,255,0.05)}.btn{background-color:#707070;color:#fff}
\ No newline at end of file
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/favicon.ico b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/favicon.ico
new file mode 100644
index 0000000..79c8f86
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/favicon.ico differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/GoogleSans-Regular.woff b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/GoogleSans-Regular.woff
new file mode 100644
index 0000000..ddd2122
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/GoogleSans-Regular.woff differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/GoogleSans-Regular.woff2 b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/GoogleSans-Regular.woff2
new file mode 100644
index 0000000..64b6ac6
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/GoogleSans-Regular.woff2 differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/TypoGraphica.woff b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/TypoGraphica.woff
new file mode 100644
index 0000000..fef38ca
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/TypoGraphica.woff differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/TypoGraphica.woff2 b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/TypoGraphica.woff2
new file mode 100644
index 0000000..2b8ff0c
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/TypoGraphica.woff2 differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/argon.woff b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/argon.woff
new file mode 100644
index 0000000..ef1e9c2
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/argon.woff differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/argon.woff2 b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/argon.woff2
new file mode 100644
index 0000000..f38c524
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/fonts/argon.woff2 differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/android-icon-192x192.png b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/android-icon-192x192.png
new file mode 100644
index 0000000..7bfe4e5
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/android-icon-192x192.png differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/apple-icon-144x144.png b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/apple-icon-144x144.png
new file mode 100644
index 0000000..8d523df
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/apple-icon-144x144.png differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/apple-icon-60x60.png b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/apple-icon-60x60.png
new file mode 100644
index 0000000..4c80656
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/apple-icon-60x60.png differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/apple-icon-72x72.png b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/apple-icon-72x72.png
new file mode 100644
index 0000000..0775302
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/apple-icon-72x72.png differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/arrow.svg b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/arrow.svg
new file mode 100644
index 0000000..a20e798
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/arrow.svg
@@ -0,0 +1,3 @@
+
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/browserconfig.xml b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/browserconfig.xml
new file mode 100644
index 0000000..c554148
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/browserconfig.xml
@@ -0,0 +1,2 @@
+
+#ffffff
\ No newline at end of file
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/favicon-16x16.png b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/favicon-16x16.png
new file mode 100644
index 0000000..88bf329
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/favicon-16x16.png differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/favicon-32x32.png b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/favicon-32x32.png
new file mode 100644
index 0000000..d524df3
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/favicon-32x32.png differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/favicon-96x96.png b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/favicon-96x96.png
new file mode 100644
index 0000000..6c8e887
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/favicon-96x96.png differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/manifest.json b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/manifest.json
new file mode 100644
index 0000000..d4b84f1
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/manifest.json
@@ -0,0 +1,41 @@
+{
+ "name": "Openwrt",
+ "icons": [
+ {
+ "src": "\/android-icon-36x36.png",
+ "sizes": "36x36",
+ "type": "image\/png",
+ "density": "0.75"
+ },
+ {
+ "src": "\/android-icon-48x48.png",
+ "sizes": "48x48",
+ "type": "image\/png",
+ "density": "1.0"
+ },
+ {
+ "src": "\/android-icon-72x72.png",
+ "sizes": "72x72",
+ "type": "image\/png",
+ "density": "1.5"
+ },
+ {
+ "src": "\/android-icon-96x96.png",
+ "sizes": "96x96",
+ "type": "image\/png",
+ "density": "2.0"
+ },
+ {
+ "src": "\/android-icon-144x144.png",
+ "sizes": "144x144",
+ "type": "image\/png",
+ "density": "3.0"
+ },
+ {
+ "src": "\/android-icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image\/png",
+ "density": "4.0"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/ms-icon-144x144.png b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/ms-icon-144x144.png
new file mode 100644
index 0000000..8d523df
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/ms-icon-144x144.png differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/spinner.svg b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/spinner.svg
new file mode 100644
index 0000000..caef4b7
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/icon/spinner.svg
@@ -0,0 +1,3 @@
+
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/argon.svg b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/argon.svg
new file mode 100644
index 0000000..62f9a86
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/argon.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/bg1.jpg b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/bg1.jpg
new file mode 100644
index 0000000..26a52a5
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/bg1.jpg differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/blank.png b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/blank.png
new file mode 100644
index 0000000..9f4100e
Binary files /dev/null and b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/blank.png differ
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/volume_high.svg b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/volume_high.svg
new file mode 100644
index 0000000..ef84c3c
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/volume_high.svg
@@ -0,0 +1,3 @@
+
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/volume_off.svg b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/volume_off.svg
new file mode 100644
index 0000000..5467d4d
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/argon/img/volume_off.svg
@@ -0,0 +1,3 @@
+
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/resources/menu-argon.js b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/resources/menu-argon.js
new file mode 100644
index 0000000..3f4dd67
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/htdocs/luci-static/resources/menu-argon.js
@@ -0,0 +1,448 @@
+'use strict';
+'require baseclass';
+'require ui';
+
+/**
+ * Native JavaScript slide animation utilities
+ * Replaces jQuery slideUp/slideDown functionality with better performance
+ */
+const SlideAnimations = {
+ /**
+ * Animation durations in milliseconds
+ */
+ durations: {
+ fast: 200,
+ normal: 400,
+ slow: 600
+ },
+
+ /**
+ * Map to track running animations and their cleanup functions
+ */
+ runningAnimations: new WeakMap(),
+
+ /**
+ * Slide element down (show) with animation
+ * @param {Element} element - DOM element to animate
+ * @param {string|number} duration - Animation duration ('fast', 'normal', 'slow' or milliseconds)
+ * @param {function} callback - Optional callback function when animation completes
+ */
+ slideDown: function(element, duration, callback) {
+ if (!element) {
+ console.warn('SlideAnimations.slideDown: No element provided');
+ return;
+ }
+
+ // Stop any existing animation on this element
+ this.stop(element);
+
+ // Convert duration string to milliseconds
+ const animDuration = typeof duration === 'string' ?
+ this.durations[duration] || this.durations.normal :
+ (duration || this.durations.normal);
+
+ // Store original styles
+ const originalStyles = {
+ display: element.style.display,
+ overflow: element.style.overflow,
+ height: element.style.height,
+ transition: element.style.transition
+ };
+
+ // Set initial state for animation
+ element.style.display = 'block';
+ element.style.overflow = 'hidden';
+ element.style.height = '0px';
+ element.style.transition = `height ${animDuration}ms ease-out`;
+
+ // Force reflow to ensure initial state is applied
+ element.offsetHeight;
+
+ // Get the target height
+ const targetHeight = element.scrollHeight;
+
+ // Animate to full height
+ element.style.height = targetHeight + 'px';
+
+ // Set up cleanup function
+ const cleanup = () => {
+ element.style.height = originalStyles.height || '';
+ element.style.overflow = originalStyles.overflow || '';
+ element.style.transition = originalStyles.transition || '';
+
+ // Remove from running animations map
+ this.runningAnimations.delete(element);
+
+ if (callback && typeof callback === 'function') {
+ try {
+ callback.call(element);
+ } catch (e) {
+ console.error('SlideAnimations callback error:', e);
+ }
+ }
+ };
+
+ // Store cleanup function for potential cancellation
+ const timeoutId = setTimeout(cleanup, animDuration);
+ this.runningAnimations.set(element, { timeoutId, cleanup });
+ },
+
+ /**
+ * Slide element up (hide) with animation
+ * @param {Element} element - DOM element to animate
+ * @param {string|number} duration - Animation duration ('fast', 'normal', 'slow' or milliseconds)
+ * @param {function} callback - Optional callback function when animation completes
+ */
+ slideUp: function(element, duration, callback) {
+ if (!element) {
+ console.warn('SlideAnimations.slideUp: No element provided');
+ return;
+ }
+
+ // Stop any existing animation on this element
+ this.stop(element);
+
+ // Convert duration string to milliseconds
+ const animDuration = typeof duration === 'string' ?
+ this.durations[duration] || this.durations.normal :
+ (duration || this.durations.normal);
+
+ // Store original styles
+ const originalStyles = {
+ display: element.style.display,
+ overflow: element.style.overflow,
+ height: element.style.height,
+ transition: element.style.transition
+ };
+
+ // Get current height before hiding
+ const currentHeight = element.scrollHeight;
+
+ // Set initial state for animation
+ element.style.overflow = 'hidden';
+ element.style.height = currentHeight + 'px';
+ element.style.transition = `height ${animDuration}ms ease-out`;
+
+ // Force reflow to ensure initial state is applied
+ element.offsetHeight;
+
+ // Animate to zero height
+ element.style.height = '0px';
+
+ // Set up cleanup function
+ const cleanup = () => {
+ element.style.display = 'none';
+ element.style.height = originalStyles.height || '';
+ element.style.overflow = originalStyles.overflow || '';
+ element.style.transition = originalStyles.transition || '';
+
+ // Remove from running animations map
+ this.runningAnimations.delete(element);
+
+ if (callback && typeof callback === 'function') {
+ try {
+ callback.call(element);
+ } catch (e) {
+ console.error('SlideAnimations callback error:', e);
+ }
+ }
+ };
+
+ // Store cleanup function for potential cancellation
+ const timeoutId = setTimeout(cleanup, animDuration);
+ this.runningAnimations.set(element, { timeoutId, cleanup });
+ },
+
+ /**
+ * Stop all running animations on an element
+ * @param {Element} element - DOM element to stop animations on
+ */
+ stop: function(element) {
+ if (!element) return;
+
+ const animationData = this.runningAnimations.get(element);
+ if (animationData) {
+ // Clear the timeout
+ clearTimeout(animationData.timeoutId);
+
+ // Run cleanup immediately
+ animationData.cleanup();
+ }
+
+ // Clear transition to immediately stop any CSS animation
+ element.style.transition = '';
+
+ // Force reflow to apply changes immediately
+ element.offsetHeight;
+ },
+
+ /**
+ * Check if element has running animation
+ * @param {Element} element - DOM element to check
+ * @returns {boolean} - True if element has running animation
+ */
+ isAnimating: function(element) {
+ return this.runningAnimations.has(element);
+ }
+};
+
+/**
+ * Argon Theme Menu Module
+ * Handles rendering and interaction of the main navigation menu and sidebar
+ */
+return baseclass.extend({
+ /**
+ * Initialize the menu module
+ * Load menu data and trigger rendering
+ */
+ __init__: function () {
+ ui.menu.load().then(L.bind(this.render, this));
+ },
+
+ /**
+ * Main render function for the menu system
+ * @param {Object} tree - Menu tree structure from LuCI
+ */
+ render: function (tree) {
+ var node = tree,
+ url = '',
+ children = ui.menu.getChildren(tree);
+
+ // Find and render the active main menu item
+ for (var i = 0; i < children.length; i++) {
+ var isActive = (L.env.requestpath.length ? children[i].name == L.env.requestpath[0] : i == 0);
+
+ if (isActive) {
+ this.renderMainMenu(children[i], children[i].name);
+ }
+ }
+
+ // Render tab menu if we're deep enough in the navigation hierarchy
+ if (L.env.dispatchpath.length >= 3) {
+ for (var i = 0; i < 3 && node; i++) {
+ node = node.children[L.env.dispatchpath[i]];
+ url = url + (url ? '/' : '') + L.env.dispatchpath[i];
+ }
+
+ if (node) {
+ this.renderTabMenu(node, url);
+ }
+ }
+
+ // Attach event listeners for sidebar toggle functionality
+ var sidebarToggle = document.querySelector('a.showSide');
+ var darkMask = document.querySelector('.darkMask');
+
+ if (sidebarToggle) {
+ sidebarToggle.addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));
+ }
+ if (darkMask) {
+ darkMask.addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));
+ }
+ },
+
+ /**
+ * Handle menu expand/collapse functionality
+ * Manages the sliding animation and active states of menu items
+ * @param {Event} ev - Click event from menu item
+ */
+ handleMenuExpand: function (ev) {
+ var target = ev.target;
+ var slide = target.parentNode;
+ var slideMenu = target.nextElementSibling;
+ var shouldCollapse = false;
+
+ // Close all currently active submenus
+ var activeMenus = document.querySelectorAll('.main .main-left .nav > li > ul.active');
+ activeMenus.forEach(function (ul) {
+ // Stop any running animations and slide up
+ SlideAnimations.stop(ul);
+ // Remove active classes immediately when starting slideUp animation
+ ul.classList.remove('active');
+ ul.previousElementSibling.classList.remove('active');
+ SlideAnimations.slideUp(ul, 'fast');
+
+ // Check if we're clicking on an already open menu (should collapse it)
+ if (!shouldCollapse && ul === slideMenu) {
+ shouldCollapse = true;
+ }
+ });
+
+ // Exit if there's no submenu to show
+ if (!slideMenu) {
+ return;
+ }
+
+ // Open the submenu if it's not already open
+ if (!shouldCollapse) {
+ // Find the slide menu within the slide element
+ var slideMenuElement = slide.querySelector(".slide-menu");
+ if (slideMenuElement) {
+ // Add active classes immediately when starting slideDown animation
+ slideMenu.classList.add('active');
+ target.classList.add('active');
+ SlideAnimations.slideDown(slideMenuElement, 'fast');
+ }
+ target.blur(); // Remove focus from the clicked element
+ }
+
+ // Prevent default link behavior and event bubbling
+ ev.preventDefault();
+ ev.stopPropagation();
+ },
+
+ /**
+ * Render the main navigation menu
+ * Creates hierarchical menu structure with active states and click handlers
+ * @param {Object} tree - Menu tree node to render
+ * @param {string} url - Base URL for menu items
+ * @param {number} level - Current nesting level (0-based)
+ * @returns {Element} - Generated menu element
+ */
+ renderMainMenu: function (tree, url, level) {
+ var currentLevel = (level || 0) + 1;
+ var menuContainer = E('ul', { 'class': level ? 'slide-menu' : 'nav' });
+ var children = ui.menu.getChildren(tree);
+
+ // Don't render empty menus or menus deeper than 2 levels
+ if (children.length === 0 || currentLevel > 2) {
+ return E([]);
+ }
+
+ // Generate menu items for each child
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ var isActive = (
+ (L.env.dispatchpath[currentLevel] === child.name) &&
+ (L.env.dispatchpath[currentLevel - 1] === tree.name)
+ );
+
+ // Recursively render submenu
+ var submenu = this.renderMainMenu(child, url + '/' + child.name, currentLevel);
+ var hasChildren = submenu.children.length > 0;
+
+ // Determine CSS classes based on state
+ var slideClass = hasChildren ? 'slide' : null;
+ var menuClass = hasChildren ? 'menu' : 'food';
+
+ if (isActive) {
+ menuContainer.classList.add('active');
+ slideClass += " active";
+ menuClass += " active";
+ }
+
+ // Create menu item with link and submenu
+ var menuItem = E('li', { 'class': slideClass }, [
+ E('a', {
+ 'href': L.url(url, child.name),
+ 'click': (currentLevel === 1) ? ui.createHandlerFn(this, 'handleMenuExpand') : null,
+ 'class': menuClass,
+ 'data-title': child.title.replace(/ /g, "_"), // More robust space replacement
+ }, [_(child.title)]),
+ submenu
+ ]);
+
+ menuContainer.appendChild(menuItem);
+ }
+
+ // Append to main menu container if this is the top level
+ if (currentLevel === 1) {
+ var mainMenuElement = document.querySelector('#mainmenu');
+ if (mainMenuElement) {
+ mainMenuElement.appendChild(menuContainer);
+ mainMenuElement.style.display = '';
+ }
+ }
+
+ return menuContainer;
+ },
+
+ /**
+ * Render tab navigation menu
+ * Creates horizontal tab menu for deeper navigation levels
+ * @param {Object} tree - Menu tree node to render
+ * @param {string} url - Base URL for tab items
+ * @param {number} level - Current nesting level (0-based)
+ * @returns {Element} - Generated tab menu element
+ */
+ renderTabMenu: function (tree, url, level) {
+ var container = document.querySelector('#tabmenu');
+ var currentLevel = (level || 0) + 1;
+ var tabContainer = E('ul', { 'class': 'tabs' });
+ var children = ui.menu.getChildren(tree);
+ var activeNode = null;
+
+ // Don't render empty tab menus
+ if (children.length === 0) {
+ return E([]);
+ }
+
+ // Generate tab items for each child
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ var isActive = (L.env.dispatchpath[currentLevel + 2] === child.name);
+ var activeClass = isActive ? ' active' : '';
+ var className = 'tabmenu-item-%s %s'.format(child.name, activeClass);
+
+ var tabItem = E('li', { 'class': className }, [
+ E('a', { 'href': L.url(url, child.name) }, [_(child.title)])
+ ]);
+
+ tabContainer.appendChild(tabItem);
+
+ // Store reference to active node for recursive rendering
+ if (isActive) {
+ activeNode = child;
+ }
+ }
+
+ // Append tab container to main tab menu element
+ if (container) {
+ container.appendChild(tabContainer);
+ container.style.display = '';
+
+ // Recursively render nested tab menus if there's an active node
+ if (activeNode) {
+ var nestedTabs = this.renderTabMenu(activeNode, url + '/' + activeNode.name, currentLevel);
+ if (nestedTabs.children.length > 0) {
+ container.appendChild(nestedTabs);
+ }
+ }
+ }
+
+ return tabContainer;
+ },
+
+ /**
+ * Handle sidebar toggle functionality
+ * Toggles the mobile/responsive sidebar menu visibility
+ * @param {Event} ev - Click event from sidebar toggle button or dark mask
+ */
+ handleSidebarToggle: function (ev) {
+ var showSideButton = document.querySelector('a.showSide');
+ var sidebar = document.querySelector('#mainmenu');
+ var darkMask = document.querySelector('.darkMask');
+ var scrollbarArea = document.querySelector('.main-right');
+
+ // Check if any required elements are missing
+ if (!showSideButton || !sidebar || !darkMask || !scrollbarArea) {
+ console.warn('Sidebar toggle elements not found');
+ return;
+ }
+
+ // Toggle sidebar visibility and related states
+ if (showSideButton.classList.contains('active')) {
+ // Close sidebar
+ showSideButton.classList.remove('active');
+ sidebar.classList.remove('active');
+ scrollbarArea.classList.remove('active');
+ darkMask.classList.remove('active');
+ } else {
+ // Open sidebar
+ showSideButton.classList.add('active');
+ sidebar.classList.add('active');
+ scrollbarArea.classList.add('active');
+ darkMask.classList.add('active');
+ }
+ }
+});
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/cascade.less b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/cascade.less
new file mode 100644
index 0000000..3c51757
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/cascade.less
@@ -0,0 +1,2500 @@
+// out: ../htdocs/luci-static/argon/css/cascade.css, compress: false , sourceMap: false
+/**
+ * Argon is a clean HTML5 theme for LuCI. It is based on luci-theme-material and Argon Template
+ *
+ * luci-theme-argon
+ * Copyright 2025 Jerrykuku
+ *
+ * Have a bug? Please create an issue here on GitHub!
+ * https://github.com/jerrykuku/luci-theme-argon/issues
+ *
+ * luci-theme-material:
+ * Copyright 2015 Lutty Yang
+ * https://github.com/LuttyYang/luci-theme-material/
+ *
+ * Agron Theme
+ * https://demos.creative-tim.com/argon-dashboard/index.html
+ *
+ * Login background
+ * https://unsplash.com/
+ * Font generate by Icomoon
+ *
+ * Licensed to the public under the Apache License 2.0
+ */
+
+:root {
+ --primary: #5e72e4;
+ --dark-primary: #483d8b;
+ --header-color: #fff;
+ --menu-bg-color: #ffffff;
+ --menu-color: #5f6368;
+ --main-menu-color: #202124;
+ --red: #f5365c;
+ --orange: #fb6340;
+ --yellow: #ffd600;
+ --green: #2dce89;
+ --teal: #11cdef;
+ --cyan: #2bffc6;
+ --white: #fff;
+ --gray: #8898aa;
+ --gray-dark: #32325d;
+ --light: #ced4da;
+ --lighter: #e9ecef;
+
+ --success: #2dce89;
+ --info: #11cdef;
+ --warning: #fb6340;
+ --danger: #f5365c;
+
+ --light: #adb5bd;
+ --dark: #212529;
+ --default: #172b4d;
+ --white: #fff;
+ --darker: black;
+ --background-color: #f4f5f7;
+ --login-form-bg-color: rgba(244, 245, 247, 0.8);
+ --blur-radius: 10px;
+ --blur-opacity: 0.5;
+ --blur-radius-dark: 10px;
+ --blur-opacity-dark: 0.5;
+ --font-family-sans-serif: "Google Sans", "Microsoft Yahei", "WenQuanYi Micro Hei", "sans-serif", "Helvetica Neue", "Helvetica", "Hiragino Sans GB";
+ --dropdown-arrow-icon: url("data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJjdXJyZW50Q29sb3IiIGQ9Ik01LjI5MyA5LjI5M2ExIDEgMCAwIDEgMS40MTQgMEwxMiAxNC41ODZsNS4yOTMtNS4yOTNhMSAxIDAgMSAxIDEuNDE0IDEuNDE0bC02IDZhMSAxIDAgMCAxLTEuNDE0IDBsLTYtNmExIDEgMCAwIDEgMC0xLjQxNCIvPjwvc3ZnPg==");
+}
+
+/*
+ * Include base and custom css
+ */
+@import url("fonts.less");
+@import url("normalize.less");
+@import url("pure-min.less");
+
+// table
+
+.table {
+ position: relative;
+ display: table;
+}
+
+.tr {
+ display: table-row;
+}
+
+.thead {
+ display: table-header-group;
+}
+
+.tbody {
+ display: table-row-group;
+}
+
+.tfoot {
+ display: table-footer-group;
+}
+
+.td,
+.th {
+ line-height: normal;
+ display: table-cell;
+ padding: .5em;
+ text-align: center;
+ vertical-align: middle;
+}
+
+.th {
+ font-weight: bold;
+ white-space: nowrap;
+}
+
+.tr.placeholder {
+ height: 4em;
+}
+
+.tr.placeholder>.td {
+ line-height: 3;
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ padding: 0.4rem 0 !important;
+ text-align: center !important;
+ background: inherit;
+}
+
+.td[width="33%"] {
+ padding: 1.1em 1.5rem;
+}
+
+.table[width="33%"],
+.th[width="33%"],
+.td[width="33%"] {
+ width: 33%;
+}
+
+.table[width="100%"],
+.th[width="100%"],
+.td[width="100%"] {
+ width: 100%;
+}
+
+
+.btn,
+button,
+select,
+input,
+.cbi-dropdown {
+ line-height: 1.5rem;
+ height: 2.5rem;
+ padding: .5rem .75rem;
+ color: #8898aa;
+ border: 1px solid #dee2e6;
+ border-radius: .25rem;
+ outline: 0;
+ background-image: none;
+ box-shadow: none;
+ transition: box-shadow .15s ease;
+}
+
+
+
+select {
+ padding-right: 1.5rem;
+ background-image: url("data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSIjODg5OGFhIiBkPSJNNS4yOTMgOS4yOTNhMSAxIDAgMCAxIDEuNDE0IDBMMTIgMTQuNTg2bDUuMjkzLTUuMjkzYTEgMSAwIDEgMSAxLjQxNCAxLjQxNGwtNiA2YTEgMSAwIDAgMS0xLjQxNCAwbC02LTZhMSAxIDAgMCAxIDAtMS40MTQiLz48L3N2Zz4=");
+ background-size: 1rem;
+ background-position: right 0.5rem center;
+ background-repeat: no-repeat;
+ appearance: none;
+}
+
+select:not([multiple="multiple"]):focus,
+input:not(.cbi-button):focus,
+.cbi-dropdown:focus {
+ border-color: var(--primary);
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+ ;
+}
+
+.cbi-dropdown,
+select[multiple="multiple"] {
+ height: auto;
+}
+
+
+@import url("sysauth.less");
+@import url("layout.less");
+
+#xhr_poll_status {
+ display: flex;
+ margin-left: 0.5rem;
+
+ * {
+ color: #fff;
+ }
+}
+
+
+// alert
+
+.danger {
+ background-color: var(--danger) !important;
+}
+
+.warning {
+ background-color: var(--warning) !important;
+}
+
+.success {
+ background-color: var(--success) !important;
+}
+
+.notice {
+ background-color: var(--info) !important;
+ color: #fff;
+}
+
+.error {
+ color: var(--danger);
+}
+
+.alert,
+.alert-message {
+ position: fixed;
+ width: 20rem;
+ z-index: 9000;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ font-weight: bold;
+ padding: 1rem 1.25rem;
+ border: 0;
+ border-radius: 0.25rem !important;
+ background-color: #fff;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12);
+ text-shadow: none;
+
+
+ &.error {
+ background-color: var(--warning);
+ }
+
+ h4 {
+ padding: 0.25rem 0rem;
+ border-radius: 0.25rem;
+ }
+
+ .btn {
+ height: auto;
+ }
+}
+
+.modal.alert-message {
+ position: static;
+ width: 90%;
+ min-width: 270px;
+ max-width: 600px;
+ left: auto;
+ top: auto;
+ transform: none;
+ margin: 5em auto;
+}
+
+.alert-message>h4 {
+ font-size: 110%;
+ font-weight: bold;
+}
+
+.alert-message>* {
+ margin: .5rem 0;
+}
+
+.alert-message .btn {
+ padding: .3rem .6rem;
+}
+
+.container .alert,
+.container .alert-message {
+ margin-left: 0;
+ margin-right: 0;
+ margin-top: 0rem;
+ position: relative;
+ top: 0;
+ transform:translate(-50%, 0);
+ width: 100%;
+}
+
+#mwan3-service-status > .alert-message {
+ left: 0.5rem;
+ transform: none;
+}
+
+.lg {
+ margin: 0;
+ padding: 0 !important;
+}
+
+.cbi-section,
+.cbi-section-error,
+#iptables,
+.Firewall form,
+#cbi-network>.cbi-section-node,
+#cbi-wireless>.cbi-section-node,
+#cbi-wireless>#wifi_assoclist_table,
+[data-tab-title],
+[data-page^="admin-system-admin"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear),
+[data-page="admin-system-opkg"] #maincontent>.container {
+ font-family: inherit;
+ font-weight: normal;
+ font-style: normal;
+ line-height: normal;
+ min-width: inherit;
+ padding: 0;
+ border: 0;
+
+ border-radius: 0.25rem;
+ background-color: #fff;
+ box-shadow: 0 0 1rem 0 rgba(136, 152, 170, .15);
+
+ &:last-child {
+ margin: 0;
+ border: 0;
+ }
+
+}
+
+.cbi-modal .cbi-section,
+.cbi-section .cbi-section {
+ padding: 0;
+ box-shadow: none;
+}
+
+.cbi-modal .cbi-tabmenu {
+ margin-left: 0;
+}
+
+.cbi-map {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.cbi-map>.cbi-tabmenu+div {
+ margin-top: -0.4375rem;
+}
+
+.cbi-map-descr {
+ font-size: small;
+ line-height: 1.5;
+ padding: 0 1.25rem;
+}
+
+.cbi-section {
+
+ &>.cbi-section-descr {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+
+ &:empty {
+ display: none;
+ }
+ }
+
+}
+
+.cbi-section-descr:not(:empty) {
+ font-size: small;
+ line-height: 1.5;
+ padding: 0rem 1rem;
+}
+
+.cbi-section-descr:empty {
+ display: none !important;
+}
+
+.cbi-map-descr+fieldset {
+ margin-top: 1rem;
+}
+
+.cbi-map-descr>abbr {
+ cursor: help;
+ text-decoration: underline;
+}
+
+.cbi-section>legend {
+ display: none !important;
+}
+
+fieldset>fieldset,
+.cbi-section>.cbi-section {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ box-shadow: none;
+}
+
+.cbi-section>h3:first-child,
+.panel-title {
+ font-size: 1.1rem;
+ line-height: 1;
+ display: block;
+ width: 100%;
+ margin: 0;
+ margin-bottom: 0;
+ padding: 0.8755rem 1.25rem;
+ color: #32325d;
+ color: var(--gray-dark);
+
+}
+
+.cbi-section>h3:first-child,
+.cbi-section>h4:first-child,
+.cbi-section>p:first-child,
+[data-tab-title]>h3:first-child,
+[data-tab-title]>h4:first-child,
+[data-tab-title]>p:first-child {
+ padding: 1rem 1.25rem;
+}
+
+.cbi-section p {
+ padding: 1rem;
+}
+
+.cbi-tblsection {
+ overflow-x: auto;
+}
+
+table {
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+
+table,
+.table {
+ overflow-y: hidden;
+ width: 100%;
+ font-size: 0.75rem;
+}
+
+.table .table-titles th {
+ background-color: var(--lighter);
+}
+
+table>tbody>tr>td,
+table>tbody>tr>th,
+table>tfoot>tr>td,
+table>tfoot>tr>th,
+table>thead>tr>td,
+table>thead>tr>th,
+.table>.tbody>.tr>.td,
+.table>.tbody>.tr>.th,
+.table>.tfoot>.tr>.td,
+.table>.tfoot>.tr>.th,
+.table>.thead>.tr>.td,
+.table>.thead>.tr>.th,
+.table>.tr>.td.cbi-value-field,
+.table>.tr>.th.cbi-section-table-cell {
+ padding: .5rem;
+}
+
+.container>.cbi-section:first-of-type>.table[width="100%"]>.tr>.td {
+ padding: .6rem;
+}
+
+.cbi-section-table-cell {
+ line-height: 1.1;
+ align-self: flex-end;
+ flex: 1 1 auto;
+}
+
+tr>td,
+tr>th,
+.tr>.td,
+.tr>.th,
+.cbi-section-table-row::before,
+#cbi-wireless>#wifi_assoclist_table>.tr:nth-child(2) {
+ border-top: thin solid #ddd;
+ padding: 1.1em 1.25rem;
+}
+
+#cbi-wireless .td,
+.table[width="100%"]>.tr:first-child>.td,
+[data-page="admin-network-diagnostics"] .tr>.td,
+.tr.table-titles>.th,
+.tr.cbi-section-table-titles>.th {
+ border-top: 0 !important;
+ background-color: #f6f9fc;
+ padding: 1.1em 1.25rem;
+ line-height: 1.3rem;
+}
+
+
+
+
+
+#cbi-network .tr:first-child>.td {
+ border-top: 0;
+}
+
+.table[width="100%"]>.tr:first-child>.td {
+ margin: auto 0;
+}
+
+.cbi-section-table-row {
+ margin-bottom: 1rem;
+ text-align: center !important;
+ background: #f4f4f4;
+}
+
+.cbi-section-table-row:last-child {
+ margin-bottom: 0;
+}
+
+.cbi-section-table-row>.cbi-value-field .cbi-dropdown,
+.cbi-section-table-row>.cbi-value-field .cbi-input-select,
+.cbi-section-table-row>.cbi-value-field .cbi-input-text,
+.cbi-section-table-row>.cbi-value-field .cbi-input-password {
+ width: 100%;
+}
+
+.cbi-section-table-row>.cbi-value-field .cbi-input-text,
+.cbi-section-table-row>.cbi-value-field .cbi-input-password {
+ min-width: 80px;
+}
+
+.cbi-section-table-row>.cbi-value-field [data-dynlist]>input,
+.cbi-section-table-row>.cbi-value-field input.cbi-input-password {
+ width: calc(100% - 1.5rem);
+}
+
+.cbi-section-table-row .td {
+ text-align: center !important;
+
+ .cbi-checkbox {
+ input[type="checkbox"] {
+ margin: 0;
+ }
+ }
+}
+
+.control-group {
+ display: inline-flex;
+ width: 100%;
+ flex-wrap: wrap;
+ gap: 1rem;
+
+ input {
+ border-right-width: 0;
+ margin-right: 0;
+ }
+
+ input+button {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+ margin-left: 0;
+ border-left-width: 0;
+ }
+}
+
+.control-group:has(> input:first-child + .cbi-button) {
+ gap: 0 !important;
+
+ input {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ width: 15.5rem;
+ min-width: 15.5rem;
+ }
+}
+
+
+
+.control-group>* {
+ vertical-align: middle;
+}
+
+div>table>tbody>tr:nth-of-type(2n),
+div>.table>.tr:nth-of-type(2n) {
+ background-color: #f9f9f9;
+}
+
+/* fix multiple table */
+table table,
+.table .table,
+.cbi-value-field table,
+.cbi-value-field .table,
+td>table>tbody>tr>td,
+.td>.table>.tbody>.tr>.td,
+.cbi-value-field>table>tbody>tr>td,
+.cbi-value-field>.table>.tbody>.tr>.td {
+ border: 0;
+}
+
+/* button style */
+.btn,
+.cbi-button,
+.item::after {
+ font-size: .875rem;
+ display: inline-block;
+ width: auto !important;
+ padding: 0.5rem .75rem;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ transition: all .2s ease-in-out;
+ text-align: center;
+ vertical-align: middle;
+ white-space: nowrap;
+ text-decoration: none;
+ border: 0;
+ border-radius: .25rem;
+ background-color: #f0f0f0;
+ background-image: none;
+ appearance: none;
+ -ms-touch-action: manipulation;
+ touch-action: manipulation;
+}
+
+
+.btn:not(button) ul:not(.dropdown) li {
+ padding: 0;
+}
+
+.cbi-button-up,
+.cbi-button-down {
+ display: inline-block;
+ min-width: 0;
+ padding: .2rem 1rem;
+ font-size: 0;
+ color: transparent !important;
+ background: url(../icon/arrow.svg) no-repeat center;
+ background-size: 12px 20px;
+}
+
+.cbi-button-up {
+ transform: scaleY(-1);
+}
+
+.cbi-button:not(select) {
+ appearance: none !important;
+}
+
+.btn:hover,
+.btn:focus,
+.btn:active,
+.cbi-button:hover,
+.cbi-button:focus,
+.cbi-button:active,
+.item:hover::after,
+.item:focus::after,
+.item:active::after,
+.cbi-page-actions .cbi-button-apply+.cbi-button-save:hover,
+.cbi-page-actions .cbi-button-apply+.cbi-button-save:focus,
+.cbi-page-actions .cbi-button-apply+.cbi-button-save:active {
+ text-decoration: none;
+ outline: 0;
+}
+
+.btn:hover,
+.btn:focus,
+.cbi-button:hover,
+.cbi-button:focus,
+.item:hover::after,
+.item:focus::after {
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+}
+
+.btn:active,
+.cbi-button:active,
+.item:active::after {
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+}
+
+.cbi-button-up:hover,
+.cbi-button-up:focus {
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+}
+
+.cbi-button-up:active {
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+}
+
+.btn:disabled,
+.cbi-button:disabled {
+ cursor: not-allowed;
+ pointer-events: none;
+ opacity: .5;
+ box-shadow: none;
+}
+
+/* gray */
+.alert-message [class="btn"],
+.modal div[class="btn"],
+.cbi-button-find,
+.cbi-button-link,
+.cbi-button-up,
+.cbi-button-down,
+.cbi-button-neutral,
+.cbi-button[name="zero"],
+.cbi-button[name="restart"],
+.cbi-button[onclick="hide_empty(this)"] {
+ color: #fff;
+ border: thin solid #8898aa;
+ background-color: #8898aa;
+}
+
+/* dark blue */
+.btn.primary,
+.cbi-page-actions .cbi-button-save,
+.cbi-page-actions .cbi-button-apply+.cbi-button-save,
+.cbi-button-add,
+.cbi-button-save,
+.cbi-button-positive,
+.cbi-button-link,
+.cbi-button[value="Enable"],
+.cbi-button[value="Scan"],
+.cbi-button[value^="Back"],
+.cbi-button-neutral[onclick="handleConfig(event)"] {
+ font-weight: normal;
+ color: #fff !important;
+ border: thin solid #5e72e4;
+ border: thin solid var(--primary);
+ background-color: #5e72e4;
+ background-color: var(--primary);
+ ;
+}
+
+/* light blue */
+.cbi-page-actions .cbi-button-apply,
+.cbi-section-actions .cbi-button-edit,
+.cbi-button-edit,
+.cbi-button-apply,
+.cbi-button-reload,
+.cbi-button-action,
+.cbi-button[value="Submit"],
+.cbi-button[value="Upload"],
+.cbi-button[value$="Apply"],
+.cbi-button[onclick="addKey(event)"] {
+ font-weight: normal;
+ color: #fff !important;
+ border: thin solid #5e72e4;
+ border: thin solid var(--primary);
+
+ background-color: #5e72e4;
+ background-color: var(--primary);
+}
+
+/* red */
+.btn.danger,
+.cbi-section-remove>.cbi-button,
+.cbi-button-remove,
+.cbi-button-reset,
+.cbi-button-negative,
+.cbi-button[value="Stop"],
+.cbi-button[value="Kill"],
+.cbi-button[onclick="reboot(this)"],
+.cbi-button-neutral[value="Restart"] {
+ font-weight: normal;
+ color: #fff;
+ border: thin solid #f5365c;
+ border: thin solid var(--red);
+ background-color: #f5365c;
+ background-color: var(--red);
+}
+
+/* yellow */
+.btn[value="Dismiss"],
+.cbi-button[value="Terminate"],
+.cbi-button[value="Reset"],
+.cbi-button[value="Disabled"],
+.cbi-button[onclick^="iface_reconnect"],
+.cbi-button[onclick="handleReset(event)"],
+.cbi-button-neutral[value="Disable"] {
+ font-weight: normal;
+ color: var(--white);
+ border: thin solid #eea236;
+ background-color: #f0ad4e;
+}
+
+/* green */
+.cbi-button-success,
+.cbi-button-download {
+ font-weight: normal;
+ color: var(--white);
+ border: thin solid #4cae4c;
+ background-color: #5cb85c;
+}
+
+.cbi-page-actions {
+ .cbi-button-link:first-child {
+ float: left;
+ }
+
+ .cbi-dropdown {
+ .open {
+ color: var(--white);
+ }
+
+ .more {
+ display: block;
+ width: 1px;
+ height: 100%;
+ overflow: hidden;
+ text-indent: 5000px;
+ background-color: var(--white)
+ }
+ }
+}
+
+
+.a-to-btn {
+ text-decoration: none;
+}
+
+.cbi-value-field .cbi-button-add {
+ font-weight: bold;
+ padding: 1px 6px;
+ display: inline-block;
+ align-items: center;
+}
+
+.tabs {
+ padding: 0 1rem;
+ background-color: #FFFFFF;
+ border-radius: 0.25rem;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, .03);
+ white-space: nowrap;
+ overflow-x: auto;
+
+ &::-webkit-scrollbar {
+ width: 1px;
+ height: 5px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: #f6f9fc
+ }
+
+ &::-webkit-scrollbar-track {
+ background-color: #fff;
+ }
+
+ li[class~="active"],
+ li:hover {
+ cursor: pointer;
+ border-bottom: 0.18751rem solid #5e72e4;
+ border-bottom: 0.18751rem solid var(--primary);
+ color: #5e72e4;
+ color: var(--primary);
+ background-color: #e4e9ee;
+ margin-bottom: 0;
+ border-radius: 0;
+
+ a {
+ color: #5e72e4;
+ color: var(--primary);
+ }
+ }
+
+ li {
+ font-size: 0.875rem;
+ display: inline-block;
+ padding: 0.875rem 0rem;
+ border-bottom: 0.18751rem solid rgba(0, 0, 0, 0);
+ margin: 0;
+ transition: all 0.2s;
+
+ a {
+ text-decoration: none;
+ color: #404040;
+ padding: 0.5rem 0.8rem;
+ }
+
+ &:hover {
+ border-bottom: 0.18751rem solid #5e72e4;
+ border-bottom: 0.18751rem solid var(--primary);
+ }
+ }
+}
+
+
+.cbi-tabmenu {
+ color: white;
+ padding: 0.5rem 1rem 0 1rem;
+ white-space: nowrap;
+ overflow-x: auto;
+
+ &::-webkit-scrollbar {
+ width: 1px;
+ height: 5px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: #f6f9fc
+ }
+
+ &::-webkit-scrollbar-track {
+ background-color: #fff;
+ }
+
+ li {
+ background: #dce3e9;
+ display: inline-block;
+ font-size: 0.875rem;
+ border-top-left-radius: 0.25rem;
+ border-top-right-radius: 0.25rem;
+ padding: 0.5rem 0rem;
+ border-bottom: 0.18751rem solid rgba(0, 0, 0, 0);
+ margin: 0 0.2rem;
+ position: relative;
+
+ a {
+ text-decoration: none;
+ color: #404040;
+ padding: 0.5rem 0.8rem;
+ }
+
+ &:hover {
+ cursor: pointer;
+ border-bottom: 0.18751rem solid #5e72e4;
+ border-bottom: 0.18751rem solid var(--primary);
+ color: #5e72e4;
+ color: var(--primary);
+ background-color: #e4e9ee;
+ margin-bottom: 0;
+
+ a {
+ color: #525f7f;
+ }
+ }
+ }
+
+ li[class~="cbi-tab"] {
+ border-bottom: 0.18751rem solid #5e72e4;
+ border-bottom: 0.18751rem solid var(--primary);
+ color: #5e72e4;
+ color: var(--primary);
+ background-color: #e4e9ee;
+ margin-bottom: 0;
+
+ a {
+ color: #5e72e4;
+ color: var(--primary);
+ }
+ }
+}
+
+.cbi-tab-descr {
+ padding: 0 1.25rem;
+}
+
+.cbi-section-node {
+ display: flex;
+ flex-direction: column;
+ gap: 0.875rem;
+
+ >[id^="cbi-config-"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.875rem;
+
+ &:first-child {
+ padding-top: 1rem;
+ }
+
+ &:last-child {
+ padding-bottom: 1rem;
+ }
+ }
+}
+
+.cbi-section .cbi-section-remove:nth-of-type(2n),
+.container>.cbi-section .cbi-section-node:nth-of-type(2n) {
+ background-color: #f9f9f9;
+}
+
+[data-tab-title] {
+ overflow: hidden;
+ height: 0;
+ opacity: 0;
+ margin: 0;
+ padding: 1rem 0;
+ display: none !important;
+
+ p {
+ margin-left: 1rem;
+ margin-bottom: 1rem;
+ }
+}
+
+[data-tab-active="true"] {
+ overflow: visible;
+ height: auto;
+ opacity: 1;
+ display: inherit !important;
+ transition: opacity .25s ease-in;
+ margin: inherit !important;
+}
+
+
+.cbi-section[id] .cbi-section-remove:nth-of-type(4n+3),
+.cbi-section[id] .cbi-section-node:nth-of-type(4n+4) {
+ background-color: #f9f9f9;
+}
+
+.cbi-section-node-tabbed {
+ margin-top: 0;
+ border: 0 solid #d4d4d4;
+ border-radius: 0.25rem;
+
+ &>div {
+ display: flex;
+
+ flex-direction: column;
+ gap: 0.875rem;
+ }
+}
+
+.cbi-tabcontainer>.cbi-value:nth-of-type(2n) {
+ background-color: #f9f9f9;
+}
+
+.cbi-value-field {
+ display: table-cell;
+
+ & div+br {
+ display: none;
+ }
+}
+
+.cbi-value-description {
+ line-height: 1.25;
+ display: table-cell;
+ padding: .5rem 0;
+ opacity: .5;
+ font-size: 0.75rem;
+
+ abbr {
+ color: #32325d;
+ color: var(--gray-dark);
+ }
+
+ &:empty {
+ display: none;
+ }
+}
+
+.cbi-value-title {
+ display: table-cell;
+ float: left;
+ width: 23rem;
+
+ padding-right: 2rem;
+ text-align: right;
+ word-wrap: break-word;
+}
+
+.cbi-value {
+ display: inline-block;
+ width: 100%;
+ padding: 0 1rem;
+ line-height: 2.4rem;
+
+ ul {
+ line-height: 1.25;
+ }
+}
+
+.cbi-value-field.cbi-dropdown-open {
+ .cbi-dropdown {
+ border-color: var(--primary);
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, .1);
+ }
+}
+
+.cbi-value-field .cbi-dropdown,
+.cbi-value-field .cbi-input-select,
+.cbi-value input[type="text"],
+.cbi-value input[type="password"],
+.cbi-value textarea {
+ min-width: 18rem;
+}
+
+
+
+
+#cbi-firewall-zone .cbi-input-select,
+#cbi-network-switch_vlan .cbi-input-select {
+ min-width: 11rem;
+}
+
+#cbi-network-switch_vlan .cbi-input-text {
+ max-width: 3rem;
+}
+
+.cbi-input-file {
+ appearance: auto !important;
+
+ &::-webkit-file-upload-button {
+ display: inline-block !important;
+ }
+}
+
+.cbi-input-invalid {
+ color: #f5365c !important;
+ border-color: #f5365c !important;
+}
+
+.cbi-section-error {
+ font-weight: bold;
+ line-height: 1.42857143;
+ margin: 18px;
+ padding: 6px;
+ border: thin solid #f5365c;
+ border-radius: 3px;
+ background-color: #fce6e6;
+}
+
+.cbi-section-error ul {
+ margin: 0 0 0 20px;
+}
+
+.cbi-section-error ul li {
+ font-weight: bold;
+ color: #f5365c;
+}
+
+.td[data-title]::before {
+ font-weight: bold;
+ display: none;
+ padding: .25rem 0;
+ content: attr(data-title) ":\20";
+ text-align: left;
+ white-space: nowrap;
+}
+
+.tr.placeholder .td[data-title]::before {
+ display: none;
+}
+
+.tr[data-title]::before,
+.tr.cbi-section-table-titles.named::before {
+ font-weight: bold;
+ display: table-cell;
+ align-self: center;
+ flex: 1 1 5%;
+ padding: .25rem;
+ content: attr(data-title) "\20";
+ text-align: center;
+ vertical-align: middle;
+ white-space: normal;
+ word-wrap: break-word;
+ background-color: #f6f9fc;
+}
+
+.cbi-rowstyle-1 {
+ background-color: #f9f9f9;
+}
+
+.cbi-rowstyle-2 {
+ background-color: #eee;
+}
+
+.cbi-rowstyle-2 .cbi-button-up,
+.cbi-rowstyle-2 .cbi-button-down,
+body:not(.Interfaces) .cbi-rowstyle-2:first-child {
+ background-color: #fff !important;
+}
+
+.cbi-section-table .cbi-section-table-titles .cbi-section-table-cell {
+ width: auto !important;
+}
+
+.td.cbi-section-actions {
+ text-align: right !important;
+ vertical-align: middle;
+}
+
+.td.cbi-section-actions>* {
+ display: inline-flex;
+ gap: 1rem;
+}
+
+.td.cbi-section-actions>*>*,
+.td.cbi-section-actions>*>form>* {
+ display: flex;
+ align-items: center;
+}
+
+.td.cbi-section-actions>*>form {
+ display: inline-flex;
+ margin: 0;
+}
+
+.td .cbi-checkbox {
+ justify-content: center;
+}
+
+.cbi-checkbox {
+ display: flex;
+ align-items: center;
+ height: 2.5rem;
+}
+
+/* lists */
+.cbi-dynlist {
+
+ line-height: 1.3;
+ flex-direction: column;
+ min-height: 30px;
+ cursor: text;
+ gap: 0.875rem;
+
+ &>.item {
+ display: inline-flex;
+ flex-wrap: nowrap;
+ position: relative;
+ max-width: 25rem;
+ pointer-events: none;
+ color: #8898aa;
+ outline: 0;
+
+ &::after {
+ content: "\00D7";
+ pointer-events: auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 2.5rem !important;
+ height: 2.5rem;
+ margin: 0;
+ font-weight: normal;
+ font-size: 1rem;
+ line-height: 1.5rem;
+
+ color: #fff;
+ border: 1px solid #f5365c;
+ border-radius: 0 0.25rem 0.25rem 0;
+ outline: 0;
+
+ background-color: var(--red);
+ background-image: none;
+ box-shadow: none;
+ box-sizing: border-box;
+ }
+
+ &>span {
+ display: block;
+ padding: 0.5rem 0.75rem;
+ box-sizing: border-box;
+ min-width: 15.5rem;
+ width: 15.5rem;
+ height: 2.5rem;
+ transition: box-shadow 0.15s ease;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ user-select: text;
+
+ white-space: nowrap;
+ word-break: break-word;
+ line-height: 1.5em;
+
+ color: #8898aa;
+ border: 1px solid #dee2e6;
+ border-radius: .25rem 0 0 .25rem;
+ outline: 0;
+ background-image: none;
+ box-shadow: none;
+ box-sizing: border-box;
+ transition: box-shadow .15s ease;
+
+ }
+ }
+
+ &>.add-item {
+ display: inline-flex;
+ align-items: center;
+ width: 100%;
+ min-width: 15.5rem;
+ flex-wrap: nowrap;
+
+ input {
+ display: block;
+ padding: 0.5rem 0.75rem;
+ box-sizing: border-box;
+ min-width: 15.5rem;
+ width: 15.5rem;
+ transition: box-shadow 0.15s ease;
+
+ white-space: nowrap;
+ word-break: break-word;
+ font-size: .875rem;
+ line-height: 1.5rem;
+
+ color: #8898aa;
+ border: 1px solid #dee2e6;
+ border-radius: .25rem 0 0 .25rem;
+ border-right-width: 0;
+ outline: 0;
+ background-image: none;
+ box-shadow: none;
+ }
+
+ .cbi-button {
+ display: flex;
+ width: auto !important;
+ padding-left: 0.8rem;
+ padding-right: 0.8rem;
+ margin-left: 0;
+ align-items: center;
+ justify-content: center;
+
+ font-size: 0.875rem;
+ line-height: 1.5rem;
+
+ outline: 0;
+ background-image: none;
+ background-color: var(--gray);
+ box-shadow: none;
+ color: var(--white);
+ border-color: var(--gray);
+ border-radius: 0.25rem;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+
+ .cbi-button-add {
+ width: 2.5rem !important;
+ padding: 0.5rem 0 !important;
+
+ font-weight: normal;
+ font-size: 1.2rem;
+
+ color: #fff;
+ background-color: var(--primary);
+ border: 1px solid var(--primary);
+ }
+
+
+ &:not([ondrop]) {
+ &>input {
+ overflow: hidden;
+ min-width: 15.5rem;
+ width: 15.5rem;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+}
+
+.cbi-dynlist>.item.drag-over,
+.cbi-dynlist>.add-item:has(.drag-over) {
+ border-top: 1px solid var(--red);
+ margin-top: -1px;
+}
+
+.cbi-dynlist[name="sshkeys"]>.item {
+ max-width: none;
+}
+
+
+.cbi-dynlist>.cbi-dynlist>.add-item[ondrop]>input {
+ min-width: 13rem;
+}
+
+.cbi-dynlist,
+.cbi-dropdown {
+ position: relative;
+ display: inline-flex;
+ padding-right: 0.25rem;
+ min-height: 2.1875rem;
+}
+
+.cbi-dropdown {
+ align-items: center;
+}
+
+.cbi-dropdown.btn>ul:not(.dropdown),
+.cbi-dropdown.cbi-button>ul:not(.dropdown) {
+ margin: 0 0.75rem;
+}
+
+.cbi-dropdown[placeholder*="select"] {
+ max-width: 25rem;
+ height: auto;
+ margin-top: -3px;
+}
+
+.cbi-dropdown>ul {
+ display: flex;
+ overflow-x: hidden;
+ overflow-y: auto;
+ width: 100%;
+ padding: 0;
+ list-style: none;
+ outline: 0;
+}
+
+
+.cbi-dropdown>ul.preview {
+ display: none;
+
+}
+
+.cbi-dropdown.cbi-button-apply,
+.cbi-dropdown.cbi-button-action {
+ padding: 0;
+}
+
+.cbi-button-apply>ul.preview {
+ display: none;
+
+ li {
+ color: #fff;
+ }
+
+}
+
+.cbi-button-apply>ul:first-child {
+ li {
+ color: #fff;
+ }
+
+}
+
+.cbi-dropdown>.open {
+ display: flex;
+ align-items: center;
+ width: 1rem;
+ height: 100%;
+ padding: 0 0.75rem;
+ cursor: pointer;
+ user-select: none;
+ font-size: 0;
+ color: #8898aa;
+ background-color: currentColor;
+ mask-image: var(--dropdown-arrow-icon);
+ mask-repeat: no-repeat;
+ mask-size: 100% 1rem;
+ mask-position: center;
+ mask-mode: match-source;
+}
+
+
+.cbi-dropdown>.more,
+.cbi-dropdown>ul>li[placeholder] {
+ display: none;
+}
+
+.cbi-dropdown>ul>li {
+ display: none;
+ overflow: hidden;
+ align-items: center;
+ align-self: center;
+ flex-grow: 1;
+ flex-shrink: 1;
+ min-height: 20px;
+ padding: 0.125rem .25em;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+
+}
+
+.cbi-dropdown>ul>li .hide-open {
+ display: initial;
+}
+
+.cbi-dropdown>ul>li .hide-close {
+ display: none;
+}
+
+.cbi-dropdown>ul>li[display]:not([display="0"]) {
+ border-left: thin solid #ccc;
+}
+
+.cbi-dropdown[empty]>ul {
+ max-width: 1px;
+}
+
+.cbi-dropdown>ul>li>form {
+ display: none;
+ margin: 0;
+ padding: 0;
+ pointer-events: none;
+}
+
+.cbi-dropdown>ul>li img {
+ margin-right: .25em;
+ vertical-align: middle;
+}
+
+.cbi-dropdown>ul>li>form>input[type="checkbox"] {
+ height: auto;
+ margin: 0;
+}
+
+.cbi-dropdown>ul>li input[type="text"] {
+ height: 2rem;
+ min-width: 16rem;
+ padding: 0 .5rem;
+}
+
+.cbi-dropdown[open]>ul.dropdown {
+ position: absolute;
+ z-index: 1100;
+ display: block;
+ width: auto;
+ min-width: 100%;
+ max-width: none;
+ max-height: 200px !important;
+ border: 0 solid #918e8c;
+ background: #ffffff;
+ box-shadow: 0 0 4px #918e8c;
+ border-radius: 0.25rem;
+ color: var(--main-menu-color);
+ margin-left: -0 !important;
+ margin-top: 0.25rem;
+ left: 0;
+
+ li {
+ color: #000;
+ }
+}
+
+
+.cbi-dropdown>ul>li[display],
+.cbi-dropdown[open]>ul.preview,
+.cbi-dropdown[open]>ul.dropdown>li,
+.cbi-dropdown[multiple]>ul>li>label,
+.cbi-dropdown[multiple][open]>ul.dropdown>li,
+.cbi-dropdown[multiple][more]>.more,
+.cbi-dropdown[multiple][empty]>.more {
+ display: flex;
+ align-items: center;
+ flex-grow: 1;
+
+}
+
+.cbi-dropdown[empty]>ul>li,
+.cbi-dropdown[optional][open]>ul.dropdown>li[placeholder],
+.cbi-dropdown[multiple][open]>ul.dropdown>li>form {
+ display: block;
+}
+
+.cbi-dropdown[open]>ul.dropdown>li .hide-open {
+ display: none;
+}
+
+.cbi-dropdown[open]>ul.dropdown>li .hide-close {
+ display: initial;
+}
+
+.cbi-dropdown[open]>ul.dropdown>li {
+ border-bottom: thin solid #ccc;
+ padding: 0.5rem 0.75rem;
+ cursor: pointer;
+}
+
+.cbi-dropdown[open]>ul.dropdown>li label {
+ margin-left: 0.5rem;
+}
+
+.cbi-dropdown[open]>ul.dropdown>li[selected] {
+ background: #e4e9ee;
+}
+
+.cbi-dropdown[open]>ul.dropdown>li.focus {
+ background: #e4e9ee;
+ outline: none;
+}
+
+.cbi-dropdown[open]>ul.dropdown>li:last-child {
+ margin-bottom: 0;
+ border-bottom: 0;
+}
+
+.cbi-dropdown[open]>ul.dropdown>li[unselectable] {
+ opacity: .7;
+}
+
+.cbi-dropdown[open]>ul.dropdown>li>input.create-item-input:first-child:last-child {
+ width: 100%;
+}
+
+.cbi-dropdown[disabled] {
+ pointer-events: none;
+ opacity: .6;
+}
+
+.cbi-dropdown .zonebadge {
+ width: 100%;
+}
+
+.cbi-dropdown[open] .zonebadge {
+ width: auto;
+}
+
+/* progressbar */
+.cbi-progressbar {
+ position: relative;
+ display: flex;
+ width: 100%;
+ font-size: 0.75rem;
+ background-color: #e9ecef;
+ border-radius: 0.5rem;
+ height: 1rem;
+ overflow: hidden;
+
+ &>div {
+ display: block;
+ position: absolute;
+ height: 100%;
+ background-color: var(--primary);
+ border-radius: 0.5rem;
+ transition: width 0.3s;
+ }
+
+ &::after {
+ content: attr(title);
+ position: absolute;
+ font-size: 0.75rem;
+ color: var(--bs-heading-color);
+ width: 100%;
+ height: 100%;
+ text-align: center;
+ line-height: 1rem;
+ z-index: 2;
+ }
+}
+
+#modal_overlay {
+ position: fixed;
+ z-index: 900;
+ top: 0;
+ right: 10000px;
+ bottom: 0;
+ left: -10000px;
+ overflow-y: scroll;
+ transition: opacity .125s ease-in;
+ opacity: 0;
+ background: rgba(0, 0, 0, .7);
+ -webkit-overflow-scrolling: touch;
+}
+
+.modal {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ width: 90%;
+ min-width: 270px;
+ max-width: 600px;
+ min-height: 32px;
+ margin: 5em auto;
+ padding: 1rem;
+ border-radius: 0.25rem !important;
+ background: #fff;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12);
+
+ &>* {
+ line-height: normal;
+ flex-basis: 100%;
+ margin-bottom: .5em;
+ max-width: 100%;
+ }
+
+ &>pre,
+ &>textarea {
+ font-size: 1rem;
+ font-size-adjust: .35;
+ overflow: auto;
+ ;
+ padding: 0.5rem;
+ cursor: auto;
+ white-space: pre-wrap;
+ color: var(--dark-primary);
+ outline: 0;
+ border-radius: 0.25rem;
+ border: 1px solid var(--lighter);
+ transition: all 0.2s ease;
+
+ &:focus {
+ border: 1px solid var(--primary);
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+ }
+ }
+
+ &>h4 {
+ display: block;
+ flex-grow: 1;
+ max-width: none;
+ padding: 1rem;
+ margin: -1rem -1rem 0 -1rem;
+ font-size: 1rem;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
+ border-radius: 0.25rem 0 0 0.25rem;
+ }
+
+ h5 {
+ margin-top: 1rem;
+ font-weight: 600;
+ }
+
+ label>input[type="checkbox"] {
+ top: 0;
+ }
+
+ ul {
+ li {
+ list-style-type: square;
+ color: #808080;
+ }
+ }
+
+ p {
+ word-break: break-word;
+ margin-top: 1rem;
+ }
+
+ .label {
+ font-size: .6rem;
+ font-weight: normal;
+ padding: .1rem .3rem;
+ padding-bottom: 0;
+ cursor: default;
+ border-radius: 0;
+ }
+
+ .label.warning {
+ background-color: #f0ad4e !important;
+ }
+
+ .btn {
+ padding: 0.45rem 0.8rem;
+ }
+
+ &.cbi-modal {
+ max-width: 90%;
+ max-height: none;
+ }
+
+ .cbi-map {
+ margin-bottom: 1rem;
+ }
+}
+
+
+body.modal-overlay-active {
+ overflow: hidden;
+ height: 100vh;
+}
+
+body.modal-overlay-active #modal_overlay {
+ right: 0;
+ left: 0;
+ opacity: 1;
+}
+
+
+.spinning {
+ position: relative;
+ padding-left: 32px !important;
+}
+
+.spinning::before {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+
+ left: .2em;
+ width: 32px;
+ content: "";
+ background: url(/luci-static/resources/icons/loading.svg) no-repeat center;
+ background-size: 16px;
+}
+
+
+
+/* luci */
+.hidden {
+ display: none;
+}
+
+.left,
+.left::before {
+ text-align: left !important;
+}
+
+.right,
+.right::before {
+ text-align: right !important;
+}
+
+.button-row {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ align-items: center;
+ justify-content: flex-end;
+ margin-bottom: 0 !important;
+}
+
+.center,
+.center::before {
+ text-align: center !important;
+}
+
+.top {
+ align-self: flex-start !important;
+ vertical-align: top !important;
+}
+
+.bottom {
+ align-self: flex-end !important;
+ vertical-align: bottom !important;
+}
+
+.inline {
+ display: inline;
+}
+
+.cbi-page-actions {
+ padding: 1rem;
+ justify-content: flex-end;
+ display: flex;
+ gap: 1rem;
+}
+
+.cbi-page-actions>form[method="post"] {
+ display: inline-block;
+}
+
+.th[data-type="button"],
+.td[data-type="button"],
+.th[data-type="fvalue"],
+.td[data-type="fvalue"] {
+ flex: 1 1 2em;
+ text-align: center;
+}
+
+.ifacebadge {
+ display: inline-flex;
+ align-items: center;
+ gap: .2rem;
+ padding: .25rem .8rem;
+
+ background: #eee;
+ border-radius: 4px;
+}
+
+td>.ifacebadge,
+.td>.ifacebadge {
+ font-size: .875rem;
+ background-color: #f0f0f0;
+}
+
+.ifacebadge>em,
+.ifacebadge>img {
+ display: inline-block;
+ margin: 0 0.75rem;
+}
+
+.ifacebadge>img+img {
+ margin: 0 .2rem 0 0;
+}
+
+.network-status-table {
+ display: flex;
+ flex-wrap: wrap;
+
+ .ifacebox {
+ flex-grow: 1;
+ border-radius: .25rem;
+ overflow: hidden;
+ margin: 1rem;
+ }
+
+ .ifacebox-body {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ gap: 0.5em;
+
+ >span {
+ flex: 10 10 auto;
+ }
+
+ >div {
+ display: flex;
+ flex-wrap: wrap;
+ gap: .5rem;
+ height: 100%;
+ }
+
+ .ifacebadge {
+ align-items: center;
+ flex: 1 1 auto;
+ min-width: 220px;
+ padding: .5em;
+ background-color: #fff;
+
+ >span {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+}
+
+/* textarea */
+.cbi-input-textarea {
+ font-family: inherit;
+ width: 100%;
+ font-size: 0.875rem;
+ min-height: 14rem;
+ padding: .8rem;
+ color: #8898aa;
+ border-radius: 0.25rem;
+ border: 1px solid #dee2e6;
+ min-width: 16rem;
+}
+
+#content_syslog {
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
+}
+
+#syslog {
+ font-size: small;
+ font-family: 'Google Sans';
+ line-height: 1.25;
+ width: 100%;
+ min-height: 15rem;
+ padding: 1rem;
+ resize: none;
+ color: #242424;
+ border: 0;
+ border-radius: 0.25rem;
+ background-color: #ffffff;
+
+}
+
+#syslog:focus {
+ outline: 0;
+}
+
+#cbi-ddns #syslog {
+ overflow: auto;
+}
+
+/* config changes */
+.uci-change-list {
+ font-family: inherit;
+ overflow: scroll;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ flex-wrap: wrap;
+}
+
+.uci-change-list ins,
+.uci-change-legend-label ins {
+ display: block;
+ padding: 2px;
+ text-decoration: none;
+ border: thin solid #0f0;
+ background-color: #cfc;
+}
+
+.uci-change-list del,
+.uci-change-legend-label del {
+ font-style: normal;
+ display: block;
+ padding: 2px;
+ text-decoration: none;
+ border: thin solid #f00;
+ background-color: #fcc;
+}
+
+.uci-change-list var,
+.uci-change-legend-label var {
+ font-style: normal;
+ display: block;
+ padding: 2px;
+ text-decoration: none;
+ border: thin solid #ccc;
+ background-color: #eee;
+}
+
+.uci-change-list var ins,
+.uci-change-list var del {
+ font-style: normal;
+ padding: 0;
+ white-space: pre;
+ border: 0;
+}
+
+.uci-change-legend {
+ padding: 5px;
+}
+
+.uci-change-legend-label {
+ float: left;
+ width: 150px;
+}
+
+.uci-change-legend-label>ins,
+.uci-change-legend-label>del,
+.uci-change-legend-label>var {
+ display: block;
+ float: left;
+ width: 10px;
+ height: 10px;
+ margin-right: 4px;
+}
+
+.uci-change-legend-label var ins,
+.uci-change-legend-label var del {
+ line-height: .4;
+ border: 0;
+}
+
+.uci-change-list var,
+.uci-change-list del,
+.uci-change-list ins {
+ padding: .5rem;
+}
+
+.uci-dialog {
+ .cbi-section {
+ padding: 0.5rem;
+
+ .uci-change-legend {
+ line-height: 15px;
+ padding: 10px 20px 0 20px;
+
+ .uci-change-legend-label {
+ padding: 0;
+ margin: 0;
+ position: relative;
+ float: none;
+ display: inline-block;
+ width: 25%;
+
+ >ins,
+ >del {
+ width: 14px;
+ height: 14px;
+ }
+
+ >var {
+ position: relative;
+ width: 14px;
+ height: 14px;
+
+ ins,
+ del {
+ position: absolute;
+ left: 2px;
+ top: 2px;
+ right: 2px;
+ bottom: 2px;
+ }
+ }
+ }
+ }
+
+ .uci-change-list {
+ overflow: auto;
+ }
+
+ .uci-change-list+.right {
+ .btn {
+ color: #333;
+ }
+
+ .cbi-dropdown ul:not(.dropdown) {
+ li {
+ color: #fff;
+ }
+ }
+
+ .cbi-button {
+ padding: .45rem .8rem;
+ }
+ }
+ }
+}
+
+/* other fix */
+#iwsvg,
+#iwsvg2,
+#bwsvg {
+ border: thin solid #d4d4d4 !important;
+}
+
+#iwsvg {
+ border-top: 0 !important;
+}
+
+.ifacebox {
+ line-height: 1.25;
+ display: inline-flex;
+ overflow: hidden;
+ flex-direction: column;
+ border-radius: 4px;
+ min-width: 100px;
+ background-color: #f9f9f9;
+ // border-bottom: thin solid #ccc;
+ // box-shadow: inset 0 1px 0 rgba(255, 255, 255, .4), 0 1px 2px rgba(0, 0, 0, .2);
+}
+
+.ifacebox-head {
+ padding: .25em;
+ background: #eee;
+}
+
+.ifacebox-head.active {
+ background: #5e72e4;
+ background: var(--primary);
+
+ * {
+ color: #fff;
+ color: var(--white);
+ }
+
+}
+
+.ifacebox-body {
+ padding: 0.875rem 1rem;
+ line-height: 1.6em;
+}
+
+.cbi-image-button {
+ margin-left: .5rem;
+}
+
+.zonebadge {
+ display: inline-block;
+ padding: .2rem .5rem;
+ border-radius: 4px;
+}
+
+.zonebadge .ifacebadge {
+ margin: .1rem .2rem;
+ padding: .2rem .3rem;
+ border: thin solid #6c6c6c;
+}
+
+.zonebadge>input[type="text"] {
+ min-width: 10rem;
+ margin-top: .3rem;
+ padding: .16rem 1rem;
+}
+
+.zonebadge>em,
+.zonebadge>strong {
+ display: inline-block;
+ margin: 0 .2rem;
+}
+
+.cbi-value-field .cbi-input-checkbox,
+.cbi-value-field .cbi-input-radio {
+ margin-top: .1rem;
+}
+
+.cbi-value-field>ul>li {
+ display: flex;
+}
+
+.cbi-value-field>ul>li>label {
+ margin-top: .5rem;
+}
+
+.cbi-value-field>ul>li .ifacebadge {
+ margin-top: -.5rem;
+ margin-left: .4rem;
+ background-color: #eee;
+}
+
+.cbi-section-table-row>.cbi-value-field .cbi-dropdown {
+ min-width: 3rem;
+}
+
+.cbi-section-create {
+ display: inline-flex;
+ align-items: center;
+ padding: .875rem 1rem;
+
+
+ &>div:first-child {
+ &>input {
+ width: 100%;
+ border-right: none;
+ border-bottom-right-radius: 0;
+ border-top-right-radius: 0;
+ }
+ }
+
+ &>div:first-child+button {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+}
+
+
+.cbi-section-remove {
+ padding: .5rem 1rem;
+}
+
+div.cbi-value var,
+td.cbi-value-field var,
+.td.cbi-value-field var {
+ font-style: italic;
+ color: #0069d6;
+}
+
+.cbi-optionals {
+ padding: 1rem 1rem 0 1rem;
+ border-top: thin solid #ccc;
+}
+
+.cbi-dropdown-container {
+ position: relative;
+}
+
+.cbi-tooltip-container,
+span[data-tooltip],
+span[data-tooltip] .label {
+ cursor: help !important;
+}
+
+.cbi-tooltip {
+ position: absolute;
+ z-index: 1000;
+ left: -10000px;
+ box-shadow: 0 3px 9px rgba(50, 50, 9, 0), 3px 4px 8px rgba(94, 114, 228, 0.1);
+ border-radius: 0.25rem;
+ background: #fff;
+ white-space: pre;
+ padding: 0.5rem;
+ opacity: 0;
+ transition: opacity .25s ease-in;
+ transform: translate(-50%, 10%);
+}
+
+.cbi-tooltip-container:hover .cbi-tooltip:not(:empty) {
+ left: auto;
+ transition: opacity .25s ease-in;
+ opacity: 1;
+}
+
+.zonebadge .cbi-tooltip {
+ margin: -1.5rem 0 0 -.5rem;
+ padding: .25rem;
+ background: inherit;
+}
+
+.zonebadge-empty {
+ color: #404040;
+ background: repeating-linear-gradient(45deg, rgba(204, 204, 204, .5), rgba(204, 204, 204, .5) 5px, rgba(255, 255, 255, .5) 5px, rgba(255, 255, 255, .5) 10px);
+}
+
+.zone-forwards {
+ display: flex;
+ min-width: 10rem;
+}
+
+.zone-forwards>* {
+ flex: 1 1 45%;
+}
+
+.zone-forwards>span {
+ flex-basis: 10%;
+ padding: 0 .25rem;
+ text-align: center;
+}
+
+.zone-forwards .zone-src,
+.zone-forwards .zone-dest {
+ display: flex;
+ flex-direction: column;
+}
+
+.label {
+ font-size: .875rem;
+ font-weight: bold;
+ padding: .3rem .8rem;
+ white-space: nowrap;
+ text-decoration: none;
+ text-transform: uppercase;
+ color: #fff !important;
+ border-radius: 3px;
+ background-color: #bfbfbf;
+ text-shadow: none;
+}
+
+label>input[type="checkbox"],
+label>input[type="radio"] {
+ position: relative;
+ top: .4rem;
+ right: .2rem;
+ margin: 0;
+ vertical-align: bottom;
+}
+
+label[data-index][data-depends] {
+ padding-right: 2em;
+}
+
+.showSide {
+ display: none;
+}
+
+.darkMask {
+ position: fixed;
+ z-index: 99;
+ display: none;
+ width: 100%;
+ height: 100%;
+ content: "";
+ top: 0;
+ background-color: rgba(0, 0, 0, .56);
+ transition: all 0.2s;
+
+ &.active {
+ display: block;
+ }
+}
+
+/* diagnostics */
+#diag-rc-output>pre,
+#command-rc-output>pre,
+[data-page="admin-services-wol"] .notice code {
+ font-size: 1.2rem;
+ font-size-adjust: .35;
+ line-height: normal;
+ display: block;
+ overflow-y: hidden;
+ width: 100%;
+ padding: 8.5px;
+ white-space: pre;
+ color: #eee;
+ background-color: #101010;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .16), 0 0 2px 0 rgba(0, 0, 0, .12);
+}
+
+
+
+input[name="ping"],
+input[name="traceroute"],
+input[name="nslookup"] {
+ width: 80%;
+}
+
+.controls {
+ gap: 0.5rem;
+ margin: 0 1.25rem !important;
+}
+
+.controls>div {
+ gap: 0.5rem;
+}
+
+.controls>*>.btn:not([aria-label$="page"]) {
+ flex-grow: initial !important;
+ margin-top: .25rem;
+}
+
+.controls>#pager>.btn[aria-label$="page"] {
+ font-size: 1.4rem;
+ font-weight: bold;
+}
+
+.controls>*>label {
+ margin-bottom: .2rem;
+}
+
+.td.version,
+.td.size {
+ white-space: normal !important;
+ word-break: break-word;
+}
+
+.cbi-tabmenu+.cbi-section {
+ margin-top: 0;
+}
+
+
+.cbi-tab-disabled[data-errors]::after {
+ position: absolute;
+ top: -0.25rem;
+ right: -0.25rem;
+ content: attr(data-errors);
+ background-color: var(--red);
+ color: #fff;
+ width: 0.875rem;
+ height: 0.875rem;
+ border-radius: 0.875rem;
+ text-align: center;
+ display: inline-flex;
+ flex-direction: column;
+ justify-content: center;
+ font-size: .75em;
+}
+
+
+
+
+/* custom commands */
+.commands {
+ display: grid !important;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 0.75rem;
+}
+
+.commandbox {
+ padding: .5rem 1rem;
+ gap: 0.5rem;
+ border-bottom: thin solid #ccc;
+ border-radius: .25rem;
+ background: #eee;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 1px 2px rgba(0, 0, 0, .05);
+
+ h3 {
+ line-height: normal !important;
+ overflow: hidden;
+ margin: 6px 0 !important;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ div {
+ left: auto !important;
+ }
+
+ code {
+ overflow: hidden;
+ max-width: fit-content;
+ padding: 2px 3px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+
+ &:hover {
+ overflow-y: auto;
+ max-height: 50px;
+ white-space: normal;
+ }
+ }
+
+ p:first-of-type {
+ margin-top: -6px;
+ }
+
+ p:nth-of-type(2) {
+ margin-top: 2px;
+ }
+}
+
+
+
+#command-rc-output .alert-message {
+ line-height: 1.42857143;
+ position: absolute;
+ top: 40px;
+ right: 32px;
+ max-width: 40%;
+ margin: 0;
+ animation: anim-fade-in 1.5s forwards;
+ word-break: break-word;
+ opacity: 0;
+}
+
+@keyframes anim-fade-in {
+ 100% {
+ opacity: 1;
+ }
+}
+
+/* other fix */
+
+
+.fb-container .cbi-button {
+ height: auto !important;
+}
+
+#cbi-usb_printer-printer em {
+ display: block;
+ padding: 1rem;
+ text-align: center;
+}
+
+pre.command-output {
+ padding: 1.5rem;
+}
+
+
+/* page fix */
+@import url("page-fix.less");
+
+/* responsive */
+@import url("responsive.less");
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/dark.less b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/dark.less
new file mode 100644
index 0000000..8e8970d
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/dark.less
@@ -0,0 +1,1222 @@
+// out: ../htdocs/luci-static/argon/css/dark.css, compress: true , sourceMap: false
+/**
+ * Argon is a clean HTML5 theme for LuCI. It is based on luci-theme-material and Argon Template
+ *
+ * luci-theme-argon
+ * Copyright 2025 Jerryk
+ *
+ * Have a bug? Please create an issue here on GitHub!
+ * https://github.com/jerrykuku/luci-theme-argon/issues
+ *
+ * luci-theme-bootstrap:
+ * Copyright 2008 Steven Barth
+ * Copyright 2008 Jo-Philipp Wich
+ * Copyright 2012 David Menting
+ *
+ * MUI:
+ * https://github.com/muicss/mui
+ *
+ * luci-theme-material:
+ * https://github.com/LuttyYang/luci-theme-material/
+ *
+ * Agron Theme
+ * https://demos.creative-tim.com/argon-dashboard/index.html
+ *
+ * Login background
+ * https://unsplash.com/
+ *
+ * Licensed to the public under the Apache License 2.0
+ */
+
+
+body {
+ background: #1e1e1e;
+ color: #cccccc;
+}
+
+.spinning::before {
+ filter: invert(1);
+}
+
+.login-page .login-container {
+
+ .login-form {
+ background-color: #1e1e1e;
+ -webkit-backdrop-filter: blur(var(--blur-radius-dark)) brightness(0);
+ backdrop-filter: blur(var(--blur-radius-dark)) brightness(0);
+ background-color: rgba(0, 0, 0, var(--blur-opacity-dark));
+
+ .brand {
+ color: #adb5bd;
+ }
+
+ .form-login {
+ .input-group {
+ &::before {
+ color: #adb5bd;
+ }
+
+ input {
+ background-color: transparent !important;
+ color: #adb5bd;
+ border-bottom: #adb5bd 1px solid !important;
+ border-radius: 0 !important;
+ border-top: none !important;
+ border-left: none !important;
+ border-right: none !important;
+ box-shadow: none;
+ }
+
+ }
+
+ .cbi-button-apply {
+ background-color: #483d8b !important;
+ background-color: var(--dark-primary) !important;
+
+ &:hover,
+ &:focus {
+ opacity: 0.9;
+ }
+ }
+ }
+ }
+
+}
+
+header::after {
+ background-color: #1e1e1e !important;
+}
+
+
+.main {
+ .main-left {
+
+ background-color: #333333 !important;
+ box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, .15);
+
+ .sidenav-header .brand {
+ color: #cccccc;
+ }
+
+ .nav {
+ .slide {
+ .slide-menu {
+ .active {
+ a {
+ color: #cccccc;
+
+ &::after {
+ background-color: #cccccc !important;
+ }
+ }
+
+ &::after {
+ background-color: var(--dark-primary) !important;
+ }
+ }
+
+ li {
+ a {
+ color: #cccccc;
+ }
+
+ a:hover {
+ background: none !important;
+ }
+
+ &::after {
+ background-color: var(--dark-primary) !important;
+ }
+ }
+ }
+
+ .menu.active {
+ background-color: #483d8b !important;
+ background-color: var(--dark-primary) !important;
+ color: #ffffff !important;
+
+ a::after {
+ background-color: #ffffff !important;
+ }
+ }
+ }
+
+ li {
+ a {
+ color: #cccccc !important;
+ }
+
+ a:hover {
+ background-color: #483d8b !important;
+ background-color: var(--dark-primary) !important;
+ color: #ffffff !important;
+ }
+
+ }
+ }
+
+
+ &::-webkit-scrollbar-thumb {
+ background-color: #252526 !important;
+ }
+
+ &::-webkit-scrollbar-track {
+ background-color: #333;
+ }
+ }
+
+ .main-right {
+ background-color: #1e1e1e;
+ }
+}
+
+h2 {
+ color: #ccc;
+ background: #333333;
+}
+
+h3 {
+ color: #ccc;
+ border-bottom: 0;
+ background: #333333;
+}
+
+h4 {
+ color: #8C6900;
+}
+
+abbr {
+ color: var(--dark-primary) !important;
+}
+
+a:link, a:visited, a:active {
+ color: #a5b2ff;
+}
+
+
+input:-webkit-autofill {
+ background-color: #3c3c3c !important;
+}
+
+#channel_graph {
+ background-color: transparent !important;
+}
+
+.cbi-value-field .cbi-input-apply,
+.cbi-button-apply,
+.cbi-button-edit {
+ color: #fff !important;
+ background-color: #483d8b !important;
+ background-color: var(--dark-primary) !important;
+ border-color: #483d8b !important;
+ border-color: var(--dark-primary) !important;
+}
+
+
+.cbi-section em {
+ color: #ccc;
+}
+
+header.bg-primary {
+ background-color: #1e1e1e !important;
+}
+
+.cbi-map-descr {
+ color: #ccc;
+}
+
+.cbi-section {
+ background: none;
+ box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, .35)
+}
+
+.panel-title {
+ color: #ccc;
+ background-color: #333333;
+}
+
+div > table > tbody > tr:nth-of-type(2n),
+div > .table > .tr:nth-of-type(2n) {
+ background-color: #252526;
+}
+
+table > tbody > tr > td,
+table > tfoot > tr > td,
+table > thead > tr > td {
+ color: #cccccc;
+}
+
+fieldset > table > tbody > tr:nth-of-type(2n) {
+ background-color: #252526;
+}
+
+table > tbody > tr > td,
+table > tfoot > tr > td,
+table > thead > tr > td {
+ border-top: 1px solid #252526;
+}
+
+#swaptotal > div > div,
+#swapfree > div > div,
+#memfree > div > div,
+#membuff > div > div,
+#conns > div > div,
+#memtotal > div > div {
+ background-color: var(--dark-primary) !important;
+}
+
+#swaptotal > div > div > div > small,
+#swapfree > div > div > div > small,
+#memfree > div > div > div > small,
+#membuff > div > div > div > small,
+#conns > div > div > div > small,
+#memtotal > div > div > div > small {
+ color: #ccc !important;
+}
+
+.node-system-packages > .main .cbi-section-node:first-child .cbi-value-last {
+ line-height: 1.8em;
+
+ div[style="margin:3px 0; width:300px; height:10px; border:1px solid #000000; background-color:#80C080"] {
+ border: 1px solid #999999 !important;
+ background-color: transparent !important;
+
+ div {
+ background-color: #ba8b00 !important;
+ }
+ }
+
+}
+
+table > tbody > tr > th,
+table > tfoot > tr > th,
+table > thead > tr > th {
+ background-color: #252526;
+ border-bottom: black 1px solid !important;
+}
+
+.tr[data-title]::before,
+.tr.cbi-section-table-titles.named::before {
+ background-color: #252526;
+}
+
+tr > td,
+tr > th,
+.tr > .td,
+.tr > .th,
+.cbi-section-table-row::before,
+#cbi-wireless > #wifi_assoclist_table > .tr:nth-child(2) {
+ border-top: 0;
+}
+
+.cbi-rowstyle-2 {
+ background-color: #1e1e1e;
+}
+
+.cbi-rowstyle-1 {
+ background-color: #252526;
+}
+
+.cbi-rowstyle-2 .cbi-button-up,
+.cbi-rowstyle-2 .cbi-button-down,
+body:not(.Interfaces) .cbi-rowstyle-2:first-child {
+ background-color: rgb(102, 102, 102) !important;
+}
+
+.cbi-section > h3:first-child,
+.panel-title,
+h3 {
+ color: #ccc;
+}
+
+h4 {
+ background-color: #1e1e1f;
+}
+
+.cbi-progressbar {
+ background: #282a2c;
+
+ div {
+ background-color: var(--dark-primary) !important;
+ }
+}
+
+.cbi-section-table .cbi-section-table-titles .cbi-section-table-cell {
+ background-color: #1e1e1f;
+}
+
+.cbi-button {
+ color: #ffffff !important;
+ background-color: #483d8b;
+ background-color: var(--dark-primary);
+}
+
+.cbi-section-node {
+ background: none;
+}
+
+abbr {
+ color: #5e72e4;
+}
+
+div > table > tbody > tr:nth-of-type(2n),
+div > .table > .tbody > .tr:nth-of-type(2n) {
+ background-color: #252526;
+}
+
+#content_syslog {
+ box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, .35)
+}
+
+#syslog {
+ color: #ccc;
+ background-color: #1e1e1e;
+}
+
+#iwsvg,
+#iwsvg2,
+#bwsvg {
+ overflow: hidden;
+ box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, .35);
+ background-color: #1e1e1e !important;
+}
+
+.tabs {
+ background-color: #252526;
+
+ li[class~="active"],
+ li:hover {
+ cursor: pointer;
+ color: #ccc !important;
+
+ a {
+ color: #ccc !important;
+ }
+ }
+}
+
+.tabs > li[class~="active"] > a {
+ color: #ccc;
+}
+
+.tabs > li[class~="active"],
+.tabs > li:hover {
+ border-bottom: 0.18751rem solid #483d8b;
+ border-bottom: 0.18751rem solid var(--dark-primary);
+ color: var(--dark-primary) !important;
+ background-color: #181819;
+}
+
+.cbi-tabmenu > li > a,
+.tabs > li > a {
+ color: #ccc;
+
+ &:hover {
+ color: #ccc;
+ }
+}
+
+.cbi-tabmenu > li {
+ background: #2d2d2d;
+}
+
+.cbi-tabmenu li[class~="cbi-tab"] a {
+ color: #ccc;
+}
+
+.cbi-tabmenu > li:hover {
+ color: #ccc;
+ background: #2d2d2d;
+ border-bottom: 0.18751rem solid var(--dark-primary) !important;
+}
+
+.cbi-tabmenu > li[class~="cbi-tab"] {
+ background-color: #181819;
+ border-bottom: 0.18751rem solid var(--dark-primary) !important;
+}
+
+.cbi-tabcontainer > .cbi-value:nth-of-type(2n) {
+ background-color: #252526;
+}
+
+.cbi-value-title {
+ color: #ccc;
+}
+
+select,
+input {
+ color: #ccc;
+ background-color: transparent !important;
+ border: 1px solid #252526;
+ box-shadow: none;
+}
+
+
+select:not([multiple="multiple"]):focus,
+input:focus {
+ border-color: #483d8b !important;
+ border-color: var(--dark-primary) !important;
+ outline: 0;
+
+}
+
+select:not([multiple="multiple"]):focus,
+input:not(.cbi-button):focus,
+.cbi-dropdown:focus {
+ border-color: #5e72e4;
+ border-color: var(--dark-primary) !important;
+}
+
+select {
+ background-color: #1e1e1e !important;
+}
+
+#cbi-dropbear h2,
+#cbi-dropbear .cbi-map-descr,
+#cbi-dropbear .cbi-map-descr abbr,
+#cbi-rc h2,
+#cbi-rc .cbi-map-descr,
+#cbi-distfeedconf h2,
+#cbi-distfeedconf .cbi-map-descr,
+#cbi-customfeedconf h2,
+#cbi-customfeedconf .cbi-map-descr,
+#cbi-download h2,
+#cbi-filelist h2 {
+ color: #ccc !important;
+}
+
+.cbi-value-field > ul > li .ifacebadge {
+ background-color: #3c3c3c;
+}
+
+.cbi-section-descr {
+ color: #ccc;
+}
+
+.cbi-input-textarea {
+ background-color: #1e1e1e;
+ color: #ccc;
+ border: 1px solid #3c3c3c !important;
+}
+
+textarea {
+ &:focus-visible {
+ border: 1px solid var(--dark-primary);
+ }
+}
+
+
+.cbi-section-remove:nth-of-type(2n),
+.cbi-section-node:nth-of-type(2n) {
+ background-color: #1e1e1e;
+}
+
+.cbi-section[id] .cbi-section-remove:nth-of-type(4n+3),
+.cbi-section[id] .cbi-section-node:nth-of-type(4n+4) {
+ background-color: #1e1e1e;
+}
+
+.node-system-packages > .main table tr td:nth-last-child(1) {
+ color: #ccc;
+
+}
+
+.cbi-tooltip {
+ background-color: var(--primary);
+ color: #fff;
+}
+
+.ifacebox {
+ background-color: transparent !important;
+ border: 1px solid #1e1e1e;
+}
+
+.ifacebox-head {
+ color: #ccc;
+ background: #666;
+}
+
+.ifacebox-body {
+ background-color: #333;
+}
+
+.zonebadge strong {
+ color: #333;
+}
+
+.zonebadge > .ifacebadge {
+ background-color: #3c3c3c;
+}
+
+div.cbi-value var,
+td.cbi-value-field var {
+ color: #5e72e4;
+}
+
+#diag-rc-output > pre {
+ color: #ccc;
+ background-color: #1e1e1e;
+}
+
+.node-services-vssr .block {
+ background-color: #3c3c3c !important;
+ box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, .35);
+}
+
+.node-services-vssr .block h4 {
+ color: #ccc !important;
+}
+
+.node-services-vssr .status-bar {
+ color: #ccc;
+ box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, .35);
+ background-color: #1e1e1e;
+}
+
+.node-services-vssr .cbi-section-table-row {
+ color: #ccc;
+ background-color: #3c3c3c !important;
+ box-shadow: 0 0 5px 0 rgba(0, 0, 0, .35)
+}
+
+.node-services-vssr .cbi-section-table-row.fast {
+ background: #483d8b !important;
+ background: var(--dark-primary) !important;
+ color: #fff;
+}
+
+.node-services-vssr .ssr-button {
+ color: #ccc;
+
+}
+
+.node-services-vssr .incon:nth-child(2) {
+ border-right: #1e1e1e 1px solid;
+}
+
+#xhr_poll_status > .label.success {
+ color: #ccc !important;
+
+ background-color: darkolivegreen !important;
+}
+
+.notice {
+ background-color: #483d8b !important;
+ background-color: var(--dark-primary) !important;
+}
+
+.cbi-section.fade-in .cbi-title>div:last-child span {
+ background: none!important;
+ color: #ced4da;
+}
+
+.cbi-input-find,
+.cbi-input-save,
+.cbi-button-add,
+.cbi-button-save,
+.cbi-button-find,
+.cbi-input-reload,
+.cbi-button-reload {
+ color: #fff !important;
+ background: darkolivegreen !important;
+ border-color: darkolivegreen !important;
+}
+
+.cbi-button-reset,
+.cbi-input-remove {
+ color: #fff !important;
+ background-color: darkorange !important;
+ border-color: darkorange !important;
+}
+
+.cbi-page-actions .cbi-button-apply,
+.cbi-section-actions .cbi-button-edit,
+.cbi-button-edit.important,
+.cbi-button-apply.important,
+.cbi-button-reload.important,
+.cbi-button-action.important {
+ border: 1px #483d8b solid !important;
+ border: 1px var(--dark-primary) solid !important;
+}
+
+.btn[value="Dismiss"],
+.cbi-button[value="Terminate"],
+.cbi-button[value="Reset"],
+.cbi-button[value="Disabled"],
+.cbi-button[onclick^="iface_reconnect"],
+.cbi-button[onclick="handleReset(event)"],
+.cbi-button-neutral[value="Disable"] {
+ color: #fff;
+ border: thin solid darkorange !important;
+ background-color: darkorange !important;
+}
+
+fieldset[id^="cbi-apply-"] {
+ background-color: #333333;
+}
+
+#detail-bubble > div {
+ border: 1px solid #ccc;
+ background: #252525;
+}
+
+.ifacebox-head.active {
+ background-color: var(--dark-primary) !important;
+}
+
+header .fill .status span[data-style="active"] {
+ color: #ccc !important;
+ background-color: darkolivegreen !important;
+}
+
+#cbi-wireless .td,
+#cbi-network .tr:first-child > .td,
+.table[width="100%"] > .tr:first-child > .td,
+[data-page="admin-network-diagnostics"] .tr > .td,
+.tr.table-titles > .th,
+.tr.cbi-section-table-titles > .th {
+ background-color: #252526;
+ border-bottom: black 1px solid !important;
+
+}
+
+.network-status-table .ifacebox-body .ifacebadge {
+ background-color: #252526;
+ border-bottom: 0;
+ box-shadow: none;
+}
+
+td > .ifacebadge,
+.td > .ifacebadge {
+ color: #fff;
+ background-color: #483d8b;
+ background-color: var(--dark-primary);
+ border: 0;
+}
+
+.alert, .alert-message {
+ background-color: #333;
+}
+
+.alert-message.warning {
+ background-color: #986400 !important;
+}
+
+.alert.error, .alert-message.error {
+ background-color: #784f00;
+}
+
+.alert h4, .alert-message h4 {
+ background-color: #784f00;
+ color: #ffbf00;
+}
+
+.alert-message [class="btn"] {
+ background-color: #777777;
+ color: #ccc;
+}
+
+.uci-dialog {
+ .cbi-section {
+
+ .uci-change-legend {
+
+ .uci-change-legend-label {
+
+ > ins {
+ border-color: #00ad00;
+ background-color: #688668;
+ }
+
+ > del {
+ border-color: #c60000;
+ background-color: #896565;
+ }
+
+ > var {
+ background-color: #333;
+ border-color: #666;
+
+ ins {
+ background-color: #688668;
+ }
+
+ del {
+ background-color: #896565;
+ }
+ }
+ }
+ }
+
+ .uci-change-list {
+ > var {
+ background-color: #333;
+ border-color: #666;
+
+ del {
+ background-color: #896565;
+ }
+
+ ins {
+ background-color: #688668;
+ }
+ }
+
+ > ins {
+ border-color: #00ad00;
+ background-color: #688668;
+ }
+
+ > del {
+ border-color: #c60000;
+ background-color: #896565;
+ }
+ }
+
+ .uci-change-list + .right {
+ .btn {
+ color: #ccc;
+ }
+
+ .cbi-button {
+ border: 1px solid #3c3c3c !important;
+ }
+ }
+ }
+}
+
+.btn.danger,
+.cbi-section-remove > .cbi-button,
+.cbi-button-remove,
+.cbi-button-reset,
+.cbi-button-negative,
+.cbi-button[value="Stop"],
+.cbi-button[value="Kill"],
+.cbi-button[onclick="reboot(this)"],
+.cbi-button-neutral[value="Restart"] {
+ border: thin solid darkorange !important;
+ background-color: darkorange !important;
+}
+
+.cbi-section,
+.cbi-section-error,
+#iptables,
+.Firewall form,
+#cbi-network > .cbi-section-node,
+#cbi-wireless > .cbi-section-node,
+#cbi-wireless > #wifi_assoclist_table,
+[data-tab-title],
+[data-page^="admin-system-admin"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear),
+[data-page="admin-system-opkg"] #maincontent > .container {
+ background: #1e1e1e !important;
+ box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.35);
+}
+
+div[style="width:100%;height:300px;border:1px solid #000;background:#fff"] {
+ background: transparent !important;
+}
+
+[data-page="admin-system-admin"] .cbi-map h2,
+[data-page="admin-system-admin-password"] .cbi-map h2,
+[data-page="admin-system-admin"] .cbi-map .cbi-map-descr,
+[data-page="admin-system-admin-password"] .cbi-map .cbi-map-descr {
+ color: #ccc;
+}
+
+[data-page="admin-system-flash"] {
+ .modal {
+ label > input[type="checkbox"] {
+ vertical-align: text-top;
+ top: auto;
+ border-color: #fff !important;
+ }
+
+ .btn {
+ white-space: normal !important;
+ background-color: darkseagreen;
+ }
+
+ .alert-message {
+ background-color: transparent !important;
+ }
+
+ .danger {
+ border: thin solid darkorange !important;
+ background-color: darkorange !important;
+ }
+ }
+}
+
+.cbi-value input[type="password"] + .cbi-button-neutral {
+ background-color: #483d8b !important;
+ background-color: var(--dark-primary) !important;
+ border-color: var(--dark-primary) !important;
+}
+
+.btn,
+button,
+select,
+input,
+.cbi-dropdown {
+ border: 1px solid #3c3c3c !important;
+}
+
+
+.cbi-dropdown .preview {
+ color: #ccc;
+}
+
+.cbi-section-table-row {
+ background-color: #1e1e1e !important;
+}
+
+.modal {
+ background-color: #1e1e1e;
+}
+
+.cbi-button-positive {
+ color: #fff !important;
+ background-color: darkolivegreen !important;
+}
+
+[data-page="admin-system-flash"] legend {
+ color: #ccc;
+}
+
+.logout:before {
+ color: #adb5bd !important;
+}
+
+.cbi-dropdown[open] {
+ border-color: #483d8b !important;
+ border-color: var(--dark-primary) !important;
+}
+
+.cbi-dropdown[open] > ul.dropdown {
+ background: #252526 !important;
+ color: #ffffff !important;
+ box-shadow: none;
+ border: 1px solid #3c3c3c !important;
+
+
+}
+
+.cbi-dropdown[open] > ul.dropdown li {
+ color: #ffffff;
+ border-bottom: 1px solid #3c3c3c !important;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li[selected] {
+ background-color: #483d8b !important;
+ background-color: var(--dark-primary) !important;
+ border-bottom: 1px solid #3c3c3c !important;
+}
+
+.cbi-dropdown[open] > ul.dropdown > li.focus {
+ background: #483d8b;
+ background: var(--dark-primary);
+ outline: none;
+}
+
+.ifacebadge {
+ background-color: #333333;
+}
+
+.cbi-dynlist > .item > span {
+ border: 1px solid #3c3c3c !important;
+}
+
+.cbi-page-actions .cbi-button-apply,
+.cbi-section-actions .cbi-button-edit,
+.cbi-button-edit,
+.cbi-button-apply,
+.cbi-button-reload,
+.cbi-button-action,
+.cbi-button[value="Submit"],
+.cbi-button[value="Upload"],
+.cbi-button[value$="Apply"],
+.cbi-button[onclick="addKey(event)"] {
+ background: #483d8b !important;
+ background: var(--dark-primary) !important;
+ border-color: var(--dark-primary) !important;
+}
+
+.btn.primary,
+.cbi-page-actions .cbi-button-save,
+.cbi-page-actions .cbi-button-apply + .cbi-button-save,
+.cbi-button-add,
+.cbi-button-save,
+.cbi-button-positive,
+.cbi-button-link,
+.cbi-button[value="Enable"],
+.cbi-button[value="Scan"],
+.cbi-button[value^="Back"],
+.cbi-button-neutral[onclick="handleConfig(event)"] {
+ background: #483d8b;
+ background: var(--dark-primary);
+}
+
+[data-page="admin-system-opkg"] h2 {
+ color: #ccc !important;
+}
+
+[data-page="admin-system-startup"] textarea {
+ color: #ccc;
+ background-color: transparent;
+
+ &:focus-visible {
+ border: 1px solid var(--dark-primary);
+ }
+}
+
+[data-page="admin-network-firewall-custom"] #view p textarea,
+[data-page="admin-status-routes"] #view p textarea,
+[data-page="admin-system-crontab"] #view p textarea {
+ color: #ccc;
+ background-color: transparent;
+}
+
+#view > .spinning {
+ background: #333333 !important;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03) !important;
+}
+
+@media screen and (min-width: 600px) {
+
+ ::-webkit-scrollbar-thumb {
+ background: var(--dark-primary);
+ }
+
+ ::-webkit-scrollbar-thumb:hover {
+ background: var(--dark-primary);
+ }
+
+ ::-webkit-scrollbar-thumb:active {
+ background: var(--dark-primary);
+ }
+}
+
+
+@media screen and (max-width: 480px) {
+ .node-status-iptables > .main div > .cbi-map > form {
+ background-color: #1e1e1e;
+ box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, .35);
+ }
+
+
+}
+
+[data-page=""],
+[data-page="admin-dashboard"] {
+ .main-right > #maincontent {
+ .Dashboard {
+ color: #ccc !important;
+
+ h3 {
+ color: #ccc;
+ }
+
+ hr {
+ border-top: 1px solid rgba(255, 255, 255, 1);
+ }
+
+ .dashboard-bg {
+ background-color: #333333;
+ }
+
+ img.svgmonochrome {
+ filter: invert(90%);
+ }
+
+ tr {
+ border-top: thin solid #4d4d4d;
+
+ &:last-child {
+ border-bottom: thin solid #4d4d4d;
+ }
+ }
+ }
+ }
+}
+
+[data-page="admin-system-fileassistant"] {
+ .fb-container .panel-container {
+ border-bottom-color: #3c3c3c !important;
+ }
+
+ .fb-container td[class$="-icon"]::before {
+ filter: invert(0.7);
+ }
+
+ div#list-content {
+ table.cbi-section-table {
+ tbody {
+ tr:nth-child(1) {
+ td.parent-icon {
+ background: #1e1e1e !important;
+ }
+ }
+ }
+ }
+ }
+}
+
+[data-page^="admin-services-openclash"] {
+ .oc {
+ --bg-light: #1e1e1e !important;
+ --bg-gray: #1e1e1e !important;
+
+ .developer-container {
+ background: transparent !important;
+ }
+ }
+
+ .config-upload-content{
+ background: #1e1e1e !important;
+ }
+
+ .cbi-tabmenu::-webkit-scrollbar-thumb {
+ background-color: #5b5b5b;
+ }
+
+ .cbi-tabmenu::-webkit-scrollbar-track {
+ background-color: rgba(60, 60, 60, 0.75);
+ }
+
+ #tab {
+ border-color: #3c3c3c;
+ }
+
+ #diag-rc-output > pre,
+ #dns-rc-output > pre {
+ color: #ccc;
+ border: 1px solid #3c3c3c !important;
+ background-color: #1e1e1e;
+ }
+
+ img[src$="arrow-clockwise-light.svg"],
+ img[src$="wrench-light.svg"],
+ img[src$="eye-light.svg"] {
+ filter: invert(0.9);
+ }
+}
+
+[data-page="admin-network-diagnostics"] {
+ .cbi-section {
+ background: #252526 !important;
+ }
+
+ textarea {
+ background: transparent;
+ border-radius: 0.25rem;
+ color: #ccc;
+ border: 1px solid #3c3c3c !important;
+ }
+
+ .tr > .td {
+ background-color: #252526 !important;
+ }
+}
+
+[data-page="admin-network-network"] {
+ .ifacebox-head[style*="--zone-color-rgb: 144, 240, 144"] {
+ background-color: rgb(73, 126, 73) !important;
+ color: #ccc !important;
+ }
+
+ .ifacebox-head[style*="--zone-color-rgb: 240, 144, 144;"] {
+ background-color: rgb(156, 79, 79) !important;
+ color: #ccc !important;
+ }
+
+ .ifacebox-head[style*="--zone-color-rgb: 238, 238, 238;"] {
+ background-color: #666 !important;
+ }
+
+ #modal_overlay > .modal.cbi-modal > div > p > textarea {
+ border: 1px solid #3c3c3c !important;
+ background-color: transparent !important;
+ }
+
+ #modal_overlay div[data-tab="peers"] div[data-name="_peers"] .ifacebadge code {
+ color: #fff !important;
+ background-color: transparent !important;
+ }
+
+ #modal_overlay > .modal.cbi-modal > div > p > textarea {
+ color: #ccc!important;
+ }
+}
+
+[data-page="admin-network-firewall-rules"] {
+ #cbi-firewall-rule {
+ .zonebadge[style*="--zone-color-rgb:240, 144, 144;"] {
+ --zone-color-rgb: 156, 79, 79 !important;
+
+ strong {
+ color: #ccc !important;
+ }
+ }
+
+ .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] {
+ --zone-color-rgb: 112, 112, 112 !important;
+
+ em {
+ color: #ccc !important;
+ }
+ }
+
+ > table > .cbi-section-table-row[data-title]::before {
+ background-color: #252526 !important;
+ }
+ }
+}
+
+[data-page="admin-network-network"],
+[data-page="admin-network-firewall-rules"] {
+ .cbi-dropdown {
+ .zonebadge[style*="--zone-color-rgb:240, 144, 144;"] {
+ --zone-color-rgb: 156, 79, 79 !important;
+ color: #ccc !important;
+
+ strong {
+ color: #ccc !important;
+ }
+ }
+
+ .zonebadge[style*="--zone-color-rgb:144, 240, 144;"] {
+ --zone-color-rgb: 73, 126, 73 !important;
+ color: #ccc !important;
+
+ strong {
+ color: #ccc !important;
+ }
+ }
+
+ .zonebadge[style*="--zone-color-rgb:238, 238, 238;"] {
+ --zone-color-rgb: 112, 112, 112 !important;
+
+ strong {
+ color: #ccc !important;
+
+ + span {
+ color: #ccc !important;
+ }
+ }
+ }
+ }
+}
+
+[data-page="admin-system-commands"],
+[data-page="admin-system-commands-dashboard"] {
+ .commandbox {
+ border-bottom: thin solid #333;
+ background: #252526;
+ box-shadow: inset 0 1px 0 rgba(0,0,0,0.2),0 1px 2px rgba(255,255,255,0.05);
+ }
+}
+
+.btn {
+ background-color: rgb(112, 112, 112);
+ color: #fff;
+}
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/fonts.less b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/fonts.less
new file mode 100644
index 0000000..afb98f8
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/fonts.less
@@ -0,0 +1,38 @@
+// out: false
+
+// ==========================================================================
+// CSS Custom Properties for Font Management
+// ==========================================================================
+
+@font-face {
+ font-family: 'Google Sans';
+ src: local('Google Sans'),
+ local('GoogleSans-Regular'),
+ url('../fonts/GoogleSans-Regular.woff2') format('woff2'),
+ url('../fonts/GoogleSans-Regular.woff') format('woff');
+ font-weight: normal;
+ font-style: normal;
+ font-display: swap;
+}
+
+/* Logo Font */
+@font-face {
+ font-family: 'TypoGraphica';
+ src: local('TypoGraphica'),
+ url('../fonts/TypoGraphica.woff2') format('woff2'),
+ url('../fonts/TypoGraphica.woff') format('woff');
+ font-weight: normal;
+ font-style: normal;
+ font-display: swap;
+}
+
+/* Icon Font */
+@font-face {
+ font-family: 'argon';
+ src: url('../fonts/argon.woff2') format('woff2'),
+ url('../fonts/argon.woff') format('woff');
+ font-weight: normal;
+ font-style: normal;
+ font-display: swap;
+}
+
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/layout.less b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/layout.less
new file mode 100644
index 0000000..0251acd
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/layout.less
@@ -0,0 +1,605 @@
+// out: false
+// ==========================================================================
+// Layout Styles
+// Main layout components for LuCI admin interface
+// ==========================================================================
+
+
+// 1. Main Container Layout
+// ==========================================================================
+
+.main {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: row;
+}
+
+// 2. Left Sidebar Layout
+// ==========================================================================
+
+.main-left {
+ width: 15rem;
+ height: 100%;
+ flex-shrink: 0;
+ z-index: 100;
+ overflow-x: auto;
+ word-break: break-word;
+ background-color: var(--menu-bg-color);
+ box-shadow: rgba(0, 0, 0, 0.75) 0px 0px 15px -5px;
+ transition: all 0.2s;
+
+ // Sidebar Header
+ .sidenav-header {
+ padding: 1.5rem 0.5rem;
+ text-align: center;
+
+ .brand {
+ display: block;
+ margin: 0 2rem;
+ font-size: 1.8rem;
+ font-family: "TypoGraphica";
+ color: var(--primary);
+ text-decoration: none;
+ text-align: center;
+ cursor: default;
+
+ .logo {
+ max-width: 100%;
+ height: auto;
+ }
+ }
+ }
+
+ // Navigation Menu
+ .nav {
+ margin-top: 0.5rem;
+
+ // Top-level navigation links
+ &>li>a:first-child {
+ display: block;
+ position: relative;
+ margin: 0.1rem 0.5rem;
+ padding: 0.675rem 0 0.675rem 2.5rem;
+ font-size: 1rem;
+ text-decoration: none;
+ border-radius: 0.25rem;
+ cursor: default;
+ transition: all 0.2s;
+
+ // Active state
+ &.active {
+ color: #fff;
+ background: var(--primary);
+
+ &::before {
+ color: #fff !important;
+ }
+
+ &::after {
+ transform: rotate(90deg);
+ color: #fff !important;
+ }
+ }
+
+ // Hover state
+ &:hover {
+ cursor: pointer;
+ color: #fff;
+ background: var(--primary);
+
+ &::before {
+ color: #fff !important;
+ }
+ }
+
+ // Icon before text
+ &::before {
+ position: absolute;
+ left: 0.8rem;
+ padding-top: 3px;
+ font-family: 'argon' !important;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ content: "\e915";
+ color: var(--primary);
+ transition: all 0.3s;
+ }
+ }
+
+ &>.slide>.menu::before {
+ transition: transform .1s ease-in-out;
+ }
+
+ &>.slide>.menu.active::before {
+ transition: transform .2s ease-in-out;
+ }
+
+ // Navigation list items
+ li {
+ padding: 0;
+ cursor: pointer;
+ user-select: none;
+
+ a {
+ display: block;
+ color: var(--menu-color);
+ }
+
+ // Collapsible menu items
+ &.slide {
+ padding: 0;
+
+ ul {
+ display: none;
+ overflow: hidden;
+ }
+
+ &:hover {
+ background: none;
+ }
+
+ // Submenu container
+ .slide-menu {
+ margin: 0 0.5rem 0 2.5rem;
+ padding: 0rem 0.5rem;
+
+ &.active {
+ display: block;
+ }
+
+ li {
+ position: relative;
+ margin: 0;
+ border-radius: 0.25rem;
+ background: none;
+ list-style: none;
+
+ a {
+ padding: 0.5rem 0rem;
+ text-decoration: none;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ // Hover indicator line
+ &::after {
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 0;
+ height: 2px;
+ content: "";
+ background-color: var(--primary);
+ transition: all 0.2s;
+ }
+
+ &:hover {
+ background: none;
+
+ &::after {
+ width: 100%;
+ }
+ }
+ }
+
+ // Active submenu item
+ .active {
+ background: none;
+ color: var(--menu-color);
+
+ a {
+ color: var(--menu-color);
+ }
+
+ &::after {
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ height: 2px;
+ content: "";
+ background-color: var(--primary);
+ transition: all 0.2s;
+ }
+
+ &:hover {
+ background: none;
+
+ &::after {
+ width: 100%;
+ }
+ }
+ }
+ }
+ }
+
+ // Main menu items
+ .menu {
+ display: block;
+ position: relative;
+ margin: 0.1rem 0.5rem;
+ padding: 0.675rem 0 0.675rem 2.5rem;
+ font-size: 1rem;
+ text-decoration: none;
+ border-radius: 0.25rem;
+ cursor: default;
+ transition: all 0.2s;
+
+ // Active state
+ &.active {
+ color: #fff;
+ background: var(--primary);
+
+ &::before {
+ color: #fff !important;
+ }
+
+ &::after {
+ transform: rotate(90deg);
+ color: #fff !important;
+ }
+ }
+
+ // Hover state
+ &:hover {
+ cursor: pointer;
+ color: #fff;
+ background: var(--primary);
+
+ &::before {
+ color: #fff !important;
+ }
+ }
+
+ // Icon before text
+ &::before {
+ position: absolute;
+ left: 0.8rem;
+ padding-top: 3px;
+ font-family: 'argon' !important;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ content: "\e915";
+ color: var(--primary);
+ transition: all 0.3s;
+ }
+
+ // Dropdown arrow
+ &::after {
+ position: absolute;
+ right: 0.5rem;
+ top: 0.8rem;
+ font-family: 'argon' !important;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ content: '\e90f';
+ color: #ced4da;
+ text-rendering: auto;
+ transition: all 0.3s;
+ }
+ }
+
+ // Menu-specific icons and colors
+ .menu[data-title="Status"]:before {
+ content: "\e906";
+ color: var(--primary);
+ }
+
+ .menu[data-title="System"]:before {
+ content: "\e90a";
+ color: #fb6340;
+ }
+
+ .menu[data-title="Services"]:before {
+ content: "\e909";
+ color: #11cdef;
+ }
+
+ .menu[data-title="NAS"]:before {
+ content: "\e90c";
+ color: #f3a4b5;
+ }
+
+ .menu[data-title="VPN"]:before {
+ content: "\e90b";
+ color: #8965e0;
+ }
+
+ .menu[data-title="Network"]:before {
+ content: "\e908";
+ color: #8965e0;
+ }
+
+ .menu[data-title="Bandwidth_Monitor"]:before {
+ content: "\e90d";
+ color: #2dce89;
+ }
+
+ .menu[data-title="Docker"]:before {
+ content: "\e911";
+ color: #6699ff;
+ }
+
+ .menu[data-title="Statistics"]:before {
+ content: "\e913";
+ color: #8965e0;
+ }
+
+ .menu[data-title="Control"]:before {
+ content: "\e912";
+ color: var(--primary);
+ }
+
+ .menu[data-title="Asterisk"]:before {
+ content: "\e914";
+ color: #fb6340;
+ }
+
+ // Logout link styling
+ a[data-title="Log_out"],
+ .food[data-title="Log_out"] {
+ &::before {
+ content: "\e907";
+ color: #adb5bd;
+ }
+ }
+ }
+ }
+
+ &[style*="overflow: hidden"]>.nav>.slide>.menu::before {
+ display: none;
+ }
+
+ // Scrollbar Styling
+ &::-webkit-scrollbar {
+ width: 5px;
+ height: 1px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: #f6f9fc;
+ }
+
+ &::-webkit-scrollbar-track {
+ background-color: #fff;
+ }
+}
+
+// 3. Right Content Area Layout
+// ==========================================================================
+
+.main-right {
+ height: 100%;
+ flex-grow: 1;
+ overflow-x: hidden;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ transition: all 0.2s;
+
+ // Main Content Container
+ &>#maincontent {
+ position: relative;
+ z-index: 50;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ &>.container {
+ margin: 0 1.25rem 1rem 1.25rem;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ // Dashboard Specific Styles
+ .Dashboard {
+ color: var(--gray-dark) !important;
+
+ h3 {
+ color: var(--gray-dark);
+ }
+
+ p {
+ margin-top: 3px;
+ margin-bottom: 3px;
+ }
+
+ hr {
+ border-top: 1px solid rgba(0, 0, 0, 1);
+ }
+
+ .dashboard-bg {
+ background-color: #fff;
+ }
+
+ .settings-info {
+ padding-top: 1em;
+ padding-bottom: 1em;
+
+ p span:nth-child(2) {
+ max-height: 18.5px;
+ top: 4px;
+ }
+
+ .label {
+ font-size: 0.7rem;
+ padding: 0.2rem 0.6rem;
+ }
+ }
+ }
+ }
+}
+
+// 4. Header Layout
+// ==========================================================================
+
+header {
+ position: relative;
+ width: 100%;
+ padding: 0;
+ color: var(--header-color);
+
+ // Primary background modifier
+ &.bg-primary {
+ background-color: var(--primary) !important;
+ }
+
+ // Header extension background
+ &::after {
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 2rem;
+ background-color: var(--primary) !important;
+ }
+
+ // Header Content Container
+ .fill {
+ padding: 0.8rem 0;
+ display: flex;
+ border-bottom: 0px solid rgba(255, 255, 255, 0.08) !important;
+
+ .container {
+ width: 100%;
+ height: 2rem;
+ padding: 0 1.25rem;
+ display: flex;
+ align-items: center;
+
+ // Left section (brand and menu toggle)
+ .flex1 {
+ flex: 1;
+
+ .showSide {
+ display: none;
+ font-size: 1.4rem;
+ color: #fff;
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+
+ .brand {
+ display: none;
+ padding-left: 1rem;
+ font-size: 1.5rem;
+ font-family: "TypoGraphica";
+ color: #fff;
+ text-decoration: none;
+ cursor: default;
+ vertical-align: text-bottom;
+ }
+ }
+
+ // Right section (status indicators)
+ .pull-right {
+ margin-top: 0rem;
+ float: right;
+ display: flex;
+ }
+ }
+
+ // Status Indicators
+ .status {
+ span {
+ display: inline-block;
+ margin: 0 0.25rem;
+ padding: 0.3rem 0.8rem;
+ font-size: 0.875rem;
+ font-weight: bold;
+ white-space: nowrap;
+ text-decoration: none;
+ text-transform: uppercase;
+ text-shadow: none;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: all 0.3s;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ span[data-indicator="poll-status"] {
+ color: #fff;
+ }
+
+ span[data-style="active"] {
+ background-color: var(--green);
+ }
+
+ span[data-style="inactive"] {
+ color: #ffffff !important;
+ background-color: #32325d;
+ }
+ }
+ }
+}
+
+// 5. Footer Layout
+// ==========================================================================
+
+footer {
+ padding: 1rem;
+ font-size: 0.875rem;
+ color: #aaa;
+ text-align: right;
+ white-space: nowrap;
+ overflow: hidden;
+
+ >a {
+ color: #aaa;
+ text-decoration: none;
+ }
+}
+
+
+// 6. View
+// ==========================================================================
+#view {
+ border-radius: 0.25rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ &>.spinning {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translateX(-50%) translateY(-50%);
+ padding: 1rem;
+ border-radius: 0.5rem;
+ background: #ffffff;
+ box-shadow: 0 0 1rem 0 rgba(136, 152, 170, .15);
+ }
+
+ &>div:first-child {
+ display: flex;
+ flex-direction: column;
+ gap: 0.875rem;
+
+ }
+}
\ No newline at end of file
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/normalize.less b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/normalize.less
new file mode 100644
index 0000000..e80ea17
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/normalize.less
@@ -0,0 +1,407 @@
+// out: false
+// ==========================================================================
+// Normalize.less
+// Based on normalize.css for style reset and standardization
+// ==========================================================================
+
+// 1. Global Reset
+// ==========================================================================
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+// 2. Document Root Elements
+// ==========================================================================
+
+html {
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+
+html,
+body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ font-family: var(--font-family-sans-serif);
+}
+
+body {
+ background-color: var(--background-color);
+ color: var(--gray-dark);
+ -webkit-tap-highlight-color: transparent;
+ font-size: 0.875rem;
+}
+
+// 3. Semantic Elements
+// ==========================================================================
+
+main {
+ display: block;
+}
+
+// 4. Heading Elements
+// ==========================================================================
+
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: inherit;
+ font-weight: normal;
+ line-height: 1.1 !important;
+ color: inherit;
+}
+
+h1 {
+ font-size: 2rem;
+ margin: 0.67em 0;
+ padding-bottom: 0.5rem;
+ border-bottom: thin solid var(--lighter);
+}
+
+h2 {
+ padding: 1rem 1.25rem;
+ font-size: 1.25rem;
+ font-weight: bold;
+ color: var(--gray-dark);
+ border-radius: 0.25rem;
+ background: var(--white);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
+}
+
+h3 {
+ margin: 0;
+ padding: 0.8755rem 1.25rem;
+ font-size: 1.1rem;
+ font-weight: bold;
+ line-height: 1;
+ color: var(--gray-dark);
+ border-radius: 0.25rem;
+ background: var(--white);
+ display: block;
+ width: 100%;
+}
+
+h4 {
+ margin: 0;
+ padding: 0.75rem 1.25rem;
+ font-size: 0.875rem;
+ font-weight: bold;
+ color: var(--gray-dark-400);
+
+ em {
+ padding: 0 0.5rem;
+ }
+}
+
+h5 {
+ margin: 2rem 0 0 0;
+ padding-bottom: 0.5rem;
+ font-size: 1rem;
+}
+
+// 5. Text Elements
+// ==========================================================================
+
+a {
+ background-color: transparent;
+}
+
+abbr {
+ cursor: help;
+ text-decoration: underline;
+ color: var(--primary);
+}
+
+abbr[title] {
+ border-bottom: none;
+ text-decoration: underline;
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+}
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+code,
+kbd,
+samp {
+ font-family: inherit;
+ font-size: inherit;
+ padding: 1px 3px;
+ color: var(--dark);
+ border-radius: 0.5rem;
+ background: var(--lighter);
+}
+
+small {
+ font-size: 90%;
+ line-height: 1.42857143;
+ white-space: normal;
+}
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+// 6. Horizontal Rules and Preformatted Text
+// ==========================================================================
+
+hr {
+ box-sizing: content-box;
+ height: 0;
+ margin: 1rem 0;
+ overflow: visible;
+ opacity: 0.1;
+ border-color: var(--lighter);
+}
+
+pre {
+ font-family: monospace, monospace;
+ font-size: 1em;
+ overflow: auto;
+}
+
+// 7. Media Elements
+// ==========================================================================
+
+img {
+ border-style: none;
+}
+
+// 8. Form Elements
+// ==========================================================================
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit;
+ font-size: 100%;
+ line-height: 1.15;
+ margin: 0;
+}
+
+button,
+input {
+ overflow: visible;
+}
+
+button,
+select {
+ text-transform: none;
+}
+
+// Button Styles
+[type=button],
+[type=reset],
+[type=submit],
+button {
+ -webkit-appearance: button;
+ appearance: button;
+}
+
+[type=button]::-moz-focus-inner,
+[type=reset]::-moz-focus-inner,
+[type=submit]::-moz-focus-inner,
+button::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+}
+
+[type=button]:-moz-focusring,
+[type=reset]:-moz-focusring,
+[type=submit]:-moz-focusring,
+button:-moz-focusring {
+ outline: 1px dotted ButtonText;
+}
+
+// Input Styles
+[type=checkbox],
+[type=radio] {
+ box-sizing: border-box;
+ padding: 0;
+}
+
+[type=number]::-webkit-inner-spin-button,
+[type=number]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+[type=search] {
+ -webkit-appearance: textfield;
+ appearance: textfield;
+ outline-offset: -2px;
+}
+
+[type=search]::-webkit-search-decoration {
+ -webkit-appearance: none;
+ appearance: none;
+}
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button;
+ appearance: button;
+ font: inherit;
+}
+
+// checkbox style
+
+input[type="checkbox"] {
+ appearance: none !important;
+ -webkit-appearance: none !important;
+ border: 1px solid var(--primary);
+
+ width: 1rem !important;
+ height: 1rem !important;
+ padding: 0;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+input[type="checkbox"]:checked {
+ border: 1px solid var(--primary);
+ background-image: url('data:image/svg+xml,%3csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 8 8\'%3e%3cpath fill=\'%23fff\' d=\'M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z\'/%3e%3c/svg%3e') !important;
+ background-color: var(--primary);
+ background-size: 70%;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+// Select Styles
+select {
+ padding: 0.36rem 0.8rem;
+ color: var(--gray-dark);
+ border: thin solid var(--lighter);
+ background-color: var(--white);
+ background-image: none;
+}
+
+// Textarea Styles
+textarea {
+ padding: 0.25rem;
+ overflow: auto;
+
+ &:focus-visible {
+ outline: none;
+ border: 1px solid var(--primary);
+ }
+}
+
+// Fieldset Styles
+fieldset {
+ padding: 0.35em 0.75em 0.625em;
+}
+
+legend {
+ box-sizing: border-box;
+ color: inherit;
+ display: table;
+ max-width: 100%;
+ padding: 0;
+ white-space: normal;
+}
+
+// 9. Interactive Elements
+// ==========================================================================
+
+details {
+ display: block;
+}
+
+summary {
+ display: list-item;
+}
+
+template {
+ display: none;
+}
+
+progress {
+ vertical-align: baseline;
+}
+
+// 10. List Elements
+// ==========================================================================
+
+ul {
+ line-height: normal;
+}
+
+li {
+ list-style-type: none;
+}
+
+// 11. State Styles
+// ==========================================================================
+
+[disabled="disabled"] {
+ pointer-events: none;
+}
+
+
+::selection {
+ background-color: var(--primary);
+ color: var(--white);
+}
+
+::placeholder {
+ color: var(--lighter);
+}
+
+a:link,
+a:visited,
+a:active {
+ color: var(--primary);
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+// 12 tools
+// ==========================================================================
+
+.pull-right {
+ float: right;
+}
+
+.pull-left {
+ float: left;
+}
+
+.nowrap:not(.td) {
+ white-space: nowrap;
+}
+
+div[style="width:100%;height:300px;border:1px solid #000;background:#fff"] {
+ border: 0 !important;
+}
\ No newline at end of file
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/page-fix.less b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/page-fix.less
new file mode 100644
index 0000000..34e9800
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/page-fix.less
@@ -0,0 +1,1004 @@
+// out: false
+// ==========================================================================
+// Page Fix Styles
+// Page-specific CSS fixes and customizations for LuCI admin interface
+// ==========================================================================
+
+// 1. System Administration Pages
+// ==========================================================================
+
+// System - Auto Reboot
+[data-page="admin-system-autoreboot"] {
+ #cbi-autoreboot {
+ margin-top: 0;
+ }
+}
+
+// System - System Settings
+[data-page="admin-system-system"] {
+ .control-group {
+ gap: .875rem;
+ }
+
+ .cbi-dynlist {
+ margin: 0.25rem 0;
+ }
+}
+
+// System - Admin Password
+[data-page="admin-system-admin"] .cbi-map h2,
+[data-page="admin-system-admin-password"] .cbi-map h2,
+[data-page="admin-system-admin"] .cbi-map .cbi-map-descr,
+[data-page="admin-system-admin-password"] .cbi-map .cbi-map-descr {
+ margin-left: 0;
+ color: var(--gray-dark);
+}
+
+[data-page="admin-system-admin"],
+[data-page="admin-system-admin-password"] {
+ .cbi-section-node {
+ padding: 1rem 0;
+ }
+}
+
+// System - SSH Keys
+[data-page="admin-system-admin-sshkeys"] {
+ .cbi-dynlist {
+ margin-left: 1rem;
+ }
+}
+
+// System - Startup Scripts
+[data-page="admin-system-startup"] {
+ #view {
+ &>div:first-child {
+ div[data-initialized="true"] {
+ margin-top: -0.4375rem;
+ }
+ }
+ }
+
+ [data-tab-title] p {
+ margin-left: 0;
+ margin-bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ textarea {
+ line-height: 1.25;
+ overflow-y: auto;
+ width: 100%;
+ min-height: 15rem;
+ margin: 0 1rem;
+ padding: 1rem;
+ resize: none;
+ color: #8898aa;
+ border-radius: 0.25rem;
+ border: 1px solid #dee2e6;
+
+ &:focus-visible {
+ outline: none;
+ box-shadow: none;
+ border: 1px solid var(--primary);
+ }
+ }
+}
+
+// System - Crontab
+[data-page="admin-system-crontab"] {
+ #view p {
+ textarea {
+ line-height: 1.25;
+ overflow-y: hidden;
+ width: 100%;
+ min-height: 15rem;
+ padding: 1rem;
+ resize: none;
+ background-color: transparent;
+ background: var(--white);
+ outline: none;
+ color: #8898aa;
+ border-radius: 0.25rem;
+ border: 1px solid #dee2e6;
+ transition: all 0.2s ease;
+
+ &:focus-visible {
+ outline: none;
+ box-shadow: none;
+ border: 1px solid var(--primary);
+ }
+ }
+ }
+}
+
+// System - Crontab Helper
+[data-page="admin-system-crontabhelper"] {
+ .crontab-row {
+ .dropdown-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.5rem;
+
+ div {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.5rem;
+ }
+ }
+ }
+}
+
+// System - Partexp
+[data-page="admin-system-partexp-global"] {
+ .cbi-section {
+ padding: 1rem 0;
+ }
+}
+
+// System - Attended Sysupgrade
+[data-page="admin-system-attendedsysupgrade"] #view .cbi-button {
+ margin-left: 0 !important;
+ margin-top: 1rem !important;
+}
+
+[data-page="admin-system-attendedsysupgrade-configuration"] {
+ .cbi-map {
+ .cbi-map-descr {
+ padding-bottom: 0;
+ }
+ }
+}
+
+// System - Flash Firmware
+[data-page="admin-system-flash"] {
+ .cbi-value {
+ padding: 0 1rem;
+ }
+
+ .cbi-section {
+ .cbi-section {
+ margin-top: 0;
+ }
+ }
+
+ .cbi-map-tabbed {
+ border-radius: 0.25rem;
+ }
+
+ .cbi-section-node {
+ padding-top: 0;
+ padding-bottom: 0.5rem;
+ }
+
+ legend {
+ font-size: 1.2rem;
+ width: 100%;
+ display: block;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+ line-height: 1.5;
+ margin-bottom: 0;
+ letter-spacing: 0.1rem;
+ color: #32325d;
+ font-weight: bold;
+ padding: 1rem 0 1rem 1rem;
+ }
+
+ .cbi-section-descr {
+ font-weight: 600;
+ padding: 1rem 0 1rem 1rem;
+ color: #525f7f;
+ }
+
+ .cbi-page-actions {
+ padding: 0rem 1rem 1rem 0rem;
+ }
+
+ .modal {
+ label>input[type="checkbox"] {
+ top: -0.25rem;
+ }
+
+ .btn {
+ white-space: normal !important;
+ }
+ }
+
+ .modal label>input[type="checkbox"] {
+ vertical-align: text-top;
+ top: auto;
+ }
+}
+
+// System - File Transfer
+[data-page="admin-system-filetransfer"] {
+ #cbi-upload {
+ margin-top: 0;
+ }
+
+ .cbi-section-table {
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
+ }
+}
+
+// System - File Assistant
+[data-page="admin-system-fileassistant"] {
+ #upload-toggle {
+ display: none !important;
+ }
+
+ .fb-container .panel-title {
+ padding: 0.5rem 0.75rem !important;
+ }
+
+ .cbi-section.fb-container {
+ padding: 0.5rem;
+
+ label.cbi-value-title {
+ line-height: 1.5rem;
+ padding: 0.5rem 0.75rem;
+ width: 60px;
+ text-align: left;
+ }
+
+ div.cbi-value-field {
+ width: 100%;
+ display: block;
+ padding-left: 60px;
+ }
+
+ div#list-content {
+ table.cbi-section-table {
+
+ thead,
+ tbody {
+ td.cbi-section-table-cell {
+ width: 232px;
+ }
+ }
+ }
+
+ tbody {
+ tr:nth-child(1) {
+ td.parent-icon {
+ strong {
+ margin-left: 0 !important;
+ }
+ }
+ }
+
+ td.cbi-value-field.file-icon,
+ td.cbi-value-field.folder-icon {
+ strong {
+ vertical-align: middle;
+ margin-left: 5px;
+ }
+ }
+ }
+ }
+ }
+
+ .fb-container .panel-container {
+ border-bottom-color: #dee2e6;
+ display: flex;
+
+ .panel-title {
+ flex: 1;
+ }
+ }
+
+ .fb-container .upload-container {
+ display: flex !important;
+
+ .upload-file {
+ flex: 1;
+ margin-right: 0 !important;
+ }
+ }
+}
+
+// System - Package Manager (Software)
+[data-page="admin-system-opkg"] {
+ h2 {
+ margin-left: 0;
+ color: var(--gray-dark);
+ }
+
+ input[name="filter_i18n"] {
+ top: 2px;
+ vertical-align: text-top;
+ }
+
+ div.btn {
+ line-height: 3;
+ display: inline;
+ padding: 0.3rem 0.6rem;
+ }
+
+ #maincontent>.container {
+ margin: 0 1.25rem 1rem 1.25rem;
+ margin-bottom: 1rem;
+ }
+}
+
+[data-page="admin-system-package-manager"] {
+ .controls {
+ div:nth-child(5) {
+ >label {
+ margin-bottom: 5px;
+ }
+
+ >div {
+ label {
+ margin-top: 1rem;
+
+ input {
+ top: -2px;
+ right: 0;
+ vertical-align: middle;
+ }
+ }
+ }
+ }
+ }
+}
+
+// System - Power Management
+[data-page="admin-system-reboot"] {
+ p {
+ padding-left: 1.5rem;
+ }
+
+ p>span {
+ position: relative;
+ top: 0.1rem;
+ left: 1rem;
+ }
+
+ .cbi-button {
+ background: #fb6340 !important;
+ border-color: #fb6340 !important;
+ margin-left: 0 !important;
+ }
+
+ .cbi-button.cbi-button-action.important {
+ align-self: flex-end;
+ }
+}
+
+/* applyreboot fix */
+#applyreboot-container {
+ margin: 2rem;
+}
+
+#applyreboot-section {
+ line-height: 300%;
+ margin: 2rem;
+}
+
+[data-page="admin-system-poweroff"] {
+ .container {
+ h2+br+p {
+ margin-bottom: 1rem;
+ padding-left: 1.5rem;
+ }
+ }
+}
+
+[data-page="admin-system-poweroffdevice"] {
+ .container {
+ h2 {
+ margin: 0;
+ }
+
+ h2+p {
+ margin-bottom: 1rem;
+ padding-left: 1.5rem;
+ }
+
+ button+div {
+ display: none;
+ }
+ }
+
+
+ .btn.cbi-button.cbi-button-negative {
+ align-self: flex-end;
+ }
+}
+
+// 2. Network Configuration Pages
+// ==========================================================================
+
+// Network - Interfaces
+[data-page="admin-network-network"] {
+ .cbi-value-field {
+ .cbi-dynlist {
+ padding: 0 !important;
+ }
+ }
+
+ .td>.ifacebadge>.cbi-tooltip-container {
+ display: flex;
+
+ img {
+ vertical-align: middle;
+ }
+ }
+
+ div[data-name="_gen_server_keypair"],
+ div[data-name="_gen_peer_keypair"],
+ div[data-name="_gen_psk"] {
+ .cbi-value-title {
+ height: 2.4rem;
+ }
+ }
+
+ #modal_overlay>.modal.cbi-modal>div>p>textarea {
+ height: 20em !important;
+ border: 1px solid #dee2e6 !important;
+ border-radius: 4px;
+ }
+}
+
+// Network - DHCP and DNS
+[data-page="admin-network-dhcp"] {
+ .cbi-value {
+ padding: 0;
+ }
+
+ [data-tab-active="true"] {
+ padding: 1rem 1rem !important;
+ }
+}
+
+// Network - Diagnostics
+[data-page="admin-network-diagnostics"] {
+ .table {
+ box-shadow: none;
+ }
+
+ .cbi-section {
+ padding: 1rem;
+ font-family: monospace;
+ background: #fff !important;
+ }
+
+ textarea {
+ background: transparent;
+ border-radius: 0.25rem;
+ color: #8898aa;
+ border: 1px solid #dee2e6;
+ padding: 0.5rem;
+ }
+
+ .tr {
+ display: flex;
+ flex-direction: row;
+
+ .td {
+ background-color: #fff !important;
+ border-bottom: 1px solid #dee2e6 !important;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ flex: 1;
+
+ input {
+ margin: 0 !important;
+ border-right: none !important;
+ border-bottom-right-radius: 0 !important;
+ border-top-right-radius: 0 !important;
+ }
+
+ .cbi-dropdown,
+ .cbi-button {
+ height: 2.5rem;
+ border-left: none !important;
+ border-bottom-left-radius: 0 !important;
+ border-top-left-radius: 0 !important;
+
+ .more {
+ display: block;
+ width: 1px;
+ height: 100%;
+ overflow: hidden;
+ text-indent: 5000px;
+ background-color: var(--white)
+ }
+ }
+ }
+ }
+
+
+ .cbi-dropdown {
+ .open {
+ color: var(--white);
+ }
+ }
+}
+
+// Network - Firewall
+[data-page="admin-network-firewall-custom"],
+[data-page="admin-status-routes"] {
+ #view {
+ p {
+ padding: 0 1.25rem;
+
+ textarea {
+ padding: 1rem;
+ border-radius: 0.25rem;
+ }
+ }
+
+ &>h3 {
+ border-radius: 0.25rem 0.25rem 0 0;
+ }
+
+ .cbi-tabmenu+div {
+ margin-top: -0.5rem;
+ }
+ }
+}
+
+// 3. Status Pages
+// ==========================================================================
+
+.cbi-section.fade-in {
+ .cbi-title {
+ position: relative;
+
+ > div:last-child {
+ position: absolute;
+ right: 1.25rem;
+ top: 0;
+
+ span {
+ font-size: 0;
+ background: none!important;
+ color: #ced4da!important;
+
+ &:after {
+ transition: all .3s;
+ font-family: 'argon' !important;
+ content: '\e90f';
+ font-size: 1.1rem;
+ padding: .8755rem 0;
+ position: absolute;
+ right: 0;
+ }
+
+ &[data-style=inactive]:after {
+ transform: rotate(90deg);
+ }
+ }
+ }
+ }
+}
+
+// Status - Overview
+[data-page="admin-status"],
+[data-page="admin-status-overview"] {
+ #view > div:first-child {
+ gap: 0 !important;
+ }
+}
+
+// Status - IPTables
+[data-page="admin-status-iptables"] {
+ .right {
+ margin-bottom: 0 !important;
+ }
+}
+
+// Status - NFTables
+[data-page="admin-status-nftables"] {
+ .nft-chain-hook {
+ padding: 0.5rem 1.25rem;
+ }
+}
+
+// Status - Network Bandwidth Monitor
+[data-page="admin-nlbw-display"] {
+ .cbi-section[data-tab="export"] {
+ padding: 1.5rem !important;
+ }
+}
+
+[data-page="admin-nlbw-backup"] {
+ form {
+ padding-left: 1.5rem;
+ }
+}
+
+// 4. Services Pages
+// ==========================================================================
+
+// Services - Dynamic DNS
+[data-page="admin-services-ddns"] {
+ #syslog {
+ overflow: auto;
+ font-size: 12px !important;
+ }
+}
+
+// Services - TTY Daemon
+[data-page="admin-services-ttyd"] {
+ .container {
+ display: flex;
+ flex-direction: column;
+ }
+
+ #view {
+ flex: 1;
+
+ iframe {
+ height: 100%;
+ }
+ }
+}
+
+// Services - Dockerman
+#cbi-dockerd {
+ .cbi-section {
+ box-sizing: border-box;
+ padding: 16px;
+ }
+
+ .cbi-section.cbi-tblsection {
+ padding: 0;
+ }
+
+}
+
+
+#cbi-docker {
+ .cbi-section {
+ .cbi-section-node {
+ box-sizing: border-box;
+ padding: 16px;
+ }
+ }
+}
+
+// Services - NPC
+[data-page="admin-services-npc"] {
+ div[id="cbid.npc.config.vkey"] {
+ white-space: normal;
+
+ input[id="widget.cbid.npc.config.vkey"][type="password"] {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+
+ .cbi-button {
+ margin: 0;
+ border: none;
+ }
+ }
+
+ div[id^="cbid.npc."][id$=".server_addr"]:not([id="cbid.npc.config.server_addr"]),
+ div[id^="cbid.npc."][id$=".vkey"]:not([id="cbid.npc.config.vkey"]),
+ div[id^="cbid.npc."][id$=".protocol"]:not([id="cbid.npc.config.protocol"]),
+ div[id^="cbid.npc."][id$=".dns"]:not([id="cbid.npc.config.dns"]) {
+ +br {
+ display: none;
+ }
+ }
+
+ div.cbi-value.nowrap {
+ white-space: normal;
+ }
+}
+
+// Services - OpenClash
+[data-page^="admin-services-openclash"] {
+ .oc {
+ --bg-light: #fff;
+ --bg-gray: #fff;
+ border-radius: 8px;
+
+ .developer-container {
+ background: transparent;
+ }
+ }
+
+ #cbi-openclash>fieldset {
+ background: none;
+ padding: 0;
+
+ >table>tbody>tr>td {
+ border: none;
+ padding: 0;
+ }
+
+ .main-card {
+ border: none;
+ box-shadow: none;
+ padding: 0 10px 10px 10px;
+ background: transparent;
+ }
+
+ .myip-main-card {
+ margin: 0;
+ padding: 10px;
+ border: none;
+ background: transparent;
+
+ .myip-section-title {
+ margin: 0;
+ border-width: 1px;
+ }
+ }
+ }
+
+ .sub-card .card-title {
+ margin-top: 5px;
+ }
+
+ .oc .main-cards-container {
+ margin: 0 !important;
+ gap: 0 !important;
+ }
+
+ .oc .config-file-bottom {
+ margin-bottom: 5px;
+
+ .card-actions {
+ margin-bottom: 2px !important;
+ }
+ }
+
+ .oc .card-content {
+ align-items: center !important;
+ justify-content: center !important;
+ }
+
+ .oc .card-controls {
+ align-self: center;
+ }
+
+ .oc .core-main-controls {
+ justify-content: center !important;
+ }
+
+ .oc .plugin-toggle-container {
+ margin-left: inherit !important;
+ }
+
+ .oc .core-status-toggle {
+ flex: 0 !important;
+ min-width: auto !important;
+ }
+
+ .oc .config-upload-content {
+ background: #fff !important;
+ }
+
+ .oc .subscription-info-container {
+ background: transparent !important;
+ }
+
+ .cbi-tabmenu>li {
+ border-right: none !important;
+ margin: 0 0.4rem 0 0 !important;
+
+ &:last-child {
+ margin-right: 0 !important;
+ }
+ }
+
+ #tab-content .dom {
+ padding: 0 1rem 1rem 1rem;
+ }
+
+ .cbi-input-file {
+ padding: 0.2813rem;
+ width: 15rem !important;
+ }
+
+ [id="container.openclash.config.debug"] fieldset {
+ border: none !important;
+ padding: 1rem !important;
+ }
+
+ #diag-rc-output>pre,
+ #dns-rc-output>pre {
+ font-size: 0.875rem;
+ color: #8898aa;
+ border: 1px solid #dee2e6;
+ background-color: transparent;
+ border-radius: 0.25rem;
+ font-family: 'Google Sans' !important;
+ box-shadow: none;
+ }
+
+ #debug-rc-output>textarea {
+ font-family: 'Google Sans' !important;
+ }
+
+ .CodeMirror {
+ font-size: inherit;
+ font-family: 'Google Sans' !important;
+ }
+
+ .cbi-button-up,
+ .cbi-button-down {
+ padding: 0.8rem 1.5rem;
+ background-color: #f1f1f1;
+ font-size: 0;
+ }
+
+ select#CORE_VERSION,
+ select#RELEASE_BRANCH {
+ width: auto;
+ }
+}
+
+// 5. VPN Pages
+// ==========================================================================
+
+// VPN - Passwall
+[data-page="admin-vpn-passwall"] h4 {
+ background: transparent;
+}
+
+/* openvpn bug fix */
+.OpenVPN a {
+ line-height: initial !important;
+}
+
+
+// 6. Other Pages
+// ==========================================================================
+
+// wireless
+/* wireless overview */
+#cbi-wireless {
+ >#wifi_assoclist_table {
+ >.tr {
+ box-shadow: inset 1px -1px 0 #ddd, inset -1px -1px 0 #ddd;
+
+ &.placeholder {
+ >.td {
+ right: 2rem;
+ bottom: 2rem;
+ left: 2rem;
+ border-top: thin solid #ddd !important;
+ }
+ }
+
+ &.table-titles {
+ box-shadow: inset 1px 0 0 #ddd, inset -1px 0 0 #ddd;
+
+ >.th {
+ border-bottom: thin solid #ddd;
+ box-shadow: 0 -1px 0 0 #ddd;
+ }
+ }
+ }
+ }
+}
+
+#wifi_assoclist_table {
+ >.tr {
+ >.td[data-title="RX Rate / TX Rate"] {
+ width: 23rem;
+ }
+ }
+}
+
+/* samba */
+#cbi-samba [data-tab="template"] {
+ .cbi-value-field {
+ display: block;
+ }
+
+ .cbi-value-title {
+ width: auto;
+ padding-bottom: .6rem;
+ }
+}
+
+/* fix status */
+.node-status-overview>.main fieldset:nth-child(4) .td:nth-child(2),
+.node-status-processes>.main .table .tr .td:nth-child(3) {
+ white-space: normal;
+}
+
+div[style*="display:grid;grid-template-columns:repeat"] {
+ display: flex !important;
+ justify-content: space-evenly !important;
+ flex-wrap: wrap;
+
+ .ifacebox {
+ text-align: center;
+ flex-basis: 100px;
+
+ .ifacebox-body {
+ font-size: 0.7rem;
+ padding: 0.875rem;
+
+ .cbi-tooltip-container {
+ font-size: inherit !important;
+ }
+ }
+ }
+}
+
+/* firewall */
+#iptables {
+ margin: 0;
+}
+
+.Firewall form {
+ margin: 2rem 2rem 0 0;
+ padding: 0;
+ box-shadow: none;
+}
+
+#cbi-firewall-redirect,
+#cbi-network-switch_vlan,
+#cbi-firewall-zone {
+ table {
+ * {
+ font-size: small;
+ }
+
+ input[type="text"] {
+ width: 5rem;
+ }
+
+ select {
+ min-width: 3.5rem;
+ }
+ }
+}
+
+#cbi-network-switch_vlan {
+
+ .th,
+ .td {
+ flex-basis: 12%;
+ }
+
+ .table {
+ display: block;
+ }
+
+ .td {
+ width: 100%;
+ }
+}
+
+#cbi-firewall-zone {
+ .table {
+ display: block;
+ }
+
+ .td {
+ width: 100%;
+ }
+}
+
+[data-page^="admin-system-commands"] .panel-title,
+[data-page^="command-cfg"] .mobile-hide,
+[data-page^="command-cfg"] .showSide {
+ display: none;
+}
+
+// overview & mwan3
+#mwan3-service-status {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+#mwan3-service-status > .alert-message {
+ position: static;
+ transform: none;
+}
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/pure-min.less b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/pure-min.less
new file mode 100644
index 0000000..c33828d
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/pure-min.less
@@ -0,0 +1,245 @@
+// out: false
+/**
+Pure v3.0.0
+Copyright 2013 Yahoo!
+Licensed under the BSD License.
+https://github.com/pure-css/pure/blob/master/LICENSE
+*/
+/**
+normalize.css v | MIT License | https://necolas.github.io/normalize.css/
+Copyright (c) Nicolas Gallagher and Jonathan Neal
+*/
+/** normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+.pure-g {
+ display: flex;
+ flex-flow: row wrap;
+ align-content: flex-start
+}
+
+.pure-u {
+ display: inline-block;
+ vertical-align: top
+}
+
+.pure-u-1,
+.pure-u-1-1,
+.pure-u-1-12,
+.pure-u-1-2,
+.pure-u-1-24,
+.pure-u-1-3,
+.pure-u-1-4,
+.pure-u-1-5,
+.pure-u-1-6,
+.pure-u-1-8,
+.pure-u-10-24,
+.pure-u-11-12,
+.pure-u-11-24,
+.pure-u-12-24,
+.pure-u-13-24,
+.pure-u-14-24,
+.pure-u-15-24,
+.pure-u-16-24,
+.pure-u-17-24,
+.pure-u-18-24,
+.pure-u-19-24,
+.pure-u-2-24,
+.pure-u-2-3,
+.pure-u-2-5,
+.pure-u-20-24,
+.pure-u-21-24,
+.pure-u-22-24,
+.pure-u-23-24,
+.pure-u-24-24,
+.pure-u-3-24,
+.pure-u-3-4,
+.pure-u-3-5,
+.pure-u-3-8,
+.pure-u-4-24,
+.pure-u-4-5,
+.pure-u-5-12,
+.pure-u-5-24,
+.pure-u-5-5,
+.pure-u-5-6,
+.pure-u-5-8,
+.pure-u-6-24,
+.pure-u-7-12,
+.pure-u-7-24,
+.pure-u-7-8,
+.pure-u-8-24,
+.pure-u-9-24 {
+ display: inline-block;
+ letter-spacing: normal;
+ word-spacing: normal;
+ vertical-align: top;
+ text-rendering: auto
+}
+
+.pure-u-1-24 {
+ width: 4.1667%
+}
+
+.pure-u-1-12,
+.pure-u-2-24 {
+ width: 8.3333%
+}
+
+.pure-u-1-8,
+.pure-u-3-24 {
+ width: 12.5%
+}
+
+.pure-u-1-6,
+.pure-u-4-24 {
+ width: 16.6667%
+}
+
+.pure-u-1-5 {
+ width: 20%
+}
+
+.pure-u-5-24 {
+ width: 20.8333%
+}
+
+.pure-u-1-4,
+.pure-u-6-24 {
+ width: 25%
+}
+
+.pure-u-7-24 {
+ width: 29.1667%
+}
+
+.pure-u-1-3,
+.pure-u-8-24 {
+ width: 33.3333%
+}
+
+.pure-u-3-8,
+.pure-u-9-24 {
+ width: 37.5%
+}
+
+.pure-u-2-5 {
+ width: 40%
+}
+
+.pure-u-10-24,
+.pure-u-5-12 {
+ width: 41.6667%
+}
+
+.pure-u-11-24 {
+ width: 45.8333%
+}
+
+.pure-u-1-2,
+.pure-u-12-24 {
+ width: 50%
+}
+
+.pure-u-13-24 {
+ width: 54.1667%
+}
+
+.pure-u-14-24,
+.pure-u-7-12 {
+ width: 58.3333%
+}
+
+.pure-u-3-5 {
+ width: 60%
+}
+
+.pure-u-15-24,
+.pure-u-5-8 {
+ width: 62.5%
+}
+
+.pure-u-16-24,
+.pure-u-2-3 {
+ width: 66.6667%
+}
+
+.pure-u-17-24 {
+ width: 70.8333%
+}
+
+.pure-u-18-24,
+.pure-u-3-4 {
+ width: 75%
+}
+
+.pure-u-19-24 {
+ width: 79.1667%
+}
+
+.pure-u-4-5 {
+ width: 80%
+}
+
+.pure-u-20-24,
+.pure-u-5-6 {
+ width: 83.3333%
+}
+
+.pure-u-21-24,
+.pure-u-7-8 {
+ width: 87.5%
+}
+
+.pure-u-11-12,
+.pure-u-22-24 {
+ width: 91.6667%
+}
+
+.pure-u-23-24 {
+ width: 95.8333%
+}
+
+.pure-u-1,
+.pure-u-1-1,
+.pure-u-24-24,
+.pure-u-5-5 {
+ width: 100%
+}
+.col-1 {
+ flex: 1 1 30px !important;
+}
+
+.col-2 {
+ flex: 2 2 60px !important;
+}
+
+.col-3 {
+ flex: 3 3 90px !important;
+}
+
+.col-4 {
+ flex: 4 4 120px !important;
+}
+
+.col-5 {
+ flex: 5 5 150px !important;
+}
+
+.col-6 {
+ flex: 6 6 180px !important;
+}
+
+.col-7 {
+ flex: 7 7 210px !important;
+}
+
+.col-8 {
+ flex: 8 8 240px !important;
+}
+
+.col-9 {
+ flex: 9 9 270px !important;
+}
+
+.col-10 {
+ flex: 10 10 300px !important;
+}
\ No newline at end of file
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/responsive.less b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/responsive.less
new file mode 100644
index 0000000..118bdb6
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/responsive.less
@@ -0,0 +1,610 @@
+// out: false
+
+// ==========================================================================
+// Responsive Styles
+// Mobile-first responsive design for LuCI admin interface
+// ==========================================================================
+
+// 1. Browser-Specific Fixes
+// ==========================================================================
+
+// Internet Explorer Specific Fixes
+@media all and (-ms-high-contrast: none) {
+ .main>.main-left>.nav>.slide>.menu::before {
+ top: 30.25%;
+ }
+
+ .main>.main-left>.nav>li:last-child::before {
+ top: 20%;
+ }
+
+ .showSide::before {
+ top: -12px;
+ }
+}
+
+// 2. Large Desktop Screens (1600px and below)
+// ==========================================================================
+
+@media screen and (max-width: 1600px) {
+
+ // Header adjustments
+ header>.fill>.container>#logo {
+ margin: 0 2.5rem 0 0.5rem;
+ }
+
+ // Sidebar width
+ .main-left {
+ width: calc(0% + 13rem);
+ }
+
+ // Button and label sizing
+ .btn:not(button),
+ .label {
+ padding: 0.5rem 0.75rem;
+ }
+
+ // Form field sizing
+ .cbi-value-title {
+ width: 15rem;
+ padding-right: 0.6rem;
+ }
+
+ .cbi-value-field .cbi-dropdown,
+ .cbi-value-field .cbi-input-select,
+ .cbi-value input[type="text"],
+ .cbi-value input[type="password"],
+ .cbi-value textarea {
+ min-width: 18rem;
+ }
+
+ // Specific component adjustments
+ #cbi-firewall-zone .cbi-input-select {
+ min-width: 9rem;
+ }
+
+ .cbi-input-textarea {
+ font-size: small;
+ }
+
+ .node-admin-status>.main fieldset li>a {
+ padding: 0.3rem 0.6rem;
+ }
+}
+
+// 3. Standard Desktop Screens (1366px and below)
+// ==========================================================================
+
+@media screen and (max-width: 1366px) {
+
+ // Header adjustments
+ header>.fill>.container {
+ cursor: default;
+ }
+
+ // Sidebar width
+ .main-left {
+ width: calc(0% + 13rem);
+ }
+
+ // Tab styling
+ .tabs>li>a,
+ .cbi-tabmenu>li>a {
+ padding: 0.2rem 0.8rem;
+ }
+
+ // Panel and table adjustments
+ .panel-title {
+ font-size: 1.1rem;
+ padding-bottom: 1rem;
+ }
+
+ table {
+ width: 100% !important;
+ }
+
+ .table .cbi-input-text {
+ width: 100%;
+ }
+
+ // Form field sizing
+ .cbi-value-field .cbi-dropdown,
+ .cbi-value-field .cbi-input-select,
+ .cbi-value input[type="text"],
+ .cbi-value input[type="password"] {
+ min-width: 16rem;
+ }
+
+ #cbi-firewall-zone .cbi-input-select {
+ min-width: 5.5rem;
+ }
+
+ // Navigation font sizes
+ .main>.main-left>.nav>li,
+ .main>.main-left>.nav>li>a,
+ .main .main-left .nav>li>a:first-child,
+ .main>.main-left>.nav>.slide>.menu,
+ .main>.main-left>.nav>li>[data-title="Log_out"] {
+ font-size: 0.9rem;
+ }
+
+ .main>.main-left>.nav>.slide>.slide-menu>li>a {
+ font-size: 0.875rem;
+ }
+
+ // Modal adjustments
+ #modal_overlay {
+ top: 0rem;
+ }
+
+ // Page-specific table fixes
+ [data-page="admin-network-firewall-forwards"] .table:not(.cbi-section-table) {
+ display: block;
+ }
+
+ [data-page="admin-network-firewall-forwards"] .table:not(.cbi-section-table),
+ [data-page="admin-network-firewall-rules"] .table:not(.cbi-section-table),
+ [data-page="admin-network-hosts"] .table,
+ [data-page="admin-network-routes"] .table {
+ overflow-y: visible;
+ }
+
+ // Button sizing
+ .btn:not(button),
+ .cbi-button {
+ font-size: 0.875rem;
+ }
+}
+
+// 4. Small Desktop/Large Tablet (1152px and below)
+// ==========================================================================
+
+@media screen and (max-width: 1152px) {
+
+ // Header adjustments
+ header>.fill>.container>#logo {
+ display: none;
+ }
+
+ header>.fill>.container>.brand {
+ position: relative;
+ }
+
+ // Layout adjustments
+ html,
+ .main {
+ overflow-y: visible;
+ }
+
+ .main>.loading>span {
+ top: 25%;
+ }
+
+ .main-left {
+ width: calc(0% + 13rem);
+ }
+
+ // Login page specific
+ body:not(.logged-in) .showSide {
+ visibility: hidden;
+ width: 0;
+ margin: 0;
+ }
+
+ .node-main-login>.main .cbi-value-title {
+ text-align: left;
+ }
+
+ // Form field adjustments
+ .cbi-value-title {
+ width: 12rem;
+ padding-right: 1rem;
+ }
+
+ .cbi-value-field .cbi-dropdown,
+ .cbi-value-field .cbi-input-select,
+ .cbi-value input[type="text"] {
+ width: 16rem;
+ min-width: 16rem;
+ }
+
+ // Password field specific sizing
+ .cbi-value input[name^="pw"],
+ .cbi-value input[data-update="change"]:nth-child(2) {
+ width: 13rem !important;
+ min-width: 13rem;
+ }
+
+ // Code output adjustments
+ #diag-rc-output>pre,
+ #command-rc-output>pre,
+ [data-page="admin-services-wol"] .notice code {
+ font-size: 1rem;
+ }
+
+ // Table responsive behavior
+ .table {
+ display: block;
+ }
+
+ .Interfaces .table {
+ overflow-x: hidden;
+ }
+
+ #packages.table {
+ display: grid;
+ }
+
+ // Table row adjustments
+ .tr {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ }
+
+ .Overview .table[width="100%"]>.tr {
+ flex-wrap: nowrap;
+ }
+
+ .tr.placeholder {
+ border-bottom: thin solid #ddd;
+ }
+
+ .tr.placeholder>.td,
+ #cbi-firewall .tr>.td,
+ #cbi-network .tr:nth-child(2)>.td,
+ .cbi-section #wifi_assoclist_table .tr>.td {
+ border-top: 0;
+ }
+
+ // Table cell adjustments
+ .th,
+ .td {
+ display: inline-block;
+ align-self: flex-start;
+ flex: 2 2 10%;
+ text-overflow: ellipsis;
+ word-wrap: break-word;
+ }
+
+ .td select,
+ .td input[type="text"] {
+ width: 100%;
+ word-wrap: normal;
+ }
+
+ .td [data-dynlist]>input,
+ .td input.cbi-input-password {
+ width: calc(100% - 1.5rem);
+ }
+
+ .td[data-type="button"],
+ .td[data-type="fvalue"] {
+ flex: 1 1 12.5%;
+ text-align: left;
+ }
+
+ .th.cbi-value-field,
+ .td.cbi-value-field,
+ .th.cbi-section-table-cell,
+ .td.cbi-section-table-cell {
+ flex-basis: auto;
+ padding-top: 1rem;
+ }
+
+ // Section table adjustments
+ .cbi-section-table-row {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
+ }
+
+ .td.cbi-value-field,
+ .cbi-section-table-cell {
+ display: inline-block;
+ flex: 10 10 auto;
+ flex-basis: 50%;
+ text-align: center;
+ }
+
+ .td.cbi-section-actions {
+ vertical-align: bottom;
+ }
+
+ // Hide table headers on mobile
+ .tr.table-titles,
+ .tr.cbi-section-table-titles,
+ .tr.cbi-section-table-descr {
+ display: none;
+ }
+
+ .tr[data-title]::before,
+ .tr.cbi-section-table-titles.named::before {
+ font-size: 0.9rem;
+ display: block;
+ flex: 1 1 100%;
+ border-bottom: thin solid rgba(0, 0, 0, 0.26);
+ background: #e9ecef;
+ }
+
+ .td[data-title],
+ [data-page^="admin-status-realtime"] .td[id] {
+ text-align: left;
+ }
+
+ .td[data-title]::before {
+ display: block;
+ }
+
+ // Button spacing
+ .cbi-button+.cbi-button {
+ margin-left: 0;
+ }
+
+ .td.cbi-section-actions>*>*,
+ .td.cbi-section-actions>*>form>* {
+ margin: 2.1px 3px;
+ }
+
+ // Firewall form adjustments
+ .Firewall form {
+ position: static !important;
+ margin: 0 0 2rem 0;
+ padding: 2rem;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
+
+ input {
+ width: 100% !important;
+ margin: 0;
+ margin-top: 1rem;
+ }
+ }
+
+ .Firewall .center,
+ .Firewall .center::before {
+ text-align: left !important;
+ }
+
+ // Button sizing
+ .btn:not(button),
+ .cbi-button {
+ font-size: 0.875rem;
+ }
+}
+
+// 5. Tablet Portrait (768px and below)
+// ==========================================================================
+
+@media screen and (max-width: 768px) {
+
+ // Base font size
+ body {
+ font-size: 0.875rem;
+ }
+
+ // Progress bar adjustments
+ .cbi-progressbar::after {
+ font-size: 0.6rem;
+ }
+
+ // Mobile sidebar behavior
+ .main-left {
+ position: fixed;
+ z-index: 100;
+ width: 0;
+
+ &.active {
+ width: 13rem;
+ }
+ }
+
+ .main-right {
+ width: 100%;
+
+ &.active {
+ overflow-y: hidden;
+ }
+ }
+
+ .darkMask.active {
+ display: block;
+ }
+
+ // Mobile menu toggle
+ .showSide {
+ padding: 0.1rem;
+ position: relative;
+ z-index: 99;
+ display: inline-block !important;
+
+ &::before {
+ font-family: 'argon' !important;
+ font-style: normal !important;
+ font-weight: normal !important;
+ font-variant: normal !important;
+ text-transform: none !important;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ content: "\e20e";
+ font-size: 1.7rem;
+ }
+ }
+
+ // Brand visibility
+ header>.fill>.container>.flex1>.brand {
+ display: inline-block;
+ }
+
+ // Navigation font size
+ .main>.main-left>.nav>.slide>.slide-menu>li>a {
+ font-size: 0.875rem;
+ }
+}
+
+// 6. Mobile Portrait (600px and below)
+// ==========================================================================
+
+@media screen and (max-width: 600px) {
+
+ // Hide mobile elements
+ .mobile-hide {
+ display: none;
+ }
+
+ // Content margins
+ #maincontent>.container {
+ margin: 0 1rem 1rem 1rem;
+ }
+
+ // Form adjustments
+ .cbi-value-title {
+ text-align: left;
+ }
+
+ .cbi-dynlist p {
+ padding: 0.5rem 1rem;
+ }
+
+ // Prevent horizontal scroll
+ body {
+ overflow-x: hidden;
+ }
+
+ // Login page adjustments
+ .node-main-login .main .main-right #maincontent .container .cbi-map .cbi-section .cbi-section-node .cbi-value .cbi-value-field {
+ width: 16rem;
+ }
+
+ .node-main-login footer {
+ display: none;
+ }
+
+ // Tab scrollbar hiding
+ .tabs,
+ .cbi-tabmenu {
+ &::-webkit-scrollbar {
+ width: 0px;
+ height: 0px;
+ }
+ }
+
+ // Form field layout
+ .cbi-value-field,
+ .cbi-value-description {
+ display: block !important;
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+ }
+
+ [data-page="admin-system-admin-password"] .cbi-value-field {
+ display: table-cell !important;
+ }
+
+ // Modal adjustments
+ .modal.cbi-modal {
+ max-width: 100%;
+ max-height: none;
+ }
+
+ .modal {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ width: 100%;
+ min-width: 270px;
+ max-width: 600px;
+ min-height: 32px;
+ margin: 5em auto;
+ padding: 1em;
+ border-radius: 3px !important;
+ background: #fff;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 2px 0 rgba(0, 0, 0, 0.12);
+ }
+
+ // Dropdown adjustments
+ .cbi-dropdown[open]>ul.dropdown {
+ margin-bottom: 1rem;
+ }
+
+ // Login page footer
+ .login-page .login-container footer {
+ display: none;
+ }
+}
+
+// 7. Small Mobile (480px and below)
+// ==========================================================================
+
+@media screen and (max-width: 480px) {
+
+ // Hide mobile elements
+ .mobile-hide {
+ display: none;
+ }
+
+ // Interface box adjustments for overview page
+ div[style*="display:grid;grid-template-columns:repeat"] {
+ .ifacebox {
+ flex-basis: 80px;
+
+ .ifacebox-body {
+ padding: 0.875rem 0.5rem;
+ font-size: 0.6rem;
+ }
+ }
+ }
+
+ // Login page mobile adjustments
+ .login-page .login-container {
+ margin-left: 0rem !important;
+ width: 100%;
+
+ .login-form {
+ .form-login {
+ .input-group {
+ &::before {
+ color: #525461;
+ }
+
+ input {
+ color: #525461;
+ border-bottom: white 1px solid;
+ border-bottom: var(--white) 1px solid;
+ border-radius: 0;
+ }
+ }
+ }
+ }
+ }
+}
+
+// 8. Desktop Scrollbar Styling (600px and above)
+// ==========================================================================
+
+@media screen and (min-width: 600px) {
+ ::-webkit-scrollbar {
+ width: 10px;
+ height: 10px;
+ }
+
+ ::-webkit-scrollbar,
+ ::-webkit-scrollbar-corner {
+ background: transparent;
+ }
+
+ ::-webkit-scrollbar-thumb {
+ background: var(--primary);
+ border-radius: 10px;
+
+ &:hover {
+ background: var(--primary);
+ }
+
+ &:active {
+ background: var(--primary);
+ }
+ }
+}
\ No newline at end of file
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/sysauth.less b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/sysauth.less
new file mode 100644
index 0000000..7a4a755
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/less/sysauth.less
@@ -0,0 +1,257 @@
+// out: false
+
+// ==========================================================================
+// Login Page Styles
+// Optimized with LESS best practices
+// ==========================================================================
+
+// Mixins for common patterns
+.flex-center() {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.absolute-full() {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100vh;
+ width: 100vw;
+}
+
+.icon-base() {
+ font-family: 'argon' !important;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.login-page {
+ display: flex;
+ height: 100%;
+ flex-direction: column;
+
+ // Background video container
+ .video {
+ .absolute-full();
+ .flex-center();
+ background-color: var(--darker);
+ overflow: hidden;
+
+ video {
+ width: 100%;
+ height: auto;
+ }
+ }
+
+ // Volume control button
+ .volume-control {
+ position: fixed;
+ top: 1rem;
+ right: 1rem;
+ width: 1.5rem;
+ height: 1.5rem;
+ z-index: 5000;
+ cursor: pointer;
+ background: url(../img/volume_high.svg) no-repeat center/contain;
+
+ &.mute {
+ background-image: url(../img/volume_off.svg);
+ }
+ }
+
+ // Main background image
+ .main-bg {
+ .absolute-full();
+ background: url(../img/blank.png) no-repeat center/cover;
+ transition: all 0.5s ease;
+ }
+
+ // Login container
+ .login-container {
+ z-index: 10;
+ margin-left: 5%;
+ display: flex;
+ height: 100vh;
+ width: 26rem;
+ flex-direction: column;
+
+ background-color: var(--white);
+ backdrop-filter: blur(var(--blur-radius));
+ background-color: rgba(244, 245, 247, var(--blur-opacity));
+ box-shadow: rgba(0, 0, 0, 0.75) 0 0 35px -5px;
+
+ // Login form
+ .login-form {
+ .absolute-full();
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ max-width: 26rem;
+
+
+ // Brand/Logo section
+ .brand {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 50px auto 100px 50px;
+ color: var(--default);
+ text-decoration: none;
+
+ .icon {
+ width: 50px;
+ height: auto;
+ margin-right: 25px;
+ }
+
+ .brand-text {
+ margin-right: 45px;
+ font-size: 1.25rem;
+ font-weight: 700;
+ font-family: "TypoGraphica";
+ word-break: break-word;
+ }
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+
+ // Form container
+ .form-login {
+ width: 100%;
+ padding: 20px 50px;
+ box-sizing: border-box;
+
+ .errorbox {
+ padding-bottom: 2rem;
+ text-align: center;
+ color: var(--warning);
+ }
+
+ // Input group styling
+ .input-group {
+ position: relative;
+ margin-bottom: 1.25rem;
+
+ // Icon styling
+ &::before {
+ .icon-base();
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 100;
+ font-size: 1.5rem;
+ color: var(--default);
+ }
+
+ // Animated border
+ .border {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ height: 1px;
+ border-bottom: 1px solid var(--primary);
+ transform: scaleX(0);
+ transition: transform 0.3s ease;
+ }
+
+ // Input field
+ input {
+ display: block;
+ width: 100%;
+ margin: 0.825rem 0;
+ padding: 0.5rem 0.75rem 0.5rem 3rem;
+ font-size: 1rem;
+ line-height: 1.5em;
+ color: var(--default);
+ background-color: transparent;
+ background-clip: padding-box;
+ border: 0;
+ border-bottom: 1px solid var(--white);
+ border-radius: 0;
+ outline: none;
+ box-sizing: border-box;
+ box-shadow: 0 3px 2px rgba(233, 236, 239, 0.05);
+ transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
+
+ &:focus+.border {
+ transform: scaleX(1);
+ }
+ }
+
+ .cbi-input-password {
+ position: relative;
+ margin-bottom: 2rem;
+ }
+
+ // Icon content
+ &.user-icon::before {
+ content: "\e971";
+ }
+
+ &.pass-icon::before {
+ content: "\e910";
+ }
+ }
+ }
+
+ // Submit button
+ .cbi-button-apply {
+ width: 100% !important;
+ min-height: 50px;
+ margin: 30px 0 100px;
+ padding: 10px 0;
+ font-size: 15px;
+ font-weight: 600;
+ color: var(--white);
+ text-align: center;
+ letter-spacing: 0.8rem;
+ background-color: var(--primary) !important;
+ border: none;
+ border-radius: 6px;
+ outline: none;
+ cursor: pointer;
+ box-shadow: rgba(0, 0, 0, 0.1) 0 0 50px 0;
+ transition: all 0.3s ease !important;
+
+ &:hover,
+ &:focus {
+ opacity: 0.9;
+ }
+ }
+ }
+
+ // Footer
+ footer {
+ position: absolute;
+ bottom: 0;
+ z-index: 10;
+ display: flex;
+ justify-content: space-evenly;
+ width: 100%;
+ margin-top: auto;
+ padding: 0 0 30px;
+ color: var(--gray);
+ text-align: center;
+ line-height: 1.6rem;
+ font-size: 0.75rem;
+
+ .ftc {
+ position: absolute;
+ bottom: 30px;
+ width: 100%;
+ }
+
+ .luci-link {
+ display: block;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/root/etc/uci-defaults/30_luci-theme-argon b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/root/etc/uci-defaults/30_luci-theme-argon
new file mode 100644
index 0000000..6820d25
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/root/etc/uci-defaults/30_luci-theme-argon
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+if [ "$PKG_UPGRADE" != 1 ]; then
+ uci get luci.themes.Argon >/dev/null 2>&1 || \
+ uci batch <<-EOF
+ set luci.themes.Argon=/luci-static/argon
+ set luci.main.mediaurlbase=/luci-static/argon
+ commit luci
+ EOF
+fi
+
+exit 0
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/root/usr/libexec/rpcd/luci.argon_wallpaper b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/root/usr/libexec/rpcd/luci.argon_wallpaper
new file mode 100644
index 0000000..f046271
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/root/usr/libexec/rpcd/luci.argon_wallpaper
@@ -0,0 +1,108 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-3.0-only
+
+. /usr/share/libubox/jshn.sh
+
+src="$(uci -q get argon.@global[0].online_wallpaper || echo bing)"
+case "$src" in
+ bing|none|ghser|unsplash|wallhaven) WEB_PIC_SRC="$src" ;;
+ unsplash_[0-9]*|wallhaven_[0-9]*) WEB_PIC_SRC="$src" ;;
+ *) WEB_PIC_SRC="bing" ;;
+esac
+
+API_KEY="$(uci -q get argon.@global[0].use_api_key)"
+EXACT_RESO="$(uci -q get argon.@global[0].use_exact_resolution || echo '1')"
+case "$API_KEY" in *[!A-Za-z0-9_.-]*|????????????????????????????????????????????????????????????????*) API_KEY="" ;; esac
+case "$EXACT_RESO" in 1) ;; *) EXACT_RESO=0 ;; esac
+
+cache_key="$(echo "$WEB_PIC_SRC" | tr -c 'A-Za-z0-9_-' '_')"
+CACHE="/var/run/argon_${cache_key}.url"
+WRLOCK="/var/lock/argon_${cache_key}.lock"
+
+valid_url() {
+ case "$1" in
+ https://www.bing.com/*|//www.bing.com/*|https://api.vvhan.com/*|https://images.unsplash.com/*|https://wallhaven.cc/*) return 0 ;;
+ esac
+ return 1
+}
+
+fetch_pic_url() {
+ case "$WEB_PIC_SRC" in
+ bing)
+ local picpath="$(wget -T3 -qO- "https://www.bing.com/HPImageArchive.aspx?format=js&n=1" | jsonfilter -qe '@.images[0].url' | sed 's/1920x1080/UHD/g')"
+ [ -n "$picpath" ] && echo "//www.bing.com${picpath}"
+ ;;
+ ghser)
+ echo "https://api.vvhan.com/api/wallpaper/acg"
+ ;;
+ unsplash)
+ if [ -z "$API_KEY" ]; then
+ local pic_id="$(wget -T3 --spider "https://source.unsplash.com/1920x1080/daily?wallpapers" 2>&1 | grep -Eo "photo-\w+-\w+" | head -n1)"
+ [ -n "$pic_id" ] && echo "https://images.unsplash.com/${pic_id}?fm=jpg&fit=crop&w=1920&h=1080"
+ else
+ wget -T3 -qO- "https://api.unsplash.com/photos/random?client_id=${API_KEY}" | jsonfilter -qe '@["urls"]["regular"]'
+ fi
+ ;;
+ unsplash_*)
+ local collection_id="${WEB_PIC_SRC#unsplash_}"
+ if [ -z "$API_KEY" ]; then
+ local pic_id="$(wget -T3 --spider "https://source.unsplash.com/collection/${collection_id}/1920x1080" 2>&1 | grep -Eo "photo-\w+-\w+" | head -n1)"
+ [ -n "$pic_id" ] && echo "https://images.unsplash.com/${pic_id}?fm=jpg&fit=crop&w=1920&h=1080"
+ else
+ wget -T3 -qO- "https://api.unsplash.com/photos/random?client_id=${API_KEY}&collections=${collection_id}" | jsonfilter -qe '@["urls"]["regular"]'
+ fi
+ ;;
+ wallhaven)
+ wget -T3 -qO- "https://wallhaven.cc/api/v1/search?resolutions=1920x1080&sorting=random" | jsonfilter -qe '@.data[0].path'
+ ;;
+ wallhaven_*)
+ local tag_id="${WEB_PIC_SRC#wallhaven_}"
+ local use_reso="resolutions"
+ [ "$EXACT_RESO" -eq 1 ] || use_reso="atleast"
+ [ -z "$API_KEY" ] || API_KEY="apikey=$API_KEY&"
+ wget -T3 -qO- "https://wallhaven.cc/api/v1/search?${API_KEY}q=id%3A${tag_id}&${use_reso}=1920x1080&sorting=random" | jsonfilter -qe '@.data[0].path'
+ ;;
+ esac
+}
+
+try_update() {
+ exec 200>"$WRLOCK"
+ if flock -n 200 >/dev/null 2>&1; then
+ local picurl="$(fetch_pic_url)"
+ if valid_url "$picurl"; then
+ printf '%s\n' "$picurl" > "$CACHE"
+ printf '%s\n' "$picurl"
+ elif [ -s "$CACHE" ]; then
+ cat "$CACHE"
+ else
+ : > "$CACHE"
+ fi
+ flock -u 200 >/dev/null 2>&1
+ elif [ -s "$CACHE" ]; then
+ cat "$CACHE"
+ fi
+}
+
+case "$1" in
+"list")
+ json_init; json_add_object "get_url"; json_close_object; json_dump; json_cleanup
+ ;;
+"call")
+ case "$2" in
+ "get_url")
+ read -r input
+ if [ -f "$CACHE" ]; then
+ idle_t="$(($(date '+%s') - $(date -r "$CACHE" '+%s' 2>/dev/null || echo '0')))"
+ if [ -s "$CACHE" ] && [ "$idle_t" -le 43200 ]; then
+ url="$(cat "$CACHE")"
+ valid_url "$url" || url=""
+ json_init; json_add_string "url" "$url"; json_dump; json_cleanup; return 0
+ elif [ ! -s "$CACHE" ] && [ "$idle_t" -le 120 ]; then
+ echo '{ "url": "" }'; return 1
+ fi
+ fi
+ json_init; json_add_string "url" "$(try_update)"; json_dump; json_cleanup; return 0
+ ;;
+ esac
+ ;;
+esac
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/root/usr/share/rpcd/acl.d/luci-theme-argon.json b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/root/usr/share/rpcd/acl.d/luci-theme-argon.json
new file mode 100644
index 0000000..5e910df
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/root/usr/share/rpcd/acl.d/luci-theme-argon.json
@@ -0,0 +1,8 @@
+{
+ "luci-theme-argon": {
+ "description": "Grant UCI access for luci-theme-argon",
+ "read": {
+ "uci": [ "argon" ]
+ }
+ }
+}
diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/footer.ut b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/footer.ut
new file mode 100644
index 0000000..07e7bd0
--- /dev/null
+++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/footer.ut
@@ -0,0 +1,55 @@
+{#
+ Argon is a clean HTML5 theme for LuCI. It is based on luci-theme-material Argon Template
+
+ luci-theme-argon
+ Copyright 2020 Jerrykuku
+
+ Have a bug? Please create an issue here on GitHub!
+ https://github.com/jerrykuku/luci-theme-argon/issues
+
+ luci-theme-material:
+ Copyright 2015 Lutty Yang
+
+ Agron Theme
+ https://demos.creative-tim.com/argon-dashboard/index.html
+
+ Licensed to the public under the Apache License 2.0
+-#}
+
+
+
+
+
+
+
+