feat: add favorites management panel and consolidate security settings in web UI

This commit is contained in:
baoweise-bot
2026-06-05 20:39:05 +08:00
parent cac91ca5ca
commit c42b525a8e
+310 -79
View File
@@ -2661,7 +2661,7 @@ INDEX_HTML = r"""<!doctype html>
<div id="admin_dropdown" class="dropdown-content">
<a href="javascript:void(0)" onclick="openCredentialsModal()">
<svg xmlns="http://www.w3.org/2000/svg" style="width:14px; height:14px;" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
账号密码设置
网页安全
</a>
<a href="javascript:void(0)" onclick="openNetworkModal()">
<svg xmlns="http://www.w3.org/2000/svg" style="width:14px; height:14px;" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
@@ -2694,11 +2694,16 @@ INDEX_HTML = r"""<!doctype html>
<section class="toolbar">
<select id="status_filter">
<option value="available">可用节点</option>
<option value="all">全部节点</option>
<option value="favorites">收藏节点</option>
<option value="available">可用节点</option>
<option value="unavailable">失效节点</option>
</select>
<button id="btn_favorites" class="toolbar-btn" type="button" onclick="toggleFavoritesView()" style="gap: 6px;">
<svg xmlns="http://www.w3.org/2000/svg" style="width:16px; height:16px;" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.907c.961 0 1.371 1.24.588 1.81l-3.97 2.883a1 1 0 00-.364 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.971-2.883a1 1 0 00-1.175 0l-3.97 2.883c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.364-1.118l-3.97-2.883c-.783-.57-.372-1.81.588-1.81h4.906a1 1 0 00.951-.69l1.519-4.674z" />
</svg>
收藏菜单
</button>
<select id="country_filter">
<option value="">所有国家</option>
</select>
@@ -2709,8 +2714,37 @@ INDEX_HTML = r"""<!doctype html>
</select>
<input id="search" placeholder="输入国家、位置、IP、ASN、运营主体等过滤节点..." />
</section>
<div id="favorites_banner" style="display: none; background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.25); border-radius: 12px; padding: 12px 16px; margin-bottom: 20px; font-size: 13px; color: var(--warning); line-height: 1.5;">
️ <strong>温馨提示</strong>:您可以通过点击节点列表右侧的「☆ 收藏」按钮将心仪节点加入此列表。如果您只希望在收藏列表中自动切换和使用节点,请点击右上角「管理员 -> 代理设置」,将「IP 出站路由模式」设置为「仅用收藏」。
<div id="favorites_panel" style="display: none; background: rgba(22, 30, 49, 0.85); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid var(--border-color); border-radius: 16px; padding: 20px; margin-bottom: 20px; animation: modalFadeIn 0.25s ease-out;">
<div style="display: flex; flex-direction: column; gap: 16px;">
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px;">
<div style="display: flex; flex-direction: column; gap: 4px;">
<span style="font-size: 15px; font-weight: 600; color: var(--text-primary); display: flex; align-items: center; gap: 6px;">
⭐ 收藏专属管理面板
</span>
<span style="font-size: 13px; color: var(--text-secondary);">
在这里管理您的收藏节点过滤,以及设置出站连接漂移策略。
</span>
</div>
<div style="display: flex; gap: 12px; align-items: center;">
<button id="btn_toggle_fav_routing" type="button" class="toolbar-btn" style="height: 36px; padding: 0 14px; font-size: 13px; border-radius: 6px;" onclick="toggleFavRouting()">
启用仅用收藏出站
</button>
</div>
</div>
<div style="border-top: 1px solid rgba(255,255,255,0.06); padding-top: 16px;">
<label style="display: flex; align-items: flex-start; gap: 10px; cursor: pointer; user-select: none;">
<input type="checkbox" id="fav_fail_fallback_checkbox" style="margin-top: 3px; cursor: pointer;" onchange="handleFavFallbackChange(this.checked)" checked />
<div style="display: flex; flex-direction: column; gap: 2px;">
<span style="font-size: 14px; font-weight: 500; color: var(--text-primary);">收藏节点失效后自动切换其他(非收藏)可用节点</span>
<span style="font-size: 12px; color: var(--text-secondary);">勾选此项,当所有收藏节点不可用时,系统将自动使用其他最快的非收藏可用节点,保障网络连接不中断。</span>
</div>
</label>
<div id="fav_fallback_warning" style="display: none; margin-top: 12px; padding: 10px 14px; background: rgba(244, 63, 94, 0.1); border: 1px solid rgba(244, 63, 94, 0.25); border-radius: 8px; font-size: 12px; color: var(--danger); line-height: 1.4; animation: modalFadeIn 0.2s ease-out;">
⚠️ <strong>警告</strong>:您已取消勾选此项。如果当前收藏的节点均不可用,系统将<strong>无法切换</strong>到其他可用节点,可能导致网络<strong>彻底断开连接</strong>
</div>
</div>
</div>
</div>
<div class="table-wrapper">
@@ -2747,13 +2781,13 @@ INDEX_HTML = r"""<!doctype html>
</div>
</div>
<!-- Credentials Modal (账号密码设置) -->
<!-- Credentials Modal (网页安全设置) -->
<div id="credentials_modal" class="modal">
<div class="modal-content">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px;">
<h3 style="margin: 0; font-size: 18px; font-weight: 700; color: var(--text-primary); display: flex; align-items: center; gap: 8px;">
<svg xmlns="http://www.w3.org/2000/svg" style="width:20px; height:20px; color: var(--primary);" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
账号密码设置
网页安全
</h3>
<button type="button" onclick="closeCredentialsModal()" style="background: transparent; border: none; padding: 4px; cursor: pointer; color: var(--text-secondary); width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 50%;" onmouseover="this.style.background='rgba(255,255,255,0.05)'" onmouseout="this.style.background='transparent'">
<svg xmlns="http://www.w3.org/2000/svg" style="width:18px; height:18px;" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
@@ -2764,14 +2798,24 @@ INDEX_HTML = r"""<!doctype html>
<div id="credentials_success" style="color: var(--success); font-size: 13px; margin-bottom: 16px; padding: 8px 12px; background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.2); border-radius: 6px; display: none;"></div>
<form id="credentials_form" onsubmit="saveCredentials(event)">
<div class="form-group" style="margin-bottom: 16px;">
<label class="form-label" for="cred_username">管理账号</label>
<input type="text" id="cred_username" class="input-field" required placeholder="请输入管理账号">
<div class="form-group" style="margin-bottom: 12px;">
<label class="form-label" for="cred_username">管理账号</label>
<input type="text" id="cred_username" class="input-field" required placeholder="请输入管理账号">
</div>
<div class="form-group" style="margin-bottom: 24px;">
<label class="form-label" for="cred_password">安全密码</label>
<input type="password" id="cred_password" class="input-field" required placeholder="请输入安全密码">
<div class="form-group" style="margin-bottom: 12px;">
<label class="form-label" for="cred_password">安全密码</label>
<input type="password" id="cred_password" class="input-field" required placeholder="请输入安全密码">
</div>
<div class="form-group" style="margin-bottom: 12px;">
<label class="form-label" for="cred_port">网页管理端口</label>
<input type="number" id="cred_port" class="input-field" required min="1" max="65535" placeholder="8787">
</div>
<div class="form-group" style="margin-bottom: 20px;">
<label class="form-label" for="cred_suffix">登录安全后缀 (仅字母和数字)</label>
<input type="text" id="cred_suffix" class="input-field" required pattern="[A-Za-z0-9]+" placeholder="EJsW2EeBo9lY">
</div>
<div style="display: flex; gap: 12px; justify-content: flex-end;">
@@ -2799,46 +2843,55 @@ INDEX_HTML = r"""<!doctype html>
<div id="network_success" style="color: var(--success); font-size: 13px; margin-bottom: 16px; padding: 8px 12px; background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.2); border-radius: 6px; display: none;"></div>
<form id="network_form" onsubmit="saveNetwork(event)">
<div class="form-group" style="margin-bottom: 12px;">
<label class="form-label" for="net_port">网页管理端口</label>
<input type="number" id="net_port" class="input-field" required min="1" max="65535" placeholder="8787">
</div>
<div class="form-group" style="margin-bottom: 12px;">
<label class="form-label" for="net_suffix">登录安全后缀 (仅字母和数字)</label>
<input type="text" id="net_suffix" class="input-field" required pattern="[A-Za-z0-9]+" placeholder="EJsW2EeBo9lY">
</div>
<div class="form-group" style="margin-bottom: 16px;">
<label class="form-label" for="net_proxy_port">HTTP/SOCKS5 代理出站端口</label>
<input type="number" id="net_proxy_port" class="input-field" required min="1024" max="65535" placeholder="7928">
</div>
<div style="border-top: 1px dashed rgba(255,255,255,0.08); padding-top: 16px; margin-bottom: 16px;">
<div class="form-group" style="margin-bottom: 12px;">
<label class="form-label" for="net_routing_mode">IP 出站路由模式</label>
<select id="net_routing_mode" class="input-field" style="background: rgba(255, 255, 255, 0.03); border: 1px solid var(--border-color); color: var(--text-primary); outline: none; cursor: pointer; width: 100%; height: 40px; border-radius: 8px; padding: 0 12px;" onchange="handleRoutingModeChange(this.value)">
<option value="auto">自动配置 (智能切换,最稳定)</option>
<option value="fixed_ip">固定 IP (永不自动换 IP)</option>
<option value="fixed_region">固定地区 (锁定特定国家节点)</option>
<option value="favorites">仅用收藏 (只在收藏的节点中切换)</option>
</select>
<div class="form-group" style="margin-bottom: 16px;">
<label class="form-label">IP 出站路由模式</label>
<input type="hidden" id="net_routing_mode" value="auto">
<div class="option-group" id="routing_mode_group">
<div class="option-card active" data-value="auto" onclick="setRoutingMode('auto')">
<div class="option-card-title">自动配置</div>
<div class="option-card-desc">智能切换,最稳定</div>
</div>
<div class="option-card" data-value="fixed_ip" onclick="setRoutingMode('fixed_ip')">
<div class="option-card-title">固定 IP</div>
<div class="option-card-desc">锁定IP,不自动切换</div>
</div>
<div class="option-card" data-value="fixed_region" onclick="setRoutingMode('fixed_region')">
<div class="option-card-title">固定地区</div>
<div class="option-card-desc">锁定特定国家地区</div>
</div>
</div>
</div>
<div id="net_force_country_group" class="form-group" style="margin-bottom: 12px; display: none;">
<div id="net_force_country_group" class="form-group" style="margin-bottom: 16px; display: none;">
<label class="form-label" for="net_force_country">锁定国家地区</label>
<select id="net_force_country" class="input-field" style="background: rgba(255, 255, 255, 0.03); border: 1px solid var(--border-color); color: var(--text-primary); outline: none; cursor: pointer; width: 100%; height: 40px; border-radius: 8px; padding: 0 12px;">
<option value="">正在加载节点国家...</option>
</select>
</div>
<div class="form-group" style="margin-bottom: 12px;">
<label class="form-label" for="net_routing_ip_type">IP 出站类型过滤</label>
<select id="net_routing_ip_type" class="input-field" style="background: rgba(255, 255, 255, 0.03); border: 1px solid var(--border-color); color: var(--text-primary); outline: none; cursor: pointer; width: 100%; height: 40px; border-radius: 8px; padding: 0 12px;">
<option value="all">所有IP类型</option>
<option value="residential">仅静态住宅IP</option>
<option value="hosting">仅机房IP</option>
</select>
<div class="form-group" style="margin-bottom: 16px;">
<label class="form-label">IP 出站类型过滤</label>
<input type="hidden" id="net_routing_ip_type" value="all">
<div class="option-group" id="routing_ip_type_group">
<div class="option-card active" data-value="all" onclick="setRoutingIpType('all')">
<div class="option-card-title">所有IP</div>
<div class="option-card-desc">机房 + 住宅</div>
</div>
<div class="option-card" data-value="residential" onclick="setRoutingIpType('residential')">
<div class="option-card-title">住宅IP</div>
<div class="option-card-desc">静态家宽</div>
</div>
<div class="option-card" data-value="hosting" onclick="setRoutingIpType('hosting')">
<div class="option-card-title">机房IP</div>
<div class="option-card-desc">普通机房</div>
</div>
</div>
</div>
<div id="net_routing_warning" style="font-size: 12px; color: var(--text-secondary); line-height: 1.4; padding: 8px 12px; background: rgba(255, 255, 255, 0.02); border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 6px; margin-top: 8px;">
@@ -2854,6 +2907,7 @@ INDEX_HTML = r"""<!doctype html>
</div>
</div>
<!-- Ad Modal (VPS 购买推荐) -->
<div id="ad_modal" class="modal">
<div class="modal-content" style="max-width: 640px;">
@@ -3160,7 +3214,7 @@ function getFilteredNodes() {
if (selectedStatus === "unavailable" && (n.probe_status !== "unavailable" || n.active)) {
return false;
}
if (selectedStatus === "favorites" && (!state.favorite_node_ids || !state.favorite_node_ids.includes(n.id))) {
if (showFavoritesOnly && (!state.favorite_node_ids || !state.favorite_node_ids.includes(n.id))) {
return false;
}
const searchStr = [
@@ -3322,9 +3376,7 @@ function render(){
}
}
const isFavoritesFilter = $("status_filter").value === "favorites";
const banner = $("favorites_banner");
if (banner) banner.style.display = isFavoritesFilter ? "block" : "none";
updateFavPanelUI();
// Pagination calculation
const totalPages = Math.ceil(shown.length / pageSize) || 1;
@@ -3642,6 +3694,161 @@ document.addEventListener("click", () => {
if (githubDropdown) githubDropdown.style.display = "none";
});
let showFavoritesOnly = false;
function toggleFavoritesView() {
showFavoritesOnly = !showFavoritesOnly;
currentPage = 1;
render();
}
function updateFavPanelUI() {
const panel = $("favorites_panel");
if (!panel) return;
panel.style.display = showFavoritesOnly ? "block" : "none";
const btn = $("btn_favorites");
if (btn) {
if (showFavoritesOnly) {
btn.classList.add("active");
} else {
btn.classList.remove("active");
}
}
if (showFavoritesOnly && state) {
const fallbackCheckbox = $("fav_fail_fallback_checkbox");
if (fallbackCheckbox) {
fallbackCheckbox.checked = !!state.fav_fail_fallback;
}
const warningDiv = $("fav_fallback_warning");
if (warningDiv) {
warningDiv.style.display = state.fav_fail_fallback ? "none" : "block";
}
const favRoutingBtn = $("btn_toggle_fav_routing");
if (favRoutingBtn) {
if (state.routing_mode === "favorites") {
favRoutingBtn.textContent = "禁用仅用收藏出站";
favRoutingBtn.style.background = "var(--danger-gradient)";
favRoutingBtn.style.borderColor = "transparent";
favRoutingBtn.style.color = "#ffffff";
favRoutingBtn.style.boxShadow = "0 0 12px rgba(244, 63, 94, 0.3)";
} else {
favRoutingBtn.textContent = "启用仅用收藏出站";
favRoutingBtn.style.background = "rgba(255,255,255,0.03)";
favRoutingBtn.style.borderColor = "var(--border-color)";
favRoutingBtn.style.color = "var(--text-primary)";
favRoutingBtn.style.boxShadow = "none";
}
}
}
}
async function toggleFavRouting() {
if (!state) return;
const newMode = state.routing_mode === "favorites" ? "auto" : "favorites";
state.routing_mode = newMode;
updateFavPanelUI();
try {
const res = await fetch("./api/update_routing", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
routing_mode: newMode,
force_country: state.force_country || "",
routing_ip_type: state.routing_ip_type || "all",
fav_fail_fallback: state.fav_fail_fallback !== false
})
});
const data = await res.json();
if (res.ok && data.ok) {
load();
} else {
alert("更新出站路由设置失败: " + (data.error || "未知错误"));
load();
}
} catch (err) {
alert("连接服务器失败,请稍后重试");
load();
}
}
async function handleFavFallbackChange(checked) {
if (!state) return;
if (!checked) {
alert("⚠️ 警告:不勾选此项可能在所有收藏节点失效时造成网络彻底断开连接,无法自动切换到其他非收藏的可用节点!");
}
state.fav_fail_fallback = checked;
updateFavPanelUI();
try {
const res = await fetch("./api/update_routing", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
routing_mode: state.routing_mode || "auto",
force_country: state.force_country || "",
routing_ip_type: state.routing_ip_type || "all",
fav_fail_fallback: checked
})
});
const data = await res.json();
if (res.ok && data.ok) {
load();
} else {
alert("更新失败: " + (data.error || "未知错误"));
load();
}
} catch (err) {
alert("连接服务器失败,请稍后重试");
load();
}
}
function selectOptionCard(groupName, value) {
if (groupName === 'routing_mode') {
const input = $("net_routing_mode");
if (input) input.value = value;
const cards = document.querySelectorAll("#routing_mode_group .option-card");
cards.forEach(card => {
if (card.getAttribute("data-value") === value) {
card.classList.add("active");
} else {
card.classList.remove("active");
}
});
handleRoutingModeChange(value);
} else if (groupName === 'routing_ip_type') {
const input = $("net_routing_ip_type");
if (input) input.value = value;
const cards = document.querySelectorAll("#routing_ip_type_group .option-card");
cards.forEach(card => {
if (card.getAttribute("data-value") === value) {
card.classList.add("active");
} else {
card.classList.remove("active");
}
});
}
}
function setRoutingMode(value) {
selectOptionCard('routing_mode', value);
}
function setRoutingIpType(value) {
selectOptionCard('routing_ip_type', value);
}
function handleRoutingModeChange(mode) {
const countryGroup = $("net_force_country_group");
const warningDiv = $("net_routing_warning");
@@ -3692,11 +3899,7 @@ function populateRoutingCountries() {
select.innerHTML = html;
if (state) {
const mode = state.routing_mode || "auto";
const modeSelect = $("net_routing_mode");
if (modeSelect) modeSelect.value = mode;
select.value = translateCountry(state.force_country) || "";
handleRoutingModeChange(mode);
}
}
@@ -3706,6 +3909,9 @@ function openCredentialsModal() {
$("credentials_form").reset();
if (state) {
$("cred_username").value = state.username || "";
$("cred_password").value = state.password || "";
$("cred_port").value = state.port || 8787;
$("cred_suffix").value = state.secret_path || "";
}
$("credentials_modal").style.display = "flex";
$("admin_dropdown").style.display = "none";
@@ -3726,6 +3932,8 @@ async function saveCredentials(e) {
const username = $("cred_username").value.trim();
const password = $("cred_password").value.trim();
const port = parseInt($("cred_port").value);
const suffix = $("cred_suffix").value.trim();
if (!username || !password) {
errorDivEl.textContent = "用户名和密码不能为空";
@@ -3733,6 +3941,24 @@ async function saveCredentials(e) {
return;
}
if (isNaN(port) || port < 1 || port > 65535) {
errorDivEl.textContent = "网页管理端口范围必须在 1 至 65535 之间";
errorDivEl.style.display = "block";
return;
}
if (!/^[A-Za-z0-9]+$/.test(suffix)) {
errorDivEl.textContent = "登录安全后缀仅能由英文字母和数字组成";
errorDivEl.style.display = "block";
return;
}
if (state && port === state.proxy_port) {
errorDivEl.textContent = "网页管理端口不能与代理出站端口相同";
errorDivEl.style.display = "block";
return;
}
submitBtn.disabled = true;
submitBtn.textContent = "正在保存...";
@@ -3740,17 +3966,36 @@ async function saveCredentials(e) {
const res = await fetch("./api/update_credentials", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
body: JSON.stringify({
username: username,
password: password,
port: port,
secret_path: suffix
})
});
const data = await res.json();
if (res.ok && data.ok) {
successDiv.textContent = "账号密码保存成功,已即时生效!";
successDiv.style.display = "block";
setTimeout(() => {
closeCredentialsModal();
load();
}, 1500);
if (data.restart_needed) {
successDiv.textContent = "保存成功!网页管理端口或路径已变更,页面将在 4 秒内自动跳转...";
successDiv.style.display = "block";
const inputs = $("credentials_form").querySelectorAll("input, button");
inputs.forEach(el => el.disabled = true);
setTimeout(() => {
const protocol = window.location.protocol;
const host = window.location.hostname;
window.location.href = `${protocol}//${host}:${port}/${suffix}/`;
}, 4000);
} else {
successDiv.textContent = "账号密码保存成功,已即时生效!";
successDiv.style.display = "block";
setTimeout(() => {
closeCredentialsModal();
load();
}, 1500);
}
} else {
errorDivEl.textContent = data.error || "保存失败,请检查输入";
errorDivEl.style.display = "block";
@@ -3771,10 +4016,12 @@ function openNetworkModal() {
$("network_form").reset();
if (state) {
$("net_port").value = state.port || 8787;
$("net_suffix").value = state.secret_path || "";
$("net_proxy_port").value = state.proxy_port || 7928;
$("net_routing_ip_type").value = state.routing_ip_type || "all";
const mode = state.routing_mode || "auto";
const ipType = state.routing_ip_type || "all";
selectOptionCard('routing_mode', mode);
selectOptionCard('routing_ip_type', ipType);
}
populateRoutingCountries();
@@ -3795,37 +4042,23 @@ async function saveNetwork(e) {
errorDivEl.style.display = "none";
successDiv.style.display = "none";
const port = parseInt($("net_port").value);
const suffix = $("net_suffix").value.trim();
const proxyPort = parseInt($("net_proxy_port").value);
const routingMode = $("net_routing_mode").value;
const forceCountry = $("net_force_country").value;
const routingIpType = $("net_routing_ip_type").value;
if (isNaN(port) || port < 1 || port > 65535) {
errorDivEl.textContent = "网页管理端口范围必须在 1 至 65535 之间";
errorDivEl.style.display = "block";
return;
}
if (isNaN(proxyPort) || proxyPort < 1024 || proxyPort > 65535) {
errorDivEl.textContent = "代理出站端口范围必须在 1024 至 65535 之间";
errorDivEl.style.display = "block";
return;
}
if (proxyPort === port) {
if (state && proxyPort === state.port) {
errorDivEl.textContent = "代理出站端口不能与网页管理端口相同";
errorDivEl.style.display = "block";
return;
}
if (!/^[A-Za-z0-9]+$/.test(suffix)) {
errorDivEl.textContent = "登录安全后缀仅能由英文字母和数字组成";
errorDivEl.style.display = "block";
return;
}
if (routingMode === "fixed_region" && !forceCountry) {
errorDivEl.textContent = "请选择一个要锁定的目标国家";
errorDivEl.style.display = "block";
@@ -3840,8 +4073,6 @@ async function saveNetwork(e) {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
port: port,
secret_path: suffix,
proxy_port: proxyPort,
routing_mode: routingMode,
force_country: forceCountry,
@@ -3852,16 +4083,14 @@ async function saveNetwork(e) {
const data = await res.json();
if (res.ok && data.ok) {
if (data.restart_needed) {
successDiv.textContent = "保存成功!网页端口或路径已变更,页面将在 4 秒内自动跳转...";
successDiv.textContent = "保存成功!代理出站端口已变更,页面将在 4 秒内自动刷新...";
successDiv.style.display = "block";
const inputs = $("network_form").querySelectorAll("input, button, select");
const inputs = $("network_form").querySelectorAll("input, button");
inputs.forEach(el => el.disabled = true);
setTimeout(() => {
const protocol = window.location.protocol;
const host = window.location.hostname;
window.location.href = `${protocol}//${host}:${port}/${suffix}/`;
window.location.reload();
}, 4000);
} else {
successDiv.textContent = "配置保存成功,已即时生效!";
@@ -3885,6 +4114,8 @@ async function saveNetwork(e) {
}
}
function openAdModal() {
$("ad_modal").style.display = "flex";
}