xray
This commit is contained in:
@@ -5,16 +5,14 @@ const dashboardScreen = $("#dashboard-screen");
|
||||
const loginForm = $("#login-form");
|
||||
const loginError = $("#login-error");
|
||||
const logoutBtn = $("#logout-btn");
|
||||
const novavpsUrlInput = $("#novavps-url");
|
||||
const subscriptionUrlInput = $("#subscription-url");
|
||||
const saveConfigBtn = $("#save-config-btn");
|
||||
const configMsg = $("#config-msg");
|
||||
const runBtn = $("#run-btn");
|
||||
const syncXray2Btn = $("#sync-xray2-btn");
|
||||
const runMsg = $("#run-msg");
|
||||
const runningIndicator = $("#running-indicator");
|
||||
const currentStep = $("#current-step");
|
||||
const lastRunEl = $("#last-run");
|
||||
const xray2Section = $("#xray2-data-section");
|
||||
|
||||
const POLL_INTERVAL = 10000;
|
||||
|
||||
@@ -42,16 +40,13 @@ function formatTime(iso) {
|
||||
|
||||
const stepLabels = {
|
||||
initializing: "Инициализация...",
|
||||
novavps_fetching: "Получение данных NOVAVPS...",
|
||||
xray1_parsing: "Чтение панели Xray 1...",
|
||||
xray1_syncing: "Синхронизация Xray 1...",
|
||||
pinging: "Проверка соединений (Xray 1)...",
|
||||
xray2_parsing: "Чтение панели Xray 2...",
|
||||
xray2_syncing: "Синхронизация Xray 2...",
|
||||
xray2_pinging: "Проверка соединений (Xray 2)...",
|
||||
novavps_fetching: "Получение данных подписки...",
|
||||
parsing: "Чтение панели Xray...",
|
||||
syncing: "Синхронизация Xray...",
|
||||
pinging: "Проверка соединений (Xray)...",
|
||||
done: "Завершено",
|
||||
novavps_url_missing: "Ошибка: ссылка NOVAVPS не задана",
|
||||
no_xray_panels: "Ошибка: нет настроенных панелей Xray",
|
||||
subscription_url_missing: "Ошибка: ссылка подписки не задана",
|
||||
no_xray_panel: "Ошибка: не настроена панель Xray",
|
||||
fatal: "Критическая ошибка",
|
||||
};
|
||||
|
||||
@@ -94,26 +89,18 @@ async function fetchStatus() {
|
||||
const data = await res.json();
|
||||
|
||||
const isRunning = data.status === "running";
|
||||
const isXray2Syncing = data.currentStep === "xray2_syncing";
|
||||
runBtn.disabled = isRunning;
|
||||
syncXray2Btn.disabled = isRunning || isXray2Syncing;
|
||||
runningIndicator.classList.toggle("hidden", !isRunning);
|
||||
currentStep.textContent = stepLabels[data.currentStep] || data.currentStep;
|
||||
|
||||
const hasXray2 = data.configuredPanels && data.configuredPanels.includes("xray2");
|
||||
syncXray2Btn.classList.toggle("hidden", !hasXray2);
|
||||
|
||||
updatePanelStatus("novavps", data.novavps);
|
||||
updatePanelStatus("xray1", data.xray1);
|
||||
updatePanelStatus("xray2", data.xray2);
|
||||
updatePanelStatus("xray", data.xray);
|
||||
|
||||
pingResults = data.xray1?.pingResults || [];
|
||||
pingResults = data.xray?.pingResults || [];
|
||||
|
||||
lastRunEl.textContent = data.lastRun ? formatTime(data.lastRun) : "Никогда";
|
||||
|
||||
if (data.xray2.status !== "idle" || data.xray2.outboundsCount > 0) {
|
||||
xray2Section.classList.remove("hidden");
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error("Status fetch error:", err);
|
||||
}
|
||||
@@ -128,7 +115,7 @@ async function fetchConfig() {
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
novavpsUrlInput.value = data.novavpsUrl || "";
|
||||
subscriptionUrlInput.value = data.subscriptionUrl || "";
|
||||
} catch (err) {
|
||||
console.error("Config fetch error:", err);
|
||||
}
|
||||
@@ -144,19 +131,13 @@ async function fetchNovavpsData() {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function fetchXrayData(panel, syncedTags) {
|
||||
async function fetchXrayData(syncedTags) {
|
||||
try {
|
||||
const res = await fetch("/api/data/xray");
|
||||
if (res.status === 401) return;
|
||||
const data = await res.json();
|
||||
|
||||
if (panel === "xray1") {
|
||||
renderXrayTable("xray1", data, syncedTags || [], pingResults);
|
||||
$("#xray1-data-count").textContent = data.length;
|
||||
} else if (panel === "xray2") {
|
||||
renderXrayTable("xray2", data, syncedTags || [], []);
|
||||
$("#xray2-data-count").textContent = data.length;
|
||||
}
|
||||
renderXrayTable("xray", data, syncedTags || [], pingResults);
|
||||
$("#xray-data-count").textContent = data.length;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -180,8 +161,7 @@ function renderNovavpsTable(connections) {
|
||||
|
||||
function renderXrayTable(prefix, outbounds, syncedTags, pingList) {
|
||||
const tbody = $(`#${prefix}-tbody`);
|
||||
const hasLatency = prefix === "xray1";
|
||||
const colspan = hasLatency ? 6 : 5;
|
||||
const colspan = 6;
|
||||
|
||||
if (!outbounds.length) {
|
||||
tbody.innerHTML = `<tr><td colspan="${colspan}" style="color:var(--text-muted);text-align:center;padding:2rem;">Нет данных</td></tr>`;
|
||||
@@ -203,11 +183,11 @@ function renderXrayTable(prefix, outbounds, syncedTags, pingList) {
|
||||
: '<span class="synced-badge no">—</span>';
|
||||
|
||||
let latencyCell = '<span class="latency-cell latency-error">—</span>';
|
||||
if (hasLatency && pingMap[tag]) {
|
||||
if (pingMap[tag]) {
|
||||
latencyCell = formatLatency(pingMap[tag].latency);
|
||||
}
|
||||
|
||||
const latencyCol = hasLatency ? `<td>${latencyCell}</td>` : "";
|
||||
const latencyCol = `<td>${latencyCell}</td>`;
|
||||
return `<tr><td>${tag}</td><td>${proto}</td><td>${addr}</td><td>${port}</td>${latencyCol}<td>${syncBadge}</td></tr>`;
|
||||
})
|
||||
.join("");
|
||||
@@ -223,14 +203,12 @@ function startPolling() {
|
||||
fetchStatus();
|
||||
fetchConfig();
|
||||
fetchNovavpsData();
|
||||
fetchXrayData("xray1", []);
|
||||
fetchXrayData("xray2", []);
|
||||
fetchXrayData([]);
|
||||
if (pollTimer) clearInterval(pollTimer);
|
||||
pollTimer = setInterval(() => {
|
||||
fetchStatus();
|
||||
fetchNovavpsData();
|
||||
fetchXrayData("xray1", []);
|
||||
fetchXrayData("xray2", []);
|
||||
fetchXrayData([]);
|
||||
}, POLL_INTERVAL);
|
||||
}
|
||||
|
||||
@@ -295,7 +273,7 @@ saveConfigBtn.addEventListener("click", async () => {
|
||||
const res = await fetch("/api/config", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ novavpsUrl: novavpsUrlInput.value }),
|
||||
body: JSON.stringify({ subscriptionUrl: subscriptionUrlInput.value }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
@@ -324,22 +302,6 @@ runBtn.addEventListener("click", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
syncXray2Btn.addEventListener("click", async () => {
|
||||
runMsg.classList.add("hidden");
|
||||
try {
|
||||
const res = await fetch("/api/sync-xray2", { method: "POST" });
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
showMsg(runMsg, data.error || "Ошибка запуска", "error");
|
||||
return;
|
||||
}
|
||||
showMsg(runMsg, "Синхронизация Xray2 запущена", "success");
|
||||
fetchStatus();
|
||||
} catch (err) {
|
||||
showMsg(runMsg, "Ошибка сети", "error");
|
||||
}
|
||||
});
|
||||
|
||||
fetch("/api/status")
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
<section class="card config-card">
|
||||
<h2>Настройки</h2>
|
||||
<div class="config-row">
|
||||
<label for="novavps-url">Ссылка NOVAVPS</label>
|
||||
<input type="text" id="novavps-url" placeholder="https://novavps.app/s/...">
|
||||
<label for="subscription-url">Ссылка подписки</label>
|
||||
<input type="text" id="subscription-url" placeholder="https://novavps.app/s/...">
|
||||
<button id="save-config-btn" class="btn btn-primary">Сохранить</button>
|
||||
</div>
|
||||
<div id="config-msg" class="msg hidden"></div>
|
||||
@@ -67,17 +67,7 @@
|
||||
</span>
|
||||
Обновить сейчас
|
||||
</button>
|
||||
<button id="sync-xray2-btn" class="btn btn-secondary btn-lg">
|
||||
<span class="btn-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
|
||||
<path d="M3 3v5h5"/>
|
||||
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/>
|
||||
<path d="M16 21h5v-5"/>
|
||||
</svg>
|
||||
</span>
|
||||
Синхр. Xray 2
|
||||
</button>
|
||||
|
||||
<div id="run-msg" class="msg hidden"></div>
|
||||
</section>
|
||||
|
||||
@@ -92,7 +82,7 @@
|
||||
<section class="status-section">
|
||||
<h2 class="section-title">Статус панелей</h2>
|
||||
<div class="status-grid">
|
||||
<div class="status-card" id="xray1-card">
|
||||
<div class="status-card" id="xray-card">
|
||||
<div class="status-card-header">
|
||||
<span class="panel-icon xray-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@@ -102,27 +92,11 @@
|
||||
<line x1="6" y1="18" x2="6" y2="18"/>
|
||||
</svg>
|
||||
</span>
|
||||
<h3>Xray 1</h3>
|
||||
<h3>Xray</h3>
|
||||
</div>
|
||||
<div class="status-badge" id="xray1-status">ожидание</div>
|
||||
<div class="status-detail" id="xray1-detail"></div>
|
||||
<div class="status-time" id="xray1-time"></div>
|
||||
</div>
|
||||
<div class="status-card" id="xray2-card">
|
||||
<div class="status-card-header">
|
||||
<span class="panel-icon xray-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="2" y="2" width="20" height="8" rx="2"/>
|
||||
<rect x="2" y="14" width="20" height="8" rx="2"/>
|
||||
<line x1="6" y1="6" x2="6" y2="6"/>
|
||||
<line x1="6" y1="18" x2="6" y2="18"/>
|
||||
</svg>
|
||||
</span>
|
||||
<h3>Xray 2</h3>
|
||||
</div>
|
||||
<div class="status-badge" id="xray2-status">ожидание</div>
|
||||
<div class="status-detail" id="xray2-detail"></div>
|
||||
<div class="status-time" id="xray2-time"></div>
|
||||
<div class="status-badge" id="xray-status">ожидание</div>
|
||||
<div class="status-detail" id="xray-detail"></div>
|
||||
<div class="status-time" id="xray-time"></div>
|
||||
</div>
|
||||
<div class="status-card" id="novavps-card">
|
||||
<div class="status-card-header">
|
||||
@@ -140,15 +114,15 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card data-section" id="xray1-data-section">
|
||||
<div class="data-section-header" onclick="toggleSection('xray1-data')">
|
||||
<h2>Исходящие Xray 1</h2>
|
||||
<span class="data-count" id="xray1-data-count"></span>
|
||||
<span class="collapse-arrow" id="xray1-data-arrow">▼</span>
|
||||
<section class="card data-section" id="xray-data-section">
|
||||
<div class="data-section-header" onclick="toggleSection('xray-data')">
|
||||
<h2>Исходящие Xray</h2>
|
||||
<span class="data-count" id="xray-data-count"></span>
|
||||
<span class="collapse-arrow" id="xray-data-arrow">▼</span>
|
||||
</div>
|
||||
<div class="data-content" id="xray1-data">
|
||||
<div class="data-content" id="xray-data">
|
||||
<div class="table-wrapper">
|
||||
<table class="data-table" id="xray1-table">
|
||||
<table class="data-table" id="xray-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Метка</th>
|
||||
@@ -159,31 +133,7 @@
|
||||
<th>Синхр.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="xray1-tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card data-section hidden" id="xray2-data-section">
|
||||
<div class="data-section-header" onclick="toggleSection('xray2-data')">
|
||||
<h2>Исходящие Xray 2</h2>
|
||||
<span class="data-count" id="xray2-data-count"></span>
|
||||
<span class="collapse-arrow" id="xray2-data-arrow">▼</span>
|
||||
</div>
|
||||
<div class="data-content" id="xray2-data">
|
||||
<div class="table-wrapper">
|
||||
<table class="data-table" id="xray2-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Метка</th>
|
||||
<th>Протокол</th>
|
||||
<th>Адрес</th>
|
||||
<th>Порт</th>
|
||||
<th>Синхр.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="xray2-tbody"></tbody>
|
||||
<tbody id="xray-tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user