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 +-#} + + + + + + + + + diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/footer_login.ut b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/footer_login.ut new file mode 100644 index 0000000..8b1547a --- /dev/null +++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/footer_login.ut @@ -0,0 +1,47 @@ +{# + 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 +-#} + + + + + + + + + diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/header.ut b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/header.ut new file mode 100644 index 0000000..92c61ca --- /dev/null +++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/header.ut @@ -0,0 +1,181 @@ +{# + 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 + + Argon Theme + https://demos.creative-tim.com/argon-dashboard/index.html + + Licensed to the public under the Apache License 2.0 +-#} + +{% + import { readfile, access } from 'fs'; + import { cursor } from 'uci'; + import { srand } from 'math'; + import { getuid, getspnam } from 'luci.core'; + + const boardinfo = ubus.call("system", "board"); + const hostname = striptags(boardinfo?.hostname ?? '?'); + + let cfg = cursor(); + + //send as HTML5 + http.prepare_content('text/html; charset=UTF-8'); + + srand(+substr(reverse(time() + ""), 0, 8)); + + //Custom settings + let mode = 'normal'; + let bar_color = '#5e72e4'; + let primary, dark_primary, blur_radius, blur_radius_dark, blur_opacity, blur_opacity_dark; + const dark_css = readfile('/www/luci-static/argon/css/dark.css'); + if (access('/etc/config/argon')) { + primary = cfg.get_first('argon', 'global', 'primary'); + dark_primary = cfg.get_first('argon', 'global', 'dark_primary'); + blur_radius = cfg.get_first('argon', 'global', 'blur'); + blur_radius_dark = cfg.get_first('argon', 'global', 'blur_dark'); + blur_opacity = cfg.get_first('argon', 'global', 'transparency'); + blur_opacity_dark = cfg.get_first('argon', 'global', 'transparency_dark'); + mode = cfg.get_first('argon', 'global', 'mode'); + bar_color = (mode == 'dark') ? dark_primary : primary; + } + +-%} + + + + + + + {{ hostname }}{{ node?.title ? ` - ${striptags(node.title)}` : '' }} - LuCI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if (node?.css): %} + + {% endif -%} + {% if (css): %} + + {% endif -%} + + + + + + + +
+ +
+
+
+
+ +
+
+
+
+
+
+
+ {% if (getuid() == 0 && getspnam('root')?.pwdp === ''): %} +
+

{{ _('No password set!') }}

+

{{ _('There is no password set on this router. Please configure a root password to protect the web interface.') }}

