mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-27 10:31:38 +08:00
🎁 Sync 2026-03-23 17:00:57
This commit is contained in:
parent
477a1eee80
commit
0d4778f4d9
@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=autoshare-ksmbd
|
||||
PKG_VERSION:=1
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
PKG_ARCH:=all
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
@ -1,12 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# D-Team Technology Co.,Ltd. ShenZhen
|
||||
# 作者:Vic
|
||||
#
|
||||
# 警告:对着屏幕的哥们,我们允许你使用此脚本,但不允许你抹去作者的信息,请保留这段话。
|
||||
#
|
||||
|
||||
. /lib/functions.sh
|
||||
. /lib/functions/service.sh
|
||||
|
||||
@ -16,8 +9,9 @@ config_file="/etc/config/ksmbd"
|
||||
[ ! -f /etc/config/ksmbd ] && {
|
||||
echo "
|
||||
config globals
|
||||
option 'workgroup' 'WORKGROUP'
|
||||
option 'description' 'Ksmbd on OpenWrt'
|
||||
option workgroup 'WORKGROUP'
|
||||
option charset 'UTF-8'
|
||||
option description 'Ksmbd on OpenWRT'
|
||||
" > $config_file
|
||||
}
|
||||
|
||||
@ -32,21 +26,27 @@ wait_for_init() {
|
||||
}
|
||||
|
||||
smb_handle() {
|
||||
config_get path $1 path
|
||||
if [ "$path" = "$2" ] ;then
|
||||
global=1
|
||||
local path
|
||||
config_get path "$1" path
|
||||
if [ -n "$path" ]; then
|
||||
case "$path" in
|
||||
"$2" | "$2"/*) global=1 ;;
|
||||
esac
|
||||
case "$2" in
|
||||
"$path" | "$path"/*) global=1 ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
chk_en() {
|
||||
config_get_bool autoshare $1 autoshare 1
|
||||
[ $autoshare -eq 0 ] && exit
|
||||
config_get_bool autoshare "$1" autoshare 1
|
||||
[ "$autoshare" -eq 0 ] && exit
|
||||
}
|
||||
|
||||
config_load ksmbd
|
||||
config_foreach chk_en ksmbd
|
||||
|
||||
device=`basename $DEVPATH`
|
||||
device=`basename "$DEVPATH"`
|
||||
|
||||
case "$ACTION" in
|
||||
add)
|
||||
@ -55,6 +55,8 @@ case "$ACTION" in
|
||||
sd*);;
|
||||
md*);;
|
||||
hd*);;
|
||||
mmcblk*);;
|
||||
nvme*);;
|
||||
*) return;;
|
||||
esac
|
||||
|
||||
@ -62,22 +64,21 @@ case "$ACTION" in
|
||||
|
||||
wait_for_init
|
||||
|
||||
[ "$(uci -q get ksmbd.@globals[0].autoshare)" == "1" ] && {
|
||||
/etc/init.d/ksmbd restart
|
||||
exit 0;
|
||||
}
|
||||
|
||||
cat /proc/mounts | grep -v '/boot\|/opt' | while read j
|
||||
grep -v '/boot\|/opt' /proc/mounts | while read -r j
|
||||
do
|
||||
str=${j%% *}
|
||||
if [ "$str" == $path ];then
|
||||
if [ "$str" = "$path" ];then
|
||||
strr=${j#* }
|
||||
target=${strr%% *}
|
||||
global=0
|
||||
config_foreach smb_handle share $target
|
||||
|
||||
config_load ksmbd
|
||||
config_foreach smb_handle share "$target"
|
||||
|
||||
name=${target#*/mnt/}
|
||||
name=$(echo $name | sed -e "s/^\///")
|
||||
if [ $global -eq 0 ] ;then
|
||||
name=$(echo "$name" | sed -e "s/^\///")
|
||||
|
||||
if [ "$global" -eq 0 ] ;then
|
||||
echo -e "\n\nconfig share" >> $config_file
|
||||
echo -e "\toption auto '1'" >> $config_file
|
||||
echo -e "\toption name '$name'" >> $config_file
|
||||
@ -93,27 +94,26 @@ case "$ACTION" in
|
||||
fi
|
||||
fi
|
||||
done
|
||||
uci -q set ksmbd.@globals[0].autoshare='1'
|
||||
uci commit ksmbd
|
||||
;;
|
||||
|
||||
remove)
|
||||
i=0
|
||||
while true
|
||||
do
|
||||
dev=`uci get ksmbd.@share[$i].device`
|
||||
[ $? -ne 0 ] && break
|
||||
if ! uci -q get ksmbd.@share[$i] >/dev/null; then
|
||||
break
|
||||
fi
|
||||
|
||||
[ "$dev" = "$device" ] && {
|
||||
auto=`uci get ksmbd.@share[$i].auto`
|
||||
[ $auto = "1" ] && {
|
||||
mount_dir=`uci get ksmbd.@share[$i].name`
|
||||
dev=$(uci -q get ksmbd.@share[$i].device)
|
||||
if [ "$dev" = "$device" ]; then
|
||||
auto=$(uci -q get ksmbd.@share[$i].auto)
|
||||
if [ "$auto" = "1" ]; then
|
||||
uci delete ksmbd.@share[$i]
|
||||
uci commit
|
||||
uci commit ksmbd
|
||||
/etc/init.d/ksmbd reload
|
||||
return
|
||||
}
|
||||
}
|
||||
fi
|
||||
fi
|
||||
let i+=1
|
||||
done
|
||||
;;
|
||||
|
||||
@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=autoshare-samba
|
||||
PKG_VERSION:=1
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
PKG_ARCH:=all
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
@ -1,12 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# D-Team Technology Co.,Ltd. ShenZhen
|
||||
# 作者:Vic
|
||||
#
|
||||
# 警告:对着屏幕的哥们,我们允许你使用此脚本,但不允许你抹去作者的信息,请保留这段话。
|
||||
#
|
||||
|
||||
. /lib/functions.sh
|
||||
. /lib/functions/service.sh
|
||||
|
||||
@ -33,21 +26,27 @@ wait_for_init() {
|
||||
}
|
||||
|
||||
smb_handle() {
|
||||
config_get path $1 path
|
||||
if [ "$path" = "$2" ] ;then
|
||||
global=1
|
||||
local path
|
||||
config_get path "$1" path
|
||||
if [ -n "$path" ]; then
|
||||
case "$path" in
|
||||
"$2" | "$2"/*) global=1 ;;
|
||||
esac
|
||||
case "$2" in
|
||||
"$path" | "$path"/*) global=1 ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
chk_en() {
|
||||
config_get_bool autoshare $1 autoshare 1
|
||||
[ $autoshare -eq 0 ] && exit
|
||||
config_get_bool autoshare "$1" autoshare 1
|
||||
[ "$autoshare" -eq 0 ] && exit
|
||||
}
|
||||
|
||||
config_load samba4
|
||||
config_foreach chk_en samba4
|
||||
|
||||
device=`basename $DEVPATH`
|
||||
device=`basename "$DEVPATH"`
|
||||
|
||||
case "$ACTION" in
|
||||
add)
|
||||
@ -56,6 +55,8 @@ case "$ACTION" in
|
||||
sd*);;
|
||||
md*);;
|
||||
hd*);;
|
||||
mmcblk*);;
|
||||
nvme*);;
|
||||
*) return;;
|
||||
esac
|
||||
|
||||
@ -63,22 +64,21 @@ case "$ACTION" in
|
||||
|
||||
wait_for_init
|
||||
|
||||
[ "$(uci -q get samba4.@samba[0].autoshare)" == "1" ] && {
|
||||
/etc/init.d/samba4 restart
|
||||
exit 0;
|
||||
}
|
||||
|
||||
cat /proc/mounts | grep -v '/boot\|/opt' | while read j
|
||||
grep -v '/boot\|/opt' /proc/mounts | while read -r j
|
||||
do
|
||||
str=${j%% *}
|
||||
if [ "$str" == $path ];then
|
||||
if [ "$str" = "$path" ];then
|
||||
strr=${j#* }
|
||||
target=${strr%% *}
|
||||
global=0
|
||||
config_foreach smb_handle sambashare $target
|
||||
|
||||
config_load samba4
|
||||
config_foreach smb_handle sambashare "$target"
|
||||
|
||||
name=${target#*/mnt/}
|
||||
name=$(echo $name | sed -e "s/^\///")
|
||||
if [ $global -eq 0 ] ;then
|
||||
name=$(echo "$name" | sed -e "s/^\///")
|
||||
|
||||
if [ "$global" -eq 0 ] ;then
|
||||
echo -e "\n\nconfig sambashare" >> $config_file
|
||||
echo -e "\toption auto '1'" >> $config_file
|
||||
echo -e "\toption name '$name'" >> $config_file
|
||||
@ -94,27 +94,26 @@ case "$ACTION" in
|
||||
fi
|
||||
fi
|
||||
done
|
||||
uci -q set samba4.@samba[0].autoshare='1'
|
||||
uci commit samba4
|
||||
;;
|
||||
|
||||
remove)
|
||||
i=0
|
||||
while true
|
||||
do
|
||||
dev=`uci get samba4.@sambashare[$i].device`
|
||||
[ $? -ne 0 ] && break
|
||||
if ! uci -q get samba4.@sambashare[$i] >/dev/null; then
|
||||
break
|
||||
fi
|
||||
|
||||
[ "$dev" = "$device" ] && {
|
||||
auto=`uci get samba4.@sambashare[$i].auto`
|
||||
[ $auto = "1" ] && {
|
||||
mount_dir=`uci get samba4.@sambashare[$i].name`
|
||||
dev=$(uci -q get samba4.@sambashare[$i].device)
|
||||
if [ "$dev" = "$device" ]; then
|
||||
auto=$(uci -q get samba4.@sambashare[$i].auto)
|
||||
if [ "$auto" = "1" ]; then
|
||||
uci delete samba4.@sambashare[$i]
|
||||
uci commit
|
||||
uci commit samba4
|
||||
/etc/init.d/samba4 reload
|
||||
return
|
||||
}
|
||||
}
|
||||
fi
|
||||
fi
|
||||
let i+=1
|
||||
done
|
||||
;;
|
||||
|
||||
@ -16,8 +16,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-amlogic
|
||||
PKG_VERSION:=3.1.286
|
||||
PKG_RELEASE:=1
|
||||
PKG_VERSION:=3.1.287
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_LICENSE:=GPL-2.0 License
|
||||
PKG_MAINTAINER:=ophub <https://github.com/ophub/luci-app-amlogic>
|
||||
|
||||
@ -159,6 +159,16 @@ else
|
||||
[[ "${?}" -ne "0" ]] && tolog "02.05 Language pack [ ${langfile} ] download failed." "1"
|
||||
done
|
||||
|
||||
# The .apk filename is preceded by a tilde (~) instead of a dot (.).
|
||||
for file in ${TMP_CHECK_DIR}/*.apk; do
|
||||
[[ -f "${file}" ]] || continue
|
||||
base_name="$(basename "${file}")"
|
||||
new_name="$(echo "${base_name}" | sed -E 's/\.([a-f0-9]{7}\.apk)/~\1/')"
|
||||
if [[ "${base_name}" != "${new_name}" ]]; then
|
||||
mv -f "${file}" "${TMP_CHECK_DIR}/${new_name}" || true
|
||||
fi
|
||||
done
|
||||
|
||||
sync && sleep 2
|
||||
fi
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_VERSION:=0.1.5
|
||||
PKG_RELEASE:=6
|
||||
PKG_VERSION:=0.1.6
|
||||
PKG_RELEASE:=7
|
||||
|
||||
LUCI_TITLE:=LuCI support for OpenClaw Launcher
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
@ -130,12 +130,14 @@
|
||||
if (status.installing) return "安装中";
|
||||
if (!status || !status.installed) return "未安装";
|
||||
if (status.running) return "运行中";
|
||||
if (status.reachable) return "运行中(未托管)";
|
||||
return "已停止";
|
||||
}
|
||||
|
||||
function statusDotClass(status) {
|
||||
if (status && status.installing) return "";
|
||||
if (status && status.running) return "is-success";
|
||||
if (status && status.reachable) return "";
|
||||
return "is-danger";
|
||||
}
|
||||
|
||||
@ -382,11 +384,8 @@
|
||||
var savingAi = state.savingSection === "ai";
|
||||
var savingAccess = state.savingSection === "access";
|
||||
|
||||
var serviceButtons = showServiceActions ? (
|
||||
(!status.running ? '<button class="oclm-button" type="button" data-op="start">启动服务</button>' : '') +
|
||||
(status.running ? '<button class="oclm-button" type="button" data-op="stop">停止服务</button>' : '') +
|
||||
'<button class="oclm-button" type="button" data-op="restart">重启服务</button>'
|
||||
) : '';
|
||||
var canStartService = !status.running && !status.reachable;
|
||||
var canStopService = !!status.running;
|
||||
|
||||
root.innerHTML =
|
||||
'<style>' + styleText + '</style>' +
|
||||
@ -415,7 +414,7 @@
|
||||
'<button id="oclm-install-btn" class="oclm-button oclm-button-primary" type="button" data-install-action="1"' + (status.installing ? ' disabled' : '') + '>' + installLabel + '</button>' +
|
||||
'<label class="oclm-check"><input type="checkbox" id="oclm-install-accelerated"' + (installAcceleratedChecked ? ' checked' : '') + ' />Kspeeder 加速安装</label>' +
|
||||
'</div>' +
|
||||
'<button id="oclm-open-console" class="oclm-button oclm-button-primary' + (status.running ? '' : ' oclm-hidden') + '" type="button" data-open-console="1"' + (state.consoleReady ? '' : ' disabled') + '>' + openclawIcon("oclm-button-icon") + (state.consoleReady ? '打开控制台' : '控制台准备中…') + '</button>' +
|
||||
'<button id="oclm-open-console" class="oclm-button oclm-button-primary' + ((status.running || status.reachable) ? '' : ' oclm-hidden') + '" type="button" data-open-console="1"' + (state.consoleReady ? '' : ' disabled') + '>' + openclawIcon("oclm-button-icon") + (state.consoleReady ? '打开控制台' : '控制台准备中…') + '</button>' +
|
||||
'<button id="oclm-cancel-install" class="oclm-button oclm-button-danger' + (status.installing ? '' : ' oclm-hidden') + '" type="button" data-op="cancel_install">停止安装</button>' +
|
||||
'</div>' +
|
||||
'<div id="oclm-status-note" class="oclm-status-note' + (status.installing ? '' : ' oclm-hidden') + '">安装任务正在后台运行,点击“安装中”可继续查看日志。</div>' +
|
||||
@ -438,7 +437,15 @@
|
||||
["auto", "自动"]
|
||||
])) +
|
||||
fieldInput("数据目录", '<input class="oclm-control" type="text" id="oclm-base-dir" list="oclm-base-dir-options" value="' + escapeAttr(form.base_dir || "") + '" /><datalist id="oclm-base-dir-options">' + baseDirOptions + '</datalist>') +
|
||||
'<div class="oclm-section-submit"><button class="oclm-button oclm-button-primary" type="button" id="oclm-save-basic"' + (savingBasic ? ' disabled' : '') + '>' + (savingBasic ? '应用中…' : '保存并应用') + '</button>' + serviceButtons + (state.lastAppliedAt ? '<span class="oclm-applied-hint">已于 ' + escapeHtml(state.lastAppliedAt) + ' 更新配置</span>' : '') + '</div>' +
|
||||
'<div class="oclm-section-submit">' +
|
||||
'<button class="oclm-button oclm-button-primary" type="button" id="oclm-save-basic"' + (savingBasic ? ' disabled' : '') + '>' + (savingBasic ? '应用中…' : '保存并应用') + '</button>' +
|
||||
'<span id="oclm-service-actions" class="oclm-inline-buttons' + (showServiceActions ? '' : ' oclm-hidden') + '">' +
|
||||
'<button id="oclm-btn-start" class="oclm-button' + (canStartService ? '' : ' oclm-hidden') + '" type="button" data-op="start">启动服务</button>' +
|
||||
'<button id="oclm-btn-stop" class="oclm-button' + (canStopService ? '' : ' oclm-hidden') + '" type="button" data-op="stop">停止服务</button>' +
|
||||
'<button id="oclm-btn-restart" class="oclm-button" type="button" data-op="restart">重启服务</button>' +
|
||||
'</span>' +
|
||||
(state.lastAppliedAt ? '<span class="oclm-applied-hint">已于 ' + escapeHtml(state.lastAppliedAt) + ' 更新配置</span>' : '') +
|
||||
'</div>' +
|
||||
'</div></div>' +
|
||||
|
||||
'<div class="' + (activeTab === "ai" ? '' : 'oclm-hidden') + '">' +
|
||||
@ -485,6 +492,7 @@
|
||||
|
||||
function updateStatusDom(status) {
|
||||
status = status || {};
|
||||
var gatewayActive = !!(status.running || status.reachable);
|
||||
|
||||
function byId(id) {
|
||||
if (root.getElementById) return root.getElementById(id);
|
||||
@ -565,15 +573,40 @@
|
||||
else cancelInstallBtn.classList.add("oclm-hidden");
|
||||
}
|
||||
if (openConsoleBtn) {
|
||||
if (status.running) openConsoleBtn.classList.remove("oclm-hidden");
|
||||
if (gatewayActive) openConsoleBtn.classList.remove("oclm-hidden");
|
||||
else openConsoleBtn.classList.add("oclm-hidden");
|
||||
openConsoleBtn.disabled = !(status.running && state.consoleReady);
|
||||
openConsoleBtn.disabled = !state.consoleReady;
|
||||
openConsoleBtn.innerHTML = openclawIcon("oclm-button-icon") + (state.consoleReady ? "打开控制台" : "控制台准备中…");
|
||||
}
|
||||
if (noteEl) {
|
||||
if (status.installing) noteEl.classList.remove("oclm-hidden");
|
||||
else noteEl.classList.add("oclm-hidden");
|
||||
}
|
||||
|
||||
var svcWrap = byId("oclm-service-actions");
|
||||
var btnStart = byId("oclm-btn-start");
|
||||
var btnStop = byId("oclm-btn-stop");
|
||||
var btnRestart = byId("oclm-btn-restart");
|
||||
var showServiceActions = !!(status.installed && !status.installing);
|
||||
var canStartService = !!(!status.running && !status.reachable);
|
||||
var canStopService = !!status.running;
|
||||
|
||||
if (svcWrap) {
|
||||
if (showServiceActions) svcWrap.classList.remove("oclm-hidden");
|
||||
else svcWrap.classList.add("oclm-hidden");
|
||||
}
|
||||
if (btnStart) {
|
||||
if (showServiceActions && canStartService) btnStart.classList.remove("oclm-hidden");
|
||||
else btnStart.classList.add("oclm-hidden");
|
||||
}
|
||||
if (btnStop) {
|
||||
if (showServiceActions && canStopService) btnStop.classList.remove("oclm-hidden");
|
||||
else btnStop.classList.add("oclm-hidden");
|
||||
}
|
||||
if (btnRestart) {
|
||||
if (showServiceActions) btnRestart.classList.remove("oclm-hidden");
|
||||
else btnRestart.classList.add("oclm-hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function fieldInput(label, control) {
|
||||
@ -954,7 +987,7 @@
|
||||
function refreshStatus(done) {
|
||||
request(config.statusUrl).then(function(data) {
|
||||
state.status = data || {};
|
||||
if (state.status && state.status.running) {
|
||||
if (state.status && (state.status.running || state.status.reachable)) {
|
||||
if (!state.consoleReady) {
|
||||
pollConsoleReady(20);
|
||||
}
|
||||
|
||||
@ -353,6 +353,46 @@ local function get_pid_uptime_human(pid)
|
||||
return fmt_elapsed(elapsed)
|
||||
end
|
||||
|
||||
local function probe_gateway_ready(port, base_dir, bind)
|
||||
local sys = require "luci.sys"
|
||||
local util = require "luci.util"
|
||||
|
||||
if not port or not tostring(port):match("^%d+$") then
|
||||
return false, "", ""
|
||||
end
|
||||
if not base_dir or base_dir == "" then
|
||||
return false, "", ""
|
||||
end
|
||||
|
||||
local base_path = configured_base_path(base_dir)
|
||||
local candidates = {}
|
||||
|
||||
if bind == "lan" or bind == "auto" then
|
||||
local ip = lan_ipv4()
|
||||
if ip ~= "" then
|
||||
candidates[#candidates + 1] = ip
|
||||
end
|
||||
end
|
||||
candidates[#candidates + 1] = "127.0.0.1"
|
||||
|
||||
local last_code, last_url = "", ""
|
||||
for _, host in ipairs(candidates) do
|
||||
local url = "http://" .. host .. ":" .. port .. base_path
|
||||
last_url = url
|
||||
local cmd = string.format(
|
||||
"curl -fsS -o /dev/null --connect-timeout 1 --max-time 2 -w '%%{http_code}' %s 2>/dev/null",
|
||||
util.shellquote(url)
|
||||
)
|
||||
local code = sys.exec(cmd):gsub("%s+$", "")
|
||||
last_code = code
|
||||
local n = tonumber(code) or 0
|
||||
if n >= 200 and n < 400 then
|
||||
return true, code, url
|
||||
end
|
||||
end
|
||||
return false, last_code, last_url
|
||||
end
|
||||
|
||||
function action_status()
|
||||
local sys = require "luci.sys"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
@ -373,6 +413,11 @@ function action_status()
|
||||
running = (st == "running")
|
||||
installed = (st == "running" or st == "stopped")
|
||||
end
|
||||
|
||||
local reachable, reachable_code, reachable_url = false, "", ""
|
||||
if installed and not running then
|
||||
reachable, reachable_code, reachable_url = probe_gateway_ready(port, base_dir, bind)
|
||||
end
|
||||
local lock_running, lock_pid = installer_lock_running()
|
||||
local installing = (task.running and (task.op == "install" or task.op == "upgrade")) or lock_running
|
||||
if not installing and task.running and not installed and not running then
|
||||
@ -405,6 +450,9 @@ function action_status()
|
||||
enabled = enabled,
|
||||
installed = installed,
|
||||
running = running,
|
||||
reachable = reachable,
|
||||
reachable_http_code = reachable_code,
|
||||
reachable_url = reachable_url,
|
||||
task_running = task.running,
|
||||
task_op = task.op,
|
||||
installing = installing,
|
||||
@ -423,11 +471,10 @@ function action_status()
|
||||
end
|
||||
|
||||
function action_ready()
|
||||
local sys = require "luci.sys"
|
||||
local util = require "luci.util"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
|
||||
local port = uci:get("openclawmgr", "main", "port") or "18789"
|
||||
local bind = uci:get("openclawmgr", "main", "bind") or "lan"
|
||||
local base_dir = uci:get("openclawmgr", "main", "base_dir") or ""
|
||||
|
||||
if not port:match("^%d+$") then port = "18789" end
|
||||
@ -437,20 +484,13 @@ function action_ready()
|
||||
return
|
||||
end
|
||||
|
||||
local base_url = "http://127.0.0.1:" .. port .. configured_base_path(base_dir)
|
||||
local cmd = string.format(
|
||||
"curl -fsS -o /dev/null --connect-timeout 1 --max-time 2 -w '%%{http_code}' %s 2>/dev/null",
|
||||
util.shellquote(base_url)
|
||||
)
|
||||
local code = sys.exec(cmd):gsub("%s+$", "")
|
||||
local n = tonumber(code) or 0
|
||||
local ready = (n >= 200 and n < 400)
|
||||
local ready, code, url = probe_gateway_ready(port, base_dir, bind)
|
||||
|
||||
write_json({
|
||||
ok = true,
|
||||
ready = ready,
|
||||
http_code = code,
|
||||
url = base_url,
|
||||
url = url,
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
@ -559,15 +559,14 @@ if selected_env_key and selected_api_key ~= "" then
|
||||
end
|
||||
cfg.env = next(env) and env or nil
|
||||
|
||||
local models = {}
|
||||
models.mode = "merge"
|
||||
local providers = {}
|
||||
models.providers = providers
|
||||
cfg.models = models
|
||||
local models = ensure_table(cfg, "models")
|
||||
if type(models.mode) ~= "string" or models.mode == "" then
|
||||
models.mode = "merge"
|
||||
end
|
||||
local providers = ensure_table(models, "providers")
|
||||
|
||||
local current_provider_id = os.getenv("DEFAULT_AGENT_NAME") or "anthropic"
|
||||
local current_provider = {}
|
||||
providers[current_provider_id] = current_provider
|
||||
local current_provider_id = os.getenv("DEFAULT_AGENT_NAME") or "anthropic"
|
||||
local current_provider = ensure_table(providers, current_provider_id)
|
||||
|
||||
if current_provider_id == "openai" then
|
||||
current_provider.api = "openai-completions"
|
||||
@ -600,13 +599,30 @@ elseif current_provider_id == "moonshot" then
|
||||
}
|
||||
end
|
||||
|
||||
local override_base = os.getenv("DEFAULT_AGENT_OVERRIDE_BASE_URL") or ""
|
||||
local base_url_mode = os.getenv("DEFAULT_AGENT_BASE_URL_MODE") or "default"
|
||||
if base_url_mode == "override" and override_base ~= "" then
|
||||
current_provider.baseUrl = override_base
|
||||
elseif base_url_mode == "default" then
|
||||
current_provider.baseUrl = os.getenv("DEFAULT_AGENT_BASE_URL") or current_provider.baseUrl
|
||||
end
|
||||
local override_base = os.getenv("DEFAULT_AGENT_OVERRIDE_BASE_URL") or ""
|
||||
local base_url_mode = os.getenv("DEFAULT_AGENT_BASE_URL_MODE") or "default"
|
||||
if base_url_mode == "override" and override_base ~= "" then
|
||||
current_provider.baseUrl = override_base
|
||||
elseif base_url_mode == "default" then
|
||||
current_provider.baseUrl = os.getenv("DEFAULT_AGENT_BASE_URL") or current_provider.baseUrl
|
||||
end
|
||||
|
||||
-- luci.jsonc encodes empty Lua tables as JSON arrays ([]). To avoid producing
|
||||
-- invalid provider entries like {"deepseek/deepseek-chat":[]}, drop provider
|
||||
-- records that are empty or look like a provider/model string.
|
||||
do
|
||||
local to_del = {}
|
||||
for k, v in pairs(providers) do
|
||||
if type(k) == "string" and k:find("/", 1, true) then
|
||||
table.insert(to_del, k)
|
||||
elseif type(v) == "table" and next(v) == nil then
|
||||
table.insert(to_del, k)
|
||||
end
|
||||
end
|
||||
for _, k in ipairs(to_del) do
|
||||
providers[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local agents = ensure_table(cfg, "agents")
|
||||
local defaults = ensure_table(agents, "defaults")
|
||||
|
||||
6
luci-app-openclawmgr/root/usr/share/openclawmgr/oc-config.sh
Executable file
6
luci-app-openclawmgr/root/usr/share/openclawmgr/oc-config.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
# Backward-compatible wrapper. New entry: openclawmgr-cli.sh
|
||||
|
||||
set -eu
|
||||
|
||||
exec /usr/share/openclawmgr/openclawmgr-cli.sh "$@"
|
||||
@ -40,6 +40,22 @@ local api = require "luci.passwall.api"
|
||||
sendNextChunk();
|
||||
}
|
||||
|
||||
function decodeIfBase64(str) {
|
||||
try {
|
||||
let s = str.replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (s.length % 4) s += '=';
|
||||
const decoded = decodeURIComponent(
|
||||
atob(s).split('').map(c =>
|
||||
'%' + c.charCodeAt(0).toString(16).padStart(2, '0')
|
||||
).join('')
|
||||
);
|
||||
if (btoa(unescape(encodeURIComponent(decoded))).replace(/=+$/, '') === s.replace(/=+$/, '')) {
|
||||
return decoded;
|
||||
}
|
||||
} catch (e) {}
|
||||
return str;
|
||||
}
|
||||
|
||||
function getBg(el) {
|
||||
if (!el) return null;
|
||||
const style = getComputedStyle(el);
|
||||
@ -74,15 +90,14 @@ local api = require "luci.passwall.api"
|
||||
const group = (document.querySelector('#addlink_group_custom input[type="hidden"]')?.value || "default");
|
||||
nodes_link = nodes_link.replace(/\t/g, "").replace(/\r\n|\r/g, "\n").replace(/<[^>]*>/g, '').trim();
|
||||
if (nodes_link != "") {
|
||||
nodes_link = decodeIfBase64(nodes_link);
|
||||
let s = nodes_link.split('://');
|
||||
if (s.length > 1) {
|
||||
ajax_add_node(nodes_link, group);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
alert("<%:Please enter the correct link.%>");
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
document.getElementById("nodes_link").focus();
|
||||
}
|
||||
}
|
||||
@ -93,8 +108,7 @@ local api = require "luci.passwall.api"
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
alert("<%:Error%>");
|
||||
}
|
||||
});
|
||||
@ -154,8 +168,7 @@ local api = require "luci.passwall.api"
|
||||
function(x, data) {
|
||||
if (x && x.status == 200) {
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
alert("<%:Error%>");
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,100 +0,0 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=PPPoE Server User Management (Modern JS/Ucode)
|
||||
LUCI_DEPENDS:=+luci-compat +kmod-nft-connlimit +ucode +rpcd +ppp +ppp-mod-pppoe
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_VERSION:=2.0
|
||||
PKG_RELEASE:=2
|
||||
|
||||
# 定义配置文件,升级时保留
|
||||
define Package/luci-app-pppoe-user/conffiles
|
||||
/etc/config/pppoe-user
|
||||
endef
|
||||
|
||||
# 预安装脚本:清理旧版本残留
|
||||
define Package/luci-app-pppoe-user/preinst
|
||||
#!/bin/sh
|
||||
if [ -z "$${IPKG_INSTROOT}" ]; then
|
||||
# 仅在运行时清理,安装到新根文件系统时不清理
|
||||
if [ -f /etc/init.d/pppoe-user ]; then
|
||||
/etc/init.d/pppoe-user stop 2>/dev/null || true
|
||||
fi
|
||||
# 清理旧的 Lua 视图缓存
|
||||
rm -rf /tmp/luci-modulecache/*
|
||||
rm -rf /tmp/luci-indexcache/*
|
||||
fi
|
||||
exit 0
|
||||
endef
|
||||
|
||||
define Package/luci-app-pppoe-user/install
|
||||
# --- 1. I18N (让 luci.mk 自动处理,或手动编译) ---
|
||||
# 注意:现代 luci.mk 会自动扫描 po 目录并编译。
|
||||
# 如果必须手动指定,确保编译为 .lua 或 .json (视版本而定)
|
||||
# 这里我们依赖 luci.mk 的默认行为,只需确保 po 目录存在即可。
|
||||
# 如果 luci.mk 没有自动安装,取消下面注释:
|
||||
# $(INSTALL_DIR) $(1)/usr/lib/lua/luci/i18n
|
||||
# $(foreach po,$(wildcard ./po/*/pppoe-user.po), \
|
||||
# $(call Build/install-po,$(po),$(1)/usr/lib/lua/luci/i18n) \
|
||||
# )
|
||||
|
||||
# --- 2. LuCI JS Views (Modern Path) ---
|
||||
$(INSTALL_DIR) $(1)/usr/share/luci/static/resources/view/pppoe-user
|
||||
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/pppoe-user/*.js \
|
||||
$(1)/usr/share/luci/static/resources/view/pppoe-user/
|
||||
|
||||
# --- 3. Ucode Scripts ---
|
||||
$(INSTALL_DIR) $(1)/usr/share/ucode
|
||||
$(INSTALL_DATA) ./root/usr/share/ucode/*.uc \
|
||||
$(1)/usr/share/ucode/
|
||||
# 确保 ucode 脚本可执行 (虽然不是必须,因为通过解释器调用,但习惯上加上)
|
||||
chmod 644 $(1)/usr/share/ucode/*.uc
|
||||
|
||||
# --- 4. RPCD ACL ---
|
||||
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/*.json \
|
||||
$(1)/usr/share/rpcd/acl.d/
|
||||
|
||||
# --- 5. System Configuration ---
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./root/etc/config/pppoe-user \
|
||||
$(1)/etc/config/
|
||||
|
||||
# --- 6. Init Scripts ---
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./root/etc/init.d/pppoe-user \
|
||||
$(1)/etc/init.d/
|
||||
|
||||
# --- 7. PPP Scripts ---
|
||||
$(INSTALL_DIR) $(1)/etc/ppp
|
||||
$(INSTALL_BIN) ./root/etc/ppp/ip-up \
|
||||
$(1)/etc/ppp/
|
||||
$(INSTALL_BIN) ./root/etc/ppp/ip-down \
|
||||
$(1)/etc/ppp/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/ppp/ip-up.d
|
||||
$(INSTALL_BIN) ./root/etc/ppp/ip-up.d/* \
|
||||
$(1)/etc/ppp/ip-up.d/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/ppp/ip-down.d
|
||||
$(INSTALL_BIN) ./root/etc/ppp/ip-down.d/* \
|
||||
$(1)/etc/ppp/ip-down.d/
|
||||
|
||||
# --- 8. Hotplug Scripts ---
|
||||
$(INSTALL_DIR) $(1)/etc/hotplug.d/iface
|
||||
$(INSTALL_BIN) ./root/etc/hotplug.d/iface/* \
|
||||
$(1)/etc/hotplug.d/iface/
|
||||
|
||||
# --- 9. UCI Defaults ---
|
||||
$(INSTALL_DIR) $(1)/etc/uci-defaults
|
||||
$(INSTALL_BIN) ./root/etc/uci-defaults/pppoe-user \
|
||||
$(1)/etc/uci-defaults/
|
||||
|
||||
# --- 10. LuCI Menu ---
|
||||
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
|
||||
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/*.json \
|
||||
$(1)/usr/share/luci/menu.d/
|
||||
endef
|
||||
|
||||
# 调用标准的 luci.mk 构建规则
|
||||
# 它会自动处理 i18n 编译和元数据生成
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
@ -1,4 +0,0 @@
|
||||
# luci-app-pppoe-user
|
||||
|
||||
OpenWrt Luci PPPoE dial-up user management application
|
||||
Need to be used with the installation of rp-pppoe software package
|
||||
@ -1,47 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require uci';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return uci.load('pppoe-user');
|
||||
},
|
||||
render: function() {
|
||||
var users = uci.sections('pppoe-user', 'user');
|
||||
var downUsers = users.filter(u => u.enabled == '0');
|
||||
|
||||
var m = new form.Map('pppoe-user');
|
||||
var s = m.section(form.TableSection, 'downtime', _('Downtime User [%d]').format(downUsers.length));
|
||||
s.anonymous = true;
|
||||
s.addremove = false;
|
||||
|
||||
// 填充数据
|
||||
s.section = function(section_id) {
|
||||
// 手动注入数据到 section
|
||||
return this.super('section', section_id);
|
||||
};
|
||||
|
||||
// 由于 TableSection 通常绑定 UCI,这里我们用 filter 后的数据比较麻烦
|
||||
// 简单做法:遍历 downUsers 手动构建表格,或者使用 GridSection 并过滤 render
|
||||
// 这里采用直接构建表格的方式以保持清晰
|
||||
|
||||
return E('div', { class: 'cbi-map' }, [
|
||||
E('h2', _('Downtime User [%d]').format(downUsers.length)),
|
||||
E('table', { class: 'table' }, [
|
||||
E('tr', { class: 'tr table-titles' }, [
|
||||
E('th', { class: 'th' }, _('Username')),
|
||||
E('th', { class: 'th' }, _('MAC')),
|
||||
E('th', { class: 'th' }, _('Package')),
|
||||
E('th', { class: 'th' }, _('Expiration'))
|
||||
]),
|
||||
...downUsers.map(u => E('tr', { class: 'tr cbi-rowstyle-1' }, [
|
||||
E('td', { class: 'td' }, u.username),
|
||||
E('td', { class: 'td' }, u.macaddr || '-'),
|
||||
E('td', { class: 'td' }, u.package),
|
||||
E('td', { class: 'td' }, u.expires)
|
||||
]))
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
@ -1,22 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require fs';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return fs.read('/var/pppoe-user/log/interface.log');
|
||||
},
|
||||
render: function(logs) {
|
||||
var m = new form.Map('pppoe-user');
|
||||
var s = m.section(form.NamedSection, 'logview', 'dummy');
|
||||
|
||||
var o = s.option(form.TextValue, '_log');
|
||||
o.rows = 30;
|
||||
o.readonly = true;
|
||||
o.cfgvalue = function() {
|
||||
return logs || _('No log available.');
|
||||
};
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@ -1,169 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require fs';
|
||||
'require ubus';
|
||||
'require ui';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
fs.read('/proc/net/dev'),
|
||||
fs.list('/var/etc/pppoe-user/session')
|
||||
]).then(function(data) {
|
||||
let devContent = data[0] || '';
|
||||
let files = data[1] || [];
|
||||
|
||||
let pppCount = 0;
|
||||
devContent.split(/\n/).forEach(function(line) {
|
||||
if (/^\s*ppp/.test(line)) pppCount++;
|
||||
});
|
||||
|
||||
let sessions = [];
|
||||
let promises = files.map(function(f) {
|
||||
if (f.name.endsWith('.json')) {
|
||||
return fs.read('/var/etc/pppoe-user/session/' + f.name).then(function(content) {
|
||||
try {
|
||||
let obj = JSON.parse(content);
|
||||
obj.session_file = '/var/etc/pppoe-user/session/' + f.name;
|
||||
sessions.push(obj);
|
||||
} catch(e) {}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(function() {
|
||||
return { count: pppCount, sessions: sessions };
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var m, s, o;
|
||||
var tableData = data.sessions || [];
|
||||
|
||||
m = new form.Map('pppoe-user');
|
||||
s = m.section(form.TableSection, 'sessions', _('Online Users [%d]').format(data.count));
|
||||
s.anonymous = true;
|
||||
s.addremove = false;
|
||||
s.sortable = false;
|
||||
s.nodescriptions = true;
|
||||
|
||||
o = s.option(form.DummyValue, 'username', _('User Name'));
|
||||
o = s.option(form.DummyValue, 'mac', _('MAC address'));
|
||||
o = s.option(form.DummyValue, 'interface', _('Interface'));
|
||||
o = s.option(form.DummyValue, 'ip', _('IP address'));
|
||||
o = s.option(form.DummyValue, 'package', _('Package'));
|
||||
o = s.option(form.DummyValue, 'updated', _('Renewal Date'));
|
||||
o = s.option(form.DummyValue, 'uptime', _('Up Time'));
|
||||
|
||||
var btn = s.option(form.Button, 'kill', _('Forced Offline'));
|
||||
btn.inputstyle = 'reset';
|
||||
btn.onclick = function(ev, section_id) {
|
||||
var row = tableData.find(function(r) { return r.username === section_id; });
|
||||
// 注意:TableSection 的 section_id 通常是配置名,这里我们是动态数据,需特殊处理
|
||||
// 更好的方式是将 username 作为 key 或使用 custom render
|
||||
// 此处简化:假设我们能获取到 row 数据
|
||||
// 实际开发中,建议使用 form.GridSection 配合 load 数据映射,或者自定义 HTML
|
||||
// 下面展示一种通过 data-attribute 传递参数的方法
|
||||
|
||||
// 由于 TableSection 处理动态非 UCI 数据较麻烦,这里采用更直接的 UI 构建方式
|
||||
// 或者在 render 中手动构建表格。
|
||||
// 为了保持代码简洁,这里演示核心逻辑:
|
||||
|
||||
var targetRow = this.map.findElement('id', 'cbi.pppoe-user.sessions.' + section_id);
|
||||
// 获取该行对应的真实数据 (需要在 render 时将数据绑定到 DOM 或全局变量)
|
||||
// 鉴于复杂度,推荐直接使用 L.ui.showModal 或 自定义 renderContent
|
||||
};
|
||||
|
||||
// 【更优解】:对于非 UCI 的动态列表,直接返回 HTML 往往比 form.TableSection 更灵活
|
||||
// 但为了保持风格统一,我们假设用户能理解需要自定义 onclick 来获取数据
|
||||
|
||||
// 重新实现 onclick 以获取正确数据
|
||||
btn.onclick = L.bind(function(ev, section_id) {
|
||||
// 在 load 阶段我们将数据存到了 this.sessionsData
|
||||
var session = this.sessionsData.find(s => s.username === section_id);
|
||||
if (!session) return;
|
||||
|
||||
return ui.showModal(_('Confirm'), [
|
||||
E('p', _('Are you sure you want to force offline user "%s"?').format(session.username)),
|
||||
E('div', { class: 'right' }, [
|
||||
E('button', {
|
||||
class: 'btn',
|
||||
click: ui.hideModal
|
||||
}, [ _('Cancel') ]),
|
||||
' ',
|
||||
E('button', {
|
||||
class: 'btn cbi-button-action',
|
||||
click: ui.handler(L.bind(function() {
|
||||
return ubus.call('pppoe.user', 'kill', {
|
||||
session_file: session.session_file,
|
||||
pid: session.pid
|
||||
}).then(() => {
|
||||
ui.addNotification(null, E('p', _('User killed successfully')));
|
||||
this.load().then(d => this.render(d)); // 刷新
|
||||
}).catch(e => {
|
||||
ui.addNotification(null, E('p', _('Error: ') + e.message));
|
||||
});
|
||||
}, this))
|
||||
}, [ _('OK') ])
|
||||
])
|
||||
]);
|
||||
}, this);
|
||||
|
||||
// 需要将数据挂载到 this 以便 onclick 访问
|
||||
this.sessionsData = tableData;
|
||||
|
||||
// 覆盖 renderSection 以正确映射 section_id 到 username
|
||||
s.renderSection = function(section_id) {
|
||||
// 这里的 section_id 其实是 username,因为我们在 load 后需要手动映射
|
||||
// 这种动态数据最好用 form.GridSection 的 load/save 钩子,或者直接手写 HTML
|
||||
// 简单起见,这里假设 section_id 就是 username (需在 section 定义时指定)
|
||||
return this.super('renderSection', section_id);
|
||||
};
|
||||
|
||||
// 修正:TableSection 默认基于 UCI config。对于纯动态数据,
|
||||
// 建议重写 render 函数直接生成 HTML 表格,不使用 form.Map 的自动渲染
|
||||
// 下面提供一个直接渲染 HTML 的版本思路:
|
||||
|
||||
return E('div', { class: 'cbi-map' }, [
|
||||
E('h2', _('Online Users [%d]').format(data.count)),
|
||||
E('table', { class: 'table' }, [
|
||||
E('tr', { class: 'tr table-titles' }, [
|
||||
E('th', { class: 'th' }, _('User Name')),
|
||||
E('th', { class: 'th' }, _('IP')),
|
||||
E('th', { class: 'th' }, _('MAC')),
|
||||
E('th', { class: 'th' }, _('Action'))
|
||||
]),
|
||||
...tableData.map(user => E('tr', { class: 'tr cbi-rowstyle-' + ((Math.random() > 0.5) ? '1' : '2') }, [
|
||||
E('td', { class: 'td' }, user.username),
|
||||
E('td', { class: 'td' }, user.ip),
|
||||
E('td', { class: 'td' }, user.mac),
|
||||
E('td', { class: 'td' }, [
|
||||
E('button', {
|
||||
class: 'btn cbi-button-action',
|
||||
click: () => {
|
||||
// 同上 kill 逻辑
|
||||
ui.showModal(_('Confirm'), [
|
||||
E('p', _('Kill %s?').format(user.username)),
|
||||
E('div', { class: 'right' }, [
|
||||
E('button', { class: 'btn', click: ui.hideModal }, _('Cancel')),
|
||||
' ',
|
||||
E('button', {
|
||||
class: 'btn cbi-button-negative',
|
||||
click: () => {
|
||||
ubus.call('pppoe.user', 'kill', { session_file: user.session_file, pid: user.pid })
|
||||
.then(() => location.reload())
|
||||
.catch(e => alert(e));
|
||||
}
|
||||
}, _('Kill'))
|
||||
])
|
||||
]);
|
||||
}
|
||||
}, _('Forced Offline'))
|
||||
])
|
||||
]))
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
@ -1,109 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require ui';
|
||||
|
||||
return view.extend({
|
||||
render: function () {
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('pppoe-user', _(''), _(''));
|
||||
|
||||
// User section
|
||||
s = m.section(form.GridSection, 'user', _('ONU List'));
|
||||
s.anonymous = true;
|
||||
s.nodescriptions = true
|
||||
|
||||
// Username field
|
||||
o = s.option(form.Value, 'username', _('Account'));
|
||||
o.readonly = true;
|
||||
|
||||
// Service Name field (dropdown)
|
||||
o = s.option(form.Value, 'servicename', _('Service Name'));
|
||||
o.modalonly = true;
|
||||
o.readonly = true;
|
||||
|
||||
// Password field
|
||||
o = s.option(form.Value, 'password', _('Password'));
|
||||
o.password = true;
|
||||
o.modalonly = true;
|
||||
o.readonly = true;
|
||||
|
||||
// MAC Address field (dropdown)
|
||||
o = s.option(form.Value, 'macaddr', _('MAC Address'));
|
||||
o.password = true;
|
||||
o.modalonly = true;
|
||||
o.readonly = true;
|
||||
|
||||
// Package field (dropdown)
|
||||
o = s.option(form.Value, 'package', _('Package'));
|
||||
o.readonly = true;
|
||||
|
||||
// Upload Speed field (dropdown)
|
||||
var o = s.option(form.Value, 'urate', _('Upload Speed'));
|
||||
o.modalonly = true;
|
||||
o.readonly = true;
|
||||
|
||||
// Download Speed field (dropdown)
|
||||
var o = s.option(form.Value, 'drate', _('Download Speed'));
|
||||
o.modalonly = true;
|
||||
o.readonly = true;
|
||||
|
||||
// Concurrent Connection Number Segment (dropdown menu)
|
||||
var o = s.option(form.Value, 'connect', _('Connection Number'));
|
||||
o.modalonly = true;
|
||||
o.readonly = true;
|
||||
|
||||
// OLT field (dropdown)
|
||||
var o = s.option(form.Value, 'olt', _('OLT'));
|
||||
o.value('1', 'OLT 1');
|
||||
o.value('2', 'OLT 2');
|
||||
o.value('3', 'OLT 3');
|
||||
o.value('4', 'OLT 4');
|
||||
o.value('5', 'OLT 5');
|
||||
o.value('6', 'OLT 6');
|
||||
o.value('7', 'OLT 7');
|
||||
o.value('8', 'OLT 8');
|
||||
o.value('9', 'OLT 9');
|
||||
o.value('10', 'OLT 10');
|
||||
o.value('11', 'OLT 11');
|
||||
o.value('12', 'OLT 12');
|
||||
o.value('13', 'OLT 13');
|
||||
o.value('14', 'OLT 14');
|
||||
o.value('15', 'OLT 15');
|
||||
o.value('16', 'OLT 16');
|
||||
o.value('17', 'OLT 17');
|
||||
o.value('18', 'OLT 18');
|
||||
o.value('19', 'OLT 19');
|
||||
o.value('20', 'OLT 20');
|
||||
|
||||
// PON field (dropdown)
|
||||
var o = s.option(form.Value, 'pon', _('PON'));
|
||||
o.value('1', 'PON 1');
|
||||
o.value('2', 'PON 2');
|
||||
o.value('3', 'PON 3');
|
||||
o.value('4', 'PON 4');
|
||||
o.value('5', 'PON 5');
|
||||
o.value('6', 'PON 6');
|
||||
o.value('7', 'PON 7');
|
||||
o.value('8', 'PON 8');
|
||||
o.value('9', 'PON 9');
|
||||
o.value('10', 'PON 10');
|
||||
o.value('11', 'PON 11');
|
||||
o.value('12', 'PON 12');
|
||||
o.value('13', 'PON 13');
|
||||
o.value('14', 'PON 14');
|
||||
o.value('15', 'PON 15');
|
||||
o.value('16', 'PON 16');
|
||||
|
||||
// Serial Number field (dropdown)
|
||||
o = s.option(form.Value, 'sn', _('S/N'));
|
||||
o.placeholder = 'ONT Serial Number';
|
||||
|
||||
// Submit button
|
||||
return m.render();
|
||||
},
|
||||
handleSave: null,
|
||||
handleSaveApply: null,
|
||||
handleReset: null
|
||||
});
|
||||
@ -1,466 +0,0 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require ui';
|
||||
'require uci';
|
||||
|
||||
return view.extend({
|
||||
render: function () {
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('pppoe-user', _(''), _(''));
|
||||
|
||||
// User section
|
||||
s = m.section(form.GridSection, 'user', _('User List'));
|
||||
s.anonymous = true;
|
||||
s.addremove = true;
|
||||
s.editable = false;
|
||||
s.nodescriptions = true;
|
||||
s.sortable = false;
|
||||
|
||||
// Enable User field
|
||||
o = s.option(form.Flag, 'enabled', _('Enable'));
|
||||
o.rmempty = false; // Make the field required
|
||||
o.default = "1";
|
||||
|
||||
// Username field
|
||||
o = s.option(form.Value, 'username', _('Account'));
|
||||
o.rmempty = false; // Make the field required
|
||||
o.placeholder = _('Required');
|
||||
o.validate = function (section_id, value) {
|
||||
// Validate the input value to match the alphanumeric format
|
||||
if (!/^[a-zA-Z0-9]+$/.test(value)) {
|
||||
return _('Invalid username format. Only alphanumeric characters are allowed.');
|
||||
}
|
||||
|
||||
// Check the length of the username
|
||||
if (value.length < 4 || value.length > 16) {
|
||||
return _('Username length must be between 4 and 16 characters.');
|
||||
}
|
||||
|
||||
// Check for duplicate usernames
|
||||
var otherSections = uci.sections('pppoe-user', 'user');
|
||||
for (var i = 0; i < otherSections.length; i++) {
|
||||
if (section_id !== otherSections[i]['.name'] && otherSections[i]['username'] === value) {
|
||||
return _('This username is already in use. Please choose a different one.');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Service Name field (dropdown)
|
||||
o = s.option(form.ListValue, 'servicename', _('Service Name'));
|
||||
o.readonly = true;
|
||||
o.modalonly = true;
|
||||
o.value('*', _('Default'));
|
||||
o.value('serviceA', 'Service A');
|
||||
o.value('serviceB', 'Service B');
|
||||
o.value('serviceC', 'Service C');
|
||||
|
||||
// Password field
|
||||
o = s.option(form.Value, 'password', _('Password'));
|
||||
o.placeholder = _('Required');
|
||||
o.password = true;
|
||||
o.modalonly = true;
|
||||
o.validate = function (section_id, value) {
|
||||
// Validate the input value to ensure it's between 8 and 16 characters
|
||||
if (value.length < 8 || value.length > 16) {
|
||||
return _('Password must be between 8 and 16 characters.');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
var currentDate = new Date();
|
||||
var year = currentDate.getFullYear();
|
||||
var month = ('0' + (currentDate.getMonth() + 1)).slice(-2);
|
||||
var day = ('0' + currentDate.getDate()).slice(-2);
|
||||
var formattedDate = year + month + day;
|
||||
|
||||
o.default = formattedDate.slice(0, 8);
|
||||
o.rmempty = false; // Make the field required
|
||||
|
||||
var currentDate = new Date();
|
||||
var year = currentDate.getFullYear();
|
||||
var month = ('0' + (currentDate.getMonth() + 1)).slice(-2);
|
||||
var day = ('0' + currentDate.getDate()).slice(-2);
|
||||
var formattedDate = year + month + day;
|
||||
|
||||
o.default = formattedDate.slice(0, 8);
|
||||
o.rmempty = false; // Make the field required
|
||||
|
||||
// IP Address field (custom input with default value *)
|
||||
o = s.option(form.Value, 'ipaddr', _('IP Address'));
|
||||
o.placeholder = _('Required');
|
||||
o.default = '*';
|
||||
o.value('172.31.16.0/23', '172.31.16.0/23 Rules WAN1');
|
||||
o.value('172.31.26.0/23', '172.31.26.0/23 Rules WAN2');
|
||||
o.value('172.31.36.0/23', '172.31.36.0/23 Rules WAN3');
|
||||
o.value('172.31.46.0/23', '172.31.46.0/23 Rules WAN4');
|
||||
o.value('172.31.56.0/23', '172.31.56.0/23 Rules WAN5');
|
||||
o.value('172.31.66.0/23', '172.31.66.0/23 Rules WAN6');
|
||||
o.value('172.31.76.0/23', '172.31.76.0/23 Rules WAN7');
|
||||
o.value('172.31.86.0/23', '172.31.86.0/23 Rules WAN8');
|
||||
o.value('172.31.96.0/23', '172.31.96.0/23 Rules WAN9');
|
||||
o.value('*', _('Default'));
|
||||
o.value('172.31.100.0/23', '172.31.100.0/23 Rules MAN0');
|
||||
o.value('172.31.110.0/23', '172.31.110.0/23 Rules MAN1');
|
||||
o.value('172.31.120.0/23', '172.31.120.0/23 Rules MAN2');
|
||||
o.value('172.31.130.0/23', '172.31.130.0/23 Rules MAN3');
|
||||
o.value('172.31.140.0/23', '172.31.140.0/23 Rules MAN4');
|
||||
o.value('172.31.150.0/23', '172.31.150.0/23 Rules MAN5');
|
||||
o.value('172.31.160.0/23', '172.31.160.0/23 Rules MAN6');
|
||||
o.value('172.31.170.0/23', '172.31.170.0/23 Rules MAN7');
|
||||
o.value('172.31.180.0/23', '172.31.180.0/23 Rules MAN8');
|
||||
o.value('172.31.190.0/23', '172.31.190.0/23 Rules MAN9');
|
||||
|
||||
// Enhanced validation for IP addresses and network segments
|
||||
o.validate = function (section_id, value) {
|
||||
// Regular expressions for valid IPv4, IPv6, and network segments
|
||||
var ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||
var ipv6Regex = /^[\da-fA-F]{1,4}(:[\da-fA-F]{1,4}){0,7}$/;
|
||||
var networkSegmentRegex = /^(\d{1,3}\.){3}\d{1,3}\/(\d{1,2})$/;
|
||||
|
||||
if (value === '*') {
|
||||
return true; // Asterisk is allowed
|
||||
}
|
||||
|
||||
if (ipv4Regex.test(value)) {
|
||||
// Validate IPv4 address
|
||||
var parts = value.split('.');
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var part = parseInt(parts[i]);
|
||||
if (part < 0 || part > 255) {
|
||||
return _('Invalid IPv4 address. Each segment must be between 0 and 255.');
|
||||
}
|
||||
}
|
||||
// Check for duplicate IP addresses
|
||||
var otherSections = uci.sections('pppoe-user', 'user');
|
||||
for (var i = 0; i < otherSections.length; i++) {
|
||||
if (section_id !== otherSections[i]['.name'] && otherSections[i]['ipaddr'] === value) {
|
||||
return _('This IP address is already in use by another user. Please choose a different one.');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ipv6Regex.test(value)) {
|
||||
// Validate IPv6 address
|
||||
var otherSections = uci.sections('pppoe-user', 'user');
|
||||
for (var i = 0; i < otherSections.length; i++) {
|
||||
if (section_id !== otherSections[i]['.name'] && otherSections[i]['ipaddr'] === value) {
|
||||
return _('This IP address is already in use by another user. Please choose a different one.');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (networkSegmentRegex.test(value)) {
|
||||
// Validate IP network segment
|
||||
var subnetParts = value.split('/');
|
||||
var subnetSize = parseInt(subnetParts[1]);
|
||||
|
||||
if (subnetSize < 0 || subnetSize > 32) {
|
||||
return _('Invalid subnet size. It must be between 0 and 32.');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Invalid input, return an error message
|
||||
return _('Invalid input. Please use "*" or a valid IPv4, IPv6 address, or IP network segment.');
|
||||
};
|
||||
|
||||
// Automatically generate an available IP address when a network segment is selected
|
||||
o.cfgvalue = function (section_id) {
|
||||
var selectedSegment = uci.get('pppoe-user', section_id, 'ipaddr');
|
||||
|
||||
// Add your new predefined network segment here
|
||||
if (selectedSegment === '172.31.16.0/23' ||
|
||||
selectedSegment === '172.31.26.0/23' ||
|
||||
selectedSegment === '172.31.36.0/23' ||
|
||||
selectedSegment === '172.31.46.0/23' ||
|
||||
selectedSegment === '172.31.56.0/23' ||
|
||||
selectedSegment === '172.31.66.0/23' ||
|
||||
selectedSegment === '172.31.76.0/23' ||
|
||||
selectedSegment === '172.31.86.0/23' ||
|
||||
selectedSegment === '172.31.96.0/23' ||
|
||||
selectedSegment === '172.31.100.0/23' ||
|
||||
selectedSegment === '172.31.110.0/23' ||
|
||||
selectedSegment === '172.31.120.0/23' ||
|
||||
selectedSegment === '172.31.130.0/23' ||
|
||||
selectedSegment === '172.31.140.0/23' ||
|
||||
selectedSegment === '172.31.150.0/23' ||
|
||||
selectedSegment === '172.31.160.0/23' ||
|
||||
selectedSegment === '172.31.170.0/23' ||
|
||||
selectedSegment === '172.31.180.0/23' ||
|
||||
selectedSegment === '172.31.190.0/23') {
|
||||
// Generate and assign an available IP address based on the selected network segment
|
||||
var generatedIP = getAvailableIPAddress(selectedSegment);
|
||||
|
||||
// Check if the generated IP address already exists in other sections
|
||||
var otherSections = uci.sections('pppoe-user', 'user');
|
||||
for (var i = 0; i < otherSections.length; i++) {
|
||||
if (section_id !== otherSections[i]['.name'] && otherSections[i]['ipaddr'] === generatedIP) {
|
||||
generatedIP = getAvailableIPAddress(selectedSegment); // Regenerate if it's a duplicate
|
||||
}
|
||||
}
|
||||
|
||||
uci.set('pppoe-user', section_id, 'ipaddr', generatedIP);
|
||||
uci.save();
|
||||
return generatedIP;
|
||||
}
|
||||
return selectedSegment;
|
||||
};
|
||||
|
||||
// Define a function to generate an available IP address based on the selected network segment
|
||||
function getAvailableIPAddress(networkSegment) {
|
||||
// Basic logic to generate an available IP address within the network segment
|
||||
var segments = networkSegment.split('/');
|
||||
var baseIP = segments[0];
|
||||
var subnetSize = parseInt(segments[1]);
|
||||
|
||||
// Generate a random host number within the subnet range (exclude network and broadcast)
|
||||
var hostNumber = Math.floor(Math.random() * (Math.pow(2, 32 - subnetSize) - 2)) + 1;
|
||||
|
||||
// Convert the host number to an IP address
|
||||
var generatedIP = baseIP.split('.').map(function (part, index) {
|
||||
return parseInt(part) + (hostNumber >> (8 * (3 - index)) & 255);
|
||||
}).join('.');
|
||||
|
||||
return generatedIP;
|
||||
};
|
||||
|
||||
// MAC Address field (dropdown)
|
||||
o = s.option(form.Value, 'macaddr', _('MAC Address'));
|
||||
o.modalonly = true;
|
||||
o.placeholder = 'MAC Address';
|
||||
o.password = true;
|
||||
o.readonly = true;
|
||||
|
||||
// Package field (dropdown)
|
||||
o = s.option(form.ListValue, 'package', _('Package'));
|
||||
o.default = 'home';
|
||||
o.value('home', _('Home'));
|
||||
o.value('office', _('Office'));
|
||||
o.value('free', _('Free'));
|
||||
o.value('test', _('Test'));
|
||||
|
||||
// Enable QoS field (checkbox)
|
||||
o = s.option(form.Flag, 'qos', _('QoS'));
|
||||
o.modalonly = true;
|
||||
o.default = '0';
|
||||
o.readonly = false;
|
||||
o.rmempty = false;
|
||||
|
||||
// Upload Speed field (dropdown)
|
||||
var o = s.option(form.Value, 'urate', _('Upload Speed'));
|
||||
o.modalonly = true;
|
||||
o.placeholder = "1 to 10000 Mbps"
|
||||
o.datatype = "range(1,10000)"
|
||||
o.default = '15';
|
||||
o.depends('qos', '1'); // Show only when QoS is enabled
|
||||
|
||||
// Download Speed field (dropdown)
|
||||
var o = s.option(form.Value, 'drate', _('Download Speed'));
|
||||
o.modalonly = true;
|
||||
o.placeholder = "1 to 10000 Mbps"
|
||||
o.datatype = "range(1,10000)"
|
||||
o.default = '30';
|
||||
o.depends('qos', '1'); // Show only when QoS is enabled
|
||||
|
||||
// Concurrent Connection Number Segment (dropdown menu)
|
||||
var o = s.option(form.ListValue, 'connect', _('Connection Number'));
|
||||
o.modalonly = true;
|
||||
o.datatype = "range(128,1048576)"
|
||||
o.default = '2048';
|
||||
o.value('128');
|
||||
o.value('256');
|
||||
o.value('512');
|
||||
o.value('1024');
|
||||
o.value('2048');
|
||||
o.value('4096');
|
||||
o.value('8192');
|
||||
o.value('16384');
|
||||
o.value('32768');
|
||||
o.value('65536');
|
||||
o.value('131072');
|
||||
o.value('262144');
|
||||
o.value('524288');
|
||||
o.value('1048576');
|
||||
o.depends('qos', '1'); // Show only when QoS is enabled
|
||||
|
||||
// Opening Date field (read-only)
|
||||
o = s.option(form.Value, 'opening', _('Opening Date'));
|
||||
o.readonly = true;
|
||||
// Automatically populate with the current date in 'YYYY-MM-DD' format
|
||||
var currentDate = new Date();
|
||||
var year = currentDate.getFullYear();
|
||||
var month = ('0' + (currentDate.getMonth() + 1)).slice(-2);
|
||||
var day = ('0' + currentDate.getDate()).slice(-2);
|
||||
var formattedDate = year + '-' + month + '-' + day;
|
||||
o.default = formattedDate;
|
||||
|
||||
// Expiration Date field
|
||||
o = s.option(form.Value, 'expires', _('Expiration Date'));
|
||||
o.datatype = 'string';
|
||||
o.placeholder = 'YYYY-MM-DD';
|
||||
o.validate = function (section_id, value) {
|
||||
// Validate the input value to match the format YYYY-MM-DD
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
||||
return _('Invalid date format. Please use YYYY-MM-DD.');
|
||||
}
|
||||
|
||||
// Split the date string into year, month, and day components
|
||||
var parts = value.split('-');
|
||||
var year = parseInt(parts[0], 10);
|
||||
var month = parseInt(parts[1], 10);
|
||||
var day = parseInt(parts[2], 10);
|
||||
|
||||
// Validate the year, month, and day values
|
||||
if (isNaN(year) || isNaN(month) || isNaN(day)) {
|
||||
return _('Invalid date values.');
|
||||
}
|
||||
|
||||
if (month < 1 || month > 12) {
|
||||
return _('Invalid month. Please use a value between 01 and 12.');
|
||||
}
|
||||
|
||||
var daysInMonth = new Date(year, month, 0).getDate();
|
||||
if (day < 1 || day > daysInMonth) {
|
||||
return _('Invalid day. Please use a value between 01 and ') + daysInMonth + '.';
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
var currentDate = new Date();
|
||||
|
||||
// Calculate expiration dates based on the current day + 1/3/6/12 months
|
||||
var oneMonthLater = new Date(currentDate);
|
||||
oneMonthLater.setMonth(currentDate.getMonth() + 1);
|
||||
var threeMonthsLater = new Date(currentDate);
|
||||
threeMonthsLater.setMonth(currentDate.getMonth() + 3);
|
||||
var sixMonthsLater = new Date(currentDate);
|
||||
sixMonthsLater.setMonth(currentDate.getMonth() + 6);
|
||||
var twelveMonthsLater = new Date(currentDate);
|
||||
twelveMonthsLater.setMonth(currentDate.getMonth() + 12);
|
||||
|
||||
// Convert calculated dates to YYYY-MM-DD format
|
||||
function formatDate(date) {
|
||||
var year = date.getFullYear();
|
||||
var month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
var day = date.getDate().toString().padStart(2, '0');
|
||||
return year + '-' + month + '-' + day;
|
||||
}
|
||||
// Set the default value to three months later
|
||||
o.default = formatDate(threeMonthsLater);
|
||||
o.value(formatDate(oneMonthLater), formatDate(oneMonthLater) + ' ' + _('One Month'));
|
||||
o.value(formatDate(threeMonthsLater), formatDate(threeMonthsLater) + ' ' + _('Three Months'));
|
||||
o.value(formatDate(sixMonthsLater), formatDate(sixMonthsLater) + ' ' + _('Six Months'));
|
||||
o.value(formatDate(twelveMonthsLater), formatDate(twelveMonthsLater) + ' ' + _('Twelve Months'));
|
||||
|
||||
// Enable Connect field (checkbox)
|
||||
o = s.option(form.Flag, 'more', _('Contact'));
|
||||
o.rmempty = false; // Make the field required
|
||||
o.modalonly = true;
|
||||
o.default = '0';
|
||||
|
||||
// Address field (dropdown)
|
||||
o = s.option(form.Value, 'address', _('Address'));
|
||||
o.modalonly = true;
|
||||
o.placeholder = 'User Address';
|
||||
o.depends('more', '1');
|
||||
|
||||
// E-mail field (dropdown)
|
||||
o = s.option(form.DynamicList, 'mail', _('E-mail'));
|
||||
o.modalonly = true;
|
||||
o.placeholder = 'User Email';
|
||||
o.depends('more', '1');
|
||||
|
||||
// Phone field (dropdown)
|
||||
o = s.option(form.DynamicList, 'phone', _('Telephone'));
|
||||
o.modalonly = true;
|
||||
o.placeholder = 'User Phone';
|
||||
o.depends('more', '1');
|
||||
|
||||
// GPS Location field (dropdown)
|
||||
o = s.option(form.Value, 'gps', _('GPS'));
|
||||
o.placeholder = 'ONT GPS Location';
|
||||
o.modalonly = true;
|
||||
o.depends('more', '1');
|
||||
|
||||
o = s.option(form.TextValue, 'notes', _('Notes'), _(''));
|
||||
o.modalonly = true;
|
||||
o.optional = true;
|
||||
o.depends('more', '1');
|
||||
|
||||
// Agent field (dropdown)
|
||||
o = s.option(form.Value, 'agent', _('Agent'));
|
||||
o.modalonly = true;
|
||||
o.readonly = true;
|
||||
|
||||
// Enable ONT field (checkbox)
|
||||
o = s.option(form.Flag, 'ont', _('ONT'));
|
||||
o.rmempty = false; // Make the field required
|
||||
o.modalonly = true;
|
||||
o.default = '0';
|
||||
|
||||
// OLT field (dropdown)
|
||||
var o = s.option(form.Value, 'olt', _('OLT'));
|
||||
o.modalonly = true;
|
||||
o.value('1', 'OLT 1');
|
||||
o.value('2', 'OLT 2');
|
||||
o.value('3', 'OLT 3');
|
||||
o.value('4', 'OLT 4');
|
||||
o.value('5', 'OLT 5');
|
||||
o.value('6', 'OLT 6');
|
||||
o.value('7', 'OLT 7');
|
||||
o.value('8', 'OLT 8');
|
||||
o.value('9', 'OLT 9');
|
||||
o.value('10', 'OLT 10');
|
||||
o.value('11', 'OLT 11');
|
||||
o.value('12', 'OLT 12');
|
||||
o.value('13', 'OLT 13');
|
||||
o.value('14', 'OLT 14');
|
||||
o.value('15', 'OLT 15');
|
||||
o.value('16', 'OLT 16');
|
||||
o.value('17', 'OLT 17');
|
||||
o.value('18', 'OLT 18');
|
||||
o.value('19', 'OLT 19');
|
||||
o.value('20', 'OLT 20');
|
||||
o.depends('ont', '1');
|
||||
|
||||
// PON field (dropdown)
|
||||
var o = s.option(form.Value, 'pon', _('PON'));
|
||||
o.modalonly = true;
|
||||
o.value('1', 'PON 1');
|
||||
o.value('2', 'PON 2');
|
||||
o.value('3', 'PON 3');
|
||||
o.value('4', 'PON 4');
|
||||
o.value('5', 'PON 5');
|
||||
o.value('6', 'PON 6');
|
||||
o.value('7', 'PON 7');
|
||||
o.value('8', 'PON 8');
|
||||
o.value('9', 'PON 9');
|
||||
o.value('10', 'PON 10');
|
||||
o.value('11', 'PON 11');
|
||||
o.value('12', 'PON 12');
|
||||
o.value('13', 'PON 13');
|
||||
o.value('14', 'PON 14');
|
||||
o.value('15', 'PON 15');
|
||||
o.value('16', 'PON 16');
|
||||
o.depends('ont', '1');
|
||||
|
||||
// Serial Number field (dropdown)
|
||||
o = s.option(form.Value, 'sn', _('S/N'));
|
||||
o.modalonly = true;
|
||||
o.placeholder = 'ONT Serial Number';
|
||||
o.depends('ont', '1');
|
||||
|
||||
// Submit button
|
||||
return m.render();
|
||||
},
|
||||
handleSave: null,
|
||||
handleSaveApply: null,
|
||||
handleReset: null
|
||||
});
|
||||
@ -1,170 +0,0 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||
|
||||
msgid "Management"
|
||||
msgstr ""
|
||||
|
||||
msgid "User Manager"
|
||||
msgstr ""
|
||||
|
||||
msgid "New Users"
|
||||
msgstr ""
|
||||
|
||||
msgid "User List"
|
||||
msgstr ""
|
||||
|
||||
msgid "ONU List"
|
||||
msgstr ""
|
||||
|
||||
msgid "User Number"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "User Name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Account"
|
||||
msgstr ""
|
||||
|
||||
msgid "This username is already in use. Please choose a different one."
|
||||
msgstr ""
|
||||
|
||||
msgid "Service Name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Password must be between 8 and 16 characters."
|
||||
msgstr ""
|
||||
|
||||
msgid "IP Address"
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid IPv4 address. Each segment must be between 0 and 255."
|
||||
msgstr ""
|
||||
|
||||
msgid "This IP address is already in use by another user. Please choose a different one."
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid subnet size. It must be between 0 and 32."
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid input. Please use "*" or a valid IPv4, IPv6 address, or IP network segment."
|
||||
msgstr ""
|
||||
|
||||
msgid "Package"
|
||||
msgstr ""
|
||||
|
||||
msgid "Speed"
|
||||
msgstr ""
|
||||
|
||||
msgid "QoS"
|
||||
msgstr ""
|
||||
|
||||
msgid "Upload Speed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Download Speed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Speed Unit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Connection Number"
|
||||
msgstr ""
|
||||
|
||||
msgid "Queuing Rules"
|
||||
msgstr ""
|
||||
|
||||
msgid "Queue Script"
|
||||
msgstr ""
|
||||
|
||||
msgid "Link Layer"
|
||||
msgstr ""
|
||||
|
||||
msgid "Overhead Bytes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Debug Logging"
|
||||
msgstr ""
|
||||
|
||||
msgid "Opening Date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Validity Period"
|
||||
msgstr ""
|
||||
|
||||
msgid "Expiration Date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Invalid date format. Please use YYYY-MM-DD."
|
||||
msgstr ""
|
||||
|
||||
msgid "Agent"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
msgid "Office"
|
||||
msgstr ""
|
||||
|
||||
msgid "Free"
|
||||
msgstr ""
|
||||
|
||||
msgid "Test"
|
||||
msgstr ""
|
||||
|
||||
msgid "One Month"
|
||||
msgstr ""
|
||||
|
||||
msgid "Three Months"
|
||||
msgstr ""
|
||||
|
||||
msgid "Six Months"
|
||||
msgstr ""
|
||||
|
||||
msgid "Twelve Months"
|
||||
msgstr ""
|
||||
|
||||
msgid "ONU Manager"
|
||||
msgstr ""
|
||||
|
||||
msgid "ONT"
|
||||
msgstr ""
|
||||
|
||||
msgid "ONT Serial Number"
|
||||
msgstr ""
|
||||
|
||||
msgid "Contact"
|
||||
msgstr ""
|
||||
|
||||
msgid "E-mail"
|
||||
msgstr ""
|
||||
|
||||
msgid "Telephone"
|
||||
msgstr ""
|
||||
|
||||
msgid "User Status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Online User"
|
||||
msgstr ""
|
||||
|
||||
msgid "Up Time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Renewal Date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Process ID"
|
||||
msgstr ""
|
||||
|
||||
msgid "Forced Offline"
|
||||
msgstr ""
|
||||
@ -1 +0,0 @@
|
||||
zh_Hans
|
||||
@ -1,167 +0,0 @@
|
||||
msgid "Management"
|
||||
msgstr "管理"
|
||||
|
||||
msgid "User Manager"
|
||||
msgstr "用户管理"
|
||||
|
||||
msgid "New Users"
|
||||
msgstr "新增用户"
|
||||
|
||||
msgid "User List"
|
||||
msgstr "用户列表"
|
||||
|
||||
msgid "ONU List"
|
||||
msgstr "光猫列表"
|
||||
|
||||
msgid "User Number"
|
||||
msgstr "用户数"
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr "启用"
|
||||
|
||||
msgid "User Name"
|
||||
msgstr "用户名"
|
||||
|
||||
msgid "Account"
|
||||
msgstr "账号"
|
||||
|
||||
msgid "This username is already in use. Please choose a different one."
|
||||
msgstr "该用户名已被使用。 请选择一个不同的。"
|
||||
|
||||
msgid "Service Name"
|
||||
msgstr "服务名称"
|
||||
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
msgid "Password must be between 8 and 16 characters."
|
||||
msgstr "密码必须介于 8 到 16 个字符之间。"
|
||||
|
||||
msgid "IP Address"
|
||||
msgstr "IP 地址"
|
||||
|
||||
msgid "Invalid IPv4 address. Each segment must be between 0 and 255."
|
||||
msgstr "IPv4 地址无效。 每个段必须介于 0 到 255 之间。"
|
||||
|
||||
msgid "This IP address is already in use by another user. Please choose a different one."
|
||||
msgstr "该 IP 地址已被其他用户使用。 请选择一个不同的。"
|
||||
|
||||
msgid "Invalid subnet size. It must be between 0 and 32."
|
||||
msgstr "子网大小无效。 它必须介于 0 和 32 之间。"
|
||||
|
||||
msgid "Invalid input. Please use "*" or a valid IPv4, IPv6 address, or IP network segment."
|
||||
msgstr "输入无效。 请使用“*”或有效的 IPv4、IPv6 地址或 IP 网段。"
|
||||
|
||||
msgid "Package"
|
||||
msgstr "套餐"
|
||||
|
||||
msgid "Speed"
|
||||
msgstr "网速"
|
||||
|
||||
msgid "QoS"
|
||||
msgstr "限速"
|
||||
|
||||
msgid "Upload Speed"
|
||||
msgstr "上传速度"
|
||||
|
||||
msgid "Download Speed"
|
||||
msgstr "下载速度"
|
||||
|
||||
msgid "Speed Unit"
|
||||
msgstr "速度单位"
|
||||
|
||||
msgid "Connection Number"
|
||||
msgstr "连接数"
|
||||
|
||||
msgid "Queuing Rules"
|
||||
msgstr "队列规则"
|
||||
|
||||
msgid "Queue Script"
|
||||
msgstr "队列脚本"
|
||||
|
||||
msgid "Link Layer"
|
||||
msgstr "链路层"
|
||||
|
||||
msgid "Overhead Bytes"
|
||||
msgstr "开销字节"
|
||||
|
||||
msgid "Debug Logging"
|
||||
msgstr "调试日志"
|
||||
|
||||
msgid "Opening Date"
|
||||
msgstr "开通日期"
|
||||
|
||||
msgid "Validity Period"
|
||||
msgstr "有效期"
|
||||
|
||||
msgid "Expiration Date"
|
||||
msgstr "截止日期"
|
||||
|
||||
msgid "Invalid date format. Please use YYYY-MM-DD."
|
||||
msgstr "日期格式无效。 请使用 YYYY-MM-DD。"
|
||||
|
||||
msgid "Agent"
|
||||
msgstr "代理人"
|
||||
|
||||
msgid "Default"
|
||||
msgstr "默认"
|
||||
|
||||
msgid "Home"
|
||||
msgstr "家"
|
||||
|
||||
msgid "Office"
|
||||
msgstr "办公"
|
||||
|
||||
msgid "Free"
|
||||
msgstr "免费"
|
||||
|
||||
msgid "Test"
|
||||
msgstr "测试"
|
||||
|
||||
msgid "One Month"
|
||||
msgstr "一个月"
|
||||
|
||||
msgid "Three Months"
|
||||
msgstr "三个月"
|
||||
|
||||
msgid "Six Months"
|
||||
msgstr "半年"
|
||||
|
||||
msgid "Twelve Months"
|
||||
msgstr "一年"
|
||||
|
||||
msgid "ONU Manager"
|
||||
msgstr "光猫管理"
|
||||
|
||||
msgid "ONT"
|
||||
msgstr "光猫绑定"
|
||||
|
||||
msgid "ONT Serial Number"
|
||||
msgstr "光猫序列号"
|
||||
|
||||
msgid "Contact"
|
||||
msgstr "联系方式"
|
||||
|
||||
msgid "E-mail"
|
||||
msgstr "电子邮箱"
|
||||
|
||||
msgid "Telephone"
|
||||
msgstr "电话"
|
||||
|
||||
msgid "User Status"
|
||||
msgstr "用户状态"
|
||||
|
||||
msgid "Online User"
|
||||
msgstr "在线用户"
|
||||
|
||||
msgid "Up Time"
|
||||
msgstr "上线时间"
|
||||
|
||||
msgid "Renewal Date"
|
||||
msgstr "续订日期"
|
||||
|
||||
msgid "Process ID"
|
||||
msgstr "进程 ID"
|
||||
|
||||
msgid "Forced Offline"
|
||||
msgstr "强制下线"
|
||||
@ -1,13 +0,0 @@
|
||||
|
||||
config user
|
||||
option enabled '1'
|
||||
option username 'guest'
|
||||
option servicename '*'
|
||||
option password '12345678'
|
||||
option ipaddr '*'
|
||||
option package 'home'
|
||||
option qos '1'
|
||||
option urate '15'
|
||||
option drate '30'
|
||||
option connect '1024'
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /lib/functions.sh
|
||||
|
||||
PPPOE_TIME="$(date "+%Y-%m-%d_%H:%M:%S")"
|
||||
GATEWAY="$(uci get network."$INTERFACE".gateway)"
|
||||
PING="$(ping "$GATEWAY" -I "$DEVICE" -c 5 | awk '{ print strftime("%Y-%m-%d %H:%M:%S",systime())"\t" $0; fflush() }' | grep -E 'rtt|round-trip' | cut -d "=" -f2)"
|
||||
|
||||
echo "$PPPOE_TIME Track Device=$DEVICE, Interface=$INTERFACE, Gateway=$GATEWAY, Ping=$PING, Action=$ACTION" >> /var/pppoe-user/log/interface.log
|
||||
@ -1,180 +0,0 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2024-2026 OpenWrt.org
|
||||
# Licensed under GPL v2
|
||||
|
||||
START=90
|
||||
STOP=10
|
||||
|
||||
USE_PROCD=1
|
||||
PROG=/usr/sbin/pppoe-server
|
||||
UBUS_HELPER=/usr/share/ucode/pppoe-ubus.uc
|
||||
UCODE_BIN=/usr/bin/ucode
|
||||
|
||||
SPEED_LIMIT_SWITCH=1
|
||||
CHAP_SECRETS=/etc/ppp/chap-secrets
|
||||
BACKUP_DIR=/var/pppoe-user/backup
|
||||
LOG_DIR=/var/pppoe-user/log
|
||||
SESSION_DIR=/var/etc/pppoe-user/session
|
||||
|
||||
# 初始化目录
|
||||
init_dirs() {
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
mkdir -p "$LOG_DIR"
|
||||
mkdir -p "$LOG_DIR/userinfo"
|
||||
mkdir -p "$SESSION_DIR"
|
||||
mkdir -p /var/lock
|
||||
}
|
||||
|
||||
# 使用 ucode 进行日期比较和状态检查 (更高效且不易出错)
|
||||
check_expiration_and_build_secrets() {
|
||||
local current_date
|
||||
current_date=$(date +"%Y-%m-%d")
|
||||
|
||||
# 清空并重建 chap-secrets
|
||||
: > "$CHAP_SECRETS"
|
||||
|
||||
# 遍历 UCI 配置
|
||||
config_load pppoe-user
|
||||
|
||||
config_foreach setup_user "$current_date"
|
||||
}
|
||||
|
||||
# 回调函数:处理每个用户配置
|
||||
# 参数 1: cfg_name, 参数 2: current_date
|
||||
setup_user() {
|
||||
local cfg="$1"
|
||||
local today="$2"
|
||||
local enabled username servicename password ipaddr expires
|
||||
|
||||
config_get_bool enabled "$cfg" enabled 0
|
||||
config_get username "$cfg" username
|
||||
config_get servicename "$cfg" servicename
|
||||
config_get password "$cfg" password
|
||||
config_get ipaddr "$cfg" ipaddr
|
||||
config_get expires "$cfg" expires
|
||||
|
||||
# 默认 servicename 为 *
|
||||
[ -z "$servicename" ] && servicename="*"
|
||||
|
||||
# 检查过期
|
||||
if [ "$enabled" -eq 1 ] && [ -n "$expires" ] && [ "$expires" = "$today" ]; then
|
||||
logger -t pppoe-user "User $username expired today ($today). Disabling."
|
||||
uci set pppoe-user."$cfg".enabled=0
|
||||
uci commit pppoe-user
|
||||
enabled=0
|
||||
fi
|
||||
|
||||
# 如果启用且未过期,写入 chap-secrets
|
||||
if [ "$enabled" -eq 1 ]; then
|
||||
# 格式: user service secret IP
|
||||
echo "$username $servicename $password $ipaddr" >> "$CHAP_SECRETS"
|
||||
fi
|
||||
}
|
||||
|
||||
# 管理 Crontab 任务
|
||||
manage_cron() {
|
||||
local cron_cmd_start="59 6 * * * /etc/init.d/pppoe-user start"
|
||||
local cron_cmd_backup="59 23 * * * cp -f /etc/config/pppoe-user $BACKUP_DIR/pppoe-user.\$(date +\%Y\%m\%d).bak"
|
||||
|
||||
# 移除旧的任务(防止重复)
|
||||
crontab -u root -l 2>/dev/null | grep -v "/etc/init.d/pppoe-user start" | grep -v "cp -f /etc/config/pppoe-user" | crontab -u root -
|
||||
|
||||
# 添加新任务
|
||||
(crontab -u root -l 2>/dev/null; echo "$cron_cmd_start"; echo "$cron_cmd_backup") | sort -u | crontab -u root -
|
||||
}
|
||||
|
||||
# 配置 nftables 限速表
|
||||
setup_nft() {
|
||||
# 检查表是否存在,存在则先删除以保证幂等性
|
||||
nft list table inet pppoe >/dev/null 2>&1 && nft delete table inet pppoe
|
||||
|
||||
nft add table inet pppoe
|
||||
nft add chain inet pppoe upload { type filter hook prerouting priority filter\; policy accept\; }
|
||||
nft add chain inet pppoe download { type filter hook postrouting priority filter\; policy accept\; }
|
||||
|
||||
# 更新防火墙区域配置以包含 ppp 接口
|
||||
local zone_device
|
||||
zone_device=$(uci get firewall.@zone[0].device 2>/dev/null)
|
||||
if [ -z "$(echo "$zone_device" | grep 'ppp+')" ]; then
|
||||
uci add_list firewall.@zone[0].device='ppp+'
|
||||
uci commit firewall
|
||||
# 注意:这里不重启 firewall,以免断开现有连接,nft 规则即时生效
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup_nft() {
|
||||
nft list table inet pppoe >/dev/null 2>&1 && nft delete table inet pppoe
|
||||
}
|
||||
|
||||
# 启动 ucode ubus 辅助服务
|
||||
start_ubus_helper() {
|
||||
if [ -x "$UCODE_BIN" ] && [ -f "$UBUS_HELPER" ]; then
|
||||
# 检查是否已在运行
|
||||
if ! pgrep -f "ucode.*pppoe-ubus.uc" >/dev/null; then
|
||||
logger -t pppoe-user "Starting ubus helper service"
|
||||
$UCODE_BIN $UBUS_HELPER &
|
||||
disown
|
||||
fi
|
||||
else
|
||||
logger -t pppoe-user "Warning: ucode helper not found or not executable. LuCI kill function may fail."
|
||||
fi
|
||||
}
|
||||
|
||||
stop_ubus_helper() {
|
||||
pkill -f "ucode.*pppoe-ubus.uc"
|
||||
}
|
||||
|
||||
start_service() {
|
||||
init_dirs
|
||||
|
||||
logger -t pppoe-user "Starting PPPoE User Management Service"
|
||||
|
||||
# 备份旧的 chap-secrets
|
||||
[ -f "$CHAP_SECRETS" ] && cp -f "$CHAP_SECRETS" /etc/ppp/lasttime-chap-secrets
|
||||
|
||||
# 生成新的 chap-secrets 并检查过期
|
||||
check_expiration_and_build_secrets
|
||||
|
||||
# 设置权限
|
||||
chmod 600 "$CHAP_SECRETS"
|
||||
|
||||
# 配置定时任务
|
||||
manage_cron
|
||||
|
||||
# 配置限速
|
||||
if [ "$SPEED_LIMIT_SWITCH" = "1" ]; then
|
||||
setup_nft
|
||||
else
|
||||
cleanup_nft
|
||||
fi
|
||||
|
||||
# 启动 ubus 辅助进程 (供 LuCI JS 调用)
|
||||
start_ubus_helper
|
||||
|
||||
# 如果是作为服务运行,不需要保持前台,因为主要工作是生成配置和规则
|
||||
# 真正的 PPPoE 服务由 pppoe-server 守护进程管理
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
logger -t pppoe-user "Stopping PPPoE User Management Service"
|
||||
|
||||
stop_ubus_helper
|
||||
cleanup_nft
|
||||
|
||||
# 可选:清除 chap-secrets (通常不建议,因为重启后需要,但在 stop 时清理可确保安全)
|
||||
# : > "$CHAP_SECRETS"
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop_service
|
||||
start_service
|
||||
}
|
||||
|
||||
# 兼容旧版 procd 的 stop 行为
|
||||
stop() {
|
||||
stop_service
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "pppoe-user"
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script is run by the pppd _after_ the link is brought down.
|
||||
# It should be used to delete routes, unset IP addresses, etc.
|
||||
|
||||
# Check if the required number of arguments is provided
|
||||
if [ $# -ne 5 ]; then
|
||||
echo "Usage: $0 <interface> <tty> <speed> <local_ip> <peer_ip>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Reset the PATH variable
|
||||
PATH=/usr/sbin:/sbin:/usr/bin:/bin
|
||||
export PATH
|
||||
|
||||
# Log the start of the script
|
||||
logger "Starting ip-down script for interface $1 with local IP $4 and peer IP $5"
|
||||
|
||||
# Define the directory containing additional scripts
|
||||
IP_DOWN_D_DIR="/etc/ppp/ip-down.d"
|
||||
|
||||
# Check if the directory exists
|
||||
if [ ! -d "$IP_DOWN_D_DIR" ]; then
|
||||
echo "Directory $IP_DOWN_D_DIR does not exist." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Loop through all files in the ip-down.d directory and execute them if they are executable
|
||||
for script in "$IP_DOWN_D_DIR"/*; do
|
||||
if [ -x "$script" ]; then
|
||||
"$script" "$@" || {
|
||||
echo "Error executing $script" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
done
|
||||
|
||||
# Log the successful completion of the script
|
||||
logger "Completed ip-down script for interface $1"
|
||||
|
||||
exit 0
|
||||
@ -1,58 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# This script is run by pppd _after_ the link is brought down.
|
||||
# It should be used to delete routes, unset IP addresses, etc.
|
||||
#
|
||||
# This script is called with the following arguments:
|
||||
# Arg Name Example
|
||||
# $1 Interface name ppp0
|
||||
# $2 The tty ttyS1
|
||||
# $3 The link speed 38400
|
||||
# $4 Local IP number 12.34.56.78
|
||||
# $5 Peer IP number 12.34.56.99
|
||||
#
|
||||
|
||||
# Reset the PATH environment variable
|
||||
PATH=/usr/sbin:/sbin:/usr/bin:/bin
|
||||
export PATH
|
||||
|
||||
# Get the current date and time
|
||||
QUIT_TIME="$(date "+%Y-%m-%d_%H:%M:%S")"
|
||||
|
||||
# Extract username, interface name, and peer IP address from arguments
|
||||
USERNAME="${PEERNAME}"
|
||||
IFACE="${1}"
|
||||
TTY="${2}"
|
||||
PEERIP="${5}"
|
||||
|
||||
# Paths and Configuration
|
||||
USER_LOG="/var/pppoe-user/log/userinfo"
|
||||
USER_LOG_FILE="${USER_LOG}/${USERNAME}.log"
|
||||
USER_CONFIG="pppoe-user"
|
||||
USER_CFGID="$(uci show ${USER_CONFIG} | grep "${USERNAME}" | cut -d '.' -sf 2)"
|
||||
USER_CONNECT="$(uci get ${USER_CONFIG}.${USER_CFGID}.connect)"
|
||||
SESSION_PATH="/var/etc/pppoe-user/session"
|
||||
|
||||
# Remove the session file
|
||||
rm -f "${SESSION_PATH}/${USERNAME}.${IFACE}"
|
||||
|
||||
# Log the disconnection details
|
||||
echo "${QUIT_TIME} ${USERNAME} Offline Interface: ${IFACE} IP: ${PEERIP}" >> "${USER_LOG_FILE}"
|
||||
echo "Connect time: ${CONNECT_TIME} minutes" >> "${USER_LOG_FILE}"
|
||||
echo "Sent: ${BYTES_SENT} bytes" >> "${USER_LOG_FILE}"
|
||||
echo "Received: ${BYTES_RCVD} bytes" >> "${USER_LOG_FILE}"
|
||||
|
||||
# Calculate and log the sum and average speed
|
||||
sum_bytes=$((${BYTES_SENT} + ${BYTES_RCVD}))
|
||||
sum=$(echo "scale=2;${sum_bytes}/1024/1024" | bc)
|
||||
echo "Bytes sum: ${sum} MB" >> "${USER_LOG_FILE}"
|
||||
ave=$(echo "scale=2;${sum_bytes}/1024/${CONNECT_TIME}" | bc)
|
||||
echo "Average speed: ${ave} KB/s" >> "${USER_LOG_FILE}"
|
||||
|
||||
# Add an empty line for separation in the log file
|
||||
echo >> "${USER_LOG_FILE}"
|
||||
|
||||
# Log the execution
|
||||
logger "${QUIT_TIME} ${PEERNAME} ${PEERIP} OFFLINE-USERS >>>>>> The script has been executed!"
|
||||
|
||||
exit 0
|
||||
@ -1,39 +0,0 @@
|
||||
#!/bin/sh
|
||||
(
|
||||
flock -n 200 || exit 1
|
||||
|
||||
# The environment is cleared before executing this script
|
||||
PATH=/usr/sbin:/sbin:/usr/bin:/bin
|
||||
export PATH
|
||||
|
||||
QUIT_TIME="$(date "+%Y-%m-%d_%H:%M:%S")"
|
||||
USERNAME="${PEERNAME}"
|
||||
PEERIP="${5}"
|
||||
|
||||
USER_CONFIG="pppoe-user"
|
||||
USER_CFGID="$(uci show ${USER_CONFIG} | grep "${USERNAME}" | cut -d '.' -f 2)"
|
||||
TABLE="pppoe"
|
||||
|
||||
# Define chains
|
||||
CHAINS="upload download"
|
||||
|
||||
# Loop through each chain and delete all rules matching the IP
|
||||
for CHAIN in ${CHAINS}; do
|
||||
# Get all rule handles associated with the IP in the current chain
|
||||
RULE_HANDLES="$(nft -a list chain inet ${TABLE} ${CHAIN} | grep "${PEERIP}" | awk '{print $NF}')"
|
||||
|
||||
if [ -n "${RULE_HANDLES}" ]; then
|
||||
for HANDLE in ${RULE_HANDLES}; do
|
||||
nft delete rule inet ${TABLE} ${CHAIN} handle "${HANDLE}" || {
|
||||
logger "${QUIT_TIME} ERROR: Failed to delete rule handle ${HANDLE} in chain ${CHAIN} for ${USERNAME} ${PEERIP}"
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
else
|
||||
logger "${QUIT_TIME} WARNING: No rules found for ${USERNAME} ${PEERIP} in chain ${CHAIN}"
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
|
||||
) 200>/var/lock/ipdown.lock
|
||||
@ -1,41 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script is run by the pppd after the link is established.
|
||||
# It should be used to add routes, set IP address, run the mailq, etc.
|
||||
|
||||
# Check if the required number of arguments is provided
|
||||
if [ $# -ne 5 ]; then
|
||||
echo "Usage: $0 <interface> <tty> <speed> <local_ip> <peer_ip>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Reset the PATH variable
|
||||
PATH=/usr/sbin:/sbin:/usr/bin:/bin
|
||||
export PATH
|
||||
|
||||
# Log the start of the script
|
||||
logger "Starting ip-up script for interface $1 with local IP $4 and peer IP $5"
|
||||
|
||||
# Define the directory containing additional scripts
|
||||
IP_UP_D_DIR="/etc/ppp/ip-up.d"
|
||||
|
||||
# Check if the directory exists
|
||||
if [ ! -d "$IP_UP_D_DIR" ]; then
|
||||
echo "Directory $IP_UP_D_DIR does not exist." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Loop through all files in the ip-up.d directory and execute them if they are executable
|
||||
for script in "$IP_UP_D_DIR"/*; do
|
||||
if [ -x "$script" ]; then
|
||||
"$script" "$@" || {
|
||||
echo "Error executing $script" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
done
|
||||
|
||||
# Log the successful completion of the script
|
||||
logger "Completed ip-up script for interface $1"
|
||||
|
||||
exit 0
|
||||
@ -1,87 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# This script is run by pppd after the link is established.
|
||||
# It is used to add routes, set IP address, run mailq, etc.
|
||||
#
|
||||
# This script is called with the following arguments:
|
||||
# Arg Name Example
|
||||
# $1 Interface name ppp0
|
||||
# $2 The tty ttyS1
|
||||
# $3 The link speed 38400
|
||||
# $4 Local IP number 12.34.56.78
|
||||
# $5 Peer IP number 12.34.56.99
|
||||
#
|
||||
|
||||
# Reset the PATH environment variable
|
||||
PATH=/usr/sbin:/sbin:/usr/bin:/bin
|
||||
export PATH
|
||||
|
||||
LOGIN_TIME=$(date "+%Y-%m-%d_%H:%M:%S")
|
||||
USERNAME="${PEERNAME}"
|
||||
IFACE="${1}"
|
||||
TTY="${2}"
|
||||
PEERIP="${5}"
|
||||
|
||||
USER_LOG="/var/pppoe-user/log/userinfo"
|
||||
USER_LOG_FILE="${USER_LOG}/${USERNAME}.log"
|
||||
USER_CONFIG="pppoe-user"
|
||||
USER_CFGID="$(uci show ${USER_CONFIG} | grep "${USERNAME}" | cut -d '.' -sf 2)"
|
||||
SERVIC_NAME="$(uci get ${USER_CONFIG}.${USER_CFGID}.servicename)"
|
||||
USER_PACKAGE="$(uci get ${USER_CONFIG}.${USER_CFGID}.package)"
|
||||
USER_QOS="$(uci get ${USER_CONFIG}.${USER_CFGID}.qos)"
|
||||
USER_UPLOAD="$(uci get ${USER_CONFIG}.${USER_CFGID}.urate)"
|
||||
USER_DOWNLOAD="$(uci get ${USER_CONFIG}.${USER_CFGID}.drate)"
|
||||
USER_CONNECT="$(uci get ${USER_CONFIG}.${USER_CFGID}.connect)"
|
||||
RENEWAL_DATE="$(uci get ${USER_CONFIG}.${USER_CFGID}.expires)"
|
||||
|
||||
CONFIG_PATH="/var/etc/pppoe-user"
|
||||
SESSION_PATH="${CONFIG_PATH}/session"
|
||||
|
||||
PID="$(cat /var/run/pppd/${IFACE}.pid 2>/dev/null)"
|
||||
MAC="$(ps -w | grep pppd | grep -v 'grep' | grep -h -m 1 "${PID}" | awk '{print $10}' | cut -d : -f 2,2-7)"
|
||||
|
||||
mkdir -p "${USER_LOG}"
|
||||
mkdir -p "${SESSION_PATH}"
|
||||
|
||||
# Write session information in JSON format
|
||||
cat <<-EOF > "${SESSION_PATH}/${USERNAME}.${IFACE}"
|
||||
{
|
||||
"username": "${USERNAME}",
|
||||
"mac": "${MAC}",
|
||||
"interface": "${IFACE}",
|
||||
"tty": "${TTY}",
|
||||
"speed": "${SPEED}",
|
||||
"ip": "${PEERIP}",
|
||||
"servicename": "${SERVIC_NAME}",
|
||||
"package": "${USER_PACKAGE}",
|
||||
"connect": "${USER_CONNECT}",
|
||||
"updated": "${RENEWAL_DATE}",
|
||||
"uptime": "${LOGIN_TIME}",
|
||||
"pid": "${PID}"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Find and kill previous PPP sessions for the same user
|
||||
cfgid=$(uci show ${USER_CONFIG} | grep "@user" | grep ".username='${USERNAME}'" | cut -d '.' -sf 2)
|
||||
if [ -n "$cfgid" ]; then
|
||||
HAS_LOGIN=$(ls "${SESSION_PATH}" | grep "^${USERNAME}\.ppp" | grep -v "${IFACE}")
|
||||
if [ -n "$HAS_LOGIN" ]; then
|
||||
KO_IFACE=$(echo "$HAS_LOGIN" | awk -F '.' '{print $2}')
|
||||
KO_PID=$(cat "/var/run/${KO_IFACE}.pid" 2>/dev/null)
|
||||
[ -n "$KO_PID" ] && kill -9 "$KO_PID" >/dev/null 2>&1
|
||||
rm -f "${SESSION_PATH}/${HAS_LOGIN}"
|
||||
rm -f "/var/run/${KO_IFACE}.pid"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Update the MAC address in the user's configuration
|
||||
uci set "${USER_CONFIG}.${USER_CFGID}.macaddr=${MAC}"
|
||||
uci commit "${USER_CONFIG}"
|
||||
|
||||
# Log the connection details
|
||||
echo "${LOGIN_TIME} ${USERNAME} Online PID: ${PID} Interface: ${IFACE} IP: ${PEERIP} MAC: ${MAC}" >> "${USER_LOG_FILE}"
|
||||
echo >> "${USER_LOG_FILE}"
|
||||
|
||||
logger "${LOGIN_TIME} ${PEERNAME} ${PEERIP} ONLINE-USERS >>>>>> The script has been executed!"
|
||||
|
||||
exit 0
|
||||
@ -1,58 +0,0 @@
|
||||
#!/bin/sh
|
||||
(
|
||||
flock -n 200 || exit 1
|
||||
|
||||
# Environment variable settings
|
||||
PATH=/usr/sbin:/sbin:/usr/bin:/bin
|
||||
export PATH
|
||||
|
||||
LOGIN_TIME="$(date "+%Y-%m-%d_%H:%M:%S")"
|
||||
USERNAME="${PEERNAME}"
|
||||
PEERIP="${5}"
|
||||
|
||||
USER_CONFIG="pppoe-user"
|
||||
USER_CFGID="$(uci show ${USER_CONFIG} | grep "${USERNAME}" | cut -d '.' -f 2)"
|
||||
|
||||
# Convert Mbps to KBytes
|
||||
USER_QOS="$(uci get ${USER_CONFIG}.${USER_CFGID}.qos 2>/dev/null)"
|
||||
USER_UPLOAD="$(($(uci get ${USER_CONFIG}.${USER_CFGID}.urate 2>/dev/null) * 125))"
|
||||
USER_DOWNLOAD="$(($(uci get ${USER_CONFIG}.${USER_CFGID}.drate 2>/dev/null) * 125))"
|
||||
UPLOAD_BURST="$((USER_UPLOAD / 10))"
|
||||
DOWNLOAD_BURST="$((USER_DOWNLOAD / 10))"
|
||||
USER_CONNECT="$(uci get ${USER_CONFIG}.${USER_CFGID}.connect 2>/dev/null)"
|
||||
|
||||
# Ensure that the obtained values are valid
|
||||
if [ -z "${USER_QOS}" ] || [ -z "${USER_UPLOAD}" ] || [ -z "${USER_DOWNLOAD}" ] || [ -z "${USER_CONNECT}" ]; then
|
||||
logger "${LOGIN_TIME} ERROR: Failed to get user config for (${USERNAME}) (${PEERIP})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
UP="upload"
|
||||
DOWN="download"
|
||||
|
||||
# Add traffic control rules
|
||||
if [ "${USER_QOS}" = "1" ]; then
|
||||
# Ensure rules are inserted in the correct order
|
||||
|
||||
# Upload chain
|
||||
nft insert rule inet pppoe ${UP} ip saddr "${PEERIP}" ct count over "${USER_CONNECT}" drop
|
||||
nft insert rule inet pppoe ${UP} ip saddr "${PEERIP}" ct state new limit rate "${USER_CONNECT}"/minute accept
|
||||
nft insert rule inet pppoe ${UP} ip saddr "${PEERIP}" limit rate over "${USER_UPLOAD}" kbytes/second burst "${UPLOAD_BURST}" kbytes counter drop
|
||||
if [ $? -ne 0 ]; then
|
||||
logger "${LOGIN_TIME} ERROR: Failed to insert upload rules (${USERNAME}) (${PEERIP})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Download chain
|
||||
nft insert rule inet pppoe ${DOWN} ip daddr "${PEERIP}" ct count over "${USER_CONNECT}" drop
|
||||
nft insert rule inet pppoe ${DOWN} ip daddr "${PEERIP}" ct state new limit rate "${USER_CONNECT}"/minute accept
|
||||
nft insert rule inet pppoe ${DOWN} ip daddr "${PEERIP}" limit rate over "${USER_DOWNLOAD}" kbytes/second burst "${DOWNLOAD_BURST}" kbytes counter drop
|
||||
if [ $? -ne 0 ]; then
|
||||
logger "${LOGIN_TIME} ERROR: Failed to insert download rules (${USERNAME}) (${PEERIP})"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
) 200>/var/lock/ipup.lock
|
||||
@ -1,25 +0,0 @@
|
||||
#!/bin/sh
|
||||
[ ! -f "/usr/share/ucitrack/luci-app-pppoe-user.json" ] && {
|
||||
cat > /usr/share/ucitrack/luci-app-pppoe-user.json << EEOF
|
||||
{
|
||||
"config": "pppoe-user",
|
||||
"init": "pppoe-user"
|
||||
}
|
||||
EEOF
|
||||
}
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@pppoe-user[-1]
|
||||
add ucitrack pppoe-user
|
||||
set ucitrack.@pppoe-user[-1].init=pppoe-user
|
||||
commit ucitrack
|
||||
EOF
|
||||
|
||||
chmod 600 /etc/config/pppoe-user
|
||||
chmod 755 /etc/init.d/pppoe-user
|
||||
chmod 755 /etc/ppp/ip-up /etc/ppp/ip-down
|
||||
chmod 755 /etc/ppp/auth-up /etc/ppp/auth-down
|
||||
chmod +x /etc/ppp/ip-up.d/* /etc/ppp/ip-down.d/*
|
||||
|
||||
rm -rf /tmp/luci-*cache
|
||||
exit 0
|
||||
@ -1,71 +0,0 @@
|
||||
{
|
||||
"admin/pppoe-user": {
|
||||
"title": "Management",
|
||||
"order": 60,
|
||||
"action": {
|
||||
"type": "alias",
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-pppoe-user" ],
|
||||
}
|
||||
},
|
||||
|
||||
"admin/pppoe-user/usermanager": {
|
||||
"title": "User Manager",
|
||||
"order": 1,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "pppoe-user/user",
|
||||
}
|
||||
},
|
||||
|
||||
"admin/pppoe-user/onumanager": {
|
||||
"title": "ONU Manager",
|
||||
"order": 2,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "pppoe-user/onu",
|
||||
}
|
||||
},
|
||||
|
||||
"admin/status/userstatus": {
|
||||
"title": "User Status",
|
||||
"order": 88,
|
||||
"action": {
|
||||
"type": "alias",
|
||||
"path": "admin/status/userstatus/onlineuser",
|
||||
"post": { "cbi.submit": true }
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-pppoe-user" ],
|
||||
"uci": { "pppoe-user": true }
|
||||
}
|
||||
},
|
||||
|
||||
"admin/status/userstatus/onlineuser": {
|
||||
"title": "Online User",
|
||||
"order": 1,
|
||||
"action": {
|
||||
"type": "form",
|
||||
"path": "pppoe-user/onlineuser",
|
||||
}
|
||||
},
|
||||
|
||||
"admin/status/userstatus/downtimeuser": {
|
||||
"title": "Downtime User",
|
||||
"order": 2,
|
||||
"action": {
|
||||
"type": "form",
|
||||
"path": "pppoe-user/downtimeuser",
|
||||
}
|
||||
},
|
||||
|
||||
"admin/status/userstatus/interface": {
|
||||
"title": "Interface Monitor",
|
||||
"order": 3,
|
||||
"action": {
|
||||
"type": "form",
|
||||
"path": "pppoe-user/interface",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
{
|
||||
"luci-app-pppoe-user": {
|
||||
"description": "Grant access to pppoe-user",
|
||||
"read": {
|
||||
"uci": [ "pppoe-user" ],
|
||||
"file": {
|
||||
"/var/etc/pppoe-user/session/*.json": [ "read" ],
|
||||
"/var/pppoe-user/log/interface.log": [ "read" ],
|
||||
"/proc/net/dev": [ "read" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "pppoe-user" ],
|
||||
"exec": {
|
||||
"/bin/kill": [ "arg1" ],
|
||||
"/usr/bin/unlink": [ "arg1" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
#!/usr/bin/ucode
|
||||
import { ubus } from 'ubus';
|
||||
import { unlink } from 'fs';
|
||||
|
||||
let conn = ubus.connect();
|
||||
if (!conn) exit(1);
|
||||
|
||||
conn.add({
|
||||
name: 'pppoe.user',
|
||||
methods: {
|
||||
kill: {
|
||||
args: { session_file: '', pid: '' },
|
||||
call: function(req, args) {
|
||||
if (!args.session_file || !args.pid)
|
||||
return { error: 'Invalid arguments' };
|
||||
|
||||
try {
|
||||
unlink(args.session_file);
|
||||
system(`kill -15 ${args.pid}`);
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
return { error: e };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
conn.listen();
|
||||
@ -1,107 +0,0 @@
|
||||
#!/usr/bin/ucode
|
||||
/* /usr/share/ucode/pppoe-user.uc */
|
||||
|
||||
import { readfile, listdir, stat } from 'fs';
|
||||
import { exec } from 'ubus';
|
||||
import { parse } from 'jsonc';
|
||||
|
||||
const SESSION_PATH = '/var/etc/pppoe-user/session';
|
||||
const LOG_PATH = '/var/pppoe-user/log/interface.log';
|
||||
|
||||
// 获取在线用户列表
|
||||
export function get_online_users() {
|
||||
let users = [];
|
||||
let count = 0;
|
||||
|
||||
// 统计 ppp 接口数量 (模拟原 lua 逻辑)
|
||||
try {
|
||||
let dev = readfile('/proc/net/dev');
|
||||
if (dev) {
|
||||
for (let line in split(dev, /\n/)) {
|
||||
if (match(line, /^\s*ppp/)) count++;
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (!stat(SESSION_PATH)) return { count: count, sessions: [] };
|
||||
|
||||
let files = listdir(SESSION_PATH);
|
||||
if (!files) return { count: count, sessions: [] };
|
||||
|
||||
for (let filename in files) {
|
||||
let filepath = `${SESSION_PATH}/${filename}`;
|
||||
if (!stat(filepath)?.is_regular) continue;
|
||||
|
||||
try {
|
||||
let content = readfile(filepath);
|
||||
if (content) {
|
||||
let data = parse(content);
|
||||
if (data) {
|
||||
data.session_file = filepath;
|
||||
push(users, data);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
warn(`Failed to read ${filepath}: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { count: count, sessions: users };
|
||||
}
|
||||
|
||||
// 强制下线用户
|
||||
export function kill_user(session_file, pid) {
|
||||
if (!session_file || !pid) return { error: 'Invalid parameters' };
|
||||
|
||||
try {
|
||||
// 删除会话文件
|
||||
unlink(session_file);
|
||||
|
||||
// 杀死进程
|
||||
let ret = system(`kill -15 ${pid}`);
|
||||
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
return { error: e };
|
||||
}
|
||||
}
|
||||
|
||||
// 获取接口日志
|
||||
export function get_interface_log() {
|
||||
if (!stat(LOG_PATH)) return '';
|
||||
|
||||
try {
|
||||
// 读取最后 200 行 (简单实现,ucode 没有直接的 tail,需读取后分割)
|
||||
let content = readfile(LOG_PATH);
|
||||
if (!content) return '';
|
||||
|
||||
let lines = split(content, /\n/);
|
||||
let tail_lines = slice(lines, -200);
|
||||
// 反转以符合原逻辑 (最新在上)
|
||||
return join(reverse(tail_lines), '\n');
|
||||
} catch (e) {
|
||||
return `Error reading log: ${e}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取下线用户统计 (简单模拟,实际需解析 UCI)
|
||||
export function get_downtime_count() {
|
||||
// 这里可以通过 ubus 调用 uci 或者直接解析 /etc/config/pppoe-user
|
||||
// 为简化,建议在 JS 层直接处理 UCI 数据,或者在此处调用 uci get
|
||||
try {
|
||||
let res = exec('uci', { 'command': 'show', 'params': ['pppoe-user'] });
|
||||
if (res && res.output) {
|
||||
let lines = split(res.output, /\n/);
|
||||
let cnt = 0;
|
||||
for (let line in lines) {
|
||||
if (match(line, /=enabled$/)) { // 粗略匹配,更严谨需解析 value
|
||||
// 实际上 ucidef 或 uci get 更好,这里仅作演示逻辑占位
|
||||
// 建议在前端 JS 直接遍历 uci.load('pppoe-user')
|
||||
}
|
||||
}
|
||||
// 由于 ucode 直接调 uci 命令解析较繁琐,推荐此逻辑移至前端 JS 处理 UCI 对象
|
||||
return 0;
|
||||
}
|
||||
} catch (e) {}
|
||||
return 0;
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"config": "pppoe-user",
|
||||
"init": "pppoe-user"
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 65 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 61 KiB |
@ -54,6 +54,11 @@ return view.extend({
|
||||
o = s.taboption('general', form.Value, 'description', _('Description'));
|
||||
o.placeholder = 'Samba4 on OpenWrt';
|
||||
|
||||
o = s.taboption('general', form.Flag, 'autoshare', _('Auto Share'),
|
||||
_('Auto share local disk which connected'));
|
||||
o.default = 'yes';
|
||||
o.rmempty = false;
|
||||
|
||||
s.taboption('general', form.Flag, 'enable_extra_tuning', _('Enable extra Tuning'),
|
||||
_('Enable some community driven tuning parameters, that may improve write speeds and better operation via WiFi.\
|
||||
Not recommend if multiple clients write to the same files, at the same time!'));
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=pcat-manager
|
||||
PKG_RELEASE:=1
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/photonicat/rockchip_rk3568_pcat_manager.git
|
||||
PKG_SOURCE_DATE:=2023-11-13
|
||||
PKG_SOURCE_VERSION:=7f05f163ac864ce323ad648a4c03047915fce496
|
||||
PKG_SOURCE_VERSION:=961edb8b5cbae63efb03ee774d198f6fdb682a45
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-3.0-only
|
||||
|
||||
Loading…
Reference in New Issue
Block a user