🛸 Sync 2022-07-15 01:22:56

This commit is contained in:
github-actions[bot] 2022-07-15 01:22:56 +08:00
parent 2cdea085e6
commit 3652b84ffb
37 changed files with 1466 additions and 0 deletions

101
homebox/Makefile Normal file
View File

@ -0,0 +1,101 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=homebox
PKG_VERSION:=0.0.0-dev.2020062901
PKG_RELEASE:=11
PKG_SOURCE_URL_FILE:=v$(PKG_VERSION).tar.gz
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_GIT_URL:=github.com/XGHeaven/homebox
PKG_GIT_REF:=v$(PKG_VERSION)
PKG_SOURCE_URL:=https://codeload.$(PKG_GIT_URL)/tar.gz/$(PKG_GIT_REF)?
PKG_HASH:=skip
PKG_BUILD_DEPENDS:=golang/host homebox/host
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
HOST_BUILD_DIR:=$(BUILD_DIR_HOST)/${PKG_NAME}
include $(INCLUDE_DIR)/host-build.mk
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
define Package/$(PKG_NAME)
SECTION:=net
CATEGORY:=Network
SUBMENU:=Web Servers/Proxies
TITLE:=A Toolbox for Home Local Networks
URL:=https://github.com/XGHeaven/homebox
DEPENDS:=$(GO_ARCH_DEPENDS)
MENU:=1
endef
define Package/$(PKG_NAME)/description
A Toolbox for Home Local Networks Speed Test
endef
GO_PKG_BUILD_VARS += GO111MODULE=auto
TAR_OPTIONS:=--strip-components 1 $(TAR_OPTIONS)
TAR_CMD=$(HOST_TAR) -C $(1) $(TAR_OPTIONS)
define Build/Configure
( \
cd $(PKG_BUILD_DIR)/server; \
$(GO_PKG_VARS) \
go get -d -modcacherw; \
)
( \
cd $(PKG_BUILD_DIR)/server; \
GOPATH=$(PKG_BUILD_DIR)/.go_work/build \
go install -modcacherw github.com/go-bindata/go-bindata/...@latest; \
)
endef
define Build/Compile
rm -rf $(PKG_BUILD_DIR)/build/static
mkdir -p $(PKG_BUILD_DIR)/build
$(CP) $(HOST_BUILD_DIR)/build/static $(PKG_BUILD_DIR)/build/
( \
cd $(PKG_BUILD_DIR); \
$(GO_PKG_VARS) PATH=$(PKG_BUILD_DIR)/.go_work/build/bin:$$$$PATH \
$(MAKE) build-server; \
)
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/bin $(1)/etc/config $(1)/etc/init.d $(1)/etc/uci-defaults
$(INSTALL_BIN) $(PKG_BUILD_DIR)/build/server $(1)/usr/bin/homebox
$(INSTALL_CONF) ./files/homebox.config $(1)/etc/config/homebox
$(INSTALL_BIN) ./files/homebox.init $(1)/etc/init.d/homebox
$(INSTALL_BIN) ./files/homebox.uci-default $(1)/etc/uci-defaults/homebox
endef
define Package/$(PKG_NAME)/conffiles
/etc/config/homebox
endef
define Package/$(PKG_NAME)/postinst
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
[ -f /etc/uci-defaults/homebox ] && /etc/uci-defaults/homebox && rm -f /etc/uci-defaults/homebox
fi
endef
define Host/Configure
cd $(HOST_BUILD_DIR)/web && rm -f package-lock.json && npm --cache-min 1440 install
endef
define Host/Compile
cd $(HOST_BUILD_DIR) && $(MAKE) build-web
endef
define Host/Install
endef
define Host/Clean
rm -f $(HOST_BUILD_DIR)/build/static
endef
$(eval $(call HostBuild))
$(eval $(call BuildPackage,homebox))

View File

@ -0,0 +1,2 @@
config homebox
option 'enabled' '0'

25
homebox/files/homebox.init Executable file
View File

@ -0,0 +1,25 @@
#!/bin/sh /etc/rc.common
START=99
USE_PROCD=1
get_config() {
config_get_bool enabled $1 enabled 1
config_get_bool logger $1 logger
}
start_service() {
config_load homebox
config_foreach get_config homebox
[ $enabled != 1 ] && return 1
procd_open_instance
procd_set_param command /usr/bin/homebox
[ "$logger" == 1 ] && procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
}
service_triggers() {
procd_add_reload_trigger "homebox"
}

View File

@ -0,0 +1,10 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@homebox[-1]
add ucitrack homebox
set ucitrack.@homebox[-1].init=homebox
commit ucitrack
EOF
exit 0

