🎁 Sync 2026-08-05 20:25:34

This commit is contained in:
github-actions[bot] committed 2026-08-05 20:25:34 +08:00
1 parent e71d910024
commit ca3fdb1643
31 files changed
+374 -113

No files matched your search

+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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

+1 -1
View File
@@ -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
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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:=
+2
View File
@@ -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`:
+2
View File
@@ -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`:
+1 -1
View File
@@ -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
View File
@@ -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