1
0
Fork 0
mirror of https://github.com/Ysurac/openmptcprouter-feeds.git synced 2025-03-09 15:40:03 +00:00
This commit is contained in:
suyuan168 2022-06-09 11:23:58 +08:00
parent 06a4f10a4d
commit 1020d095bc
44 changed files with 3520 additions and 31 deletions

19
luci-app-cpufreq/Makefile Normal file
View file

@ -0,0 +1,19 @@
# Copyright (C) 2016 Openwrt.org
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI for CPU Freq Setting
LUCI_DEPENDS:=@(aarch64||arm)
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-cpufreq
PKG_VERSION:=1
PKG_RELEASE:=7
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View file

@ -0,0 +1,9 @@
module("luci.controller.cpufreq", package.seeall)
function index()
if not nixio.fs.access("/etc/config/cpufreq") then
return
end
entry({"admin", "services", "cpufreq"}, cbi("cpufreq"), _("CPU Freq"), 900).dependent = true
end

View file

@ -0,0 +1,60 @@
local fs = require "nixio.fs"
cpu_freqs = fs.readfile("/sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies") or "100000"
cpu_freqs = string.sub(cpu_freqs, 1, -3)
cpu_governors = fs.readfile("/sys/devices/system/cpu/cpufreq/policy0/scaling_available_governors") or "performance"
cpu_governors = string.sub(cpu_governors, 1, -3)
function string.split(input, delimiter)
input = tostring(input)
delimiter = tostring(delimiter)
if (delimiter=='') then return false end
local pos,arr = 0, {}
for st,sp in function() return string.find(input, delimiter, pos, true) end do
table.insert(arr, string.sub(input, pos, st - 1))
pos = sp + 1
end
table.insert(arr, string.sub(input, pos))
return arr
end
freq_array = string.split(cpu_freqs, " ")
governor_array = string.split(cpu_governors, " ")
mp = Map("cpufreq", translate("CPU Freq Settings"))
mp.description = translate("Set CPU Scaling Governor to Max Performance or Balance Mode")
s = mp:section(NamedSection, "cpufreq", "settings")
s.anonymouse = true
governor = s:option(ListValue, "governor", translate("CPU Scaling Governor"))
for _, e in ipairs(governor_array) do
if e ~= "" then governor:value(e, translate(e,string.upper(e))) end
end
minfreq = s:option(ListValue, "minifreq", translate("Min Idle CPU Freq"))
for _, e in ipairs(freq_array) do
if e ~= "" then minfreq:value(e) end
end
maxfreq = s:option(ListValue, "maxfreq", translate("Max Turbo Boost CPU Freq"))
for _, e in ipairs(freq_array) do
if e ~= "" then maxfreq:value(e) end
end
upthreshold = s:option(Value, "upthreshold", translate("CPU Switching Threshold"))
upthreshold.datatype="range(1,99)"
upthreshold.rmempty = false
upthreshold.description = translate("Kernel make a decision on whether it should increase the frequency (%)")
upthreshold.placeholder = 50
upthreshold.default = 50
factor = s:option(Value, "factor", translate("CPU Switching Sampling rate"))
factor.datatype="range(1,100000)"
factor.rmempty = false
factor.description = translate("The sampling rate determines how frequently the governor checks to tune the CPU (ms)")
factor.placeholder = 10
factor.default = 10
return mp

View file

@ -0,0 +1,57 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: dingpengyu <dingpengyu06@gmail.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: zh_CN\n"
"X-Generator: Poedit 2.3.1\n"
msgid "CPU Freq"
msgstr "CPU 性能优化调节"
msgid "CPU Freq Settings"
msgstr "CPU 性能优化调节设置"
msgid "Set CPU Scaling Governor to Max Performance or Balance Mode"
msgstr "设置路由器的 CPU 性能模式(高性能/均衡省电)"
msgid "CPU Scaling Governor"
msgstr "CPU 工作模式"
msgid "Ondemand Balance Mode"
msgstr "Ondemand 自动平衡模式"
msgid "Performance Mode"
msgstr "Performance 高性能模式(降低游戏转发延迟)"
msgid "ondemand"
msgstr "ondemand 自动平衡模式"
msgid "performance"
msgstr "performance 高性能模式(降低游戏转发延迟)"
msgid "CPU Freq from 48000 to 716000 (Khz)"
msgstr "CPU 频率范围为 48000 到 716000 (Khz)"
msgid "Min Idle CPU Freq"
msgstr "待机 CPU 最小频率"
msgid "Max Turbo Boost CPU Freq"
msgstr "最大 Turbo Boost CPU 频率"
msgid "CPU Switching Threshold"
msgstr "CPU 切换频率触发阈值"
msgid "Kernel make a decision on whether it should increase the frequency (%)"
msgstr "当 CPU 占用率超过 (%) 的情况下触发内核切换频率"
msgid "CPU Switching Sampling rate"
msgstr "CPU 切换周期"
msgid "The sampling rate determines how frequently the governor checks to tune the CPU (ms)"
msgstr "CPU 检查切换的周期 (ms) 。注意:过于频繁的切换频率会引起网络延迟抖动"

View file

@ -0,0 +1,8 @@
config settings 'cpufreq'
option maxfreq '716000'
option upthreshold '50'
option factor '10'
option minifreq '300000'
option governor 'ondemand'

View file

@ -0,0 +1,30 @@
#!/bin/sh /etc/rc.common
START=50
NAME=cpufreq
uci_get_by_type() {
local ret=$(uci get $NAME.@$1[0].$2 2>/dev/null)
echo ${ret:=$3}
}
start()
{
local min=$(cat /sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies | awk -F ' ' '{print $1 }')
local max=$(cat /sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies | awk -F ' ' '{print $NF }')
config_load cpufreq
local governor=$(uci_get_by_type settings governor schedutil)
local minifreq=$(uci_get_by_type settings minifreq $min)
local maxfreq=$(uci_get_by_type settings maxfreq $max)
local upthreshold=$(uci_get_by_type settings upthreshold 50)
local factor=$(uci_get_by_type settings factor 10)
echo $governor > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor
echo $minifreq > /sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq
echo $maxfreq > /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq
if [ "$governor" == "ondemand" ]; then
echo $upthreshold > /sys/devices/system/cpu/cpufreq/ondemand/up_threshold
echo $factor > /sys/devices/system/cpu/cpufreq/ondemand/sampling_down_factor
fi
}

View file

@ -0,0 +1,11 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@cpufreq[-1]
add ucitrack cpufreq
set ucitrack.@cpufreq[-1].init=cpufreq
commit ucitrack
EOF
rm -f /tmp/luci-indexcache
exit 0

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

@ -0,0 +1,53 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-diskman
PKG_VERSION:=v0.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 ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View file

