🐶 Sync 2026-03-30 14:49:16

This commit is contained in:
github-actions[bot] 2026-03-30 14:49:17 +08:00
parent f00793a73b
commit f28e441b8e
9 changed files with 110 additions and 187 deletions

View File

@ -1,5 +1,4 @@
# Copyright (C) 2016 Openwrt.org
# Copyright (C) 2024 iv7777 <hongba@rocketmail.com>
# Copyright (C) 2025 Openwrt.org
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
@ -7,15 +6,14 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for Timewol
LUCI_PKGARCH:=all
LUCI_DEPENDS:=+etherwake
PKG_VERSION:=20250407
PKG_RELEASE:=2
define Package/luci-app-timewol/postinst
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
chmod +x /etc/init.d/timewol
fi
exit 0
LUCI_DEPENDS:=+luci-base +etherwake
LUCI_PKGARCH:=all
define Package/luci-app-timewol/conffiles
/etc/config/timewol
endef
include $(TOPDIR)/feeds/luci/luci.mk

View File

@ -1,46 +1,21 @@
module("luci.controller.timewol", package.seeall)
local x = luci.model.uci.cursor()
function index()
if not nixio.fs.access("/etc/config/timewol") then return end
if not nixio.fs.access("/etc/config/timewol") then return end
entry({"admin", "control"}, firstchild(), "Control", 44).dependent = false
local page = entry({"admin", "control", "timewol"}, cbi("timewol"), _("Timed WOL"))
entry({"admin", "control"}, firstchild(), "Control", 44).dependent = false
local page = entry({"admin", "control", "timewol"}, cbi("timewol"), _("Timed WOL"))
page.order = 95
page.dependent = true
page.acl_depends = { "luci-app-timewol" }
entry({"admin", "control", "timewol", "status"}, call("status")).leaf = true
entry( {"admin", "control", "timewol", "awake"}, call("awake") ).leaf = true
entry({"admin", "control", "timewol", "status"}, call("status")).leaf = true
end
function status()
local e = {}
e.status = luci.sys.call("cat /etc/crontabs/root |grep etherwake >/dev/null") == 0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function awake(sections)
lan = x:get("timewol",sections,"maceth")
mac = x:get("timewol",sections,"macaddr")
local e = {}
cmd = "/usr/bin/etherwake -D -i " .. lan .. " -b " .. mac .. " 2>&1"
local p = io.popen(cmd)
local msg = ""
if p then
while true do
local l = p:read("*l")
if l then
if #l > 100 then l = l:sub(1, 100) .. "..." end
msg = msg .. l
else
break
end
end
p:close()
end
e["data"] = msg
luci.http.prepare_content("application/json")
luci.http.write_json(e)
local e = {}
e.status = luci.sys.call("cat /etc/crontabs/root | grep -v '^[ \t]*#' | grep etherwake >/dev/null") == 0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end

View File

