update 2024-12-04 10:15:35

This commit is contained in:
actions-user 2024-12-04 10:15:35 +08:00
parent 318f5b9936
commit ea37b1ce6f
89 changed files with 5684 additions and 0 deletions

12
luci-app-cpufreq/Makefile Normal file
View File

@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021-2022 ImmortalWrt.org
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI for CPU Freq Setting
LUCI_DEPENDS:=+cpufreq
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,95 @@
/* SPDX-License-Identifier: GPL-3.0-only
*
* Copyright (C) 2022 ImmortalWrt.org
*/
'use strict';
'require form';
'require fs';
'require uci';
'require view';
return view.extend({
load: function() {
return Promise.all([
uci.load('cpufreq'),
L.resolveDefault(fs.exec_direct('/etc/init.d/cpufreq', [ 'get_policies' ], 'json'), {})
]);
},
render: function(data) {
var m, s, o;
m = new form.Map('cpufreq', _('CPU Freq Settings'),
_('Set CPU Scaling Governor to Max Performance or Balance Mode'));
s = m.section(form.NamedSection, 'cpufreq', 'settings');
if (Object.keys(data[1]).length === 0) {
s.render = () => {
this.handleSaveApply = null;
this.handleSave = null;
this.handleReset = null;
return E('div', { 'class': 'cbi-section warning' }, [
E('h3', {}, _('Unsupported device!')),
E('p', {}, _('Your device/kernel does not support CPU frequency scaling.'))
]);
}
} else {
/* Mark user edited */
var ss = m.section(form.NamedSection, 'global', 'settings');
var so = ss.option(form.HiddenValue, 'set');
so.load = (/* ... */) => { return 1 };
so.readonly = true;
so.rmempty = false;
for (var i in data[1]) {
var index = data[1][i].index;
s.tab(index, i, _('<h4>Apply for CPU %s.</h4>').format(data[1][i].cpus));
o = s.taboption(index, form.ListValue, 'governor' + index, _('CPU Scaling Governor'));
for (var gov of data[1][i].governors)
o.value(gov);
o.rmempty = false;
o = s.taboption(index, form.ListValue, 'minfreq' + index, _('Min Idle CPU Freq'));
for (var freq of data[1][i].freqs)
o.value(freq);
o.rmempty = false;
o = s.taboption(index, form.ListValue, 'maxfreq' + index, _('Max Turbo Boost CPU Freq'));
for (var freq of data[1][i].freqs)
o.value(freq);
o.validate = function(section_id, value) {
if (!section_id)
return true;
else if (value === null || value === '')
return _('Expecting: %s').format('non-empty value');
var minfreq = this.map.lookupOption('minfreq' + index, section_id)[0].formvalue(section_id);
if (parseInt(value) < parseInt(minfreq))
return _('Max CPU Freq cannot be lower than Min CPU Freq.');
return true;
}
o = s.taboption(index, form.Value, 'sdfactor' + index, _('CPU Switching Sampling rate'),
_('The sampling rate determines how frequently the governor checks to tune the CPU (ms)'));
o.datatype = 'range(1,100000)';
o.default = '10';
o.depends('governor' + index, 'ondemand');
o.rmempty = false;
o = s.taboption(index, form.Value, 'upthreshold' + index, _('CPU Switching Threshold'),
_('Kernel make a decision on whether it should increase the frequency (%)'));
o.datatype = 'range(1,99)';
o.default = '50';
o.depends('governor' + index, 'ondemand');
o.rmempty = false;
}
}
return m.render();
}
});

View File

@ -0,0 +1,68 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:49
msgid "<h4>Apply for CPU %s.</h4>"
msgstr ""
#: applications/luci-app-cpufreq/root/usr/share/luci/menu.d/luci-app-cpufreq.json:3
msgid "CPU Freq"
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:23
msgid "CPU Freq Settings"
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:51
msgid "CPU Scaling Governor"
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:77
msgid "CPU Switching Sampling rate"
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:84
msgid "CPU Switching Threshold"
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:68
msgid "Expecting: %s"
msgstr ""
#: applications/luci-app-cpufreq/root/usr/share/rpcd/acl.d/luci-app-cpufreq.json:3
msgid "Grant access to CPUFreq configuration"
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:85
msgid "Kernel make a decision on whether it should increase the frequency (%)"
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:72
msgid "Max CPU Freq cannot be lower than Min CPU Freq."
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:61
msgid "Max Turbo Boost CPU Freq"
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:56
msgid "Min Idle CPU Freq"
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:24
msgid "Set CPU Scaling Governor to Max Performance or Balance Mode"
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:78
msgid ""
"The sampling rate determines how frequently the governor checks to tune the "
"CPU (ms)"
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:35
msgid "Unsupported device!"
msgstr ""
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:36
msgid "Your device/kernel does not support CPU frequency scaling."
msgstr ""

1
luci-app-cpufreq/po/zh-cn Symbolic link
View File

@ -0,0 +1 @@
zh_Hans

View File

@ -0,0 +1,75 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: zh-Hans\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:49
msgid "<h4>Apply for CPU %s.</h4>"
msgstr "<h4>应用于 CPU %s。</h4>"
#: applications/luci-app-cpufreq/root/usr/share/luci/menu.d/luci-app-cpufreq.json:3
msgid "CPU Freq"
msgstr "CPU 性能优化调节"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:23
msgid "CPU Freq Settings"
msgstr "CPU 性能优化调节设置"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:51
msgid "CPU Scaling Governor"
msgstr "CPU 工作模式"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:77
msgid "CPU Switching Sampling rate"
msgstr "CPU 切换周期"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:84
msgid "CPU Switching Threshold"
msgstr "CPU 切换频率触发阈值"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:68
msgid "Expecting: %s"
msgstr "请输入:%s"
#: applications/luci-app-cpufreq/root/usr/share/rpcd/acl.d/luci-app-cpufreq.json:3
msgid "Grant access to CPUFreq configuration"
msgstr "授予访问 CPUFreq 配置的权限"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:85
msgid "Kernel make a decision on whether it should increase the frequency (%)"
msgstr "当 CPU 占用率超过 (%) 的情况下触发内核切换频率"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:72
msgid "Max CPU Freq cannot be lower than Min CPU Freq."
msgstr "最大 CPU 频率不能低于最小 CPU 频率。"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:61
msgid "Max Turbo Boost CPU Freq"
msgstr "最大 Turbo Boost CPU 频率"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:56
msgid "Min Idle CPU Freq"
msgstr "待机 CPU 最小频率"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:24
msgid "Set CPU Scaling Governor to Max Performance or Balance Mode"
msgstr "设置路由器的 CPU 性能模式(高性能/均衡省电)"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:78
msgid ""
"The sampling rate determines how frequently the governor checks to tune the "
"CPU (ms)"
msgstr "CPU 检查切换的周期 (ms)。注意:过于频繁的切换频率会引起网络延迟抖动"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:35
msgid "Unsupported device!"
msgstr "不支持的设备!"
#: applications/luci-app-cpufreq/htdocs/luci-static/resources/view/cpufreq.js:36
msgid "Your device/kernel does not support CPU frequency scaling."
msgstr "您的 设备/内核 不支持 CPU 动态调频。"

View File

@ -0,0 +1,14 @@
{
"admin/system/cpufreq": {
"title": "CPU Freq",
"order": 90,
"action": {
"type": "view",
"path": "cpufreq"
},
"depends": {
"acl": [ "luci-app-cpufreq" ],
"uci": { "cpufreq": true }
}
}
}

View File

@ -0,0 +1,14 @@
{
"luci-app-cpufreq": {
"description": "Grant access to CPUFreq configuration",
"read": {
"file": {
"/etc/init.d/cpufreq get_policies": [ "exec" ]
},
"uci": [ "cpufreq" ]
},
"write": {
"uci": [ "cpufreq" ]
}
}
}

53
luci-app-diskman/Makefile Normal file
View File

@ -0,0 +1,53 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-diskman
PKG_VERSION:=0.2.11
PKG_RELEASE:=2
PKG_MAINTAINER:=lisaac <lisaac.cn@gmail.com>
PKG_LICENSE:=AGPL-3.0
LUCI_TITLE:=Disk Manager interface for LuCI
LUCI_DEPENDS:=+blkid +e2fsprogs +parted +smartmontools \
+PACKAGE_$(PKG_NAME)_INCLUDE_btrfs_progs:btrfs-progs \
+PACKAGE_$(PKG_NAME)_INCLUDE_lsblk:lsblk \
+PACKAGE_$(PKG_NAME)_INCLUDE_mdadm:mdadm \
+PACKAGE_$(PKG_NAME)_INCLUDE_kmod_md_raid456:mdadm \
+PACKAGE_$(PKG_NAME)_INCLUDE_kmod_md_raid456:kmod-md-raid456 \
+PACKAGE_$(PKG_NAME)_INCLUDE_kmod_md_linears:mdadm \
+PACKAGE_$(PKG_NAME)_INCLUDE_kmod_md_linears:kmod-md-linear
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)/config
config PACKAGE_$(PKG_NAME)_INCLUDE_btrfs_progs
bool "Include btrfs-progs"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_lsblk
bool "Include lsblk"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_mdadm
bool "Include mdadm"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_kmod_md_raid456
depends on PACKAGE_$(PKG_NAME)_INCLUDE_mdadm
bool "Include kmod-md-raid456"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_kmod_md_linear
depends on PACKAGE_$(PKG_NAME)_INCLUDE_mdadm
bool "Include kmod-md-linear"
default n
endef
define Package/$(PKG_NAME)/postinst
#!/bin/sh
rm -fr /tmp/luci-indexcache /tmp/luci-modulecache
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,151 @@
--[[
LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-diskman>
]]--
require "luci.util"
module("luci.controller.diskman",package.seeall)
function index()
-- check all used executables in disk management are existed
local CMD = {"parted", "blkid", "smartctl"}
local executables_all_existed = true
for _, cmd in ipairs(CMD) do
local command = luci.sys.exec("/usr/bin/which " .. cmd)
if not command:match(cmd) then
executables_all_existed = false
break
end
end
if not executables_all_existed then return end
-- entry(path, target, title, order)
-- set leaf attr to true to pass argument throughe url (e.g. admin/system/disk/partition/sda)
entry({"admin", "system", "diskman"}, alias("admin", "system", "diskman", "disks"), _("Disk Man"), 55)
entry({"admin", "system", "diskman", "disks"}, form("diskman/disks"), nil).leaf = true
entry({"admin", "system", "diskman", "partition"}, form("diskman/partition"), nil).leaf = true
entry({"admin", "system", "diskman", "btrfs"}, form("diskman/btrfs"), nil).leaf = true
entry({"admin", "system", "diskman", "format_partition"}, call("format_partition"), nil).leaf = true
entry({"admin", "system", "diskman", "get_disk_info"}, call("get_disk_info"), nil).leaf = true
entry({"admin", "system", "diskman", "mk_p_table"}, call("mk_p_table"), nil).leaf = true
entry({"admin", "system", "diskman", "smartdetail"}, call("smart_detail"), nil).leaf = true
entry({"admin", "system", "diskman", "smartattr"}, call("smart_attr"), nil).leaf = true
end
function format_partition()
local partation_name = luci.http.formvalue("partation_name")
local fs = luci.http.formvalue("file_system")
if not partation_name then
luci.http.status(500, "Partition NOT found!")
luci.http.write_json("Partition NOT found!")
return
elseif not nixio.fs.access("/dev/"..partation_name) then
luci.http.status(500, "Partition NOT found!")
luci.http.write_json("Partition NOT found!")
return
elseif not fs then
luci.http.status(500, "no file system")
luci.http.write_json("no file system")
return
end
local dm = require "luci.model.diskman"
code, msg = dm.format_partition(partation_name, fs)
luci.http.status(code, msg)
luci.http.write_json(msg)
end
function get_disk_info(dev)
if not dev then
luci.http.status(500, "no device")
luci.http.write_json("no device")
return
elseif not nixio.fs.access("/dev/"..dev) then
luci.http.status(500, "no device")
luci.http.write_json("no device")
return
end
local dm = require "luci.model.diskman"
local device_info = dm.get_disk_info(dev)
luci.http.status(200, "ok")
luci.http.prepare_content("application/json")
luci.http.write_json(device_info)
end
function mk_p_table()
local p_table = luci.http.formvalue("p_table")
local dev = luci.http.formvalue("dev")
if not dev then
luci.http.status(500, "no device")
luci.http.write_json("no device")
return
elseif not nixio.fs.access("/dev/"..dev) then
luci.http.status(500, "no device")
luci.http.write_json("no device")
return
end
local dm = require "luci.model.diskman"
if p_table == "GPT" or p_table == "MBR" then
p_table = p_table == "MBR" and "msdos" or "gpt"
local res = luci.sys.call(dm.command.parted .. " -s /dev/" .. dev .. " mktable ".. p_table)
if res == 0 then
luci.http.status(200, "ok")
else
luci.http.status(500, "command exec error")
end
luci.http.prepare_content("application/json")
luci.http.write_json({code=res})
else
luci.http.status(404, "not support")
luci.http.prepare_content("application/json")
luci.http.write_json({code="1"})
end
end
function smart_detail(dev)
luci.template.render("diskman/smart_detail", {dev=dev})
end
function smart_attr(dev)
local attr = { }
local dm = require "luci.model.diskman"
local cmd = io.popen(dm.command.smartctl .. " -H -A -i /dev/%s" % dev)
if cmd then
local content = cmd:read("*all")
local ln
cmd:close()
if content:match("NVMe Version:")then
for ln in string.gmatch(content,'[^\r\n]+') do
if ln:match("^(.-):%s+(.+)") then
local key, value = ln:match("^(.-):%s+(.+)")
attr[#attr+1]= {
key = key,
value = value
}
end
end
else
for ln in string.gmatch(content,'[^\r\n]+') do
if ln:match("^.*%d+%s+.+%s+.+%s+.+%s+.+%s+.+%s+.+%s+.+%s+.+%s+.+") then
local id,attrbute,flag,value,worst,thresh,type,updated,raw = ln:match("^%s*(%d+)%s+([%a%p]+)%s+(%w+)%s+(%d+)%s+(%d+)%s+(%d+)%s+([%a%p]+)%s+(%a+)%s+[%w%p]+%s+(.+)")
id= "%x" % id
if not id:match("^%w%w") then
id = "0%s" % id
end
attr[#attr+1]= {
id = id:upper(),
attrbute = attrbute,
flag = flag,
value = value,
worst = worst,
thresh = thresh,
type = type,
updated = updated,
raw = raw
}
end
end
end
end
luci.http.prepare_content("application/json")
luci.http.write_json(attr)
end

View File

@ -0,0 +1,210 @@
--[[
LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-diskman>
]]--
require "luci.util"
require("luci.tools.webadmin")
local dm = require "luci.model.diskman"
local uuid = arg[1]
if not uuid then luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman")) end
-- mount subv=/ to tempfs
mount_point = "/tmp/.btrfs_tmp"
nixio.fs.mkdirr(mount_point)
luci.util.exec(dm.command.umount .. " "..mount_point .. " >/dev/null 2>&1")
luci.util.exec(dm.command.mount .. " -t btrfs -o subvol=/ UUID="..uuid.." "..mount_point)
m = SimpleForm("btrfs", translate("Btrfs"), translate("Manage Btrfs"))
m.template = "diskman/cbi/xsimpleform"
m.redirect = luci.dispatcher.build_url("admin/system/diskman")
m.submit = false
m.reset = false
-- info
local btrfs_info = dm.get_btrfs_info(mount_point)
local table_btrfs_info = m:section(Table, {btrfs_info}, translate("Btrfs Info"))
table_btrfs_info:option(DummyValue, "uuid", translate("UUID"))
table_btrfs_info:option(DummyValue, "members", translate("Members"))
table_btrfs_info:option(DummyValue, "data_raid_level", translate("Data"))
table_btrfs_info:option(DummyValue, "metadata_raid_lavel", translate("Metadata"))
table_btrfs_info:option(DummyValue, "size_formated", translate("Size"))
table_btrfs_info:option(DummyValue, "used_formated", translate("Used"))
table_btrfs_info:option(DummyValue, "free_formated", translate("Free Space"))
table_btrfs_info:option(DummyValue, "usage", translate("Usage"))
local v_btrfs_label = table_btrfs_info:option(Value, "label", translate("Label"))
local value_btrfs_label = ""
v_btrfs_label.write = function(self, section, value)
value_btrfs_label = value or ""
end
local btn_update_label = table_btrfs_info:option(Button, "_update_label")
btn_update_label.inputtitle = translate("Update")
btn_update_label.inputstyle = "edit"
btn_update_label.write = function(self, section, value)
local cmd = dm.command.btrfs .. " filesystem label " .. mount_point .. " " .. value_btrfs_label
local res = luci.util.exec(cmd)
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/btrfs/" .. uuid))
end
-- subvolume
local subvolume_list = dm.get_btrfs_subv(mount_point)
subvolume_list["_"] = { ID = 0 }
table_subvolume = m:section(Table, subvolume_list, translate("SubVolumes"))
table_subvolume:option(DummyValue, "id", translate("ID"))
table_subvolume:option(DummyValue, "top_level", translate("Top Level"))
table_subvolume:option(DummyValue, "uuid", translate("UUID"))
table_subvolume:option(DummyValue, "otime", translate("Otime"))
table_subvolume:option(DummyValue, "snapshots", translate("Snapshots"))
local v_path = table_subvolume:option(Value, "path", translate("Path"))
v_path.forcewrite = true
v_path.render = function(self, section, scope)
if subvolume_list[section].ID == 0 then
self.template = "cbi/value"
self.placeholder = "/my_subvolume"
self.forcewrite = true
Value.render(self, section, scope)
else
self.template = "cbi/dvalue"
DummyValue.render(self, section, scope)
end
end
local value_path
v_path.write = function(self, section, value)
value_path = value
end
local btn_set_default = table_subvolume:option(Button, "_subv_set_default", translate("Set Default"))
btn_set_default.forcewrite = true
btn_set_default.inputstyle = "edit"
btn_set_default.template = "diskman/cbi/disabled_button"
btn_set_default.render = function(self, section, scope)
if subvolume_list[section].default_subvolume then
self.view_disabled = true
self.inputtitle = translate("Set Default")
elseif subvolume_list[section].ID == 0 then
self.template = "cbi/dvalue"
else
self.inputtitle = translate("Set Default")
self.view_disabled = false
end
Button.render(self, section, scope)
end
btn_set_default.write = function(self, section, value)
local cmd
if value == translate("Set Default") then
cmd = dm.command.btrfs .. " subvolume set-default " .. mount_point..subvolume_list[section].path
else
cmd = dm.command.btrfs .. " subvolume set-default " .. mount_point.."/"
end
local res = luci.util.exec(cmd.. " 2>&1")
if res and (res:match("ERR") or res:match("not enough arguments")) then
m.errmessage = res
else
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/btrfs/" .. uuid))
end
end
local btn_remove = table_subvolume:option(Button, "_subv_remove")
btn_remove.template = "diskman/cbi/disabled_button"
btn_remove.forcewrite = true
btn_remove.render = function(self, section, scope)
if subvolume_list[section].ID == 0 then
btn_remove.inputtitle = translate("Create")
btn_remove.inputstyle = "add"
self.view_disabled = false
elseif subvolume_list[section].path == "/" or subvolume_list[section].default_subvolume then
btn_remove.inputtitle = translate("Delete")
btn_remove.inputstyle = "remove"
self.view_disabled = true
else
btn_remove.inputtitle = translate("Delete")
btn_remove.inputstyle = "remove"
self.view_disabled = false
end
Button.render(self, section, scope)
end
btn_remove.write = function(self, section, value)
local cmd
if value == translate("Delete") then
cmd = dm.command.btrfs .. " subvolume delete " .. mount_point .. subvolume_list[section].path
elseif value == translate("Create") then
if value_path and value_path:match("^/") then
cmd = dm.command.btrfs .. " subvolume create " .. mount_point .. value_path
else
m.errmessage = translate("Please input Subvolume Path, Subvolume must start with '/'")
return
end
end
local res = luci.util.exec(cmd.. " 2>&1")
if res and (res:match("ERR") or res:match("not enough arguments")) then
m.errmessage = luci.util.pcdata(res)
else
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/btrfs/" .. uuid))
end
end
-- snapshot
-- local snapshot_list = dm.get_btrfs_subv(mount_point, 1)
-- table_snapshot = m:section(Table, snapshot_list, translate("Snapshots"))
-- table_snapshot:option(DummyValue, "id", translate("ID"))
-- table_snapshot:option(DummyValue, "top_level", translate("Top Level"))
-- table_snapshot:option(DummyValue, "uuid", translate("UUID"))
-- table_snapshot:option(DummyValue, "otime", translate("Otime"))
-- table_snapshot:option(DummyValue, "path", translate("Path"))
-- local snp_remove = table_snapshot:option(Button, "_snp_remove")
-- snp_remove.inputtitle = translate("Delete")
-- snp_remove.inputstyle = "remove"
-- snp_remove.write = function(self, section, value)
-- local cmd = dm.command.btrfs .. " subvolume delete " .. mount_point .. snapshot_list[section].path
-- local res = luci.util.exec(cmd.. " 2>&1")
-- if res and (res:match("ERR") or res:match("not enough arguments")) then
-- m.errmessage = luci.util.pcdata(res)
-- else
-- luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/btrfs/" .. uuid))
-- end
-- end
-- new snapshots
local s_snapshot = m:section(SimpleSection, translate("New Snapshot"))
local value_sorce, value_dest, value_readonly
local v_sorce = s_snapshot:option(Value, "_source", translate("Source Path"), translate("The source path for create the snapshot"))
v_sorce.placeholder = "/data"
v_sorce.forcewrite = true
v_sorce.write = function(self, section, value)
value_sorce = value
end
local v_readonly = s_snapshot:option(Flag, "_readonly", translate("Readonly"), translate("The path where you want to store the snapshot"))
v_readonly.forcewrite = true
v_readonly.rmempty = false
v_readonly.disabled = 0
v_readonly.enabled = 1
v_readonly.default = 1
v_readonly.write = function(self, section, value)
value_readonly = value
end
local v_dest = s_snapshot:option(Value, "_dest", translate("Destination Path (optional)"))
v_dest.forcewrite = true
v_dest.placeholder = "/.snapshot/202002051538"
v_dest.write = function(self, section, value)
value_dest = value
end
local btn_snp_create = s_snapshot:option(Button, "_snp_create")
btn_snp_create.title = " "
btn_snp_create.inputtitle = translate("New Snapshot")
btn_snp_create.inputstyle = "add"
btn_snp_create.write = function(self, section, value)
if value_sorce and value_sorce:match("^/") then
if not value_dest then value_dest = "/.snapshot"..value_sorce.."/"..os.date("%Y%m%d%H%M%S") end
nixio.fs.mkdirr(mount_point..value_dest:match("(.-)[^/]+$"))
local cmd = dm.command.btrfs .. " subvolume snapshot" .. (value_readonly == 1 and " -r " or " ") .. mount_point..value_sorce .. " " .. mount_point..value_dest
local res = luci.util.exec(cmd .. " 2>&1")
if res and (res:match("ERR") or res:match("not enough arguments")) then
m.errmessage = luci.util.pcdata(res)
else
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/btrfs/" .. uuid))
end
else
m.errmessage = translate("Please input Source Path of snapshot, Source Path must start with '/'")
end
end
return m

