⛄ Sync 2026-09-29 03:13:52

This commit is contained in:
github-actions[bot] committed 2026-09-29 03:13:52 +08:00
1 parent 198ad5b3b8
commit 359c0e3d05
41 files changed
+1553 -5334

No files matched your search

+125
View File
@@ -0,0 +1,125 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-open-box
PKG_VERSION:=0.1.263
PKG_RELEASE:=1
PKG_MAINTAINER:=liandu2024
PKG_LICENSE:=GPL-3.0-or-later
# 1. 自动判断目标编译架构,映射至 Open-Box Release 预编译资产名
ifeq ($(ARCH),x86_64)
OPENBOX_ARCH:=x64
endif
ifeq ($(ARCH),aarch64)
OPENBOX_ARCH:=arm64
endif
PKG_SOURCE:=open-box-linux-$(OPENBOX_ARCH).tar.gz
PKG_SOURCE_URL:=https://github.com/liandu2024/Open-Box/releases/download/v$(PKG_VERSION)/
PKG_HASH:=skip
include $(INCLUDE_DIR)/package.mk
define Package/luci-app-open-box
SECTION:=luci
CATEGORY:=LuCI
SUBMENU:=3. Applications
TITLE:=LuCI support for Open-Box Transparent Proxy (Full Core Included)
# 架构约束:仅在目标平台为 x86_64 或 aarch64 时在 menuconfig 中展示,防止误选到不支持的 MIPS/ARMv7 平台
DEPENDS:=@(x86_64||aarch64) +kmod-tun +kmod-nft-queue +kmod-nft-nat +kmod-veth +ip-full +ca-bundle +luci-base
endef
define Package/luci-app-open-box/description
Open-Box all-in-one transparent proxy management suite for OpenWrt.
Pre-packaged with full sing-box core, embedded Node.js runtime, GeoSite/GeoIP databases and web dashboard.
endef
define Package/luci-app-open-box/conffiles
/etc/config/openbox
/opt/open-box/data/
endef
# 2. 构建准备:解压下载的对应架构核心资产包至构建目录
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)/_core
tar -xzf $(DL_DIR)/$(PKG_SOURCE) -C $(PKG_BUILD_DIR)/_core
endef
define Build/Configure
endef
define Build/Compile
endef
# 3. 安装与打包:同时铺装 LuCI 页面、系统脚本以及 /opt/open-box 核心
define Package/luci-app-open-box/install
# 安装 LuCI 菜单与权限声明
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-openbox.json $(1)/usr/share/luci/menu.d/
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-openbox.json $(1)/usr/share/rpcd/acl.d/
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/openbox
$(INSTALL_DATA) ./root/www/luci-static/resources/view/openbox/main.js $(1)/www/luci-static/resources/view/openbox/
# 安装系统服务与默认配置
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./root/etc/init.d/openbox $(1)/etc/init.d/openbox
$(INSTALL_BIN) ./root/etc/init.d/openbox-panel $(1)/etc/init.d/openbox-panel
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./root/etc/config/openbox $(1)/etc/config/openbox
$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_BIN) ./root/etc/uci-defaults/99-luci-app-openbox $(1)/etc/uci-defaults/99-luci-app-openbox
# 创建 CLI 管理命令软链接(直接指向 /opt/open-box/openwrt/bin/open-box 原版脚本)
$(INSTALL_DIR) $(1)/usr/bin
ln -sf /opt/open-box/openwrt/bin/open-box $(1)/usr/bin/open-box
# 仅复制 _core 子目录内容,避免递归包含
$(INSTALL_DIR) $(1)/opt/open-box
$(CP) $(PKG_BUILD_DIR)/_core/* $(1)/opt/open-box/
chmod -R 0755 $(1)/opt/open-box/bin $(1)/opt/open-box/node/bin $(1)/opt/open-box/openwrt/bin 2>/dev/null || true
chmod +x $(1)/opt/open-box/update.sh $(1)/opt/open-box/uninstall.sh 2>/dev/null || true
echo "3036" > $(1)/opt/open-box/data/panel-port
endef
define Package/luci-app-open-box/extra_provides
echo "libc.musl-x86_64.so.1";
endef
define Package/luci-app-open-box/prerm
#!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] || {
/etc/init.d/openbox stop >/dev/null 2>&1 || true
/etc/init.d/openbox-panel stop >/dev/null 2>&1 || true
}
exit 0
endef
# 6. 包卸载后钩子:清理残留静态资源与防火墙规则
define Package/luci-app-open-box/postrm
#!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] || {
rm -rf /tmp/luci-*cache* 2>/dev/null || true
rm -rf /www/luci-static/resources/view/openbox 2>/dev/null || true
uci -q delete firewall.openbox_panel || true
uci -q delete firewall.openbox_dns || true
uci -q delete firewall.openbox_tun_forward || true
uci -q delete firewall.openbox_tun_input || true
uci -q delete firewall.openbox_v6block || true
for _ob_rule in $$(uci -q show firewall 2>/dev/null | sed -n 's/^firewall\.\(openbox_srv_[A-Za-z0-9_]*\)=rule$$/\1/p'); do
uci -q delete "firewall.$$_ob_rule" || true
done
if [ -n "$$(uci -q changes firewall)" ]; then
uci -q commit firewall
/etc/init.d/firewall reload >/dev/null 2>&1 || true
fi
}
exit 0
endef
$(eval $(call BuildPackage,luci-app-open-box))
+1
View File
@@ -0,0 +1 @@
来自 https://github.com/liandu2024/Open-Box
+1
View File
@@ -0,0 +1 @@
zh_Hans
+65
View File
@@ -0,0 +1,65 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "Open-Box"
msgstr "Open-Box"
msgid "Open-Box 管理面板"
msgstr "Open-Box 管理面板"
msgid "服务运行状态"
msgstr "服务运行状态"
msgid "面板服务 (openbox-panel)"
msgstr "面板服务 (openbox-panel)"
msgid "内核代理服务 (openbox)"
msgstr "内核代理服务 (openbox)"
msgid "组件与更新维护"
msgstr "组件与更新维护"
msgid "核心组件状态"
msgstr "核心组件状态"
msgid "更新加速镜像"
msgstr "更新加速镜像"
msgid "系统升级与维护"
msgstr "系统升级与维护"
msgid "打开管理面板"
msgstr "打开管理面板"
msgid "修改端口"
msgstr "修改端口"
msgid "复制密码"
msgstr "复制密码"
msgid "启动面板"
msgstr "启动面板"
msgid "停止面板"
msgstr "停止面板"
msgid "重启面板"
msgstr "重启面板"
msgid "启动内核"
msgstr "启动内核"
msgid "紧急停止内核 (恢复直连)"
msgstr "紧急停止内核 (恢复直连)"
msgid "按配置重载/重启"
msgstr "按配置重载/重启"
msgid "检查并升级到最新版"
msgstr "检查并升级到最新版"
msgid "回退到上个版本 (Rollback)"
msgstr "回退到上个版本 (Rollback)"
msgid "卸载 Open-Box"
msgstr "卸载 Open-Box"
@@ -0,0 +1,6 @@
config openbox 'main'
option enabled '1'
option port '3036'
option channel 'mirror'
option mirror_prefix 'https://ghfast.top'
option auto_restart '1'
+96
View File
@@ -0,0 +1,96 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2026 OpenWrt.org
# Open-Box Core Service (sing-box transparent proxy)
START=96
STOP=09
USE_PROCD=1
INSTALL_ROOT="/opt/open-box"
openbox_cleanup() {
if command -v uci >/dev/null 2>&1; then
local changed=0
for rule in openbox_dns openbox_tun_forward openbox_tun_input openbox_v6block; do
if [ -n "$(uci -q get firewall.$rule 2>/dev/null)" ]; then
uci -q delete firewall.$rule
changed=1
fi
done
for _ob_rule in $(uci -q show firewall 2>/dev/null | sed -n 's/^firewall\.\(openbox_srv_[A-Za-z0-9_]*\)=rule$/\1/p'); do
uci -q delete "firewall.$_ob_rule" || true
changed=1
done
if [ "$changed" = "1" ] && [ -n "$(uci -q changes firewall)" ]; then
uci -q commit firewall
/etc/init.d/firewall reload >/dev/null 2>&1 || true
fi
fi
if command -v nft >/dev/null 2>&1; then
nft delete table inet openbox 2>/dev/null || true
fi
}
start_service() {
if [ -f "$INSTALL_ROOT/openwrt/initd/openbox" ] && [ ! -L "$INSTALL_ROOT/openwrt/initd/openbox" ]; then
"$INSTALL_ROOT/openwrt/initd/openbox" start
return $?
fi
if [ ! -d "$INSTALL_ROOT" ]; then
echo "[openbox] 核心组件尚未安装在 $INSTALL_ROOT,请先安装或下载核心组件。" >&2
return 1
fi
local deploy_cli="$INSTALL_ROOT/panel/server/cli/deploy.mjs"
local node_bin="$INSTALL_ROOT/node/bin/node"
local core_bin="$INSTALL_ROOT/bin/sing-box"
if [ -x "$node_bin" ] && [ -f "$deploy_cli" ]; then
OPENBOX_ROOT="$INSTALL_ROOT" ZASHBOARD_DB_PATH="$INSTALL_ROOT/data/openbox.sqlite" \
OPENSSL_CONF=/dev/null LD_LIBRARY_PATH="$INSTALL_ROOT/node/lib" \
"$node_bin" "$deploy_cli" >/dev/null 2>&1
return $?
elif [ -x "$core_bin" ] && [ -f "$INSTALL_ROOT/etc/config.json" ]; then
procd_open_instance openbox
procd_set_param command "$core_bin" run -c "$INSTALL_ROOT/etc/config.json" -D "$INSTALL_ROOT"
procd_set_param respawn
procd_set_param limits core="unlimited"
procd_close_instance
else
echo "[openbox] 核心服务未就绪,请在面板完成订阅或分流配置后启动。" >&2
return 1
fi
}
stop_service() {
if [ -f "$INSTALL_ROOT/openwrt/initd/openbox" ] && [ ! -L "$INSTALL_ROOT/openwrt/initd/openbox" ]; then
"$INSTALL_ROOT/openwrt/initd/openbox" stop
return $?
fi
if [ -x "$INSTALL_ROOT/bin/sing-box" ]; then
killall -9 sing-box 2>/dev/null || true
fi
openbox_cleanup
}
restart() {
stop
sleep 1
start
}
status() {
if [ -x "$INSTALL_ROOT/bin/sing-box" ] && pidof sing-box >/dev/null; then
echo "openbox is running (pid $(pidof sing-box))"
return 0
fi
echo "openbox is not running"
return 1
}
reload_service() {
restart
}
+98
View File
@@ -0,0 +1,98 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2026 OpenWrt.org
# Open-Box Panel Web Service
START=95
STOP=10
USE_PROCD=1
INSTALL_ROOT="/opt/open-box"
openbox_get_port() {
local p=""
[ -f "$INSTALL_ROOT/data/panel-port" ] && p=$(cat "$INSTALL_ROOT/data/panel-port" 2>/dev/null | tr -dc '0-9')
if [ -z "$p" ]; then
config_load openbox
config_get p main port 3036
fi
[ -n "$p" ] || p=3036
echo "$p"
}
ensure_firewall_rule() {
local port="$1"
command -v uci >/dev/null 2>&1 || return 0
local cur_port=$(uci -q get firewall.openbox_panel.dest_port 2>/dev/null)
if [ "$cur_port" != "$port" ]; then
uci -q delete firewall.openbox_panel 2>/dev/null || true
uci -q set firewall.openbox_panel=rule
uci -q set firewall.openbox_panel.name='Open-Box Panel'
uci -q set firewall.openbox_panel.src='lan'
uci -q set firewall.openbox_panel.proto='tcp'
uci -q set firewall.openbox_panel.dest_port="$port"
uci -q set firewall.openbox_panel.target='ACCEPT'
uci -q commit firewall
/etc/init.d/firewall reload >/dev/null 2>&1 || true
fi
}
start_service() {
if [ -f "$INSTALL_ROOT/openwrt/initd/openbox-panel" ] && [ ! -L "$INSTALL_ROOT/openwrt/initd/openbox-panel" ]; then
"$INSTALL_ROOT/openwrt/initd/openbox-panel" start
return $?
fi
if [ ! -d "$INSTALL_ROOT" ] || [ ! -x "$INSTALL_ROOT/node/bin/node" ]; then
echo "[openbox-panel] 核心未安装在 $INSTALL_ROOT,请先执行 open-box install 或在 LuCI 页面下载安装核心。" >&2
return 1
fi
local node_bin="$INSTALL_ROOT/node/bin/node"
local panel_app="$INSTALL_ROOT/panel/server/index.mjs"
[ -f "$panel_app" ] || panel_app="$INSTALL_ROOT/panel/server/index.js"
local port=$(openbox_get_port)
mkdir -p "$INSTALL_ROOT/data" 2>/dev/null || true
echo "$port" > "$INSTALL_ROOT/data/panel-port"
ensure_firewall_rule "$port"
if [ -f "$INSTALL_ROOT/openwrt/bin/open-box" ] && [ ! -e /usr/bin/open-box ]; then
ln -sf "$INSTALL_ROOT/openwrt/bin/open-box" /usr/bin/open-box 2>/dev/null || true
fi
procd_open_instance openbox-panel
procd_set_param command "$node_bin" "$panel_app"
procd_set_param env OPENBOX_ROOT="$INSTALL_ROOT" OPENSSL_CONF=/dev/null LD_LIBRARY_PATH="$INSTALL_ROOT/node/lib" PORT="$port"
procd_set_param respawn 3600 5 0
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
stop_service() {
if [ -f "$INSTALL_ROOT/openwrt/initd/openbox-panel" ] && [ ! -L "$INSTALL_ROOT/openwrt/initd/openbox-panel" ]; then
"$INSTALL_ROOT/openwrt/initd/openbox-panel" stop
return $?
fi
return 0
}
status() {
if pgrep -f "$INSTALL_ROOT/panel/server" >/dev/null 2>&1; then
local pid=$(pgrep -f "$INSTALL_ROOT/panel/server" | head -n 1)
local port=$(openbox_get_port)
echo "openbox-panel is running (pid $pid, port $port)"
return 0
fi
echo "openbox-panel is not running"
return 1
}
reload_service() {
restart
}
service_triggers() {
procd_add_reload_trigger "openbox"
}
@@ -0,0 +1,26 @@
#!/bin/sh
# Set default firewall rule for openbox-panel
uci -q get firewall.openbox_panel >/dev/null || {
uci -q set firewall.openbox_panel=rule
uci -q set firewall.openbox_panel.name='Open-Box Panel'
uci -q set firewall.openbox_panel.src='lan'
uci -q set firewall.openbox_panel.proto='tcp'
uci -q set firewall.openbox_panel.dest_port='3036'
uci -q set firewall.openbox_panel.target='ACCEPT'
uci -q commit firewall
}
# Create open-box CLI link if available
[ -e /usr/bin/open-box ] || {
if [ -f /usr/share/openbox/open-box ]; then
ln -sf /usr/share/openbox/open-box /usr/bin/open-box
elif [ -f /opt/open-box/openwrt/bin/open-box ]; then
ln -sf /opt/open-box/openwrt/bin/open-box /usr/bin/open-box
fi
}
# Clean LuCI cache
rm -rf /tmp/luci-*cache* 2>/dev/null || true
exit 0
+208
View File
@@ -0,0 +1,208 @@
#!/bin/sh
# Open-Box 命令行管理工具
INSTALL_ROOT="/opt/open-box"
OPENBOX_PORT_FILE="$INSTALL_ROOT/data/panel-port"
OPENBOX_PORT_FALLBACK=2026
OPENBOX_PORT_DEFAULT=3036
OPENBOX_RESERVED_PORTS="9095 7853 7854 7891 53 22 80 443"
if [ -f "$INSTALL_ROOT/openwrt/bin/open-box" ] && [ "$0" != "$INSTALL_ROOT/openwrt/bin/open-box" ] && [ ! -L "$INSTALL_ROOT/openwrt/bin/open-box" ]; then
exec "$INSTALL_ROOT/openwrt/bin/open-box" "$@"
fi
openbox_port_problem() {
_p="$1"
case "$_p" in
''|*[!0-9]*) echo "端口要填 1024-65535 的数字"; return 0 ;;
esac
if [ "$_p" -lt 1024 ] || [ "$_p" -gt 65535 ]; then
echo "端口要在 1024-65535 之间(1024 以下是系统保留端口)"
return 0
fi
for _r in $OPENBOX_RESERVED_PORTS; do
if [ "$_p" = "$_r" ]; then
echo "$_p 是 Open-Box 自己或系统服务在用的端口(内核 API / DNS / SSH / HTTP 等),换一个"
return 0
fi
done
_busy=""
if command -v ss >/dev/null 2>&1; then
_busy=$(ss -ltn 2>/dev/null | awk 'NR>1 {print $4}' | sed 's/.*[:.]//' | grep -x "$_p" | head -n 1)
elif command -v netstat >/dev/null 2>&1; then
_busy=$(netstat -ltn 2>/dev/null | awk '{print $4}' | sed 's/.*[:.]//' | grep -x "$_p" | head -n 1)
fi
if [ -n "$_busy" ]; then
echo "$_p 已经被别的程序占用(已在监听)"
return 0
fi
return 1
}
openbox_current_port() {
_cp=$(cat "$OPENBOX_PORT_FILE" 2>/dev/null | tr -dc '0-9')
[ -n "$_cp" ] || _cp="$OPENBOX_PORT_DEFAULT"
printf '%s\n' "$_cp"
}
openbox_get_lan_ip() {
LAN_IP=""
command -v uci >/dev/null 2>&1 && LAN_IP=$(uci -q get network.lan.ipaddr 2>/dev/null | tr " " "\n" | head -n 1 | cut -d/ -f1)
if [ -z "$LAN_IP" ] && command -v ip >/dev/null 2>&1; then
LAN_IP=$(ip -4 -o addr show br-lan 2>/dev/null | awk '{ print $4 }' | cut -d/ -f1 | head -n 1)
fi
[ -n "$LAN_IP" ] || LAN_IP="192.168.1.1"
printf '%s\n' "$LAN_IP"
}
openbox_get_password() {
if [ -f "$INSTALL_ROOT/data/openbox.sqlite" ] && [ -x "$INSTALL_ROOT/node/bin/node" ]; then
_pw=$(OPENSSL_CONF=/dev/null LD_LIBRARY_PATH="$INSTALL_ROOT/node/lib" "$INSTALL_ROOT/node/bin/node" -e '
try {
const sqlite3 = require("sqlite3");
const db = new sqlite3.Database("'$INSTALL_ROOT'/data/openbox.sqlite", sqlite3.OPEN_READONLY, (err) => {
if (err) process.exit(1);
db.get("SELECT value FROM settings WHERE key = ?", ["password"], (e, row) => {
if (row && row.value) console.log(row.value);
db.close();
});
});
} catch (_) {}
' 2>/dev/null)
if [ -n "$_pw" ]; then
printf '%s\n' "$_pw"
return 0
fi
fi
if [ -f "$INSTALL_ROOT/data/password" ]; then
cat "$INSTALL_ROOT/data/password" 2>/dev/null
return 0
fi
echo "尚未设置(首次打开面板设置管理密码)"
}
openbox_get_version() {
_ver=$(sed -n 's/.*"version" *: *"\([^"]*\)".*/\1/p' "$INSTALL_ROOT/meta.json" 2>/dev/null | head -n 1)
[ -n "$_ver" ] || _ver="未安装"
printf '%s\n' "$_ver"
}
cmd="${1:-}"
case "$cmd" in
password)
openbox_get_password
exit 0
;;
port)
if [ $# -ge 2 ]; then
new_port="$2"
_reason=$(openbox_port_problem "$new_port") && {
echo "面板端口不能用:$_reason" >&2
exit 1
}
mkdir -p "$INSTALL_ROOT/data"
printf '%s\n' "$new_port" > "$OPENBOX_PORT_FILE"
if command -v uci >/dev/null 2>&1; then
uci -q set openbox.main.port="$new_port"
uci -q set firewall.openbox_panel.dest_port="$new_port"
uci -q commit openbox 2>/dev/null || true
uci -q commit firewall 2>/dev/null || true
/etc/init.d/firewall reload >/dev/null 2>&1 || true
fi
echo "面板端口已修改为: $new_port"
/etc/init.d/openbox-panel restart >/dev/null 2>&1 || true
exit 0
else
openbox_current_port
exit 0
fi
;;
check)
if [ -f "$INSTALL_ROOT/update.sh" ]; then
sh "$INSTALL_ROOT/update.sh" --probe direct
elif [ -f /usr/share/openbox/update.sh ]; then
sh /usr/share/openbox/update.sh --probe direct
else
echo "未找到更新脚本"
fi
exit 0
;;
update)
shift
if [ -f "$INSTALL_ROOT/update.sh" ]; then
sh "$INSTALL_ROOT/update.sh" "$@"
elif [ -f /usr/share/openbox/update.sh ]; then
sh /usr/share/openbox/update.sh "$@"
else
echo "未找到更新脚本" >&2
exit 1
fi
exit 0
;;
restart)
/etc/init.d/openbox restart
/etc/init.d/openbox-panel restart
echo "已重启 Open-Box 服务与面板"
exit 0
;;
uninstall)
shift
if [ -f "$INSTALL_ROOT/uninstall.sh" ]; then
sh "$INSTALL_ROOT/uninstall.sh" "$@"
elif [ -f /usr/share/openbox/uninstall.sh ]; then
sh /usr/share/openbox/uninstall.sh "$@"
else
/etc/init.d/openbox stop
/etc/init.d/openbox-panel stop
rm -rf "$INSTALL_ROOT"
echo "已停止并清理"
fi
exit 0
;;
install)
shift
if [ -f /usr/share/openbox/install.sh ]; then
sh /usr/share/openbox/install.sh "$@"
else
curl -fsSL https://raw.githubusercontent.com/liandu2024/Open-Box/main/scripts/install.sh | sh -s -- "$@"
fi
exit 0
;;
*)
VERSION=$(openbox_get_version)
echo ""
echo "Open-Box $VERSION"
echo " 1) 当前密码"
echo " 2) 重新启动"
echo " 3) 检查升级"
echo " 4) 卸载"
echo " 5) 退出"
printf '请选择 [1-5]: '
read -r choice
case "$choice" in
1)
port=$(openbox_current_port)
lan_ip=$(openbox_get_lan_ip)
pw=$(openbox_get_password)
echo ""
echo " 面板地址: http://$lan_ip:$port"
echo " 当前密码: $pw"
echo ""
;;
2)
/etc/init.d/openbox restart
/etc/init.d/openbox-panel restart
echo "服务已重启。"
;;
3)
sh "$0" update
;;
4)
sh "$0" uninstall
;;
5|*)
exit 0
;;
esac
;;
esac
@@ -0,0 +1,12 @@
{
"admin/services/openbox": {
"title": "Open-Box",
"action": {
"type": "view",
"path": "openbox/main"
},
"depends": {
"acl": [ "luci-app-openbox" ]
}
}
}
+136
View File
@@ -0,0 +1,136 @@
#!/bin/sh
# Open-Box 一键安装脚本
set -eu
umask 022
REPO="liandu2024/Open-Box"
INSTALL_ROOT="/opt/open-box"
CHANNEL="mirror"
MIRROR_PREFIX="https://ghfast.top"
PANEL_PORT="3036"
info() { echo "[open-box] $*"; }
warn() { echo "[open-box] 警告:$*" >&2; }
die() { echo "[open-box] 错误:$*" >&2; exit 1; }
while [ $# -gt 0 ]; do
case "$1" in
--mirror)
CHANNEL="mirror"
shift
if [ $# -ge 1 ]; then
case "$1" in
--*) ;;
*) MIRROR_PREFIX="$1"; shift ;;
esac
fi
;;
--direct)
CHANNEL="direct"
MIRROR_PREFIX=""
shift
;;
--port)
shift
[ $# -ge 1 ] || die "--port 后面要跟端口号"
PANEL_PORT="$1"
shift
;;
*) shift ;;
esac
done
detect_arch() {
RAW_ARCH=$(uname -m 2>/dev/null || true)
case "$RAW_ARCH" in
x86_64) ARCH="x64" ;;
aarch64) ARCH="arm64" ;;
*) die "不支持的 CPU 架构:${RAW_ARCH:-未知}。Open-Box 目前仅支持 x86_64 与 aarch64(arm64)。" ;;
esac
}
detect_arch
if [ -f "/etc/config/openbox" ]; then
p=$(uci -q get openbox.main.port 2>/dev/null || true)
[ -n "$p" ] && PANEL_PORT="$p"
fi
TMP_DL=$(mktemp -d /tmp/openbox-inst.XXXXXX 2>/dev/null || echo "/tmp/openbox-inst.$$")
mkdir -p "$TMP_DL"
trap 'rm -rf "$TMP_DL"' EXIT INT TERM
ASSET="open-box-linux-${ARCH}.tar.gz"
ASSET_URL="https://github.com/$REPO/releases/latest/download/$ASSET"
SHA_URL="$ASSET_URL.sha256"
build_url() {
url="$1"
if [ "$CHANNEL" = "mirror" ] && [ -n "$MIRROR_PREFIX" ]; then
case "$MIRROR_PREFIX" in
http://*|https://*) printf '%s/%s
' "${MIRROR_PREFIX%/}" "$url" ;;
*) printf 'https://%s/%s
' "${MIRROR_PREFIX%/}" "$url" ;;
esac
else
printf '%s
' "$url"
fi
}
info "下载校验和与发布包: $ASSET ..."
curl -fsSL -o "$TMP_DL/$ASSET.sha256" "$(build_url "$SHA_URL")" 2>/dev/null || wget -q -O "$TMP_DL/$ASSET.sha256" "$(build_url "$SHA_URL")" || die "下载校验文件失败"
curl -fsSL -o "$TMP_DL/$ASSET" "$(build_url "$ASSET_URL")" 2>/dev/null || wget -q -O "$TMP_DL/$ASSET" "$(build_url "$ASSET_URL")" || die "下载发布包失败"
info "校验 SHA256 ..."
( cd "$TMP_DL" && sha256sum -c "$ASSET.sha256" >/dev/null 2>&1 ) || die "安装包校验失败 (SHA256 不匹配)"
info "解包到 $INSTALL_ROOT ..."
mkdir -p "$INSTALL_ROOT"
tar -xzf "$TMP_DL/$ASSET" -C "$INSTALL_ROOT" || die "解包失败"
chown -R 0:0 "$INSTALL_ROOT" 2>/dev/null || true
mkdir -p "$INSTALL_ROOT/data"
printf '%s
' "$PANEL_PORT" > "$INSTALL_ROOT/data/panel-port"
if [ "$CHANNEL" = "mirror" ]; then
printf 'mirror
%s
' "$MIRROR_PREFIX" > "$INSTALL_ROOT/data/channel"
else
printf 'direct
' > "$INSTALL_ROOT/data/channel"
fi
cp -f "$INSTALL_ROOT/openwrt/initd/openbox" /etc/init.d/openbox 2>/dev/null || true
cp -f "$INSTALL_ROOT/openwrt/initd/openbox-panel" /etc/init.d/openbox-panel 2>/dev/null || true
chmod +x /etc/init.d/openbox /etc/init.d/openbox-panel 2>/dev/null || true
if [ -f "$INSTALL_ROOT/openwrt/bin/open-box" ]; then
chmod +x "$INSTALL_ROOT/openwrt/bin/open-box" 2>/dev/null || true
ln -sf "$INSTALL_ROOT/openwrt/bin/open-box" /usr/bin/open-box 2>/dev/null || true
fi
if [ -d "$INSTALL_ROOT/openwrt/luci/htdocs/luci-static/resources/view/openbox" ]; then
mkdir -p /www/luci-static/resources/view/openbox
cp -f "$INSTALL_ROOT/openwrt/luci/htdocs/luci-static/resources/view/openbox/"*.js /www/luci-static/resources/view/openbox/ 2>/dev/null || true
fi
if [ -f "$INSTALL_ROOT/openwrt/luci/root/usr/share/luci/menu.d/luci-app-openbox.json" ]; then
mkdir -p /usr/share/luci/menu.d
cp -f "$INSTALL_ROOT/openwrt/luci/root/usr/share/luci/menu.d/luci-app-openbox.json" /usr/share/luci/menu.d/ 2>/dev/null || true
fi
if [ -f "$INSTALL_ROOT/openwrt/luci/root/usr/share/rpcd/acl.d/luci-app-openbox.json" ]; then
mkdir -p /usr/share/rpcd/acl.d
cp -f "$INSTALL_ROOT/openwrt/luci/root/usr/share/rpcd/acl.d/luci-app-openbox.json" /usr/share/rpcd/acl.d/ 2>/dev/null || true
fi
rm -rf /tmp/luci-*cache* 2>/dev/null || true
/etc/init.d/rpcd restart >/dev/null 2>&1 || true
/etc/init.d/openbox-panel enable >/dev/null 2>&1 || true
/etc/init.d/openbox-panel restart >/dev/null 2>&1 || true
info "Open-Box 核心组件安装完成!"
+85
View File
@@ -0,0 +1,85 @@
#!/bin/sh
# Open-Box 卸载清理脚本
set -eu
INSTALL_ROOT="/opt/open-box"
STATUS_PATH="/tmp/openbox-uninstall.status"
PURGE=0
DETACH=0
while [ $# -gt 0 ]; do
case "$1" in
--purge) PURGE=1; shift ;;
--detach) DETACH=1; shift ;;
*) shift ;;
esac
done
if [ "$DETACH" = "1" ]; then
_args=""
[ "$PURGE" = "1" ] && _args="--purge"
nohup sh "$0" $_args >/dev/null 2>&1 &
echo "卸载已在后台启动"
exit 0
fi
{
echo "pid=$$"
echo "stage=stopping"
} > "$STATUS_PATH" 2>/dev/null || true
/etc/init.d/openbox stop >/dev/null 2>&1 || true
/etc/init.d/openbox-panel stop >/dev/null 2>&1 || true
/etc/init.d/openbox disable >/dev/null 2>&1 || true
/etc/init.d/openbox-panel disable >/dev/null 2>&1 || true
{
echo "pid=$$"
echo "stage=firewall"
} > "$STATUS_PATH" 2>/dev/null || true
if command -v uci >/dev/null 2>&1; then
uci -q delete firewall.openbox_panel || true
uci -q delete firewall.openbox_dns || true
uci -q delete firewall.openbox_tun_forward || true
uci -q delete firewall.openbox_tun_input || true
uci -q delete firewall.openbox_v6block || true
for _ob_rule in $(uci -q show firewall 2>/dev/null | sed -n 's/^firewall\.\(openbox_srv_[A-Za-z0-9_]*\)=rule$/\1/p'); do
uci -q delete "firewall.$_ob_rule" || true
done
if [ -n "$(uci -q changes firewall)" ]; then
uci -q commit firewall
/etc/init.d/firewall reload >/dev/null 2>&1 || true
fi
fi
{
echo "pid=$$"
echo "stage=files"
} > "$STATUS_PATH" 2>/dev/null || true
rm -f /etc/init.d/openbox /etc/init.d/openbox-panel
rm -f /www/luci-static/resources/view/openbox/main.js /www/luci-static/resources/view/openbox/status.js
rmdir /www/luci-static/resources/view/openbox 2>/dev/null || true
rm -f /usr/share/luci/menu.d/luci-app-openbox.json
rm -f /usr/share/rpcd/acl.d/luci-app-openbox.json
[ -L /usr/bin/open-box ] && rm -f /usr/bin/open-box
rm -rf /tmp/luci-*cache* 2>/dev/null || true
if [ "$PURGE" = "1" ]; then
rm -rf "$INSTALL_ROOT"
else
for entry in "$INSTALL_ROOT"/*; do
[ -e "$entry" ] || continue
base=$(basename -- "$entry")
[ "$base" = "data" ] && continue
rm -rf "$entry"
done
fi
{
echo "pid=$$"
echo "stage=done"
} > "$STATUS_PATH" 2>/dev/null || true
echo "Open-Box 卸载完成"
+204
View File
@@ -0,0 +1,204 @@
#!/bin/sh
# Open-Box 更新/回退脚本
set -eu
umask 022
REPO="liandu2024/Open-Box"
INSTALL_ROOT="/opt/open-box"
STATUS_PATH="/tmp/openbox-update.status"
CANCEL_FLAG="/tmp/openbox-update.cancel"
UPDATE_LOG="/tmp/openbox-update.log"
STATUS_PID="$$"
DETACH=0
ROLLBACK_MODE=0
CHANNEL="direct"
MIRROR_PREFIX=""
PROBE_CHANNEL=""
CANCEL_MODE=0
info() { echo "[open-box] $*"; }
warn() { echo "[open-box] 警告:$*" >&2; }
die() {
echo "[open-box] 错误:$*" >&2
write_status failed "" "" "$*"
exit 1
}
write_status() {
[ -n "$STATUS_PID" ] || return 0
_ws_tmp="$STATUS_PATH.$$.tmp"
{
echo "pid=$STATUS_PID"
echo "stage=$1"
echo "bytes=${2:-}"
echo "total=${3:-}"
echo "message=${4:-}"
} > "$_ws_tmp" 2>/dev/null && mv -f "$_ws_tmp" "$STATUS_PATH" 2>/dev/null || true
}
check_cancel() {
if [ -e "$CANCEL_FLAG" ]; then
write_status cancelled "" "" "已取消更新"
exit 0
fi
}
while [ $# -gt 0 ]; do
case "$1" in
--detach) DETACH=1; shift ;;
--rollback) ROLLBACK_MODE=1; shift ;;
--cancel) CANCEL_MODE=1; shift ;;
--direct) CHANNEL="direct"; MIRROR_PREFIX=""; shift ;;
--mirror)
CHANNEL="mirror"
shift
if [ $# -ge 1 ]; then
case "$1" in
--*) ;;
*) MIRROR_PREFIX="$1"; shift ;;
esac
fi
;;
--probe)
shift
PROBE_CHANNEL="$1"
shift
;;
*) shift ;;
esac
done
if [ "$CANCEL_MODE" = "1" ]; then
touch "$CANCEL_FLAG" 2>/dev/null || true
echo "requested"
exit 0
fi
if [ -n "$PROBE_CHANNEL" ]; then
start_time=$(date +%s%N 2>/dev/null || date +%s)
target="https://github.com/$REPO/releases/latest/download/open-box-linux-x64.tar.gz.sha256"
if [ "$PROBE_CHANNEL" != "direct" ]; then
case "$PROBE_CHANNEL" in
http://*|https://*) target="${PROBE_CHANNEL%/}/$target" ;;
*) target="https://${PROBE_CHANNEL%/}/$target" ;;
esac
fi
if curl -fsSL --connect-timeout 8 --max-time 15 -o /dev/null "$target" 2>/dev/null || wget -q --timeout=15 -O /dev/null "$target" 2>/dev/null; then
end_time=$(date +%s%N 2>/dev/null || date +%s)
ms=$(( (end_time - start_time) / 1000000 2>/dev/null || 100 ))
[ "$ms" -le 0 ] && ms=80
echo "ok $ms ms"
else
echo "fail 连接超时或无法访问"
fi
exit 0
fi
if [ "$DETACH" = "1" ]; then
: > "$UPDATE_LOG" 2>/dev/null || true
write_status starting "" "" ""
rm -f "$CANCEL_FLAG" 2>/dev/null || true
_args=""
[ "$ROLLBACK_MODE" = "1" ] && _args="$_args --rollback"
if [ "$CHANNEL" = "mirror" ]; then
_args="$_args --mirror $MIRROR_PREFIX"
else
_args="$_args --direct"
fi
nohup sh "$0" $_args >"$UPDATE_LOG" 2>&1 </dev/null &
echo "升级已在后台启动,进度见 $STATUS_PATH"
exit 0
fi
write_status probing "" "" ""
check_cancel
RAW_ARCH=$(uname -m 2>/dev/null || true)
case "$RAW_ARCH" in
x86_64) ARCH="x64" ;;
aarch64) ARCH="arm64" ;;
*) die "不支持的 CPU 架构: $RAW_ARCH" ;;
esac
ASSET="open-box-linux-${ARCH}.tar.gz"
ASSET_URL="https://github.com/$REPO/releases/latest/download/$ASSET"
SHA_URL="$ASSET_URL.sha256"
build_url() {
url="$1"
if [ "$CHANNEL" = "mirror" ] && [ -n "$MIRROR_PREFIX" ]; then
case "$MIRROR_PREFIX" in
http://*|https://*) printf '%s/%s
' "${MIRROR_PREFIX%/}" "$url" ;;
*) printf 'https://%s/%s
' "${MIRROR_PREFIX%/}" "$url" ;;
esac
else
printf '%s
' "$url"
fi
}
TMP_DL=$(mktemp -d /tmp/openbox-upd.XXXXXX 2>/dev/null || echo "/tmp/openbox-upd.$$")
mkdir -p "$TMP_DL"
trap 'rm -rf "$TMP_DL"' EXIT INT TERM
write_status downloading 0 100000000 "开始下载"
check_cancel
curl -fsSL -o "$TMP_DL/$ASSET.sha256" "$(build_url "$SHA_URL")" 2>/dev/null || wget -q -O "$TMP_DL/$ASSET.sha256" "$(build_url "$SHA_URL")" || die "下载校验和失败"
check_cancel
curl -fsSL -o "$TMP_DL/$ASSET" "$(build_url "$ASSET_URL")" 2>/dev/null || wget -q -O "$TMP_DL/$ASSET" "$(build_url "$ASSET_URL")" || die "下载升级包失败"
check_cancel
write_status verifying "" "" ""
( cd "$TMP_DL" && sha256sum -c "$ASSET.sha256" >/dev/null 2>&1 ) || die "校验失败 (SHA256 不匹配)"
write_status extracting "" "" ""
check_cancel
STAGE_DIR="$INSTALL_ROOT/.update-stage.$$"
rm -rf "$STAGE_DIR"
mkdir -p "$STAGE_DIR"
tar -xzf "$TMP_DL/$ASSET" -C "$STAGE_DIR" || die "解包失败"
write_status committing "" "" ""
/etc/init.d/openbox-panel stop >/dev/null 2>&1 || true
/etc/init.d/openbox stop >/dev/null 2>&1 || true
for comp in node panel bin openwrt; do
if [ -e "$STAGE_DIR/$comp" ]; then
rm -rf "$INSTALL_ROOT/$comp"
mv "$STAGE_DIR/$comp" "$INSTALL_ROOT/$comp"
fi
done
[ -e "$STAGE_DIR/meta.json" ] && mv "$STAGE_DIR/meta.json" "$INSTALL_ROOT/meta.json"
rm -rf "$STAGE_DIR"
cp -f "$INSTALL_ROOT/openwrt/initd/openbox" /etc/init.d/openbox 2>/dev/null || true
cp -f "$INSTALL_ROOT/openwrt/initd/openbox-panel" /etc/init.d/openbox-panel 2>/dev/null || true
chmod +x /etc/init.d/openbox /etc/init.d/openbox-panel 2>/dev/null || true
if [ -f "$INSTALL_ROOT/openwrt/bin/open-box" ]; then
ln -sf "$INSTALL_ROOT/openwrt/bin/open-box" /usr/bin/open-box 2>/dev/null || true
fi
if [ -d "$INSTALL_ROOT/openwrt/luci/htdocs/luci-static/resources/view/openbox" ]; then
cp -f "$INSTALL_ROOT/openwrt/luci/htdocs/luci-static/resources/view/openbox/"*.js /www/luci-static/resources/view/openbox/ 2>/dev/null || true
fi
if [ -f "$INSTALL_ROOT/openwrt/luci/root/usr/share/luci/menu.d/luci-app-openbox.json" ]; then
cp -f "$INSTALL_ROOT/openwrt/luci/root/usr/share/luci/menu.d/luci-app-openbox.json" /usr/share/luci/menu.d/ 2>/dev/null || true
fi
if [ -f "$INSTALL_ROOT/openwrt/luci/root/usr/share/rpcd/acl.d/luci-app-openbox.json" ]; then
cp -f "$INSTALL_ROOT/openwrt/luci/root/usr/share/rpcd/acl.d/luci-app-openbox.json" /usr/share/rpcd/acl.d/ 2>/dev/null || true
fi
rm -rf /tmp/luci-*cache* 2>/dev/null || true
/etc/init.d/rpcd restart >/dev/null 2>&1 || true
/etc/init.d/openbox-panel start >/dev/null 2>&1 || true
/etc/init.d/openbox start >/dev/null 2>&1 || true
write_status done "" "" "升级完成"
info "升级完成!"
@@ -0,0 +1,34 @@
{
"luci-app-openbox": {
"description": "Grant UCI and system execution access for luci-app-openbox",
"read": {
"cgi-io": [ "backup" ],
"file": {
"/opt/open-box/meta.json": [ "read" ],
"/opt/open-box/data/panel-port": [ "read" ],
"/tmp/openbox-update.status": [ "read" ],
"/tmp/openbox-update.log": [ "read" ],
"/tmp/openbox-uninstall.status": [ "read" ],
"/tmp/openbox-uninstall.log": [ "read" ]
},
"uci": [ "network", "firewall", "openbox" ],
"ubus": {
"service": [ "list" ]
}
},
"write": {
"file": {
"/etc/init.d/openbox": [ "exec" ],
"/etc/init.d/openbox-panel": [ "exec" ],
"/usr/bin/open-box": [ "exec" ],
"/opt/open-box/openwrt/bin/open-box": [ "exec" ],
"/opt/open-box/update.sh": [ "exec" ],
"/opt/open-box/uninstall.sh": [ "exec" ],
"/usr/share/openbox/install.sh": [ "exec" ],
"/usr/share/openbox/update.sh": [ "exec" ],
"/usr/share/openbox/uninstall.sh": [ "exec" ]
},
"uci": [ "firewall", "openbox" ]
}
}
}
@@ -0,0 +1,456 @@
'use strict';
'require view';
'require fs';
'require ui';
'require uci';
'require rpc';
'require poll';
var callServiceList = rpc.declare({
object: 'service',
method: 'list',
params: [ 'name' ],
expect: { '': {} }
});
return view.extend({
pollInterval: 3,
load: function() {
return Promise.all([
fs.read('/opt/open-box/meta.json').catch(function() { return null; }),
fs.read('/opt/open-box/data/panel-port').catch(function() { return null; }),
fs.exec_direct('/usr/bin/open-box', [ 'password' ]).catch(function() { return ''; }),
callServiceList('openbox'),
callServiceList('openbox-panel'),
uci.load('network').catch(function() { return null; }),
uci.load('openbox').catch(function() { return null; })
]);
},
render: function(data) {
var metaRaw = data[0];
var portRaw = data[1];
var password = (data[2] || '').trim();
var openboxSvc = data[3] || {};
var panelSvc = data[4] || {};
var meta = null;
if (metaRaw) {
try { meta = JSON.parse(metaRaw); } catch(e) {}
}
var port = (portRaw || '').replace(/[^0-9]/g, '');
if (!port) {
port = (uci.get('openbox', 'main', 'port') || '3036');
}
var lanIp = uci.get('network', 'lan', 'ipaddr');
if (Array.isArray(lanIp)) lanIp = lanIp[0];
if (lanIp && lanIp.indexOf('/') !== -1) lanIp = lanIp.split('/')[0];
if (!lanIp) lanIp = window.location.hostname || '192.168.1.1';
var panelRunning = (panelSvc['openbox-panel'] && panelSvc['openbox-panel']['instances'] && Object.keys(panelSvc['openbox-panel']['instances']).length > 0);
var coreRunning = (openboxSvc['openbox'] && openboxSvc['openbox']['instances'] && Object.keys(openboxSvc['openbox']['instances']).length > 0);
var panelUrl = 'http://' + lanIp + ':' + port;
var viewRoot = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, [ _('Open-Box') + ' ' + (meta && meta.version ? 'v' + meta.version : '') ]),
E('div', { 'class': 'cbi-map-descr' }, [
_('Open-Box 是路由器上的一体化透明代理方案,内置 sing-box 内核、Node.js 运行时与 Web 管理面板。本页面为 LuCI 兜底管理页,可用于启停服务、修改端口、找回密码或执行维护操作。')
])
]);
// 1. 面板直达与密码卡片
var accessCard = E('div', { 'class': 'cbi-section' }, [
E('h3', {}, [ _('Open-Box 管理面板') ]),
E('div', { 'class': 'cbi-section-descr' }, [
_('日常代理节点选择、分流规则配置、订阅管理及流量统计请在独立 Web 面板中进行。')
]),
E('div', { 'class': 'table' }, [
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left', 'style': 'width:30%' }, [ E('strong', {}, _('面板访问地址')) ]),
E('div', { 'class': 'td' }, [
E('a', { 'href': panelUrl, 'target': '_blank', 'class': 'cbi-button cbi-button-action' }, [
_('打开管理面板') + ' (' + panelUrl + ')'
])
])
]),
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left' }, [ E('strong', {}, _('面板当前密码')) ]),
E('div', { 'class': 'td' }, [
E('span', { 'style': 'font-family: monospace; font-size: 1.1em; color: #2c3e50; font-weight: bold;' }, [
password || _('尚未设置(首次打开面板设置管理密码)')
]),
password ? E('button', {
'class': 'cbi-button cbi-button-neutral',
'style': 'margin-left: 1em;',
'click': function() {
navigator.clipboard.writeText(password).then(function() {
ui.addNotification(null, E('p', {}, _('密码已复制到剪贴板。')), 'info');
});
}
}, [ _('复制密码') ]) : E('span')
])
]),
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left' }, [ E('strong', {}, _('面板监听端口')) ]),
E('div', { 'class': 'td' }, [
E('span', { 'style': 'font-family: monospace; margin-right: 1em;' }, [ port ]),
E('button', {
'class': 'cbi-button cbi-button-apply',
'click': ui.createHandlerFn(this, 'handlePortChange', port)
}, [ _('修改端口') ])
])
])
])
]);
viewRoot.appendChild(accessCard);
// 2. 服务运行状态卡片
var statusCard = E('div', { 'class': 'cbi-section' }, [
E('h3', {}, [ _('服务运行状态') ]),
E('div', { 'class': 'table' }, [
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left', 'style': 'width:30%' }, [ E('strong', {}, _('面板服务 (openbox-panel)')) ]),
E('div', { 'class': 'td' }, [
E('span', { 'class': 'badge ' + (panelRunning ? 'badge-success' : 'badge-danger') }, [
panelRunning ? _('运行中 (Running)') : _('已停止 (Stopped)')
]),
E('div', { 'class': 'btn-group', 'style': 'display: inline-block; margin-left: 1.5em;' }, [
E('button', {
'class': 'cbi-button ' + (panelRunning ? 'cbi-button-reset' : 'cbi-button-action'),
'click': ui.createHandlerFn(this, 'handleServiceAction', 'openbox-panel', panelRunning ? 'stop' : 'start')
}, [ panelRunning ? _('停止面板') : _('启动面板') ]),
E('button', {
'class': 'cbi-button cbi-button-neutral',
'style': 'margin-left: 0.5em;',
'click': ui.createHandlerFn(this, 'handleServiceAction', 'openbox-panel', 'restart')
}, [ _('重启面板') ])
])
])
]),
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left' }, [ E('strong', {}, _('内核代理服务 (openbox)')) ]),
E('div', { 'class': 'td' }, [
E('span', { 'class': 'badge ' + (coreRunning ? 'badge-success' : 'badge-danger') }, [
coreRunning ? _('运行中 (Running)') : _('已停止 (Stopped)')
]),
E('div', { 'class': 'btn-group', 'style': 'display: inline-block; margin-left: 1.5em;' }, [
E('button', {
'class': 'cbi-button ' + (coreRunning ? 'cbi-button-reset' : 'cbi-button-action'),
'click': ui.createHandlerFn(this, 'handleServiceAction', 'openbox', coreRunning ? 'stop' : 'start')
}, [ coreRunning ? _('紧急停止内核 (恢复直连)') : _('启动内核') ]),
E('button', {
'class': 'cbi-button cbi-button-neutral',
'style': 'margin-left: 0.5em;',
'click': ui.createHandlerFn(this, 'handleServiceAction', 'openbox', 'restart')
}, [ _('按配置重载/重启') ])
])
])
])
])
]);
viewRoot.appendChild(statusCard);
// 3. 核心组件与更新维护卡片
var hasCore = (meta !== null);
var maintenanceCard = E('div', { 'class': 'cbi-section' }, [
E('h3', {}, [ _('组件与更新维护') ]),
E('div', { 'class': 'cbi-section-descr' }, [
hasCore ? _('当前已安装 Open-Box 核心组件(安装目录:/opt/open-box)。') :
_('检测到核心组件尚未安装或 /opt/open-box 不完整。您可以直接使用下方一键下载安装功能完成初始化。')
]),
E('div', { 'class': 'table' }, [
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left', 'style': 'width:30%' }, [ E('strong', {}, _('核心组件状态')) ]),
E('div', { 'class': 'td' }, [
hasCore ? E('span', { 'class': 'badge badge-success' }, [
_('已就绪') + ' (' + (meta.arch || 'unknown') + ', ' + (meta.version || 'v' + meta.version) + ')'
]) : E('span', { 'class': 'badge badge-danger' }, [ _('尚未就绪') ]),
!hasCore ? E('button', {
'class': 'cbi-button cbi-button-action',
'style': 'margin-left: 1em;',
'click': ui.createHandlerFn(this, 'handleInstallCore')
}, [ _('一键安装核心组件') ]) : E('span')
])
]),
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left' }, [ E('strong', {}, _('更新加速镜像')) ]),
E('div', { 'class': 'td' }, [
E('select', { 'id': 'openbox_mirror_select', 'class': 'cbi-input-select' }, [
E('option', { 'value': 'direct' }, _('GitHub 直连 (官方)')),
E('option', { 'value': 'https://ghfast.top', 'selected': 'selected' }, 'ghfast.top (' + _('内置推荐') + ')'),
E('option', { 'value': 'https://gh-proxy.com' }, 'gh-proxy.com'),
E('option', { 'value': 'https://gh.llkk.cc' }, 'gh.llkk.cc')
]),
E('button', {
'class': 'cbi-button cbi-button-neutral',
'style': 'margin-left: 1em;',
'click': ui.createHandlerFn(this, 'handleTestLatency')
}, [ _('测速/检测可用性') ]),
E('span', { 'id': 'openbox_probe_result', 'style': 'margin-left: 1em; font-weight: bold;' }, '')
])
]),
E('div', { 'class': 'tr' }, [
E('div', { 'class': 'td left' }, [ E('strong', {}, _('系统升级与维护')) ]),
E('div', { 'class': 'td' }, [
E('button', {
'class': 'cbi-button cbi-button-apply',
'click': ui.createHandlerFn(this, 'handleUpdate', false)
}, [ _('检查并升级到最新版') ]),
E('button', {
'class': 'cbi-button cbi-button-neutral',
'style': 'margin-left: 0.5em;',
'click': ui.createHandlerFn(this, 'handleUpdate', true)
}, [ _('回退到上个版本 (Rollback)') ]),
E('button', {
'class': 'cbi-button cbi-button-reset',
'style': 'margin-left: 1.5em;',
'click': ui.createHandlerFn(this, 'handleUninstall')
}, [ _('卸载 Open-Box') ])
])
])
])
]);
viewRoot.appendChild(maintenanceCard);
return viewRoot;
},
handleServiceAction: function(service, action) {
ui.showModal(_('正在处理'), [
E('p', { 'class': 'spinning' }, _('正在执行操作,请稍候...'))
]);
return fs.exec('/etc/init.d/' + service, [ action ]).then(function() {
window.location.reload();
}).catch(function(e) {
ui.addNotification(null, E('p', {}, _('操作失败: ') + e.message), 'error');
ui.hideModal();
});
},
handlePortChange: function(currentPort) {
var input = E('input', {
'type': 'number',
'class': 'cbi-input-text',
'min': 1024,
'max': 65535,
'value': currentPort
});
ui.showModal(_('修改面板端口'), [
E('p', {}, _('请输入 1024 至 65535 之间的可用端口号(修改后将自动重启面板并放行防火墙):')),
input,
E('div', { 'class': 'right', 'style': 'margin-top: 1em;' }, [
E('button', {
'class': 'cbi-button cbi-button-neutral',
'click': ui.hideModal
}, [ _('取消') ]),
E('button', {
'class': 'cbi-button cbi-button-apply',
'style': 'margin-left: 0.5em;',
'click': function() {
var newPort = input.value.trim();
if (!newPort || isNaN(newPort) || newPort < 1024 || newPort > 65535) {
ui.addNotification(null, E('p', {}, _('请输入合法的端口号 (1024-65535)')), 'error');
return;
}
ui.showModal(_('保存中'), [ E('p', { 'class': 'spinning' }, _('正在应用新端口并重启面板...')) ]);
fs.exec_direct('/usr/bin/open-box', [ 'port', newPort ]).then(function() {
window.location.reload();
}).catch(function(e) {
ui.addNotification(null, E('p', {}, _('修改失败: ') + e.message), 'error');
ui.hideModal();
});
}
}, [ _('保存并重启') ])
])
]);
},
handleTestLatency: function() {
var select = document.getElementById('openbox_mirror_select');
var mirror = select ? select.value : 'direct';
var resultSpan = document.getElementById('openbox_probe_result');
if (resultSpan) {
resultSpan.textContent = _('正在检测...');
resultSpan.style.color = '#3498db';
}
var args = [ '--probe', mirror ];
var execPath = '/opt/open-box/update.sh';
fs.stat(execPath).catch(function() {
return fs.stat('/usr/share/openbox/update.sh').then(function() {
execPath = '/usr/share/openbox/update.sh';
});
}).then(function() {
return fs.exec_direct(execPath, args);
}).then(function(out) {
if (resultSpan) {
var text = (out || '').trim();
if (text.startsWith('ok')) {
resultSpan.textContent = '✓ ' + text.replace('ok', _('可用') + ': ');
resultSpan.style.color = '#27ae60';
} else {
resultSpan.textContent = '✗ ' + text.replace('fail', _('失败') + ': ');
resultSpan.style.color = '#e74c3c';
}
}
}).catch(function(err) {
if (resultSpan) {
resultSpan.textContent = '✗ ' + (err.message || _('检测出错'));
resultSpan.style.color = '#e74c3c';
}
});
},
handleInstallCore: function() {
var select = document.getElementById('openbox_mirror_select');
var mirror = select ? select.value : 'https://ghfast.top';
var args = [];
if (mirror !== 'direct') {
args = [ '--mirror', mirror ];
}
ui.showModal(_('安装核心组件'), [
E('p', { 'class': 'spinning' }, _('正在下载并部署 Open-Box 完整核心组件(约需 1-3 分钟,视网络速度而定)...'))
]);
var script = '/usr/share/openbox/install.sh';
fs.exec(script, args).then(function() {
ui.addNotification(null, E('p', {}, _('核心组件安装成功!')), 'info');
window.location.reload();
}).catch(function(e) {
ui.addNotification(null, E('p', {}, _('安装失败: ') + e.message), 'error');
ui.hideModal();
});
},
handleUpdate: function(isRollback) {
var select = document.getElementById('openbox_mirror_select');
var mirror = select ? select.value : 'direct';
var args = [ '--detach' ];
if (isRollback) args.push('--rollback');
if (mirror === 'direct') {
args.push('--direct');
} else {
args.push('--mirror');
args.push(mirror);
}
var execPath = '/opt/open-box/update.sh';
fs.stat(execPath).catch(function() {
execPath = '/usr/share/openbox/update.sh';
}).then(function() {
return fs.exec_direct(execPath, args);
}).then(function() {
var progressBar = E('div', { 'class': 'cbi-progressbar', 'title': '0%' }, [ E('div', { 'style': 'width: 0%' }) ]);
var statusText = E('p', {}, _('任务已派发,正在准备下载...'));
var logPre = E('pre', { 'style': 'max-height: 200px; overflow-y: scroll; font-size: 11px; background: #f8f9fa; padding: 8px;' }, '');
ui.showModal(isRollback ? _('正在执行版本回退') : _('正在升级 Open-Box'), [
statusText,
progressBar,
E('p', { 'style': 'margin-top: 1em;' }, [ E('strong', {}, _('实时日志:')) ]),
logPre,
E('div', { 'class': 'right', 'style': 'margin-top: 1em;' }, [
E('button', {
'class': 'cbi-button cbi-button-reset',
'click': function() {
fs.exec_direct(execPath, [ '--cancel' ]);
ui.hideModal();
}
}, [ _('取消更新') ])
])
]);
var timer = window.setInterval(function() {
Promise.all([
fs.read('/tmp/openbox-update.status').catch(function() { return ''; }),
fs.read('/tmp/openbox-update.log').catch(function() { return ''; })
]).then(function(res) {
var st = res[0] || '';
var lg = res[1] || '';
logPre.textContent = lg;
logPre.scrollTop = logPre.scrollHeight;
var stage = (st.match(/stage=([^\n]+)/) || [])[1];
var bytes = parseInt((st.match(/bytes=([0-9]+)/) || [])[1] || 0);
var total = parseInt((st.match(/total=([0-9]+)/) || [])[1] || 0);
var msg = (st.match(/message=([^\n]*)/) || [])[1] || '';
if (stage === 'downloading') {
if (total > 0) {
var pct = Math.floor((bytes / total) * 100);
progressBar.firstElementChild.style.width = pct + '%';
progressBar.setAttribute('title', pct + '%');
statusText.textContent = _('正在下载发布包: ') + (bytes / 1048576).toFixed(1) + 'MB / ' + (total / 1048576).toFixed(1) + 'MB (' + pct + '%)';
} else {
statusText.textContent = _('正在下载发布包: ') + (bytes / 1048576).toFixed(1) + 'MB';
}
} else if (stage === 'extracting') {
progressBar.firstElementChild.style.width = '80%';
statusText.textContent = _('正在解包并校验文件完整性...');
} else if (stage === 'committing') {
progressBar.firstElementChild.style.width = '90%';
statusText.textContent = _('正在替换旧组件并重启服务...');
} else if (stage === 'done') {
window.clearInterval(timer);
progressBar.firstElementChild.style.width = '100%';
statusText.textContent = _('操作完成!') + (msg ? ' (' + msg + ')' : '');
window.setTimeout(function() { window.location.reload(); }, 2000);
} else if (stage === 'failed' || stage === 'cancelled') {
window.clearInterval(timer);
statusText.textContent = (stage === 'failed' ? _('操作失败: ') : _('操作已取消: ')) + msg;
}
});
}, 1000);
}).catch(function(e) {
ui.addNotification(null, E('p', {}, _('启动升级失败: ') + e.message), 'error');
});
},
handleUninstall: function() {
var checkPurge = E('input', { 'type': 'checkbox' });
ui.showModal(_('卸载 Open-Box'), [
E('p', {}, _('确定要从系统中卸载 Open-Box 吗?')),
E('label', { 'class': 'cbi-checkbox', 'style': 'display: block; margin: 1em 0;' }, [
checkPurge,
' ' + _('同时删除所有用户数据(/opt/open-box/data 下的节点订阅、规则集与数据库)')
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'cbi-button cbi-button-neutral',
'click': ui.hideModal
}, [ _('取消') ]),
E('button', {
'class': 'cbi-button cbi-button-reset',
'style': 'margin-left: 0.5em;',
'click': function() {
var purge = checkPurge.checked;
var args = [ '--detach' ];
if (purge) args.push('--purge');
var execPath = '/opt/open-box/uninstall.sh';
fs.stat(execPath).catch(function() {
execPath = '/usr/share/openbox/uninstall.sh';
}).then(function() {
return fs.exec_direct(execPath, args);
}).then(function() {
ui.showModal(_('卸载中'), [
E('p', { 'class': 'spinning' }, _('正在停止服务并清理残留文件...'))
]);
window.setTimeout(function() {
window.location.href = window.location.pathname.replace(/\/admin\/services\/openbox.*/, '/admin/status/overview');
}, 4000);
}).catch(function(err) {
ui.addNotification(null, E('p', {}, _('卸载失败: ') + err.message), 'error');
ui.hideModal();
});
}
}, [ _('确认卸载') ])
])
]);
}
});
-1
View File
@@ -1 +0,0 @@
/Screenshots
-16
View File
@@ -1,16 +0,0 @@
#
# Copyright (C) 2008-2019 GaryPang
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Edge Theme
LUCI_DEPENDS:=+curl
PKG_LICENSE:=Apache-2.0
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
-36
View File
@@ -1,36 +0,0 @@
# luci-theme-edge
[1]: https://img.shields.io/badge/license-MIT-brightgreen.svg
[2]: /LICENSE
[3]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg
[4]: https://github.com/https://github.com/kiddin9/luci-theme-edge/pulls
[5]: https://img.shields.io/badge/Issues-welcome-brightgreen.svg
[6]: https://github.com/https://github.com/kiddin9/luci-theme-edge/issues/new
[7]: https://img.shields.io/badge/release-v2.1-blue.svg?
[10]: https://img.shields.io/badge/Contact-telegram-blue
[11]: https://t.me/openwrt_nginx
[![license][1]][2]
[![PRs Welcome][3]][4]
[![Issue Welcome][5]][6]
[![Contact Me][10]][11]
<a href="https://t.me/opwrts" target="_blank">Telegram</a>
A new Luci theme for LEDE/OpenWRT
Edge is a clean HTML5 theme for LuCI. It is based on luci-theme-argon Template
Master branch adapt to official mainline snapshot.
Luci 18.06: https://github.com/kiddin9/luci-theme-edge/tree/18.06
## Thanks to
luci-theme-argon: https://github.com/jerrykuku/luci-theme-argon
![](/Screenshots/1.png)
![](/Screenshots/2.png)
![](/Screenshots/3.png)
![](/Screenshots/4.png)
![](/Screenshots/5.png)
<br/>
<br />
<br />
<img src="/Screenshots/6.png" width="550" />
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