@ -0,0 +1,155 @@
--[[
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 dm = require "luci.model.diskman"
local cmd = io.popen(dm.command.smartctl .. " -H -A -i /dev/%s" % dev)
if cmd then
local attr = { }
if cmd:match("NVMe Version:")then
while true do
local ln = cmd:read("*l")
if not ln then
break
elseif ln:match("^(.-):%s+(.+)") then
local key, value = ln:match("^(.-):%s+(.+)")
attr[#attr+1]= {
key = key,
value = value
}
end
end
else
while true do
local ln = cmd:read("*l")
if not ln then
break
elseif 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
cmd:close()
luci.http.prepare_content("application/json")
luci.http.write_json(attr)
end
end

View file

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

View file

@ -0,0 +1,327 @@
--[[
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", translate("Health"))
d:option(DummyValue, "status", translate("Status"))
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 = "/dev/md0"
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("lsmod | grep md_mod")
if valid_raid:match("linear") then
r_level:value("linear", "Linear")
end
if valid_raid:match("raid456") then
r_level:value("5", "Raid 5")
r_level:value("6", "Raid 6")
end
if valid_raid:match("raid1") then
r_level:value("1", "Raid 1")
end
if valid_raid:match("raid0") then
r_level:value("0", "Raid 0")
end
if valid_raid:match("raid10") then
r_level:value("10", "Raid 10")
end
r_level.write = function(self, section, value)
rlevel = value
end
local r_member = creation_section:taboption("raid", DynamicList, "_rmember", translate("Raid Member"))
for dev, info in pairs(disks) do
if not info.inuse and #info.partitions == 0 then
r_member:value(info.path, info.path.. " ".. info.size_formated)
end
for i, v in ipairs(info.partitions) do
if not v.inuse then
r_member:value("/dev/".. v.name, "/dev/".. v.name .. " ".. v.size_formated)
end
end
end
r_member.write = function(self, section, value)
rmembers = value
end
local r_create = creation_section:taboption("raid", Button, "_rcreate")
r_create.render = function(self, section, scope)
self.title = " "
self.inputtitle = translate("Create Raid")
self.inputstyle = "add"
Button.render(self, section, scope)
end
r_create.write = function(self, section, value)
-- mdadm --create --verbose /dev/md0 --level=stripe --raid-devices=2 /dev/sdb6 /dev/sdc5
local res = dm.create_raid(rname, rlevel, rmembers)
if res and res:match("^ERR") then
m.errmessage = luci.util.pcdata(res)
return
end
dm.gen_mdadm_config()
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman"))
end
end
-- btrfs
if dm.command.btrfs then
local blabel, bmembers, blevel
local btrfs_label = creation_section:taboption("btrfs", Value, "_blabel", translate("Btrfs Label"))
btrfs_label.write = function(self, section, value)
blabel = value
end
local btrfs_level = creation_section:taboption("btrfs", ListValue, "_blevel", translate("Btrfs Raid Level"))
btrfs_level:value("single", "Single")
btrfs_level:value("raid0", "Raid 0")
btrfs_level:value("raid1", "Raid 1")
btrfs_level:value("raid10", "Raid 10")
btrfs_level.write = function(self, section, value)
blevel = value
end
local btrfs_member = creation_section:taboption("btrfs", DynamicList, "_bmember", translate("Btrfs Member"))
for dev, info in pairs(disks) do
if not info.inuse and #info.partitions == 0 then
btrfs_member:value(info.path, info.path.. " ".. info.size_formated)
end
for i, v in ipairs(info.partitions) do
if not v.inuse then
btrfs_member:value("/dev/".. v.name, "/dev/".. v.name .. " ".. v.size_formated)
end
end
end
btrfs_member.write = function(self, section, value)
bmembers = value
end
local btrfs_create = creation_section:taboption("btrfs", Button, "_bcreate")
btrfs_create.render = function(self, section, scope)
self.title = " "
self.inputtitle = translate("Create Btrfs")
self.inputstyle = "add"
Button.render(self, section, scope)
end
btrfs_create.write = function(self, section, value)
-- mkfs.btrfs -L label -d blevel /dev/sda /dev/sdb
local res = dm.create_btrfs(blabel, blevel, bmembers)
if res and res:match("^ERR") then
m.errmessage = luci.util.pcdata(res)
return
end
luci.http.redirect(luci.dispatcher.build_url("admin/system/diskman"))
end
end
end
return m

View file

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

View file

@ -0,0 +1,738 @@
--[[
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
smart_info.temp = val:match("(%d+)") .. "°C"
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") 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.phy_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
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+$")
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.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
local mdnum = 0
for num=1,127 do
local md = io.open("/dev/md"..tostring(num), "r")
if md == nil then
mdnum = num
break
else
io.close(md)
end
end
if mdnum == 0 then return "ERR: Cannot find proper md number" end
rname = "/dev/md"..mdnum
end
if rlevel == "5" or rlevel == "6" then
if #rmembers < 3 then return "ERR: Not enough members" end
end
if rlevel == "10" then
if #rmembers < 4 then return "ERR: Not enough members" end
end
if #rmembers < 2 then return "ERR: Not enough members" end
local cmd = d.command.mdadm .. " --create "..rname.." --run --assume-clean --homehost=any --level=" .. rlevel .. " --raid-devices=" .. #rmembers .. " " .. table.concat(rmembers, " ")
local res = luci.util.exec(cmd)
return res
end
d.gen_mdadm_config = function()
if not nixio.fs.access("/etc/config/mdadm") then return end
local uci = require "luci.model.uci"
local x = uci.cursor()
-- delete all array sections
x:foreach("mdadm", "array", function(s) x:delete("mdadm",s[".name"]) end)
local cmd = d.command.mdadm .. " -D -s"
--ARRAY /dev/md1 metadata=1.2 name=any:1 UUID=f998ae14:37621b27:5c49e850:051f6813
--ARRAY /dev/md3 metadata=1.2 name=any:3 UUID=c068c141:4b4232ca:f48cbf96:67d42feb
for _, v in ipairs(luci.util.execl(cmd)) do
local device, uuid = v:match("^ARRAY%s-([^%s]+)%s-[^%s]-%s-[^%s]-%s-UUID=([^%s]+)%s-")
if device and uuid then
local section_name = x:add("mdadm", "array")
x:set("mdadm", section_name, "device", device)
x:set("mdadm", section_name, "uuid", uuid)
end
end
x:commit("mdadm")
-- enable mdadm
luci.util.exec("/etc/init.d/mdadm enable")
end
-- list btrfs filesystem device
-- {uuid={uuid, label, members, size, used}...}
d.list_btrfs_devices = function()
local btrfs_device = {}
if not d.command.btrfs then return btrfs_device end
local line, _uuid
for _, line in ipairs(luci.util.execl(d.command.btrfs .. " filesystem show -d --raw"))
do
local label, uuid = line:match("^Label:%s+([^%s]+)%s+uuid:%s+([^%s]+)")
if label and uuid then
_uuid = uuid
local _label = label:match("^'([^']+)'")
btrfs_device[_uuid] = {label = _label or label, uuid = uuid}
-- table.insert(btrfs_device, {label = label, uuid = uuid})
end
local used = line:match("Total devices[%w%s]+used%s+(%d+)$")
if used then
btrfs_device[_uuid]["used"] = tonumber(used)
btrfs_device[_uuid]["used_formated"] = byte_format(tonumber(used))
end
local size, device = line:match("devid[%w.%s]+size%s+(%d+)[%w.%s]+path%s+([^%s]+)$")
if size and device then
btrfs_device[_uuid]["size"] = btrfs_device[_uuid]["size"] and btrfs_device[_uuid]["size"] + tonumber(size) or tonumber(size)
btrfs_device[_uuid]["size_formated"] = byte_format(btrfs_device[_uuid]["size"])
btrfs_device[_uuid]["members"] = btrfs_device[_uuid]["members"] and btrfs_device[_uuid]["members"]..", "..device or device
end
end
return btrfs_device
end
d.create_btrfs = function(blabel, blevel, bmembers)
-- mkfs.btrfs -L label -d blevel /dev/sda /dev/sdb
if not d.command.btrfs or type(bmembers) ~= "table" or next(bmembers) == nil then return "ERR no btrfs support or no members" end
local label = blabel and " -L " .. blabel or ""
local cmd = "mkfs.btrfs -f " .. label .. " -d " .. blevel .. " " .. table.concat(bmembers, " ")
return luci.util.exec(cmd)
end
-- get btrfs info
-- {uuid, label, members, data_raid_level,metadata_raid_lavel, size, used, size_formated, used_formated, free, free_formated, usage}
d.get_btrfs_info = function(m_point)
local btrfs_info = {}
if not m_point or not d.command.btrfs then return btrfs_info end
local cmd = d.command.btrfs .. " filesystem show --raw " .. m_point
local _, line, uuid, _label, members
for _, line in ipairs(luci.util.execl(cmd)) do
if not uuid and not _label then
_label, uuid = line:match("^Label:%s+([^%s]+)%s+uuid:%s+([^s]+)")
else
local mb = line:match("%s+devid.+path%s+([^%s]+)")
if mb then
members = members and (members .. ", ".. mb) or mb
end
end
end
if not _label or not uuid then return btrfs_info end
local label = _label:match("^'([^']+)'")
cmd = d.command.btrfs .. " filesystem usage -b " .. m_point
local used, free, data_raid_level, metadata_raid_lavel
for _, line in ipairs(luci.util.execl(cmd)) do
if not used then
used = line:match("^%s+Used:%s+(%d+)")
elseif not free then
free = line:match("^%s+Free %(estimated%):%s+(%d+)")
elseif not data_raid_level then
data_raid_level = line:match("^Data,%s-(%w+)")
elseif not metadata_raid_lavel then
metadata_raid_lavel = line:match("^Metadata,%s-(%w+)")
end
end
if used and free and data_raid_level and metadata_raid_lavel then
used = tonumber(used)
free = tonumber(free)
btrfs_info = {
uuid = uuid,
label = label,
data_raid_level = data_raid_level,
metadata_raid_lavel = metadata_raid_lavel,
used = used,
free = free,
size = used + free,
size_formated = byte_format(used + free),
used_formated = byte_format(used),
free_formated = byte_format(free),
members = members,
usage = string.format("%.2f",(used / (free+used) * 100)) .. "%"
}
end
return btrfs_info
end
-- get btrfs subvolume
-- {id={id, gen, top_level, path, snapshots, otime, default_subvolume}...}
d.get_btrfs_subv = function(m_point, snapshot)
local subvolume = {}
if not m_point or not d.command.btrfs then return subvolume end
-- get default subvolume
local cmd = d.command.btrfs .. " subvolume get-default " .. m_point
local res = luci.util.exec(cmd)
local default_subvolume_id = res:match("^ID%s+([^%s]+)")
-- get the root subvolume
if not snapshot then
local _, line, section_snap, _uuid, _otime, _id, _snap
cmd = d.command.btrfs .. " subvolume show ".. m_point
for _, line in ipairs(luci.util.execl(cmd)) do
if not section_snap then
if not _uuid then
_uuid = line:match("^%s-UUID:%s+([^%s]+)")
elseif not _otime then
_otime = line:match("^%s+Creation time:%s+(.+)")
elseif not _id then
_id = line:match("^%s+Subvolume ID:%s+([^%s]+)")
elseif line:match("^%s+(Snapshot%(s%):)") then
section_snap = true
end
else
local snapshot = line:match("^%s+(.+)")
if snapshot then
_snap = _snap and (_snap ..", /".. snapshot) or ("/"..snapshot)
end
end
end
if _uuid and _otime and _id then
subvolume["0".._id] = {id = _id , uuid = _uuid, otime = _otime, snapshots = _snap, path = "/"}
if default_subvolume_id == _id then
subvolume["0".._id].default_subvolume = 1
end
end
end
-- get subvolume of btrfs
cmd = d.command.btrfs .. " subvolume list -gcu" .. (snapshot and "s " or " ") .. m_point
for _, line in ipairs(luci.util.execl(cmd)) do
-- ID 259 gen 11 top level 258 uuid 26ae0c59-199a-cc4d-bd58-644eb4f65d33 path 1a/2b'
local id, gen, top_level, uuid, path, otime, otime2
if snapshot then
id, gen, top_level, otime, otime2, uuid, path = line:match("^ID%s+([^%s]+)%s+gen%s+([^%s]+)%s+cgen.-top level%s+([^%s]+)%s+otime%s+([^%s]+)%s+([^%s]+)%s+uuid%s+([^%s]+)%s+path%s+([^%s]+)%s-$")
else
id, gen, top_level, uuid, path = line:match("^ID%s+([^%s]+)%s+gen%s+([^%s]+)%s+cgen.-top level%s+([^%s]+)%s+uuid%s+([^%s]+)%s+path%s+([^%s]+)%s-$")
end
if id and gen and top_level and uuid and path then
subvolume[id] = {id = id, gen = gen, top_level = top_level, otime = (otime and otime or "") .." ".. (otime2 and otime2 or ""), uuid = uuid, path = '/'.. path}
if not snapshot then
-- use btrfs subv show to get snapshots
local show_cmd = d.command.btrfs .. " subvolume show "..m_point.."/"..path
local __, line_show, section_snap
for __, line_show in ipairs(luci.util.execl(show_cmd)) do
if not section_snap then
local create_time = line_show:match("^%s+Creation time:%s+(.+)")
if create_time then
subvolume[id]["otime"] = create_time
elseif line_show:match("^%s+(Snapshot%(s%):)") then
section_snap = "true"
end
else
local snapshot = line_show:match("^%s+(.+)")
subvolume[id]["snapshots"] = subvolume[id]["snapshots"] and (subvolume[id]["snapshots"] .. ", /".. snapshot) or ("/"..snapshot)
end
end
end
end
end
if subvolume[default_subvolume_id] then
subvolume[default_subvolume_id].default_subvolume = 1
end
-- if m_point == "/tmp/.btrfs_tmp" then
-- luci.util.exec("umount " .. m_point)
-- end
return subvolume
end
d.format_partition = function(partition, fs)
local partition_name = "/dev/".. partition
if not nixio.fs.access(partition_name) then
return 500, "Partition NOT found!"
end
local format_cmd = d.get_format_cmd()
if not format_cmd[fs] then
return 500, "Filesystem NOT support!"
end
local cmd = format_cmd[fs].cmd .. " " .. format_cmd[fs].option .. " " .. partition_name
local res = luci.util.exec(cmd .. " 2>&1")
if res and res:lower():match("error+") then
return 500, res
else
return 200, "OK"
end
end
return d

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,88 @@
<% 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")
}
}))
%>>
<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
<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
self:render_children()
%></div><%
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
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="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="cbi-button cbi-button-link" type="button" value="<%=label%>" onclick="cbi_submit(this, 'cbi.cancel')" /> <%
end
if display_skip then
%><input class="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="cbi-button cbi-button-save" type="submit" value="<%=label%>" /> <%
end
if display_reset then
local label = pcdata(self.reset or translate("Reset"))
%><input class="cbi-button cbi-button-reset" type="reset" value="<%=label%>" /> <%
end
%></div><%
end
%></form><%
end
%>
<script type="text/javascript">cbi_init();</script>

View file

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

View file

@ -0,0 +1,129 @@
<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 btn_p_table = document.getElementById("widget.cbid.table.1.p_table") || document.getElementById("cbid.table.1.p_table")
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 {
}
}
}
// handle smartinfo
const url = location.href.split('/')
const dev = url[url.length - 1]
const btn_smart_detail = document.getElementById("cbi-table-1-health")
btn_smart_detail.children[0].onclick = show_detail.bind(this, dev)
}
function close_dialog() {
document.body.classList.remove('dialog-format-active')
document.documentElement.style.overflowY = 'scroll'
}
function do_format(partation_name){
let fs = document.getElementById("filesystem_list").value
let status = document.getElementById("format-status")
if(!fs) {
status.innerHTML = "<%:Please select file system!%>"
return
}
status.innerHTML = "<%:Formatting..%>"
let b = document.getElementById('btn_format')
b.disabled = true
let xhr = new XHR()
xhr.post('<%=luci.dispatcher.build_url("admin/system/diskman/format_partition")%>', { partation_name: partation_name, file_system: fs }, (x, res) => {
if (x.status == 200) {
status.innerHTML = x.statusText
location.reload();
}else{
status.innerHTML = x.statusText
}
})
}
function clear_text(){
let s = document.getElementById('format-status')
s.innerHTML = ""
let b = document.getElementById('btn_format')
b.disabled = false
}
function partition_format(partition_name, format_cmd, current_fs){
let list = ''
format_cmd.split(",").forEach(e => {
list = list + '<option value="'+e+'">'+e+'</option>'
});
document.getElementById('dialog_format') || document.body.insertAdjacentHTML("beforeend", '<div id="dialog_format"><div class="dialog_box"><div class="dialog_line"></div><div class="dialog_line"><span><%:Format partation:%> <b>'+partition_name+'</b></span><br><span id="format-status" style="color: red;"></span></div><div class="dialog_line"><select id="filesystem_list" class="cbi-input-select" onchange="clear_text()">'+list+'</select></div><div class="dialog_line" style="text-align: right;"><input type="button" class="cbi-button cbi-button-apply" id="btn_format" type="submit" value="<%:Format%>" onclick="do_format(`'+partition_name+'`)" /> <input type="button"class="cbi-button cbi-button-reset" type="reset" value="<%:Cancel%>" onclick="close_dialog()" /></div><div class="dialog_line"></div></div></div>>')
document.body.classList.add('dialog-format-active')
document.documentElement.style.overflowY = 'hidden'
let fs_list = document.getElementById("filesystem_list")
fs_list.value = current_fs
}
</script>