15
luci-app-homebox/Makefile Normal file
View File

@ -0,0 +1,15 @@
# Copyright (C) 2016 Openwrt.org
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for homebox
LUCI_DEPENDS:=+homebox
LUCI_PKGARCH:=all
include ../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,24 @@
module("luci.controller.homebox", package.seeall)
function index()
if not nixio.fs.access("/etc/config/homebox") then
return
end
entry({"admin", "services", "homebox"}, cbi("homebox"), _("Homebox"), 20).dependent = true
entry({"admin", "services", "homebox_status"}, call("homebox_status"))
end
function homebox_status()
local sys = require "luci.sys"
local uci = require "luci.model.uci".cursor()
local status = {
running = (sys.call("pidof homebox >/dev/null") == 0),
port = 3300
}
luci.http.prepare_content("application/json")
luci.http.write_json(status)
end

View File

@ -0,0 +1,16 @@
local m, s
m = Map("homebox", translate("Homebox"), translate("Homebox is a tool for local network speed testing"))
m:section(SimpleSection).template = "homebox_status"
s=m:section(TypedSection, "homebox", translate("Global Settings"))
s.addremove=false
s.anonymous=true
s:option(Flag, "enabled", translate("Enable")).rmempty=false
return m

View File

@ -0,0 +1 @@
<%+homebox_status%>

View File

@ -0,0 +1,27 @@
<script type="text/javascript">//<![CDATA[
XHR.poll(5, '<%=url("admin/services/homebox_status")%>', null,
function(x, st)
{
var tb = document.getElementById('homebox_status');
if (st && tb)
{
if (st.running)
{
tb.innerHTML = '<br/><em style=\"color:green\"><%:The Homebox service is running.%></em>'
+ "<br/><br/><input class=\"btn cbi-button cbi-button-apply\" type=\"button\" value=\" <%:Click to open Homebox%> \" onclick=\"window.open('http://" + window.location.hostname + ":" + st.port + "/')\"/>";
}
else
{
tb.innerHTML = '<br/><em style=\"color:red\"><%:The Homebox service is not running.%></em>';
}
}
}
);
//]]></script>
<fieldset class="cbi-section">
<legend><%:Homebox Status%></legend>
<p id="homebox_status">
<em><%:Collecting data...%></em>
</p>
</fieldset>

View File

@ -0,0 +1,24 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Homebox"
msgstr "Homebox"
msgid "Click to open Homebox"
msgstr "点击打开Homebox"
msgid "Homebox is a tool for local network speed testing"
msgstr "Homebox是一个局域网测速工具"
msgid "The Homebox service is running."
msgstr "Homebox服务已启动"
msgid "The Homebox service is not running."
msgstr "Homebox服务未启动"
msgid "Homebox Status"
msgstr "Homebox服务状态"
msgid "Collecting data..."
msgstr "收集数据..."

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

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

View File

@ -0,0 +1,4 @@
#!/bin/sh
rm -f /tmp/luci-indexcache
exit 0

16
luci-app-tasks/Makefile Normal file
View File

@ -0,0 +1,16 @@
#
# Copyright (C) 2022 jjm2473 <jjm2473@gmail.com>
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=luci for taskd
LUCI_DEPENDS:=+luci-lib-taskd
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
include ../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,9 @@
module("luci.controller.tasks.app", package.seeall)
function index()
entry({"admin", "system", "tasks"}, alias("admin", "system", "tasks", "all"), _("Tasks"), 56)
--entry({"admin", "system", "tasks", "user"}, cbi("tasks/user"), _("User Tasks"), 1)
entry({"admin", "system", "tasks", "all"}, form("tasks/all"))
end

View File

@ -0,0 +1,59 @@
--[[
LuCI - Lua Configuration Interface
]]--
local taskd = require "luci.model.tasks"
local tasks = taskd.status()
local show_log_taskid
local m, s, o
local t=Template("tasks/all")
m = SimpleForm("taskd",
translate("All Tasks"),
translate("All submitted tasks, including system defined tasks"))
m.submit=false
m.reset=false
s = m:section(Table, tasks)
s.config = "tasks"
o = s:option(DummyValue, "_id", translate("ID"))
o.width="10%"
o.cfgvalue = function(self, section)
return section
end
o = s:option(DummyValue, "_status", translate("Status"))
o.width="15%"
o.cfgvalue = function(self, section)
local task = tasks[section]
return task.running and translate("Running") or (translate("Stopped:") .. " " .. task.exit_code)
end
o = s:option(DummyValue, "_start", translate("Start Time"))
o.width="15%"
o.cfgvalue = function(self, section)
local task = tasks[section]
return os.date("%Y/%m/%d %H:%M:%S", task.start)
end
-- os.date("%Y/%m/%d %H:%M:%S", 1657163212)
local btn_log = s:option(Button, "_log", translate("Log"))
btn_log.inputstyle = "find"
btn_log.write = function(self, section, value)
t.show_log_taskid = section
end
local btn_remove = s:option(Button, "_remove", translate("Remove"))
btn_remove.inputstyle = "remove"
btn_remove.forcewrite = true
btn_remove.write = function(self, section, value)
local task_id = section
os.execute("/etc/init.d/tasks task_del "..task_id.." >/dev/null 2>&1")
tasks[task_id] = nil
end
m:append(t)
return m

