diff --git a/frontend/admin/app.js b/frontend/admin/app.js
index c325c15..9ab72fb 100644
--- a/frontend/admin/app.js
+++ b/frontend/admin/app.js
@@ -45,20 +45,20 @@
};
const ROUTE_META = {
- overview: { title: "总览", description: "查看收入、用户和流量概况。" },
- "dashboard-node": { title: "节点状态", description: "查看节点在线状态、负载和推送情况。" },
- "node-manage": { title: "节点管理", description: "管理服务节点、可见性以及父子节点关系。" },
- "node-group": { title: "权限组", description: "管理节点权限组和用户分组映射。" },
- "node-route": { title: "路由规则", description: "维护节点路由匹配规则。" },
- "plan-manage": { title: "套餐管理", description: "维护套餐、流量和价格配置。" },
- "order-manage": { title: "订单管理", description: "处理待支付和已支付订单。" },
- "coupon-manage": { title: "优惠券", description: "创建和维护优惠券信息。" },
- "user-manage": { title: "用户管理", description: "查看用户订阅、流量和封禁状态。" },
- "ticket-manage": { title: "工单中心", description: "查看用户工单和处理状态。" },
- realname: { title: "实名认证", description: "审核实名记录和同步状态。" },
- "user-online-devices": { title: "在线设备", description: "查看用户在线 IP 和设备分布。" },
- "user-ipv6-subscription": { title: "IPv6 子账号", description: "管理 IPv6 阴影账号与密码同步。" },
- "system-config": { title: "系统设置", description: "编辑站点、订阅和安全参数。" }
+ overview: { title: '总览', description: '查看收入、用户和流量概况。' },
+ 'dashboard-node': { title: '节点状态', description: '查看节点在线状态、负载和推送情况。' },
+ 'node-manage': { title: '节点管理', description: '管理服务节点、可见性以及父子节点关系。' },
+ 'node-group': { title: '权限组', description: '管理节点权限组和用户分组映射。' },
+ 'node-route': { title: '路由规则', description: '维护节点路由匹配规则。' },
+ 'plan-manage': { title: '套餐管理', description: '维护套餐、流量和价格配置。' },
+ 'order-manage': { title: '订单管理', description: '处理待支付和已支付订单。' },
+ 'coupon-manage': { title: '优惠券', description: '创建和维护优惠券信息。' },
+ 'user-manage': { title: '用户管理', description: '查看用户订阅、流量和封禁状态。' },
+ 'ticket-manage': { title: '工单中心', description: '查看用户工单和处理状态。' },
+ realname: { title: '实名认证', description: '审核实名记录和同步状态。' },
+ 'user-online-devices': { title: '在线设备', description: '查看用户在线 IP 和设备分布。' },
+ 'user-ipv6-subscription': { title: 'IPv6 子账号', description: '管理 IPv6 阴影账号与密码同步。' },
+ 'system-config': { title: '系统设置', description: '编辑站点、订阅和安全参数。' }
};
state.route = normalizeRoute(readRoute());
@@ -66,14 +66,14 @@
boot();
async function boot() {
- window.addEventListener("hashchange", async function () {
+ window.addEventListener('hashchange', async function () {
state.route = normalizeRoute(readRoute());
state.modal = null;
await hydrateRoute();
});
- root.addEventListener("click", onClick);
- root.addEventListener("submit", onSubmit);
+ root.addEventListener('click', onClick);
+ root.addEventListener('submit', onSubmit);
// Initialize shell and modal containers
root.innerHTML = '
';
@@ -94,7 +94,7 @@
async function loadBootstrap() {
try {
setBusy(true);
- const loginCheck = unwrap(await request("/api/v1/user/checkLogin", { method: "GET" }));
+ const loginCheck = unwrap(await request('/api/v1/user/checkLogin', { method: 'GET' }));
if (!loginCheck || !loginCheck.is_admin) {
clearSession();
return;
@@ -102,15 +102,15 @@
state.user = loginCheck;
const [config, system] = await Promise.all([
- request(cfg.api.adminConfig, { method: "GET" }),
- request(cfg.api.systemStatus, { method: "GET" })
+ request(cfg.api.adminConfig, { method: 'GET' }),
+ request(cfg.api.systemStatus, { method: 'GET' })
]);
state.config = unwrap(config) || {};
state.system = unwrap(system) || {};
} catch (error) {
- console.error("bootstrap failed", error);
+ console.error('bootstrap failed', error);
clearSession();
- show(error.message || "管理端初始化失败", "error");
+ show(error.message || '管理端初始化失败', 'error');
} finally {
setBusy(false);
render();
@@ -127,23 +127,23 @@
setBusy(true);
const page = getCurrentPage();
- if (state.route === "overview") {
+ if (state.route === 'overview') {
state.dashboard = unwrap(await request(cfg.api.dashboardSummary));
- } else if (state.route === "dashboard-node") {
+ } else if (state.route === 'dashboard-node') {
const [dashboard, nodes] = await Promise.all([
request(cfg.api.dashboardSummary),
request(cfg.api.serverNodes)
]);
state.dashboard = unwrap(dashboard) || {};
state.nodes = toArray(unwrap(nodes));
- } else if (state.route === "node-manage") {
+ } else if (state.route === 'node-manage') {
const [nodes, groups] = await Promise.all([
request(cfg.api.serverNodes),
request(cfg.api.serverGroups)
]);
state.nodes = toArray(unwrap(nodes));
state.groups = toArray(unwrap(groups));
- } else if (state.route === "node-group") {
+ } else if (state.route === 'node-group') {
state.groups = toArray(unwrap(await request(cfg.api.serverGroups)));
} else if (state.route === "node-route") {
state.routes = toArray(unwrap(await request(cfg.api.serverRoutes)));
diff --git a/frontend/admin/main.js b/frontend/admin/main.js
index a38caf0..bb589e8 100644
--- a/frontend/admin/main.js
+++ b/frontend/admin/main.js
@@ -1,11 +1,11 @@
const settings = window.settings || {};
const assetNonce = window.__ADMIN_ASSET_NONCE__ || String(Date.now());
-const securePath = String(settings.secure_path || "admin").replace(/^\/+/, "");
+const securePath = String(settings.secure_path || 'admin').replace(/^\/+/, '');
const adminBase = `/api/v2/${securePath}`;
window.ADMIN_APP_CONFIG = {
- title: settings.title || "XBoard Admin",
- version: settings.version || "1.0.0",
+ title: settings.title || 'XBoard Admin',
+ version: settings.version || '1.0.0',
securePath,
baseUrl: settings.base_url || window.location.origin,
api: {
@@ -27,28 +27,28 @@ window.ADMIN_APP_CONFIG = {
},
};
-document.documentElement.dataset.adminExecutionMode = "main-app";
+document.documentElement.dataset.adminExecutionMode = 'main-app';
function showBootError(error) {
- console.error("Failed to boot admin app", error);
- const root = document.getElementById("admin-app");
+ console.error('Failed to boot admin app', error);
+ const root = document.getElementById('admin-app');
if (root) {
root.innerHTML =
`Admin app failed to load.
${String(
- error && error.message ? error.message : error || "Unknown error",
+ error && error.message ? error.message : error || 'Unknown error',
)}
`;
}
}
-window.addEventListener("error", (event) => {
+window.addEventListener('error', (event) => {
if (!event || !event.error) {
return;
}
showBootError(event.error);
});
-const script = document.createElement("script");
+const script = document.createElement('script');
script.src = `/admin-assets/app.js?v=${encodeURIComponent(assetNonce)}`;
script.defer = true;
-script.onerror = () => showBootError(new Error("Failed to load /admin-assets/app.js"));
+script.onerror = () => showBootError(new Error('Failed to load /admin-assets/app.js'));
document.body.appendChild(script);
diff --git a/frontend/theme/Nebula/assets/app.js b/frontend/theme/Nebula/assets/app.js
index b035346..5e54fab 100644
--- a/frontend/theme/Nebula/assets/app.js
+++ b/frontend/theme/Nebula/assets/app.js
@@ -1,22 +1,22 @@
(function () {
"use strict";
- var theme = window.NEBULA_THEME || {};
- var app = document.getElementById("app");
- var loader = document.getElementById("nebula-loader");
- var themeMediaQuery = getThemeMediaQuery();
+ const theme = window.NEBULA_THEME || {};
+ const app = document.getElementById('app');
+ const loader = document.getElementById('nebula-loader');
+ const themeMediaQuery = getThemeMediaQuery();
if (!app) {
return;
}
- var state = {
- mode: "login",
+ const state = {
+ mode: 'login',
loading: true,
- message: "",
- messageType: "",
+ message: '',
+ messageType: '',
themePreference: getStoredThemePreference(),
- themeMode: "dark",
+ themeMode: 'dark',
uplinkMbps: null,
currentRoute: getCurrentRoute(),
nodePage: 1,
@@ -41,7 +41,7 @@
realNameVerification: null,
appConfig: null,
isSidebarOpen: false,
- serverSearch: "",
+ serverSearch: '',
cachedSubNodes: null,
ipv6CachedSubNodes: null
};
@@ -55,13 +55,13 @@
window.setInterval(loadUplinkMetric, 5000);
try {
- var verify = getVerifyToken();
+ const verify = getVerifyToken();
if (verify) {
- var verifyResponse = await fetchJson("/api/v1/passport/auth/token2Login?verify=" + encodeURIComponent(verify), {
- method: "GET",
+ const verifyResponse = await fetchJson('/api/v1/passport/auth/token2Login?verify=' + encodeURIComponent(verify), {
+ method: 'GET',
auth: false
});
- var verifyPayload = unwrap(verifyResponse);
+ const verifyPayload = unwrap(verifyResponse);
if (verifyPayload && verifyPayload.auth_data) {
saveToken(verifyPayload.auth_data);
clearVerifyToken();
@@ -101,16 +101,16 @@
}
try {
- var results = await Promise.all([
- fetchJson("/api/v1/user/info", { method: "GET" }),
- fetchJson("/api/v1/user/getSubscribe", { method: "GET" }),
- fetchJson("/api/v1/user/getStat", { method: "GET" }),
- fetchJson("/api/v1/user/server/fetch", { method: "GET" }),
- fetchJson("/api/v1/user/ticket/fetch", { method: "GET" }),
+ const results = await Promise.all([
+ fetchJson('/api/v1/user/info', { method: 'GET' }),
+ fetchJson('/api/v1/user/getSubscribe', { method: 'GET' }),
+ fetchJson('/api/v1/user/getStat', { method: 'GET' }),
+ fetchJson('/api/v1/user/server/fetch', { method: 'GET' }),
+ fetchJson('/api/v1/user/ticket/fetch', { method: 'GET' }),
fetchSessionOverview(),
- fetchJson("/api/v1/user/comm/config", { method: "GET" }),
+ fetchJson('/api/v1/user/comm/config', { method: 'GET' }),
fetchRealNameVerificationStatus(),
- fetchJson("/api/v1/user/user-add-ipv6-subscription/check", { method: "GET" })
+ fetchJson('/api/v1/user/user-add-ipv6-subscription/check', { method: 'GET' })
]);
state.user = unwrap(results[0]);
@@ -126,10 +126,10 @@
if (state.ipv6AuthToken) {
try {
- var ipv6Results = await Promise.all([
- fetchJson("/api/v1/user/info", { method: "GET", ipv6: true }),
- fetchJson("/api/v1/user/getSubscribe", { method: "GET", ipv6: true }),
- fetchJson("/api/v1/user/server/fetch", { method: "GET", ipv6: true }),
+ const ipv6Results = await Promise.all([
+ fetchJson('/api/v1/user/info', { method: 'GET', ipv6: true }),
+ fetchJson('/api/v1/user/getSubscribe', { method: 'GET', ipv6: true }),
+ fetchJson('/api/v1/user/server/fetch', { method: 'GET', ipv6: true }),
fetchSessionOverview(true)
]);
state.ipv6User = unwrap(ipv6Results[0]);
@@ -138,9 +138,9 @@
state.ipv6SessionOverview = ipv6Results[3];
} catch (e) {
if (handleIpv6AuthFailure(e)) {
- console.warn("Nebula: cleared stale IPv6 session after unauthorized response");
+ console.warn('Nebula: cleared stale IPv6 session after unauthorized response');
}
- console.error("Failed to load IPv6 dashboard data", e);
+ console.error('Failed to load IPv6 dashboard data', e);
}
}
@@ -150,41 +150,41 @@
} catch (error) {
state.loading = false;
if (error.status === 401 || error.status === 403) {
- var hadSession = Boolean(state.authToken);
+ const hadSession = Boolean(state.authToken);
clearToken();
resetDashboard();
- showMessage(hadSession ? "Session expired. Please sign in again." : "", hadSession ? "error" : "");
+ showMessage(hadSession ? 'Session expired. Please sign in again.' : '', hadSession ? 'error' : '');
return false;
}
- showMessage(error.message || "Failed to load dashboard", "error");
+ showMessage(error.message || 'Failed to load dashboard', 'error');
return false;
}
}
async function fetchSessionOverview(isIpv6) {
try {
- var response = await fetchJson("/api/v1/user/user-online-devices/get-ip", { method: "GET", ipv6: !!isIpv6 });
- var ipPayload = unwrap(response) || {};
+ const response = await fetchJson('/api/v1/user/user-online-devices/get-ip', { method: 'GET', ipv6: !!isIpv6 });
+ const ipPayload = unwrap(response) || {};
- var reportedIps = ipPayload.ips || [];
+ const reportedIps = ipPayload.ips || [];
if (!isIpv6) {
state.reportedIps = reportedIps;
}
-
- var overview = ipPayload.session_overview || { sessions: [], online_ips: [] };
+
+ const overview = ipPayload.session_overview || { sessions: [], online_ips: [] };
if (!isIpv6) {
overview.online_ips = state.reportedIps;
}
-
+
return buildSessionOverview(overview.sessions, overview, !!isIpv6);
} catch (error) {
if (isIpv6 && handleIpv6AuthFailure(error)) {
return null;
}
- console.error("Nebula: Failed to synchronize online status", error);
+ console.error('Nebula: Failed to synchronize online status', error);
// Fallback for sessions if plugin fails
try {
- var sessionsFallback = unwrap(await fetchJson("/api/v1/user/getActiveSession", { method: "GET", ipv6: !!isIpv6 })) || [];
+ const sessionsFallback = unwrap(await fetchJson('/api/v1/user/getActiveSession', { method: 'GET', ipv6: !!isIpv6 })) || [];
return buildSessionOverview(sessionsFallback, {}, !!isIpv6);
} catch (e) {
if (isIpv6 && handleIpv6AuthFailure(e)) {
@@ -823,43 +823,43 @@
}
// Trojan
- if (link.indexOf("trojan://") === 0) {
- var body = link.split("#")[0].slice(9);
- var main = body.split("?")[0];
- var query = body.split("?")[1] || "";
- var parts = main.split("@");
- var password = parts[0];
- var serverParts = (parts[1] || "").split(":");
+ if (link.indexOf('trojan://') === 0) {
+ const body = link.split('#')[0].slice(9);
+ const main = body.split('?')[0];
+ const query = body.split('?')[1] || '';
+ const parts = main.split('@');
+ const password = parts[0];
+ const serverParts = (parts[1] || '').split(':');
- var params = {};
- query.split("&").forEach(function (pair) {
- var p = pair.split("=");
- params[p[0]] = decodeURIComponent(p[1] || "");
+ const params = {};
+ query.split('&').forEach((pair) => {
+ const p = pair.split('=');
+ params[p[0]] = decodeURIComponent(p[1] || '');
});
- var yaml = [
- "- name: \"" + (remark || "Trojan Node") + "\"",
- " type: trojan",
- " server: " + serverParts[0],
- " port: " + (parseInt(serverParts[1]) || 443),
- " password: " + password,
- " udp: true",
- " sni: " + (params.sni || params.peer || ""),
- " skip-cert-verify: true"
+ const yaml = [
+ '- name: "' + (remark || 'Trojan Node') + '"',
+ ' type: trojan',
+ ' server: ' + serverParts[0],
+ ' port: ' + (parseInt(serverParts[1]) || 443),
+ ' password: ' + password,
+ ' udp: true',
+ ' sni: ' + (params.sni || params.peer || ''),
+ ' skip-cert-verify: true'
];
- if (params.type === "ws") {
- yaml.push(" ws-opts:");
- yaml.push(" path: " + (params.path || "/"));
- if (params.host) yaml.push(" headers:");
- if (params.host) yaml.push(" Host: " + params.host);
+ if (params.type === 'ws') {
+ yaml.push(' ws-opts:');
+ yaml.push(' path: ' + (params.path || '/'));
+ if (params.host) yaml.push(' headers:');
+ if (params.host) yaml.push(' Host: ' + params.host);
}
- if (params.type === "grpc") {
- yaml.push(" grpc-opts:");
- yaml.push(" grpc-service-name: " + (params.serviceName || ""));
+ if (params.type === 'grpc') {
+ yaml.push(' grpc-opts:');
+ yaml.push(' grpc-service-name: ' + (params.serviceName || ''));
}
- return yaml.join("\n");
+ return yaml.join('\n');
}
return link;
@@ -867,27 +867,27 @@
function utf8Base64Decode(str) {
try {
- var b64 = str.trim().replace(/-/g, "+").replace(/_/g, "/");
- while (b64.length % 4 !== 0) b64 += "=";
-
- var binary = atob(b64);
+ let b64 = str.trim().replace(/-/g, '+').replace(/_/g, '/');
+ while (b64.length % 4 !== 0) b64 += '=';
+
+ const binary = atob(b64);
try {
- if (typeof TextDecoder !== "undefined") {
- var bytes = new Uint8Array(binary.length);
- for (var i = 0; i < binary.length; i++) {
+ if (typeof TextDecoder !== 'undefined') {
+ const bytes = new Uint8Array(binary.length);
+ for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
- return new TextDecoder("utf-8").decode(bytes);
+ return new TextDecoder('utf-8').decode(bytes);
}
-
- return decodeURIComponent(binary.split("").map(function (c) {
- return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
- }).join(""));
+
+ return decodeURIComponent(binary.split('').map((c) => {
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
+ }).join(''));
} catch (e) {
return binary;
}
} catch (e) {
- throw new Error("Invalid base64 content: " + e.message);
+ throw new Error('Invalid base64 content: ' + e.message);
}
}
@@ -912,24 +912,24 @@
if (state.authToken) {
return;
}
- var baseUrl = getMetricsBaseUrl();
+ const baseUrl = getMetricsBaseUrl();
if (!baseUrl) {
return;
}
try {
- var response = await fetch(baseUrl + "/api/metrics/overview", {
- method: "GET",
+ const response = await fetch(baseUrl + '/api/metrics/overview', {
+ method: 'GET',
headers: {
- "X-Requested-With": "XMLHttpRequest"
+ 'X-Requested-With': 'XMLHttpRequest'
}
});
- var payload = await response.json();
- var network = (payload && payload.network) || {};
- var tx = Number(network.tx || 0);
- var total = tx;
+ const payload = await response.json();
+ const network = (payload && payload.network) || {};
+ const tx = Number(network.tx || 0);
+ const total = tx;
if (total > 0) {
state.uplinkMbps = (total * 8) / 1000000;
- var el = document.getElementById("nebula-hero-metric-stack");
+ const el = document.getElementById('nebula-hero-metric-stack');
if (el) {
el.innerHTML = renderUplinkMetricStack();
} else if (!state.authToken || !state.user) {
@@ -942,37 +942,37 @@
}
function onSubmit(event) {
- var form = event.target;
- if (!form.matches("[data-form]")) {
+ const form = event.target;
+ if (!form.matches('[data-form]')) {
return;
}
event.preventDefault();
- var formType = form.getAttribute("data-form");
- var data = Object.fromEntries(new FormData(form).entries());
- var submitButton = form.querySelector("[type='submit']");
+ const formType = form.getAttribute('data-form');
+ const data = Object.fromEntries(new FormData(form).entries());
+ const submitButton = form.querySelector('[type="submit"]');
if (submitButton) {
submitButton.disabled = true;
}
- if (formType === "create-ticket") {
- fetchJson("/api/v1/user/ticket/save", {
- method: "POST",
+ if (formType === 'create-ticket') {
+ fetchJson('/api/v1/user/ticket/save', {
+ method: 'POST',
body: {
- subject: data.subject || "",
- level: data.level || "0",
- message: data.message || ""
+ subject: data.subject || '',
+ level: data.level || '0',
+ message: data.message || ''
}
- }).then(function () {
- showMessage("工单已创建", "success");
+ }).then(() => {
+ showMessage('工单已创建', 'success');
state.selectedTicketId = null;
state.selectedTicket = null;
return loadDashboard(true);
- }).then(render).catch(function (error) {
- showMessage(error.message || "工单创建失败", "error");
+ }).then(render).catch((error) => {
+ showMessage(error.message || '工单创建失败', 'error');
render();
- }).finally(function () {
+ }).finally(() => {
if (submitButton) {
submitButton.disabled = false;
}
@@ -980,28 +980,28 @@
return;
}
- if (formType === "real-name-verification") {
- fetchJson("/api/v1/user/real-name-verification/submit", {
- method: "POST",
+ if (formType === 'real-name-verification') {
+ fetchJson('/api/v1/user/real-name-verification/submit', {
+ method: 'POST',
body: {
- real_name: data.real_name || "",
- identity_no: data.identity_no || ""
+ real_name: data.real_name || '',
+ identity_no: data.identity_no || ''
}
- }).then(function (response) {
- var payload = unwrap(response) || {};
- var verification = payload.verification || payload;
+ }).then((response) => {
+ const payload = unwrap(response) || {};
+ const verification = payload.verification || payload;
state.realNameVerification = verification;
showMessage(
- verification && verification.status === "approved"
- ? "实名认证已通过。"
- : "认证信息已提交,请等待审核。",
- "success"
+ verification && verification.status === 'approved'
+ ? '实名认证已通过。'
+ : '认证信息已提交,请等待审核。',
+ 'success'
);
return loadDashboard(true);
- }).then(render).catch(function (error) {
- showMessage(error.message || "认证提交失败", "error");
+ }).then(render).catch((error) => {
+ showMessage(error.message || '认证提交失败', 'error');
render();
- }).finally(function () {
+ }).finally(() => {
if (submitButton) {
submitButton.disabled = false;
}
@@ -1009,24 +1009,24 @@
return;
}
- if (formType === "change-password") {
+ if (formType === 'change-password') {
if (data.new_password !== data.confirm_password) {
- showMessage("两次输入的新密码不一致", "error");
+ showMessage('两次输入的新密码不一致', 'error');
if (submitButton) submitButton.disabled = false;
return;
}
- fetchJson("/api/v1/user/changePassword", {
- method: "POST",
+ fetchJson('/api/v1/user/changePassword', {
+ method: 'POST',
body: {
- old_password: data.old_password || "",
- new_password: data.new_password || ""
+ old_password: data.old_password || '',
+ new_password: data.new_password || ''
}
- }).then(function () {
- showMessage("密码已修改,建议在 IPv6 节点同步新密码", "success");
+ }).then(() => {
+ showMessage('密码已修改,建议在 IPv6 节点同步新密码', 'success');
form.reset();
- }).catch(function (error) {
- showMessage(error.message || "密码修改失败", "error");
- }).finally(function () {
+ }).catch((error) => {
+ showMessage(error.message || '密码修改失败', 'error');
+ }).finally(() => {
if (submitButton) {
submitButton.disabled = false;
}
@@ -1034,22 +1034,22 @@
return;
}
- if (formType === "forget-password") {
- fetchJson("/api/v1/passport/auth/forget", {
- method: "POST",
+ if (formType === 'forget-password') {
+ fetchJson('/api/v1/passport/auth/forget', {
+ method: 'POST',
auth: false,
body: {
email: data.email,
email_code: data.email_code,
password: data.password
}
- }).then(function () {
- showMessage("密码已重置,请使用新密码登录", "success");
- state.mode = "login";
+ }).then(() => {
+ showMessage('密码已重置,请使用新密码登录', 'success');
+ state.mode = 'login';
render();
- }).catch(function (error) {
- showMessage(error.message || "重置失败", "error");
- }).finally(function () {
+ }).catch((error) => {
+ showMessage(error.message || '重置失败', 'error');
+ }).finally(() => {
if (submitButton) {
submitButton.disabled = false;
}
@@ -1057,48 +1057,48 @@
return;
}
- fetchJson(formType === "register" ? "/api/v1/passport/auth/register" : "/api/v1/passport/auth/login", {
- method: "POST",
+ fetchJson(formType === 'register' ? '/api/v1/passport/auth/register' : '/api/v1/passport/auth/login', {
+ method: 'POST',
auth: false,
body: data
- }).then(function (response) {
- var payload = unwrap(response);
+ }).then((response) => {
+ const payload = unwrap(response);
if (!payload || !payload.auth_data) {
- throw new Error("Authentication succeeded but token payload is missing");
+ throw new Error('Authentication succeeded but token payload is missing');
}
saveToken(payload.auth_data);
state.authToken = getStoredToken();
- showMessage(formType === "register" ? "Account created" : "Signed in", "success");
- return loadDashboard(true).then(function () {
- if (formType !== "login" || !data.email || !data.password || state.ipv6AuthToken) {
+ showMessage(formType === 'register' ? 'Account created' : 'Signed in', 'success');
+ return loadDashboard(true).then(() => {
+ if (formType !== 'login' || !data.email || !data.password || state.ipv6AuthToken) {
return null;
}
if (!(state.ipv6Eligibility && state.ipv6Eligibility.is_active)) {
return null;
}
- var ipv6Email = data.email.replace("@", "-ipv6@");
- return fetchJson("/api/v1/passport/auth/login", {
- method: "POST",
+ const ipv6Email = data.email.replace('@', '-ipv6@');
+ return fetchJson('/api/v1/passport/auth/login', {
+ method: 'POST',
auth: false,
body: Object.assign({}, data, { email: ipv6Email })
- }).then(function (ipv6Response) {
- var ipv6Payload = unwrap(ipv6Response);
+ }).then((ipv6Response) => {
+ const ipv6Payload = unwrap(ipv6Response);
if (ipv6Payload && ipv6Payload.auth_data) {
saveIpv6Token(ipv6Payload.auth_data);
state.ipv6AuthToken = getStoredIpv6Token();
}
- }).catch(function () {
+ }).catch(() => {
return null;
- }).then(function () {
+ }).then(() => {
return loadDashboard(true);
});
});
- }).then(render).catch(function (error) {
- showMessage(error.message || "Unable to continue", "error");
+ }).then(render).catch((error) => {
+ showMessage(error.message || 'Unable to continue', 'error');
render();
- }).finally(function () {
+ }).finally(() => {
if (submitButton) {
submitButton.disabled = false;
}
@@ -1107,23 +1107,23 @@
function handleResetSecurity(actionEl, isIpv6) {
showNebulaModal({
- title: "安全提醒",
- content: "确定要重置订阅令牌吗?重置令牌后,原本的订阅地址将失效,你需要重新获取并配置订阅。",
- confirmText: "确认重置",
- onConfirm: function () {
+ title: '安全提醒',
+ content: '确定要重置订阅令牌吗?重置令牌后,原本的订阅地址将失效,你需要重新获取并配置订阅。',
+ confirmText: '确认重置',
+ onConfirm: () => {
actionEl.disabled = true;
- fetchJson("/api/v1/user/resetSecurity", { method: "GET", ipv6: !!isIpv6 }).then(function (response) {
- var subscribeUrl = unwrap(response);
- var sub = isIpv6 ? state.ipv6Subscribe : state.subscribe;
+ fetchJson('/api/v1/user/resetSecurity', { method: 'GET', ipv6: !!isIpv6 }).then((response) => {
+ const subscribeUrl = unwrap(response);
+ const sub = isIpv6 ? state.ipv6Subscribe : state.subscribe;
if (sub && subscribeUrl) {
sub.subscribe_url = subscribeUrl;
}
- showMessage((isIpv6 ? "IPv6 " : "") + "订阅令牌已重置", "success");
+ showMessage((isIpv6 ? 'IPv6 ' : '') + '订阅令牌已重置', 'success');
render();
- }).catch(function (error) {
- showMessage(error.message || "重置失败", "error");
+ }).catch((error) => {
+ showMessage(error.message || '重置失败', 'error');
render();
- }).finally(function () {
+ }).finally(() => {
actionEl.disabled = false;
});
}
@@ -1131,68 +1131,68 @@
}
function handleRemoveSession(actionEl) {
- var sessionId = actionEl.getAttribute("data-session-id");
+ const sessionId = actionEl.getAttribute('data-session-id');
if (!sessionId) {
return;
}
actionEl.disabled = true;
- fetchJson("/api/v1/user/removeActiveSession", {
- method: "POST",
+ fetchJson('/api/v1/user/removeActiveSession', {
+ method: 'POST',
body: { session_id: sessionId }
- }).then(function () {
- showMessage("Session revoked", "success");
+ }).then(() => {
+ showMessage('Session revoked', 'success');
return loadDashboard(true);
- }).then(render).catch(function (error) {
- showMessage(error.message || "Unable to revoke session", "error");
+ }).then(render).catch((error) => {
+ showMessage(error.message || 'Unable to revoke session', 'error');
render();
- }).finally(function () {
+ }).finally(() => {
actionEl.disabled = false;
});
}
function handleRemoveOtherSessions(actionEl) {
- var sessions = ((state.sessionOverview && state.sessionOverview.sessions) || []).filter(function (session) {
+ const sessions = ((state.sessionOverview && state.sessionOverview.sessions) || []).filter((session) => {
return !session.is_current;
});
if (!sessions.length) {
- showMessage("No other sessions to sign out.", "success");
+ showMessage('No other sessions to sign out.', 'success');
render();
return;
}
actionEl.disabled = true;
- Promise.all(sessions.map(function (session) {
- return fetchJson("/api/v1/user/removeActiveSession", {
- method: "POST",
+ Promise.all(sessions.map((session) => {
+ return fetchJson('/api/v1/user/removeActiveSession', {
+ method: 'POST',
body: { session_id: session.id }
});
- })).then(function () {
- showMessage("Signed out all other sessions.", "success");
+ })).then(() => {
+ showMessage('Signed out all other sessions.', 'success');
return loadDashboard(true);
- }).then(render).catch(function (error) {
- showMessage(error.message || "Unable to sign out other sessions", "error");
+ }).then(render).catch((error) => {
+ showMessage(error.message || 'Unable to sign out other sessions', 'error');
render();
- }).finally(function () {
+ }).finally(() => {
actionEl.disabled = false;
});
}
function handleOpenTicketDetail(actionEl) {
- var ticketId = actionEl.getAttribute("data-ticket-id");
+ const ticketId = actionEl.getAttribute('data-ticket-id');
if (!ticketId) {
return;
}
actionEl.disabled = true;
- fetchJson("/api/v1/user/ticket/fetch?id=" + encodeURIComponent(ticketId), { method: "GET" }).then(function (response) {
+ fetchJson('/api/v1/user/ticket/fetch?id=' + encodeURIComponent(ticketId), { method: 'GET' }).then((response) => {
state.selectedTicketId = ticketId;
state.selectedTicket = unwrap(response);
render();
- }).catch(function (error) {
- showMessage(error.message || "工单详情加载失败", "error");
+ }).catch((error) => {
+ showMessage(error.message || '工单详情加载失败', 'error');
render();
- }).finally(function () {
+ }).finally(() => {
actionEl.disabled = false;
});
}
@@ -1219,8 +1219,8 @@
return;
}
- var isLoggedIn = !!(state.authToken && state.user);
- var targetLayout = isLoggedIn ? "dashboard" : "login";
+ const isLoggedIn = !!(state.authToken && state.user);
+ const targetLayout = isLoggedIn ? 'dashboard' : 'login';
// If layout type changed (e.g. Login -> Dashboard), do a full shell render
if (currentLayout !== targetLayout) {
@@ -1232,21 +1232,21 @@
// If already in Dashboard, only update the content area and sidebar state
if (isLoggedIn) {
- var usedTraffic = ((state.subscribe && state.subscribe.u) || 0) + ((state.subscribe && state.subscribe.d) || 0);
- var totalTraffic = (state.subscribe && state.subscribe.transfer_enable) || 0;
- var remainingTraffic = Math.max(totalTraffic - usedTraffic, 0);
- var percent = totalTraffic > 0 ? Math.min(100, Math.round((usedTraffic / totalTraffic) * 100)) : 0;
- var stats = Array.isArray(state.stats) ? state.stats : [0, 0, 0];
- var overview = state.sessionOverview || {};
+ const usedTraffic = ((state.subscribe && state.subscribe.u) || 0) + ((state.subscribe && state.subscribe.d) || 0);
+ const totalTraffic = (state.subscribe && state.subscribe.transfer_enable) || 0;
+ const remainingTraffic = Math.max(totalTraffic - usedTraffic, 0);
+ const percent = totalTraffic > 0 ? Math.min(100, Math.round((usedTraffic / totalTraffic) * 100)) : 0;
+ const stats = Array.isArray(state.stats) ? state.stats : [0, 0, 0];
+ const overview = state.sessionOverview || {};
- var contentArea = document.querySelector(".dashboard-main");
+ const contentArea = document.querySelector('.dashboard-main');
if (contentArea) {
contentArea.innerHTML = renderCurrentRoute(remainingTraffic, usedTraffic, totalTraffic, percent, stats, overview);
}
- var sidebarContainer = document.querySelector(".dashboard-shell");
+ const sidebarContainer = document.querySelector('.dashboard-shell');
if (sidebarContainer) {
- var sidebar = sidebarContainer.querySelector(".dashboard-sidebar");
+ const sidebar = sidebarContainer.querySelector('.dashboard-sidebar');
if (sidebar) {
sidebar.innerHTML = renderSidebar(remainingTraffic, usedTraffic, totalTraffic, percent, overview, true);
}
@@ -1292,12 +1292,12 @@
}
function renderDashboard() {
- var usedTraffic = ((state.subscribe && state.subscribe.u) || 0) + ((state.subscribe && state.subscribe.d) || 0);
- var totalTraffic = (state.subscribe && state.subscribe.transfer_enable) || 0;
- var remainingTraffic = Math.max(totalTraffic - usedTraffic, 0);
- var percent = totalTraffic > 0 ? Math.min(100, Math.round((usedTraffic / totalTraffic) * 100)) : 0;
- var stats = Array.isArray(state.stats) ? state.stats : [0, 0, 0];
- var overview = state.sessionOverview || {};
+ const usedTraffic = ((state.subscribe && state.subscribe.u) || 0) + ((state.subscribe && state.subscribe.d) || 0);
+ const totalTraffic = (state.subscribe && state.subscribe.transfer_enable) || 0;
+ const remainingTraffic = Math.max(totalTraffic - usedTraffic, 0);
+ const percent = totalTraffic > 0 ? Math.min(100, Math.round((usedTraffic / totalTraffic) * 100)) : 0;
+ const stats = Array.isArray(state.stats) ? state.stats : [0, 0, 0];
+ const overview = state.sessionOverview || {};
return renderDashboardByRoute(remainingTraffic, usedTraffic, totalTraffic, percent, stats, overview);
}
@@ -1385,14 +1385,14 @@
].join("");
}
- var html = [
+ const html = [
'',
- renderTrafficOverviewCard(remainingTraffic, usedTraffic, totalTraffic, percent, "span-12 section-card--overview"),
- renderIpv6TrafficOverviewCard("span-12"),
- renderAccessSnapshotCard(overview, "span-12 section-card--overview"),
- renderRealNameVerificationOverviewCard("span-12 section-card--overview"),
+ renderTrafficOverviewCard(remainingTraffic, usedTraffic, totalTraffic, percent, 'span-12 section-card--overview'),
+ renderIpv6TrafficOverviewCard('span-12'),
+ renderAccessSnapshotCard(overview, 'span-12 section-card--overview'),
+ renderRealNameVerificationOverviewCard('span-12 section-card--overview'),
''
- ].join("");
+ ].join('');
return html;
}
@@ -1426,19 +1426,19 @@
}
function renderAccessSnapshotCard(overview, extraClass) {
- extraClass = extraClass || "span-7";
- var ipv6Overview = state.ipv6SessionOverview;
- var limitDisplay = formatLimit(overview.device_limit) + (ipv6Overview ? " / " + formatLimit(ipv6Overview.device_limit) : "");
+ extraClass = extraClass || 'span-7';
+ const ipv6Overview = state.ipv6SessionOverview;
+ const limitDisplay = formatLimit(overview.device_limit) + (ipv6Overview ? ' / ' + formatLimit(ipv6Overview.device_limit) : '');
return [
''
- ].join("");
+ ].join('');
}
function renderIpv6TrafficOverviewCard(extraClass) {
@@ -1477,21 +1477,21 @@
}
function renderSidebar(remainingTraffic, usedTraffic, totalTraffic, percent, overview, innerOnly) {
- var content = [
+ let content = [
''
- ].join("");
- content = content.replace(/