View File

@ -0,0 +1,360 @@
--[[
LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-diskman>
]]--
require "luci.util"
require("luci.tools.webadmin")
local dm = require "luci.model.diskman"
-- Use (non-UCI) SimpleForm since we have no related config file
m = SimpleForm("diskman", translate("DiskMan"), translate("Manage Disks over LuCI."))
m.template = "diskman/cbi/xsimpleform"
m:append(Template("diskman/disk_info"))
-- disable submit and reset button
m.submit = false
m.reset = false
-- rescan disks
rescan = m:section(SimpleSection)
rescan_button = rescan:option(Button, "_rescan")
rescan_button.inputtitle= translate("Rescan Disks")
rescan_button.template = "diskman/cbi/inlinebutton"
rescan_button.inputstyle = "add"
rescan_button.forcewrite = true
rescan_button.write = function(self, section, value)
luci.util.exec("echo '- - -' | tee /sys/class/scsi_host/host*/scan > /dev/null")
if dm.command.mdadm then
luci.util.exec(dm.command.mdadm .. " --assemble --scan")
end
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman"))
end
-- disks
local disks = dm.list_devices()
d = m:section(Table, disks, translate("Disks"))
d.config = "disk"
-- option(type, id(key of table), text)
d:option(DummyValue, "path", translate("Path"))
d:option(DummyValue, "model", translate("Model"))
d:option(DummyValue, "sn", translate("Serial Number"))
d:option(DummyValue, "size_formated", translate("Size"))
d:option(DummyValue, "temp", translate("Temp"))
-- d:option(DummyValue, "sec_size", translate("Sector Size "))
d:option(DummyValue, "p_table", translate("Partition Table"))
d:option(DummyValue, "sata_ver", translate("SATA Version"))
-- d:option(DummyValue, "rota_rate", translate("Rotation Rate"))
d:option(DummyValue, "health_status", translate("Health") .. "<br/>" .. translate("Status"))
-- d:option(DummyValue, "status", translate("Status"))
local btn_eject = d:option(Button, "_eject")
btn_eject.template = "diskman/cbi/disabled_button"
btn_eject.inputstyle = "remove"
btn_eject.inputtitle = translate("Eject")
btn_eject.forcewrite = true
btn_eject.write = function(self, section, value)
local dev = section
local disk_info = dm.get_disk_info(dev, true)
if disk_info.p_table:match("Raid") then
m.errmessage = translate("Unsupported raid reject!")
return
end
for i, p in ipairs(disk_info.partitions) do
if p.mount_point ~= "-" then
m.errmessage = p.name .. translate("is in use! please unmount it first!")
return
end
end
if disk_info.type:match("md") then
luci.util.exec(dm.command.mdadm .. " --stop /dev/" .. dev)
luci.util.exec(dm.command.mdadm .. " --remove /dev/" .. dev)
for _, disk in ipairs(disk_info.members) do
luci.util.exec(dm.command.mdadm .. " --zero-superblock " .. disk)
end
dm.gen_mdadm_config()
else
luci.util.exec("echo 1 > /sys/block/" .. dev .. "/device/delete")
end
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman"))
end
d.extedit = luci.dispatcher.build_url("admin/system/diskman/partition/%s")
-- raid devices
if dm.command.mdadm then
local raid_devices = dm.list_raid_devices()
-- raid_devices = diskmanager.getRAIDdevices()
if next(raid_devices) ~= nil then
local r = m:section(Table, raid_devices, translate("RAID Devices"))
r.config = "_raid"
r:option(DummyValue, "path", translate("Path"))
r:option(DummyValue, "level", translate("RAID mode"))
r:option(DummyValue, "size_formated", translate("Size"))
r:option(DummyValue, "p_table", translate("Partition Table"))
r:option(DummyValue, "status", translate("Status"))
r:option(DummyValue, "members_str", translate("Members"))
r:option(DummyValue, "active", translate("Active"))
r.extedit = luci.dispatcher.build_url("admin/system/diskman/partition/%s")
end
end
-- btrfs devices
if dm.command.btrfs then
btrfs_devices = dm.list_btrfs_devices()
if next(btrfs_devices) ~= nil then
local table_btrfs = m:section(Table, btrfs_devices, translate("Btrfs"))
table_btrfs:option(DummyValue, "uuid", translate("UUID"))
table_btrfs:option(DummyValue, "label", translate("Label"))
table_btrfs:option(DummyValue, "members", translate("Members"))
-- sieze is error, since there is RAID
-- table_btrfs:option(DummyValue, "size_formated", translate("Size"))
table_btrfs:option(DummyValue, "used_formated", translate("Usage"))
table_btrfs.extedit = luci.dispatcher.build_url("admin/system/diskman/btrfs/%s")
end
end
-- mount point
local mount_point = dm.get_mount_points()
local _mount_point = {}
table.insert( mount_point, { device = 0 } )
local table_mp = m:section(Table, mount_point, translate("Mount Point"))
local v_device = table_mp:option(Value, "device", translate("Device"))
v_device.render = function(self, section, scope)
if mount_point[section].device == 0 then
self.template = "cbi/value"
self.forcewrite = true
for dev, info in pairs(disks) do
for i, v in ipairs(info.partitions) do
self:value("/dev/".. v.name, "/dev/".. v.name .. " ".. v.size_formated)
end
end
Value.render(self, section, scope)
else
self.template = "cbi/dvalue"
DummyValue.render(self, section, scope)
end
end
v_device.write = function(self, section, value)
_mount_point.device = value and value:gsub("%s+", "") or ""
end
local v_fs = table_mp:option(Value, "fs", translate("File System"))
v_fs.render = function(self, section, scope)
if mount_point[section].device == 0 then
self.template = "cbi/value"
self:value("auto", "auto")
self.default = "auto"
self.forcewrite = true
Value.render(self, section, scope)
else
self.template = "cbi/dvalue"
DummyValue.render(self, section, scope)
end
end
v_fs.write = function(self, section, value)
_mount_point.fs = value and value:gsub("%s+", "") or ""
end
local v_mount_option = table_mp:option(Value, "mount_options", translate("Mount Options"))
v_mount_option.render = function(self, section, scope)
if mount_point[section].device == 0 then
self.template = "cbi/value"
self.placeholder = "rw,noauto"
self.forcewrite = true
Value.render(self, section, scope)
else
self.template = "cbi/dvalue"
local mp = mount_point[section].mount_options
mount_point[section].mount_options = nil
local length = 0
for k in mp:gmatch("([^,]+)") do
mount_point[section].mount_options = mount_point[section].mount_options and (mount_point[section].mount_options .. ",") or ""
if length > 20 then
mount_point[section].mount_options = mount_point[section].mount_options.. " <br>"
length = 0
end
mount_point[section].mount_options = mount_point[section].mount_options .. k
length = length + #k
end
self.rawhtml = true
-- mount_point[section].mount_options = #mount_point[section].mount_options > 50 and mount_point[section].mount_options:sub(1,50) .. "..." or mount_point[section].mount_options
DummyValue.render(self, section, scope)
end
end
v_mount_option.write = function(self, section, value)
_mount_point.mount_options = value and value:gsub("%s+", "") or ""
end
local v_mount_point = table_mp:option(Value, "mount_point", translate("Mount Point"))
v_mount_point.render = function(self, section, scope)
if mount_point[section].device == 0 then
self.template = "cbi/value"
self.placeholder = "/media/diskX"
self.forcewrite = true
Value.render(self, section, scope)
else
self.template = "cbi/dvalue"
local new_mp = ""
local v_mp_d
for v_mp_d in self["section"]["data"][section]["mount_point"]:gmatch('[^/]+') do
if #v_mp_d > 12 then
new_mp = new_mp .. "/" .. v_mp_d:sub(1,7) .. ".." .. v_mp_d:sub(-4)
else
new_mp = new_mp .."/".. v_mp_d
end
end
self["section"]["data"][section]["mount_point"] = '<span title="'..self["section"]["data"][section]["mount_point"] .. '" >'..new_mp..'</span>'
self.rawhtml = true
DummyValue.render(self, section, scope)
end
end
v_mount_point.write = function(self, section, value)
_mount_point.mount_point = value
end
local btn_umount = table_mp:option(Button, "_mount", translate("Mount"))
btn_umount.forcewrite = true
btn_umount.render = function(self, section, scope)
if mount_point[section].device == 0 then
self.inputtitle = translate("Mount")
btn_umount.inputstyle = "add"
else
self.inputtitle = translate("Umount")
btn_umount.inputstyle = "remove"
end
Button.render(self, section, scope)
end
btn_umount.write = function(self, section, value)
local res
if value == translate("Mount") then
if not _mount_point.mount_point or not _mount_point.device then return end
luci.util.exec("mkdir -p ".. _mount_point.mount_point)
res = luci.util.exec(dm.command.mount .. " ".. _mount_point.device .. (_mount_point.fs and (" -t ".. _mount_point.fs )or "") .. (_mount_point.mount_options and (" -o " .. _mount_point.mount_options.. " ") or " ").._mount_point.mount_point .. " 2>&1")
elseif value == translate("Umount") then
res = luci.util.exec(dm.command.umount .. " "..mount_point[section].mount_point .. " 2>&1")
end
if res:match("^mount:") or res:match("^umount:") then
m.errmessage = luci.util.pcdata(res)
else
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman"))
end
end
if dm.command.mdadm or dm.command.btrfs then
local creation_section = m:section(TypedSection, "_creation")
creation_section.cfgsections=function()
return {translate("Creation")}
end
creation_section:tab("raid", translate("RAID"), translate("RAID Creation"))
creation_section:tab("btrfs", translate("Btrfs"), translate("Multiple Devices Btrfs Creation"))
-- raid functions
if dm.command.mdadm then
local rname, rmembers, rlevel
local r_name = creation_section:taboption("raid", Value, "_rname", translate("Raid Name"))
r_name.placeholder = dm.find_free_md_device()
r_name.write = function(self, section, value)
rname = value
end
local r_level = creation_section:taboption("raid", ListValue, "_rlevel", translate("Raid Level"))
local valid_raid = luci.util.exec("grep -m1 'Personalities :' /proc/mdstat")
if valid_raid:match("%[linear%]") then
r_level:value("linear", "Linear")
end
if valid_raid:match("%[raid5%]") then
r_level:value("5", "Raid 5")
end
if valid_raid:match("%[raid6%]") then
r_level:value("6", "Raid 6")
end
if valid_raid:match("%[raid1%]") then
r_level:value("1", "Raid 1")
end
if valid_raid:match("%[raid0%]") then
r_level:value("0", "Raid 0")
end
if valid_raid:match("%[raid10%]") then
r_level:value("10", "Raid 10")
end
r_level.write = function(self, section, value)
rlevel = value
end
local r_member = creation_section:taboption("raid", DynamicList, "_rmember", translate("Raid Member"))
for dev, info in pairs(disks) do
if not info.inuse and #info.partitions == 0 then
r_member:value(info.path, info.path.. " ".. info.size_formated)
end
for i, v in ipairs(info.partitions) do
if not v.inuse then
r_member:value("/dev/".. v.name, "/dev/".. v.name .. " ".. v.size_formated)
end
end
end
r_member.write = function(self, section, value)
rmembers = value
end
local r_create = creation_section:taboption("raid", Button, "_rcreate")
r_create.render = function(self, section, scope)
self.title = " "
self.inputtitle = translate("Create Raid")
self.inputstyle = "add"
Button.render(self, section, scope)
end
r_create.write = function(self, section, value)
-- mdadm --create --verbose /dev/md0 --level=stripe --raid-devices=2 /dev/sdb6 /dev/sdc5
local res = dm.create_raid(rname, rlevel, rmembers)
if res and res:match("^ERR") then
m.errmessage = luci.util.pcdata(res)
return
end
dm.gen_mdadm_config()
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman"))
end
end
-- btrfs
if dm.command.btrfs then
local blabel, bmembers, blevel
local btrfs_label = creation_section:taboption("btrfs", Value, "_blabel", translate("Btrfs Label"))
btrfs_label.write = function(self, section, value)
blabel = value
end
local btrfs_level = creation_section:taboption("btrfs", ListValue, "_blevel", translate("Btrfs Raid Level"))
btrfs_level:value("single", "Single")
btrfs_level:value("raid0", "Raid 0")
btrfs_level:value("raid1", "Raid 1")
btrfs_level:value("raid10", "Raid 10")
btrfs_level.write = function(self, section, value)
blevel = value
end
local btrfs_member = creation_section:taboption("btrfs", DynamicList, "_bmember", translate("Btrfs Member"))
for dev, info in pairs(disks) do
if not info.inuse and #info.partitions == 0 then
btrfs_member:value(info.path, info.path.. " ".. info.size_formated)
end
for i, v in ipairs(info.partitions) do
if not v.inuse then
btrfs_member:value("/dev/".. v.name, "/dev/".. v.name .. " ".. v.size_formated)
end
end
end
btrfs_member.write = function(self, section, value)
bmembers = value
end
local btrfs_create = creation_section:taboption("btrfs", Button, "_bcreate")
btrfs_create.render = function(self, section, scope)
self.title = " "
self.inputtitle = translate("Create Btrfs")
self.inputstyle = "add"
Button.render(self, section, scope)
end
btrfs_create.write = function(self, section, value)
-- mkfs.btrfs -L label -d blevel /dev/sda /dev/sdb
local res = dm.create_btrfs(blabel, blevel, bmembers)
if res and res:match("^ERR") then
m.errmessage = luci.util.pcdata(res)
return
end
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman"))
end
end
end
return m

View File