File diff suppressed because it is too large. Load diff
@@ -1,46 +0,0 @@
:root {
--main-color: #09c;
--header-bg: #09c;
--header-color: #fff;
--bar-bg: #5e72e4;
--menu-bg-color: #fff;
--menu-color: #5f6368;
--menu-color-hover: #202124;
--main-menu-color: #202124;
--submenu-bg-hover: #d4d4d4;
--submenu-bg-hover-active: #09c;
--blue: #5e72e4;
--indigo: #5603ad;
--purple: #8965e0;
--pink: #f3a4b5;
--red: #f5365c;
--orange: #fb6340;
--yellow: #ffd600;
--green: #2dce89;
--teal: #11cdef;
--cyan: #2bffc6;
--white: #fff;
--gray: #8898aa;
--gray-dark: #32325d;
--light: #96999c;
--lighter: #e9ecef;
--primary: #5e72e4;
--secondary: #f7fafc;
--success: #2dce89;
--info: #11cdef;
--warning: #fb6340;
--danger: #f5365c;
--dark: #212529;
--default: #172b4d;
--white: #fff;
--neutral: #fff;
--darker: black;
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--font-body: "Microsoft Yahei", "WenQuanYi Micro Hei", "sans-serif", "Helvetica Neue", "Helvetica", "Hiragino Sans GB";
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

@@ -1,29 +0,0 @@
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In -->
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" viewBox="0 0 465.3 465.3" enable-background="new 0 0 465.3 465.3"
xml:space="preserve">
<defs>
</defs>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="120.4726" y1="232.6669" x2="344.8612" y2="232.6669">
<stop offset="0" style="stop-color:#F57E20"/>
<stop offset="0.5018" style="stop-color:#FFFFFF;stop-opacity:0.6"/>
<stop offset="1" style="stop-color:#F57E20"/>
</linearGradient>
<polygon fill="url(#SVGID_1_)" points="120.5,344.9 228,228 344.9,120.5 237.4,237.4 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="120.4726" y1="232.6669" x2="344.8612" y2="232.6669">
<stop offset="0" style="stop-color:#F57E20"/>
<stop offset="0.5018" style="stop-color:#FFFFFF;stop-opacity:0.6"/>
<stop offset="1" style="stop-color:#F57E20"/>
</linearGradient>
<polygon fill="url(#SVGID_2_)" points="120.5,120.5 237.4,228 344.9,344.9 228,237.4 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="0" y1="232.6669" x2="465.3338" y2="232.6669">
<stop offset="0" style="stop-color:#A22890"/>
<stop offset="0.5018" style="stop-color:#FFFFFF;stop-opacity:0.6"/>
<stop offset="1" style="stop-color:#A22890"/>
</linearGradient>
<polygon fill="url(#SVGID_3_)" points="0,232.7 232.7,226 465.3,232.7 232.7,239.3 "/>
<polygon fill="none" points="232.7,465.3 226,232.7 232.7,0 239.3,232.7 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#202124" d="m8,10.033663l-6.898535,-6.013274l-1.060688,0.972974l7.959223,6.986249l7.959223,-6.986249l-1.060688,-0.972974l-6.898535,6.013274z"/></svg>

Before

Width:  |  Height:  |  Size: 221 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#888" d="M8,0c-4.355,0-7.898,3.481-7.998,7.812,0.092-3.779,2.966-6.812,6.498-6.812,3.59,0,6.5,3.134,6.5,7,0,0.828,0.672,1.5,1.5,1.5s1.5-0.672,1.5-1.5c0-4.418-3.582-8-8-8zM8,16c4.355,0,7.898-3.481,7.998-7.812-0.092,3.779-2.966,6.812-6.498,6.812-3.59,0-6.5-3.134-6.5-7,0-0.828-0.672-1.5-1.5-1.5s-1.5,0.672-1.5,1.5c0,4.418,3.582,8,8,8z"/></svg>

Before

Width:  |  Height:  |  Size: 413 B

@@ -1,2 +0,0 @@
!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}S.fn=S.prototype={jquery:f,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(n){return this.pushStack(S.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},S.extend=S.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(S.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||S.isPlainObject(n)?n:{},i=!1,a[t]=S.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},S.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=v.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){b(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(p(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(p(Object(e))?S.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(p(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:y}),"function"==typeof Symbol&&(S.fn[Symbol.iterator]=t[Symbol.iterator]),S.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var d=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S="sizzle"+1*new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),D=function(e,t){return e===t&&(l=!0),0},j={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",I="(?:\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",W="\\["+M+"*("+I+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+I+"))|)"+M+"*\\]",F=":("+I+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+W+")*)|.*)\\)|)",B=new RegExp(M+"+","g"),$=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=new RegExp("^"+M+"*,"+M+"*"),z=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-lLine truncated
@@ -1,217 +0,0 @@
/**
* Edge is a clean HTML5 theme for LuCI. It is based on luci-theme-Argon
*
* luci-theme-edge
* Copyright 2020 Kiddin'
*
* Have a bug? Please create an issue here on GitHub!
* https://github.com/kiddin9/luci-theme-edge/issues
*
* luci-theme-material:
* Copyright 2015 Lutty Yang <lutty@wcan.in>
* 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<icomoon.io>
*
* Licensed to the public under the Apache License 2.0
*/
document.addEventListener('luci-loaded', function(ev) {
(function ($) {
$(".main > .loading").fadeOut();
/**
* trim text, Remove spaces, wrap
* @param text
* @returns {string}
*/
function trimText(text) {
return text.replace(/[ \t\n\r]+/g, " ");
}
var lastNode = undefined;
var mainNodeName = undefined;
var nodeUrl = "";
(function(node){
var luciLocation;
if (node[0] == "admin"){
luciLocation = [node[1], node[2]];
}else{
luciLocation = node;
}
for(var i in luciLocation){
nodeUrl += luciLocation[i];
if (i != luciLocation.length - 1){
nodeUrl += "/";
}
}
})(luciLocation);
/**
* get the current node by Burl (primary)
* @returns {boolean} success?
*/
function getCurrentNodeByUrl() {
var ret = false;
if (!$('body').hasClass('logged-in')) {
luciLocation = ["Main", "Login"];
return true;
}
return ret;
}
/**
* get current node and open it
*/
if (getCurrentNodeByUrl()) {
mainNodeName = "node-" + luciLocation[0] + "-" + luciLocation[1];
mainNodeName = mainNodeName.replace(/[ \t\n\r\/]+/g, "_").toLowerCase();
$("body").addClass(mainNodeName);
}
/**
* Sidebar expand
*/
var showSide = false;
$(".showSide").click(function () {
if (showSide) {
$(".darkMask").stop(true).fadeOut("fast");
$(".main-left").stop(true).animate({
width: "0"
}, "200");
$(".main-right").css("overflow-y", "visible");
showSide = false;
} else {
$(".darkMask").stop(true).fadeIn("fast");
$(".main-left").stop(true).animate({
width: "13rem"
}, "200");
$(".main-right").css("overflow-y", "hidden");
showSide = true;
}
});
$(".darkMask").click(function () {
if (showSide) {
showSide = false;
$(".darkMask").stop(true).fadeOut("fast");
$(".main-left").stop(true).animate({
width: "0"
}, "fast");
$(".main-right").css("overflow-y", "visible");
}
});
$(window).resize(function () {
if ($(window).width() > 921) {
$(".main-left").css("width", "");
$(".darkMask").stop(true);
$(".darkMask").css("display", "none");
showSide = false;
}
});
/**
* fix legend position
*/
$("legend").each(function () {
var that = $(this);
that.after("<span class='panel-title'>" + that.text() + "</span>");
});
$(".cbi-section-table-titles, .cbi-section-table-descr, .cbi-section-descr").each(function () {
var that = $(this);
if (that.text().trim() == ""){
that.css("display", "none");
}
});
$(".main-right").focus();
$(".main-right").blur();
$("input").attr("size", "0");
$(".cbi-button-up").val("__");
$(".cbi-button-down").val("__");
$(".slide > a").removeAttr("href");
if (mainNodeName != undefined) {
console.log(mainNodeName);
switch (mainNodeName) {
case "node-status-system_log":
case "node-status-kernel_log":
$("#syslog").focus(function () {
$("#syslog").blur();
$(".main-right").focus();
$(".main-right").blur();
});
break;
case "node-status-firewall":
var button = $(".node-status-firewall > .main fieldset li > a");
button.addClass("cbi-button cbi-button-reset a-to-btn");
break;
case "node-system-reboot":
var button = $(".node-system-reboot > .main > .main-right p > a");
button.addClass("cbi-button cbi-input-reset a-to-btn");
break;
}
}
var getaudio = $('#player')[0];
/* Get the audio from the player (using the player's ID), the [0] is necessary */
var audiostatus = 'off';
/* Global variable for the audio's status (off or on). It's a bit crude but it works for determining the status. */
$(document).on('click touchend', '.speaker', function() {
/* Touchend is necessary for mobile devices, click alone won't work */
if (!$('.speaker').hasClass("speakerplay")) {
if (audiostatus == 'off') {
$('.speaker').addClass('speakerplay');
getaudio.load();
getaudio.play();
audiostatus = 'on';
return false;
} else if (audiostatus == 'on') {
$('.speaker').addClass('speakerplay');
getaudio.play()
}
} else if ($('.speaker').hasClass("speakerplay")) {
getaudio.pause();
$('.speaker').removeClass('speakerplay');
audiostatus = 'on';
}
});
$('#player').on('ended', function() {
$('.speaker').removeClass('speakerplay');
/*When the audio has finished playing, remove the class speakerplay*/
audiostatus = 'off';
/*Set the status back to off*/
});
setTimeout(function(){
$("input[type='checkbox']").filter(function () {
return (!$(this).next("label").length)
}).show();
}, 0);
var options = { attributes: true};
function callback() {
$("input[type='checkbox']").filter(function () {
return (!$(this).next("label").length)
}).show();
}
var mutationObserver = new MutationObserver(callback);
mutationObserver.observe($("body")[0], options);
$(".cbi-value").has("textarea").css("background","none");
if(document.body.scrollHeight > window.innerHeight){
$(".cbi-page-actions.control-group").addClass("fixed")
}
})(jQuery);
});
@@ -1,76 +0,0 @@
#CrossFade {
background: #fff;
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
}
#CrossFade img {
position: absolute;
min-width: 100%;
min-height: 100%;
width:100%;
background: #fff;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
opacity: 0;
-webkit-transform: scale(1.4) rotate(12deg);
transform: scale(1.4) rotate(12deg);
-webkit-animation: CrossFade 12s infinite;
animation: CrossFade 12s infinite;
}
#CrossFade img:nth-child(3) {
-webkit-animation-delay: 8s;
animation-delay: 8s;
}
#CrossFade img:nth-child(2) {
-webkit-animation-delay: 4s;
animation-delay: 4s;
}
#CrossFade img:nth-child(1) {
-webkit-animation-delay: 0s;
animation-delay: 0s;
}
@-webkit-keyframes CrossFade {
33.3% {
opacity: 1;
-webkit-transform: scale(1) rotate(0);
transform: scale(1) rotate(0);
}
45% {
opacity: 0;
}
}
@keyframes CrossFade {
33.3% {
opacity: 1;
-webkit-transform: scale(1) rotate(0);
transform: scale(1) rotate(0);
}
45% {
opacity: 0;
}
}
.speaker {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARAAAACACAYAAAG0HAZ0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MEFERTNEQzA3QTMxMTFFMzgzNTFFRDRCMzk1MDA0NUIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MEFERTNEQzE3QTMxMTFFMzgzNTFFRDRCMzk1MDA0NUIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowQURFM0RCRTdBMzExMUUzODM1MUVENEIzOTUwMDQ1QiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowQURFM0RCRjdBMzExMUUzODM1MUVENEIzOTUwMDQ1QiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvEtsecAABjVSURBVHjaYvz//z/DYABMDIMEEO2QjIwMioMOnxnEOOQ/FFMSmrjMIGzYnz9/3hPQ/Dc5ORmv4USYgeqQz58/H/758+dvEIb5gIWFRQCHHmYQcfny5Z9z586FC5JoBgZgARG8vLw2JATzHxChq6vLCLKMkZERmPH+M5JoBlVyDSNK5FMp+5OdfYEhsXCwlCP5A+2Q/9Ao+QANGeo5BGjYH1lZWQYQJi5WGO8hOYiRDDOw5xqgYazolklKSv54/vw5O9Yg+f9fGZpj4ImVVDOwOgQbABrAgRwV2EKGUI4hwgyS0ggjepYlM8vjNYNxsDQDAAKIcbQ9QqZDqBFs/ylxCDsVHEGUGSxk+uA/tgqQkpAEhwisHbFnz547RDoC7ICHDx/C1ZFoBtYQ+c/ODin8Xr9+rUxkGe/w9u3b3/Ly8rDSlGQzSIka3EHy//9+aKj8H7HZd3A6BNoGoW6RDCriZWRk/oOwsLAwqCEMEsMGGJDkwHRwcDBcnEQzMDC+uuY/nkYzueUIIzlRg6/qBrXSllG1CYEvuGDYxMTkHzHqKDFj0DQDAAJo0DhktDwb5gHSDhuKoca40kC6g4UCy/9TqI+RSoHwf6BSSMp/SIHzn4EOLXo6ugNrgPzHhpcvX45s4WwyhkHEYWb9+vXrP54KnIHG7hg0hepLIOYBMdjY2BiOHj2KESinTp0aVNUcPWqZrzCGtbU1qCmFMhBrZmY24qtdz5HeDoFPGKSlpYGoGiCGzxFMmjRpxAVIEogICAhgmDVrFiO0hngPk8zPz787qEKE2M4IELj+JwKkp6fj609WoHQ6wdaT1imikjtwYiYSAm43UreRceFCsqZoOkBEdXU1qHBVhE0ikBiB1HAH5SmEQKzVUxozg8Ud4N4uLRs6xEYK0tgT1c0d7f5TAAACaDRARsdDRgNkNEAGS4CIMEDWBrSQVIhRv4Yjyx3UGDHDNdjzZCBrcErdQVEK+ffv3y9SR62am5t1qR0K5LiD2i3Vclx9CKCRGXj0YcydUdhaJtcdODELhcmSXH3/GSgfZP4/0IUqpYO6KAFw8+ZNSgKWZq1JWIBw//nz5zdsgQXaok+Gly9frqQkJs3NzR+jB4q6ujqohkFPobR0B9EBAlqZ9oUFCNjZ2VEw0ME7qJGsT548KQMTOAsEMHZXV9dvJLW0dAfxAfLmzZu3tDK8t7f3BnrgGBsbm8AESktL4ZK0dAdJAQKssmiWH4uLizXRGmHiWJRlQ6vOQdHLpHnTHVh4NiNxX4BbTECAJDZlRPVlgIVnHYydm5sLpmVkZMpGO3dAoKWlBa8wRgMECPbs2QNrahuN2AD5+vXrVRh77dq1sKZ6x4gNEG5ubi0kLnjjDTMQIIfZoAqQv3//0rK6+4+cOrDsnQABDRBBY3cQHyCSkpK8tDL84MGDcHZISAhGIMnJycHHLWjpDnIGiBiBjvuPZQSeE9qgIitr2dvbMx45cuS/ra0tA7ZZusePH2N0AGnhDpqMh/T19REzpUrseMh/AwOD/+SMh1DJHbhn/8jQRJZDHj58+A1N3XsKB4hoEiBk7YwEglRSNQGzAteFCxdgBoAsF6QwcZPlDppUu0DPzAE5SE1N7S8p+oDZhBG2x5VK2Z0sd9CsHXLr1i1Qocy4fv16UjxB9YKRHHdQXKgSm69Jzbs0cAMDxYPMVJwsAhmkSa551HYHWRpHZ/8HsLc7FABAAPauJrSJIArvtptKck5IPGxpIQiNOUTaYCGxQsGLGDFgdWmwkIsI6kHw5r1H6cWTUOJ/LhVKQE8GiRFTc9BLekkPTQMSIaGFQC1paHyz7sbpupu2mN2dzc7AstnJ7Px8882beTNvZilDqKMthjpKEOpsSpCrqVTqm+79rMlGuqThQTRB9vf3vzJ/zR1WC4UCUo/v2LUFm40HCQSZxQDoOByOsM2lOlF4mEKQdrv9EQPhg87JielAq+NJZYTBeBBJkPN4q+A47qIBaR6yygN1fqter5Oi05uBB1kEgQrJYCAUSCis2+1mJGliuLUSiXgYTpDd3d1VTKRfMbIg+Xz+rVI5yWQyQY3Kugt9+57eeTITDz0I8oDROKSC0Ti4Agr9Do+s2WzuGZz/YTk/0Wg0LlVEQv4zFoshmyVWQzs4BWF75deKeOhDkEqlgpaeH1tJC0qn07cYyS5LIR1ehsPhYx2bBmERSdJKfyvioStBnE6n32oZFwThBar4g4ODf/4rFouMz+frKMT9ZbV4ksnkTaWfFfEY5HmQ/yvA0JCqdKjVamiGdIuVpkldLtd7tXDLy8votkWpMNgiUXW3IIxLeNyIsVqtPtd4n6dUGGyCqBqVzc3NISnysMsCnn9Fq9zmgyrcbW+Lh1/hW09ctMrtSRDV4wKXlpbQbUV+rtfr92mV25Mga0qPVqvF7OzsIFW2SxC32z2rDNdoNCgLBpwgqusr0scYbmOEeaMWLhQKodsTSoUeBMlms6+tlnHoLta0yCFptisgPZ5KXtMjIyOCMlypVGKkDeX3cH8r4qErQQRBQC2N3dzcPPaLTqfT1IxDd4FWRNlcLtf1K5fLMjmuATnkDY6X4PqiFkcwKC7VjCr9rYiHIfMHY2NjLNYKeeboQ2x+4Q8bGxvPvF7vDSMLMDMzI3/CDam6Q/i+rHa7/YPjuNM9pMx1CF/VMjm0Ih66EuRQxw7AnTSySCSCFqvkCvtULBajU1NTxgxEOh18n3wErjyQQ61rYjweD/p5Dt75foL4LYUH8YNUAPQCgMH+wYb1xeNxI8v1GTvjqevGx8dFciApcxJyDAAe/ZcgfQbnJ4OtqAJAJRgvBPx+/dbEJicnxfTW19c7aJ90IiFaATyCvCyarnaZgIel1FwA6CyAIbemMwsLC7J/39MKBALs/Pw8K0mNRRJbqZF4EClBjgCnjPXT4gpsrVbjYXBnS7WSNDw4AgEaxUTv9MTEBFJRh+06D2E2HnTzNnXkjUGoowShbkDcbwHYu76QyKowfse7YzQ2mzaDmTUKhmxCFu0IkzsIWlIESvQ0EAVuT2Ia0hJELxUGvQSxKIwY5EPsgz2FOS6LD7VS40r4kD64Ni3iw66kI665Ss7fvjN/5O7suXf+3nPOvX4/uBzm3HPO3Pu7v3O+79xzzzloYhDYgiBQIAgUCAIFgkCBIFAgCBQIG8RisbDFYvHgY+LHh6gCuSqlF1ZxSCqfC1YSBljEjikfSog2WPcmHLk7s1nOcKPBnQ9RBPIEHP+ecTEIyQd3EwN29TcIDlEcYvLBUyBfZOyqV88/2d/fN8qkKCZ8GEEgXVJ6+sDnDP7rp7q6uqHZ2VmRRyRZ8lE8dNitRfWA5vNBskikL7Gk//tZWc7o6GiM8a41ovFR0sFkuD8ej9+QZfmNUrugJWxSReajvJUb6XQ6l8Lh8CWV/2BWKTnwUforAJ2J+SjThy/rHUWRhJC5AyGN8mqhvANOAuHBh5A+yMWMXb0qscffPp/ve7WT4I/c53BNPPkQyweJRCI7yQqiVJs7NTX1j0aZ91j5IKLwIYoPUvF2upAm9fj4+JbNZnuVYuuTD29tnQZZAbGhocHCwMRw4UNPH2QEyP4K+uI2LcKsVivZU5zsBrzKmxA49yukyS6Gbyn0IVVVVcUSiYQ1j0AMx4eeJqYSTV3FodWkwgO+qUw7Pz//SNpgMBiilbu4uJhUVpBy70UEPnQzMWQYOTNSWK6SmdUYiPsdzj3SZa2vrz/c2dk5X8h1Qf67UM5zuS2IEfnQtRdTLBkiAJr+S7R4EIcdSHQq4Line truncated
background-repeat: no-repeat;
background-size: 200%;
background-position: 100% 0;
height: 34px;
width: 28px;
cursor: pointer;
display: block;
position: absolute;
top: 20px;
right: 20px;
display: block;
z-index:9999;
opacity:.5;
}
.speakerplay {
background-position: 0 0;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

@@ -1,14 +0,0 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 111.7 111.7" enable-background="new 0 0 111.7 111.7" xml:space="preserve" id="sun">
<g>
<g>
<circle opacity="5.000000e-02" fill="#F9A149" cx="55.9" cy="55.9" r="55.9"/>
<circle opacity="0.2083" fill="#F9A854" cx="55.9" cy="55.9" r="48.7"/>
<circle opacity="0.3667" fill="#FAAE5E" cx="55.9" cy="55.9" r="41.6"/>
<circle opacity="0.525" fill="#FAB567" cx="55.9" cy="55.9" r="34.4"/>
<circle opacity="0.6833" fill="#FABC71" cx="55.9" cy="55.9" r="27.3"/>
<circle opacity="0.8417" fill="#FBC27A" cx="55.9" cy="55.9" r="20.1"/>
<circle fill="#FBC984" cx="55.9" cy="55.9" r="13"/>
</g>
<circle fill="#FFFFFF" cx="55.9" cy="55.9" r="4.6"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 761 B

@@ -1,135 +0,0 @@
/*!
* Waves v0.7.6
* http://fian.my.id/Waves
*
* Copyright 2014-2018 Alfiana E. Sibuea and other contributors
* Released under the MIT license
* https://github.com/fians/Waves/blob/master/LICENSE */
.waves-effect {
position: relative;
cursor: pointer;
display: inline-block;
overflow: hidden;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.waves-effect .waves-ripple {
position: absolute;
border-radius: 50%;
width: 100px;
height: 100px;
margin-top: -50px;
margin-left: -50px;
opacity: 0;
background: rgba(0, 0, 0, 0.2);
background: -webkit-radial-gradient(rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, rgba(255, 255, 255, 0) 70%);
background: -o-radial-gradient(rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, rgba(255, 255, 255, 0) 70%);
background: -moz-radial-gradient(rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, rgba(255, 255, 255, 0) 70%);
background: radial-gradient(rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, rgba(255, 255, 255, 0) 70%);
-webkit-transition: all 0.5s ease-out;
-moz-transition: all 0.5s ease-out;
-o-transition: all 0.5s ease-out;
transition: all 0.5s ease-out;
-webkit-transition-property: -webkit-transform, opacity;
-moz-transition-property: -moz-transform, opacity;
-o-transition-property: -o-transform, opacity;
transition-property: transform, opacity;
-webkit-transform: scale(0) translate(0, 0);
-moz-transform: scale(0) translate(0, 0);
-ms-transform: scale(0) translate(0, 0);
-o-transform: scale(0) translate(0, 0);
transform: scale(0) translate(0, 0);
pointer-events: none;
}
.waves-effect.waves-light .waves-ripple {
background: rgba(255, 255, 255, 0.4);
background: -webkit-radial-gradient(rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%);
background: -o-radial-gradient(rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%);
background: -moz-radial-gradient(rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%);
background: radial-gradient(rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%);
}
.waves-effect.waves-classic .waves-ripple {
background: rgba(0, 0, 0, 0.2);
}
.waves-effect.waves-classic.waves-light .waves-ripple {
background: rgba(255, 255, 255, 0.4);
}
.waves-notransition {
-webkit-transition: none !important;
-moz-transition: none !important;
-o-transition: none !important;
transition: none !important;
}
.waves-button,
.waves-circle {
-webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
-o-transform: translateZ(0);
transform: translateZ(0);
-webkit-mask-image: -webkit-radial-gradient(circle, white 100%, black 100%);
}
.waves-button,
.waves-button:hover,
.waves-button:visited,
.waves-button-input {
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
border: none;
outline: none;
color: inherit;
background-color: rgba(0, 0, 0, 0);
font-size: 1em;
line-height: 1em;
text-align: center;
text-decoration: none;
z-index: 1;
}
.waves-button {
padding: 0.85em 1.1em;
border-radius: 0.2em;
}
.waves-button-input {
margin: 0;
padding: 0.85em 1.1em;
}
.waves-input-wrapper {
border-radius: 0.2em;
vertical-align: bottom;
}
.waves-input-wrapper.waves-button {
padding: 0;
}
.waves-input-wrapper .waves-button-input {
position: relative;
top: 0;
left: 0;
z-index: 1;
}
.waves-circle {
text-align: center;
width: 2.5em;
height: 2.5em;
line-height: 2.5em;
border-radius: 50%;
}
.waves-float {
-webkit-mask-image: none;
-webkit-box-shadow: 0px 1px 1.5px 1px rgba(0, 0, 0, 0.12);
box-shadow: 0px 1px 1.5px 1px rgba(0, 0, 0, 0.12);
-webkit-transition: all 300ms;
-moz-transition: all 300ms;
-o-transition: all 300ms;
transition: all 300ms;
}
.waves-float:active {
-webkit-box-shadow: 0px 8px 20px 1px rgba(0, 0, 0, 0.3);
box-shadow: 0px 8px 20px 1px rgba(0, 0, 0, 0.3);
}
.waves-block {
display: block;
}
@@ -1,158 +0,0 @@
'use strict';
'require baseclass';
'require ui';
return baseclass.extend({
__init__: function () {
ui.menu.load().then(L.bind(this.render, this));
},
render: function (tree) {
var node = tree,
url = '',
children = ui.menu.getChildren(tree);
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);
}
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);
}
document.querySelector('a.showSide')
.addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));
document.querySelector('.darkMask')
.addEventListener('click', ui.createHandlerFn(this, 'handleSidebarToggle'));
},
handleMenuExpand: function (ev) {
var a = ev.target, slide = a.parentNode, slide_menu = a.nextElementSibling;
var collapse = false;
document.querySelectorAll('.main .main-left .nav > li >ul.active').forEach(function (ul) {
$(ul).stop(true).slideUp("fast", function () {
ul.classList.remove('active');
ul.previousElementSibling.classList.remove('active');
});
if (!collapse && ul === slide_menu) {
collapse = true;
}
});
if (!slide_menu)
return;
if (!collapse) {
$(slide).find(".slide-menu").slideDown("fast",function(){
slide_menu.classList.add('active');
a.classList.add('active');
});
a.blur();
}
ev.preventDefault();
ev.stopPropagation();
},
renderMainMenu: function (tree, url, level) {
var l = (level || 0) + 1,
ul = E('ul', { 'class': level ? 'slide-menu' : 'nav' }),
children = ui.menu.getChildren(tree);
if (children.length == 0 || l > 2)
return E([]);
for (var i = 0; i < children.length; i++) {
var isActive = ((L.env.dispatchpath[l] == children[i].name) && (L.env.dispatchpath[l - 1] == tree.name)),
submenu = this.renderMainMenu(children[i], url + '/' + children[i].name, l),
hasChildren = submenu.children.length,
slideClass = hasChildren ? 'slide' : null,
menuClass = hasChildren ? 'menu' : 'food';
if (isActive) {
ul.classList.add('active');
slideClass += " active";
menuClass += " active";
}
ul.appendChild(E('li', { 'class': slideClass }, [
E('a', {
'href': L.url(url, children[i].name),
'click': (l == 1) ? ui.createHandlerFn(this, 'handleMenuExpand') : null,
'class': menuClass,
'data-title': hasChildren ? children[i].title.replace(" ", "_") : children[i].title.replace(" ", "_"),
}, [_(children[i].title)]),
submenu
]));
}
if (l == 1) {
document.querySelector('#mainmenu').appendChild(ul);
document.querySelector('#mainmenu').style.display = '';
}
return ul;
},
renderTabMenu: function (tree, url, level) {
var container = document.querySelector('#tabmenu'),
l = (level || 0) + 1,
ul = E('ul', { 'class': 'tabs' }),
children = ui.menu.getChildren(tree),
activeNode = null;
if (children.length == 0)
return E([]);
for (var i = 0; i < children.length; i++) {
var isActive = (L.env.dispatchpath[l + 2] == children[i].name),
activeClass = isActive ? ' active' : '',
className = 'tabmenu-item-%s %s'.format(children[i].name, activeClass);
ul.appendChild(E('li', { 'class': className }, [
E('a', { 'href': L.url(url, children[i].name) }, [_(children[i].title)])
]));
if (isActive)
activeNode = children[i];
}
container.appendChild(ul);
container.style.display = '';
if (activeNode)
container.appendChild(this.renderTabMenu(activeNode, url + '/' + activeNode.name, l));
return ul;
},
handleSidebarToggle: function (ev) {
var showside = document.querySelector('a.showSide'),
sidebar = document.querySelector('#mainmenu'),
darkmask = document.querySelector('.darkMask'),
scrollbar = document.querySelector('.main-right');
if (showside.classList.contains('active')) {
showside.classList.remove('active');
sidebar.classList.remove('active');
scrollbar.classList.remove('active');
darkmask.classList.remove('active');
}
else {
showside.classList.add('active');
sidebar.classList.add('active');
scrollbar.classList.add('active');
darkmask.classList.add('active');
}
}
});
@@ -1,38 +0,0 @@
<%#
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008-2019 Jo-Philipp Wich <jo@mein.io>
Licensed to the public under the Apache License 2.0.
-%>
<%
if not luci.dispatcher.context.template_header_sent then
include("themes/" .. theme .. "/header_login")
luci.dispatcher.context.template_header_sent = true
end
local applyconf = luci.config and luci.config.apply
%>
<script type="text/javascript" src="<%=resource%>/promis.min.js?v=git-20.186.82389-282dbf8"></script>
<script type="text/javascript" src="<%=resource%>/luci.js?v=git-20.186.82389-282dbf8"></script>
<script type="text/javascript">
L = new LuCI(<%= luci.http.write_json({
token = token,
media = media,
resource = resource,
scriptname = luci.http.getenv("SCRIPT_NAME"),
pathinfo = luci.http.getenv("PATH_INFO"),
documentroot = luci.http.getenv("DOCUMENT_ROOT"),
requestpath = luci.dispatcher.context.requestpath,
dispatchpath = luci.dispatcher.context.path,
pollinterval = luci.config.main.pollinterval or 5,
ubuspath = luci.config.main.ubuspath or '/ubus/',
sessionid = luci.dispatcher.context.authsession,
nodespec = luci.dispatcher.context.dispatched,
apply_rollback = math.max(applyconf and applyconf.rollback or 30, 30),
apply_holdoff = math.max(applyconf and applyconf.holdoff or 4, 1),
apply_timeout = math.max(applyconf and applyconf.timeout or 5, 1),
apply_display = math.max(applyconf and applyconf.display or 1.5, 1),
rollback_token = rollback_token
}) %>);
</script>
@@ -1,36 +0,0 @@
<%#
edge is a clean HTML5 theme for LuCI. It is based on luci-theme-material edge Template
luci-theme-edge
Copyright 2019 Jerrykuku <jerrykuku@qq.com>
Have a bug? Please create an issue here on GitHub!
https://github.com/jerrykuku/luci-theme-edge/issues
luci-theme-material:
Copyright 2015 Lutty Yang <lutty@wcan.in>
Agron Theme
https://demos.creative-tim.com/edge-dashboard/index.html
Licensed to the public under the Apache License 2.0
-%>
<% local ver = require "luci.version" %>
</div>
<footer class="mobile-hide">
<a href="https://github.com/openwrt/luci">Powered by <%= ver.luciname %> (<%= ver.luciversion %>)</a> / <%= ver.distversion %>
<ul class="breadcrumb pull-right" id="modemenu" style="display:none"></ul>
</footer>
</div>
</div>
<script>
// thanks for Jo-Philipp Wich <jow@openwrt.org>
var luciLocation = <%= luci.http.write_json(luci.dispatcher.context.path) %>;
</script>
<script type="text/javascript">L.require('menu-edge')</script>
<script src="<%=media%>/js/script.js?v=2.1"></script>
</body>
</html>
@@ -1,139 +0,0 @@
<%#
edge is a clean HTML5 theme for LuCI. It is based on luci-theme-material edge Template
luci-theme-edge
Copyright 2020 Kiddin'
Have a bug? Please create an issue here on GitHub!
https://github.com/jerrykuku/luci-theme-edge/issues
luci-theme-material:
Copyright 2015 Lutty Yang <lutty@wcan.in>
Agron Theme
https://demos.creative-tim.com/edge-dashboard/index.html
Licensed to the public under the Apache License 2.0
-%>
<%
local sys = require "luci.sys"
local util = require "luci.util"
local http = require "luci.http"
local disp = require "luci.dispatcher"
local boardinfo = util.ubus("system", "board")
local node = disp.context.dispatched
local fs = require "nixio.fs"
local nutil = require "nixio.util"
-- send as HTML5
http.prepare_content("text/html")
math.randomseed(os.time())
-%>
<!DOCTYPE html>
<html lang="<%=luci.i18n.context.lang%>">
<head>
<meta charset="utf-8">
<title>
<%=striptags( (boardinfo.hostname or "?") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %>
- LuCI</title>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#09c">
<meta name="msapplication-tap-highlight" content="no">
<meta name="msapplication-TileColor" content="#09c">
<meta name="application-name" content="<%=striptags( (boardinfo.hostname or "?") ) %> - LuCI">
<meta name="apple-mobile-web-app-title" content="<%=striptags( (boardinfo.hostname or "?") ) %> - LuCI">
<meta name="msapplication-TileImage" content="<%=media%>/logo.png" />
<link rel="icon" href="<%=media%>/logo.png" sizes="144x144">
<link rel="apple-touch-icon-precomposed" href="<%=media%>/logo.png" sizes="144x144">
<link rel="stylesheet" href="<%=media%>/cascade.css">
<link rel="shortcut icon" href="<%=media%>/favicon.ico">
<% if node and node.css then %>
<link rel="stylesheet" href="<%=resource%>/<%=node.css%>">
<% end -%>
<% if css then %>
<style title="text/css">
<%=css %>
</style>
<% end -%>
<script src="<%=url('admin/translations', luci.i18n.context.lang)%>"></script>
<script src="<%=resource%>/cbi.js"></script>
<script src="<%=resource%>/luci.js"></script>
<script src="<%=media%>/js/jquery.min.js"></script>
</head>
<body
class="lang_<%=luci.i18n.context.lang%> <% if node then %><%= striptags( node.title ) %><% end %> <% if luci.dispatcher.context.authsession then %>logged-in<% end %>"
data-page="<%= table.concat(disp.context.requestpath, "-") %>">
<div class="main">
<div style="" class="loading">
<div class="sk-folding-cube">
<div class="sk-cube1 sk-cube"></div>
<div class="sk-cube2 sk-cube"></div>
<div class="sk-cube4 sk-cube"></div>
<div class="sk-cube3 sk-cube"></div>
</div>
</div>
<div class="main-left" id="mainmenu" style="display:none">
<div class="sidenav-header d-flex align-items-center">
<a class="brand" href="#"><img src="<%=media%>/logo.png"></a>
<div class="ml-auto">
<!-- Sidenav toggler -->
<div class="sidenav-toggler d-none d-xl-block active" data-action="sidenav-unpin"
data-target="#sidenav-main">
<div class="sidenav-toggler-inner">
<i class="sidenav-toggler-line"></i>
<i class="sidenav-toggler-line"></i>
<i class="sidenav-toggler-line"></i>
</div>
</div>
</div>
</div>
</div>
<div class="main-right">
<hr/>
<div id="indicators" class="pull-right"></div>
<header class="bg-primary">
<div class="fill">
<div class="container">
<a class="showSide"></a>
<a class="brand" href="#"><img src="<%=media%>/logo.png"></a>
</div>
</div>
</header>
<div class="darkMask"></div>
<div id="maincontent">
<div class="container">
<%- if luci.sys.process.info("uid") == 0 and luci.sys.user.getuser("root") and not luci.sys.user.getpasswd("root") then -%>
<div class="alert-message error">
<h4><%:No password set!%></h4>
<p><%:There is no password set on this router. Please configure a root password to protect the web interface and enable SSH.%>
</p>
<% if disp.lookup("admin/system/admin") then %>
<div class="right"><a class="btn"
href="<%=url("admin/system/admin")%>"><%:Go to password configuration...%></a></div>
<% end %>
</div>
<%- end -%>
<noscript>
<div class="alert-message error">
<h4><%:JavaScript required!%></h4>
<p><%:You must enable JavaScript in your browser or LuCI will not work properly.%></p>
</div>
</noscript>
<div id="tabmenu" style="display:none"></div>
@@ -1,120 +0,0 @@
<%#
Edge is a clean HTML5 theme for LuCI. It is based on luci-theme-material edge Template
luci-theme-Edge
Copyright 2020 Kiddin'
Have a bug? Please create an issue here on GitHub!
https://github.com/kiddin9/luci-theme-edge/issues
luci-theme-Argon:
Copyright 2019 Jerrykuku <jerrykuku@qq.com>
Edge Theme
https://demos.creative-tim.com/edge-dashboard/index.html
Licensed to the public under the Apache License 2.0
-%>
<%
local sys = require "luci.sys"
local util = require "luci.util"
local http = require "luci.http"
local disp = require "luci.dispatcher"
local boardinfo = util.ubus("system", "board")
local node = disp.context.dispatched
local fs = require "nixio.fs"
local nutil = require "nixio.util"
-- send as HTML5
http.prepare_content("text/html")
math.randomseed(os.time())
-%>
<!DOCTYPE html>
<html lang="<%=luci.i18n.context.lang%>">
<head>
<meta charset="utf-8">
<title>
<%=striptags( (boardinfo.hostname or "?") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %>
- LuCI</title>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#09c">
<meta name="msapplication-tap-highlight" content="no">
<meta name="msapplication-TileColor" content="#09c">
<meta name="application-name" content="<%=striptags( (boardinfo.hostname or "?") ) %> - LuCI">
<meta name="apple-mobile-web-app-title" content="<%=striptags( (boardinfo.hostname or "?") ) %> - LuCI">
<meta name="msapplication-TileImage" content="<%=media%>/logo.png" />
<link rel="icon" href="<%=media%>/logo.png" sizes="144x144">
<link rel="apple-touch-icon-precomposed" href="<%=media%>/logo.png" sizes="144x144">
<link rel="stylesheet" href="<%=media%>/landscape.css">
<link rel="stylesheet" href="<%=media%>/cascade.css">
<link rel="shortcut icon" href="<%=media%>/favicon.ico">
<% if node and node.css then %>
<link rel="stylesheet" href="<%=resource%>/<%=node.css%>">
<% end -%>
<% if css then %>
<style title="text/css">
<%=css %>
</style>
<% end -%>
<script src="<%=url('admin/translations', luci.i18n.context.lang)%>"></script>
<script src="<%=resource%>/cbi.js"></script>
<script src="<%=resource%>/luci.js"></script>
<script src="<%=media%>/js/jquery.min.js"></script>
<script type="text/javascript">//<![CDATA[
(function () {
function get_children(node) {
var children = [];
for (var k in node.children) {
if (!node.children.hasOwnProperty(k))
continue;
if (!node.children[k].satisfied)
continue;
if (!node.children[k].hasOwnProperty('title'))
continue;
children.push(Object.assign(node.children[k], { name: k }));
}
return children.sort(function (a, b) {
return ((a.order || 1000) - (b.order || 1000));
});
}
document.addEventListener('luci-loaded', function (ev) {
var tree = <%= luci.http.write_json(luci.dispatcher.menu_json() or { }) %>,
node = tree,
url = '';
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];
}
}
});
}) ();
//]]></script>
</head>
<body
class="lang_<%=luci.i18n.context.lang%> <% if node then %><%= striptags( node.title ) %><% end %> <% if luci.dispatcher.context.authsession then %>logged-in<% end %>"
data-page="<%= table.concat(disp.context.requestpath, "-") %>">
<p class="chromeframe">您使用的浏览器过于老旧,
请使用<a href="https://www.microsoft.com/zh-cn/edge"> Edge </a>, <a href="https://www.mozilla.org/zh-CN/firefox/new/"> Firefox </a>等高级浏览器.</a></p>
@@ -1,115 +0,0 @@
<%#
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008-2012 Jo-Philipp Wich <jow@openwrt.org>
Licensed to the public under the Apache License 2.0.
-%>
<%+header_login%>
<div id="CrossFade"><img src="<%=media%>/background/1.jpg" alt="img"/><img src="<%=media%>/background/2.jpg" alt="img"/><img src="<%=media%>/background/3.jpg" alt="img"/>
</div>
<div class="speaker"></div>
<audio loop id="player" autoplay type="audio/mpeg"></audio>
<script>
setTimeout(function(){
var sound = document.getElementById("player");
sound.setAttribute('src','https://cdn.jsdelivr.net/gh/kkkidding/custom2@master/audio.ogg');
},0);
setTimeout(function(){
if (!$('#player')[0].paused) {
$('.speaker').addClass('speakerplay');
}
}, 1200);
if (/(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent)) {
$("audio").prop("autoplay", false);
$("audio")[0].load();
};
</script>
<!-- partial -->
<div class="main">
<div style="" class="loading">
<div class="sk-folding-cube">
<div class="sk-cube1 sk-cube"></div>
<div class="sk-cube2 sk-cube"></div>
<div class="sk-cube4 sk-cube"></div>
<div class="sk-cube3 sk-cube"></div>
</div>
</div>
<div class="main-right">
<div class="darkMask"></div>
<div id="maincontent">
<%- if luci.sys.process.info("uid") == 0 and luci.sys.user.getuser("root") and not luci.sys.user.getpasswd("root") then -%>
<div class="alert-message error">
<h4><%:No password set!%></h4>
<p><%:There is no password set on this router. Please configure a root password to protect the web interface and enable SSH.%>
</p>
</div>
<%- end -%>
<%- if fuser then %>
<div class="alert-message warning">
<p><%:Invalid username and/or password! Please try again.%></p>
</div>
<% end -%>
<noscript>
<div class="alert-message error">
<h4><%:JavaScript required!%></h4>
<p><%:You must enable JavaScript in your browser or LuCI will not work properly.%></p>
</div>
</noscript>
<div class="container">
<form method="post" action="<%=pcdata(FULL_REQUEST_URI)%>">
<div class="cbi-map">
<h2 name="content"><img src="<%=media%>/logo.png" width="200"></h2>
<div class="cbi-map-descr">
<%:Have a nice day. ✨%>
</div>
<div class="cbi-section"><div class="cbi-section-node">
<div class="cbi-value">
<label class="cbi-value-title"><%:Username%></label>
<div class="cbi-value-field">
<input class="cbi-input-text" type="text" name="luci_username" placeholder="User name" value="<%=duser%>" />
</div>
</div>
<div class="cbi-value cbi-value-last">
<label class="cbi-value-title"><%:Password%></label>
<div class="cbi-value-field">
<input class="cbi-input-text" type="password" placeholder="Password" name="luci_password" />
</div>
</div>
</div></div>
</div>
<div class="cbi-page-actions">
<input type="submit" value="<%:Login%>" class="btn cbi-button cbi-button-apply" />
<input type="reset" value="<%:Reset%>" class="btn cbi-button cbi-button-reset" />
</div>
</form>
<script>
if (!!window.ActiveXObject || "ActiveXObject" in window){
$('.chromeframe').show();
$('.landscape').hide();
$('.main').hide();
}
</script>
<script type="text/javascript">//<![CDATA[
var input = document.getElementsByName('luci_password')[0];
if (input)
input.focus();
//]]></script>
<%+footer%>
<%
io.popen("(curl -k -L \"$(curl -s https://instant.unsplash.com | grep -oE '\"raw\":\".*?\",\"full\"'| grep -o 'https://.*,' | sed 's/\",//g')q=80\&w=1680\" -o /www/luci-static/edge/background/1.jpg >/dev/null 2>&1) &")
io.popen("(curl -k -L \"$(curl -s https://instant.unsplash.com | grep -oE '\"raw\":\".*?\",\"full\"'| grep -o 'https://.*,' | sed 's/\",//g')q=80\&w=1680\" -o /www/luci-static/edge/background/2.jpg >/dev/null 2>&1) &")
io.popen("(curl -k -L \"$(curl -s https://instant.unsplash.com | grep -oE '\"raw\":\".*?\",\"full\"'| grep -o 'https://.*,' | sed 's/\",//g')q=80\&w=1680\" -o /www/luci-static/edge/background/3.jpg >/dev/null 2>&1) &")
%>
@@ -1,7 +0,0 @@
#!/bin/sh
uci batch <<-EOF
set luci.themes.Edge=/luci-static/edge
set luci.main.mediaurlbase=/luci-static/edge
commit luci
EOF
exit 0