View File

@ -0,0 +1,28 @@
<%+tasks/embed%>
<script>
window.addEventListener("load", function(){
const taskd = window.taskd;
const logfunc = function(e) {
taskd.show_log(this);
return false;
};
const delfunc = function(e) {
taskd.remove(this).then(()=>{
location.reload();
});
return false;
};
const rows = document.querySelectorAll('#cbi-tasks-table tr.cbi-section-table-row');
rows.forEach(row => {
if (row.id) {
const task_id = row.id.match(/cbi-tasks-(.*)/)[1];
row.querySelector('td[data-name=_log] .cbi-button').onclick = logfunc.bind(task_id);
row.querySelector('td[data-name=_remove] .cbi-button').onclick = delfunc.bind(task_id);
}
});
<% if self.show_log_taskid then -%>
taskd.show_log("<%=self.show_log_taskid%>");
<%- end %>
});
</script>

View File

@ -0,0 +1,29 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Tasks"
msgstr "后台任务"
msgid "All Tasks"
msgstr "所有任务"
msgid "All submitted tasks, including system defined tasks"
msgstr "所有已提交的任务,包括系统定义的任务"
msgid "Status"
msgstr "状态"
msgid "Running"
msgstr "运行中"
msgid "Stopped:"
msgstr "已停止:"
msgid "Start Time"
msgstr "开始时间"
msgid "Log"
msgstr "日志"
msgid "Remove"
msgstr "移除"

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

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

View File

@ -0,0 +1,29 @@
# Copyright (C) 2016 Openwrt.org
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI mac to vendor map
LUCI_DESCRIPTION:=provide a js for mac address to vendor mapping
LUCI_PKGARCH:=all
# PKG_RELEASE MUST be empty for luci.mk
LIB_VERSION:=1.0.2
PKG_HASH:=skip
PKG_SOURCE_URL_FILE:=v$(LIB_VERSION).tar.gz
PKG_SOURCE:=mac_vendor-$(PKG_SOURCE_URL_FILE)
PKG_SOURCE_URL:=https://github.com/jjm2473/mac_vendor/archive/refs/tags
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
TARGET_CONFIGURE_OPTS= LIB_DIST="$(BUILD_DIR)/mac_vendor-$(LIB_VERSION)/dist" LIB_VERSION="$(LIB_VERSION)"
TARGET_CONFIGURE_OPTS+= SED="$(SED)"
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,6 @@
clean:
compile:
install:
mkdir -p "$(DESTDIR)/www/luci-static/resources/mac_vendor"
cp -a "$(LIB_DIST)/." "$(DESTDIR)/www/luci-static/resources/mac_vendor/"
$(SED) 's#=PKG_VERSION"#=$(LIB_VERSION)"#g' "$(DESTDIR)/www/luci-static/resources/mac_vendor/mac_vendor.js"

17
luci-lib-taskd/Makefile Normal file
View File