@ -0,0 +1,366 @@
--[[
LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-diskman>
]]--
require "luci.util"
require("luci.tools.webadmin")
local dm = require "luci.model.diskman"
local dev = arg[1]
if not dev then
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman"))
elseif not nixio.fs.access("/dev/"..dev) then
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman"))
end
m = SimpleForm("partition", translate("Partition Management"), translate("Partition Disk over LuCI."))
m.template = "diskman/cbi/xsimpleform"
m.redirect = luci.dispatcher.build_url("admin/system/diskman")
m:append(Template("diskman/partition_info"))
-- disable submit and reset button
m.submit = false
m.reset = false
local disk_info = dm.get_disk_info(dev, true)
local format_cmd = dm.get_format_cmd()
s = m:section(Table, {disk_info}, translate("Device Info"))
-- s:option(DummyValue, "key")
-- s:option(DummyValue, "value")
s:option(DummyValue, "path", translate("Path"))
s:option(DummyValue, "model", translate("Model"))
s:option(DummyValue, "sn", translate("Serial Number"))
s:option(DummyValue, "size_formated", translate("Size"))
s:option(DummyValue, "sec_size", translate("Sector Size"))
local dv_p_table = s:option(ListValue, "p_table", translate("Partition Table"))
dv_p_table.render = function(self, section, scope)
-- create table only if not used by raid and no partitions on disk
if not disk_info.p_table:match("Raid") and (#disk_info.partitions == 0 or (#disk_info.partitions == 1 and disk_info.partitions[1].number == -1) or (disk_info.p_table:match("LOOP") and not disk_info.partitions[1].inuse)) then
self:value(disk_info.p_table, disk_info.p_table)
self:value("GPT", "GPT")
self:value("MBR", "MBR")
self.default = disk_info.p_table
ListValue.render(self, section, scope)
else
self.template = "cbi/dvalue"
DummyValue.render(self, section, scope)
end
end
if disk_info.type:match("md") then
s:option(DummyValue, "level", translate("Level"))
s:option(DummyValue, "members_str", translate("Members"))
else
s:option(DummyValue, "temp", translate("Temp"))
s:option(DummyValue, "sata_ver", translate("SATA Version"))
s:option(DummyValue, "rota_rate", translate("Rotation Rate"))
end
s:option(DummyValue, "status", translate("Status"))
local btn_health = s:option(Button, "health", translate("Health"))
btn_health.render = function(self, section, scope)
if disk_info.health then
self.inputtitle = disk_info.health
if disk_info.health == "PASSED" then
self.inputstyle = "add"
else
self.inputstyle = "remove"
end
Button.render(self, section, scope)
else
self.template = "cbi/dvalue"
DummyValue.render(self, section, scope)
end
end
local btn_eject = s:option(Button, "_eject")
btn_eject.template = "diskman/cbi/disabled_button"
btn_eject.inputstyle = "remove"
btn_eject.render = function(self, section, scope)
for i, p in ipairs(disk_info.partitions) do
if p.mount_point ~= "-" then
self.view_disabled = true
break
end
end
if disk_info.p_table:match("Raid") then
self.view_disabled = true
end
if disk_info.type:match("md") then
btn_eject.inputtitle = translate("Remove")
else
btn_eject.inputtitle = translate("Eject")
end
Button.render(self, section, scope)
end
btn_eject.forcewrite = true
btn_eject.write = function(self, section, value)
for i, p in ipairs(disk_info.partitions) do
if p.mount_point ~= "-" then
m.errmessage = p.name .. translate("is in use! please unmount it first!")
return
end
end
if disk_info.type:match("md") then
luci.util.exec(dm.command.mdadm .. " --stop /dev/" .. dev)
luci.util.exec(dm.command.mdadm .. " --remove /dev/" .. dev)
for _, disk in ipairs(disk_info.members) do
luci.util.exec(dm.command.mdadm .. " --zero-superblock " .. disk)
end
dm.gen_mdadm_config()
else
luci.util.exec("echo 1 > /sys/block/" .. dev .. "/device/delete")
end
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman"))
end
-- eject: echo 1 > /sys/block/(device)/device/delete
-- rescan: echo '- - -' | tee /sys/class/scsi_host/host*/scan > /dev/null
-- partitions info
if not disk_info.p_table:match("Raid") then
s_partition_table = m:section(Table, disk_info.partitions, translate("Partitions Info"), translate("Default 2048 sector alignment, support +size{b,k,m,g,t} in End Sector"))
-- s_partition_table:option(DummyValue, "number", translate("Number"))
s_partition_table:option(DummyValue, "name", translate("Name"))
local val_sec_start = s_partition_table:option(Value, "sec_start", translate("Start Sector"))
val_sec_start.render = function(self, section, scope)
-- could create new partition
if disk_info.partitions[section].number == -1 and disk_info.partitions[section].size > 1 * 1024 * 1024 then
self.template = "cbi/value"
Value.render(self, section, scope)
else
self.template = "cbi/dvalue"
DummyValue.render(self, section, scope)
end
end
local val_sec_end = s_partition_table:option(Value, "sec_end", translate("End Sector"))
val_sec_end.render = function(self, section, scope)
-- could create new partition
if disk_info.partitions[section].number == -1 and disk_info.partitions[section].size > 1 * 1024 * 1024 then
self.template = "cbi/value"
Value.render(self, section, scope)
else
self.template = "cbi/dvalue"
DummyValue.render(self, section, scope)
end
end
val_sec_start.forcewrite = true
val_sec_start.write = function(self, section, value)
disk_info.partitions[section]._sec_start = value
end
val_sec_end.forcewrite = true
val_sec_end.write = function(self, section, value)
disk_info.partitions[section]._sec_end = value
end
s_partition_table:option(DummyValue, "size_formated", translate("Size"))
if disk_info.p_table == "MBR" then
s_partition_table:option(DummyValue, "type", translate("Type"))
end
s_partition_table:option(DummyValue, "used_formated", translate("Used"))
s_partition_table:option(DummyValue, "free_formated", translate("Free Space"))
s_partition_table:option(DummyValue, "usage", translate("Usage"))
local dv_mount_point = s_partition_table:option(DummyValue, "mount_point", translate("Mount Point"))
dv_mount_point.rawhtml = true
dv_mount_point.render = function(self, section, scope)
local new_mp = ""
local v_mp_d
for line in self["section"]["data"][section]["mount_point"]:gmatch("[^%s]+") do
if line == '-' then
new_mp = line
break
end
for v_mp_d in line:gmatch('[^/]+') do
if #v_mp_d > 12 then
new_mp = new_mp .. "/" .. v_mp_d:sub(1,7) .. ".." .. v_mp_d:sub(-4)
else
new_mp = new_mp .."/".. v_mp_d
end
end
new_mp = '<span title="'.. line .. '" >' ..new_mp ..'</span>' .. "<br/>"
end
self["section"]["data"][section]["mount_point"] = new_mp
DummyValue.render(self, section, scope)
end
local val_fs = s_partition_table:option(Value, "fs", translate("File System"))
val_fs.forcewrite = true
val_fs.partitions = disk_info.partitions
for k, v in pairs(format_cmd) do
val_fs.format_cmd = val_fs.format_cmd and (val_fs.format_cmd .. "," .. k) or k
end
val_fs.write = function(self, section, value)
disk_info.partitions[section]._fs = value
end
val_fs.render = function(self, section, scope)
-- use listvalue when partition not mounted
if disk_info.partitions[section].mount_point == "-" and disk_info.partitions[section].number ~= -1 and disk_info.partitions[section].type ~= "extended" then
self.template = "diskman/cbi/format_button"
self.inputstyle = "reset"
self.inputtitle = disk_info.partitions[section].fs == "raw" and translate("Format") or disk_info.partitions[section].fs
Button.render(self, section, scope)
-- self:reset_values()
-- self.keylist = {}
-- self.vallist = {}
-- for k, v in pairs(format_cmd) do
-- self:value(k,k)
-- end
-- self.default = disk_info.partitions[section].fs
else
-- self:reset_values()
-- self.keylist = {}
-- self.vallist = {}
self.template = "cbi/dvalue"
DummyValue.render(self, section, scope)
end
end
-- btn_format = s_partition_table:option(Button, "_format")
-- btn_format.template = "diskman/cbi/format_button"
-- btn_format.partitions = disk_info.partitions
-- btn_format.render = function(self, section, scope)
-- if disk_info.partitions[section].mount_point == "-" and disk_info.partitions[section].number ~= -1 and disk_info.partitions[section].type ~= "extended" then
-- self.inputtitle = translate("Format")
-- self.template = "diskman/cbi/disabled_button"
-- self.view_disabled = false
-- self.inputstyle = "reset"
-- for k, v in pairs(format_cmd) do
-- self:depends("val_fs", "k")
-- end
-- -- elseif disk_info.partitions[section].mount_point ~= "-" and disk_info.partitions[section].number ~= -1 then
-- -- self.inputtitle = "Format"
-- -- self.template = "diskman/cbi/disabled_button"
-- -- self.view_disabled = true
-- -- self.inputstyle = "reset"
-- else
-- self.inputtitle = ""
-- self.template = "cbi/dvalue"
-- end
-- Button.render(self, section, scope)
-- end
-- btn_format.forcewrite = true
-- btn_format.write = function(self, section, value)
-- local partition_name = "/dev/".. disk_info.partitions[section].name
-- if not nixio.fs.access(partition_name) then
-- m.errmessage = translate("Partition NOT found!")
-- return
-- end
-- local fs = disk_info.partitions[section]._fs
-- if not format_cmd[fs] then
-- m.errmessage = translate("Filesystem NOT support!")
-- return
-- end
-- local cmd = format_cmd[fs].cmd .. " " .. format_cmd[fs].option .. " " .. partition_name
-- local res = luci.util.exec(cmd .. " 2>&1")
-- if res and res:lower():match("error+") then
-- m.errmessage = luci.util.pcdata(res)
-- else
-- luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/partition/" .. dev))
-- end
-- end
local btn_action = s_partition_table:option(Button, "_action")
btn_action.forcewrite = true
btn_action.template = "diskman/cbi/disabled_button"
btn_action.render = function(self, section, scope)
-- if partition is mounted or the size < 1mb, then disable the add action
if disk_info.partitions[section].mount_point ~= "-" or (disk_info.partitions[section].type ~= "extended" and disk_info.partitions[section].number == -1 and disk_info.partitions[section].size <= 1 * 1024 * 1024) then
self.view_disabled = true
-- self.inputtitle = ""
-- self.template = "cbi/dvalue"
elseif disk_info.partitions[section].type == "extended" and next(disk_info.partitions[section]["logicals"]) ~= nil then
self.view_disabled = true
else
-- self.template = "diskman/cbi/disabled_button"
self.view_disabled = false
end
if disk_info.partitions[section].number ~= -1 then
self.inputtitle = translate("Remove")
self.inputstyle = "remove"
else
self.inputtitle = translate("New")
self.inputstyle = "add"
end
Button.render(self, section, scope)
end
btn_action.write = function(self, section, value)
if value == translate("New") then
local start_sec = disk_info.partitions[section]._sec_start and tonumber(disk_info.partitions[section]._sec_start) or tonumber(disk_info.partitions[section].sec_start)
local end_sec = disk_info.partitions[section]._sec_end
if start_sec then
-- for sector alignment
local align = tonumber(disk_info.phy_sec) / tonumber(disk_info.logic_sec)
align = (align < 2048) and 2048
if start_sec < 2048 then
start_sec = "2048" .. "s"
elseif math.fmod( start_sec, align ) ~= 0 then
start_sec = tostring(start_sec + align - math.fmod( start_sec, align )) .. "s"
else
start_sec = start_sec .. "s"
end
else
m.errmessage = translate("Invalid Start Sector!")
return
end
-- support +size format for End sector
local end_size, end_unit = end_sec:match("^+(%d-)([bkmgtsBKMGTS])$")
if tonumber(end_size) and end_unit then
local unit ={
B=1,
S=512,
K=1024,
M=1048576,
G=1073741824,
T=1099511627776
}
end_unit = end_unit:upper()
end_sec = tostring(tonumber(end_size) * unit[end_unit] / unit["S"] + tonumber(start_sec:sub(1,-2)) - 1 ) .. "s"
elseif tonumber(end_sec) then
end_sec = end_sec .. "s"
else
m.errmessage = translate("Invalid End Sector!")
return
end
local part_type = "primary"
if disk_info.p_table == "MBR" and disk_info["extended_partition_index"] then
if tonumber(disk_info.partitions[disk_info["extended_partition_index"]].sec_start) <= tonumber(start_sec:sub(1,-2)) and tonumber(disk_info.partitions[disk_info["extended_partition_index"]].sec_end) >= tonumber(end_sec:sub(1,-2)) then
part_type = "logical"
if tonumber(start_sec:sub(1,-2)) - tonumber(disk_info.partitions[section].sec_start) < 2048 then
start_sec = tonumber(start_sec:sub(1,-2)) + 2048
start_sec = start_sec .."s"
end
end
elseif disk_info.p_table == "GPT" then
-- AUTOMATIC FIX GPT PARTITION TABLE
-- Not all of the space available to /dev/sdb appears to be used, you can fix the GPT to use all of the space (an extra 16123870 blocks) or continue with the current setting?
local cmd = ' printf "ok\nfix\n" | parted ---pretend-input-tty /dev/'.. dev ..' print'
luci.util.exec(cmd .. " 2>&1")
end
-- partiton
local cmd = dm.command.parted .. " -s -a optimal /dev/" .. dev .. " mkpart " .. part_type .." " .. start_sec .. " " .. end_sec
local res = luci.util.exec(cmd .. " 2>&1")
if res and res:lower():match("error+") then
m.errmessage = luci.util.pcdata(res)
else
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/partition/" .. dev))
end
elseif value == translate("Remove") then
-- remove partition
local number = tostring(disk_info.partitions[section].number)
if (not number) or (number == "") then
m.errmessage = translate("Partition not exists!")
return
end
local cmd = dm.command.parted .. " -s /dev/" .. dev .. " rm " .. number
local res = luci.util.exec(cmd .. " 2>&1")
if res and res:lower():match("error+") then
m.errmessage = luci.util.pcdata(res)
else
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman/partition/" .. dev))
end
end
end
end
return m

View File

@ -0,0 +1,743 @@
--[[
LuCI - Lua Configuration Interface
Copyright 2019 lisaac <https://github.com/lisaac/luci-app-diskman>
]]--
require "luci.util"
local ver = require "luci.version"
local CMD = {"parted", "mdadm", "blkid", "smartctl", "df", "btrfs", "lsblk"}
local d = {command ={}}
for _, cmd in ipairs(CMD) do
local command = luci.sys.exec("/usr/bin/which " .. cmd)
d.command[cmd] = command:match("^.+"..cmd) or nil
end
d.command.mount = nixio.fs.access("/usr/bin/mount") and "/usr/bin/mount" or "/bin/mount"
d.command.umount = nixio.fs.access("/usr/bin/umount") and "/usr/bin/umount" or "/bin/umount"
local proc_mounts = nixio.fs.readfile("/proc/mounts") or ""
local mounts = luci.util.exec(d.command.mount .. " 2>/dev/null") or ""
local swaps = nixio.fs.readfile("/proc/swaps") or ""
local df = luci.sys.exec(d.command.df .. " 2>/dev/null") or ""
function byte_format(byte)
local suff = {"B", "KB", "MB", "GB", "TB"}
for i=1, 5 do
if byte > 1024 and i < 5 then
byte = byte / 1024
else
return string.format("%.2f %s", byte, suff[i])
end
end
end
local get_smart_info = function(device)
local section
local smart_info = {}
for _, line in ipairs(luci.util.execl(d.command.smartctl .. " -H -A -i -n standby -f brief /dev/" .. device)) do
local attrib, val
if section == 1 then
attrib, val = line:match "^(.-):%s+(.+)"
elseif section == 2 and smart_info.nvme_ver then
attrib, val = line:match("^(.-):%s+(.+)")
if not smart_info.health then smart_info.health = line:match(".-overall%-health.-: (.+)") end
elseif section == 2 then
attrib, val = line:match("^([0-9 ]+)%s+[^ ]+%s+[POSRCK-]+%s+[0-9-]+%s+[0-9-]+%s+[0-9-]+%s+[0-9-]+%s+([0-9-]+)")
if not smart_info.health then smart_info.health = line:match(".-overall%-health.-: (.+)") end
else
attrib = line:match "^=== START OF (.*) SECTION ==="
if attrib and attrib:match("INFORMATION") then
section = 1
elseif attrib and attrib:match("SMART DATA") then
section = 2
elseif not smart_info.status then
val = line:match "^Device is in (.*) mode"
if val then smart_info.status = val end
end
end
if not attrib then
if section ~= 2 then section = 0 end
elseif (attrib == "Power mode is") or
(attrib == "Power mode was") then
smart_info.status = val:match("(%S+)")
-- elseif attrib == "Sector Sizes" then
-- -- 512 bytes logical, 4096 bytes physical
-- smart_info.phy_sec = val:match "([0-9]*) bytes physical"
-- smart_info.logic_sec = val:match "([0-9]*) bytes logical"
-- elseif attrib == "Sector Size" then
-- -- 512 bytes logical/physical
-- smart_info.phy_sec = val:match "([0-9]*)"
-- smart_info.logic_sec = smart_info.phy_sec
elseif attrib == "Serial Number" then
smart_info.sn = val
elseif attrib == "194" or attrib == "Temperature" then
if val ~= "-" then
smart_info.temp = (val:match("(%d+)") or "?") .. "°C"
end
elseif attrib == "Rotation Rate" then
smart_info.rota_rate = val
elseif attrib == "SATA Version is" then
smart_info.sata_ver = val
elseif attrib == "NVMe Version" then
smart_info.nvme_ver = val
end
end
return smart_info
end
local parse_parted_info = function(keys, line)
-- parse the output of parted command (machine parseable format)
-- /dev/sda:5860533168s:scsi:512:4096:gpt:ATA ST3000DM001-1ER1:;
-- 1:34s:2047s:2014s:free;
-- 1:2048s:1073743872s:1073741825s:ext4:primary:;
local result = {}
local values = {}
for value in line:gmatch("(.-)[:;]") do table.insert(values, value) end
for i = 1,#keys do
result[keys[i]] = values[i] or ""
end
return result
end
local is_raid_member = function(partition)
-- check if inuse as raid member
if nixio.fs.access("/proc/mdstat") then
for _, result in ipairs(luci.util.execl("grep md /proc/mdstat | sed 's/[][]//g'")) do
local md, buf
md, buf = result:match("(md.-):(.+)")
if buf:match(partition) then
return "Raid Member: ".. md
end
end
end
return nil
end
local get_mount_point = function(partition)
local mount_point
for m in mounts:gmatch("/dev/"..partition.." on ([^ ]*)") do
mount_point = (mount_point and (mount_point .. " ") or "") .. m
end
if mount_point then return mount_point end
-- result = luci.sys.exec('cat /proc/mounts | awk \'{if($1=="/dev/'.. partition ..'") print $2}\'')
-- if result ~= "" then return result end
if swaps:match("\n/dev/" .. partition .."%s") then return "swap" end
-- result = luci.sys.exec("cat /proc/swaps | grep /dev/" .. partition)
-- if result ~= "" then return "swap" end
return is_raid_member(partition)
end
-- return used, free, usage
local get_partition_usage = function(partition)
if not nixio.fs.access("/dev/"..partition) then return false end
local used, free, usage = df:match("\n/dev/" .. partition .. "%s+%d+%s+(%d+)%s+(%d+)%s+(%d+)%%%s-")
usage = usage and (usage .. "%") or "-"
used = used and (tonumber(used) * 1024) or 0
free = free and (tonumber(free) * 1024) or 0
return used, free, usage
end
local get_parted_info = function(device)
if not device then return end
local result = {partitions={}}
local DEVICE_INFO_KEYS = { "path", "size", "type", "logic_sec", "phy_sec", "p_table", "model", "flags" }
local PARTITION_INFO_KEYS = { "number", "sec_start", "sec_end", "size", "fs", "tag_name", "flags" }
local partition_temp
local partitions_temp = {}
local disk_temp
for line in luci.util.execi(d.command.parted .. " -s -m /dev/" .. device .. " unit s print free", "r") do
if line:find("^/dev/"..device..":.+") then
disk_temp = parse_parted_info(DEVICE_INFO_KEYS, line)
disk_temp.partitions = {}
if disk_temp["size"] then
local length = disk_temp["size"]:gsub("^(%d+)s$", "%1")
local newsize = tostring(tonumber(length)*tonumber(disk_temp["logic_sec"]))
disk_temp["size"] = newsize
end
if disk_temp["p_table"] == "msdos" then
disk_temp["p_table"] = "MBR"
else
disk_temp["p_table"] = disk_temp["p_table"]:upper()
end
elseif line:find("^%d-:.+") then
partition_temp = parse_parted_info(PARTITION_INFO_KEYS, line)
-- use human-readable form instead of sector number
if partition_temp["size"] then
local length = partition_temp["size"]:gsub("^(%d+)s$", "%1")
local newsize = (tonumber(length) * tonumber(disk_temp["logic_sec"]))
partition_temp["size"] = newsize
partition_temp["size_formated"] = byte_format(newsize)
end
partition_temp["number"] = tonumber(partition_temp["number"]) or -1
if partition_temp["fs"] == "free" then
partition_temp["number"] = -1
partition_temp["fs"] = "Free Space"
partition_temp["name"] = "-"
elseif device:match("sd") or device:match("sata") or device:match("vd") then
partition_temp["name"] = device..partition_temp["number"]
elseif device:match("mmcblk") or device:match("md") or device:match("nvme") then
partition_temp["name"] = device.."p"..partition_temp["number"]
end
if partition_temp["number"] > 0 and partition_temp["fs"] == "" and d.command.lsblk then
partition_temp["fs"] = luci.util.exec(d.command.lsblk .. " /dev/"..device.. tostring(partition_temp["number"]) .. " -no fstype"):match("([^%s]+)") or ""
end
partition_temp["fs"] = partition_temp["fs"] == "" and "raw" or partition_temp["fs"]
partition_temp["sec_start"] = partition_temp["sec_start"] and partition_temp["sec_start"]:sub(1,-2)
partition_temp["sec_end"] = partition_temp["sec_end"] and partition_temp["sec_end"]:sub(1,-2)
partition_temp["mount_point"] = partition_temp["name"]~="-" and get_mount_point(partition_temp["name"]) or "-"
if partition_temp["mount_point"]~="-" then
partition_temp["used"], partition_temp["free"], partition_temp["usage"] = get_partition_usage(partition_temp["name"])
partition_temp["used_formated"] = partition_temp["used"] and byte_format(partition_temp["used"]) or "-"
partition_temp["free_formated"] = partition_temp["free"] and byte_format(partition_temp["free"]) or "-"
else
partition_temp["used"], partition_temp["free"], partition_temp["usage"] = 0,0,"-"
partition_temp["used_formated"] = "-"
partition_temp["free_formated"] = "-"
end
-- if disk_temp["p_table"] == "MBR" and (partition_temp["number"] < 4) and (partition_temp["number"] > 0) then
-- local real_size_sec = tonumber(nixio.fs.readfile("/sys/block/"..device.."/"..partition_temp["name"].."/size")) * tonumber(disk_temp.phy_sec)
-- if real_size_sec ~= partition_temp["size"] then
-- disk_temp["extended_partition_index"] = partition_temp["number"]
-- partition_temp["type"] = "extended"
-- partition_temp["size"] = real_size_sec
-- partition_temp["fs"] = "-"
-- partition_temp["logicals"] = {}
-- else
-- partition_temp["type"] = "primary"
-- end
-- end
table.insert(partitions_temp, partition_temp)
end
end
if disk_temp and disk_temp["p_table"] == "MBR" then
for i, p in ipairs(partitions_temp) do
if disk_temp["extended_partition_index"] and p["number"] > 4 then
if tonumber(p["sec_end"]) <= tonumber(partitions_temp[disk_temp["extended_partition_index"]]["sec_end"]) and tonumber(p["sec_start"]) >= tonumber(partitions_temp[disk_temp["extended_partition_index"]]["sec_start"]) then
p["type"] = "logical"
table.insert(partitions_temp[disk_temp["extended_partition_index"]]["logicals"], i)
end
elseif (p["number"] <= 4) and (p["number"] > 0) then
local s = nixio.fs.readfile("/sys/block/"..device.."/"..p["name"].."/size")
if s then
local real_size_sec = tonumber(s) * tonumber(disk_temp.logic_sec)
-- if size not equal, it's an extended
if real_size_sec ~= p["size"] then
disk_temp["extended_partition_index"] = i
p["type"] = "extended"
p["size"] = real_size_sec
p["fs"] = "-"
p["logicals"] = {}
else
p["type"] = "primary"
end
else
-- if not found in "/sys/block"
p["type"] = "primary"
end
end
end
end
result = disk_temp or result
result.partitions = partitions_temp
return result
end
local mddetail = function(mdpath)
local detail = {}
local path = mdpath:match("^/dev/md%d+$")
if path then
local mdadm = io.popen(d.command.mdadm .. " --detail "..path, "r")
for line in mdadm:lines() do
local key, value = line:match("^%s*(.+) : (.+)")
if key then
detail[key] = value
end
end
mdadm:close()
end
return detail
end
-- return {{device="", mount_points="", fs="", mount_options="", dump="", pass=""}..}
d.get_mount_points = function()
local mount
local res = {}
local h ={"device", "mount_point", "fs", "mount_options", "dump", "pass"}
for mount in proc_mounts:gmatch("[^\n]+") do
local device = mount:match("^([^%s]+)%s+.+")
-- only show /dev/xxx device
if device and device:match("/dev/") then
res[#res+1] = {}
local i = 0
for v in mount:gmatch("[^%s]+") do
i = i + 1
res[#res][h[i]] = v
end
end
end
return res
end
d.get_disk_info = function(device, wakeup)
--[[ return:
{
path, model, sn, size, size_mounted, flags, type, temp, p_table, logic_sec, phy_sec, sec_size, sata_ver, rota_rate, status, health,
partitions = {
1 = { number, name, sec_start, sec_end, size, size_mounted, fs, tag_name, type, flags, mount_point, usage, used, free, used_formated, free_formated},
2 = { number, name, sec_start, sec_end, size, size_mounted, fs, tag_name, type, flags, mount_point, usage, used, free, used_formated, free_formated},
...
}
--raid devices only
level, members, members_str
}
--]]
if not device then return end
local disk_info
local smart_info = get_smart_info(device)
-- check if divice is the member of raid
smart_info["p_table"] = is_raid_member(device..'0')
-- if status is not active(standby), only check smart_info.
-- if only weakup == true, weakup the disk and check parted_info.
if smart_info.status ~= "STANDBY" or wakeup or (smart_info["p_table"] and not smart_info["p_table"]:match("Raid")) or device:match("^md") then
disk_info = get_parted_info(device)
disk_info["sec_size"] = disk_info["logic_sec"] .. "/" .. disk_info["phy_sec"]
disk_info["size_formated"] = byte_format(tonumber(disk_info["size"]))
-- if status is standby, after get part info, the disk is weakuped, then get smart_info again for more informations
if smart_info.status ~= "ACTIVE" then smart_info = get_smart_info(device) end
else
disk_info = {}
end
for k, v in pairs(smart_info) do
disk_info[k] = v
end
if disk_info.type and disk_info.type:match("md") then
local raid_info = d.list_raid_devices()[disk_info["path"]:match("/dev/(.+)")]
for k, v in pairs(raid_info) do
disk_info[k] = v
end
end
return disk_info
end
d.list_raid_devices = function()
local fs = require "nixio.fs"
local raid_devices = {}
if not fs.access("/proc/mdstat") then return raid_devices end
local mdstat = io.open("/proc/mdstat", "r")
for line in mdstat:lines() do
-- md1 : active raid1 sdb2[1] sda2[0]
-- md127 : active raid5 sdh1[6] sdg1[4] sdf1[3] sde1[2] sdd1[1] sdc1[0]
local device_info = {}
local mdpath, list = line:match("^(md%d+) : (.+)")
if mdpath then
local members = {}
for member in string.gmatch(list, "%S+") do
member_path = member:match("^(%S+)%[%d+%]")
if member_path then
member = '/dev/'..member_path
end
table.insert(members, member)
end
local active = table.remove(members, 1)
local level = "-"
if active == "active" then
level = table.remove(members, 1)
end
local size = tonumber(fs.readfile(string.format("/sys/class/block/%s/size", mdpath)))
local ss = tonumber(fs.readfile(string.format("/sys/class/block/%s/queue/logical_block_size", mdpath)))
device_info["path"] = "/dev/"..mdpath
device_info["size"] = size*ss
device_info["size_formated"] = byte_format(size*ss)
device_info["active"] = active:upper()
device_info["level"] = level
device_info["members"] = members
device_info["members_str"] = table.concat(members, ", ")
-- Get more info from output of mdadm --detail
local detail = mddetail(device_info["path"])
device_info["status"] = detail["State"]:upper()
raid_devices[mdpath] = device_info
end
end
mdstat:close()
return raid_devices
end
-- Collect Devices information
--[[ return:
{
sda={
path, model, inuse, size_formated,
partitions={
{ name, inuse, size_formated }
...
}
}
..
}
--]]
d.list_devices = function()
local fs = require "nixio.fs"
-- get all device names (sdX and mmcblkX)
local target_devnames = {}
for dev in fs.dir("/dev") do
if dev:match("^sd[a-z]$")
or dev:match("^mmcblk%d+$")
or dev:match("^sata[a-z]$")
or dev:match("^nvme%d+n%d+$")
or dev:match("^vd[a-z]$")
then
table.insert(target_devnames, dev)
end
end
local devices = {}
for i, bname in pairs(target_devnames) do
local device_info = {}
local device = "/dev/" .. bname
local size = tonumber(fs.readfile(string.format("/sys/class/block/%s/size", bname)) or "0")
local ss = tonumber(fs.readfile(string.format("/sys/class/block/%s/queue/logical_block_size", bname)) or "0")
local model = fs.readfile(string.format("/sys/class/block/%s/device/model", bname))
local partitions = {}
for part in nixio.fs.glob("/sys/block/" .. bname .."/" .. bname .. "*") do
local pname = nixio.fs.basename(part)
local psize = byte_format(tonumber(nixio.fs.readfile(part .. "/size"))*ss)
local mount_point = get_mount_point(pname)
if mount_point then device_info["inuse"] = true end
table.insert(partitions, {name = pname, size_formated = psize, inuse = mount_point})
end
device_info["path"] = device
device_info["size_formated"] = byte_format(size*ss)
device_info["model"] = model
device_info["partitions"] = partitions
-- true or false
device_info["inuse"] = device_info["inuse"] or get_mount_point(bname)
local udevinfo = {}
if luci.sys.exec("which udevadm") ~= "" then
local udevadm = io.popen("udevadm info --query=property --name="..device)
for attr in udevadm:lines() do
local k, v = attr:match("(%S+)=(%S+)")
udevinfo[k] = v
end
udevadm:close()
device_info["info"] = udevinfo
if udevinfo["ID_MODEL"] then device_info["model"] = udevinfo["ID_MODEL"] end
end
devices[bname] = device_info
end
-- luci.util.perror(luci.util.serialize_json(devices))
return devices
end
-- get formart cmd
d.get_format_cmd = function()
local AVAILABLE_FMTS = {
ext2 = { cmd = "mkfs.ext2", option = "-F -E lazy_itable_init=1" },
ext3 = { cmd = "mkfs.ext3", option = "-F -E lazy_itable_init=1" },
ext4 = { cmd = "mkfs.ext4", option = "-F -E lazy_itable_init=1" },
fat32 = { cmd = "mkfs.vfat", option = "-F" },
exfat = { cmd = "mkexfat", option = "-f" },
hfsplus = { cmd = "mkhfs", option = "-f" },
ntfs = { cmd = "mkntfs", option = "-f" },
swap = { cmd = "mkswap", option = "" },
btrfs = { cmd = "mkfs.btrfs", option = "-f" }
}
result = {}
for fmt, obj in pairs(AVAILABLE_FMTS) do
local cmd = luci.sys.exec("/usr/bin/which " .. obj["cmd"])
if cmd:match(obj["cmd"]) then
result[fmt] = { cmd = cmd:match("^.+"..obj["cmd"]) ,option = obj["option"] }
end
end
return result
end
d.find_free_md_device = function()
for num=0,127 do
local md = io.open("/dev/md"..tostring(num), "r")
if md == nil then
return "/dev/md"..tostring(num)
else
io.close(md)
end
end
return nil
end
d.create_raid = function(rname, rlevel, rmembers)
local mb = {}
for _, v in ipairs(rmembers) do
mb[v]=v
end
rmembers = {}
for _, v in pairs(mb) do
table.insert(rmembers, v)
end
if type(rname) == "string" then
if rname:match("^md%d-%s+") then
rname = "/dev/"..rname:match("^(md%d-)%s+")
elseif rname:match("^/dev/md%d-%s+") then
rname = "/dev/"..rname:match("^(/dev/md%d-)%s+")
elseif not rname:match("/") then
rname = "/dev/md/".. rname
else
return "ERR: Invalid raid name"
end
else
rname = d.find_free_md_device()
if rname == nil then return "ERR: Cannot find free md device" end
end
if rlevel == "5" or rlevel == "6" then
if #rmembers < 3 then return "ERR: Not enough members" end
end
if rlevel == "10" then
if #rmembers < 4 then return "ERR: Not enough members" end
end
if #rmembers < 2 then return "ERR: Not enough members" end
local cmd = d.command.mdadm .. " --create "..rname.." --run --assume-clean --homehost=any --level=" .. rlevel .. " --raid-devices=" .. #rmembers .. " " .. table.concat(rmembers, " ")
local res = luci.util.exec(cmd)
return res
end
d.gen_mdadm_config = function()
if not nixio.fs.access("/etc/config/mdadm") then return end
local uci = require "luci.model.uci"
local x = uci.cursor()
-- delete all array sections
x:foreach("mdadm", "array", function(s) x:delete("mdadm",s[".name"]) end)
local cmd = d.command.mdadm .. " -D -s"
--ARRAY /dev/md1 metadata=1.2 name=any:1 UUID=f998ae14:37621b27:5c49e850:051f6813
--ARRAY /dev/md3 metadata=1.2 name=any:3 UUID=c068c141:4b4232ca:f48cbf96:67d42feb
for _, v in ipairs(luci.util.execl(cmd)) do
local device, uuid = v:match("^ARRAY%s-([^%s]+)%s-[^%s]-%s-[^%s]-%s-UUID=([^%s]+)%s-")
if device and uuid then
local section_name = x:add("mdadm", "array")
x:set("mdadm", section_name, "device", device)
x:set("mdadm", section_name, "uuid", uuid)
end
end
x:commit("mdadm")
-- enable mdadm
luci.util.exec("/etc/init.d/mdadm enable")
end
-- list btrfs filesystem device
-- {uuid={uuid, label, members, size, used}...}
d.list_btrfs_devices = function()
local btrfs_device = {}
if not d.command.btrfs then return btrfs_device end
local line, _uuid
for _, line in ipairs(luci.util.execl(d.command.btrfs .. " filesystem show -d --raw"))
do
local label, uuid = line:match("^Label:%s+([^%s]+)%s+uuid:%s+([^%s]+)")
if label and uuid then
_uuid = uuid
local _label = label:match("^'([^']+)'")
btrfs_device[_uuid] = {label = _label or label, uuid = uuid}
-- table.insert(btrfs_device, {label = label, uuid = uuid})
end
local used = line:match("Total devices[%w%s]+used%s+(%d+)$")
if used then
btrfs_device[_uuid]["used"] = tonumber(used)
btrfs_device[_uuid]["used_formated"] = byte_format(tonumber(used))
end
local size, device = line:match("devid[%w.%s]+size%s+(%d+)[%w.%s]+path%s+([^%s]+)$")
if size and device then
btrfs_device[_uuid]["size"] = btrfs_device[_uuid]["size"] and btrfs_device[_uuid]["size"] + tonumber(size) or tonumber(size)
btrfs_device[_uuid]["size_formated"] = byte_format(btrfs_device[_uuid]["size"])
btrfs_device[_uuid]["members"] = btrfs_device[_uuid]["members"] and btrfs_device[_uuid]["members"]..", "..device or device
end
end
return btrfs_device
end
d.create_btrfs = function(blabel, blevel, bmembers)
-- mkfs.btrfs -L label -d blevel /dev/sda /dev/sdb
if not d.command.btrfs or type(bmembers) ~= "table" or next(bmembers) == nil then return "ERR no btrfs support or no members" end
local label = blabel and " -L " .. blabel or ""
local cmd = "mkfs.btrfs -f " .. label .. " -d " .. blevel .. " " .. table.concat(bmembers, " ")
return luci.util.exec(cmd)
end
-- get btrfs info
-- {uuid, label, members, data_raid_level,metadata_raid_lavel, size, used, size_formated, used_formated, free, free_formated, usage}
d.get_btrfs_info = function(m_point)
local btrfs_info = {}
if not m_point or not d.command.btrfs then return btrfs_info end
local cmd = d.command.btrfs .. " filesystem show --raw " .. m_point
local _, line, uuid, _label, members
for _, line in ipairs(luci.util.execl(cmd)) do
if not uuid and not _label then
_label, uuid = line:match("^Label:%s+([^%s]+)%s+uuid:%s+([^s]+)")
else
local mb = line:match("%s+devid.+path%s+([^%s]+)")
if mb then
members = members and (members .. ", ".. mb) or mb
end
end
end
if not _label or not uuid then return btrfs_info end
local label = _label:match("^'([^']+)'")
cmd = d.command.btrfs .. " filesystem usage -b " .. m_point
local used, free, data_raid_level, metadata_raid_lavel
for _, line in ipairs(luci.util.execl(cmd)) do
if not used then
used = line:match("^%s+Used:%s+(%d+)")
elseif not free then
free = line:match("^%s+Free %(estimated%):%s+(%d+)")
elseif not data_raid_level then
data_raid_level = line:match("^Data,%s-(%w+)")
elseif not metadata_raid_lavel then
metadata_raid_lavel = line:match("^Metadata,%s-(%w+)")
end
end
if used and free and data_raid_level and metadata_raid_lavel then
used = tonumber(used)
free = tonumber(free)
btrfs_info = {
uuid = uuid,
label = label,
data_raid_level = data_raid_level,
metadata_raid_lavel = metadata_raid_lavel,
used = used,
free = free,
size = used + free,
size_formated = byte_format(used + free),
used_formated = byte_format(used),
free_formated = byte_format(free),
members = members,
usage = string.format("%.2f",(used / (free+used) * 100)) .. "%"
}
end
return btrfs_info
end
-- get btrfs subvolume
-- {id={id, gen, top_level, path, snapshots, otime, default_subvolume}...}
d.get_btrfs_subv = function(m_point, snapshot)
local subvolume = {}
if not m_point or not d.command.btrfs then return subvolume end
-- get default subvolume
local cmd = d.command.btrfs .. " subvolume get-default " .. m_point
local res = luci.util.exec(cmd)
local default_subvolume_id = res:match("^ID%s+([^%s]+)")
-- get the root subvolume
if not snapshot then
local _, line, section_snap, _uuid, _otime, _id, _snap
cmd = d.command.btrfs .. " subvolume show ".. m_point
for _, line in ipairs(luci.util.execl(cmd)) do
if not section_snap then
if not _uuid then
_uuid = line:match("^%s-UUID:%s+([^%s]+)")
elseif not _otime then
_otime = line:match("^%s+Creation time:%s+(.+)")
elseif not _id then
_id = line:match("^%s+Subvolume ID:%s+([^%s]+)")
elseif line:match("^%s+(Snapshot%(s%):)") then
section_snap = true
end
else
local snapshot = line:match("^%s+(.+)")
if snapshot then
_snap = _snap and (_snap ..", /".. snapshot) or ("/"..snapshot)
end
end
end
if _uuid and _otime and _id then
subvolume["0".._id] = {id = _id , uuid = _uuid, otime = _otime, snapshots = _snap, path = "/"}
if default_subvolume_id == _id then
subvolume["0".._id].default_subvolume = 1
end
end
end
-- get subvolume of btrfs
cmd = d.command.btrfs .. " subvolume list -gcu" .. (snapshot and "s " or " ") .. m_point
for _, line in ipairs(luci.util.execl(cmd)) do
-- ID 259 gen 11 top level 258 uuid 26ae0c59-199a-cc4d-bd58-644eb4f65d33 path 1a/2b'
local id, gen, top_level, uuid, path, otime, otime2
if snapshot then
id, gen, top_level, otime, otime2, uuid, path = line:match("^ID%s+([^%s]+)%s+gen%s+([^%s]+)%s+cgen.-top level%s+([^%s]+)%s+otime%s+([^%s]+)%s+([^%s]+)%s+uuid%s+([^%s]+)%s+path%s+([^%s]+)%s-$")
else
id, gen, top_level, uuid, path = line:match("^ID%s+([^%s]+)%s+gen%s+([^%s]+)%s+cgen.-top level%s+([^%s]+)%s+uuid%s+([^%s]+)%s+path%s+([^%s]+)%s-$")
end
if id and gen and top_level and uuid and path then
subvolume[id] = {id = id, gen = gen, top_level = top_level, otime = (otime and otime or "") .." ".. (otime2 and otime2 or ""), uuid = uuid, path = '/'.. path}
if not snapshot then
-- use btrfs subv show to get snapshots
local show_cmd = d.command.btrfs .. " subvolume show "..m_point.."/"..path
local __, line_show, section_snap
for __, line_show in ipairs(luci.util.execl(show_cmd)) do
if not section_snap then
local create_time = line_show:match("^%s+Creation time:%s+(.+)")
if create_time then
subvolume[id]["otime"] = create_time
elseif line_show:match("^%s+(Snapshot%(s%):)") then
section_snap = "true"
end
else
local snapshot = line_show:match("^%s+(.+)")
subvolume[id]["snapshots"] = subvolume[id]["snapshots"] and (subvolume[id]["snapshots"] .. ", /".. snapshot) or ("/"..snapshot)
end
end
end
end
end
if subvolume[default_subvolume_id] then
subvolume[default_subvolume_id].default_subvolume = 1
end
-- if m_point == "/tmp/.btrfs_tmp" then
-- luci.util.exec("umount " .. m_point)
-- end
return subvolume
end
d.format_partition = function(partition, fs)
local partition_name = "/dev/".. partition
if not nixio.fs.access(partition_name) then
return 500, "Partition NOT found!"
end
local format_cmd = d.get_format_cmd()
if not format_cmd[fs] then
return 500, "Filesystem NOT support!"
end
local cmd = format_cmd[fs].cmd .. " " .. format_cmd[fs].option .. " " .. partition_name
local res = luci.util.exec(cmd .. " 2>&1")
if res and res:lower():match("error+") then
return 500, res
else
return 200, "OK"
end
end
return d

View File

@ -0,0 +1,7 @@
<%+cbi/valueheader%>
<% if self:cfgvalue(section) ~= false then %>
<input class="cbi-button cbi-button-<%=self.inputstyle or "button" %>" type="submit"<%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> <% if self.view_disabled then %> disabled <% end %>/>
<% else %>
-
<% end %>
<%+cbi/valuefooter%>

View File

@ -0,0 +1,7 @@
<%+cbi/valueheader%>
<% if self:cfgvalue(section) ~= false then %>
<input class="cbi-button cbi-button-<%=self.inputstyle or "button" %>" onclick="event.preventDefault();partition_format('<%=self.partitions[section].name%>', '<%=self.format_cmd%>', '<%=self.inputtitle%>');" type="submit"<%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> <% if self.view_disabled then %> disabled <% end %>/>
<% else %>
-
<% end %>
<%+cbi/valuefooter%>

View File

@ -0,0 +1,7 @@
<div style="display: inline-block;">
<% if self:cfgvalue(section) ~= false then %>
<input class="cbi-button cbi-button-<%=self.inputstyle or "button" %>" type="submit"" <% if self.disable then %>disabled <% end %><%= attr("name", cbid) .. attr("id", cbid) .. attr("value", self.inputtitle or self.title)%> />
<% else %>
-
<% end %>
</div>

View File

@ -0,0 +1,37 @@
<div class="cbi-section" id="cbi-<%=self.config%>-section">
<% if self.title and #self.title > 0 then -%>
<legend><%=self.title%></legend>
<%- end %>
<% if self.description and #self.description > 0 then -%>
<div class="cbi-section-descr"><%=self.description%></div>
<%- end %>
<div class="cbi-section-node">
<div id="cbi-<%=self.config%>-<%=tostring(self):sub(8)%>">
<% self:render_children(1, scope or {}) %>
</div>
<% if self.error and self.error[1] then -%>
<div class="cbi-section-error">
<ul><% for _, e in ipairs(self.error[1]) do -%>
<li>
<%- if e == "invalid" then -%>
<%:One or more fields contain invalid values!%>
<%- elseif e == "missing" then -%>
<%:One or more required fields have no value!%>
<%- else -%>
<%=pcdata(e)%>
<%- end -%>
</li>
<%- end %></ul>
</div>
<%- end %>
</div>
</div>
<%-
if type(self.hidden) == "table" then
for k, v in pairs(self.hidden) do
-%>
<input type="hidden" id="<%=k%>" name="<%=k%>" value="<%=pcdata(v)%>" />
<%-
end
end
%>

View File

@ -0,0 +1,87 @@
<% if not self.embedded then %>
<form method="post" enctype="multipart/form-data" action="<%=REQUEST_URI%>"<%=
attr("data-strings", luci.util.serialize_json({
label = {
choose = translate('-- Please choose --'),
custom = translate('-- custom --'),
},
path = {
resource = resource,
browser = url("admin/filebrowser")
}
}))
%>>
<input type="hidden" name="token" value="<%=token%>" />
<input type="hidden" name="cbi.submit" value="1" /><%
end
%><div class="cbi-map" id="cbi-<%=self.config%>"><%
if self.title and #self.title > 0 then
%><h2 name="content"><%=self.title%></h2><%
end
if self.description and #self.description > 0 then
%><div class="cbi-map-descr"><%=self.description%></div><%
end
if self.message then
%><div class="alert-message notice"><%=self.message%></div><%
end
if self.errmessage then
%><div class="alert-message warning"><%=self.errmessage%></div><%
end
self:render_children()
%></div><%
if not self.embedded then
if type(self.hidden) == "table" then
local k, v
for k, v in pairs(self.hidden) do
%><input type="hidden" id="<%=k%>" name="<%=k%>" value="<%=pcdata(v)%>" /><%
end
end
local display_back = (self.redirect)
local display_cancel = (self.cancel ~= false and self.on_cancel)
local display_skip = (self.flow and self.flow.skip)
local display_submit = (self.submit ~= false)
local display_reset = (self.reset ~= false)
if display_back or display_cancel or display_skip or display_submit or display_reset then
%><div class="cbi-page-actions"><%
if display_back then
%><input class="btn cbi-button cbi-button-link" type="button" value="<%:Back to Overview%>" onclick="location.href='<%=pcdata(self.redirect)%>'" /> <%
end
if display_cancel then
local label = pcdata(self.cancel or translate("Cancel"))
%><input class="btn cbi-button cbi-button-link" type="button" value="<%=label%>" onclick="cbi_submit(this, 'cbi.cancel')" /> <%
end
if display_skip then
%><input class="btn cbi-button cbi-button-neutral" type="button" value="<%:Skip%>" onclick="cbi_submit(this, 'cbi.skip')" /> <%
end
if display_submit then
local label = pcdata(self.submit or translate("Submit"))
%><input class="btn cbi-button cbi-button-save" type="submit" value="<%=label%>" /> <%
end
if display_reset then
local label = pcdata(self.reset or translate("Reset"))
%><input class="btn cbi-button cbi-button-reset" type="reset" value="<%=label%>" /> <%
end
%></div><%
end
%></form><%
end
%>
<script type="text/javascript">cbi_init();</script>

View File

@ -0,0 +1,110 @@
<script type="text/javascript">
window.onload = function () {
//disk partition info
let p_colors = ["#c0c0ff", "#fbbd00", "#e97c30", "#a0e0a0", "#e0c0ff"]
let lines = document.querySelectorAll('[id^=cbi-disk-]')
lines.forEach((item) => {
let dev = item.id.match(/cbi-disk-(.*)/)[1]
if (dev == "table") { return }
XHR.get('<%=luci.dispatcher.build_url("admin/system/diskman/get_disk_info")%>/' + dev, null, (x, disk_info) => {
// handle disk info
item.childNodes.forEach((cell) => {
if (cell && cell.attributes) {
if (cell.getAttribute("data-name") == "sn" || cell.childNodes[1] && cell.childNodes[1].id.match(/sn/)) {
cell.innerText = disk_info.sn || "-"
} else if (cell.getAttribute("data-name") == "temp" || cell.childNodes[1] && cell.childNodes[1].id.match(/temp/)) {
cell.innerText = disk_info.temp || "-"
} else if (cell.getAttribute("data-name") == "p_table" || cell.childNodes[1] && cell.childNodes[1].id.match(/p_table/)) {
cell.innerText = disk_info.p_table || "-"
} else if (cell.getAttribute("data-name") == "sata_ver" || cell.childNodes[1] && cell.childNodes[1].id.match(/sata_ver/)) {
cell.innerText = disk_info.sata_ver || "-"
} else if (cell.getAttribute("data-name") == "health_status" || cell.childNodes[1] && cell.childNodes[1].id.match(/health_status/)) {
cell.innerText = (disk_info.health || "-") + "\n" + (disk_info.status || "-")
} else if (cell.getAttribute("data-name") == "health" || cell.childNodes[1] && cell.childNodes[1].id.match(/health/)) {
cell.innerText = disk_info.health || "-"
} else if (cell.getAttribute("data-name") == "status" || cell.childNodes[1] && cell.childNodes[1].id.match(/status/)) {
cell.innerText = disk_info.status || "-"
}
}
})
// handle partitons info
if (disk_info.partitions && disk_info.partitions.length > 0) {
let partitons_div
if (item.nodeName == "TR") {
partitons_div = '<tr width="100%" style="white-space:nowrap;"><td style="margin:0px; padding:0px; border:0px; white-space:nowrap;" colspan="15">'
} else if (item.nodeName == "DIV") {
partitons_div = '<div class="tr cbi-section-table-row cbi-rowstyle-1"><div style="white-space:nowrap; position:absolute; width:100%">'
}
let expand = 0
let need_expand = 0
disk_info.partitions.forEach((part) => {
let p = part.size / disk_info.size * 100
if (p <= 8) {
expand += 8
need_expand += p
part.part_percent = 8
}
})
let n = 0
disk_info.partitions.forEach((part) => {
let p = part.size / disk_info.size * 100
if (p > 8) {
part.part_percent = p * (100 - expand) / (100 - need_expand)
}
let part_percent = part.part_percent + '%'
let p_color = p_colors[n++]
if (n > 4) { n = 0 }
let inline_txt = (part.name != '-' && part.name || '') + ' ' + (part.fs != 'Free Space' && part.fs || '') + ' ' + part.size_formated + ' ' + (part.useage != '-' && part.useage || '')
let partiton_div = '<div title="' + inline_txt + '" style="color: #525F7F; display:inline-block; text-align:center;background-color:' + p_color + '; width:' + part_percent + '">' + inline_txt + '</div>'
partitons_div += partiton_div
})
if (item.nodeName == "TR") {
partitons_div += '</td></tr>'
} else if (item.nodeName == "DIV") {
partitons_div += '</div><div>&nbsp</div></div>'
}
item.insertAdjacentHTML('afterend', partitons_div);
}
})
})
//raid table
lines = document.querySelectorAll('[id^=cbi-_raid-]')
lines.forEach((item) => {
let dev = item.id.match(/cbi-_raid-(.*)/)[1]
if (dev == "table") { return }
console.log(dev)
XHR.get('<%=luci.dispatcher.build_url("admin/system/diskman/get_disk_info")%>/' + dev, null, (x, disk_info) => {
// handle raid info
item.childNodes.forEach((cell) => {
if (cell && cell.attributes) {
if (cell.getAttribute("data-name") == "p_table" || cell.childNodes[1] && cell.childNodes[1].id.match(/p_table/)) {
cell.innerText = disk_info.p_table || "-"
}
}
})
// handle partitons info
let partitons_div
if (item.nodeName == "TR") {
partitons_div = '<tr width="100%" style="white-space:nowrap;"><td style="margin:0px; padding:0px; border:0px; white-space:nowrap;" colspan="15">'
} else if (item.nodeName == "DIV") {
partitons_div = '<div class="tr cbi-section-table-row cbi-rowstyle-1"><div style="white-space:nowrap; position:absolute; width:100%">'
}
let n = 0
disk_info.partitions.forEach((part) => {
let part_percent = part.size / disk_info.size * 100 + '%'
let p_color = p_colors[n++]
if (n > 4) { n = 0 }
let inline_txt = (part.name != '-' && part.name || '') + ' ' + (part.fs != 'Free Space' && part.fs || '') + ' ' + part.size_formated + ' ' + (part.useage != '-' && part.useage || '')
let partiton_div = '<div title="' + inline_txt + '" style="display:inline-block; text-align:center;background-color:' + p_color + '; width:' + part_percent + '">' + inline_txt + '</div>'
partitons_div += partiton_div
})
if (item.nodeName == "TR") {
partitons_div += '</td></tr>'
} else if (item.nodeName == "DIV") {
partitons_div += '</div><div>&nbsp</div></div>'
}
item.insertAdjacentHTML('afterend', partitons_div);
})
})
}
</script>

View File

@ -0,0 +1,138 @@
<style type="text/css">
#dialog_format {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
display: none;
z-index: 20000;
}
#dialog_format .dialog_box {
position: relative;
background: rgba(255, 255, 255);
top: 35%;
width: 40%;
min-width: 20em;
margin: auto;
display: flex;
flex-wrap: wrap;
height:auto;
align-items: center;
}
#dialog_format .dialog_line {
margin-top: .5em;
margin-bottom: .5em;
margin-left: 2em;
margin-right: 2em;
}
#dialog_format .dialog_box>h4,
#dialog_format .dialog_box>p,
#dialog_format .dialog_box>div {
flex-basis: 100%;
}
#dialog_format .dialog_box>img {
margin-right: 1em;
flex-basis: 32px;
}
body.dialog-format-active {
overflow: hidden;
height: 100vh;
}
body.dialog-format-active #dialog_format {
display: block;
}
</style>
<script type="text/javascript">//<![CDATA[
function show_detail(dev, e) {
e.preventDefault()
window.open('<%=luci.dispatcher.build_url("admin/system/diskman/smartdetail")%>/' + dev,
'newwindow', 'height=480,width=800,top=100,left=200,toolbar=no,menubar=no,scrollbars=yes, resizable=no,location=no, status=no')
}
window.onload = function () {
// handle partition table
const init_pt_btn = function() {
const btn_p_table = document.getElementById("widget.cbid.table.1.p_table") || document.getElementById("cbid.table.1.p_table")
if (!btn_p_table) {
setTimeout(init_pt_btn, 500);
return;
}
if (btn_p_table.type == 'hidden')
return;
const btn_p_table_raw_index = btn_p_table.selectedIndex
const val_name = document.getElementById("cbi-table-1-path").innerText.split('/').pop()
btn_p_table.onchange = function () {
let btn_p_table_index = btn_p_table.selectedIndex
if (btn_p_table_index != btn_p_table_raw_index) {
if (confirm("<%:Warnning !! \nTHIS WILL OVERWRITE EXISTING PARTITIONS!! \nModify the partition table?%>")) {
let p_table = btn_p_table.options[btn_p_table_index].value
XHR.get('<%=luci.dispatcher.build_url("admin/system/diskman/mk_p_table")%>', { dev: val_name, p_table: p_table }, (x, res) => {
if (res.code == 0) {
location.reload();
}
}
);
}
else {
}
}
}
};
init_pt_btn();
// handle smartinfo
const url = location.href.split('/')
const dev = url[url.length - 1]
const btn_smart_detail = document.getElementById("cbi-table-1-health")
btn_smart_detail.children[0].onclick = show_detail.bind(this, dev)
}
function close_dialog() {
document.body.classList.remove('dialog-format-active')
document.documentElement.style.overflowY = 'scroll'
}
function do_format(partation_name){
let fs = document.getElementById("filesystem_list").value
let status = document.getElementById("format-status")
if(!fs) {
status.innerHTML = "<%:Please select file system!%>"
return
}
status.innerHTML = "<%:Formatting..%>"
let b = document.getElementById('btn_format')
b.disabled = true
let xhr = new XHR()
xhr.post('<%=luci.dispatcher.build_url("admin/system/diskman/format_partition")%>', { partation_name: partation_name, file_system: fs }, (x, res) => {
if (x.status == 200) {
status.innerHTML = x.statusText
location.reload();
}else{
status.innerHTML = x.statusText
}
})
}
function clear_text(){
let s = document.getElementById('format-status')
s.innerHTML = ""
let b = document.getElementById('btn_format')
b.disabled = false
}
function partition_format(partition_name, format_cmd, current_fs){
let list = ''
format_cmd.split(",").forEach(e => {
list = list + '<option value="'+e+'">'+e+'</option>'
});
document.getElementById('dialog_format') || document.body.insertAdjacentHTML("beforeend", '<div id="dialog_format"><div class="dialog_box"><div class="dialog_line"></div><div class="dialog_line"><span><%:Format partation:%> <b>'+partition_name+'</b></span><br><span id="format-status" style="color: red;"></span></div><div class="dialog_line"><select id="filesystem_list" class="cbi-input-select" onchange="clear_text()">'+list+'</select></div><div class="dialog_line" style="text-align: right;"><input type="button" class="cbi-button cbi-button-apply" id="btn_format" type="submit" value="<%:Format%>" onclick="do_format(`'+partition_name+'`)" /> <input type="button"class="cbi-button cbi-button-reset" type="reset" value="<%:Cancel%>" onclick="close_dialog()" /></div><div class="dialog_line"></div></div></div>>')
document.body.classList.add('dialog-format-active')
document.documentElement.style.overflowY = 'hidden'
let fs_list = document.getElementById("filesystem_list")
fs_list.value = current_fs
}
</script>

