mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 15:45:13 +08:00
🎁 Sync 2026-08-05 20:25:34
This commit is contained in:
1 parent
e71d910024
commit
ca3fdb1643
31 files changed
+374
-113
No files matched your search
+2
-2
@@ -10,8 +10,8 @@ include $(TOPDIR)/rules.mk
|
|||||||
PKG_ARCH_BAIDUDRIVE:=$(ARCH)
|
PKG_ARCH_BAIDUDRIVE:=$(ARCH)
|
||||||
|
|
||||||
PKG_NAME:=baidudrive
|
PKG_NAME:=baidudrive
|
||||||
PKG_VERSION:=baidudrive-runtime-v1.0.4
|
PKG_VERSION:=linkease-runtime-v1.7.5
|
||||||
PKG_RELEASE:=5
|
PKG_RELEASE:=6
|
||||||
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/baidudrive-runtime-v$(PKG_VERSION)/
|
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/baidudrive-runtime-v$(PKG_VERSION)/
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
|
|||||||
+3
-3
@@ -5,10 +5,10 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=dae
|
PKG_NAME:=dae
|
||||||
PKG_VERSION:=2026.08.03
|
PKG_VERSION:=2026.08.05
|
||||||
PKG_RELEASE:=30
|
PKG_RELEASE:=31
|
||||||
|
|
||||||
PKG_SOURCE:=dae-src-2026.08.03-354bb6787b4e.tar.gz
|
PKG_SOURCE:=dae-src-2026.08.05-03ce5c39b38c.tar.gz
|
||||||
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
|
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
|
|||||||
+3
-3
@@ -5,10 +5,10 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=daed
|
PKG_NAME:=daed
|
||||||
PKG_VERSION:=2026.08.03
|
PKG_VERSION:=2026.08.05
|
||||||
PKG_RELEASE:=41
|
PKG_RELEASE:=42
|
||||||
|
|
||||||
PKG_SOURCE:=daed-src-2026.08.03-5f7939adc12a.tar.gz
|
PKG_SOURCE:=daed-src-2026.08.05-ebb45ff28107.tar.gz
|
||||||
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
|
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||||
PKG_HASH:=skip
|
PKG_HASH:=skip
|
||||||
|
|||||||
+2
-2
@@ -5,8 +5,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=dnsproxy
|
PKG_NAME:=dnsproxy
|
||||||
PKG_VERSION:=0.83.1
|
PKG_VERSION:=0.83.2
|
||||||
PKG_RELEASE:=14
|
PKG_RELEASE:=15
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/AdguardTeam/dnsproxy/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/AdguardTeam/dnsproxy/tar.gz/v$(PKG_VERSION)?
|
||||||
|
|||||||
+2
-2
@@ -6,8 +6,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=gowebdav
|
PKG_NAME:=gowebdav
|
||||||
PKG_VERSION:=5.14.1
|
PKG_VERSION:=5.14.2
|
||||||
PKG_RELEASE:=18
|
PKG_RELEASE:=19
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||||
PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)?
|
PKG_SOURCE_URL:=https://codeload.github.com/hacdias/webdav/tar.gz/v$(PKG_VERSION)?
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
@@ -12,7 +12,7 @@ LUCI_PKGARCH:=all
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-accesscontrol-plus
|
PKG_NAME:=luci-app-accesscontrol-plus
|
||||||
PKG_VERSION:=1
|
PKG_VERSION:=1
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
include $(TOPDIR)/feeds/luci/luci.mk
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
# call BuildPackage - OpenWrt buildroot signature
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
@@ -15,7 +15,7 @@ end
|
|||||||
|
|
||||||
function act_status()
|
function act_status()
|
||||||
local e = {}
|
local e = {}
|
||||||
e.running = luci.sys.call("iptables -L INPUT |grep MIAPLUS >/dev/null") == 0
|
e.running = luci.sys.call("iptables -t filter -S MIAPLUS >/dev/null 2>&1") == 0
|
||||||
luci.http.prepare_content("application/json")
|
luci.http.prepare_content("application/json")
|
||||||
luci.http.write_json(e)
|
luci.http.write_json(e)
|
||||||
end
|
end
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
local ds = require "luci.dispatcher"
|
local ds = require "luci.dispatcher"
|
||||||
|
|
||||||
a = Map("miaplus")
|
a = Map("miaplus")
|
||||||
|
a.apply_on_parse = true
|
||||||
|
function a.on_apply(self)
|
||||||
|
luci.sys.call("/etc/init.d/miaplus restart >/dev/null 2>&1 &")
|
||||||
|
end
|
||||||
|
|
||||||
t = a:section(TypedSection, "templates")
|
t = a:section(TypedSection, "templates")
|
||||||
t.template = "cbi/tblsection"
|
t.template = "cbi/tblsection"
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ local uci = luci.model.uci.cursor()
|
|||||||
a = Map("miaplus")
|
a = Map("miaplus")
|
||||||
a.title = translate("Internet Access Schedule Control Plus")
|
a.title = translate("Internet Access Schedule Control Plus")
|
||||||
a.description = translate("Access Schedule Control Description")
|
a.description = translate("Access Schedule Control Description")
|
||||||
|
a.apply_on_parse = true
|
||||||
|
function a.on_apply(self)
|
||||||
|
luci.sys.call("/etc/init.d/miaplus restart >/dev/null 2>&1 &")
|
||||||
|
end
|
||||||
|
|
||||||
a:section(SimpleSection).template = "miaplus/miaplus_status"
|
a:section(SimpleSection).template = "miaplus/miaplus_status"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
a = Map("miaplus")
|
a = Map("miaplus")
|
||||||
|
a.apply_on_parse = true
|
||||||
|
function a.on_apply(self)
|
||||||
|
luci.sys.call("/etc/init.d/miaplus restart >/dev/null 2>&1 &")
|
||||||
|
end
|
||||||
|
|
||||||
local section = arg[1]
|
local section = arg[1]
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
msgid "Internet Access Schedule Control Plus"
|
msgid "Internet Access Schedule Control Plus"
|
||||||
msgstr "上网时间控制Plus"
|
msgstr "上网时间控制增强版"
|
||||||
|
|
||||||
msgid "Access Schedule Control Description"
|
msgid "Access Schedule Control Description"
|
||||||
msgstr "设置客户端访问互联网的时间"
|
msgstr "设置客户端访问互联网的时间"
|
||||||
|
|
||||||
|
msgid "Base Setting"
|
||||||
|
msgstr "基础设置"
|
||||||
|
|
||||||
|
msgid "Advance Setting"
|
||||||
|
msgstr "高级设置"
|
||||||
|
|
||||||
msgid "General switch"
|
msgid "General switch"
|
||||||
msgstr "开启/关闭"
|
msgstr "开启/关闭"
|
||||||
|
|
||||||
@@ -19,6 +25,9 @@ msgstr "严格模式"
|
|||||||
msgid "Strict Mode will degrade CPU performance, but it can achieve better results"
|
msgid "Strict Mode will degrade CPU performance, but it can achieve better results"
|
||||||
msgstr "严格模式会损耗部分CPU资源,但可以按照时间规则立即拦截数据包,效果更好"
|
msgstr "严格模式会损耗部分CPU资源,但可以按照时间规则立即拦截数据包,效果更好"
|
||||||
|
|
||||||
|
msgid "IPV6 Enabled"
|
||||||
|
msgstr "启用 IPV6"
|
||||||
|
|
||||||
msgid "Client Rules"
|
msgid "Client Rules"
|
||||||
msgstr "客户端规则"
|
msgstr "客户端规则"
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ START=30
|
|||||||
|
|
||||||
CONFIG=miaplus
|
CONFIG=miaplus
|
||||||
|
|
||||||
|
. /lib/functions.sh
|
||||||
|
|
||||||
uci_export_section_name() {
|
uci_export_section_name() {
|
||||||
local ret=$(uci -n export $CONFIG | grep "config $1" | awk '{print $3}' | awk -F\' '{print $2}')
|
local ret=$(uci -n export $CONFIG | grep "config $1" | awk '{print $3}' | awk -F\' '{print $2}')
|
||||||
echo ${ret:=$2}
|
echo ${ret:=$2}
|
||||||
@@ -50,23 +52,18 @@ uci_get_mac_by_template(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
add_rules(){
|
add_rules(){
|
||||||
local sections=$(uci_export_section_name templates '')
|
config_load "$CONFIG"
|
||||||
if [ -n "$sections" ]; then
|
config_foreach add_template_rule templates
|
||||||
for s in $sections
|
}
|
||||||
do
|
add_template_rule(){
|
||||||
local enable=$(uci_get_by_name $s enable '' $i)
|
local section="$1"
|
||||||
local title=$(uci_get_by_name $s title '' $i)
|
local enable
|
||||||
if [ -z $enable ]; then
|
|
||||||
break
|
config_get enable "$section" enable 0
|
||||||
fi
|
[ "$enable" = "1" ] && add_rule "$section"
|
||||||
if [ "$enable" == "1" ]; then
|
|
||||||
add_rule $s
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
add_rule(){
|
add_rule(){
|
||||||
ipv6enable=$(uci get miaplus.@basic[0].ipv6enable)
|
ipv6enable=$(uci -q get miaplus.@basic[0].ipv6enable)
|
||||||
local macaddrs=$(uci_get_mac_by_template $1 '')
|
local macaddrs=$(uci_get_mac_by_template $1 '')
|
||||||
if [ -z "$macaddrs" ]; then
|
if [ -z "$macaddrs" ]; then
|
||||||
exit 0
|
exit 0
|
||||||
@@ -74,7 +71,7 @@ add_rule(){
|
|||||||
for macaddr in $macaddrs
|
for macaddr in $macaddrs
|
||||||
do
|
do
|
||||||
iptables -t filter -A MIAPLUS -m mac --mac-source $macaddr -j DROP
|
iptables -t filter -A MIAPLUS -m mac --mac-source $macaddr -j DROP
|
||||||
[ $ipv6enable -eq 1 ] && ip6tables -t filter -A MIAPLUS -m mac --mac-source $macaddr -j DROP
|
[ "${ipv6enable:-0}" -eq 1 ] && ip6tables -t filter -A MIAPLUS -m mac --mac-source $macaddr -j DROP
|
||||||
done
|
done
|
||||||
for i in $(seq 0 100)
|
for i in $(seq 0 100)
|
||||||
do
|
do
|
||||||
@@ -103,7 +100,7 @@ add_rule(){
|
|||||||
for macaddr in $macaddrs
|
for macaddr in $macaddrs
|
||||||
do
|
do
|
||||||
iptables -t filter -I MIAPLUS -m mac --mac-source $macaddr -m time --kerneltz --timestart $timeon --timestop $timeoff --weekdays $Z1$Z2$Z3$Z4$Z5$Z6$Z7 -j ACCEPT
|
iptables -t filter -I MIAPLUS -m mac --mac-source $macaddr -m time --kerneltz --timestart $timeon --timestop $timeoff --weekdays $Z1$Z2$Z3$Z4$Z5$Z6$Z7 -j ACCEPT
|
||||||
[ $ipv6enable -eq 1 ] && ip6tables -t filter -I MIAPLUS -m mac --mac-source $macaddr -m time --kerneltz --timestart $timeon --timestop $timeoff --weekdays $Z1$Z2$Z3$Z4$Z5$Z6$Z7 -j ACCEPT
|
[ "${ipv6enable:-0}" -eq 1 ] && ip6tables -t filter -I MIAPLUS -m mac --mac-source $macaddr -m time --kerneltz --timestart $timeon --timestop $timeoff --weekdays $Z1$Z2$Z3$Z4$Z5$Z6$Z7 -j ACCEPT
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
for n in $(seq 1 7)
|
for n in $(seq 1 7)
|
||||||
@@ -126,27 +123,36 @@ del_rule(){
|
|||||||
|
|
||||||
start(){
|
start(){
|
||||||
stop
|
stop
|
||||||
enable=$(uci get miaplus.@basic[0].enable)
|
enable=$(uci -q get miaplus.@basic[0].enable)
|
||||||
[ $enable -eq 0 ] && exit 0
|
[ "${enable:-0}" -eq 0 ] && exit 0
|
||||||
iptables -t filter -N MIAPLUS
|
iptables -t filter -N MIAPLUS
|
||||||
iptables -I INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
|
iptables -I INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
|
||||||
iptables -I INPUT -p tcp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
|
iptables -I INPUT -p tcp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
|
||||||
iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control"
|
iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control"
|
||||||
iptables -t nat -A PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control"
|
iptables -t nat -A PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control"
|
||||||
strict=$(uci get miaplus.@basic[0].strict)
|
strict=$(uci -q get miaplus.@basic[0].strict)
|
||||||
[ $strict -eq 1 ] && iptables -t filter -I FORWARD -m comment --comment "Rule For Control" -j MIAPLUS
|
[ "${strict:-0}" -eq 1 ] && iptables -t filter -I FORWARD -m comment --comment "Rule For Control" -j MIAPLUS
|
||||||
|
|
||||||
ipv6enable=$(uci get miaplus.@basic[0].ipv6enable)
|
ipv6enable=$(uci -q get miaplus.@basic[0].ipv6enable)
|
||||||
if [ "$ipv6enable" -eq 1 ]; then
|
if [ "${ipv6enable:-0}" -eq 1 ]; then
|
||||||
ip6tables -t filter -N MIAPLUS
|
ip6tables -t filter -N MIAPLUS
|
||||||
ip6tables -I INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
|
ip6tables -I INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
|
||||||
ip6tables -I INPUT -p tcp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
|
ip6tables -I INPUT -p tcp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
|
||||||
ip6tables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control"
|
ip6tables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control"
|
||||||
ip6tables -t nat -A PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control"
|
ip6tables -t nat -A PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control"
|
||||||
[ $strict -eq 1 ] && ip6tables -t filter -I FORWARD -m comment --comment "Rule For Control" -j MIAPLUS
|
[ "${strict:-0}" -eq 1 ] && ip6tables -t filter -I FORWARD -m comment --comment "Rule For Control" -j MIAPLUS
|
||||||
fi
|
fi
|
||||||
add_rules
|
add_rules
|
||||||
}
|
}
|
||||||
|
status(){
|
||||||
|
if iptables -t filter -S MIAPLUS >/dev/null 2>&1; then
|
||||||
|
echo "running"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "not running"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
stop(){
|
stop(){
|
||||||
iptables -t filter -D FORWARD -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
|
iptables -t filter -D FORWARD -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
|
||||||
iptables -D INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
|
iptables -D INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ PKG_MAINTAINER:=JiaY-shi <shi05275@163.com>
|
|||||||
|
|
||||||
PKG_NAME:=luci-app-fancontrol
|
PKG_NAME:=luci-app-fancontrol
|
||||||
PKG_VERSION:=2.0.0
|
PKG_VERSION:=2.0.0
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
|
|
||||||
include $(TOPDIR)/feeds/luci/luci.mk
|
include $(TOPDIR)/feeds/luci/luci.mk
|
||||||
# call BuildPackage - OpenWrt buildroot signature
|
# call BuildPackage - OpenWrt buildroot signature
|
||||||
@@ -70,24 +70,38 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fancontrol-level {
|
.fancontrol-chart-wrap {
|
||||||
height: 4px;
|
height: 72px;
|
||||||
margin-top: .35rem;
|
margin-top: .4rem;
|
||||||
overflow: hidden;
|
|
||||||
background: rgba(127, 127, 127, .2);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fancontrol-level > div {
|
.fancontrol-chart {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--primary-color-medium, #1976d2);
|
color: var(--text-color-medium, #666);
|
||||||
border-radius: 3px;
|
background: rgba(127, 127, 127, .025);
|
||||||
transition: width .25s ease-in;
|
border: 1px solid rgba(127, 127, 127, .24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fancontrol-chart-temperature {
|
||||||
|
--fancontrol-chart-line: #c74b45;
|
||||||
|
--fancontrol-chart-fill: rgba(199, 75, 69, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fancontrol-chart-pwm {
|
||||||
|
--fancontrol-chart-line: #1976d2;
|
||||||
|
--fancontrol-chart-fill: rgba(25, 118, 210, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fancontrol-chart-rpm {
|
||||||
|
--fancontrol-chart-line: #2f8a57;
|
||||||
|
--fancontrol-chart-fill: rgba(47, 138, 87, .2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fancontrol-details {
|
.fancontrol-details {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
gap: .3rem 1rem;
|
gap: .3rem 1rem;
|
||||||
padding: .42rem .7rem;
|
padding: .42rem .7rem;
|
||||||
background: rgba(127, 127, 127, .04);
|
background: rgba(127, 127, 127, .04);
|
||||||
@@ -102,10 +116,6 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fancontrol-detail:last-child {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fancontrol-detail-value {
|
.fancontrol-detail-value {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
@@ -133,9 +143,6 @@
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fancontrol-detail:last-child {
|
|
||||||
grid-column: auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 420px) {
|
@media (max-width: 420px) {
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ const callServiceList = rpc.declare({
|
|||||||
expect: { '': {} }
|
expect: { '': {} }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const HISTORY_WINDOW_MS = 2 * 60 * 1000;
|
||||||
|
const TIME_GRID_INTERVAL_MS = 10 * 1000;
|
||||||
|
const TIME_LABEL_INTERVAL_MS = 30 * 1000;
|
||||||
|
const VALUE_GRID_DIVISIONS = 4;
|
||||||
|
|
||||||
function serviceRunning(result) {
|
function serviceRunning(result) {
|
||||||
const instances = result?.fancontrol?.instances || {};
|
const instances = result?.fancontrol?.instances || {};
|
||||||
|
|
||||||
@@ -35,20 +40,173 @@ function parseStatus(text) {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusMetric(title, value, unit, level) {
|
function validStatusNumber(value) {
|
||||||
|
const number = Number(value);
|
||||||
|
|
||||||
|
return Number.isFinite(number) && number >= 0 ? number : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendHistory(history, running, status) {
|
||||||
|
const now = Date.now();
|
||||||
|
const temperature = validStatusNumber(status.temperature_mc);
|
||||||
|
const pwm = validStatusNumber(status.pwm);
|
||||||
|
const rpm = validStatusNumber(status.rpm);
|
||||||
|
|
||||||
|
history.push({
|
||||||
|
time: now,
|
||||||
|
temperature: running && temperature != null ? temperature / 1000 : null,
|
||||||
|
pwm: running && pwm != null ? pwm * 100 / 255 : null,
|
||||||
|
rpm: running ? rpm : null
|
||||||
|
});
|
||||||
|
|
||||||
|
while (history.length && history[0].time < now - HISTORY_WINDOW_MS)
|
||||||
|
history.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
function chartScale(history, key, minimumMax, step) {
|
||||||
|
let maximum = minimumMax;
|
||||||
|
|
||||||
|
history.forEach(function(sample) {
|
||||||
|
if (sample[key] != null)
|
||||||
|
maximum = Math.max(maximum, sample[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Math.ceil(maximum / step) * step;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawChart(canvas, history, key, options) {
|
||||||
|
const style = window.getComputedStyle(canvas);
|
||||||
|
const width = Math.max(canvas.clientWidth, 1);
|
||||||
|
const height = Math.max(canvas.clientHeight, 1);
|
||||||
|
const ratio = Math.min(window.devicePixelRatio || 1, 2);
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
const padding = { left: 28, right: 5, top: 6, bottom: 14 };
|
||||||
|
const plotWidth = Math.max(width - padding.left - padding.right, 1);
|
||||||
|
const plotHeight = Math.max(height - padding.top - padding.bottom, 1);
|
||||||
|
const plotBottom = padding.top + plotHeight;
|
||||||
|
const now = history.length ? history[history.length - 1].time : Date.now();
|
||||||
|
const start = now - HISTORY_WINDOW_MS;
|
||||||
|
const maximum = chartScale(history, key, options.minimumMax, options.step);
|
||||||
|
const lineColor = style.getPropertyValue('--fancontrol-chart-line').trim() || '#1976d2';
|
||||||
|
const fillColor = style.getPropertyValue('--fancontrol-chart-fill').trim() || 'rgba(25, 118, 210, .2)';
|
||||||
|
const labelColor = style.color || '#666';
|
||||||
|
const gridColor = 'rgba(127, 127, 127, .2)';
|
||||||
|
let segment = [];
|
||||||
|
|
||||||
|
canvas.width = Math.round(width * ratio);
|
||||||
|
canvas.height = Math.round(height * ratio);
|
||||||
|
context.setTransform(ratio, 0, 0, ratio, 0, 0);
|
||||||
|
context.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
context.strokeStyle = gridColor;
|
||||||
|
context.lineWidth = 1;
|
||||||
|
context.beginPath();
|
||||||
|
for (let elapsed = 0; elapsed <= HISTORY_WINDOW_MS; elapsed += TIME_GRID_INTERVAL_MS) {
|
||||||
|
const x = padding.left + plotWidth * elapsed / HISTORY_WINDOW_MS;
|
||||||
|
|
||||||
|
context.moveTo(x, padding.top);
|
||||||
|
context.lineTo(x, plotBottom);
|
||||||
|
}
|
||||||
|
for (let division = 0; division <= VALUE_GRID_DIVISIONS; division++) {
|
||||||
|
const y = padding.top + plotHeight * division / VALUE_GRID_DIVISIONS;
|
||||||
|
|
||||||
|
context.moveTo(padding.left, y);
|
||||||
|
context.lineTo(padding.left + plotWidth, y);
|
||||||
|
}
|
||||||
|
context.stroke();
|
||||||
|
|
||||||
|
context.fillStyle = labelColor;
|
||||||
|
context.font = '9px sans-serif';
|
||||||
|
context.textAlign = 'right';
|
||||||
|
context.textBaseline = 'top';
|
||||||
|
context.fillText(options.format(maximum), padding.left - 4, padding.top - 1);
|
||||||
|
context.textBaseline = 'bottom';
|
||||||
|
context.fillText(options.format(0), padding.left - 4, plotBottom + 1);
|
||||||
|
context.textBaseline = 'middle';
|
||||||
|
context.fillText(options.format(maximum / 2), padding.left - 4,
|
||||||
|
padding.top + plotHeight / 2);
|
||||||
|
|
||||||
|
context.textBaseline = 'bottom';
|
||||||
|
for (let elapsed = 0; elapsed <= HISTORY_WINDOW_MS; elapsed += TIME_LABEL_INTERVAL_MS) {
|
||||||
|
const x = padding.left + plotWidth * elapsed / HISTORY_WINDOW_MS;
|
||||||
|
const remaining = (HISTORY_WINDOW_MS - elapsed) / 1000;
|
||||||
|
|
||||||
|
context.textAlign = elapsed === 0 ? 'left' :
|
||||||
|
elapsed === HISTORY_WINDOW_MS ? 'right' : 'center';
|
||||||
|
context.fillText(remaining ? '-%ds'.format(remaining) : '0', x, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
function point(sample) {
|
||||||
|
return {
|
||||||
|
x: padding.left + (sample.time - start) / HISTORY_WINDOW_MS * plotWidth,
|
||||||
|
y: plotBottom - sample[key] / maximum * plotHeight
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawSegment() {
|
||||||
|
if (!segment.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (segment.length > 1) {
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(segment[0].x, plotBottom);
|
||||||
|
segment.forEach(function(item) { context.lineTo(item.x, item.y); });
|
||||||
|
context.lineTo(segment[segment.length - 1].x, plotBottom);
|
||||||
|
context.closePath();
|
||||||
|
context.fillStyle = fillColor;
|
||||||
|
context.fill();
|
||||||
|
|
||||||
|
context.beginPath();
|
||||||
|
segment.forEach(function(item, index) {
|
||||||
|
if (index)
|
||||||
|
context.lineTo(item.x, item.y);
|
||||||
|
else
|
||||||
|
context.moveTo(item.x, item.y);
|
||||||
|
});
|
||||||
|
context.strokeStyle = lineColor;
|
||||||
|
context.lineWidth = 1.5;
|
||||||
|
context.lineJoin = 'round';
|
||||||
|
context.stroke();
|
||||||
|
} else {
|
||||||
|
context.beginPath();
|
||||||
|
context.arc(segment[0].x, segment[0].y, 2, 0, Math.PI * 2);
|
||||||
|
context.fillStyle = lineColor;
|
||||||
|
context.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
segment = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
history.forEach(function(sample) {
|
||||||
|
if (sample[key] == null) {
|
||||||
|
drawSegment();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
segment.push(point(sample));
|
||||||
|
});
|
||||||
|
drawSegment();
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusMetric(title, value, unit, chartClass) {
|
||||||
|
const canvas = E('canvas', {
|
||||||
|
'class': 'fancontrol-chart fancontrol-chart-%s'.format(chartClass),
|
||||||
|
'role': 'img',
|
||||||
|
'aria-label': title
|
||||||
|
});
|
||||||
const children = [
|
const children = [
|
||||||
E('span', { 'class': 'fancontrol-metric-label' }, title),
|
E('span', { 'class': 'fancontrol-metric-label' }, title),
|
||||||
E('div', { 'class': 'fancontrol-metric-reading' }, [
|
E('div', { 'class': 'fancontrol-metric-reading' }, [
|
||||||
E('span', { 'class': 'fancontrol-metric-value' }, value),
|
E('span', { 'class': 'fancontrol-metric-value' }, value),
|
||||||
unit ? E('span', { 'class': 'fancontrol-metric-unit' }, unit) : ''
|
unit ? E('span', { 'class': 'fancontrol-metric-unit' }, unit) : ''
|
||||||
])
|
]),
|
||||||
|
E('div', { 'class': 'fancontrol-chart-wrap' }, canvas)
|
||||||
];
|
];
|
||||||
|
|
||||||
if (level != null)
|
return {
|
||||||
children.push(E('div', { 'class': 'fancontrol-level', 'aria-hidden': 'true' },
|
node: E('div', { 'class': 'fancontrol-metric' }, children),
|
||||||
E('div', { 'style': 'width:%d%%'.format(level) })));
|
canvas: canvas
|
||||||
|
};
|
||||||
return E('div', { 'class': 'fancontrol-metric' }, children);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusDetail(title, value) {
|
function statusDetail(title, value) {
|
||||||
@@ -58,7 +216,7 @@ function statusDetail(title, value) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderStatus(node, running, status) {
|
function renderStatus(node, running, status, history) {
|
||||||
const temp = Number(status.temperature_mc);
|
const temp = Number(status.temperature_mc);
|
||||||
const pwm = Number(status.pwm);
|
const pwm = Number(status.pwm);
|
||||||
const rpm = Number(status.rpm);
|
const rpm = Number(status.rpm);
|
||||||
@@ -69,6 +227,12 @@ function renderStatus(node, running, status) {
|
|||||||
const fault = status.error
|
const fault = status.error
|
||||||
? E('span', { 'class': 'label warning' }, status.error)
|
? E('span', { 'class': 'label warning' }, status.error)
|
||||||
: E('span', { 'class': 'fancontrol-ok' }, _('None'));
|
: E('span', { 'class': 'fancontrol-ok' }, _('None'));
|
||||||
|
const temperatureMetric = statusMetric(_('Temperature'),
|
||||||
|
tempValid ? '%.1f'.format(temp / 1000) : '-', tempValid ? '°C' : '', 'temperature');
|
||||||
|
const pwmMetric = statusMetric(_('PWM output'), pwmValid ? String(pwm) : '-',
|
||||||
|
pwmValid ? '/ 255 · %d%%'.format(pwmPercent) : '', 'pwm');
|
||||||
|
const rpmMetric = statusMetric(_('Fan speed'), rpmValid ? String(rpm) : '-',
|
||||||
|
rpmValid ? 'RPM' : '', 'rpm');
|
||||||
|
|
||||||
dom.content(node, E('div', { 'class': 'fancontrol-status' }, [
|
dom.content(node, E('div', { 'class': 'fancontrol-status' }, [
|
||||||
E('div', { 'class': 'fancontrol-status-head' }, [
|
E('div', { 'class': 'fancontrol-status-head' }, [
|
||||||
@@ -77,11 +241,9 @@ function renderStatus(node, running, status) {
|
|||||||
running ? _('Running') : _('Stopped'))
|
running ? _('Running') : _('Stopped'))
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'fancontrol-metrics' }, [
|
E('div', { 'class': 'fancontrol-metrics' }, [
|
||||||
statusMetric(_('Temperature'), tempValid ? '%.1f'.format(temp / 1000) : '-',
|
temperatureMetric.node,
|
||||||
tempValid ? '°C' : ''),
|
pwmMetric.node,
|
||||||
statusMetric(_('PWM output'), pwmValid ? String(pwm) : '-',
|
rpmMetric.node
|
||||||
pwmValid ? '/ 255 · %d%%'.format(pwmPercent) : '', pwmPercent),
|
|
||||||
statusMetric(_('Fan speed'), rpmValid ? String(rpm) : '-', rpmValid ? 'RPM' : '')
|
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'fancontrol-details' }, [
|
E('div', { 'class': 'fancontrol-details' }, [
|
||||||
statusDetail(_('Temperature input'), status.thermal_file || '-'),
|
statusDetail(_('Temperature input'), status.thermal_file || '-'),
|
||||||
@@ -89,6 +251,22 @@ function renderStatus(node, running, status) {
|
|||||||
statusDetail(_('Fault'), fault)
|
statusDetail(_('Fault'), fault)
|
||||||
])
|
])
|
||||||
]));
|
]));
|
||||||
|
|
||||||
|
drawChart(temperatureMetric.canvas, history, 'temperature', {
|
||||||
|
minimumMax: 100,
|
||||||
|
step: 10,
|
||||||
|
format: function(value) { return '%d°'.format(value); }
|
||||||
|
});
|
||||||
|
drawChart(pwmMetric.canvas, history, 'pwm', {
|
||||||
|
minimumMax: 100,
|
||||||
|
step: 100,
|
||||||
|
format: function(value) { return '%d%%'.format(value); }
|
||||||
|
});
|
||||||
|
drawChart(rpmMetric.canvas, history, 'rpm', {
|
||||||
|
minimumMax: 1000,
|
||||||
|
step: 500,
|
||||||
|
format: function(value) { return String(value); }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateAutoPath(sectionId, value) {
|
function validateAutoPath(sectionId, value) {
|
||||||
@@ -114,6 +292,7 @@ return view.extend({
|
|||||||
s.anonymous = true;
|
s.anonymous = true;
|
||||||
s.addremove = false;
|
s.addremove = false;
|
||||||
s.render = function() {
|
s.render = function() {
|
||||||
|
const history = [];
|
||||||
const node = E('div', { 'class': 'cbi-section fancontrol-runtime' }, [
|
const node = E('div', { 'class': 'cbi-section fancontrol-runtime' }, [
|
||||||
E('link', {
|
E('link', {
|
||||||
'rel': 'stylesheet',
|
'rel': 'stylesheet',
|
||||||
@@ -128,7 +307,11 @@ return view.extend({
|
|||||||
L.resolveDefault(callServiceList('fancontrol'), {}),
|
L.resolveDefault(callServiceList('fancontrol'), {}),
|
||||||
L.resolveDefault(fs.read_direct('/var/run/fancontrol.status'), '')
|
L.resolveDefault(fs.read_direct('/var/run/fancontrol.status'), '')
|
||||||
]).then(function(result) {
|
]).then(function(result) {
|
||||||
renderStatus(statusNode, serviceRunning(result[0]), parseStatus(result[1]));
|
const running = serviceRunning(result[0]);
|
||||||
|
const status = parseStatus(result[1]);
|
||||||
|
|
||||||
|
appendHistory(history, running, status);
|
||||||
|
renderStatus(statusNode, running, status, history);
|
||||||
});
|
});
|
||||||
}, 5);
|
}, 5);
|
||||||
|
|
||||||
|
|||||||
@@ -890,14 +890,6 @@ return view.extend({
|
|||||||
so.depends('type', 'zerotier');
|
so.depends('type', 'zerotier');
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.ListValue, 'zerotier_ipstack', _('IP stack'));
|
|
||||||
so.value('', _('Keep default'));
|
|
||||||
so.value('auto', _('Auto'));
|
|
||||||
so.value('gvisor', _('gVisor'));
|
|
||||||
so.value('mips', _('mihomo IP stack (MIPS)'));
|
|
||||||
so.depends('type', 'zerotier');
|
|
||||||
so.modalonly = true;
|
|
||||||
|
|
||||||
/* WireGuard fields */
|
/* WireGuard fields */
|
||||||
so = ss.taboption('field_general', hm.GenValue, 'wireguard_private_key', _('Private key'),
|
so = ss.taboption('field_general', hm.GenValue, 'wireguard_private_key', _('Private key'),
|
||||||
_('WireGuard requires base64-encoded private keys.'));
|
_('WireGuard requires base64-encoded private keys.'));
|
||||||
@@ -1048,6 +1040,14 @@ return view.extend({
|
|||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
/* Extra fields */
|
/* Extra fields */
|
||||||
|
so = ss.taboption('field_general', form.ListValue, 'ipstack', _('IP stack'));
|
||||||
|
so.value('', _('Keep default'));
|
||||||
|
so.value('auto', _('Auto'));
|
||||||
|
so.value('gvisor', _('gVisor'));
|
||||||
|
so.value('mips', _('mihomo IP stack (MIPS)'));
|
||||||
|
so.depends({type: /^(zerotier|wireguard|masque|openvpn)$/});
|
||||||
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.ListValue, 'congestion_controller', _('Congestion controller'));
|
so = ss.taboption('field_general', form.ListValue, 'congestion_controller', _('Congestion controller'));
|
||||||
so.default = hm.congestion_controller[0][0];
|
so.default = hm.congestion_controller[0][0];
|
||||||
hm.congestion_controller.forEach((res) => {
|
hm.congestion_controller.forEach((res) => {
|
||||||
@@ -1055,7 +1055,7 @@ return view.extend({
|
|||||||
})
|
})
|
||||||
so.depends({type: /^(tuic|shadowquic|trusttunnel)$/});
|
so.depends({type: /^(tuic|shadowquic|trusttunnel)$/});
|
||||||
so.depends({type: 'masque', masque_network: /^(|h3-l4proxy)$/});
|
so.depends({type: 'masque', masque_network: /^(|h3-l4proxy)$/});
|
||||||
so.depends({type: 'zerotier', zerotier_ipstack: /^(auto|mips)$/}); // not empty not gvisor
|
so.depends({ipstack: /^(auto|mips)$/}); // not empty not gvisor
|
||||||
so.modalonly = true;
|
so.modalonly = true;
|
||||||
|
|
||||||
so = ss.taboption('field_general', form.ListValue, 'bbr_profile', _('BBR profile'));
|
so = ss.taboption('field_general', form.ListValue, 'bbr_profile', _('BBR profile'));
|
||||||
|
|||||||
@@ -163,6 +163,14 @@ migrate() {
|
|||||||
config_list_foreach "$1" masque_dns add_list "$CONF" "$1" endpoint_dns
|
config_list_foreach "$1" masque_dns add_list "$CONF" "$1" endpoint_dns
|
||||||
uci_remove "$CONF" "$1" masque_dns
|
uci_remove "$CONF" "$1" masque_dns
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# zerotier_ipstack -> ipstack
|
||||||
|
if isDefined "$1" zerotier_ipstack; then
|
||||||
|
local zerotier_ipstack
|
||||||
|
config_get zerotier_ipstack "$1" zerotier_ipstack ""
|
||||||
|
uci_remove "$CONF" "$1" zerotier_ipstack
|
||||||
|
uci_set "$CONF" "$1" ipstack "$zerotier_ipstack"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
config_foreach migrate node
|
config_foreach migrate node
|
||||||
|
|||||||
@@ -652,10 +652,6 @@ uci.foreach(uciconf, ucinode, (cfg) => {
|
|||||||
return orbits;
|
return orbits;
|
||||||
})[0]
|
})[0]
|
||||||
}),
|
}),
|
||||||
"ip-stack": cfg.zerotier_ipstack ? {
|
|
||||||
mode: cfg.zerotier_ipstack,
|
|
||||||
"congestion-controller": replace(cfg.congestion_controller, 'new_reno', 'reno')
|
|
||||||
} : null,
|
|
||||||
|
|
||||||
/* WireGuard */
|
/* WireGuard */
|
||||||
"pre-shared-key": cfg.wireguard_pre_shared_key,
|
"pre-shared-key": cfg.wireguard_pre_shared_key,
|
||||||
@@ -680,10 +676,14 @@ uci.foreach(uciconf, ucinode, (cfg) => {
|
|||||||
"host-key": cfg.ssh_host_key,
|
"host-key": cfg.ssh_host_key,
|
||||||
|
|
||||||
/* Extra fields */
|
/* Extra fields */
|
||||||
|
"ip-stack": cfg.ipstack ? {
|
||||||
|
mode: cfg.ipstack,
|
||||||
|
"congestion-controller": replace(cfg.congestion_controller, 'new_reno', 'reno')
|
||||||
|
} : null,
|
||||||
"udp-over-stream": strToBool(cfg.shadowquic_udp_over_stream || cfg.tuic_udp_over_stream),
|
"udp-over-stream": strToBool(cfg.shadowquic_udp_over_stream || cfg.tuic_udp_over_stream),
|
||||||
"heartbeat-interval": strToInt(cfg.tuic_heartbeat) || null,
|
"heartbeat-interval": strToInt(cfg.tuic_heartbeat) || null,
|
||||||
"keep-alive-interval": strToInt(cfg.shadowquic_heartbeat) || null,
|
"keep-alive-interval": strToInt(cfg.shadowquic_heartbeat) || null,
|
||||||
"congestion-controller": cfg.type in ['zerotier'] ? null : cfg.congestion_controller,
|
"congestion-controller": cfg.type in ['zerotier', 'wireguard', 'masque', 'openvpn'] ? null : cfg.congestion_controller,
|
||||||
"bbr-profile": cfg.bbr_profile,
|
"bbr-profile": cfg.bbr_profile,
|
||||||
"max-open-streams": strToInt(cfg.max_open_streams) || null,
|
"max-open-streams": strToInt(cfg.max_open_streams) || null,
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CS
|
|||||||
LUCI_DEPENDS:=+luci-base
|
LUCI_DEPENDS:=+luci-base
|
||||||
|
|
||||||
PKG_VERSION:=1.1.13
|
PKG_VERSION:=1.1.13
|
||||||
PKG_RELEASE:=64
|
PKG_RELEASE:=65
|
||||||
PKG_LICENSE:=Apache-2.0
|
PKG_LICENSE:=Apache-2.0
|
||||||
|
|
||||||
LUCI_MINIFY_CSS:=
|
LUCI_MINIFY_CSS:=
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ wget -qO- https://openwrt.eamonxg.fun/install.sh | sh
|
|||||||
|
|
||||||
That is the whole installation — the script adds the feed and installs whatever you tick from the list it shows. Upgrade later with the usual commands: `apk update && apk upgrade luci-theme-aurora`, or `opkg update && opkg upgrade luci-theme-aurora`. Details: [openwrt.eamonxg.fun](https://openwrt.eamonxg.fun/).
|
That is the whole installation — the script adds the feed and installs whatever you tick from the list it shows. Upgrade later with the usual commands: `apk update && apk upgrade luci-theme-aurora`, or `opkg update && opkg upgrade luci-theme-aurora`. Details: [openwrt.eamonxg.fun](https://openwrt.eamonxg.fun/).
|
||||||
|
|
||||||
|
> **apk**: if this package was previously installed from a downloaded `.apk` file, that pinned it in `/etc/apk/world` and `apk upgrade` will silently do nothing — success reported, version unchanged. Run `apk add luci-theme-aurora` once (the name, no path) to clear the pin. The script above already does this for you.
|
||||||
|
|
||||||
### Via a GitHub release
|
### Via a GitHub release
|
||||||
|
|
||||||
OpenWrt 25.12+ and snapshots use `apk`; other versions use `opkg`:
|
OpenWrt 25.12+ and snapshots use `apk`; other versions use `opkg`:
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ wget -qO- https://openwrt.eamonxg.fun/install.sh | sh
|
|||||||
|
|
||||||
这就是全部安装步骤——脚本会添加软件源,并安装您从列表中勾选的软件包。之后用常规命令升级即可:`apk update && apk upgrade luci-theme-aurora`,或 `opkg update && opkg upgrade luci-theme-aurora`。详细信息见 [openwrt.eamonxg.fun](https://openwrt.eamonxg.fun/)。
|
这就是全部安装步骤——脚本会添加软件源,并安装您从列表中勾选的软件包。之后用常规命令升级即可:`apk update && apk upgrade luci-theme-aurora`,或 `opkg update && opkg upgrade luci-theme-aurora`。详细信息见 [openwrt.eamonxg.fun](https://openwrt.eamonxg.fun/)。
|
||||||
|
|
||||||
|
> **apk**:若此前是从下载的 `.apk` 文件安装的,该文件会把软件包钉死在 `/etc/apk/world` 里,此后 `apk upgrade` 会静默地什么都不做——报告成功,版本却没变。执行一次 `apk add luci-theme-aurora`(只写包名,不带路径)即可解除。上面的脚本已自动处理这一步。
|
||||||
|
|
||||||
### 通过 GitHub Release
|
### 通过 GitHub Release
|
||||||
|
|
||||||
OpenWrt 25.12+ 和 Snapshot 版本使用 `apk`;其他版本使用 `opkg`:
|
OpenWrt 25.12+ 和 Snapshot 版本使用 `apk`;其他版本使用 `opkg`:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
|
|||||||
FOOTSTRAP_VERSION?=
|
FOOTSTRAP_VERSION?=
|
||||||
ifneq ($(FOOTSTRAP_VERSION),)
|
ifneq ($(FOOTSTRAP_VERSION),)
|
||||||
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
||||||
PKG_RELEASE:=5
|
PKG_RELEASE:=6
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LUCI_TITLE:=Footstrap Theme
|
LUCI_TITLE:=Footstrap Theme
|
||||||
|
|||||||
@@ -519,10 +519,14 @@ function silence(el) {
|
|||||||
* A foreign sheet is injected by ONE page and has no business painting any other. Before this, an
|
* A foreign sheet is injected by ONE page and has no business painting any other. Before this, an
|
||||||
* invasive sheet made the whole document spent (documentPoisoned) and the SPA fell back to a full
|
* invasive sheet made the whole document spent (documentPoisoned) and the SPA fell back to a full
|
||||||
* load on the way OUT — correct, and paid by ORDINARY pages: `luci-app-filemanager`, a stock app,
|
* load on the way OUT — correct, and paid by ORDINARY pages: `luci-app-filemanager`, a stock app,
|
||||||
* calls its `insertCss()` at MODULE EVAL and lands two <style>s in <head> (its own rules plus the
|
* lands TWO <style>s in <head>. Its HexEditor module calls `injectHexEditorCSS()` at MODULE EVAL
|
||||||
* HexEditor's), and `luci-app-ssclash` adds four more as the Ace editor initialises. Both carry
|
* and the view's own `render()` calls `insertCss()` on every arrival; both are invasive on their
|
||||||
* real properties on bare selectors (`.hexview:focus`, `.ace_gutter`), which is invasive by the
|
* BARE selectors, not on the ones pinned to `#file-manager-container` — HexEditor declares
|
||||||
* only definition that also catches `[class] { padding: 0 !important }`.
|
* `:root { --span-spacing; --clr-background; … }`, the view adds `:root`, `.cbi-page-actions`,
|
||||||
|
* `.cbi-button-save:not(.custom-save-button)` and a `td:last-child` riding as the second half of
|
||||||
|
* `#file-manager-container th:last-child, td:last-child`. `luci-app-ssclash` adds four more as the
|
||||||
|
* Ace editor initialises. Invasive by the only definition that also catches
|
||||||
|
* `[class] { padding: 0 !important }`.
|
||||||
*
|
*
|
||||||
* Measured on owrt2512, 25.12.4, with ownership taken out of documentPoisoned() and put back:
|
* Measured on owrt2512, 25.12.4, with ownership taken out of documentPoisoned() and put back:
|
||||||
* leaving either page is a FULL LOAD, 5 runs of 5, and with ownership all 5 are in place —
|
* leaving either page is a FULL LOAD, 5 runs of 5, and with ownership all 5 are in place —
|
||||||
@@ -531,9 +535,13 @@ function silence(el) {
|
|||||||
* 24.10/25.12 — the realtime graphs style their SVG text with an inline `style=` attribute — so
|
* 24.10/25.12 — the realtime graphs style their SVG text with an inline `style=` attribute — so
|
||||||
* what this costs a router is decided entirely by which apps are installed on it.
|
* what this costs a router is decided entirely by which apps are installed on it.
|
||||||
*
|
*
|
||||||
* Removing the sheet on the way out is NOT the fix, and it is the obvious one: the append is at
|
* Removing the sheet on the way out is NOT the fix, and it is the obvious one: an append at MODULE
|
||||||
* module top level, `L.require` caches the module, so a second visit re-runs nothing and the page
|
* TOP LEVEL happens once, because `L.require` caches the module, so a second visit re-runs nothing
|
||||||
* would render unstyled. Disabling is reversible, which is the whole difference.
|
* and the page renders unstyled — HexEditor's injector above is exactly that shape, and its own
|
||||||
|
* `getElementById('hexeditor-styles')` guard never gets a second chance to notice the element is
|
||||||
|
* gone, because nothing calls it again. A sheet injected from
|
||||||
|
* `render()` would survive removal, but the two cases are indistinguishable from here and only one
|
||||||
|
* mechanism can be right for both. Disabling is reversible, which is the whole difference.
|
||||||
*
|
*
|
||||||
* OWNER = body[data-page] when the sheet was re-hosted. The order that makes this sound is in
|
* OWNER = body[data-page] when the sheet was re-hosted. The order that makes this sound is in
|
||||||
* fs-router.js: it stamps data-page (line ~371) BEFORE require()ing the view class (~450), so at the
|
* fs-router.js: it stamps data-page (line ~371) BEFORE require()ing the view class (~450), so at the
|
||||||
|
|||||||
@@ -101,8 +101,9 @@ mkdir -p /etc/footstrap 2>/dev/null
|
|||||||
# response by extension, and an SVG served as application/octet-stream is one no browser will
|
# response by extension, and an SVG served as application/octet-stream is one no browser will
|
||||||
# paint — as a CSS mask, as a background image or at all.
|
# paint — as a CSS mask, as a background image or at all.
|
||||||
[ -d /www/luci-static/footstrap ] && ln -sf /etc/footstrap/pattern.svg /www/luci-static/footstrap/pattern.svg 2>/dev/null
|
[ -d /www/luci-static/footstrap ] && ln -sf /etc/footstrap/pattern.svg /www/luci-static/footstrap/pattern.svg 2>/dev/null
|
||||||
# The admin-installed webfonts (fonts/set-font.sh) are a whole DIRECTORY rather than one file: the
|
# The admin-installed webfonts (/etc/footstrap/fonts, see partials/head.ut) are a whole DIRECTORY
|
||||||
# faces plus the @font-face sheet generated next to them. Same arrangement as the two images above,
|
# rather than one file: the faces plus an @font-face sheet beside them. Same arrangement as the two
|
||||||
|
# images above,
|
||||||
# and linking a directory has TWO traps, both of which end with `ln` exiting 0 and the link sitting
|
# and linking a directory has TWO traps, both of which end with `ln` exiting 0 and the link sitting
|
||||||
# one level too deep, at .../fonts/fonts — where no browser looks, so the fonts silently do not
|
# one level too deep, at .../fonts/fonts — where no browser looks, so the fonts silently do not
|
||||||
# serve and nothing reports a thing:
|
# serve and nothing reports a thing:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Preserve what the ADMIN put on this router across sysupgrade: the login/page background
|
# Preserve what the ADMIN put on this router across sysupgrade: the login/page background
|
||||||
# (Appearance -> Wallpaper -> File), the wallpaper pattern (Appearance -> Wallpaper -> Pattern) and
|
# (Appearance -> Wallpaper -> File), the wallpaper pattern (Appearance -> Wallpaper -> Pattern) and
|
||||||
# the webfonts installed by fonts/set-font.sh. All three are written at runtime — an upload or a
|
# the webfonts the admin put in /etc/footstrap/fonts. All three are written at runtime — an upload
|
||||||
# script, never the package — so all three survive a package upgrade on their own; a firmware
|
# or the admin, never the package — so all three survive a package upgrade on their own; a firmware
|
||||||
# sysupgrade, however, only keeps paths listed here (or in a conffile / /etc/sysupgrade.conf).
|
# sysupgrade, however, only keeps paths listed here (or in a conffile / /etc/sysupgrade.conf).
|
||||||
# NOT conffiles: they live outside root/etc/config, and tools/conffiles.mjs would reject a
|
# NOT conffiles: they live outside root/etc/config, and tools/conffiles.mjs would reject a
|
||||||
# conffiles-define entry the package does not ship.
|
# conffiles-define entry the package does not ship.
|
||||||
|
|||||||
@@ -64,8 +64,9 @@
|
|||||||
/* the fonts, same shape and for the same reason: no browser layer, no Appearance control,
|
/* the fonts, same shape and for the same reason: no browser layer, no Appearance control,
|
||||||
no entry in snapshotAxes(). font_sans/font_mono are font-family stacks (head.ut
|
no entry in snapshotAxes(). font_sans/font_mono are font-family stacks (head.ut
|
||||||
whitelists the charset and prints them into a style element); `fonts` is the cache-bust
|
whitelists the charset and prints them into a style element); `fonts` is the cache-bust
|
||||||
token of the generated @font-face sheet, hex like the two above. All three are written by
|
token of the @font-face sheet the admin installs beside the faces, hex like the two above.
|
||||||
fonts/set-font.sh or by hand with `uci set`, never by Save as default. */
|
All three are written with `uci set` — by hand, or by the one-line installer that lives in
|
||||||
|
the theme's own repository and not in this package — never by Save as default. */
|
||||||
'font_sans', 'font_mono', 'fonts'
|
'font_sans', 'font_mono', 'fonts'
|
||||||
];
|
];
|
||||||
const fs_defaults = {};
|
const fs_defaults = {};
|
||||||
|
|||||||
@@ -85,21 +85,42 @@
|
|||||||
styles/02-tokens.css are what everyone gets unless the ROUTER names something else here.
|
styles/02-tokens.css are what everyone gets unless the ROUTER names something else here.
|
||||||
Two independent things, either usable alone:
|
Two independent things, either usable alone:
|
||||||
font_sans / font_mono a font-family stack, printed into a style element further down.
|
font_sans / font_mono a font-family stack, printed into a style element further down.
|
||||||
fonts the cache-bust token of the @font-face sheet fonts/set-font.sh
|
fonts the cache-bust token (an md5) of an @font-face sheet at
|
||||||
generates when an admin also installs the FILE. Hex, exactly like
|
/etc/footstrap/fonts/fonts.css, for when the admin installs the
|
||||||
login_bg and pattern, and for the same reason.
|
FILES too. Hex, exactly like login_bg and pattern, and for the same
|
||||||
|
reason.
|
||||||
|
THE PACKAGE IS THE SERVING HALF ONLY. It reads these three options, serves whatever is in
|
||||||
|
/etc/footstrap/fonts (uci-defaults links it under /www) and keeps that directory across a
|
||||||
|
sysupgrade — it installs no faces and fetches nothing at run time, which is the rule the
|
||||||
|
whole theme is built on. Putting the files there is the admin's move: drop the .woff2 files
|
||||||
|
and a fonts.css beside them, then `uci set footstrap.settings.fonts=<md5 of fonts.css>`.
|
||||||
|
`fonts/set-font.sh` in the theme's OWN repository does exactly that in one line and is NOT
|
||||||
|
part of this package — it names third-party font hosts, which is precisely what a theme in
|
||||||
|
this tree must not carry.
|
||||||
Neither is an axis: there is no browser layer, no Appearance control and no entry in
|
Neither is an axis: there is no browser layer, no Appearance control and no entry in
|
||||||
snapshotAxes() — a font is a router-wide decision here, not a per-visitor one.
|
snapshotAxes() — a font is a router-wide decision here, not a per-visitor one.
|
||||||
|
|
||||||
THE CHARSET IS THE WHOLE DEFENCE. A family stack is free text and it is printed UNESCAPED,
|
THE CHARSET IS MOST OF THE DEFENCE. A family stack is free text and it is printed UNESCAPED,
|
||||||
so the whitelist has to exclude everything that could end the declaration or the element:
|
so the whitelist has to exclude everything that could end the declaration or the element:
|
||||||
< > ( ) { } ; and the backslash are all absent, which leaves no way to close a style element,
|
< > ( ) { } ; and the backslash are all absent, which leaves no way to close a style element,
|
||||||
start a second declaration or write a url(). Anything else becomes '' = "no router font".
|
start a second declaration or write a url(). Anything else becomes '' = "no router font".
|
||||||
A Cyrillic or CJK family name is refused by this too — those faces carry a Latin name as well,
|
A Cyrillic or CJK family name is refused by this too — those faces carry a Latin name as well,
|
||||||
and fonts/README.md says to use it. */
|
and the tool's README says to use it.
|
||||||
|
|
||||||
|
THE QUOTE IS THE ONE CHARACTER THE CHARSET CANNOT REFUSE, and it does not end anything — it
|
||||||
|
EXTENDS. Both quotes have to be in the class (the shipped default is `"Manrope",system-ui,…`),
|
||||||
|
and a value with an ODD count of either opens a CSS string that runs to the end of the sheet:
|
||||||
|
`font_sans=My"Font` swallows `; --fs-font-mono: …; }` with it, so a typo in the sans stack
|
||||||
|
silently costs the MONO one on a router that set that one correctly. Two options this file
|
||||||
|
calls independent, and one takes the other down. So count them and refuse the odd case: n
|
||||||
|
quotes split a string into n+1 parts, hence the even-length test. */
|
||||||
function _sd_family(v) {
|
function _sd_family(v) {
|
||||||
return (type(v) == 'string' && length(v) >= 1 && length(v) <= 120 &&
|
if (!(type(v) == 'string' && length(v) >= 1 && length(v) <= 120 &&
|
||||||
match(v, /^[A-Za-z0-9 ,._"'-]+$/)) ? v : '';
|
match(v, /^[A-Za-z0-9 ,._"'-]+$/)))
|
||||||
|
return '';
|
||||||
|
if ((length(split(v, '"')) % 2) == 0 || (length(split(v, "'")) % 2) == 0)
|
||||||
|
return '';
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
let _sd_fsans = _sd_family(fs_defaults.font_sans);
|
let _sd_fsans = _sd_family(fs_defaults.font_sans);
|
||||||
let _sd_fmono = _sd_family(fs_defaults.font_mono);
|
let _sd_fmono = _sd_family(fs_defaults.font_mono);
|
||||||
@@ -354,13 +375,14 @@
|
|||||||
no webfonts: --fs-font-sans and --fs-font-mono name Manrope and JetBrains Mono FIRST and
|
no webfonts: --fs-font-sans and --fs-font-mono name Manrope and JetBrains Mono FIRST and
|
||||||
the system stack after, so a browser that has either installed locally uses it and one
|
the system stack after, so a browser that has either installed locally uses it and one
|
||||||
that does not falls through silently — no request, no 404, no flash. What follows the
|
that does not falls through silently — no request, no 404, no flash. What follows the
|
||||||
stylesheet below is the OPT-IN: a router whose admin ran fonts/set-font.sh.
|
stylesheet below is the OPT-IN: a router whose admin installed faces of their own under
|
||||||
|
/etc/footstrap/fonts (see the option block at the top of this file).
|
||||||
|
|
||||||
The preload pair is why both tags down there are written the way they are. A preload is
|
The preload pair is why both tags down there are written the way they are. A preload is
|
||||||
not in the stylesheet, so deleting the @font-face rules alone left these two behind and
|
not in the stylesheet, so deleting the @font-face rules alone left these two behind and
|
||||||
every page went on asking the router for files that were gone: SIX 404s per page, and
|
every page went on asking the router for files that were gone: SIX 404s per page, and
|
||||||
nothing in CSS could have told anyone. So the sheet link is emitted only when the token
|
nothing in CSS could have told anyone. So the sheet link is emitted only when the TOKEN
|
||||||
says the sheet exists, and the token is written by set-font.sh only after the file is. #}
|
says the sheet exists — set the option after the file, never before. #}
|
||||||
{# `data-fs-shell` marks the SHELL's own CSS: on every nav the SPA router asks whether a
|
{# `data-fs-shell` marks the SHELL's own CSS: on every nav the SPA router asks whether a
|
||||||
VIEW put CSS in the document, and this is how it tells its own sheet from an app's. #}
|
VIEW put CSS in the document, and this is how it tells its own sheet from an app's. #}
|
||||||
<link rel="stylesheet" data-fs-shell href="{{ media }}/cascade.css?v={{ pkgs_update_time }}">
|
<link rel="stylesheet" data-fs-shell href="{{ media }}/cascade.css?v={{ pkgs_update_time }}">
|
||||||
|
|||||||
+2
-2
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
|
|||||||
|
|
||||||
PKG_NAME:=natflow
|
PKG_NAME:=natflow
|
||||||
PKG_VERSION:=20260531
|
PKG_VERSION:=20260531
|
||||||
PKG_RELEASE:=59
|
PKG_RELEASE:=60
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
||||||
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_VERSION:=9061fb3c2974689a4dfd118f607a400d47d6b16f
|
PKG_SOURCE_VERSION:=c4c0370953045bac23728fbeff456fc584cd9c80
|
||||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||||
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
||||||
PKG_LICENSE:=GPL-2.0
|
PKG_LICENSE:=GPL-2.0
|
||||||
|
|||||||
Reference in new issue
Block a user