🌴 Sync 2026-03-05 23:51:42

This commit is contained in:
github-actions[bot] committed 2026-03-05 23:51:42 +08:00
1 parent 97c049ed0a
commit 0acff03804
14253 files changed
+3893239

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
# Copyright (C) 2020-2026 Hyy2001X <https://github.com/Hyy2001X>
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-shutdown
PKG_VERSION:=2.0
PKG_RELEASE:=1
PKG_LICENSE:=GPL-3.0-or-later
LUCI_TITLE:=LuCI Support for shutdown/reboot
LUCI_DEPENDS:=+luci-base
LUCI_PKGARCH:=all
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
+3
View File
@@ -0,0 +1,3 @@
## luci-app-shutdown
一键关闭/重启设备
+83
View File
@@ -0,0 +1,83 @@
module("luci.controller.shutdown", package.seeall)
local http = require "luci.http"
local sys = require "luci.sys"
local jsonc = require "luci.jsonc"
local fs = require "nixio.fs"
local function json_ok(tbl)
http.prepare_content("application/json")
http.write_json(tbl or { ok = true })
end
local function json_err(msg, code)
http.status(code or 500, "Error")
http.prepare_content("application/json")
http.write_json({ ok = false, error = msg or "unknown error" })
end
local function poweroff_supported()
-- OpenWrt 常见:/sbin/poweroff(procd)
if fs.access("/sbin/poweroff") then
return true, "found /sbin/poweroff"
end
-- BusyBox applet 兜底(有些系统没有独立 /sbin/poweroff)
local ret = sys.call("/bin/busybox poweroff -h >/dev/null 2>&1")
if ret == 0 then
return true, "busybox poweroff applet"
end
return false, "poweroff command not found"
end
local function get_board()
local out = sys.exec("ubus call system board 2>/dev/null")
local obj = (out and #out > 0) and jsonc.parse(out) or nil
return obj or {}
end
function index()
local page = entry({"admin", "system", "shutdown"}, cbi("shutdown"), _("Shutdown / Reboot"), 90)
page.dependent = true
page.acl_depends = { "luci-app-shutdown" }
-- JSON endpoints
entry({"admin", "system", "shutdown", "status"}, call("action_status")).leaf = true
entry({"admin", "system", "shutdown", "reboot"}, post("action_reboot")).leaf = true
entry({"admin", "system", "shutdown", "poweroff"}, post("action_poweroff")).leaf = true
end
function action_status()
local supported, reason = poweroff_supported()
local board = get_board()
json_ok({
ok = true,
poweroff_supported = supported,
poweroff_reason = reason,
board = {
model = board.model,
system = board.system,
release = board.release,
kernel = board.kernel,
hostname = board.hostname
}
})
end
function action_reboot()
-- 后台执行,先返回 JSON
sys.call("(sleep 1; sync; reboot) >/dev/null 2>&1 &")
json_ok({ ok = true })
end
function action_poweroff()
local supported, reason = poweroff_supported()
if not supported then
return json_err("Poweroff not supported: " .. (reason or ""), 400)
end
sys.call("(sleep 1; sync; poweroff) >/dev/null 2>&1 &")
json_ok({ ok = true })
end
+7
View File
@@ -0,0 +1,7 @@
m = Map("system", translate("Shutdown / Reboot"),
translate("Use the buttons below to reboot or shut down the device."))
s = m:section(SimpleSection)
s.template = "shutdown/actions"
return m
@@ -0,0 +1,115 @@
<div class="cbi-section">
<div class="cbi-section-node">
<div id="shutdown_info" class="alert-message" style="display:none;"></div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Device%></label>
<div class="cbi-value-field">
<strong id="dev_model">-</strong><br />
<small id="dev_release">-</small>
</div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"><%:Actions%></label>
<div class="cbi-value-field">
<button id="btn_reboot" class="cbi-button cbi-button-apply"><%:Reboot%></button>
<button id="btn_poweroff" class="cbi-button cbi-button-reset"><%:Shutdown%></button>
<span id="poweroff_hint" style="margin-left:8px;"></span>
</div>
</div>
</div>
</div>
<script type="text/javascript">
(function() {
var statusUrl = '<%=luci.dispatcher.build_url("admin","system","shutdown","status")%>';
var rebootUrl = '<%=luci.dispatcher.build_url("admin","system","shutdown","reboot")%>';
var poweroffUrl = '<%=luci.dispatcher.build_url("admin","system","shutdown","poweroff")%>';
var infoBox = document.getElementById('shutdown_info');
var btnReboot = document.getElementById('btn_reboot');
var btnPoweroff = document.getElementById('btn_poweroff');
var hint = document.getElementById('poweroff_hint');
function showInfo(msg, isErr) {
infoBox.style.display = '';
infoBox.className = isErr ? 'alert-message error' : 'alert-message success';
infoBox.textContent = msg;
}
function setPoweroffAvailable(ok) {
if (ok) {
btnPoweroff.disabled = false;
hint.textContent = '';
} else {
btnPoweroff.disabled = true;
hint.textContent = '(<%:Poweroff is not supported on this device.%>)';
}
}
function xhrGet(url, cb) {
fetch(url, { method: 'GET', credentials: 'same-origin' })
.then(r => r.json()).then(cb)
.catch(() => cb(null));
}
function xhrPost(url, cb) {
fetch(url, { method: 'POST', credentials: 'same-origin' })
.then(r => r.json()).then(cb)
.catch(() => cb(null));
}
function loadStatus() {
xhrGet(statusUrl, function(data) {
if (!data || !data.ok) {
showInfo('<%:Failed to load status.%>', true);
setPoweroffAvailable(false, 'status unavailable');
return;
}
var board = data.board || {};
document.getElementById('dev_model').textContent =
board.model || board.system || '-';
var rel = board.release || {};
var relTxt = [];
if (rel.distribution) relTxt.push(rel.distribution);
if (rel.version) relTxt.push(rel.version);
if (rel.revision) relTxt.push(rel.revision);
document.getElementById('dev_release').textContent = relTxt.join(' ') || '-';
setPoweroffAvailable(!!data.poweroff_supported);
});
}
btnReboot.addEventListener('click', function() {
if (!confirm('<%:Are you sure you want to reboot the system?%>'))
return;
btnReboot.disabled = true;
xhrPost(rebootUrl, function() {
/* no ui feedback, system will reboot */
});
});
btnPoweroff.addEventListener('click', function() {
if (btnPoweroff.disabled)
return;
if (!confirm('<%:Are you sure you want to shut down the system?%>'))
return;
btnPoweroff.disabled = true;
xhrPost(poweroffUrl, function() {
/* no ui feedback, system will power off */
});
});
loadStatus();
})();
</script>
+35
View File
@@ -0,0 +1,35 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Language: zh-cn\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Shutdown / Reboot"
msgstr "关机 / 重启"
msgid "Use the buttons below to reboot or shut down the device."
msgstr "使用下方按钮执行重启或关机。"
msgid "Device"
msgstr "设备"
msgid "Actions"
msgstr "操作"
msgid "Reboot"
msgstr "重启"
msgid "Shutdown"
msgstr "关机"
msgid "Not supported"
msgstr "不支持"
msgid "Failed to load status."
msgstr "加载状态失败。"
msgid "Are you sure you want to reboot the system?"
msgstr "确认要重启系统吗?"
msgid "Are you sure you want to shut down the system?"
msgstr "确认要关闭系统吗?"
+35
View File
@@ -0,0 +1,35 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Language: zh_Hans\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Shutdown / Reboot"
msgstr "关机 / 重启"
msgid "Use the buttons below to reboot or shut down the device."
msgstr "使用下方按钮执行重启或关机。"
msgid "Device"
msgstr "设备"
msgid "Actions"
msgstr "操作"
msgid "Reboot"
msgstr "重启"
msgid "Shutdown"
msgstr "关机"
msgid "Not supported"
msgstr "不支持"
msgid "Failed to load status."
msgstr "加载状态失败。"
msgid "Are you sure you want to reboot the system?"
msgstr "确认要重启系统吗?"
msgid "Are you sure you want to shut down the system?"
msgstr "确认要关闭系统吗?"
@@ -0,0 +1,12 @@
{
"luci-app-shutdown": {
"description": "Grant access to LuCI shutdown/reboot app",
"read": {
"luci": [ "shutdown" ],
"uci": [ "system" ]
},
"write": {
"luci": [ "shutdown" ]
}
}
}