View File

@ -0,0 +1,78 @@
<html>
<head>
<title>S.M.A.R.T detail of <%=dev%></title>
<script type="text/javascript">//<![CDATA[
let formData = new FormData()
let xhr = new XMLHttpRequest()
xhr.open("GET", '<%=luci.dispatcher.build_url("admin", "system", "diskman", "smartattr", dev)%>', true)
xhr.onload = function () {
let st = JSON.parse(xhr.responseText)
let tb = document.getElementById('smart_attr_table');
if (st && tb) {
/* clear all rows */
while (tb.rows.length > 1)
tb.deleteRow(1);
for (var i = 0; i < st.length; i++) {
var tr = tb.insertRow(-1);
tr.className = 'cbi-section-table-row cbi-rowstyle-' + ((i % 2) + 1);
var td = null
<% if dev: match("nvme") then %>
tr.insertCell(-1).innerHTML = st[i].key;
tr.insertCell(-1).innerHTML = st[i].value;
<% else %>
tr.insertCell(-1).innerHTML = st[i].id;
tr.insertCell(-1).innerHTML = st[i].attrbute;
tr.insertCell(-1).innerHTML = st[i].flag;
tr.insertCell(-1).innerHTML = st[i].value;
tr.insertCell(-1).innerHTML = st[i].worst;
tr.insertCell(-1).innerHTML = st[i].thresh;
tr.insertCell(-1).innerHTML = st[i].type;
tr.insertCell(-1).innerHTML = st[i].updated;
tr.insertCell(-1).innerHTML = st[i].raw;
if ((st[i].id == '05' || st[i].id == 'C5') && st[i].raw != '0') {
tr.style.cssText = "background-color:red !important;";
}
<% end %>
}
if (tb.rows.length == 1) {
var tr = tb.insertRow(-1);
tr.className = 'cbi-section-table-row';
var td = tr.insertCell(-1);
td.colSpan = 4;
td.innerHTML = '<em><br /><%:No Attrbute to display.%></em>';
}
}
}
xhr.send(formData)
//]]></script>
</head>
<body>
<div id="maincontainer">
<fieldset class="cbi-section">
<legend><%:S.M.A.R.T Attrbutes%>: /dev/<%=dev%></legend>
<table class="cbi-section-table" id="smart_attr_table">
<tr class="cbi-section-table-titles">
<% if dev:match("nvme") then %>
<!-- <th class="cbi-section-table-cell"><%:KEY%></th>
<th class="cbi-section-table-cell"><%:VALUE%></th> -->
<% else %>
<th class="cbi-section-table-cell"><%:ID%></th>
<th class="cbi-section-table-cell"><%:Attrbute%></th>
<th class="cbi-section-table-cell"><%:Flag%></th>
<th class="cbi-section-table-cell"><%:Value%></th>
<th class="cbi-section-table-cell"><%:Worst%></th>
<th class="cbi-section-table-cell"><%:Thresh%></th>
<th class="cbi-section-table-cell"><%:Type%></th>
<th class="cbi-section-table-cell"><%:Updated%></th>
<th class="cbi-section-table-cell"><%:Raw%></th>
<% end %>
</tr>
<tr class="cbi-section-table-row">
<td colspan="4"><em><br /><%:Collecting data...%></em></td>
</tr>
</table>
</fieldset>
</div>
</body>
</html>