View file

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

View file

@ -0,0 +1,239 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "DiskMan"
msgstr "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确定修改分区表"

18
luci-app-netdata/Makefile Normal file
View file

@ -0,0 +1,18 @@
# 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_NAME:=luci-app-netdata
PKG_VERSION:=1.0
PKG_RELEASE:=3
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View file

@ -0,0 +1,6 @@
module("luci.controller.netdata", package.seeall)
function index()
entry({"admin", "status", "netdata"}, template("netdata/netdata"), _("NetData"), 10).leaf = true
end

View file

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

View file

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

View file

@ -0,0 +1,72 @@
# SPDX-Identifier-License: GPL-3.0-only
#
# Copyright (C) 2018 Lean <coolsnowwolf@gmail.com>
# Copyright (C) 2019-2021 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-turboacc
PKG_RELEASE:=$(COMMITCOUNT)
PKG_LICENSE:=GPL-3.0-only
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_BBR_CCA \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_DNSFORWARDER \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_DNSPROXY \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_OFFLOADING \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_SHORTCUT_FE \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_SHORTCUT_FE_DRV
LUCI_TITLE:=LuCI support for Flow Offload / Shortcut-FE
LUCI_PKGARCH:=all
LUCI_DEPENDS:=+pdnsd-alt \
+PACKAGE_$(PKG_NAME)_INCLUDE_BBR_CCA:kmod-tcp-bbr \
+PACKAGE_$(PKG_NAME)_INCLUDE_DNSFORWARDER:dnsforwarder \
+PACKAGE_$(PKG_NAME)_INCLUDE_DNSPROXY:dnsproxy \
+PACKAGE_$(PKG_NAME)_INCLUDE_OFFLOADING:kmod-ipt-offload \
+PACKAGE_$(PKG_NAME)_INCLUDE_SHORTCUT_FE:kmod-shortcut-fe-cm \
+PACKAGE_$(PKG_NAME)_INCLUDE_SHORTCUT_FE_DRV:kmod-shortcut-fe-drv
define Package/$(PKG_NAME)/config
config PACKAGE_$(PKG_NAME)_INCLUDE_OFFLOADING
bool "Include Flow Offload"
depends on (PACKAGE_$(PKG_NAME)_INCLUDE_SHORTCUT_FE=n && PACKAGE_$(PKG_NAME)_INCLUDE_SHORTCUT_FE_DRV=n)
default y if TARGET_ramips
config PACKAGE_$(PKG_NAME)_INCLUDE_SHORTCUT_FE_DRV
bool "Include Shortcut-FE for ECM"
depends on PACKAGE_$(PKG_NAME)_INCLUDE_SHORTCUT_FE=n
default y if (TARGET_ipq806x||TARGET_ipq807x)
config PACKAGE_$(PKG_NAME)_INCLUDE_SHORTCUT_FE
bool "Include Shortcut-FE"
default y if !(TARGET_ipq806x||TARGET_ipq807x||TARGET_ramips)
config PACKAGE_$(PKG_NAME)_INCLUDE_BBR_CCA
bool "Include BBR CCA"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_DNSFORWARDER
bool "Include DNSForwarder"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_DNSPROXY
bool "Include DNSProxy"
default n
endef
define Package/$(PKG_NAME)/preinst
#!/bin/sh
sed -i 's/s:option(Flag, "fullcone", translate("Enable FullCone NAT"))/-- e:option(Flag,"fullcone",translate("Enable FullCone NAT"))/g' $${IPKG_INSTROOT}/usr/lib/lua/luci/model/cbi/firewall/zones.lua
endef
define Package/$(PKG_NAME)/postrm
#!/bin/sh
sed -i 's/-- s:option(Flag, "fullcone", translate("Enable FullCone NAT"))/s:option(Flag, "fullcone", translate("Enable FullCone NAT"))/g' $${IPKG_INSTROOT}/usr/lib/lua/luci/model/cbi/firewall/zones.lua
endef
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View file

