mirror of
https://github.com/baoweise-bot/aimili-vpngate.git
synced 2026-09-04 23:26:47 +08:00
fix: reduce dashboard polling jank
This commit is contained in:
+21
-12
@@ -186,18 +186,27 @@ INSTALL_DIR = "/opt/aimilivpn"
|
||||
LOG_FILE = "/opt/aimilivpn/vpngate_data/vpngate.log"
|
||||
|
||||
def generate_random_password():
|
||||
import random
|
||||
import secrets
|
||||
import string
|
||||
chars = string.ascii_letters + string.digits
|
||||
while True:
|
||||
pwd = "".join(random.choices(chars, k=12))
|
||||
pwd = "".join(secrets.choice(chars) for _ in range(12))
|
||||
if any(c.islower() for c in pwd) and any(c.isupper() for c in pwd) and any(c.isdigit() for c in pwd):
|
||||
return pwd
|
||||
|
||||
def generate_random_suffix():
|
||||
import random
|
||||
import secrets
|
||||
import string
|
||||
return "".join(random.choices(string.ascii_letters + string.digits, k=12))
|
||||
chars = string.ascii_letters + string.digits
|
||||
return "".join(secrets.choice(chars) for _ in range(12))
|
||||
|
||||
def get_app_version():
|
||||
try:
|
||||
with open(os.path.join(INSTALL_DIR, "VERSION"), "r", encoding="utf-8") as f:
|
||||
version = f.read().strip().lstrip("vV")
|
||||
return version or "2.1.0"
|
||||
except Exception:
|
||||
return "2.1.0"
|
||||
|
||||
def load_ui_cfg():
|
||||
import json
|
||||
@@ -406,7 +415,7 @@ def print_status():
|
||||
openvpn_status = f"{green}[已连接]{reset}" if openvpn_ok else f"{red}[未连接]{reset}"
|
||||
|
||||
print_line("=======================================================")
|
||||
print_line(f" {bold}AimiliVPN 管理终端 v2.0{reset} ")
|
||||
print_line(f" {bold}AimiliVPN 管理终端 v{get_app_version()}{reset} ")
|
||||
print_line("=======================================================")
|
||||
print_line("【核心服务状态】")
|
||||
print_line(format_line(f"代理网关 (Port {proxy_port})", gateway_status))
|
||||
@@ -728,7 +737,7 @@ def configure_credentials():
|
||||
new_uname = input(f"请输入新管理账号 (回车默认 {curr_uname}): ").strip()
|
||||
if not new_uname:
|
||||
new_uname = curr_uname
|
||||
new_pwd = input("请输入新管理密码 (不能为空): ").strip()
|
||||
new_pwd = input("请输入新管理密码 (不能为空): ")
|
||||
if not new_pwd:
|
||||
print("错误: 密码不能为空!")
|
||||
time.sleep(2)
|
||||
@@ -965,22 +974,22 @@ if [ ! -f "$AUTH_FILE" ]; then
|
||||
# Initialize defaults
|
||||
UI_PORT=8787
|
||||
# generate random secret suffix (12 chars alphanumeric)
|
||||
SECRET_PATH=$(python3 -c "import random, string; print(''.join(random.choices(string.ascii_letters + string.digits, k=12)))")
|
||||
SECRET_PATH=$(python3 -c "import secrets, string; chars = string.ascii_letters + string.digits; print(''.join(secrets.choice(chars) for _ in range(12)))")
|
||||
# generate random password
|
||||
UI_PASSWORD=$(python3 -c "
|
||||
import random, string
|
||||
import secrets, string
|
||||
chars = string.ascii_letters + string.digits
|
||||
while True:
|
||||
pwd = ''.join(random.choices(chars, k=12))
|
||||
pwd = ''.join(secrets.choice(chars) for _ in range(12))
|
||||
if any(c.islower() for c in pwd) and any(c.isupper() for c in pwd) and any(c.isdigit() for c in pwd):
|
||||
print(pwd)
|
||||
break
|
||||
")
|
||||
UI_USERNAME=$(python3 -c "
|
||||
import random, string
|
||||
import secrets, string
|
||||
chars = string.ascii_letters + string.digits
|
||||
while True:
|
||||
uname = ''.join(random.choices(chars, k=12))
|
||||
uname = ''.join(secrets.choice(chars) for _ in range(12))
|
||||
if uname[0].isalpha() and any(c.islower() for c in uname) and any(c.isupper() for c in uname) and any(c.isdigit() for c in uname):
|
||||
print(uname)
|
||||
break
|
||||
@@ -1096,7 +1105,7 @@ elif command -v rc-service >/dev/null 2>&1; then
|
||||
fi
|
||||
|
||||
# Wait and poll for node loading and active connection
|
||||
echo -e "\n正在等待 AimiliVPN 首次获取节点并建立加密通道 (此过程可能需要 5-30 秒)..."
|
||||
echo -e "\n正在等待 AimiliVPN 首次获取节点并建立加密通道 (此过程可能需要 5-90 秒)..."
|
||||
ACTIVE_ID=""
|
||||
LAST_MSG=""
|
||||
for i in {1..90}; do
|
||||
|
||||
@@ -402,9 +402,13 @@ class ManagerLogicTests(unittest.TestCase):
|
||||
self.assertIn("@media (prefers-reduced-motion: reduce)", manager.LOGIN_HTML)
|
||||
self.assertIn("@media (prefers-reduced-motion: reduce)", manager.INDEX_HTML)
|
||||
self.assertIn("const pageSize = 50;", manager.INDEX_HTML)
|
||||
self.assertIn('id="pagination_container"', manager.INDEX_HTML)
|
||||
self.assertIn('paginationContainer.style.display = totalPages > 1 ? "flex" : "none";', manager.INDEX_HTML)
|
||||
self.assertIn("const MAX_RENDERED_LOG_LINES = 300;", manager.INDEX_HTML)
|
||||
self.assertIn("nodesRequestPromise", manager.INDEX_HTML)
|
||||
self.assertIn("backgroundPollInFlight", manager.INDEX_HTML)
|
||||
self.assertIn('let lastNodesSnapshotSignature = "";', manager.INDEX_HTML)
|
||||
self.assertIn("if (signature === lastNodesSnapshotSignature) return false;", manager.INDEX_HTML)
|
||||
self.assertIn('typeof document.hidden !== "boolean" || !document.hidden', manager.INDEX_HTML)
|
||||
self.assertEqual(500, manager.WEB_LOG_MAX_ENTRIES)
|
||||
|
||||
@@ -476,6 +480,16 @@ class ManagerLogicTests(unittest.TestCase):
|
||||
self.assertNotIn("origin/master", install_text)
|
||||
self.assertNotIn("bate", install_text.lower())
|
||||
|
||||
def test_installer_uses_secure_credentials_and_current_version(self) -> None:
|
||||
install_text = (manager.ROOT_DIR / "install.sh").read_text(encoding="utf-8")
|
||||
|
||||
self.assertNotIn("random.choices", install_text)
|
||||
self.assertIn("secrets.choice", install_text)
|
||||
self.assertIn('get_app_version()', install_text)
|
||||
self.assertNotIn("管理终端 v2.0", install_text)
|
||||
self.assertIn("5-90 秒", install_text)
|
||||
self.assertIn('new_pwd = input("请输入新管理密码 (不能为空): ")', install_text)
|
||||
|
||||
def test_latest_release_check_ignores_non_version_name_text(self) -> None:
|
||||
release = {
|
||||
"tag_name": "v2.2.0",
|
||||
|
||||
+13
-3
@@ -4135,7 +4135,7 @@ INDEX_HTML = r"""<!doctype html>
|
||||
</div>
|
||||
|
||||
<!-- 分页控制栏 -->
|
||||
<div class="pagination-container" style="padding: 16px; display: none; justify-content: space-between; align-items: center; border-top: 1px solid var(--border-color); flex-wrap: wrap; gap: 12px;">
|
||||
<div id="pagination_container" class="pagination-container" style="padding: 16px; display: none; justify-content: space-between; align-items: center; border-top: 1px solid var(--border-color); flex-wrap: wrap; gap: 12px;">
|
||||
<div style="font-size: 13px; color: var(--text-secondary);">
|
||||
显示第 <span id="page_start" style="color: var(--text-primary); font-weight:600;">0</span> - <span id="page_end" style="color: var(--text-primary); font-weight:600;">0</span> 条,共 <span id="filtered_count" style="color: var(--text-primary); font-weight:600;">0</span> 条备选节点
|
||||
</div>
|
||||
@@ -4441,6 +4441,7 @@ let selectedDiscoveryCountries = new Set();
|
||||
let discoveryCountriesInitialized = false;
|
||||
let discoveryCountriesDirty = false;
|
||||
let countryFilterSignature = "";
|
||||
let lastNodesSnapshotSignature = "";
|
||||
|
||||
const $=id=>document.getElementById(id);
|
||||
const esc=s=>String(s||"").replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||||
@@ -4968,6 +4969,8 @@ function render(){
|
||||
|
||||
// Pagination calculation
|
||||
const totalPages = Math.ceil(shown.length / pageSize) || 1;
|
||||
const paginationContainer = $("pagination_container");
|
||||
if (paginationContainer) paginationContainer.style.display = totalPages > 1 ? "flex" : "none";
|
||||
if (currentPage > totalPages) currentPage = totalPages;
|
||||
if (currentPage < 1) currentPage = 1;
|
||||
|
||||
@@ -5132,11 +5135,18 @@ async function fetchNodesSnapshot() {
|
||||
}
|
||||
|
||||
function applyNodesSnapshot(data) {
|
||||
nodes = Array.isArray(data.nodes) ? data.nodes : [];
|
||||
state = data.state || {};
|
||||
const nextNodes = Array.isArray(data && data.nodes) ? data.nodes : [];
|
||||
const nextState = data && data.state ? data.state : {};
|
||||
const signature = JSON.stringify([nextNodes, nextState]);
|
||||
if (signature === lastNodesSnapshotSignature) return false;
|
||||
|
||||
lastNodesSnapshotSignature = signature;
|
||||
nodes = nextNodes;
|
||||
state = nextState;
|
||||
stableSortNodes();
|
||||
updateCountryFilter();
|
||||
render();
|
||||
return true;
|
||||
}
|
||||
|
||||
function refreshButtonBusy(message = "正在后台更新...") {
|
||||
|
||||
Reference in New Issue
Block a user