View File

@ -0,0 +1,239 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "DiskMan"
msgstr "Menadżer dysków"
msgid "Manage Disks over LuCI."
msgstr "Zarządzaj dyskami przez LuCI."
msgid "Rescan Disks"
msgstr "Skanuj dyski"
msgid "Disks"
msgstr "Dyski"
msgid "Path"
msgstr "Ścieżka"
msgid "Serial Number"
msgstr "Numer seryjny"
msgid "Temp"
msgstr "Temperatura"
msgid "Partition Table"
msgstr "Tablica partycji"
msgid "SATA Version"
msgstr "Wersja SATA"
msgid "Health"
msgstr "Kondycja"
msgid "File System"
msgstr "System plików"
msgid "Mount Options"
msgstr "Opcje montowania"
msgid "Mount"
msgstr "Montuj"
msgid "Umount"
msgstr "Odmontuj"
msgid "Eject"
msgstr "Wysuń"
msgid "New"
msgstr "Nowa"
msgid "Remove"
msgstr "Wysuń"
msgid "Format"
msgstr "Formatuj"
msgid "Start Sector"
msgstr "Sektor początkowy"
msgid "End Sector"
msgstr "Sektor końcowy"
msgid "Usage"
msgstr "Wykorzystane"
msgid "Used"
msgstr "Zajęte"
msgid "Free Space"
msgstr "Wolna przestrzeń"
msgid "Model"
msgstr "Model urządzenia"
msgid "Size"
msgstr "Rozmiar"
msgid "Status"
msgstr "Status"
msgid "Mount Point"
msgstr "Punkt montowania"
msgid "Sector Size"
msgstr "Rozmiar sektora"
msgid "Rotation Rate"
msgstr "Obroty"
msgid "RAID Devices"
msgstr "Urządzenia RAID"
msgid "RAID mode"
msgstr "Tryb RAID"
msgid "Members"
msgstr "Członkowie"
msgid "Active"
msgstr "Aktywny"
msgid "RAID Creation"
msgstr "Kreator RAID"
msgid "Raid Name"
msgstr "Nazwa RAID"
msgid "Raid Level"
msgstr "Poziom RAID"
msgid "Raid Member"
msgstr "Członek RAID"
msgid "Create Raid"
msgstr "Utwórz RAID"
msgid "Partition Management"
msgstr "Menadżer partycji"
msgid "Partition Disk over LuCI."
msgstr "Zarządzaj partycjami przez LuCI."
msgid "Device Info"
msgstr "Informacja o urządzeniu"
msgid "Disk Man"
msgstr "Menadżer dysków"
msgid "Partitions Info"
msgstr "Informacja o partycjach"
msgid "Default 2048 sector alignment, support +size{b,k,m,g,t} in End Sector"
msgstr "Sektory wyrównywane są do wielokrotności 2048, sektor końcowy można podać jako rozmiar w postaci +size{b,k,m,g,t}"
msgid "Multiple Devices Btrfs Creation"
msgstr "Kreator urządzeń Btrfs"
msgid "Label"
msgstr "Etykieta"
msgid "Btrfs Label"
msgstr "Etykieta Btrfs"
msgid "Btrfs Raid Level"
msgstr "Poziom Raid Btrfs"
msgid "Btrfs Member"
msgstr "Członek Btrfs"
msgid "Create Btrfs"
msgstr "Utwórz Btrfs"
msgid "New Snapshot"
msgstr "Nowy obraz"
msgid "SubVolumes"
msgstr "Sub-wolumeny"
msgid "Top Level"
msgstr "Top Level"
msgid "Manage Btrfs"
msgstr "Zarządzaj Btrfs"
msgid "Otime"
msgstr "Otime"
msgid "Snapshots"
msgstr "Obrazy"
msgid "Set Default"
msgstr "Ustaw domyślnie"
msgid "Source Path"
msgstr "Ścieżka źródłowa"
msgid "Readonly"
msgstr "Tylko do odczytu"
msgid "Delete"
msgstr "Usuń"
msgid "Create"
msgstr "Utwórz"
msgid "Destination Path (optional)"
msgstr "Ścieżka docelowa (opcjonalnie)"
msgid "Metadata"
msgstr "Metadata"
msgid "Data"
msgstr "Data"
msgid "Btrfs Info"
msgstr "Informacja o Btrfs"
msgid "The source path for create the snapshot"
msgstr "Ścieżka źródłowa do utworzenia obrazu"
msgid "The path where you want to store the snapshot"
msgstr "Ścieżka w której chcesz przechowywać obraz"
msgid "Please input Source Path of snapshot, Source Path must start with '/'"
msgstr "Proszę podać ścieżkę źródłową dla obrazu, ścieżka musi zaczynać się od '/'"
msgid "Please input Subvolume Path, Subvolume must start with '/'"
msgstr "Proszę podać ścieżkę dla sub-wolumenu, sub-wolumen musi zaczynać się od '/'"
msgid "is in use! please unmount it first!"
msgstr "aktualnie jest w użyciu, proszę najpierw odmontować"
msgid "Partition NOT found!"
msgstr "Nie znaleziono partycji"
msgid "Filesystem NOT support!"
msgstr "System plików nie jest obsługiwany"
msgid "Invalid Start Sector!"
msgstr "Nieprawidłowy sektor początkowy"
msgid "Invalid End Sector"
msgstr "Nieprawidłowy sektor końcowy"
msgid "Partition not exists!"
msgstr "Partycja nie istnieje!"
msgid "Creation"
msgstr "Kreator"
msgid "Please select file system!"
msgstr "Proszę wybrać system plików!"
msgid "Format partation:"
msgstr "Format partycji:"
msgid "Warnning !! \nTHIS WILL OVERWRITE EXISTING PARTITIONS!! \nModify the partition table?"
msgstr "Ostrzeżenie !! \nISTNIEJĄCE PARTYCJE ZOSTANĄ NADPISANE!! \nZmodyfikować tablicę partycji?"

1
luci-app-diskman/po/zh-cn Symbolic link
View File

@ -0,0 +1 @@
zh_Hans

View File

@ -0,0 +1,239 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "DiskMan"
msgstr "DiskMan 磁盘管理"
msgid "Manage Disks over LuCI."
msgstr "通过 LuCI 管理磁盘"
msgid "Rescan Disks"
msgstr "重新扫描磁盘"
msgid "Disks"
msgstr "磁盘"
msgid "Path"
msgstr "路径"
msgid "Serial Number"
msgstr "序列号"
msgid "Temp"
msgstr "温度"
msgid "Partition Table"
msgstr "分区表"
msgid "SATA Version"
msgstr "SATA 版本"
msgid "Health"
msgstr "健康"
msgid "File System"
msgstr "文件系统"
msgid "Mount Options"
msgstr "挂载选项"
msgid "Mount"
msgstr "挂载"
msgid "Umount"
msgstr "卸载"
msgid "Eject"
msgstr "弹出"
msgid "New"
msgstr "新建"
msgid "Remove"
msgstr "移除"
msgid "Format"
msgstr "格式化"
msgid "Start Sector"
msgstr "起始扇区"
msgid "End Sector"
msgstr "中止扇区"
msgid "Usage"
msgstr "用量"
msgid "Used"
msgstr "已使用"
msgid "Free Space"
msgstr "空闲空间"
msgid "Model"
msgstr "型号"
msgid "Size"
msgstr "容量"
msgid "Status"
msgstr "状态"
msgid "Mount Point"
msgstr "挂载点"
msgid "Sector Size"
msgstr "扇区/物理扇区大小"
msgid "Rotation Rate"
msgstr "转速"
msgid "RAID Devices"
msgstr "RAID 设备"
msgid "RAID mode"
msgstr "RAID 模式"
msgid "Members"
msgstr "成员"
msgid "Active"
msgstr "活动"
msgid "RAID Creation"
msgstr "RAID 创建"
msgid "Raid Name"
msgstr "RAID 名称"
msgid "Raid Level"
msgstr "RAID 级别"
msgid "Raid Member"
msgstr "磁盘阵列成员"
msgid "Create Raid"
msgstr "创建 RAID"
msgid "Partition Management"
msgstr "分区管理"
msgid "Partition Disk over LuCI."
msgstr "通过LuCI分区磁盘。"
msgid "Device Info"
msgstr "设备信息"
msgid "Disk Man"
msgstr "磁盘管理"
msgid "Partitions Info"
msgstr "分区信息"
msgid "Default 2048 sector alignment, support +size{b,k,m,g,t} in End Sector"
msgstr "默认2048扇区对齐【中止扇区】支持 +容量{b,k,m,g,t} 格式,例:+500m +10g +1t"
msgid "Multiple Devices Btrfs Creation"
msgstr "Btrfs 阵列创建"
msgid "Label"
msgstr "卷标"
msgid "Btrfs Label"
msgstr "Btrfs 卷标"
msgid "Btrfs Raid Level"
msgstr "Btrfs Raid 级别"
msgid "Btrfs Member"
msgstr "Btrfs 阵列成员"
msgid "Create Btrfs"
msgstr "创建 Btrfs"
msgid "New Snapshot"
msgstr "新建快照"
msgid "SubVolumes"
msgstr "子卷"
msgid "Top Level"
msgstr "父ID"
msgid "Manage Btrfs"
msgstr "Btrfs 管理"
msgid "Otime"
msgstr "创建时间"
msgid "Snapshots"
msgstr "快照"
msgid "Set Default"
msgstr "默认子卷"
msgid "Source Path"
msgstr "源目录"
msgid "Readonly"
msgstr "只读"
msgid "Delete"
msgstr "删除"
msgid "Create"
msgstr "创建"
msgid "Destination Path (optional)"
msgstr "目标目录(可选)"
msgid "Metadata"
msgstr "元数据"
msgid "Data"
msgstr "数据"
msgid "Btrfs Info"
msgstr "Btrfs 信息"
msgid "The source path for create the snapshot"
msgstr "创建快照的源数据目录"
msgid "The path where you want to store the snapshot"
msgstr "存放快照数据目录"
msgid "Please input Source Path of snapshot, Source Path must start with '/'"
msgstr "请输入快照源路径,源路径必须以'/'开头"
msgid "Please input Subvolume Path, Subvolume must start with '/'"
msgstr "请输入子卷路径,子卷路径必须以'/'开头"
msgid "is in use! please unmount it first!"
msgstr "正在被使用!请先卸载!"
msgid "Partition NOT found!"
msgstr "分区未找到!"
msgid "Filesystem NOT support!"
msgstr "文件系统不支持!"
msgid "Invalid Start Sector!"
msgstr "无效的起始扇区!"
msgid "Invalid End Sector"
msgstr "无效的终止扇区!"
msgid "Partition not exists!"
msgstr "分区不存在!"
msgid "Creation"
msgstr "创建"
msgid "Please select file system!"
msgstr "请选择文件系统!"
msgid "Format partation:"
msgstr "格式化分区:"
msgid "Warnning !! \nTHIS WILL OVERWRITE EXISTING PARTITIONS!! \nModify the partition table?"
msgstr "警告!!\n此操作会覆盖现有分区\n确定修改分区表"

View File

@ -0,0 +1,239 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "DiskMan"
msgstr "DiskMan 硬碟管理"
msgid "Manage Disks over LuCI."
msgstr "通过 LuCI 管理硬碟"
msgid "Rescan Disks"
msgstr "重新掃描硬碟"
msgid "Disks"
msgstr "硬碟"
msgid "Path"
msgstr "路徑"
msgid "Serial Number"
msgstr "序列號"
msgid "Temp"
msgstr "溫度"
msgid "Partition Table"
msgstr "分割區表"
msgid "SATA Version"
msgstr "SATA 版本"
msgid "Health"
msgstr "健康"
msgid "File System"
msgstr "文件系統"
msgid "Mount Options"
msgstr "掛載選項"
msgid "Mount"
msgstr "掛載"
msgid "Umount"
msgstr "卸載"
msgid "Eject"
msgstr "彈出"
msgid "New"
msgstr "新建"
msgid "Remove"
msgstr "移除"
msgid "Format"
msgstr "格式化"
msgid "Start Sector"
msgstr "起始磁區"
msgid "End Sector"
msgstr "結束磁區"
msgid "Usage"
msgstr "用量"
msgid "Used"
msgstr "已使用"
msgid "Free Space"
msgstr "空閒空間"
msgid "Model"
msgstr "型號"
msgid "Size"
msgstr "容量"
msgid "Status"
msgstr "狀態"
msgid "Mount Point"
msgstr "掛載點"
msgid "Sector Size"
msgstr "磁區/物理磁區大小"
msgid "Rotation Rate"
msgstr "轉速"
msgid "RAID Devices"
msgstr "RAID 裝置"
msgid "RAID mode"
msgstr "RAID 模式"
msgid "Members"
msgstr "成員"
msgid "Active"
msgstr "活躍"
msgid "RAID Creation"
msgstr "RAID 創建"
msgid "Raid Name"
msgstr "RAID 名稱"
msgid "Raid Level"
msgstr "RAID 等級"
msgid "Raid Member"
msgstr "硬碟陣列成員"
msgid "Create Raid"
msgstr "創建 RAID"
msgid "Partition Management"
msgstr "分割區管理"
msgid "Partition Disk over LuCI."
msgstr "通過 LuCI 分割硬碟。"
msgid "Device Info"
msgstr "裝置信息"
msgid "Disk Man"
msgstr "硬碟管理"
msgid "Partitions Info"
msgstr "分割區信息"
msgid "Default 2048 sector alignment, support +size{b,k,m,g,t} in End Sector"
msgstr "默認 2048 磁區對齊,【結束磁區】支持 +容量{b,k,m,g,t} 格式,例:+500m +10g +1t"
msgid "Multiple Devices Btrfs Creation"
msgstr "Btrfs 陣列創建"
msgid "Label"
msgstr "卷標"
msgid "Btrfs Label"
msgstr "Btrfs 卷標"
msgid "Btrfs Raid Level"
msgstr "Btrfs Raid 等級"
msgid "Btrfs Member"
msgstr "Btrfs 陣列成員"
msgid "Create Btrfs"
msgstr "創建 Btrfs"
msgid "New Snapshot"
msgstr "新建快照"
msgid "SubVolumes"
msgstr "子卷"
msgid "Top Level"
msgstr "父 ID"
msgid "Manage Btrfs"
msgstr "Btrfs 管理"
msgid "Otime"
msgstr "創建時間"
msgid "Snapshots"
msgstr "快照"
msgid "Set Default"
msgstr "默認子卷"
msgid "Source Path"
msgstr "源目錄"
msgid "Readonly"
msgstr "只讀"
msgid "Delete"
msgstr "刪除"
msgid "Create"
msgstr "創建"
msgid "Destination Path (optional)"
msgstr "目標目錄(可選)"
msgid "Metadata"
msgstr "元數據"
msgid "Data"
msgstr "數據"
msgid "Btrfs Info"
msgstr "Btrfs 信息"
msgid "The source path for create the snapshot"
msgstr "創建快照的源數據目錄"
msgid "The path where you want to store the snapshot"
msgstr "存放快照的數據目錄"
msgid "Please input Source Path of snapshot, Source Path must start with '/'"
msgstr "請輸入快照源路径,源路径必須以'/'開頭"
msgid "Please input Subvolume Path, Subvolume must start with '/'"
msgstr "請輸入子卷路径,子卷路径必須以'/'開頭"
msgid "is in use! please unmount it first!"
msgstr "正在被使用!請先卸載!"
msgid "Partition NOT found!"
msgstr "分割區未找到!"
msgid "Filesystem NOT support!"
msgstr "文件系統不支持!"
msgid "Invalid Start Sector!"
msgstr "無效的起始磁區!"
msgid "Invalid End Sector"
msgstr "無效的結束磁區!"
msgid "Partition not exists!"
msgstr "分割區不存在!"
msgid "Creation"
msgstr "創建"
msgid "Please select file system!"
msgstr "請選擇文件系統!"
msgid "Format partation:"
msgstr "格式化分割區:"
msgid "Warnning !! \nTHIS WILL OVERWRITE EXISTING PARTITIONS!! \nModify the partition table?"
msgstr "警告!!\n此操作会覆蓋現有分割區\n確定修改分割區表"

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

@ -0,0 +1,16 @@
# 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 netdata
LUCI_DEPENDS:=+netdata
LUCI_PKGARCH:=all
PKG_VERSION:=1.0
PKG_RELEASE:=1
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,9 @@
module("luci.controller.netdata", package.seeall)
function index()
if not (luci.sys.call("pidof netdata > /dev/null") == 0) then
return
end
entry({"admin", "system", "netdata"}, template("netdata"), _("NetData"), 10).leaf = true
end

