update 2024-12-04 10:15:35
This commit is contained in:
parent
318f5b9936
commit
ea37b1ce6f
12
luci-app-cpufreq/Makefile
Normal file
12
luci-app-cpufreq/Makefile
Normal 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
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
68
luci-app-cpufreq/po/templates/cpufreq.pot
Normal file
68
luci-app-cpufreq/po/templates/cpufreq.pot
Normal 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
1
luci-app-cpufreq/po/zh-cn
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
zh_Hans
|
75
luci-app-cpufreq/po/zh_Hans/cpufreq.po
Normal file
75
luci-app-cpufreq/po/zh_Hans/cpufreq.po
Normal 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 动态调频。"
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
53
luci-app-diskman/Makefile
Normal 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
|
151
luci-app-diskman/luasrc/controller/diskman.lua
Normal file
151
luci-app-diskman/luasrc/controller/diskman.lua
Normal 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
|
210
luci-app-diskman/luasrc/model/cbi/diskman/btrfs.lua
Normal file
210
luci-app-diskman/luasrc/model/cbi/diskman/btrfs.lua
Normal 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
|
360
luci-app-diskman/luasrc/model/cbi/diskman/disks.lua
Normal file
360
luci-app-diskman/luasrc/model/cbi/diskman/disks.lua
Normal 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
|
366
luci-app-diskman/luasrc/model/cbi/diskman/partition.lua
Normal file
366
luci-app-diskman/luasrc/model/cbi/diskman/partition.lua
Normal 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
|
743
luci-app-diskman/luasrc/model/diskman.lua
Normal file
743
luci-app-diskman/luasrc/model/diskman.lua
Normal 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
|
@ -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%>
|
@ -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%>
|
@ -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>
|
37
luci-app-diskman/luasrc/view/diskman/cbi/xnullsection.htm
Normal file
37
luci-app-diskman/luasrc/view/diskman/cbi/xnullsection.htm
Normal 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
|
||||||
|
%>
|
87
luci-app-diskman/luasrc/view/diskman/cbi/xsimpleform.htm
Normal file
87
luci-app-diskman/luasrc/view/diskman/cbi/xsimpleform.htm
Normal 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>
|
110
luci-app-diskman/luasrc/view/diskman/disk_info.htm
Normal file
110
luci-app-diskman/luasrc/view/diskman/disk_info.htm
Normal 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> </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> </div></div>'
|
||||||
|
}
|
||||||
|
item.insertAdjacentHTML('afterend', partitons_div);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
138
luci-app-diskman/luasrc/view/diskman/partition_info.htm
Normal file
138
luci-app-diskman/luasrc/view/diskman/partition_info.htm
Normal 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>
|
78
luci-app-diskman/luasrc/view/diskman/smart_detail.htm
Normal file
78
luci-app-diskman/luasrc/view/diskman/smart_detail.htm
Normal 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>
|
239
luci-app-diskman/po/pl/diskman.po
Normal file
239
luci-app-diskman/po/pl/diskman.po
Normal 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
1
luci-app-diskman/po/zh-cn
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
zh_Hans
|
239
luci-app-diskman/po/zh_Hans/diskman.po
Normal file
239
luci-app-diskman/po/zh_Hans/diskman.po
Normal 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确定修改分区表?"
|
239
luci-app-diskman/po/zh_Hant/diskman.po
Normal file
239
luci-app-diskman/po/zh_Hant/diskman.po
Normal 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
16
luci-app-netdata/Makefile
Normal 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
|
9
luci-app-netdata/luasrc/controller/netdata.lua
Normal file
9
luci-app-netdata/luasrc/controller/netdata.lua
Normal 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
|
9
luci-app-netdata/luasrc/view/netdata.htm
Normal file
9
luci-app-netdata/luasrc/view/netdata.htm
Normal 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
1
luci-app-netdata/po/zh-cn
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
zh_Hans
|
5
luci-app-netdata/po/zh_Hans/netdata.po
Normal file
5
luci-app-netdata/po/zh_Hans/netdata.po
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||||
|
|
||||||
|
msgid "NetData"
|
||||||
|
msgstr "实时监控"
|
17
luci-app-ramfree/Makefile
Normal file
17
luci-app-ramfree/Makefile
Normal 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
|
||||||
|
|
||||||
|
|
9
luci-app-ramfree/luasrc/controller/release_ram.lua
Normal file
9
luci-app-ramfree/luasrc/controller/release_ram.lua
Normal 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
1
luci-app-ramfree/po/zh-cn
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
zh_Hans
|
2
luci-app-ramfree/po/zh_Hans/release_ram.po
Normal file
2
luci-app-ramfree/po/zh_Hans/release_ram.po
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
msgid "Release Ram"
|
||||||
|
msgstr "释放内存"
|
@ -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
16
luci-app-socat/Makefile
Normal 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
|
19
luci-app-socat/luasrc/controller/socat.lua
Normal file
19
luci-app-socat/luasrc/controller/socat.lua
Normal 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
|
82
luci-app-socat/luasrc/model/cbi/socat/config.lua
Normal file
82
luci-app-socat/luasrc/model/cbi/socat/config.lua
Normal 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
|
76
luci-app-socat/luasrc/model/cbi/socat/index.lua
Normal file
76
luci-app-socat/luasrc/model/cbi/socat/index.lua
Normal 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
|
||||||
|
|
19
luci-app-socat/luasrc/view/socat/list_status.htm
Normal file
19
luci-app-socat/luasrc/view/socat/list_status.htm
Normal 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>
|
3
luci-app-socat/luasrc/view/socat/status.htm
Normal file
3
luci-app-socat/luasrc/view/socat/status.htm
Normal 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
1
luci-app-socat/po/zh-cn
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
zh_Hans
|
50
luci-app-socat/po/zh_Hans/socat.po
Normal file
50
luci-app-socat/po/zh_Hans/socat.po
Normal 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 "打开防火墙端口"
|
3
luci-app-socat/root/etc/config/socat
Normal file
3
luci-app-socat/root/etc/config/socat
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
config global 'global'
|
||||||
|
option enable '0'
|
119
luci-app-socat/root/etc/init.d/socat
Executable file
119
luci-app-socat/root/etc/init.d/socat
Executable 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
|
||||||
|
}
|
12
luci-app-socat/root/etc/uci-defaults/luci-app-socat
Executable file
12
luci-app-socat/root/etc/uci-defaults/luci-app-socat
Executable 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
|
11
luci-app-socat/root/usr/share/rpcd/acl.d/luci-app-socat.json
Normal file
11
luci-app-socat/root/usr/share/rpcd/acl.d/luci-app-socat.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"luci-app-socat": {
|
||||||
|
"description": "Grant UCI access for luci-app-socat",
|
||||||
|
"read": {
|
||||||
|
"uci": [ "socat" ]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"uci": [ "socat" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"config": "socat",
|
||||||
|
"init": [ "socat" ]
|
||||||
|
}
|
17
luci-app-syncdial/Makefile
Normal file
17
luci-app-syncdial/Makefile
Normal 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
|
32
luci-app-syncdial/luasrc/controller/syncdial.lua
Normal file
32
luci-app-syncdial/luasrc/controller/syncdial.lua
Normal 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
|
88
luci-app-syncdial/luasrc/model/cbi/syncdial.lua
Normal file
88
luci-app-syncdial/luasrc/model/cbi/syncdial.lua
Normal 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
|
17
luci-app-syncdial/luasrc/view/syncdial/redial_button.htm
Normal file
17
luci-app-syncdial/luasrc/view/syncdial/redial_button.htm
Normal 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%>
|
17
luci-app-syncdial/luasrc/view/syncdial/syncdial_status.htm
Normal file
17
luci-app-syncdial/luasrc/view/syncdial/syncdial_status.htm
Normal 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>
|
278
luci-app-syncdial/root/bin/genwancfg
Executable file
278
luci-app-syncdial/root/bin/genwancfg
Executable 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
|
19
luci-app-syncdial/root/etc/config/syncdial
Normal file
19
luci-app-syncdial/root/etc/config/syncdial
Normal 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'
|
||||||
|
|
36
luci-app-syncdial/root/etc/hotplug.d/iface/01-dialcheck
Executable file
36
luci-app-syncdial/root/etc/hotplug.d/iface/01-dialcheck
Executable 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
|
||||||
|
}
|
||||||
|
}
|
16
luci-app-syncdial/root/etc/hotplug.d/iface/01-mvifcreate
Executable file
16
luci-app-syncdial/root/etc/hotplug.d/iface/01-mvifcreate
Executable 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
|
||||||
|
}
|
12
luci-app-syncdial/root/etc/uci-defaults/luci-syncdial
Executable file
12
luci-app-syncdial/root/etc/uci-defaults/luci-syncdial
Executable 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
|
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"luci-app-syncdial": {
|
||||||
|
"description": "Grant UCI access for luci-app-syncdial",
|
||||||
|
"read": {
|
||||||
|
"uci": [ "syncdial" ]
|
||||||
|
},
|
||||||
|
"write": {
|
||||||
|
"uci": [ "syncdial" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
luci-app-usb-printer/Makefile
Normal file
18
luci-app-usb-printer/Makefile
Normal 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/
|
6
luci-app-usb-printer/ipkg/postinst
Executable file
6
luci-app-usb-printer/ipkg/postinst
Executable 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
|
||||||
|
}
|
||||||
|
|
30
luci-app-usb-printer/luasrc/controller/usb_printer.lua
Normal file
30
luci-app-usb-printer/luasrc/controller/usb_printer.lua
Normal 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
|
130
luci-app-usb-printer/luasrc/model/cbi/usb_printer.lua
Normal file
130
luci-app-usb-printer/luasrc/model/cbi/usb_printer.lua
Normal 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
|
1
luci-app-usb-printer/po/zh-cn
Symbolic link
1
luci-app-usb-printer/po/zh-cn
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
zh_Hans
|
83
luci-app-usb-printer/po/zh_Hans/luci-app-usb-printer.po
Normal file
83
luci-app-usb-printer/po/zh_Hans/luci-app-usb-printer.po
Normal 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 "架构"
|
65
luci-app-usb-printer/po/zh_Hans/usb-printer.po
Normal file
65
luci-app-usb-printer/po/zh_Hans/usb-printer.po
Normal 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 "架构"
|
||||||
|
|
0
luci-app-usb-printer/root/etc/config/usb_printer
Normal file
0
luci-app-usb-printer/root/etc/config/usb_printer
Normal file
7
luci-app-usb-printer/root/etc/hotplug.d/usb/10-usb_printer
Executable file
7
luci-app-usb-printer/root/etc/hotplug.d/usb/10-usb_printer
Executable 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
|
22
luci-app-usb-printer/root/etc/init.d/usb_printer
Executable file
22
luci-app-usb-printer/root/etc/init.d/usb_printer
Executable 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
|
||||||
|
}
|
5
luci-app-usb-printer/root/etc/uci-defaults/luci-usb-printer
Executable file
5
luci-app-usb-printer/root/etc/uci-defaults/luci-usb-printer
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ -f /etc/init.d/p910nd ] && /etc/init.d/p910nd disable
|
||||||
|
|
||||||
|
exit 0
|
20
luci-app-usb-printer/root/usr/bin/detectlp
Executable file
20
luci-app-usb-printer/root/usr/bin/detectlp
Executable 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
|
72
luci-app-usb-printer/root/usr/bin/usb_printer_hotplug
Executable file
72
luci-app-usb-printer/root/usr/bin/usb_printer_hotplug
Executable 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
|
@ -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" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"config": "usb_printer",
|
||||||
|
"init": [ "usb_printer" ]
|
||||||
|
}
|
20
luci-app-zerotier/Makefile
Normal file
20
luci-app-zerotier/Makefile
Normal 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
|
||||||
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
@ -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
|
||||||
|
});
|
112
luci-app-zerotier/po/templates/zerotier.pot
Normal file
112
luci-app-zerotier/po/templates/zerotier.pot
Normal 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
1
luci-app-zerotier/po/zh-cn
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
zh_Hans
|
118
luci-app-zerotier/po/zh_Hans/zerotier.po
Normal file
118
luci-app-zerotier/po/zh_Hans/zerotier.po
Normal 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。"
|
118
luci-app-zerotier/po/zh_Hant/zerotier.po
Normal file
118
luci-app-zerotier/po/zh_Hant/zerotier.po
Normal 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"
|
20
luci-app-zerotier/root/etc/config/zerotier
Normal file
20
luci-app-zerotier/root/etc/config/zerotier
Normal 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>'
|
9
luci-app-zerotier/root/etc/hotplug.d/iface/40-zerotier
Normal file
9
luci-app-zerotier/root/etc/hotplug.d/iface/40-zerotier
Normal 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 &
|
115
luci-app-zerotier/root/etc/init.d/zerotier
Executable file
115
luci-app-zerotier/root/etc/init.d/zerotier
Executable 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
|
||||||
|
}
|
12
luci-app-zerotier/root/etc/uci-defaults/40_luci-zerotier
Executable file
12
luci-app-zerotier/root/etc/uci-defaults/40_luci-zerotier
Executable 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
|
37
luci-app-zerotier/root/etc/zerotier.start
Executable file
37
luci-app-zerotier/root/etc/zerotier.start
Executable 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
|
||||||
|
}
|
0
luci-app-zerotier/root/etc/zerotier/zerotier.log
Normal file
0
luci-app-zerotier/root/etc/zerotier/zerotier.log
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user