+ {% if (dispatcher.lookup("admin/system/admin")): %} + + {% endif %} +
+ {% endif %} + + + + diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/header_login.ut b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/header_login.ut new file mode 100644 index 0000000..bc40263 --- /dev/null +++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/header_login.ut @@ -0,0 +1,125 @@ +{# + 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 + + Argon Theme + https://demos.creative-tim.com/argon-dashboard/index.html + + Licensed to the public under the Apache License 2.0 +-#} + +{% + import { readfile, access } from 'fs'; + import { cursor } from 'uci'; + import { srand } from 'math'; + + const boardinfo = ubus.call("system", "board"); + const hostname = striptags(boardinfo?.hostname ?? '?'); + + let cfg = cursor(); + + //send as HTML5 + http.prepare_content('text/html; charset=UTF-8'); + + srand(+substr(reverse(time() + ""), 0, 8)); + + //Custom settings + let mode = 'normal'; + let bar_color = '#5e72e4'; + let primary, dark_primary, blur_radius, blur_radius_dark, blur_opacity, blur_opacity_dark; + const dark_css = readfile('/www/luci-static/argon/css/dark.css'); + if (access('/etc/config/argon')) { + primary = cfg.get_first('argon', 'global', 'primary'); + dark_primary = cfg.get_first('argon', 'global', 'dark_primary'); + blur_radius = cfg.get_first('argon', 'global', 'blur'); + blur_radius_dark = cfg.get_first('argon', 'global', 'blur_dark'); + blur_opacity = cfg.get_first('argon', 'global', 'transparency'); + blur_opacity_dark = cfg.get_first('argon', 'global', 'transparency_dark'); + mode = cfg.get_first('argon', 'global', 'mode'); + bar_color = (mode == 'dark') ? dark_primary : primary; + } + +-%} + + + + + + + {{ hostname }}{{ node?.title ? ` - ${striptags(node.title)}` : '' }} - LuCI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if (node?.css): %} + + {% endif -%} + {% if (css): %} + + {% endif -%} + + + + diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/out_header_login.ut b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/out_header_login.ut new file mode 100644 index 0000000..54b599f --- /dev/null +++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/out_header_login.ut @@ -0,0 +1,12 @@ +{# + Copyright 2008 Steven Barth + Copyright 2008-2019 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-#} + +{% + if (!ctx.template_header_sent) { + include("themes/" + theme + "/header_login"); + ctx.template_header_sent = true; + } +%} diff --git a/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/sysauth.ut b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/sysauth.ut new file mode 100644 index 0000000..8c7f76e --- /dev/null +++ b/immortalwrt-patches/feeds/luci/themes/luci-theme-argon/ucode/template/themes/argon/sysauth.ut @@ -0,0 +1,165 @@ +{# + Argon is a clean HTML5 theme for LuCI. It is based on luci-theme-bootstrap and MUI and Argon Template + + luci-theme-argon + Copyright 2020 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-2016 Jo-Philipp Wich + Copyright 2012 David Menting + + MUI: + https://github.com/muicss/mui + + Argon Theme + https://demos.creative-tim.com/argon-dashboard/index.html + + Licensed to the public under the Apache License 2.0 +-#} + +{% include("themes/" + theme + "/out_header_login") %} +{% + import * as fs from 'fs'; + import { cursor } from 'uci'; + import { rand } from 'math'; + + let cfg = cursor(); + + //Fetch Local Background Media + + const imageTypes = " jpg jpeg png gif webp "; + const videoTypes = " mp4 webm "; + const allTypes = imageTypes + videoTypes; + + function fetchMedia(path, themeDir) { + let backgroundTable = []; + for (f in (fs.lsdir(path))) { + let ext = lc(split(f, '.')?.[1]); + if (ext && index(allTypes, " " + ext + " ") != -1) { + let bg = {}; + bg.type = ext; + bg.url = themeDir + f; + push(backgroundTable, bg); + } + } + return backgroundTable; + } + + function selectBackground(themeDir) { + let bgUrl = media + "/img/bg1.jpg"; + let remoteBgUrl = ""; + let backgroundType = "Image"; + let mimeType = ""; + + if (fs.access("/etc/config/argon")) { + let online_wallpaper = cfg.get_first('argon', 'global', 'online_wallpaper') ?? (cfg.get_first('argon', 'global', 'bing_background') == '1' ? 'bing' : null); + if (online_wallpaper && online_wallpaper != "none") { + const picurl = ubus.call("luci.argon_wallpaper", "get_url") ?? {}; + if (picurl?.url && match(picurl.url, /^(https?:\/\/|\/\/)www\.bing\.com\/|^(https?:\/\/)api\.vvhan\.com\/|^(https?:\/\/)images\.unsplash\.com\/|^(https?:\/\/)wallhaven\.cc\//)) + remoteBgUrl = picurl.url; + } + } + + let background = fetchMedia("/www" + themeDir, themeDir); + // Priority: local uploaded backgrounds win; only fall through to + // the remote Bing/preload URL when no local files exist. + if (length(background) > 0) { + let currentBg = background[(rand() % length(background))]; + bgUrl = currentBg.url; + if (index(videoTypes, " " + currentBg.type + " ") != -1) { + backgroundType = "Video"; + mimeType = "video/" + currentBg.type; + } + } + + return {bgUrl, remoteBgUrl, backgroundType, mimeType}; + } + + const boardinfo = ubus.call("system", "board"); + const hostname = striptags(boardinfo?.hostname ?? '?'); + const themeDir = media + "/background/"; + const bgArry = selectBackground(themeDir); + +%} + +