View File

@ -0,0 +1,9 @@
<%+header%>
<div class="cbi-map">
<h2 name="content"><%=translate("NetData")%></h2>
<iframe id="netdata" style="width: 100%; min-height: 1200px; border: none; border-radius: 3px;"></iframe>
</div>
<script type="text/javascript">
document.getElementById("netdata").src = window.location.protocol + "//" + window.location.hostname + ":19999";
</script>
<%+footer%>

1
luci-app-netdata/po/zh-cn Symbolic link
View File

@ -0,0 +1 @@
zh_Hans

View File

@ -0,0 +1,5 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "NetData"
msgstr "实时监控"

17
luci-app-ramfree/Makefile Normal file
View File

@ -0,0 +1,17 @@
# Copyright (C) 2016 Openwrt.org
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=luci-app-ramfree
LUCI_PKGARCH:=all
PKG_VERSION:=1.0
PKG_RELEASE:=1
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,9 @@
module("luci.controller.release_ram",package.seeall)
function index()
entry({"admin","status","release_ram"}, call("release_ram"), _("Release Ram"), 9999).acl_depends = { "luci-app-ramfree" }
end
function release_ram()
luci.sys.call("sync && echo 3 > /proc/sys/vm/drop_caches")
luci.http.redirect(luci.dispatcher.build_url("admin/status"))
end

1
luci-app-ramfree/po/zh-cn Symbolic link
View File

@ -0,0 +1 @@
zh_Hans

View File

@ -0,0 +1,2 @@
msgid "Release Ram"
msgstr "释放内存"

View File

@ -0,0 +1,11 @@
{
"luci-app-ramfree": {
"description": "Free Your Boom",
"read": {
"uci": ["release_ram"]
},
"write": {
"uci": ["release_ram"]
}
}
}

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

@ -0,0 +1,16 @@
# Copyright (C) 2020 Lienol <lawlienol@gmail.com>
#
# This is free software, licensed under the GNU General Public License v3.
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for Socat
LUCI_DEPENDS:=+socat
LUCI_PKGARCH:=all
PKG_RELEASE:=3
PKG_DATE:=20200824
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,19 @@
-- Copyright 2020 Lienol <lawlienol@gmail.com>
module("luci.controller.socat", package.seeall)
local http = require "luci.http"
function index()
if not nixio.fs.access("/etc/config/socat") then return end
entry({"admin", "network", "socat"}, cbi("socat/index"), _("Socat"), 100).acl_depends = { "luci-app-socat" }
entry({"admin", "network", "socat", "config"}, cbi("socat/config")).leaf = true
entry({"admin", "network", "socat", "status"}, call("status")).leaf = true
end
function status()
local e = {}
e.index = luci.http.formvalue("index")
e.status = luci.sys.call(string.format("ps -w | grep -v 'grep' | grep '/var/etc/socat/%s' >/dev/null", luci.http.formvalue("id"))) == 0
http.prepare_content("application/json")
http.write_json(e)
end

View File

@ -0,0 +1,82 @@
local d = require "luci.dispatcher"
m = Map("socat", translate("Socat Config"))
m.redirect = d.build_url("admin", "network", "socat")
s = m:section(NamedSection, arg[1], "config", "")
s.addremove = false
s.dynamic = false
o = s:option(Flag, "enable", translate("Enable"))
o.default = "1"
o.rmempty = false
o = s:option(Value, "remarks", translate("Remarks"))
o.default = translate("Remarks")
o.rmempty = false
o = s:option(ListValue, "protocol", translate("Protocol"))
o:value("port_forwards", translate("Port Forwards"))
o = s:option(ListValue, "family", translate("Restrict to address family"))
o:value("", translate("IPv4 and IPv6"))
o:value("4", translate("IPv4 only"))
o:value("6", translate("IPv6 only"))
o:depends("protocol", "port_forwards")
o = s:option(ListValue, "proto", translate("Protocol"))
o:value("tcp", "TCP")
o:value("udp", "UDP")
o:depends("protocol", "port_forwards")
o = s:option(Value, "listen_port", translate("Listen port"))
o.datatype = "portrange"
o.rmempty = false
o:depends("protocol", "port_forwards")
o = s:option(Flag, "reuseaddr", "reuseaddr", translate("Bind to a port local"))
o.default = "1"
o.rmempty = false
o = s:option(ListValue, "dest_proto", translate("Destination Protocol"))
o:value("tcp4", "IPv4-TCP")
o:value("udp4", "IPv4-UDP")
o:value("tcp6", "IPv6-TCP")
o:value("udp6", "IPv6-UDP")
o:depends("protocol", "port_forwards")
dest_ipv4 = s:option(Value, "dest_ipv4", translate("Destination address"))
luci.sys.net.ipv4_hints(function(ip, name)
dest_ipv4:value(ip, "%s (%s)" %{ ip, name })
end)
function dest_ipv4.cfgvalue(self, section)
return m:get(section, "dest_ip")
end
function dest_ipv4.write(self, section, value)
m:set(section, "dest_ip", value)
end
dest_ipv4:depends("dest_proto", "tcp4")
dest_ipv4:depends("dest_proto", "udp4")
dest_ipv6 = s:option(Value, "dest_ipv6", translate("Destination address"))
luci.sys.net.ipv6_hints(function(ip, name)
dest_ipv6:value(ip, "%s (%s)" %{ ip, name })
end)
function dest_ipv6.cfgvalue(self, section)
return m:get(section, "dest_ip")
end
function dest_ipv6.write(self, section, value)
m:set(section, "dest_ip", value)
end
dest_ipv6:depends("dest_proto", "tcp6")
dest_ipv6:depends("dest_proto", "udp6")
o = s:option(Value, "dest_port", translate("Destination port"))
o.datatype = "portrange"
o.rmempty = false
o = s:option(Flag, "firewall_accept", translate("Open firewall port"))
o.default = "1"
o.rmempty = false
return m

View File