@ -0,0 +1,39 @@
module("luci.controller.turboacc", package.seeall)
function index()
if not nixio.fs.access("/etc/config/turboacc") then
return
end
local page
page = entry({"admin", "network", "turboacc"}, cbi("turboacc"), _("Turbo ACC Center"), 1000)
page.i18n = "turboacc"
page.dependent = true
entry({"admin", "network", "turboacc", "status"}, call("action_status"))
end
local function fastpath_status()
return luci.sys.call("/etc/init.d/turboacc check_status fastpath") == 0
end
local function bbr_status()
return luci.sys.call("/etc/init.d/turboacc check_status bbr") == 0
end
local function fullconenat_status()
return luci.sys.call("/etc/init.d/turboacc check_status fullconenat") == 0
end
local function dnscaching_status()
return luci.sys.call("/etc/init.d/turboacc check_status dns") == 0
end
function action_status()
luci.http.prepare_content("application/json")
luci.http.write_json({
fastpath_state = fastpath_status(),
fullconenat_state = fullconenat_status(),
bbr_state = bbr_status(),
dnscaching_state = dnscaching_status()
})
end

View file

@ -0,0 +1,67 @@
local kernel_version = luci.sys.exec("echo -n $(uname -r)")
m = Map("turboacc")
m.title = translate("Turbo ACC Acceleration Settings")
m.description = translate("Opensource Flow Offloading driver (Fast Path or Hardware NAT)")
m:append(Template("turboacc/turboacc_status"))
s = m:section(TypedSection, "turboacc", "")
s.addremove = false
s.anonymous = true
if nixio.fs.access("/lib/modules/" .. kernel_version .. "/xt_FLOWOFFLOAD.ko") then
sw_flow = s:option(Flag, "sw_flow", translate("Software flow offloading"))
sw_flow.default = 0
sw_flow.description = translate("Software based offloading for routing/NAT")
sw_flow:depends("sfe_flow", 0)
end
if luci.sys.call("cat /etc/openwrt_release | grep -q mt762") == 0 then
hw_flow = s:option(Flag, "hw_flow", translate("Hardware flow offloading"))
hw_flow.default = 0
hw_flow.description = translate("Requires hardware NAT support. Implemented at least for mt762x")
hw_flow:depends("sw_flow", 1)
end
if nixio.fs.access("/lib/modules/" .. kernel_version .. "/shortcut-fe-cm.ko") then
sfe_flow = s:option(Flag, "sfe_flow", translate("Shortcut-FE flow offloading"))
sfe_flow.default = 0
sfe_flow.description = translate("Shortcut-FE based offloading for routing/NAT")
sfe_flow:depends("sw_flow", 0)
end
if nixio.fs.access("/lib/modules/" .. kernel_version .. "/tcp_bbr.ko") then
bbr_cca = s:option(Flag, "bbr_cca", translate("BBR CCA"))
bbr_cca.default = 0
bbr_cca.description = translate("Using BBR CCA can improve TCP network performance effectively")
end
if nixio.fs.access("/lib/modules/" .. kernel_version .. "/xt_FULLCONENAT.ko") then
fullcone_nat = s:option(Flag, "fullcone_nat", translate("FullCone NAT"))
fullcone_nat.default = 0
fullcone_nat.description = translate("Using FullCone NAT can improve gaming performance effectively")
end
dns_caching = s:option(Flag, "dns_caching", translate("DNS Caching"))
dns_caching.default = 0
dns_caching.rmempty = false
dns_caching.description = translate("Enable DNS Caching and anti ISP DNS pollution")
dns_caching_mode = s:option(ListValue, "dns_caching_mode", translate("Resolve DNS Mode"), translate("DNS Program"))
dns_caching_mode:value("1", translate("Using PDNSD to query and cache"))
if nixio.fs.access("/usr/bin/dnsforwarder") then
dns_caching_mode:value("2", translate("Using DNSForwarder to query and cache"))
end
if nixio.fs.access("/usr/bin/dnsproxy") then
dns_caching_mode:value("3", translate("Using DNSProxy to query and cache"))
end
dns_caching_mode.default = 1
dns_caching_mode:depends("dns_caching", 1)
dns_caching_dns = s:option(Value, "dns_caching_dns", translate("Upsteam DNS Server"))
dns_caching_dns.default = "114.114.114.114,114.114.115.115,223.5.5.5,223.6.6.6,180.76.76.76,119.29.29.29,119.28.28.28,1.2.4.8,210.2.4.8"
dns_caching_dns.description = translate("Muitiple DNS server can saperate with ','")
dns_caching_dns:depends("dns_caching", 1)
return m