@ -0,0 +1,17 @@
#
# Copyright (C) 2022 jjm2473 <jjm2473@gmail.com>
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Task library
LUCI_DEPENDS:=+luci-lib-xterm +taskd
LUCI_PKGARCH:=all
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
include ../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,107 @@
[hidden] {
display: none !important;
}
#tasks_detail_container {
position: fixed;
z-index: 1000;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #0008;
}
#tasks_dialog {
position: absolute;
width: 770px;
max-height: 100%;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: #000;
border-radius: 10px;
box-shadow: 2px 2px 6px #000a;
padding: 20px;
color: white;
}
.dialog-title-bar {
margin-top: -10px;
margin-right: -10px;
margin-bottom: 5px;
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: nowrap;
align-items: center;
}
.dialog-title-bar .dialog-title {
word-break: break-all;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.dialog-content {
max-height: 500px;
overflow-y: scroll;
margin-right: -10px;
}
.dialog-icons {
align-self: center;
display: flex;
justify-content: flex-end;
}
.dialog-icon {
display: flex;
align-items: center;
flex-wrap: nowrap;
width: 20px;
height: 20px;
background-color: white;
color: black;
border-radius: 50%;
font-size: 10px;
font-weight: bold;
user-select: none;
margin-left: 10px;
line-height: 1;
font-family: sans-serif;
justify-content: center;
cursor: pointer;
}
.dialog-icon.dialog-icon-min {
background-color: darkorange;
}
.dialog-icon.dialog-icon-close {
background-color: #ff5f56;
}
.dialog-icons:hover .dialog-icon.dialog-icon-min:before {
content: "_";
}
.dialog-icons:hover .dialog-icon.dialog-icon-close:before {
content: "X";
}
.tasks_stopped .dialog-icon.dialog-icon-close {
background-color: #27c840;
}
.tasks_stopped #tasks_dialog {
padding: 19px;
border: 1px #27c840 solid;
animation: border-blink 1s;
animation-iteration-count: infinite;
}
.tasks_failed #tasks_dialog {
border-color: #ff0000;
}
.tasks_failed .dialog-icon.dialog-icon-close {
background-color: #ff0000;
}
@keyframes border-blink { 50% { border-color:#fff ; } }

View File

@ -0,0 +1,198 @@
(function(){
const taskd={};
const $gettext = function(str) {
return taskd.i18n[str] || str;
};
const request = function(url, method, data) {
return new Promise((resolve, reject) => {
var oReq = new XMLHttpRequest();
oReq.open(method || 'GET', url, true);
oReq.onload = function (oEvent) {
resolve(oReq.responseText);
};
if (method=='POST') {
oReq.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
}
oReq.send(data || method=='POST'?"token="+taskd.csrfToken:null);
});
};
const getBin = function(url) {
return new Promise((resolve, reject) => {
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
oReq.onload = function (oEvent) {
resolve({status: oReq.status, buffer: new Uint8Array(oReq.response)});
};
oReq.send(null);
});
};
const getTaskDetail = function(task_id) {
return request("/cgi-bin/luci/admin/system/tasks/status?task_id="+task_id).then(data=>JSON.parse(data));
};
const create_dialog = function(cfg) {
const container = document.createElement('div');
container.id = "tasks_detail_container";
container.innerHTML = taskd.dialog_template;
document.body.appendChild(container);
const title_view = container.querySelector(".dialog-title-bar .dialog-title");
title_view.innerText = cfg.title;
const term = new Terminal({convertEol: cfg.convertEol||false});
if (cfg.nohide) {
container.querySelector(".dialog-icon-min").hidden = true;
} else {
container.querySelector(".dialog-icon-min").onclick = function(){
container.hidden=true;
term.dispose();
document.body.removeChild(container);
cfg.onhide && cfg.onhide();
return false;
};
}
term.open(document.getElementById("tasks_xterm_log"));
return {term,container};
};
const show_log_txt = function(title, content, onclose) {
const dialog = create_dialog({title, convertEol:true, onhine:onclose});
const container = dialog.container;
const term = dialog.term;
container.querySelector(".dialog-icon-close").hidden = true;
term.write(content);
};
const show_log = function(task_id, nohide) {
let showing = true;
let running = true;
const dialog = create_dialog({title:task_id, nohide, onhide:function(){showing=false;}});
const container = dialog.container;
const term = dialog.term;
const title_view = container.querySelector(".dialog-title-bar .dialog-title");
container.querySelector(".dialog-icon-close").onclick = function(){
if (!running || confirm($gettext("Stop running task?"))) {
running=false;
showing=false;
del_task(task_id).then(()=>{
location.href = location.href;
});
}
return false;
};
const checkTask = function() {
return getTaskDetail(task_id).then(data=>{
if (!running) {
return false;
}
running = data.running;
let title = task_id;
if (!data.running && data.stop) {
title += " (" + (data.exit_code?$gettext("Failed at:"):$gettext("Finished at:")) + " " + new Date(data.stop * 1000).toLocaleString() + ")";
}
title += " > " + (data.command || '');
title_view.title = title;
title_view.innerText = title;
if (!data.running) {
container.classList.add('tasks_stopped')
if (data.exit_code) {
container.classList.add('tasks_failed')
}
}
return data.running;
});
};
let logoffset = 0;
const pulllog = function() {
getBin("/cgi-bin/luci/admin/system/tasks/log?task_id="+task_id+"&offset="+logoffset).then(function(res){
if (!showing) {
return false;
}
switch(res.status){
case 205:
term.reset();
logoffset = 0;
return running;
break;
case 204:
return running && checkTask();
break;
case 200:
logoffset += res.buffer.byteLength;
term.write(res.buffer);
return running;
break;
}
}).then(again => {
if (again) {
setTimeout(pulllog, 0);
}
});
};
checkTask().then(pulllog);
};
const del_task = function(task_id) {
return request("/cgi-bin/luci/admin/system/tasks/stop?task_id="+task_id, "POST");
};
taskd.show_log = show_log;
taskd.remove = del_task;
taskd.show_log_txt = show_log_txt;
window.taskd=taskd;
})();
(function(){
// compat
if (typeof(window.findParent) !== 'function') {
const elem = function(e) {
return (e != null && typeof(e) == 'object' && 'nodeType' in e);
};
const matches = function(node, selector) {
var m = elem(node) ? node.matches || node.msMatchesSelector : null;
return m ? m.call(node, selector) : false;
};
window.findParent = function (node, selector) {
if (elem(node) && node.closest)
return node.closest(selector);
while (elem(node))
if (matches(node, selector))
return node;
else
node = node.parentNode;
return null;
};
}
if (typeof(window.cbi_submit) !== 'function') {
const makeHidden = function(name) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = name;
return input;
};
window.cbi_submit = function(elem, name, value, action) {
var form = elem.form || findParent(elem, 'form');
if (!form)
return false;
if (action)
form.action = action;
if (name) {
var hidden = form.querySelector('input[type="hidden"][name="%s"]'.format(name)) ||
makeHidden(name);
hidden.value = value || '1';
form.appendChild(hidden);
}
form.submit();
return true;
};
}
})();

View File

@ -0,0 +1,91 @@
module("luci.controller.tasks-lib", package.seeall)
function index()
entry({"admin", "system", "tasks", "status"}, call("tasks_status")).dependent=false
entry({"admin", "system", "tasks", "log"}, call("tasks_log")).dependent=false
entry({"admin", "system", "tasks", "stop"}, post("tasks_stop")).dependent=false
end
local util = require "luci.util"
local jsonc = require "luci.jsonc"
local ltn12 = require "luci.ltn12"
local taskd = require "luci.model.tasks"
function tasks_status()
local data = taskd.status(luci.http.formvalue("task_id"))
luci.http.prepare_content("application/json")
luci.http.write_json(data)
end
function tasks_log()
local task_id = luci.http.formvalue("task_id")
local offset = luci.http.formvalue("offset")
offset = offset and tonumber(offset) or 0
local logpath = "/var/log/tasks/"..task_id..".log"
local i
local logfd = io.open(logpath, "rb")
if logfd == nil then
luci.http.status(404)
luci.http.write("log not found")
return
end
local size = logfd:seek("end")
if size < offset then
luci.http.status(205, "Reset Content")
luci.http.write("reset offset")
return
end
i = 0
while (i<200)
do
if size > offset then
break
end
nixio.nanosleep(0, 10000000) -- sleep 10ms
size = logfd:seek("end")
i = i+1
end
if i == 200 then
logfd:close()
luci.http.status(204)
luci.http.prepare_content("application/octet-stream")
return
end
logfd:seek("set", offset)
local write_log = function()
local buffer = logfd:read(4096)
if buffer and #buffer > 0 then
return buffer
else
logfd:close()
return nil
end
end
luci.http.prepare_content("application/octet-stream")
if logfd then
ltn12.pump.all(write_log, luci.http.write)
end
end
function tasks_stop()
local sys = require("luci.sys")
local task_id = luci.http.formvalue("task_id") or ""
if task_id == "" then
luci.http.status(400)
luci.http.write("task_id is empty")
return
end
if sys.call("/etc/init.d/tasks task_del "..task_id.." >/dev/null 2>&1") ~= 0 then
nixio.nanosleep(2, 10000000)
end
luci.http.status(204)
end

View File

@ -0,0 +1,96 @@
local util = require "luci.util"
local jsonc = require "luci.jsonc"
local taskd = {}
local function output(data)
local ret={}
ret.running=data.running
if not data.running then
ret.exit_code=data.exit_code
end
ret.command=data["command"] and data["command"][4] or '#'
if data["data"] then
ret.start=tonumber(data["data"]["start"])
if not data.running and data["data"]["stop"] then
ret.stop=tonumber(data["data"]["stop"])
end
end
return ret
end
taskd.status = function (task_id)
task_id = task_id or ""
local data = util.trim(util.exec("/etc/init.d/tasks task_status "..task_id.." 2>/dev/null")) or ""
if data ~= "" then
data = jsonc.parse(data)
if task_id ~= "" and not data.running and data["data"] then
data["data"]["stop"] = util.trim(util.exec("/etc/init.d/tasks task_stop_at "..task_id.." 2>/dev/null")) or ""
end
else
if task_id == "" then
data = {}
else
data = {running=false, exit_code=255}
end
end
if task_id ~= "" then
return output(data)
end
local ary={}
for k, v in pairs(data) do
ary[k] = output(v)
end
return ary
end
taskd.docker_map = function(config, task_id, script_path, title, desc)
require("luci.cbi")
require("luci.http")
require("luci.sys")
local translate = require("luci.i18n").translate
local m
m = luci.cbi.Map(config, title, desc)
m.template = "tasks/docker"
-- hide default buttons
m.pageaction = false
-- we want hook 'on_after_apply' works, 'apply_on_parse' can be true (rollback) or false (no rollback),
-- but 'apply_on_parse' must be true for luci 17.01 and below
m.apply_on_parse = true
m.script_path = script_path
m.task_id = task_id
m.auto_show_task = true
m.on_before_apply = function(self)
if self.uci.rollback then
-- luci 18.06+ has 'rollback' function
-- rollback dialog will show because 'apply_on_parse' is true,
-- hide rollback dialog by hook 'apply' function
local apply = self.uci.apply
self.uci.apply = function(uci, rollback)
apply(uci, false)
end
end
end
m.on_after_apply = function(self)
local cmd
local action = luci.http.formvalue("cbi.apply") or "null"
if "upgrade" == action or "install" == action
or "start" == action or "stop" == action or "restart" == action or "rm" == action then
cmd = string.format("\"%s\" %s", script_path, action)
end
if cmd then
if luci.sys.call("/etc/init.d/tasks task_add " .. task_id .. " '" .. cmd .. "' >/dev/null 2>&1") ~= 0 then
self.task_start_failed = true
self.message = translate("Config saved, but apply failed")
end
else
self.message = translate("Unknown command: ") .. action
end
if self.message then
self.auto_show_task = false
end
end
return m
end
return taskd

View File

@ -0,0 +1,56 @@
<% if self.task_start_failed then %>
<div class="alert-message warning"><%:Another task running, try again later.%> <a href="javascript:void(taskd.show_log('<%=self.task_id%>'))"><%:Click here to check running task%></a></div>
<% end %>
<%+cbi/map%>
<%
local task_running = false
local taskd = require "luci.model.tasks"
local status = taskd.status(self.task_id)
task_running = status.running
-%>
<div class="cbi-page-actions control-group">
<%
if not task_running then
%>
<%
local util = require "luci.util"
local container_status = util.trim(util.exec(self.script_path.." status"))
local container_install = (string.len(container_status) > 0)
local container_running = container_status == "running"
if container_install then
-%>
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Upgrade%>/<%:Apply%>" onclick="cbi_submit(this, 'cbi.apply', 'upgrade')" />
<%
if container_running then
-%>
<input class="btn cbi-button cbi-button-remove" type="button" value="<%:Stop%>" onclick="cbi_submit(this, 'cbi.apply', 'stop')" />
<input class="btn cbi-button cbi-button-reload" type="button" value="<%:Restart%>" onclick="cbi_submit(this, 'cbi.apply', 'restart')" />
<% else %>
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Start%>" onclick="cbi_submit(this, 'cbi.apply', 'start')" />
<input class="btn cbi-button cbi-button-remove" type="button" value="<%:Remove%>" onclick="cbi_submit(this, 'cbi.apply', 'rm')" />
<% end
else %>
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Install%>" onclick="cbi_submit(this, 'cbi.apply', 'install')" />
<% end
else
%>
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Task Running%>&hellip;" onclick="taskd.show_log('<%=self.task_id%>')" />
<%
end
%>
</div>
<%+tasks/embed%>
<%
if self.auto_show_task and task_running then
-%>
<script>
taskd.show_log("<%=self.task_id%>");
</script>
<%
end
%>

View File

@ -0,0 +1,31 @@
<%+xterm/embed%>
<link rel="stylesheet" href="<%=resource%>/tasks/tasks.css<%# ?v=PKG_VERSION %>">
<script src="<%=resource%>/tasks/tasks.js<%# ?v=PKG_VERSION %>"></script>
<%
local i18n = {}
local function tr(str)
i18n[str]=translate(str)
end
tr("Stop running task?")
tr("Stopped at:")
tr("Finished at:")
tr("Failed at:")
-%>
<script>
window.taskd.csrfToken="<%=token%>";
window.taskd.i18n=<% luci.http.write_json(i18n) %>;
window.taskd.dialog_template=`
<div id="tasks_dialog">
<div class="dialog-title-bar">
<span class="dialog-title" id="tasks_id"></span>
<span class="dialog-icons">
<span class="dialog-icon dialog-icon-close" title="<%:Stop and Remove%>"></span>
<span class="dialog-icon dialog-icon-min" title="<%:Hide%>"></span>
</span>
</div>
<div class="dialog-content">
<div id="tasks_xterm_log"></div>
</div>
</div>
`;
</script>

View File

@ -0,0 +1,15 @@
clean:
compile:
include $(TOPDIR)/rules.mk
LUCI_NAME:=luci-lib-dummy
INCLUDE_DIR:=./dummy
include $(TOPDIR)/feeds/luci/luci.mk
install:
mkdir -p "$(DESTDIR)$(LUCI_LIBRARYDIR)/i18n"
$(foreach lang,$(LUCI_LANGUAGES),$(foreach po,$(wildcard ${CURDIR}/po/$(lang)/*.po), \
po2lmo $(po) \
$(DESTDIR)$(LUCI_LIBRARYDIR)/i18n/$(basename $(notdir $(po))).$(lang).lmo;))

View File

@ -0,0 +1,2 @@
define BuildPackage
endef

View File

@ -0,0 +1,32 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Stop running task?"
msgstr "删除运行中的任务?"
msgid "Finished at:"
msgstr "完成于:"
msgid "Failed at:"
msgstr "失败于:"
msgid "Stop and Remove"
msgstr "停止并删除"
msgid "Hide"
msgstr "隐藏"
msgid "Config saved, but apply failed"
msgstr "配置已保存,但应用失败"
msgid "Unknown command: "
msgstr "未知命令:"
msgid "Another task running, try again later."
msgstr "已有后台任务运行中,请稍后重试。"
msgid "Click here to check running task"
msgstr "点此查看运行中的任务"
msgid "Task Running"
msgstr "任务执行中"

19
luci-lib-xterm/Makefile Normal file
View File

@ -0,0 +1,19 @@
#
# Copyright (c) 2017-2019, The xterm.js authors (MIT License)
# Copyright (c) 2014-2017, SourceLair, Private Company (www.sourcelair.com) (MIT License)
# Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Xterm.js library
LUCI_DEPENDS:=
PKG_LICENSE:=MIT
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,180 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
* @license MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
/**
* Default styles for xterm.js
*/
.xterm {
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 5;
}
.xterm .xterm-helper-textarea {
padding: 0;
border: 0;
margin: 0;
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -5;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0;
left: -9999em;
line-height: normal;
}
.xterm {
cursor: text;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm.xterm-cursor-pointer,
.xterm .xterm-cursor-pointer {
cursor: pointer;
}
.xterm.column-select.focus {
/* Column selection mode */
cursor: crosshair;
}
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 10;
color: transparent;
}
.xterm .live-region {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.xterm-dim {
opacity: 0.5;
}
.xterm-underline {
text-decoration: underline;
}
.xterm-strikethrough {
text-decoration: line-through;
}
.xterm-screen .xterm-decoration-container .xterm-decoration {
z-index: 6;
position: absolute;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
<link rel="stylesheet" href="<%=resource%>/xterm/xterm.css<%# ?v=PKG_VERSION %>">
<script src="<%=resource%>/xterm/xterm.js<%# ?v=PKG_VERSION %>"></script>

41
taskd/Makefile Normal file
View File

@ -0,0 +1,41 @@
#
# Copyright (C) 2022 jjm2473 <jjm2473@gmail.com>
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=taskd
PKG_VERSION:=1.0.2
PKG_RELEASE:=1
PKG_MAINTAINER:=jjm2473 <jjm2473@gmail.com>
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=utils
CATEGORY:=Utilities
TITLE:=Simple Task Manager
DEPENDS:=+procd +script-utils +coreutils-stty
PKGARCH:=all
endef
define Package/$(PKG_NAME)/description
Simple Task Manager based on procd
endef
define Build/Configure
endef
define Build/Compile
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/etc/init.d $(1)/usr/libexec
$(INSTALL_BIN) ./files/tasks.init $(1)/etc/init.d/tasks
$(INSTALL_BIN) ./files/taskd.sh $(1)/usr/libexec/taskd
endef
$(eval $(call BuildPackage,$(PKG_NAME)))

16
taskd/files/taskd.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/sh
TASK_ID="$1"
TASK_CMD="$2"
exec </dev/null >>"/var/log/tasks/$TASK_ID.log" 2>&1
export HOME=/root
export TERM=xterm-256color
exec script -efqc 'onexit() {
/etc/init.d/tasks _task_onstop '"$TASK_ID"'
}
trap onexit EXIT;
stty cols 80 rows 24;
'"$TASK_CMD" /dev/null

133
taskd/files/tasks.init Executable file
View File

@ -0,0 +1,133 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2022 jjm2473@gmail.com
USE_PROCD=1
START=49
extra_command "task_add" "<task_id> <task_cmd> [<time_wait>] Add and run a task, time_wait is wait time before auto delete stopped task, in seconds, -1 means forever"
extra_command "task_del" "<task_id> Stop and delete task"
extra_command "task_status" "[<task_id>] Dump task status, dump all tasks if no task_id specified"
extra_command "task_stop_at" "<task_id> Get stop time"
extra_command "task_gc" "Auto delete exipred (stopped and after timw_wait) tasks"
extra_command "_task_onstop" "<task_id> Update stop time, for internal usage"
_task_add() {
local task_id="${1}"
local task_cmd="${2}"
local time_wait="${3}"
> "/var/log/tasks/$task_id.log"
procd_open_instance "$task_id"
procd_set_param data start=`date +'%s'` time_wait="$time_wait"
procd_set_param command sh -c "exec /usr/libexec/taskd '$task_id' \"\$0\"" "$task_cmd"
procd_set_param stderr 1
procd_close_instance
}
task_add() {
local task_id="${1}"
local task_cmd="${2}"
local time_wait="${3}"
[ -z "$task_id" -o -z "$task_cmd" ] && return 127
if service_running "$task_id"; then
echo "already running" >&2
return 1
fi
if ! mkdir -p /var/log/tasks; then
echo "create /var/log/tasks failed!" >&2
return 1
fi
rc_procd _task_add "$task_id" "$task_cmd" "$time_wait"
return 0
}
_task_del() {
local service="${1}"
local task_id="${2}"
procd_kill "$service" "$task_id"
> "/var/log/tasks/$task_id.log"
rm -f "/var/log/tasks/$task_id.log"
}
task_del() {
local task_id="${1}"
[ -z "$task_id" ] && return 127
procd_lock
_task_del "$(basename ${basescript:-$initscript})" "$task_id"
if [ "$(_task_status "$task_id" | jsonfilter -e '$.running' 2>/dev/null)" = "true" ]; then
return 1
else
return 0
fi
}
_task_status() {
local service="$(basename ${basescript:-$initscript})"
local instance="$1"
local data
json_init
[ -n "$service" ] && json_add_string name "$service"
data=$(_procd_ubus_call list | jsonfilter -e '@["'"$service"'"]')
[ -z "$data" ] && return 1
data=$(echo "$data" | jsonfilter -e '$.instances')
if [ -z "$data" ]; then
if [ -z "$instance" ]; then
echo "{}"
return 0
fi
return 1
fi
if [ -z "$instance" ]; then
echo "$data"
else
instance="\"$instance\""
echo "$data" | jsonfilter -e '$['"$instance"']'
fi
return 0
}
task_status() {
local task_id="${1}"
_task_status "$task_id"
}
task_stop_at() {
local task_id="${1}"
[ -z "$task_id" ] && return 127
date +'%s' -r "/var/log/tasks/$task_id.log"
}
task_gc() {
local service="$(basename ${basescript:-$initscript})"
local task_id instance time_wait
local data
json_init
[ -n "$service" ] && json_add_string name "$service"
data=$(_procd_ubus_call list | jsonfilter -e '@["'"$service"'"]')
[ -z "$data" ] && return 1
data=$(echo "$data" | jsonfilter -e '$.instances')
[ -z "$data" ] && return 1
procd_lock
ls /var/log/tasks/ | sed 's/.log$//g' | while read task_id; do
instance=$(echo "$data" | jsonfilter -e '$["'"$task_id"'"]')
[ "$(echo "$instance" | jsonfilter -e '$.running')" = "false" ] || continue
time_wait=$(echo "$instance" | jsonfilter -e '$.data.time_wait')
[ "$time_wait" = "-1" ] && continue
[ $(($(date +'%s' -r "/var/log/tasks/$task_id.log") + ${time_wait:-0})) -lt `date +'%s'` ] && _task_del "$service" "$task_id"
done
}
_task_onstop() {
local task_id="${1}"
[ -z "$task_id" ] && return 127
touch "/var/log/tasks/$task_id.log"
}