@ -0,0 +1,76 @@
local d = require "luci.dispatcher"
local e = luci.model.uci.cursor()
m = Map("socat", translate("Socat"), translate("Socat is a versatile networking tool named after 'Socket CAT', which can be regarded as an N-fold enhanced version of NetCat"))
s = m:section(NamedSection, "global", "global")
s.anonymous = true
s.addremove = false
o = s:option(Flag, "enable", translate("Enable"))
o.rmempty = false
s = m:section(TypedSection, "config", translate("Port Forwards"))
s.anonymous = true
s.addremove = true
s.template = "cbi/tblsection"
s.extedit = d.build_url("admin", "network", "socat", "config", "%s")
function s.filter(e, t)
if m:get(t, "protocol") == "port_forwards" then
return true
end
end
function s.create(e, t)
local uuid = string.gsub(luci.sys.exec("echo -n $(cat /proc/sys/kernel/random/uuid)"), "-", "")
t = uuid
TypedSection.create(e, t)
luci.http.redirect(e.extedit:format(t))
end
function s.remove(e, t)
e.map.proceed = true
e.map:del(t)
luci.http.redirect(d.build_url("admin", "network", "socat"))
end
o = s:option(Flag, "enable", translate("Enable"))
o.width = "5%"
o.rmempty = false
o = s:option(DummyValue, "status", translate("Status"))
o.template = "socat/status"
o.value = translate("Collecting data...")
o = s:option(DummyValue, "remarks", translate("Remarks"))
o = s:option(DummyValue, "family", translate("Listen Protocol"))
o.cfgvalue = function(t, n)
local listen = Value.cfgvalue(t, n) or ""
local protocol = (m:get(n, "proto") or ""):upper()
if listen == "" then
return protocol
else
return "IPv" .. listen .. "-" .. protocol
end
end
o = s:option(DummyValue, "listen_port", translate("Listen port"))
o = s:option(DummyValue, "dest_proto", translate("Destination Protocol"))
o.cfgvalue = function(t, n)
local listen = Value.cfgvalue(t, n)
local protocol = listen:sub(0, #listen - 1):upper()
local ip_type = "IPv" .. listen:sub(#listen)
return ip_type .. "-" .. protocol
end
o = s:option(DummyValue, "dest_ip", translate("Destination address"))
o = s:option(DummyValue, "dest_port", translate("Destination port"))
o = s:option(Flag, "firewall_accept", translate("Open firewall port"))
o.default = "1"
o.rmempty = false
m:append(Template("socat/list_status"))
return m

View File

@ -0,0 +1,19 @@
<script type="text/javascript">
//<![CDATA[
var _status = document.getElementsByClassName('_status');
for(var i = 0; i < _status.length; i++) {
var id = _status[i].parentElement.parentElement.parentElement.id;
id = id.substr(id.lastIndexOf("-") + 1);
XHR.poll(1,'<%=url([[admin]], [[network]], [[socat]], [[status]])%>', {
index: i,
id: id
},
function(x, result) {
_status[result.index].setAttribute("style","font-weight:bold;");
_status[result.index].setAttribute("color",result.status ? "green":"red");
_status[result.index].innerHTML = (result.status ? '✓' : 'X');
}
);
}
//]]>
</script>

View File

@ -0,0 +1,3 @@
<%+cbi/valueheader%>
<font class="_status" hint="<%=self:cfgvalue(section)%>">--</font>
<%+cbi/valuefooter%>

1
luci-app-socat/po/zh-cn Symbolic link
View File

@ -0,0 +1 @@
zh_Hans

View File

@ -0,0 +1,50 @@
msgid "Socat"
msgstr "Socat"
msgid "Socat is a versatile networking tool named after 'Socket CAT', which can be regarded as an N-fold enhanced version of NetCat"
msgstr "Socat 是 Linux 下的一个多功能的网络工具名字来由是「Socket CAT」。其功能与有瑞士军刀之称的 Netcat 类似,可以看做是 Netcat 的加强版。"
msgid "Socat Config"
msgstr "Socat 配置"
msgid "Status"
msgstr "状态"
msgid "Enabled"
msgstr "启用"
msgid "Remarks"
msgstr "备注"
msgid "Protocol"
msgstr "协议"
msgid "IPv6 Only"
msgstr "仅IPv6"
msgid "When checked, only IPv6 ports are listen for, otherwise IPv4 will also be listened for."
msgstr "当勾选时仅监听IPv6否则将会同时监听IPv4。"
msgid "Port Forwards"
msgstr "端口转发"
msgid "Listen Protocol"
msgstr "监听协议"
msgid "Listen port"
msgstr "监听端口"
msgid "Bind to a port local"
msgstr "绑定到本地端口"
msgid "Destination Protocol"
msgstr "目标协议"
msgid "Destination address"
msgstr "目标地址"
msgid "Destination port"
msgstr "目标端口"
msgid "Open firewall port"
msgstr "打开防火墙端口"

View File

@ -0,0 +1,3 @@
config global 'global'
option enable '0'

View File

@ -0,0 +1,119 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2020 Lienol <lawlienol@gmail.com>
START=99
CONFIG=socat
CONFIG_PATH=/var/etc/$CONFIG
add_rule() {
iptables -N SOCAT
iptables -I INPUT -j SOCAT
ip6tables -N SOCAT
ip6tables -I INPUT -j SOCAT
}
del_rule() {
iptables -D INPUT -j SOCAT 2>/dev/null
iptables -F SOCAT 2>/dev/null
iptables -X SOCAT 2>/dev/null
ip6tables -D INPUT -j SOCAT 2>/dev/null
ip6tables -F SOCAT 2>/dev/null
ip6tables -X SOCAT 2>/dev/null
}
gen_include() {
echo '#!/bin/sh' > /var/etc/$CONFIG.include
extract_rules() {
local _ipt="iptables"
[ "$1" == "6" ] && _ipt="ip6tables"
echo "*$2"
${_ipt}-save -t $2 | grep "SOCAT" | \
sed -e "s/^-A \(INPUT\)/-I \1 1/"
echo 'COMMIT'
}
cat <<-EOF >> /var/etc/$CONFIG.include
iptables-save -c | grep -v "SOCAT" | iptables-restore -c
iptables-restore -n <<-EOT
$(extract_rules 4 filter)
EOT
ip6tables-save -c | grep -v "SOCAT" | ip6tables-restore -c
ip6tables-restore -n <<-EOT
$(extract_rules 6 filter)
EOT
EOF
return 0
}
run_service() {
config_get enable $1 enable
[ "$enable" = "0" ] && return 0
config_get remarks $1 remarks
config_get protocol $1 protocol
config_get family $1 family
config_get proto $1 proto
config_get listen_port $1 listen_port
config_get reuseaddr $1 reuseaddr
config_get dest_proto $1 dest_proto
config_get dest_ip $1 dest_ip
config_get dest_port $1 dest_port
config_get firewall_accept $1 firewall_accept
ln -s /usr/bin/socat ${CONFIG_PATH}/$1
if [ "$reuseaddr" == "1" ]; then
reuseaddr=",reuseaddr"
else
reuseaddr=""
fi
if [ "$family" == "6" ]; then
ipv6only_params=",ipv6-v6only"
else
ipv6only_params=""
fi
# 端口转发
if [ "$protocol" == "port_forwards" ]; then
listen=${proto}${family}
[ "$family" == "" ] && listen=${proto}6
${CONFIG_PATH}/$1 ${listen}-listen:${listen_port}${ipv6only_params}${reuseaddr},fork ${dest_proto}:${dest_ip}:${dest_port} >/dev/null 2>&1 &
fi
[ "$firewall_accept" == "1" ] && {
if [ -z "$family" ] || [ "$family" == "6" ]; then
ip6tables -A SOCAT -p $proto --dport $listen_port -m comment --comment "$remarks" -j ACCEPT
fi
if [ -z "$family" ] || [ "$family" == "4" ]; then
iptables -A SOCAT -p $proto --dport $listen_port -m comment --comment "$remarks" -j ACCEPT
fi
}
}
stop_service() {
ps -w | grep "$CONFIG_PATH/" | grep -v "grep" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
del_rule
rm -rf $CONFIG_PATH /var/etc/$CONFIG.include
}
start() {
enable=$(uci -q get $CONFIG.@global[0].enable)
if [ "$enable" = "0" ];then
stop_service
else
mkdir -p $CONFIG_PATH
add_rule
config_load $CONFIG
config_foreach run_service "config"
gen_include
fi
}
stop() {
stop_service
}
restart() {
stop
start
}

View File

@ -0,0 +1,12 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete firewall.socat
set firewall.socat=include
set firewall.socat.type=script
set firewall.socat.path=/var/etc/socat.include
set firewall.socat.reload=1
EOF
rm -rf /tmp/luci-*cache
exit 0

View File

@ -0,0 +1,11 @@
{
"luci-app-socat": {
"description": "Grant UCI access for luci-app-socat",
"read": {
"uci": [ "socat" ]
},
"write": {
"uci": [ "socat" ]
}
}
}

View File

@ -0,0 +1,4 @@
{
"config": "socat",
"init": [ "socat" ]
}

View File

@ -0,0 +1,17 @@
#
# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Virtual WAN config generator
LUCI_DEPENDS:=+kmod-macvlan +luci-app-mwan3
PKG_VERSION:=2.0
PKG_RELEASE:=25
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
#Makefile for syncdial

View File

@ -0,0 +1,32 @@
module("luci.controller.syncdial", package.seeall)
function index()
if not nixio.fs.access("/etc/config/syncdial") then
return
end
local page
page = entry({"admin", "network", "syncdial"}, cbi("syncdial"), _("多线多拨"),103)
page.dependent = true
page.acl_depends = { "luci-app-syncdial" }
page = entry({"admin", "network", "syncdial", "status"}, call("act_status"))
page.leaf = true
page = entry({"admin", "network", "macvlan_redial"}, call("redial"), nil)
page.leaf = true
end
function act_status()
local e = {}
local mwan3_status = luci.util.exec("mwan3 status")
e.num_online = 0
for _ in mwan3_status:gmatch("tracking is active") do
e.num_online = e.num_online + 1
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function redial()
os.execute("killall -9 pppd")
end

View File

@ -0,0 +1,88 @@
local e = require"nixio.fs"
require("luci.tools.webadmin")
m = Map("syncdial")
m.title = translate("多线多拨")
m.description = translate("使用macvlan驱动创建多个虚拟WAN口支持并发多拨。")
m:section(SimpleSection).template = "syncdial/syncdial_status"
s = m:section(NamedSection, "config", "syncdial", translate("配置"))
s.anonymous = true
o = s:option(Flag, "enabled", translate("启用"))
o.rmempty = false
o = s:option(Flag, "syncon", translate("启用并发多拨"))
o.rmempty = false
o = s:option(ListValue, "dial_type", translate("多拨类型"))
o:value("1", translate("单线多拨"))
o:value("2", translate("双线多拨"))
o.rmempty = false
o = s:option(Value, "wanselect", translate("选择外网接口"))
o.description = translate("指定要多拨的外网接口如wan")
luci.tools.webadmin.cbi_add_networks(o)
o.optional = false
o.rmempty = false
o = s:option(Value, "wannum", translate("虚拟WAN接口数量"))
o.datatype = "and(uinteger,range(0,249))"
o.optional = false
o.default = 1
o = s:option(Flag, "bindwan", translate("绑定物理接口"))
o.rmempty = false
o = s:option(Value, "wanselect2", translate("选择第二个外网接口"))
o.description = translate("<font color=\"red\">指定要多拨的第二个外网接口如wan2</font>")
luci.tools.webadmin.cbi_add_networks(o)
o.optional=false
o:depends("dial_type","2")
o = s:option(Value, "wannum2", translate("第二条线虚拟WAN接口数量"))
o.description = translate("设置第二条线的拨号数")
o.datatype = "and(uinteger,range(0,249))"
o.optional = false
o.default = 1
o:depends("dial_type","2")
o = s:option(Flag, "bindwan2", translate("绑定物理接口"))
o.description = translate("第二条线生成的虚拟接口绑定当前物理接口")
o.rmempty = false
o:depends("dial_type","2")
o = s:option(Flag, "dialchk", translate("启用掉线检测"))
o.rmempty = false
o = s:option(Value, "dialnum", translate("最低在线接口数量"))
o.description = translate("如果在线接口数量小于这个值则重拨。")
o.datatype = "and(uinteger,range(0,248))"
o.optional = false
o.default = 2
o = s:option(Value, "dialnum2", translate("第二条线最低在线接口数量"))
o.description = translate("如果第二条线在线接口数量小于这个值则重拨。")
o.datatype = "range(0,248)"
o.optional = false
o.default = 2
o:depends("dial_type","2")
o = s:option(Value, "dialwait", translate("重拨等待时间"))
o.description = translate("重拨时,接口全部下线后下一次拨号前的等待时间。单位:秒 最小值5秒")
o.datatype = "and(uinteger,min(5))"
o.optional = false
o = s:option(Flag, "old_frame", translate("使用旧的macvlan创建方式"))
o.rmempty = false
o = s:option(Flag, "nomwan", translate("不自动配置MWAN3负载均衡"))
o.description = translate("需要自定义负载均衡设置或者要使用策略路由的用户选择")
o.rmempty = false
o = s:option(DummyValue, "_redial", translate("重新并发拨号"))
o.template = "syncdial/redial_button"
o.width = "10%"
return m

View File

@ -0,0 +1,17 @@
<%+cbi/valueheader%>
<script type="text/javascript" src="<%=resource%>/cbi.js?v=git-17.110.13538-6360059"></script>
<script type="text/javascript">//<![CDATA[
function do_macvlan_redial()
{
XHR.get('<%=luci.dispatcher.build_url("admin", "network", "macvlan_redial")%>', null,
function(x, st)
{
window.location.href='<%=luci.dispatcher.build_url("admin", "network", "network")%>';
}
);
}
//]]></script>
<input class="btn cbi-button cbi-button-save" id="cbi-macvlan_rediag-<%=section%>-action" type="button" value="重新拨号" onclick="do_macvlan_redial()" />
<%+cbi/valuefooter%>

View File

@ -0,0 +1,17 @@
<script type="text/javascript">//<![CDATA[
XHR.poll(3, '<%=luci.dispatcher.build_url("admin", "network", "syncdial", "status")%>', null,
function(x, data) {
var tb = document.getElementById('syncdial_status');
if (data && tb) {
tb.innerHTML = '<b><%:当前在线接口数量:%> ' + data.num_online + '</b>';
}
}
);
//]]>
</script>
<style>.mar-10 {margin-left: 50px; margin-right: 10px;}</style>
<fieldset class="cbi-section">
<p id="syncdial_status">
<em><%:Collecting data...%></em>
</p>
</fieldset>

View File

@ -0,0 +1,278 @@
#!/bin/sh
#macvlan及PPPoE拨号接口配置批量自动生成脚本
#Copyright (C) 2016
. /lib/functions.sh
#检测IP列表
chk_ip_list="www.baidu.com 114.114.114.114 119.29.29.29"
origfirewall=$(uci get firewall.@zone[1].network)
backupdev=$(uci get syncdial.config.devbackup)
norun=$(echo $origfirewall|grep vwan)
nomwan=$(uci get syncdial.config.nomwan)
wanselect=$(uci get syncdial.config.wanselect)
wannum=$(uci get syncdial.config.wannum)
oldframe=$(uci get syncdial.config.old_frame)
bindwan=$(uci -q get syncdial.config.bindwan)
dial_type=$(uci get syncdial.config.dial_type)
[ "$dial_type" -eq 2 ] && {
wanselect2=$(uci get syncdial.config.wanselect2)
[ $? -ne 0 ] && {
logger -t Syncppp "You must select another pppoe interface ! 启用双线多拨必须同时选择第二个外网接口!"
return 0
}
wannum2=$(uci get syncdial.config.wannum2)
[ $? -ne 0 ] && {
logger -t Syncppp "When dualdial is enabled, the number of the second virtual WAN cannot be blank! 启用双线多拨时第二个虚拟WAN接口数不能为空"
return 0
}
bindwan2=$(uci -q get syncdial.config.bindwan2)
}
#添加MWAN负载均衡相关配置
#$1:接口名称
mwan_cfg_add() {
#gen mwan3_interface
uci set mwan3.${1}=interface
uci set mwan3.${1}.enabled=1
uci set mwan3.${1}.count=2
uci set mwan3.${1}.timeout=2
uci set mwan3.${1}.interval=5
uci set mwan3.${1}.down=4
uci set mwan3.${1}.up=1
for i in $chk_ip_list
do
uci add_list mwan3.${1}.track_ip="$i"
done
uci set mwan3.${1}.reliability=1
uci set mwan3.${1}.initial_state=online
uci set mwan3.${1}.family=ipv4
uci set mwan3.${1}.track_method=ping
uci set mwan3.${1}.size=56
uci set mwan3.${1}.failure_interval=5
uci set mwan3.${1}.recovery_interval=5
uci set mwan3.${1}.flush_conntrack=never
#gen mwan3_member
uci set mwan3.${1}_m1_w1=member
uci set mwan3.${1}_m1_w1.interface=${1}
uci set mwan3.${1}_m1_w1.metric=1
uci set mwan3.${1}_m1_w1.weight=1
#gen mwan3_policy
uci add_list mwan3.balanced.use_member=${1}_m1_w1
}
#删除MWAN负载均衡相关配置
#$1:接口名称
mwan_cfg_del() {
uci del mwan3.${1}
uci del mwan3.${1}_m1_w1
uci del_list mwan3.balanced.use_member=${1}_m1_w1
}
#添加macvlan设备
#$1:设虚拟备名称 $2:原始设备名称
macvlan_dev_add() {
uci set network.macvlandev_${1}=device
uci set network.macvlandev_${1}.name=${1}
uci set network.macvlandev_${1}.device=${2}
uci set network.macvlandev_${1}.type=macvlan
}
#添加PPPoE接口
#$1:接口名称 $2:设备名称 $3:账户 $4:密码 $5:网关跃点
pppoe_if_add() {
#gen vwan macaddr
NEW_MACADDR=$(openssl rand -hex 6 | sed 's/\(..\)/\1:/g; s/.$//')
#gen wan if
uci set network.${1}=interface
uci set network.${1}.device=${3}
uci set network.${1}.proto=pppoe
uci set network.${1}.username=${4}
uci set network.${1}.password=${5}
uci set network.${1}.metric=${2}
uci set network.${1}.macaddr=$NEW_MACADDR
#gen firewall
uci add_list firewall.@zone[1].network=${1}
}
orig_firewall_add() {
need_del_rule=`uci -q get firewall.@zone[1].network | awk -F"'" '{print $2}'`
uci del_list firewall.@zone[1].network="$need_del_rule"
for k in $( seq 1 250 )
do
origdev=$(echo $origfirewall | cut -d " " -f$k)
if [ -z "$origdev" ]; then
break
fi
[ -z "$(uci get firewall.@zone[1].network | grep -w $origdev)" ] && uci add_list firewall.@zone[1].network=$origdev
done
}
apply_cfg() {
uci commit
#/etc/init.d/network restart &
logger -t Syncppp "Apply syncdial configuaration."
ifup wan &
killall pppconnectcheck
}
general_config_load() {
config_load 'syncdial'
config_get_bool enabled 'config' 'enabled'
config_get_bool old_frame 'config' 'old_frame'
congig_get_bool dial_type 'config' 'dial_type'
if [ "$enabled" -eq 0 ]; then
if [ "$old_frame" -eq 1 ]; then
mwan_cfg_add $wanselect
if [ "$dial_type" -eq 2 ]; then
mwan_cfg_add $wanselect2
fi
fi
echo "Disabled.Exit now."
apply_cfg
exit 1
fi
config_load 'network'
config_get pppoe_user $wanselect 'username'
config_get pppoe_password $wanselect 'password'
pppoe_device=$(uci get network.$wanselect.device)
[ "$dial_type" -eq 2 ] && {
config_get pppoe_user2 $wanselect2 'username'
config_get pppoe_password2 $wanselect2 'password'
pppoe_device2=$(uci get network.$wanselect2.device)
}
}
check_remove_device() {
local devcfg=${1}
[ ${devcfg::11} == 'macvlandev_' ] && uci del network.${devcfg}
}
check_remove_interface() {
local ifcfg=${1}
[ ${ifcfg::4} == 'vwan' ] && {
uci del network.${ifcfg}
uci del_list firewall.@zone[1].network=${ifcfg}
[ "$nomwan" -ne 1 ] && mwan_cfg_del ${ifcfg}
}
uci set firewall.@zone[1].network="$backupdev"
}
general_config_remove() {
config_load network
config_foreach check_remove_device 'device'
config_foreach check_remove_interface 'interface'
all_macvlans=`ip link show |grep macvlan | awk -F":" '{print $2}' | awk -F"@" '{print $1}'`
[ -n "$all_macvlans" ] && {
for macvlan in $all_macvlans
do
ip link delete $macvlan
done
}
[ "$(uci get network.$wanselect.proto)" == "none" ] && {
uci set network.$wanselect.proto=pppoe
}
if [ "$oldframe" -eq 0 ]; then
[ "$wanselect" != "$(echo $(uci get syncdial.config.devbackup)| cut -d " " -f1)" ] && \
[ "$wanselect" != "$(echo $(uci get syncdial.config.devbackup)| cut -d " " -f2)" ] && \
[ "$nomwan" -ne 1 ] && mwan_cfg_del $wanselect
else
[ "$nomwan" -ne 1 ] && mwan_cfg_del $wanselect
fi
[ "$dial_type" -eq 2 ] && {
[ $(uci get network.$wanselect2.proto) == "none" ] && {
uci set network.$wanselect2.proto=pppoe
}
if [ "$oldframe" -eq 0 ]; then
[ "$wanselect2" != "$(echo $(uci get syncdial.config.devbackup)| cut -d " " -f1)" ] && \
[ "$wanselect2" != "$(echo $(uci get syncdial.config.devbackup)| cut -d " " -f2)" ] && \
[ "$nomwan" -ne 1 ] && mwan_cfg_del $wanselect2
else
[ "$nomwan" -ne 1 ] && mwan_cfg_del $wanselect2
fi
}
}
[ -z "$norun" ] && uci set syncdial.config.devbackup="$origfirewall" && uci commit syncdial
general_config_remove
general_config_load
uci set network.$wanselect.metric=40
if [ "$wannum" -gt 0 ]; then
[ "$old_frame" -eq 1 ] && {
uci set network.$wanselect.proto=none
device=$(uci get network.$wanselect.device)
for i in $(seq 1 $wannum)
do
ip link add link $device name macvlan$i type macvlan
ifconfig macvlan$i hw ether $(echo $(cat /sys/class/net/$device/address|awk -F ":" '{print $1":"$2":"$3":"$4":"$5":" }')$(echo "" | awk -F ":" '{printf("%X\n", 16+i);}' i=$i))
ifconfig macvlan$i up
done
}
[ "$wanselect" != "$(echo $(uci get syncdial.config.devbackup)| cut -d " " -f1)" ] && \
[ "$wanselect" != "$(echo $(uci get syncdial.config.devbackup)| cut -d " " -f2)" ] && \
[ "$old_frame" -eq 0 -a "$nomwan" -ne 1 ] && mwan_cfg_add $wanselect
for i in $(seq 1 $wannum)
do
[ "$old_frame" -eq 0 ] && macvlan_dev_add macvlan$i $pppoe_device
if [ "$bindwan" != "" -a "$bindwan" == "1" ]; then
pppoe_if_add vwan$i $((40+$i)) $pppoe_device $pppoe_user $pppoe_password
else
pppoe_if_add vwan$i $((40+$i)) macvlan$i $pppoe_user $pppoe_password
fi
[ "$nomwan" -ne 1 ] && mwan_cfg_add vwan$i $((40+$i))
done
else
[ "$nomwan" -ne 1 ] && mwan_cfg_add $wanselect
fi
###dualdial configuration
[ "$(uci -q get syncdial.config.dial_type)" = "2" ] && {
uci set network.$wanselect2.metric=60
if [ "$wannum2" -gt 0 ]; then
[ "$old_frame" -eq 1 ] && {
uci set network.$wanselect2.proto=none
device2=$(uci get network.$wanselect2.device)
for i in $(seq 1 $wannum2)
do
ip link add link $device2 name macvlan$(($wannum+$i)) type macvlan
ifconfig macvlan$(($wannum+$i)) hw ether $(echo $(cat /sys/class/net/$device2/address|awk -F ":" '{print $1":"$2":"$3":"$4":"$5":" }')$(echo "" | awk -F ":" '{printf("%X\n", 16+i);}' i=$i))
ifconfig macvlan$(($wannum+$i)) up
done
}
[ "$wanselect2" != "$(echo $(uci get syncdial.config.devbackup)| cut -d " " -f1)" ] && \
[ "$wanselect2" != "$(echo $(uci get syncdial.config.devbackup)| cut -d " " -f2)" ] && \
[ "$old_frame" -eq 0 -a "$nomwan" -ne 1 ] && mwan_cfg_add $wanselect2
for i in $(seq 1 $wannum2)
do
[ "$old_frame" -eq 0 ] && macvlan_dev_add macvlan$(($wannum+$i)) $pppoe_device2
if [ "$bindwan2" != "" -a "$bindwan2" == "1" ]; then
pppoe_if_add vwan$(($wannum+$i)) $((60+$i)) $pppoe_device2 $pppoe_user2 $pppoe_password2
else
pppoe_if_add vwan$(($wannum+$i)) $((60+$i)) macvlan$(($wannum+$i)) $pppoe_user2 $pppoe_password2
fi
[ "$nomwan" -ne 1 ] && mwan_cfg_add vwan$(($wannum+$i)) $((60+$i))
done
else
[ "$nomwan" -ne 1 ] && mwan_cfg_add $wanselect2
fi
}
orig_firewall_add
apply_cfg
return 0

View File

@ -0,0 +1,19 @@
config syncdial 'config'
option syncon '1'
option dialwait '25'
option dialchk '1'
option nomwan '0'
option wanselect 'wan'
option dial_type '2'
option wannum '3'
option wanselect2 'wan2'
option wannum2 '2'
option dialnum '3'
option dialnum2 '2'
option old_frame '1'
option devbackup 'wan wan6 wan2'
option bindwan '0'
option bindwan2 '0'
option enabled '0'

View File

@ -0,0 +1,36 @@
#!/bin/sh
[ "$ACTION" = "ifdown" ] && pppconnectcheck &
wanselect=$(uci get syncdial.config.wanselect)
[ "$(uci get syncdial.config.dial_type)" = "2" ] && {
wanselect2=$(uci get syncdial.config.wanselect2)
}
[ "$(uci get syncdial.config.enabled)" = "1" ] && \
[ "$(uci get syncdial.config.old_frame)" = "1" ] && \
[ "$DEVICE" = "$(uci get network.$wanselect.device)" ] && \
[ "$ACTION" = "ifup" ] && {
device=$(uci get network.$wanselect.device)
wannum=$(uci get syncdial.config.wannum)
for i in $(seq 1 $wannum)
do
[ -d /sys/class/net/macvlan$i ] || {
ip link add link $device name macvlan$i type macvlan
ifconfig macvlan$i hw ether $(echo $(cat /sys/class/net/$device/address|awk -F ":" '{print $1":"$2":"$3":"$4":"$5":" }')$(echo "" | awk -F ":" '{printf("%X\n", 16+i);}' i=$i))
ifconfig macvlan$i up
}
done
[ "$(uci get syncdial.config.dial_type)" = "2" ] && {
device2=$(uci get network.$wanselect2.device)
wannum2=$(uci get syncdial.config.wannum2)
for i in $(seq 1 $wannum2)
do
[ -d /sys/class/net/macvlan$(($wannum+$i)) ] || {
ip link add link $device2 name macvlan$(($wannum+$i)) type macvlan
ifconfig macvlan$(($wannum+$i)) hw ether $(echo $(cat /sys/class/net/$device2/address|awk -F ":" '{print $1":"$2":"$3":"$4":"$5":" }')$(echo "" | awk -F ":" '{printf("%X\n", 16+i);}' i=$i))
ifconfig macvlan$(($wannum+$i)) up
}
done
}
}

View File

@ -0,0 +1,16 @@
#!/bin/sh
[ "$(uci get syncdial.config.enabled)" = "1" ] && \
[ "$(uci get syncdial.config.old_frame)" = "1" ] && \
[ "$DEVICE" = "$(uci get network.wan.device)" ] && \
[ "$ACTION" = "ifup" ] && {
device=$(uci get network.wan.device)
wannum=$(uci get syncdial.config.wannum)
for i in $(seq 1 $wannum)
do
[ -d /sys/class/net/macvlan$i ] || {
ip link add link $device name macvlan$i type macvlan
ifconfig macvlan$i hw ether $(echo $(cat /sys/class/net/$device/address|awk -F ":" '{print $1":"$2":"$3":"$4":"$5":" }')$(echo "" | awk -F ":" '{printf("%X\n", 16+i);}' i=$i))
ifconfig macvlan$i up
}
done
}

View File

@ -0,0 +1,12 @@
#!/bin/sh
touch /etc/config/syncdial
uci -q batch <<-EOF >/dev/null
delete ucitrack.@syncdial[-1]
add ucitrack syncdial
set ucitrack.@syncdial[-1].exec='/bin/genwancfg'
commit ucitrack
EOF
rm -f /tmp/luci-indexcache
exit 0

View File

@ -0,0 +1,11 @@
{
"luci-app-syncdial": {
"description": "Grant UCI access for luci-app-syncdial",
"read": {
"uci": [ "syncdial" ]
},
"write": {
"uci": [ "syncdial" ]
}
}
}

View File

@ -0,0 +1,18 @@
#
# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=USB Printer Share via TCP/IP
LUCI_DEPENDS:=+p910nd +kmod-usb-printer
PKG_VERSION:=1.0
PKG_RELEASE:=2
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
#applications/luci-app-usb-printer/
#applications/luci-app-usb-printer/

View File

@ -0,0 +1,6 @@
#!/bin/sh
[ -n "${IPKG_INSTROOT}" ] || {
( . /etc/uci-defaults/luci-usb-printer ) && rm -f /etc/uci-defaults/luci-usb-printer
exit 0
}

View File

@ -0,0 +1,30 @@
--[[
LuCI - Lua Configuration Interface
Copyright 2008 Steven Barth <steven@midlink.org>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
$Id$
]]--
require("luci.sys")
module("luci.controller.usb_printer", package.seeall)
function index()
if not nixio.fs.access("/etc/config/usb_printer") then
return
end
entry({"admin", "nas"}, firstchild(), "NAS", 44).dependent = false
local page
page = entry({"admin", "nas", "usb_printer"}, cbi("usb_printer"), _("USB Printer Server"), 50)
page.acl_depends = { "luci-app-usb-printer" }
end

View File

@ -0,0 +1,130 @@
--[[
LuCI - Lua Configuration Interface
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2005-2013 hackpascal <hackpascal@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
$Id$
]]--
require "luci.util"
local uci = luci.model.uci.cursor_state()
local net = require "luci.model.network"
m = Map("usb_printer", translate("USB Printer Server"),
translate("Shares multiple USB printers via TCP/IP.<br />When modified bingings, re-plug usb connectors to take effect.<br />This module requires kmod-usb-printer."))
function hex_align(hex, num)
local len = num - string.len(hex)
return string.rep("0", len) .. hex
end
function detect_usb_printers()
local data = {}
local lps = luci.util.execi("/usr/bin/detectlp")
for value in lps do
local row = {}
--[[
detectlp
VID/PID/?,
]]--
local pos = string.find(value, ",")
local devname = string.sub(value, 1, pos - 1)
local value = string.sub(value, pos + 1, string.len(value))
pos = string.find(value, ",")
local product = string.sub(value, 1, pos - 1)
value = string.sub(value, pos + 1, string.len(value))
pos = string.find(value, ",")
local model = string.sub(value, 1, pos - 1)
local name = string.sub(value, pos + 1, string.len(value))
pos = string.find(product, "/");
local vid = string.sub(product, 1, pos - 1)
local pid = string.sub(product, pos + 1, string.len(product))
pos = string.find(pid, "/")
pid = string.sub(pid, 1, pos - 1)
row["description"] = name
row["model"] = model
row["id"] = hex_align(vid, 4) .. ":" .. hex_align(pid, 4)
row["name"] = devname
row["product"] = product
table.insert(data, row)
end
return data
end
local printers = detect_usb_printers()
v = m:section(Table, printers, translate("Detected printers"))
v:option(DummyValue, "description", translate("Description"))
v:option(DummyValue, "model", translate("Printer Model"))
v:option(DummyValue, "id", translate("VID/PID"))
v:option(DummyValue, "name", translate("Device Name"))
net = net.init(m.uci)
s = m:section(TypedSection, "printer", translate("Bindings"))
s.addremove = true
s.anonymous = true
s:option(Flag, "enabled", translate("enable"))
d = s:option(Value, "device", translate("Device"))
d.rmempty = true
for key, item in ipairs(printers) do
d:value(item["product"], item["description"] .. " [" .. item["id"] .. "]")
end
b = s:option(Value, "bind", translate("Interface"), translate("Specifies the interface to listen on."))
b.template = "cbi/network_netlist"
b.nocreate = true
b.unspecified = true
function b.cfgvalue(...)
local v = Value.cfgvalue(...)
if v then
return (net:get_status_by_address(v))
end
end
function b.write(self, section, value)
local n = net:get_network(value)
if n and n:ipaddr() then
Value.write(self, section, n:ipaddr())
end
end
p = s:option(ListValue, "port", translate("Port"), translate("TCP listener port."))
p.rmempty = true
for i = 0, 9 do
p:value(i, 9100 + i)
end
s:option(Flag, "bidirectional", translate("Bidirectional mode"))
return m

View File

@ -0,0 +1 @@
zh_Hans

View File

@ -0,0 +1,83 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: zh-Hans\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
#: luasrc/model/cbi/usb_printer.lua:128
msgid "Bidirectional mode"
msgstr "双向模式"
#: luasrc/model/cbi/usb_printer.lua:90
msgid "Bindings"
msgstr "绑定"
#: luasrc/model/cbi/usb_printer.lua:83
msgid "Description"
msgstr ""
#: luasrc/model/cbi/usb_printer.lua:81
msgid "Detected printers"
msgstr "检测到的打印机"
#: luasrc/model/cbi/usb_printer.lua:96
msgid "Device"
msgstr "设备"
#: luasrc/model/cbi/usb_printer.lua:86
msgid "Device Name"
msgstr "设备名"
#: luasrc/model/cbi/usb_printer.lua:103
msgid "Interface"
msgstr ""
#: luasrc/model/cbi/usb_printer.lua:122
msgid "Port"
msgstr "端口"
#: luasrc/model/cbi/usb_printer.lua:84
msgid "Printer Model"
msgstr "打印机型号"
#: luasrc/model/cbi/usb_printer.lua:21
msgid ""
"Shares multiple USB printers via TCP/IP.<br />When modified bingings, re-"
"plug usb connectors to take effect.<br />This module requires kmod-usb-"
"printer."
msgstr ""
"通过 TCP/IP 共享 USB 打印机。<br />修改设置后,请重新连接打印机以使设置生效。"
"<br />此模块需要 kmod-usb-printer 支持。"
#: luasrc/model/cbi/usb_printer.lua:103
msgid "Specifies the interface to listen on."
msgstr "指定要监听的接口。"
#: luasrc/model/cbi/usb_printer.lua:122
msgid "TCP listener port."
msgstr "TCP 监听端口。"
#: luasrc/controller/usb_printer.lua:28 luasrc/model/cbi/usb_printer.lua:20
msgid "USB Printer Server"
msgstr "USB 打印服务器"
#: luasrc/model/cbi/usb_printer.lua:85
msgid "VID/PID"
msgstr ""
#: luasrc/model/cbi/usb_printer.lua:94
msgid "enable"
msgstr "启用"
#~ msgid "Settings"
#~ msgstr "设置"
#~ msgid "NAS"
#~ msgstr "网络存储"
#~ msgid "Architecture"
#~ msgstr "架构"

View File

@ -0,0 +1,65 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-05-18 01:34+0800\n"
"PO-Revision-Date: 2014-05-18 01:34+0800\n"
"Last-Translator: hackpascal <hackpascal@gmail.com>\n"
"Language-Team: \n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Pootle 2.0.6\n"
msgid "Bidirectional mode"
msgstr "双向模式"
msgid "Bindings"
msgstr "绑定"
msgid "Device"
msgstr "设备"
msgid "Device Name"
msgstr "设备名"
msgid "Detected printers"
msgstr "检测到的打印机"
msgid ""
"Shares multiple USB printers via TCP/IP.<br />"
"When modified bingings, re-plug usb connectors to take effect.<br />"
"This module requires kmod-usb-printer."
msgstr ""
"通过 TCP/IP 共享 USB 打印机。<br />修改设置后,请重新连接打印机以使设置生效。<br />"
"此模块需要 kmod-usb-printer 支持。"
msgid "Port"
msgstr "端口"
msgid "Printer Model"
msgstr "打印机型号"
msgid "Settings"
msgstr "设置"
msgid "TCP listener port."
msgstr "TCP 监听端口。"
msgid "enable"
msgstr "启用"
msgid "USB Printer Server"
msgstr "USB 打印服务器"
msgid "Specifies the interface to listen on."
msgstr "指定要监听的接口。"
msgid "NAS"
msgstr "网络存储"
msgid "Architecture"
msgstr "架构"

View File

@ -0,0 +1,7 @@
#!/bin/sh
# Copyright (C) 2005-2014 NowRush Studio
# Author: hackpascal <hackpascal@gmail.com>
if [ x"$INTERFACE" = x"7/1/1" ] || [ x"$INTERFACE" = x"7/1/2" ]; then
/usr/bin/usb_printer_hotplug "$PRODUCT" "$ACTION"
fi

View File

@ -0,0 +1,22 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2005-2013 NowRush Studio
# Author: hackpascal <hackpascal@gmail.com>
START=70
stop() {
killall p910nd 2>/dev/null
}
start() {
for lps in `/usr/bin/detectlp`; do
product=`echo $lps | cut -d , -f 2`
/usr/bin/usb_printer_hotplug "$product" add
done
}
restart() {
stop
start
}

View File

@ -0,0 +1,5 @@
#!/bin/sh
[ -f /etc/init.d/p910nd ] && /etc/init.d/p910nd disable
exit 0

View File

@ -0,0 +1,20 @@
#!/bin/sh
lp_path=/sys/class/usbmisc
if ! [ -d "$lp_path" ]; then
exit
fi
cd $lp_path
for lps in `ls`; do
desc_file=$lp_path/$lps/device/ieee1284_id
uevent_file=$lp_path/$lps/device/uevent
name=`cat $desc_file | sed 's/.*DES:\(.*\);.*/\1/' | cut -d ';' -f 1`
model=`cat $desc_file | sed 's/.*MDL:\(.*\);.*/\1/' | cut -d ';' -f 1`
product=`cat $uevent_file | grep PRODUCT= | sed 's/PRODUCT=\(.*\)/\1/'`
echo $lps,$product,$model,$name;
done

View File

@ -0,0 +1,72 @@
#!/bin/sh
# Copyright (C) 2005-2014 NowRush Studio
# Author: hackpascal <hackpascal@gmail.com>
. $IPKG_INSTROOT/lib/functions.sh
PRODUCT=$1
ACTION=$2
DEVICES=
check_printer() {
local cfg=$1
local enabled
local device_id
local bind_ip
local port
local bidirect
local device_file
local args=""
local pid_file
config_get_bool enabled "$cfg" enabled 0
[ "$enabled" -eq 0 ] && return 0
config_get device_id "$cfg" device ""
config_get bind_ip "$cfg" bind "0.0.0.0"
config_get port "$cfg" port ""
config_get_bool bidirect "$cfg" bidirectional "0"
if [ -z "$device_id" ] || [ -z "$port" ]; then
return
fi
if [ x"$PRODUCT" != x"$device_id" ]; then
return
fi
device_file=`echo "$DEVICES" | grep $device_id | cut -d , -f 1`
if [ "$ACTION" = "add" ] && [ -z "$device_file" ]; then
return
fi
pid_file=/var/run/p910${port}d.pid
[ -f $pid_file ] && kill `cat $pid_file` 2>/dev/null
if [ "$ACTION" = "add" ]; then
if [ "$bidirect" != 0 ]; then
args='-b'
fi
logger "usb_printer: start p910nd on $bind_ip:$port for /dev/usb/$device_file"
/usr/sbin/p910nd $args -f /dev/usb/$device_file -i $bind_ip $port
fi
}
if [ -z "$PRODUCT" ] || [ -z "$ACTION" ]; then
echo "Arguements required"
exit 1
fi
if [ "$ACTION" != "add" ] && [ "$ACTION" != "remove" ]; then
echo "Invalid action arguement"
exit 1
fi
DEVICES=`/usr/bin/detectlp`
config_load usb_printer
config_foreach check_printer printer

View File

@ -0,0 +1,11 @@
{
"luci-app-usb-printer": {
"description": "Grant UCI access for luci-app-usb-printer",
"read": {
"uci": [ "usb_printer" ]
},
"write": {
"uci": [ "usb_printer" ]
}
}
}

View File

@ -0,0 +1,4 @@
{
"config": "usb_printer",
"init": [ "usb_printer" ]
}

View File

@ -0,0 +1,20 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2022 ImmortalWrt.org
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI for Zerotier
LUCI_DEPENDS:=+zerotier +jsonfilter +ucode
LUCI_PKGARCH:=all
define Package/luci-app-zerotier/conffiles
/etc/config/zero/
/etc/config/zerotier
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,94 @@
/* SPDX-License-Identifier: GPL-3.0-only
*
* Copyright (C) 2022 ImmortalWrt.org
*/
'use strict';
'require form';
'require poll';
'require rpc';
'require uci';
'require view';
var callServiceList = rpc.declare({
object: 'service',
method: 'list',
params: ['name'],
expect: { '': {} }
});
function getServiceStatus() {
return L.resolveDefault(callServiceList('zerotier'), {}).then(function (res) {
var isRunning = false;
try {
isRunning = res['zerotier']['instances']['instance1']['running'];
} catch (e) { }
return isRunning;
});
}
function renderStatus(isRunning) {
var spanTemp = '<em><span style="color:%s"><strong>%s %s</strong></span></em>';
var renderHTML;
if (isRunning) {
renderHTML = String.format(spanTemp, 'green', _('ZeroTier'), _('RUNNING'));
} else {
renderHTML = String.format(spanTemp, 'red', _('ZeroTier'), _('NOT RUNNING'));
}
return renderHTML;
}
return view.extend({
load: function() {
return Promise.all([
uci.load('zerotier')
]);
},
render: function(data) {
var m, s, o;
m = new form.Map('zerotier', _('ZeroTier'),
_('ZeroTier is an open source, cross-platform and easy to use virtual LAN.'));
s = m.section(form.TypedSection);
s.anonymous = true;
s.render = function () {
poll.add(function () {
return L.resolveDefault(getServiceStatus()).then(function (res) {
var view = document.getElementById("service_status");
view.innerHTML = renderStatus(res);
});
});
return E('div', { class: 'cbi-section', id: 'status_bar' }, [
E('p', { id: 'service_status' }, _('Collecting data ...'))
]);
}
s = m.section(form.NamedSection, 'sample_config', 'config');
o = s.option(form.Flag, 'enabled', _('Enable'));
o.default = o.disabled;
o.rmempty = false;
o = s.option(form.DynamicList, 'join', _('Network ID'));
o.rmempty = false;
o = s.option(form.Flag, 'nat', _('Auto NAT clients'),
_('Allow ZeroTier clients access your LAN network.'));
o.default = o.disabled;
o.rmempty = false;
o = s.option(form.Button, '_panel', _('ZeroTier Central'),
_('Create or manage your ZeroTier network, and auth clients who could access.'));
o.inputtitle = _('Open website');
o.inputstyle = 'apply';
o.onclick = function () {
window.open("https://my.zerotier.com/network", '_blank');
}
return m.render();
}
});

View File

@ -0,0 +1,119 @@
/* SPDX-License-Identifier: GPL-3.0-only
*
* Copyright (C) 2022 ImmortalWrt.org
*/
'use strict';
'require fs';
'require ui';
'require view';
return view.extend({
load: function() {
return fs.exec('/sbin/ifconfig').then(function(res) {
if (res.code !== 0 || !res.stdout || res.stdout.trim() === '') {
ui.addNotification(null, E('p', {}, _('Unable to get interface info: %s.').format(res.message)));
return '';
}
var interfaces = res.stdout.match(/zt[a-z0-9]+/g);
if (!interfaces || interfaces.length === 0)
return 'No interface online.';
var promises = interfaces.map(function(name) {
return fs.exec('/sbin/ifconfig', [name]);
});
return Promise.all(promises).then(function(results) {
var data = results.map(function(res, index) {
if (res.code !== 0 || !res.stdout || res.stdout.trim() === '') {
ui.addNotification(null, E('p', {}, _('Unable to get interface %s info: %s.').format(interfaces[index], res.message)));
return null;
}
return {
name: interfaces[index],
stdout: res.stdout.trim()
};
}).filter(Boolean);
return data.map(function(info) {
var lines = info.stdout.split('\n');
var parsedInfo = {
name: info.name
};
lines.forEach(function(line) {
if (line.includes('HWaddr')) {
parsedInfo.mac = line.split('HWaddr')[1].trim().split(' ')[0];
} else if (line.includes('inet addr:')) {
parsedInfo.ipv4 = line.split('inet addr:')[1].trim().split(' ')[0];
} else if (line.includes('inet6 addr:')) {
parsedInfo.ipv6 = line.split('inet6 addr:')[1].trim().split('/')[0];
} else if (line.includes('MTU:')) {
parsedInfo.mtu = line.split('MTU:')[1].trim().split(' ')[0];
} else if (line.includes('RX bytes:')) {
var rxMatch = line.match(/RX bytes:\d+ \(([\d.]+\s*[a-zA-Z]+)\)/);
if (rxMatch && rxMatch[1]) {
parsedInfo.rxBytes = rxMatch[1];
}
var txMatch = line.match(/TX bytes:\d+ \(([\d.]+\s*[a-zA-Z]+)\)/);
if (txMatch && txMatch[1]) {
parsedInfo.txBytes = txMatch[1];
}
}
});
return parsedInfo;
});
});
});
},
render: function(data) {
var title = E('h2', {class: 'content'}, _('ZeroTier'));
var desc = E('div', {class: 'cbi-map-descr'}, _('ZeroTier is an open source, cross-platform and easy to use virtual LAN.'));
if (!Array.isArray(data)) {
return E('div', {}, [title, desc, E('div', {}, _('No interface online.'))]);
}
var rows = data.flatMap(function(interfaceData) {
return [
E('th', {class: 'th', colspan: '2'}, _('Network Interface Information')),
E('tr', {class: 'tr'}, [
E('td', {class: 'td left', width: '25%'}, _('Interface Name')),
E('td', {class: 'td left', width: '25%'}, interfaceData.name)
]),
E('tr', {class: 'tr'}, [
E('td', {class: 'td left', width: '25%'}, _('MAC Address')),
E('td', {class: 'td left', width: '25%'}, interfaceData.mac)
]),
E('tr', {class: 'tr'}, [
E('td', {class: 'td left', width: '25%'}, _('IPv4 Address')),
E('td', {class: 'td left', width: '25%'}, interfaceData.ipv4)
]),
E('tr', {class: 'tr'}, [
E('td', {class: 'td left', width: '25%'}, _('IPv6 Address')),
E('td', {class: 'td left', width: '25%'}, interfaceData.ipv6)
]),
E('tr', {class: 'tr'}, [
E('td', {class: 'td left', width: '25%'}, _('MTU')),
E('td', {class: 'td left', width: '25%'}, interfaceData.mtu)
]),
E('tr', {class: 'tr'}, [
E('td', {class: 'td left', width: '25%'}, _('Total Download')),
E('td', {class: 'td left', width: '25%'}, interfaceData.rxBytes)
]),
E('tr', {class: 'tr'}, [
E('td', {class: 'td left', width: '25%'}, _('Total Upload')),
E('td', {class: 'td left', width: '25%'}, interfaceData.txBytes)
])
];
});
return E('div', {}, [title, desc, E('table', { 'class': 'table' }, rows)]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -0,0 +1,112 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:80
msgid "Allow ZeroTier clients access your LAN network."
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:79
msgid "Auto NAT clients"
msgstr ""
#: applications/luci-app-zerotier/root/usr/share/luci/menu.d/luci-app-zerotier.json:14
msgid "Base settings"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:66
msgid "Collecting data ..."
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:85
msgid ""
"Create or manage your ZeroTier network, and auth clients who could access."
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:72
msgid "Enable"
msgstr ""
#: applications/luci-app-zerotier/root/usr/share/rpcd/acl.d/luci-app-zerotier.json:3
msgid "Grant access to ZeroTier configuration"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:91
msgid "IPv4 Address"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:95
msgid "IPv6 Address"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:83
msgid "Interface Name"
msgstr ""
#: applications/luci-app-zerotier/root/usr/share/luci/menu.d/luci-app-zerotier.json:22
msgid "Interface info"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:87
msgid "MAC Address"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:99
msgid "MTU"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:36
msgid "NOT RUNNING"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:76
msgid "Network ID"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:81
msgid "Network Interface Information"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:77
msgid "No interface online."
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:86
msgid "Open website"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:34
msgid "RUNNING"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:103
msgid "Total Download"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:107
msgid "Total Upload"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:30
msgid "Unable to get interface %s info: %s."
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:15
msgid "Unable to get interface info: %s."
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:34
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:36
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:52
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:73
#: applications/luci-app-zerotier/root/usr/share/luci/menu.d/luci-app-zerotier.json:3
msgid "ZeroTier"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:84
msgid "ZeroTier Central"
msgstr ""
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:53
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:74
msgid "ZeroTier is an open source, cross-platform and easy to use virtual LAN."
msgstr ""

1
luci-app-zerotier/po/zh-cn Symbolic link
View File

@ -0,0 +1 @@
zh_Hans

View File

@ -0,0 +1,118 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:80
msgid "Allow ZeroTier clients access your LAN network."
msgstr "允许 ZeroTier 客户端访问您的局域网。"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:79
msgid "Auto NAT clients"
msgstr "自动客户端 NAT"
#: applications/luci-app-zerotier/root/usr/share/luci/menu.d/luci-app-zerotier.json:14
msgid "Base settings"
msgstr "基本设置"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:66
msgid "Collecting data ..."
msgstr "收集数据中 ..."
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:85
msgid ""
"Create or manage your ZeroTier network, and auth clients who could access."
msgstr "创建或管理您的 ZeroTier 网络,并允许客户端接入。"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:72
msgid "Enable"
msgstr "启用"
#: applications/luci-app-zerotier/root/usr/share/rpcd/acl.d/luci-app-zerotier.json:3
msgid "Grant access to ZeroTier configuration"
msgstr "授予访问 ZeroTier 配置的权限"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:91
msgid "IPv4 Address"
msgstr "IPv4 地址"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:95
msgid "IPv6 Address"
msgstr "IPv6 地址"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:83
msgid "Interface Name"
msgstr "接口名称"
#: applications/luci-app-zerotier/root/usr/share/luci/menu.d/luci-app-zerotier.json:22
msgid "Interface info"
msgstr "接口信息"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:87
msgid "MAC Address"
msgstr "MAC 地址"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:99
msgid "MTU"
msgstr "MTU"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:36
msgid "NOT RUNNING"
msgstr "未运行"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:76
msgid "Network ID"
msgstr "网络 ID"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:81
msgid "Network Interface Information"
msgstr "网络接口信息"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:77
msgid "No interface online."
msgstr "没有在线的接口。"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:86
msgid "Open website"
msgstr "打开网站"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:34
msgid "RUNNING"
msgstr "运行中"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:103
msgid "Total Download"
msgstr "总下载"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:107
msgid "Total Upload"
msgstr "总上传"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:30
msgid "Unable to get interface %s info: %s."
msgstr "无法获取接口 %s 的信息:%s。"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:15
msgid "Unable to get interface info: %s."
msgstr "无法获取接口信息:%s。"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:34
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:36
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:52
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:73
#: applications/luci-app-zerotier/root/usr/share/luci/menu.d/luci-app-zerotier.json:3
msgid "ZeroTier"
msgstr "ZeroTier"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:84
msgid "ZeroTier Central"
msgstr "ZeroTier 管理中心"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:53
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:74
msgid "ZeroTier is an open source, cross-platform and easy to use virtual LAN."
msgstr "ZeroTier 是一个开源、跨平台且易于使用的虚拟局域网 VPN。"

View File

@ -0,0 +1,118 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: zh_Hant\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:80
msgid "Allow ZeroTier clients access your LAN network."
msgstr "允許 ZeroTier 用戶端訪問您的區域網路"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:79
msgid "Auto NAT clients"
msgstr "自動 NAT 用戶端"
#: applications/luci-app-zerotier/root/usr/share/luci/menu.d/luci-app-zerotier.json:14
msgid "Base settings"
msgstr "基礎設定"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:66
msgid "Collecting data ..."
msgstr "收集資料中..."
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:85
msgid ""
"Create or manage your ZeroTier network, and auth clients who could access."
msgstr "新建或管理您的 ZeroTier 網路,並授權用戶端訪問"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:72
msgid "Enable"
msgstr "啟用"
#: applications/luci-app-zerotier/root/usr/share/rpcd/acl.d/luci-app-zerotier.json:3
msgid "Grant access to ZeroTier configuration"
msgstr "允許訪問 ZeroTier 的配置"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:91
msgid "IPv4 Address"
msgstr "IPv4 位址"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:95
msgid "IPv6 Address"
msgstr "IPv6 位址"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:83
msgid "Interface Name"
msgstr "介面名稱"
#: applications/luci-app-zerotier/root/usr/share/luci/menu.d/luci-app-zerotier.json:22
msgid "Interface info"
msgstr "介面資訊"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:87
msgid "MAC Address"
msgstr "MAC 位址"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:99
msgid "MTU"
msgstr "MTU"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:36
msgid "NOT RUNNING"
msgstr "尚未執行"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:76
msgid "Network ID"
msgstr "網路 ID"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:81
msgid "Network Interface Information"
msgstr "網路介面資訊"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:77
msgid "No interface online."
msgstr "沒有介面在線上。"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:86
msgid "Open website"
msgstr "開啟網站"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:34
msgid "RUNNING"
msgstr "正在執行"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:103
msgid "Total Download"
msgstr "總下載量"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:107
msgid "Total Upload"
msgstr "總上傳量"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:30
msgid "Unable to get interface %s info: %s."
msgstr "無法取得介面 %s 的資訊:%s"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:15
msgid "Unable to get interface info: %s."
msgstr "無法取得介面資訊:%s"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:34
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:36
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:52
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:73
#: applications/luci-app-zerotier/root/usr/share/luci/menu.d/luci-app-zerotier.json:3
msgid "ZeroTier"
msgstr "ZeroTier"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:84
msgid "ZeroTier Central"
msgstr "ZeroTier 管理中心"
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/base.js:53
#: applications/luci-app-zerotier/htdocs/luci-static/resources/view/zerotier/interface.js:74
msgid "ZeroTier is an open source, cross-platform and easy to use virtual LAN."
msgstr "ZeroTier 是一個開源、跨平台且易於使用的虛擬區域網路 VPN"

View File

@ -0,0 +1,20 @@
config zerotier sample_config
option enabled 0
# persistent configuration folder (for ZT controller mode)
#option config_path '/etc/zerotier'
# copy <config_path> to RAM to prevent writing to flash (for ZT controller mode)
#option copy_config_path '1'
#option port '9993'
# path to the local.conf
#option local_conf '/etc/zerotier.conf'
# Generate secret on first start
option secret ''
# Join a public network called Earth
list join '8056c2e21c000001'
#list join '<other_network>'

View File

@ -0,0 +1,9 @@
#!/bin/sh
zero_enable="$(uci get zerotier.sample_config.enabled)"
nat_enable="$(uci get zerotier.sample_config.nat)"
[ "$ACTION" = ifup -o "$ACTION" = ifupdate ] || exit 0
[ "$ACTION" = ifupdate -a -z "$IFUPDATE_ADDRESSES" -a -z "$IFUPDATE_DATA" ] && exit 0
[ "$zero_enable" -eq "1" -a "${nat_enable}" -eq "1" ] || exit 0
/etc/zerotier.start > /tmp/zero.log 2>&1 &

View File

@ -0,0 +1,115 @@
#!/bin/sh /etc/rc.common
START=99
USE_PROCD=1
PROG=/usr/bin/zerotier-one
CONFIG_PATH=/var/lib/zerotier-one
service_triggers() {
procd_add_reload_trigger "zerotier"
procd_add_interface_trigger "interface.*.up" wan /etc/init.d/zerotier restart
}
section_enabled() {
config_get_bool enabled "$1" 'enabled' 0
[ $enabled -gt 0 ]
}
start_instance() {
local cfg="$1"
local port secret config_path
local ARGS=""
if ! section_enabled "$cfg"; then
echo "disabled in config"
return 1
fi
[ -d /etc/config/zero ] || mkdir -p /etc/config/zero
config_path=/etc/config/zero
config_get_bool port $cfg 'port'
config_get secret $cfg 'secret'
# Remove existing link or folder
rm -rf $CONFIG_PATH
# Create link from CONFIG_PATH to config_path
if [ -n "$config_path" -a "$config_path" != $CONFIG_PATH ]; then
if [ ! -d "$config_path" ]; then
echo "ZeroTier config_path does not exist: $config_path"
return
fi
ln -s $config_path $CONFIG_PATH
fi
mkdir -p $CONFIG_PATH/networks.d
if [ -n "$port" ]; then
ARGS="$ARGS -p$port"
fi
if [ "$secret" = "generate" ]; then
echo "Generate secret - please wait..."
local sf="/tmp/zt.$cfg.secret"
zerotier-idtool generate "$sf" > /dev/null
[ $? -ne 0 ] && return 1
secret="$(cat $sf)"
rm "$sf"
uci set zerotier.$cfg.secret="$secret"
uci commit zerotier
fi
if [ -n "$secret" ]; then
echo "$secret" > $CONFIG_PATH/identity.secret
# make sure there is not previous identity.public
rm -f $CONFIG_PATH/identity.public
fi
add_join() {
# an (empty) config file will cause ZT to join a network
touch $CONFIG_PATH/networks.d/$1.conf
}
config_list_foreach $cfg 'join' add_join
procd_open_instance
procd_set_param command $PROG $ARGS $CONFIG_PATH
procd_set_param stderr 1
procd_close_instance
}
start_service() {
config_load 'zerotier'
config_foreach start_instance 'zerotier'
touch /tmp/zero.log && /etc/zerotier.start > /tmp/zero.log 2>&1 &
}
stop_instance() {
rm -f /tmp/zero.log
local cfg="$1"
rm -f "/usr/share/nftables.d/chain-pre/forward/zerotier.nft" "/usr/share/nftables.d/chain-pre/srcnat/zerotier.nft"
fw4 reload
echo "zt interface rules removed!" > /tmp/zero.log 2>&1 &
# Remove existing link or folder
rm -f $CONFIG_PATH/networks.d/*.conf
rm -rf $CONFIG_PATH
}
stop_service() {
config_load 'zerotier'
config_foreach stop_instance 'zerotier'
}
reload_service() {
stop
start
}

View File

@ -0,0 +1,12 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@zerotier[-1]
commit ucitrack
delete firewall.zerotier
commit firewall
EOF
rm -f /tmp/luci-indexcache
exit 0

View File

@ -0,0 +1,37 @@
#!/bin/sh
zero_enable="$(uci get zerotier.sample_config.enabled)"
[ "$zero_enable" -eq "1" ] || exit 1
count=0
[ -f "/tmp/zero.log" ] && {
while [ -z "$(ifconfig | grep 'zt' | awk '{print $1}')" ]
do
sleep 2
let count++
[ "$count" -lt 5 ] || exit 19
done
}
nft_incdir="/usr/share/nftables.d/chain-pre"
rm -f "$nft_incdir/input/zerotier.nft" "$nft_incdir/forward/zerotier.nft" "$$nft_incdir/srcnat/zerotier.nft"
nat_enable="$(uci get zerotier.sample_config.nat)"
[ "$nat_enable" -eq "1" ] && {
[ -d "$nft_incdir/input" ] || mkdir -p "$nft_incdir/input"
[ -d "$nft_incdir/forward" ] || mkdir -p "$nft_incdir/forward"
[ -d "$nft_incdir/srcnat" ] || mkdir -p "$nft_incdir/srcnat"
for i in $(ifconfig | grep 'zt' | awk '{print $1}')
do
ip_segment="$(ip route | grep "dev $i proto kernel" | awk '{print $1}')"
echo "iifname $i counter accept comment \"!fw4: Zerotier allow inbound $i\"" >> "$nft_incdir/input/zerotier.nft"
echo "iifname $i counter accept comment \"!fw4: Zerotier allow inbound forward $i\"" >> "$nft_incdir/forward/zerotier.nft"
echo "oifname $i counter accept comment \"!fw4: Zerotier allow outbound forward $i\"" >> "$nft_incdir/forward/zerotier.nft"
echo "oifname $i counter masquerade comment \"!fw4: Zerotier $i outbound postrouting masq\"" >> "$nft_incdir/srcnat/zerotier.nft"
[ -z "$ip_segment" ] || echo "ip saddr $ip_segment counter masquerade comment \"!fw4: Zerotier $ip_segment postrouting masq\"" >> "$nft_incdir/srcnat/zerotier.nft"
done
echo "zt interface rules added!" > "/tmp/zero.log"
uci -q set firewall.@defaults[0].auto_includes="1"
uci -q commit firewall
fw4 reload
}

View File

@ -0,0 +1,29 @@
{
"admin/vpn/zerotier": {
"title": "ZeroTier",
"order": 90,
"action": {
"type": "firstchild"
},
"depends": {
"acl": [ "luci-app-zerotier" ],
"uci": { "zerotier": true }
}
},
"admin/vpn/zerotier/base": {
"title": "Base settings",
"order": 10,
"action": {
"type": "view",
"path": "zerotier/base"
}
},
"admin/vpn/zerotier/interface": {
"title": "Interface info",
"order": 20,
"action": {
"type": "view",
"path": "zerotier/interface"
}
}
}

View File

@ -0,0 +1,17 @@
{
"luci-app-zerotier": {
"description": "Grant access to ZeroTier configuration",
"read": {
"file": {
"/sbin/ifconfig": [ "exec" ]
},
"ubus": {
"service": [ "list" ]
},
"uci": [ "zerotier" ]
},
"write": {
"uci": [ "zerotier" ]
}
}
}