View file

@ -0,0 +1,25 @@
<fieldset class="cbi-section">
<legend><%:Running Status%></legend>
<table width="100%" cellspacing="10" id="_turboacc_status_table">
<tr><td width="33%"><%:Flow Offloading%></td><td id="_fastpath_state"><em><%:Collecting data...%></em></td></tr>
<tr><td width="33%"><%:FullCone NAT%></td><td id="_fullconenat_state"><em><%:Collecting data...%></em></td></tr>
<tr><td width="33%"><%:BBR CCA%></td><td id="_bbr_state"><em><%:Collecting data...%></em></td></tr>
<tr><td width="33%"><%:DNS Caching%></td><td id="_dnscaching_state"><em><%:Collecting data...%></em></td></tr>
</table>
</fieldset>
<script type="text/javascript">//<![CDATA[
var fastpath_state = document.getElementById('_fastpath_state');
var bbr_state = document.getElementById('_bbr_state');
var fullconenat_state = document.getElementById('_fullconenat_state');
var dnscaching_state = document.getElementById('_dnscaching_state');
XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "network", "turboacc", "status")%>', null, function(x, status) {
if ( x && x.status == 200 ) {
fastpath_state.innerHTML = status.fastpath_state ? '<em><b><font color=green><%=luci.sys.exec("/etc/init.d/turboacc check_status fastpath")%></font></b></em>' : '<em><b><font color=red><%:NOT RUNNING%></font></b></em>';
fullconenat_state.innerHTML = status.fullconenat_state ? '<em><b><font color=green><%:RUNNING%></font></b></em>' : '<em><b><font color=red><%:NOT RUNNING%></font></b></em>';
bbr_state.innerHTML = status.bbr_state ? '<em><b><font color=green><%:RUNNING%></font></b></em>' : '<em><b><font color=red><%:NOT RUNNING%></font></b></em>';
dnscaching_state.innerHTML = status.dnscaching_state ? '<em><b><font color=green><%:RUNNING%></font></b></em>' : '<em><b><font color=red><%:NOT RUNNING%></font></b></em>';
}
});
//]]>
//]]></script>

View file