@ -1,93 +1,90 @@
local sys = require "luci.sys"
local m, s, o
-- Create the main map object
local map = Map("timewol", translate("Timed Wake on LAN"),
translate("Wake up your local area network devices on schedule"))
map.template = "timewol/index"
m = Map("timewol", translate("Timed Wake on LAN"),
translate("Wake up your local area network devices on schedule"))
m.template = "timewol/index"
-- Running Status Section
local status_section = map:section(TypedSection, "basic", translate("Running Status"))
status_section.anonymous = true
s = m:section(TypedSection, "basic", translate("Running Status"))
s.anonymous = true
local status = status_section:option(DummyValue, "timewol_status", translate("Current Status"))
status.template = "timewol/timewol"
status.value = translate("Collecting data...")
o = s:option(DummyValue, "timewol_status", translate("Current Status"))
o.template = "timewol/timewol"
o.value = translate("Collecting data...")
-- Basic Settings Section
local basic_section = map:section(TypedSection, "basic", translate("Basic Settings"))
basic_section.anonymous = true
s = m:section(TypedSection, "basic", translate("Basic Settings"))
s.anonymous = true
local enable = basic_section:option(Flag, "enable", translate("Enable"))
enable.rmempty = false
o = s:option(Flag, "enable", translate("Enable"))
o.rmempty = false
-- Client Settings Section
local client_section = map:section(TypedSection, "macclient", translate("Client Settings"))
client_section.template = "cbi/tblsection"
client_section.anonymous = true
client_section.addremove = true
s = m:section(TypedSection, "macclient", translate("Client Settings"))
s.template = "cbi/tblsection"
s.anonymous = true
s.addremove = true
o = s:option(Flag, "enable", translate("Enable"))
o.default = 1
o.rmempty = false
-- Client MAC Address
local mac_addr = client_section:option(Value, "macaddr", translate("Client MAC"))
mac_addr.rmempty = false
o = s:option(Value, "macaddr", translate("Client MAC"))
o.rmempty = false
sys.net.mac_hints(function(mac, hint)
mac_addr:value(mac, string.format("%s (%s)", mac, hint))
o:value(mac, string.format("%s (%s)", mac, hint))
end)
-- Network Interface
local net_iface = client_section:option(Value, "maceth", translate("Network Interface"))
net_iface.rmempty = false
net_iface.default = "br-lan"
o = s:option(Value, "maceth", translate("Network Interface"))
o.rmempty = false
o.default = "br-lan"
for _, device in ipairs(sys.net.devices()) do
if device ~= "lo" then
net_iface:value(device)
end
if device ~= "lo" then
o:value(device)
end
end
-- wake device
local btn = client_section:option(Button, "_awake",translate("Wake Up Host"))
btn.inputtitle = translate("Awake")
btn.inputstyle = "apply"
btn.disabled = false
btn.template = "timewol/awake"
-- Function to validate cron field values
local function validate_cron_field(option_name, value, min, max, default)
if value == "" then
return default
elseif value == "*" then
return value
end
local num = tonumber(value)
if num and num >= min and num <= max then
return value
else
return nil, translatef("Invalid value for %s: %s. Must be between %d and %d or '*'", option_name, value, min, max)
end
if value == "" then
return default
elseif value == "*" then
return value
end
local num = tonumber(value)
if num and num >= min and num <= max then
return value
else
return nil, translatef("Invalid value for %s: %s. Must be between %d and %d or '*'", option_name, value, min, max)
end
end
-- Scheduling Options with Default Values and Range Checks
local schedule_options = {
{ "minute", translate("Minute"), 0, 59, "0" },
{ "hour", translate("Hour"), 0, 23, "0" },
{ "day", translate("Day"), 1, 31, "*" },
{ "month", translate("Month"), 1, 12, "*" },
{ "weeks", translate("Week"), 0, 6, "*" } -- 0 for Sunday, 6 for Saturday
{ "minute", translate("Minute"), 0, 59, "0" },
{ "hour", translate("Hour"), 0, 23, "0" },
{ "day", translate("Day"), 1, 31, "*" },
{ "month", translate("Month"), 1, 12, "*" },
{ "weeks", translate("Week"), 0, 6, "*" } -- 0 for Sunday, 6 for Saturday
}
for _, opt in ipairs(schedule_options) do
local field = client_section:option(Value, opt[1], opt[2])
field.default = opt[5] or opt[4] -- Use default value if present, otherwise use maximum value
field.optional = false
field.validate = function(self, value)
return validate_cron_field(opt[2], value, opt[3], opt[4], field.default)
end
o = s:option(Value, opt[1], opt[2])
o.default = opt[5] or opt[4] -- Use default value if present, otherwise use maximum value
o.optional = false
o.validate = function(self, value)
return validate_cron_field(opt[2], value, opt[3], opt[4], o.default)
end
end
-- Apply the configuration changes
map.apply_on_parse = true
function map.on_apply(self)
sys.exec("/etc/init.d/timewol restart")
m.apply_on_parse = true
function m.on_apply(self)
sys.exec("/etc/init.d/timewol restart")
end
return map
return m

View File

@ -1,3 +0,0 @@
<%+cbi/valueheader%>
<input class="cbi-button cbi-input-<%=self.inputstyle or "button" %>" style="font-size: 100%;" type="button" onclick="onclick_awake(this.id)" <%=attr("name", section) .. attr("id", cbid) .. attr("value", self.inputtitle)%> />
<%+cbi/valuefooter%>

View File

@ -14,23 +14,5 @@
status.innerHTML = result.status?'<%=translate("RUNNING")%>':'<%=translate("NOT RUNNING")%>';
}
)
function _id2section(id) {
var x = id.split(".");
return x[2];
}
function onclick_awake(id) {
var section = _id2section(id);
var btnXHR = new XHR();
btnXHR.post('<%=url([[admin]], [[control]], [[timewol]], [[awake]])%>/' + section, { token: '<%=token%>' },
function(x, data) {
if (x.responseText == "_uncommitted_") {
txt="<%:Please [Save & Apply] your changes first%>";
alert( txt.replace(new RegExp("<%:&%>", "g"), "&") );
} else {
alert( JSON.parse(x.response).data );
}
}
);
}
//]]>
</script>

