mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 12:53:56 +08:00
🎁 Sync 2026-03-23 17:00:57
This commit is contained in:
1 parent
42786ced0b
commit
190ca87b14
42 files changed
+242
-2157
No files matched your search
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=autoshare-ksmbd
|
PKG_NAME:=autoshare-ksmbd
|
||||||
PKG_VERSION:=1
|
PKG_VERSION:=1
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
PKG_ARCH:=all
|
PKG_ARCH:=all
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
|
||||||
# D-Team Technology Co.,Ltd. ShenZhen
|
|
||||||
# 作者:Vic
|
|
||||||
#
|
|
||||||
# 警告:对着屏幕的哥们,我们允许你使用此脚本,但不允许你抹去作者的信息,请保留这段话。
|
|
||||||
#
|
|
||||||
|
|
||||||
. /lib/functions.sh
|
. /lib/functions.sh
|
||||||
. /lib/functions/service.sh
|
. /lib/functions/service.sh
|
||||||
|
|
||||||
@@ -16,8 +9,9 @@ config_file="/etc/config/ksmbd"
|
|||||||
[ ! -f /etc/config/ksmbd ] && {
|
[ ! -f /etc/config/ksmbd ] && {
|
||||||
echo "
|
echo "
|
||||||
config globals
|
config globals
|
||||||
option 'workgroup' 'WORKGROUP'
|
option workgroup 'WORKGROUP'
|
||||||
option 'description' 'Ksmbd on OpenWrt'
|
option charset 'UTF-8'
|
||||||
|
option description 'Ksmbd on OpenWRT'
|
||||||
" > $config_file
|
" > $config_file
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,21 +26,27 @@ wait_for_init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
smb_handle() {
|
smb_handle() {
|
||||||
config_get path $1 path
|
local path
|
||||||
if [ "$path" = "$2" ] ;then
|
config_get path "$1" path
|
||||||
global=1
|
if [ -n "$path" ]; then
|
||||||
|
case "$path" in
|
||||||
|
"$2" | "$2"/*) global=1 ;;
|
||||||
|
esac
|
||||||
|
case "$2" in
|
||||||
|
"$path" | "$path"/*) global=1 ;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
chk_en() {
|
chk_en() {
|
||||||
config_get_bool autoshare $1 autoshare 1
|
config_get_bool autoshare "$1" autoshare 1
|
||||||
[ $autoshare -eq 0 ] && exit
|
[ "$autoshare" -eq 0 ] && exit
|
||||||
}
|
}
|
||||||
|
|
||||||
config_load ksmbd
|
config_load ksmbd
|
||||||
config_foreach chk_en ksmbd
|
config_foreach chk_en ksmbd
|
||||||
|
|
||||||
device=`basename $DEVPATH`
|
device=`basename "$DEVPATH"`
|
||||||
|
|
||||||
case "$ACTION" in
|
case "$ACTION" in
|
||||||
add)
|
add)
|
||||||
@@ -55,6 +55,8 @@ case "$ACTION" in
|
|||||||
sd*);;
|
sd*);;
|
||||||
md*);;
|
md*);;
|
||||||
hd*);;
|
hd*);;
|
||||||
|
mmcblk*);;
|
||||||
|
nvme*);;
|
||||||
*) return;;
|
*) return;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -62,22 +64,21 @@ case "$ACTION" in
|
|||||||
|
|
||||||
wait_for_init
|
wait_for_init
|
||||||
|
|
||||||
[ "$(uci -q get ksmbd.@globals[0].autoshare)" == "1" ] && {
|
grep -v '/boot\|/opt' /proc/mounts | while read -r j
|
||||||
/etc/init.d/ksmbd restart
|
|
||||||
exit 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
cat /proc/mounts | grep -v '/boot\|/opt' | while read j
|
|
||||||
do
|
do
|
||||||
str=${j%% *}
|
str=${j%% *}
|
||||||
if [ "$str" == $path ];then
|
if [ "$str" = "$path" ];then
|
||||||
strr=${j#* }
|
strr=${j#* }
|
||||||
target=${strr%% *}
|
target=${strr%% *}
|
||||||
global=0
|
global=0
|
||||||
config_foreach smb_handle share $target
|
|
||||||
|
config_load ksmbd
|
||||||
|
config_foreach smb_handle share "$target"
|
||||||
|
|
||||||
name=${target#*/mnt/}
|
name=${target#*/mnt/}
|
||||||
name=$(echo $name | sed -e "s/^\///")
|
name=$(echo "$name" | sed -e "s/^\///")
|
||||||
if [ $global -eq 0 ] ;then
|
|
||||||
|
if [ "$global" -eq 0 ] ;then
|
||||||
echo -e "\n\nconfig share" >> $config_file
|
echo -e "\n\nconfig share" >> $config_file
|
||||||
echo -e "\toption auto '1'" >> $config_file
|
echo -e "\toption auto '1'" >> $config_file
|
||||||
echo -e "\toption name '$name'" >> $config_file
|
echo -e "\toption name '$name'" >> $config_file
|
||||||
@@ -93,27 +94,26 @@ case "$ACTION" in
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
uci -q set ksmbd.@globals[0].autoshare='1'
|
|
||||||
uci commit ksmbd
|
|
||||||
;;
|
;;
|
||||||
|
|
||||||
remove)
|
remove)
|
||||||
i=0
|
i=0
|
||||||
while true
|
while true
|
||||||
do
|
do
|
||||||
dev=`uci get ksmbd.@share[$i].device`
|
if ! uci -q get ksmbd.@share[$i] >/dev/null; then
|
||||||
[ $? -ne 0 ] && break
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
[ "$dev" = "$device" ] && {
|
dev=$(uci -q get ksmbd.@share[$i].device)
|
||||||
auto=`uci get ksmbd.@share[$i].auto`
|
if [ "$dev" = "$device" ]; then
|
||||||
[ $auto = "1" ] && {
|
auto=$(uci -q get ksmbd.@share[$i].auto)
|
||||||
mount_dir=`uci get ksmbd.@share[$i].name`
|
if [ "$auto" = "1" ]; then
|
||||||
uci delete ksmbd.@share[$i]
|
uci delete ksmbd.@share[$i]
|
||||||
uci commit
|
uci commit ksmbd
|
||||||
/etc/init.d/ksmbd reload
|
/etc/init.d/ksmbd reload
|
||||||
return
|
return
|
||||||
}
|
fi
|
||||||
}
|
fi
|
||||||
let i+=1
|
let i+=1
|
||||||
done
|
done
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=autoshare-samba
|
PKG_NAME:=autoshare-samba
|
||||||
PKG_VERSION:=1
|
PKG_VERSION:=1
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
PKG_ARCH:=all
|
PKG_ARCH:=all
|
||||||
|
|
||||||
include $(INCLUDE_DIR)/package.mk
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
|
||||||
# D-Team Technology Co.,Ltd. ShenZhen
|
|
||||||
# 作者:Vic
|
|
||||||
#
|
|
||||||
# 警告:对着屏幕的哥们,我们允许你使用此脚本,但不允许你抹去作者的信息,请保留这段话。
|
|
||||||
#
|
|
||||||
|
|
||||||
. /lib/functions.sh
|
. /lib/functions.sh
|
||||||
. /lib/functions/service.sh
|
. /lib/functions/service.sh
|
||||||
|
|
||||||
@@ -33,21 +26,27 @@ wait_for_init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
smb_handle() {
|
smb_handle() {
|
||||||
config_get path $1 path
|
local path
|
||||||
if [ "$path" = "$2" ] ;then
|
config_get path "$1" path
|
||||||
global=1
|
if [ -n "$path" ]; then
|
||||||
|
case "$path" in
|
||||||
|
"$2" | "$2"/*) global=1 ;;
|
||||||
|
esac
|
||||||
|
case "$2" in
|
||||||
|
"$path" | "$path"/*) global=1 ;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
chk_en() {
|
chk_en() {
|
||||||
config_get_bool autoshare $1 autoshare 1
|
config_get_bool autoshare "$1" autoshare 1
|
||||||
[ $autoshare -eq 0 ] && exit
|
[ "$autoshare" -eq 0 ] && exit
|
||||||
}
|
}
|
||||||
|
|
||||||
config_load samba4
|
config_load samba4
|
||||||
config_foreach chk_en samba4
|
config_foreach chk_en samba4
|
||||||
|
|
||||||
device=`basename $DEVPATH`
|
device=`basename "$DEVPATH"`
|
||||||
|
|
||||||
case "$ACTION" in
|
case "$ACTION" in
|
||||||
add)
|
add)
|
||||||
@@ -56,6 +55,8 @@ case "$ACTION" in
|
|||||||
sd*);;
|
sd*);;
|
||||||
md*);;
|
md*);;
|
||||||
hd*);;
|
hd*);;
|
||||||
|
mmcblk*);;
|
||||||
|
nvme*);;
|
||||||
*) return;;
|
*) return;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -63,22 +64,21 @@ case "$ACTION" in
|
|||||||
|
|
||||||
wait_for_init
|
wait_for_init
|
||||||
|
|
||||||
[ "$(uci -q get samba4.@samba[0].autoshare)" == "1" ] && {
|
grep -v '/boot\|/opt' /proc/mounts | while read -r j
|
||||||
/etc/init.d/samba4 restart
|
|
||||||
exit 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
cat /proc/mounts | grep -v '/boot\|/opt' | while read j
|
|
||||||
do
|
do
|
||||||
str=${j%% *}
|
str=${j%% *}
|
||||||
if [ "$str" == $path ];then
|
if [ "$str" = "$path" ];then
|
||||||
strr=${j#* }
|
strr=${j#* }
|
||||||
target=${strr%% *}
|
target=${strr%% *}
|
||||||
global=0
|
global=0
|
||||||
config_foreach smb_handle sambashare $target
|
|
||||||
|
config_load samba4
|
||||||
|
config_foreach smb_handle sambashare "$target"
|
||||||
|
|
||||||
name=${target#*/mnt/}
|
name=${target#*/mnt/}
|
||||||
name=$(echo $name | sed -e "s/^\///")
|
name=$(echo "$name" | sed -e "s/^\///")
|
||||||
if [ $global -eq 0 ] ;then
|
|
||||||
|
if [ "$global" -eq 0 ] ;then
|
||||||
echo -e "\n\nconfig sambashare" >> $config_file
|
echo -e "\n\nconfig sambashare" >> $config_file
|
||||||
echo -e "\toption auto '1'" >> $config_file
|
echo -e "\toption auto '1'" >> $config_file
|
||||||
echo -e "\toption name '$name'" >> $config_file
|
echo -e "\toption name '$name'" >> $config_file
|
||||||
@@ -94,27 +94,26 @@ case "$ACTION" in
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
uci -q set samba4.@samba[0].autoshare='1'
|
|
||||||
uci commit samba4
|
|
||||||
;;
|
;;
|
||||||
|
|
||||||
remove)
|
remove)
|
||||||
i=0
|
i=0
|
||||||
while true
|
while true
|
||||||
do
|
do
|
||||||
dev=`uci get samba4.@sambashare[$i].device`
|
if ! uci -q get samba4.@sambashare[$i] >/dev/null; then
|
||||||
[ $? -ne 0 ] && break
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
[ "$dev" = "$device" ] && {
|
dev=$(uci -q get samba4.@sambashare[$i].device)
|
||||||
auto=`uci get samba4.@sambashare[$i].auto`
|
if [ "$dev" = "$device" ]; then
|
||||||
[ $auto = "1" ] && {
|
auto=$(uci -q get samba4.@sambashare[$i].auto)
|
||||||
mount_dir=`uci get samba4.@sambashare[$i].name`
|
if [ "$auto" = "1" ]; then
|
||||||
uci delete samba4.@sambashare[$i]
|
uci delete samba4.@sambashare[$i]
|
||||||
uci commit
|
uci commit samba4
|
||||||
/etc/init.d/samba4 reload
|
/etc/init.d/samba4 reload
|
||||||
return
|
return
|
||||||
}
|
fi
|
||||||
}
|
fi
|
||||||
let i+=1
|
let i+=1
|
||||||
done
|
done
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-amlogic
|
PKG_NAME:=luci-app-amlogic
|
||||||
PKG_VERSION:=3.1.286
|
PKG_VERSION:=3.1.287
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
PKG_LICENSE:=GPL-2.0 License
|
PKG_LICENSE:=GPL-2.0 License
|
||||||
PKG_MAINTAINER:=ophub <https://github.com/ophub/luci-app-amlogic>
|
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"
|
[[ "${?}" -ne "0" ]] && tolog "02.05 Language pack [ ${langfile} ] download failed." "1"
|
||||||
done
|
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
|
sync && sleep 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_VERSION:=0.1.5
|
PKG_VERSION:=0.1.6
|
||||||
PKG_RELEASE:=6
|
PKG_RELEASE:=7
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI support for OpenClaw Launcher
|
LUCI_TITLE:=LuCI support for OpenClaw Launcher
|
||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
|
|||||||
@@ -130,12 +130,14 @@
|
|||||||
if (status.installing) return "安装中";
|
if (status.installing) return "安装中";
|
||||||
if (!status || !status.installed) return "未安装";
|
if (!status || !status.installed) return "未安装";
|
||||||
if (status.running) return "运行中";
|
if (status.running) return "运行中";
|
||||||
|
if (status.reachable) return "运行中(未托管)";
|
||||||
return "已停止";
|
return "已停止";
|
||||||
}
|
}
|
||||||
|
|
||||||
function statusDotClass(status) {
|
function statusDotClass(status) {
|
||||||
if (status && status.installing) return "";
|
if (status && status.installing) return "";
|
||||||
if (status && status.running) return "is-success";
|
if (status && status.running) return "is-success";
|
||||||
|
if (status && status.reachable) return "";
|
||||||
return "is-danger";
|
return "is-danger";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,11 +384,8 @@
|
|||||||
var savingAi = state.savingSection === "ai";
|
var savingAi = state.savingSection === "ai";
|
||||||
var savingAccess = state.savingSection === "access";
|
var savingAccess = state.savingSection === "access";
|
||||||
|
|
||||||
var serviceButtons = showServiceActions ? (
|
var canStartService = !status.running && !status.reachable;
|
||||||
(!status.running ? '<button class="oclm-button" type="button" data-op="start">启动服务</button>' : '') +
|
var canStopService = !!status.running;
|
||||||
(status.running ? '<button class="oclm-button" type="button" data-op="stop">停止服务</button>' : '') +
|
|
||||||
'<button class="oclm-button" type="button" data-op="restart">重启服务</button>'
|
|
||||||
) : '';
|
|
||||||
|
|
||||||
root.innerHTML =
|
root.innerHTML =
|
||||||
'<style>' + styleText + '</style>' +
|
'<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>' +
|
'<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>' +
|
'<label class="oclm-check"><input type="checkbox" id="oclm-install-accelerated"' + (installAcceleratedChecked ? ' checked' : '') + ' />Kspeeder 加速安装</label>' +
|
||||||
'</div>' +
|
'</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>' +
|
'<button id="oclm-cancel-install" class="oclm-button oclm-button-danger' + (status.installing ? '' : ' oclm-hidden') + '" type="button" data-op="cancel_install">停止安装</button>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div id="oclm-status-note" class="oclm-status-note' + (status.installing ? '' : ' oclm-hidden') + '">安装任务正在后台运行,点击“安装中”可继续查看日志。</div>' +
|
'<div id="oclm-status-note" class="oclm-status-note' + (status.installing ? '' : ' oclm-hidden') + '">安装任务正在后台运行,点击“安装中”可继续查看日志。</div>' +
|
||||||
@@ -438,7 +437,15 @@
|
|||||||
["auto", "自动"]
|
["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>') +
|
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></div>' +
|
||||||
|
|
||||||
'<div class="' + (activeTab === "ai" ? '' : 'oclm-hidden') + '">' +
|
'<div class="' + (activeTab === "ai" ? '' : 'oclm-hidden') + '">' +
|
||||||
@@ -485,6 +492,7 @@
|
|||||||
|
|
||||||
function updateStatusDom(status) {
|
function updateStatusDom(status) {
|
||||||
status = status || {};
|
status = status || {};
|
||||||
|
var gatewayActive = !!(status.running || status.reachable);
|
||||||
|
|
||||||
function byId(id) {
|
function byId(id) {
|
||||||
if (root.getElementById) return root.getElementById(id);
|
if (root.getElementById) return root.getElementById(id);
|
||||||
@@ -565,15 +573,40 @@
|
|||||||
else cancelInstallBtn.classList.add("oclm-hidden");
|
else cancelInstallBtn.classList.add("oclm-hidden");
|
||||||
}
|
}
|
||||||
if (openConsoleBtn) {
|
if (openConsoleBtn) {
|
||||||
if (status.running) openConsoleBtn.classList.remove("oclm-hidden");
|
if (gatewayActive) openConsoleBtn.classList.remove("oclm-hidden");
|
||||||
else openConsoleBtn.classList.add("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 ? "打开控制台" : "控制台准备中…");
|
openConsoleBtn.innerHTML = openclawIcon("oclm-button-icon") + (state.consoleReady ? "打开控制台" : "控制台准备中…");
|
||||||
}
|
}
|
||||||
if (noteEl) {
|
if (noteEl) {
|
||||||
if (status.installing) noteEl.classList.remove("oclm-hidden");
|
if (status.installing) noteEl.classList.remove("oclm-hidden");
|
||||||
else noteEl.classList.add("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) {
|
function fieldInput(label, control) {
|
||||||
@@ -954,7 +987,7 @@
|
|||||||
function refreshStatus(done) {
|
function refreshStatus(done) {
|
||||||
request(config.statusUrl).then(function(data) {
|
request(config.statusUrl).then(function(data) {
|
||||||
state.status = data || {};
|
state.status = data || {};
|
||||||
if (state.status && state.status.running) {
|
if (state.status && (state.status.running || state.status.reachable)) {
|
||||||
if (!state.consoleReady) {
|
if (!state.consoleReady) {
|
||||||
pollConsoleReady(20);
|
pollConsoleReady(20);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,6 +353,46 @@ local function get_pid_uptime_human(pid)
|
|||||||
return fmt_elapsed(elapsed)
|
return fmt_elapsed(elapsed)
|
||||||
end
|
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()
|
function action_status()
|
||||||
local sys = require "luci.sys"
|
local sys = require "luci.sys"
|
||||||
local uci = require "luci.model.uci".cursor()
|
local uci = require "luci.model.uci".cursor()
|
||||||
@@ -373,6 +413,11 @@ function action_status()
|
|||||||
running = (st == "running")
|
running = (st == "running")
|
||||||
installed = (st == "running" or st == "stopped")
|
installed = (st == "running" or st == "stopped")
|
||||||
end
|
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 lock_running, lock_pid = installer_lock_running()
|
||||||
local installing = (task.running and (task.op == "install" or task.op == "upgrade")) or 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
|
if not installing and task.running and not installed and not running then
|
||||||
@@ -405,6 +450,9 @@ function action_status()
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
installed = installed,
|
installed = installed,
|
||||||
running = running,
|
running = running,
|
||||||
|
reachable = reachable,
|
||||||
|
reachable_http_code = reachable_code,
|
||||||
|
reachable_url = reachable_url,
|
||||||
task_running = task.running,
|
task_running = task.running,
|
||||||
task_op = task.op,
|
task_op = task.op,
|
||||||
installing = installing,
|
installing = installing,
|
||||||
@@ -423,11 +471,10 @@ function action_status()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function action_ready()
|
function action_ready()
|
||||||
local sys = require "luci.sys"
|
|
||||||
local util = require "luci.util"
|
|
||||||
local uci = require "luci.model.uci".cursor()
|
local uci = require "luci.model.uci".cursor()
|
||||||
|
|
||||||
local port = uci:get("openclawmgr", "main", "port") or "18789"
|
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 ""
|
local base_dir = uci:get("openclawmgr", "main", "base_dir") or ""
|
||||||
|
|
||||||
if not port:match("^%d+$") then port = "18789" end
|
if not port:match("^%d+$") then port = "18789" end
|
||||||
@@ -437,20 +484,13 @@ function action_ready()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local base_url = "http://127.0.0.1:" .. port .. configured_base_path(base_dir)
|
local ready, code, url = probe_gateway_ready(port, base_dir, bind)
|
||||||
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)
|
|
||||||
|
|
||||||
write_json({
|
write_json({
|
||||||
ok = true,
|
ok = true,
|
||||||
ready = ready,
|
ready = ready,
|
||||||
http_code = code,
|
http_code = code,
|
||||||
url = base_url,
|
url = url,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -559,15 +559,14 @@ if selected_env_key and selected_api_key ~= "" then
|
|||||||
end
|
end
|
||||||
cfg.env = next(env) and env or nil
|
cfg.env = next(env) and env or nil
|
||||||
|
|
||||||
local models = {}
|
local models = ensure_table(cfg, "models")
|
||||||
models.mode = "merge"
|
if type(models.mode) ~= "string" or models.mode == "" then
|
||||||
local providers = {}
|
models.mode = "merge"
|
||||||
models.providers = providers
|
end
|
||||||
cfg.models = models
|
local providers = ensure_table(models, "providers")
|
||||||
|
|
||||||
local current_provider_id = os.getenv("DEFAULT_AGENT_NAME") or "anthropic"
|
local current_provider_id = os.getenv("DEFAULT_AGENT_NAME") or "anthropic"
|
||||||
local current_provider = {}
|
local current_provider = ensure_table(providers, current_provider_id)
|
||||||
providers[current_provider_id] = current_provider
|
|
||||||
|
|
||||||
if current_provider_id == "openai" then
|
if current_provider_id == "openai" then
|
||||||
current_provider.api = "openai-completions"
|
current_provider.api = "openai-completions"
|
||||||
@@ -600,13 +599,30 @@ elseif current_provider_id == "moonshot" then
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
local override_base = os.getenv("DEFAULT_AGENT_OVERRIDE_BASE_URL") or ""
|
local override_base = os.getenv("DEFAULT_AGENT_OVERRIDE_BASE_URL") or ""
|
||||||
local base_url_mode = os.getenv("DEFAULT_AGENT_BASE_URL_MODE") or "default"
|
local base_url_mode = os.getenv("DEFAULT_AGENT_BASE_URL_MODE") or "default"
|
||||||
if base_url_mode == "override" and override_base ~= "" then
|
if base_url_mode == "override" and override_base ~= "" then
|
||||||
current_provider.baseUrl = override_base
|
current_provider.baseUrl = override_base
|
||||||
elseif base_url_mode == "default" then
|
elseif base_url_mode == "default" then
|
||||||
current_provider.baseUrl = os.getenv("DEFAULT_AGENT_BASE_URL") or current_provider.baseUrl
|
current_provider.baseUrl = os.getenv("DEFAULT_AGENT_BASE_URL") or current_provider.baseUrl
|
||||||
end
|
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 agents = ensure_table(cfg, "agents")
|
||||||
local defaults = ensure_table(agents, "defaults")
|
local defaults = ensure_table(agents, "defaults")
|
||||||
|
|||||||
@@ -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();
|
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) {
|
function getBg(el) {
|
||||||
if (!el) return null;
|
if (!el) return null;
|
||||||
const style = getComputedStyle(el);
|
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");
|
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();
|
nodes_link = nodes_link.replace(/\t/g, "").replace(/\r\n|\r/g, "\n").replace(/<[^>]*>/g, '').trim();
|
||||||
if (nodes_link != "") {
|
if (nodes_link != "") {
|
||||||
|
nodes_link = decodeIfBase64(nodes_link);
|
||||||
let s = nodes_link.split('://');
|
let s = nodes_link.split('://');
|
||||||
if (s.length > 1) {
|
if (s.length > 1) {
|
||||||
ajax_add_node(nodes_link, group);
|
ajax_add_node(nodes_link, group);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
alert("<%:Please enter the correct link.%>");
|
alert("<%:Please enter the correct link.%>");
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
document.getElementById("nodes_link").focus();
|
document.getElementById("nodes_link").focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,8 +108,7 @@ local api = require "luci.passwall.api"
|
|||||||
function(x, data) {
|
function(x, data) {
|
||||||
if(x && x.status == 200) {
|
if(x && x.status == 200) {
|
||||||
window.location.href = '<%=api.url("node_list")%>';
|
window.location.href = '<%=api.url("node_list")%>';
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
alert("<%:Error%>");
|
alert("<%:Error%>");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -154,8 +168,7 @@ local api = require "luci.passwall.api"
|
|||||||
function(x, data) {
|
function(x, data) {
|
||||||
if (x && x.status == 200) {
|
if (x && x.status == 200) {
|
||||||
window.location.href = '<%=api.url("node_list")%>';
|
window.location.href = '<%=api.url("node_list")%>';
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
alert("<%:Error%>");
|
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 = s.taboption('general', form.Value, 'description', _('Description'));
|
||||||
o.placeholder = 'Samba4 on OpenWrt';
|
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'),
|
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.\
|
_('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!'));
|
Not recommend if multiple clients write to the same files, at the same time!'));
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=pcat-manager
|
PKG_NAME:=pcat-manager
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=2
|
||||||
|
|
||||||
PKG_SOURCE_PROTO:=git
|
PKG_SOURCE_PROTO:=git
|
||||||
PKG_SOURCE_URL:=https://github.com/photonicat/rockchip_rk3568_pcat_manager.git
|
PKG_SOURCE_URL:=https://github.com/photonicat/rockchip_rk3568_pcat_manager.git
|
||||||
PKG_SOURCE_DATE:=2023-11-13
|
PKG_SOURCE_DATE:=2023-11-13
|
||||||
PKG_SOURCE_VERSION:=7f05f163ac864ce323ad648a4c03047915fce496
|
PKG_SOURCE_VERSION:=961edb8b5cbae63efb03ee774d198f6fdb682a45
|
||||||
PKG_MIRROR_HASH:=skip
|
PKG_MIRROR_HASH:=skip
|
||||||
|
|
||||||
PKG_LICENSE:=GPL-3.0-only
|
PKG_LICENSE:=GPL-3.0-only
|
||||||
|
|||||||
Reference in new issue
Block a user