@ -0,0 +1,99 @@
msgid "Turbo ACC Center"
msgstr "Turbo ACC 网络加速"
msgid "Turbo ACC Acceleration Settings"
msgstr "Turbo ACC 网络加速设置"
msgid "Linux Flow Offload Forwarding Engine Settings"
msgstr "Linux Flow Offload Forwarding 转发加速引擎设置"
msgid "Opensource Flow Offloading driver (Fast Path or Hardware NAT)"
msgstr "开源流量分载驱动 (支持 Fast Path 或者 硬件 NAT)"
msgid "Software flow offloading"
msgstr "软件流量分载"
msgid "Software based offloading for routing/NAT"
msgstr "基于软件的 Routing/NAT 分载"
msgid "Hardware flow offloading"
msgstr "硬件流量分载"
msgid "Requires hardware NAT support. Implemented at least for mt762x"
msgstr "需要硬件 NAT 支持。目前 mt762x 已实现"
msgid "Shortcut-FE flow offloading"
msgstr "Shortcut-FE 流量分载"
msgid "Shortcut-FE based offloading for routing/NAT"
msgstr "基于 Shortcut-FE 的 Routing/NAT 分载"
msgid "Bridge Acceleration"
msgstr "桥接加速"
msgid "Enable Bridge Acceleration (may be functional conflict with bridge-mode VPN server)"
msgstr "启用桥接加速 (可能会和路由器上桥接模式的VPN服务器冲突)"
msgid "IPv6 Acceleration"
msgstr "IPv6 加速"
msgid "Enable IPv6 Acceleration"
msgstr "启用 IPv6 加速"
msgid "BBR CCA"
msgstr "BBR 拥塞控制算法"
msgid "Using BBR CCA can improve TCP network performance effectively"
msgstr "使用 BBR 拥塞控制算法可以有效提升 TCP 网络性能"
msgid "FullCone NAT"
msgstr "全锥形 NAT"
msgid "Using FullCone NAT can improve gaming performance effectively"
msgstr "使用全锥形 NAT 可以有效提升游戏体验"
msgid "DNS Caching"
msgstr "DNS 缓存"
msgid "Enable DNS Caching and anti ISP DNS pollution"
msgstr "启用 DNS 多线程查询、缓存,并防止 ISP 的 DNS 广告和域名劫持"
msgid "Resolve DNS Mode"
msgstr "DNS 解析方式"
msgid "DNS Program"
msgstr "DNS 解析程序"
msgid "Using PDNSD to query and cache"
msgstr "使用 PDNSD 解析"
msgid "Using DNSForwarder to query and cache"
msgstr "使用 DNSForwarder 解析"
msgid "Using DNSProxy to query and cache"
msgstr "使用 DNSProxy 解析"
msgid "Upsteam DNS Server"
msgstr "上游 DNS 服务器"
msgid "Muitiple DNS server can saperate with ','"
msgstr "多个上游 DNS 服务器请用 ',' 分隔(注意用英文逗号)"
msgid "Running Status"
msgstr "运行状态"
msgid "Flow Offloading"
msgstr "流量分载"
msgid "BBR CCA"
msgstr "BBR 拥塞控制算法"
msgid "FullCone NAT"
msgstr "全锥型 NAT"
msgid "DNS Caching"
msgstr "DNS 缓存"
msgid "Open Web Interface"
msgstr "打开 Web 界面"

View file

@ -0,0 +1,10 @@
config turboacc 'config'
option sw_flow '1'
option hw_flow '1'
option sfe_flow '1'
option fullcone_nat '1'
option bbr_cca '0'
option dns_caching '0'
option dns_caching_mode '1'
option dns_caching_dns '114.114.114.114,114.114.115.115,223.5.5.5,223.6.6.6,180.76.76.76,119.29.29.29,119.28.28.28,1.2.4.8,210.2.4.8'

View file