View File

@ -1 +0,0 @@
zh_Hans

View File

@ -34,9 +34,6 @@ msgstr "客户端MAC"
msgid "Network Interface"
msgstr "网络接口"
msgid "Awake"
msgstr "立即唤醒"
msgid "Minute"
msgstr "分钟"

1
luci-app-timewol/po/zh_Hans Symbolic link
View File

@ -0,0 +1 @@
zh-cn

View File

@ -1,8 +1,6 @@
#!/bin/sh /etc/rc.common
#
# Copyright (C) 2015 OpenWrt-dist
# Copyright (C) 2016 fw867 <ffkykzs@gmail.com>
# Copyright (C) 2024 iv7777 <hongba@rocketmail.com>
# Copyright (C) 2025 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
@ -13,75 +11,54 @@ CONFIG=timewol
CRONTAB_FILE="/etc/crontabs/root"
ETHERWAKE_CMD="/usr/bin/etherwake"
# Function to get UCI configuration values with defaults
uci_get_by_type() {
local type=$1
local option=$2
local default=$3
local index=${4:-0} # Use 0 if $4 is not provided
local value
value=$(uci get "$CONFIG.@$type[$index].$option" 2>/dev/null) || value=$default
echo "$value"
config_n_get() {
local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null)
echo "${ret:=$3}"
}
# Function to check if a value represents a true boolean
is_true() {
case "$1" in
1|on|true|yes|enabled) return 0 ;;
*) return 1 ;;
esac
case "$1" in
1|on|true|yes|enabled) return 0 ;;
*) return 1 ;;
esac
}
# Function to load configuration and check if enabled
load_config() {
local enabled
enabled=$(uci_get_by_type basic enable "0")
is_true "$enabled"
local enabled=$(config_n_get @basic[0] enable 0)
is_true "$enabled"
}
# Function to add WoL rules to the crontab
add_rule() {
# Remove existing etherwake entries
sed -i '/etherwake/d' "$CRONTAB_FILE"
sed -i '/etherwake/d' "$CRONTAB_FILE"
for i in $(seq 0 100); do
local macaddr
local maceth
local minute
local hour
local day
local month
local weeks
macaddr=$(uci_get_by_type macclient macaddr "" "$i")
maceth=$(uci_get_by_type macclient maceth "" "$i")
# Stop if no more macaddr entries
[ -z "$macaddr" ] && break
[ -z "$maceth" ] && break
minute=$(uci_get_by_type macclient minute "0" "$i")
hour=$(uci_get_by_type macclient hour "*" "$i")
day=$(uci_get_by_type macclient day "*" "$i")
month=$(uci_get_by_type macclient month "*" "$i")
weeks=$(uci_get_by_type macclient weeks "*" "$i")
echo "$minute $hour $day $month $weeks $ETHERWAKE_CMD -D -i $maceth $macaddr" >> "$CRONTAB_FILE"
done
local ids=$(uci show $CONFIG | grep "=macclient" | awk -F '.' '{print $2}' | awk -F '=' '{print $1}')
[ -n "$ids" ] && {
for id in $ids; do
local enabled=$(config_n_get $id enable 0)
local macaddr=$(config_n_get $id macaddr)
local maceth=$(config_n_get $id maceth)
[ $enabled -eq 1 ] && [ -n "$macaddr" ] && [ -n "$maceth" ] && {
local minute=$(config_n_get $id minute "0")
local hour=$(config_n_get $id hour "*")
local day=$(config_n_get $id day "*")
local month=$(config_n_get $id month "*")
local weeks=$(config_n_get $id weeks "*")
echo "$minute $hour $day $month $weeks $ETHERWAKE_CMD -D -i $maceth $macaddr" >> "$CRONTAB_FILE"
}
done
}
}
# Function to start the service
start() {
if load_config; then
add_rule
else
exit 0
fi
if load_config; then
add_rule
/etc/init.d/cron restart
else
exit 0
fi
}
# Function to stop the service
stop() {
# Remove etherwake entries from crontab
sed -i '/etherwake/d' "$CRONTAB_FILE"
sed -i '/etherwake/d' "$CRONTAB_FILE"
/etc/init.d/cron restart
}