@ -0,0 +1,364 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2018 Lean <coolsnowwolf@gmail.com>
# Copyright (C) 2019-2021 Tianling Shen <cnsztl@immortalwrt.org>
START=90
STOP=10
EXTRA_COMMANDS="check_status"
EXTRA_HELP=" check_status Check running status of utils"
restart_utils="true"
PS="/bin/busybox ps"
inital_conf(){
config_load "turboacc"
config_get "sw_flow" "config" "sw_flow" "0"
config_get "hw_flow" "config" "hw_flow" "0"
config_get "sfe_flow" "config" "sfe_flow" "0"
config_get "bbr_cca" "config" "bbr_cca" "0"
config_get "fullcone_nat" "config" "fullcone_nat" "0"
config_get "dns_caching" "config" "dns_caching" "0"
config_get "dns_caching_mode" "config" "dns_caching_mode" "0"
config_get "dns_caching_dns" "config" "dns_caching_dns"
[ ! -e "/lib/modules/$(uname -r)/xt_FLOWOFFLOAD.ko" ] && { sw_flow="0"; hw_flow="0"; }
[ ! -e "/lib/modules/$(uname -r)/shortcut-fe-cm.ko" ] && sfe_flow="0"
[ ! -e "/lib/modules/$(uname -r)/tcp_bbr.ko" ] && bbr_cca="0"
[ ! -e "/lib/modules/$(uname -r)/xt_FULLCONENAT.ko" ] && fullcone_nat="0"
}
start_pdnsd() {
[ -d "/var/run/dnscache" ] || mkdir -p "/var/run/dnscache"
cat > "/var/run/dnscache/dnscache.conf" <<EOF
global {
perm_cache=1024; # dns缓存大小单位KB建议不要写的太大
cache_dir="/var/dnscache"; # 缓存文件的位置
pid_file = /var/run/dnscache.pid;
server_ip = 127.0.0.1; # pdnsd监听的网卡0.0.0.0是全部网卡
server_port=5333; # pdnsd监听的端口不要和别的服务冲突即可
status_ctl = on;
paranoid=on; # 二次请求模式如果请求主DNS服务器返回的是垃圾地址就向备用服务器请求
query_method=udp_only;
neg_domain_pol = off;
par_queries = 400; # 最多同时请求数
min_ttl = 1h; # DNS结果最短缓存时间
max_ttl = 1w; # DNS结果最长缓存时间
timeout = 10; # DNS请求超时时间单位秒
}
server {
label = "routine";
ip = ${dns_caching_dns}; # 这里为主要上级 dns 的 ip 地址建议填写一个当地最快的DNS地址
timeout = 5; # DNS请求超时时间
reject = 74.125.127.102, # 以下是脏IP也就是DNS污染一般会返回的结果如果收到如下DNS结果会触发二次请求TCP协议一般不会碰到脏IP
74.125.155.102,
74.125.39.102,
74.125.39.113,
209.85.229.138,
128.121.126.139,
159.106.121.75,
169.132.13.103,
192.67.198.6,
202.106.1.2,
202.181.7.85,
203.161.230.171,
203.98.7.65,
207.12.88.98,
208.56.31.43,
209.145.54.50,
209.220.30.174,
209.36.73.33,
211.94.66.147,
213.169.251.35,
216.221.188.182,
216.234.179.13,
243.185.187.39,
37.61.54.158,
4.36.66.178,
46.82.174.68,
59.24.3.173,
64.33.88.161,
64.33.99.47,
64.66.163.251,
65.104.202.252,
65.160.219.113,
66.45.252.237,
69.55.52.253,
72.14.205.104,
72.14.205.99,
78.16.49.15,
8.7.198.45,
93.46.8.89,
37.61.54.158,
243.185.187.39,
190.93.247.4,
190.93.246.4,
190.93.245.4,
190.93.244.4,
65.49.2.178,
189.163.17.5,
23.89.5.60,
49.2.123.56,
54.76.135.1,
77.4.7.92,
118.5.49.6,
159.24.3.173,
188.5.4.96,
197.4.4.12,
220.250.64.24,
243.185.187.30,
249.129.46.48,
253.157.14.165;
reject_policy = fail;
}
server {
label = "special"; # 这个随便写
ip = 117.50.10.10,52.80.52.52,119.29.29.29; # 这里为备用DNS服务器的 ip 地址
port = 5353; # 推荐使用53以外的端口DNS服务器必须支持
proxy_only = on;
timeout = 5;
}
source {
owner=localhost;
// serve_aliases=on;
file="/etc/hosts";
}
rr {
name=localhost;
reverse=on;
a=127.0.0.1;
owner=localhost;
soa=localhost,root.localhost,42,86400,900,86400,86400;
}
EOF
if [ ! -f "/var/dnscache/pdnsd.cache" ]; then
mkdir -p "/var/dnscache"
echo -ne "pd13\000\000\000\000" > "/var/dnscache/pdnsd.cache"
chown -R nobody.nogroup "/var/dnscache"
fi
[ -d "/var/sbin" ] || mkdir -p "/var/sbin"
cp -a "/usr/sbin/pdnsd" "/var/sbin/dnscache"
/var/sbin/dnscache -c "/var/run/dnscache/dnscache.conf" > "/var/log/dnscache.file" 2>&1 &
echo "PDNSD: Start DNS Caching"
}
start_dnsforwarder() {
mkdir -p "/var/run/dnscache"
cat > "/var/run/dnscache/dnscache.conf" <<EOF
LogOn false
LogFileThresholdLength 102400
LogFileFolder /var/run/dnscache
UDPLocal 127.0.0.1:5333
UDPGroup ${dns_caching_dns} * on
GroupFile
BlockIP 243.185.187.39,46.82.174.68,37.61.54.158,93.46.8.89,59.24.3.173,203.98.7.65,8.7.198.45,78.16.49.15,159.106.121.75,69.63.187.12,31.13.76.8,31.13.64.49
IPSubstituting
BlockNegativeResponse false
Hosts
HostsUpdateInterval 18000
HostsDownloadPath
HostsScript
HostsRetryInterval 30
AppendHosts
BlockIpv6WhenIpv4Exists false
UseCache true
CacheSize 1048576
MemoryCache true
CacheFile
IgnoreTTL false
OverrideTTL -1
MultipleTTL 1
ReloadCache false
OverwriteCache false
DisabledType
DisabledDomain
DisabledList
DomainStatistic false
DomainStatisticTempletFile
StatisticUpdateInterval 29
EOF
[ -d "/var/sbin" ] || mkdir -p "/var/sbin"
cp -a "/usr/bin/dnsforwarder" "/var/sbin/dnscache"
/var/sbin/dnscache -f "/var/run/dnscache/dnscache.conf" > "/var/log/dnscache.file" 2>&1 &
echo "DnsForwarder: Start DNS Caching"
}
start_dnsproxy() {
[ -d "/var/run/dnscache" ] || mkdir -p "/var/run/dnscache"
echo -e "${dns_caching_dns//,/\\n}" > "/var/run/dnscache/dnscache.conf"
[ -d "/var/sbin" ] || mkdir -p "/var/sbin"
cp -a "/usr/bin/dnsproxy" "/var/sbin/dnscache"
/var/sbin/dnscache -l "127.0.0.1" -p "5333" -b "tls://9.9.9.9" -f "tls://8.8.8.8" -u "/var/run/dnscache/dnscache.conf" --all-servers --cache --cache-min-ttl=3600 > "/var/log/dnscache.file" 2>&1 &
echo "DNSProxy: Start DNS Caching"
}
stop_dnscache() {
$PS -w | grep dnscache | grep -v "grep" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
$PS -w | grep dnscache-while.sh | grep -v "grep" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
killall -q -9 dnscache
rm -rf "/var/dnscache" "/var/run/dnscache"
echo "Stop DNS Caching"
}
change_dns() {
uci -q delete dhcp.@dnsmasq[0].server
uci add_list dhcp.@dnsmasq[0].server="127.0.0.1#5333"
uci set dhcp.@dnsmasq[0].noresolv="1"
uci commit dhcp
}
revert_dns() {
uci -q del_list dhcp.@dnsmasq[0].server="127.0.0.1#5333"
uci set dhcp.@dnsmasq[0].resolvfile="/tmp/resolv.conf.d/resolv.conf.auto"
uci set dhcp.@dnsmasq[0].noresolv="0"
uci commit dhcp
}
start(){
inital_conf
uci set firewall.@defaults[0].flow_offloading="${sw_flow}"
uci set firewall.@defaults[0].flow_offloading_hw="${hw_flow}"
uci set firewall.@defaults[0].fullcone="${fullcone_nat}"
uci commit firewall
[ "${sw_flow}" -ne "1" ] && {
[ "${sfe_flow}" -eq "1" ] && {
[ "$(have_ecm_init)" = "0" ] && {
/etc/init.d/shortcut-fe enabled || /etc/init.d/shortcut-fe enable
/etc/init.d/shortcut-fe start
}
}
[ "${sfe_flow}" -eq "0" ] && [ -e "/etc/init.d/shortcut-fe" ] && [ "$(have_ecm_init)" = "0" ] && \
/etc/init.d/shortcut-fe enabled && {
/etc/init.d/shortcut-fe stop 2>"/dev/null"
/etc/init.d/shortcut-fe disable
}
}
if [ "${bbr_cca}" -eq "1" ]; then
sysctl -w net.ipv4.tcp_congestion_control="bbr"
else
sysctl -w net.ipv4.tcp_congestion_control="cubic"
fi
if [ "${dns_caching}" -eq "1" ]; then
stop_dnscache
sleep 1
rm -f "/var/log/dnscache.file"
if [ "${dns_caching_mode}" = "1" ]; then
start_pdnsd
elif [ "${dns_caching_mode}" = "2" ]; then
start_dnsforwarder
elif [ "${dns_caching_mode}" = "3" ]; then
start_dnsproxy
fi
change_dns
/usr/share/dnscache/dnscache-while.sh > "/var/log/dnscache.file" 2>&1 &
else
stop_dnscache
revert_dns
fi
if [ "${restart_utils}" = "true" ]; then
/etc/init.d/dnsmasq restart >"/dev/null" 2>&1 && echo "DNSMASQ change"
/etc/init.d/firewall restart >"/dev/null" 2>&1
fi
}
stop(){
inital_conf
uci set firewall.@defaults[0].flow_offloading="${sw_flow}"
uci set firewall.@defaults[0].flow_offloading_hw="${hw_flow}"
uci set firewall.@defaults[0].fullcone="${fullcone_nat}"
uci commit firewall
[ "${sfe_flow}" -ne "1" ] && {
[ -e "/etc/init.d/shortcut-fe" ] && [ "$(have_ecm_init)" = "0" ] && {
/etc/init.d/shortcut-fe stop 2>"/dev/null"
/etc/init.d/shortcut-fe enabled && /etc/init.d/shortcut-fe disable
}
}
stop_dnscache
revert_dns
if [ "${restart_utils}" = "true" ]; then
/etc/init.d/dnsmasq restart >"/dev/null" 2>&1 && echo "DNSMASQ revert"
/etc/init.d/firewall restart >"/dev/null" 2>&1
fi
}
restart(){
restart_utils="false"
stop
start
/etc/init.d/dnsmasq restart >"/dev/null" 2>&1 && echo "DNSMASQ restart"
/etc/init.d/firewall restart >"/dev/null" 2>&1
}
have_ecm_init() {
[ -e "/etc/init.d/qca-nss-ecm" ] && echo 1 && return
echo 0
}
ecm_mode(){
config_load "ecm"
config_get front_end global acceleration_engine
case $front_end in
auto | nss | sfe | hybrid)
[ -d /sys/kernel/debug/ecm/ecm_nss_ipv4 ] && echo 'NSS: Enabled ' || echo 'NSS: Disabled '
[ -d /sys/kernel/debug/ecm/ecm_sfe_ipv4 ] && echo 'SFE: Enabled' || echo 'SFE: Disabled'
;;
*)
echo 'Unknown'
esac
}
check_status(){
case "$1" in
"fastpath")
if [ "$(cat "/sys/module/xt_FLOWOFFLOAD/refcnt" 2>"/dev/null" || echo 0)" -ne "0" ]; then
echo -n "Flow Offloading"
exit 0
elif lsmod | grep -q "ecm"; then
echo -n "QCA-ECM Engine: "$(ecm_mode)
exit 0
elif lsmod | grep -q "shortcut_fe_cm"; then
echo -n "Shortcut-FE"
exit 0
else
exit 1
fi
;;
"fullconenat")
[ "$(cat "/sys/module/xt_FULLCONENAT/refcnt" 2>"/dev/null" || echo 0)" -ne "0" ] && \
exit 0 || exit 1
;;
"bbr")
[ "x$(cat "/proc/sys/net/ipv4/tcp_congestion_control" 2>"/dev/null")" = "xbbr" ] && \
exit 0 || exit 1
;;
"dns")
pgrep "dnscache" >"/dev/null" && exit 0 || exit 1
;;
*)
exit 2
;;
esac
}

View file

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

View file

@ -0,0 +1,38 @@
#!/bin/sh
sleeptime=60
dnsconf="/var/run/dnscache/dnscache.conf"
dnsprogram="/var/sbin/dnscache"
logfile="/var/log/dnscache.file"
dns_caching="$(uci -q get turboacc.config.dns_caching)"
dns_caching_mode="$(uci -q get turboacc.config.dns_caching_mode)"
clean_log() {
logrow="$(grep -c "" "${logfile}")"
[ "${logrow}" -lt "500" ] || echo "${curtime} Log 条数超限,清空处理!" > "${logfile}"
}
while [ "${dns_caching}" -eq "1" ];
do
curtime="$(date "+%H:%M:%S")"
clean_log
if pidof dnscache > "/dev/null"; then
echo -e "${curtime} online!" >> "${logfile}"
else
if [ "${dns_caching_mode}" = "1" ]; then
${dnsprogram} -c "${dnsconf}" > "${logfile}" 2>&1 &
elif [ "${dns_caching_mode}" = "2" ]; then
${dnsprogram} -f "${dnsconf}" > "${logfile}" 2>&1 &
elif [ "${dns_caching_mode}" = "3" ]; then
${dnsprogram} -o "${logfile}" -l "127.0.0.1" -p "5333" -b "tls://9.9.9.9" -f "tls://8.8.8.8" -u "${dnsconf}" --all-servers --cache --cache-min-ttl=3600 > "${logfile}" 2>&1 &
fi
echo "${curtime} 重启服务!" >> ${logfile}
fi
sleep "${sleeptime}"
continue
done

View file

@ -9,9 +9,11 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI for Zerotier
LUCI_DEPENDS:=+zerotier
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-zerotier
PKG_VERSION:=1.0
PKG_RELEASE:=20
include $(TOPDIR)/feeds/luci/luci.mk
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View file

@ -1,14 +1,13 @@
module("luci.controller.zerotier",package.seeall)
module("luci.controller.zerotier", package.seeall)
function index()
if not nixio.fs.access("/etc/config/zerotier") then
return
end
entry({"admin","vpn"}, firstchild(), "VPN", 45).dependent = false
entry({"admin", "vpn", "zerotier"},firstchild(), _("ZeroTier")).dependent = false
entry({"admin", "vpn"}, firstchild(), "VPN", 45).dependent = false
entry({"admin", "vpn", "zerotier"}, alias("admin", "vpn", "zerotier", "general"), _("ZeroTier"), 99)
entry({"admin", "vpn", "zerotier", "general"}, cbi("zerotier/settings"), _("Base Setting"), 1)
entry({"admin", "vpn", "zerotier", "log"}, form("zerotier/info"), _("Interface Info"), 2)

View file

@ -7,9 +7,9 @@ t = f:field(TextValue, "conf")
t.rmempty = true
t.rows = 19
function t.cfgvalue()
luci.sys.exec("for i in $(ifconfig | grep 'zt' | awk '{print $1}'); do ifconfig $i; done > /tmp/zero.info")
luci.sys.exec("for i in $(ifconfig | grep 'zt' | awk '{print $1}'); do ifconfig $i; done > /tmp/zero.info")
return fs.readfile(conffile) or ""
end
t.readonly="readonly"
t.readonly = "readonly"
return f

View file

@ -2,7 +2,7 @@ a = Map("zerotier")
a.title = translate("ZeroTier")
a.description = translate("Zerotier is an open source, cross-platform and easy to use virtual LAN")
a:section(SimpleSection).template = "zerotier/zerotier_status"
a:section(SimpleSection).template = "zerotier/zerotier_status"
t = a:section(NamedSection, "sample_config", "zerotier")
t.anonymous = true
@ -10,7 +10,7 @@ t.addremove = false
e = t:option(Flag, "enabled", translate("Enable"))
e.default = 0
e.rmempty=false
e.rmempty = false
e = t:option(DynamicList, "join", translate('ZeroTier Network ID'))
e.password = true

View file

@ -1,11 +1,3 @@
msgid ""
msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Gtranslator 2.91.7\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Zerotier is an open source, cross-platform and easy to use virtual LAN"
msgstr "Zerotier 是一个开源,跨平台,而且适合内网穿透互联的傻瓜配置虚拟 VPN LAN"

View file

@ -110,4 +110,4 @@ stop_service() {
reload_service() {
stop
start
}
}

View file

@ -5,7 +5,8 @@ uci -q batch <<-EOF >/dev/null
add ucitrack zerotier
set ucitrack.@zerotier[-1].init=zerotier
commit ucitrack
delete firewall.zerotier
delete firewall.zerotier
set firewall.zerotier=include
set firewall.zerotier.type=script
set firewall.zerotier.path=/etc/zerotier.start

View file

@ -12,4 +12,4 @@ do
ip_segment="$(ip route | grep "dev $i proto" | awk '{print $1}')"
iptables -t nat -D POSTROUTING -s "${ip_segment}" -j MASQUERADE 2>/dev/null
echo "zt interface $i is stopped!"
done
done

View file

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