v2
This commit is contained in:
@@ -8,12 +8,23 @@ export interface AppConfig {
|
||||
novavpsUrl: string;
|
||||
}
|
||||
|
||||
export interface PingResult {
|
||||
tag: string;
|
||||
name: string;
|
||||
address: string;
|
||||
port: string;
|
||||
latency: number | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface PanelStatus {
|
||||
status: "idle" | "success" | "error";
|
||||
count?: number;
|
||||
outboundsCount?: number;
|
||||
synced?: boolean;
|
||||
syncedOutbounds?: string[];
|
||||
pingResults?: PingResult[];
|
||||
allPingsOk?: boolean;
|
||||
timestamp: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
@@ -39,7 +50,7 @@ const defaultState: AppState = {
|
||||
status: "idle",
|
||||
currentStep: "",
|
||||
novavps: { status: "idle", count: 0, timestamp: null, error: null },
|
||||
xray1: { status: "idle", outboundsCount: 0, synced: false, syncedOutbounds: [], timestamp: null, error: null },
|
||||
xray1: { status: "idle", outboundsCount: 0, synced: false, syncedOutbounds: [], pingResults: [], allPingsOk: false, timestamp: null, error: null },
|
||||
xray2: { status: "idle", outboundsCount: 0, synced: false, syncedOutbounds: [], timestamp: null, error: null },
|
||||
};
|
||||
|
||||
@@ -90,7 +101,7 @@ export async function resetState(): Promise<AppState> {
|
||||
status: "running",
|
||||
currentStep: "initializing",
|
||||
novavps: { status: "idle", count: 0, timestamp: null, error: null },
|
||||
xray1: { status: "idle", outboundsCount: 0, synced: false, syncedOutbounds: [], timestamp: null, error: null },
|
||||
xray1: { status: "idle", outboundsCount: 0, synced: false, syncedOutbounds: [], pingResults: [], allPingsOk: false, timestamp: null, error: null },
|
||||
xray2: { status: "idle", outboundsCount: 0, synced: false, syncedOutbounds: [], timestamp: null, error: null },
|
||||
};
|
||||
await saveState(state);
|
||||
|
||||
37
src/ping.ts
Normal file
37
src/ping.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import net from "net";
|
||||
|
||||
export interface PingResult {
|
||||
tag: string;
|
||||
name: string;
|
||||
address: string;
|
||||
port: string;
|
||||
latency: number | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export function tcpPing(host: string, port: number, timeout = 5000): Promise<number | null> {
|
||||
return new Promise((resolve) => {
|
||||
const start = Date.now();
|
||||
const socket = new net.Socket();
|
||||
|
||||
socket.setTimeout(timeout);
|
||||
|
||||
socket.on("connect", () => {
|
||||
const latency = Date.now() - start;
|
||||
socket.destroy();
|
||||
resolve(latency);
|
||||
});
|
||||
|
||||
socket.on("timeout", () => {
|
||||
socket.destroy();
|
||||
resolve(null);
|
||||
});
|
||||
|
||||
socket.on("error", () => {
|
||||
socket.destroy();
|
||||
resolve(null);
|
||||
});
|
||||
|
||||
socket.connect(port, host);
|
||||
});
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import fs from "fs/promises";
|
||||
import { parseNovavps, getValidConnections } from "./novavps";
|
||||
import { parseXrayPanel, syncNovavpsToXray } from "./xray-panel";
|
||||
import { closeBrowser } from "./browser";
|
||||
import { tcpPing } from "./ping";
|
||||
import {
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
@@ -107,6 +108,7 @@ async function runParser(): Promise<void> {
|
||||
isRunning = true;
|
||||
|
||||
const state = await resetState();
|
||||
const existingNovavpsPath = path.resolve(__dirname, "..", "data", "novavps-connections.json");
|
||||
|
||||
try {
|
||||
const config = await loadConfig(process.env.NOVAVPS_URL || "");
|
||||
@@ -133,11 +135,23 @@ async function runParser(): Promise<void> {
|
||||
let novavpsConnections: Awaited<ReturnType<typeof parseNovavps>> = [];
|
||||
try {
|
||||
novavpsConnections = await parseNovavps(novavpsUrl);
|
||||
|
||||
if (novavpsConnections.length === 0) {
|
||||
try {
|
||||
const existingRaw = await fs.readFile(existingNovavpsPath, "utf-8");
|
||||
const existing = JSON.parse(existingRaw);
|
||||
if (Array.isArray(existing) && existing.length > 0) {
|
||||
console.log(`[novavps] Страница недоступна, сохранено ${existing.length} предыдущих подключений`);
|
||||
novavpsConnections = existing;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
state.novavps = {
|
||||
status: "success",
|
||||
status: novavpsConnections.length > 0 ? "success" : "error",
|
||||
count: novavpsConnections.length,
|
||||
timestamp: new Date().toISOString(),
|
||||
error: null,
|
||||
error: novavpsConnections.length === 0 ? "Нет данных" : null,
|
||||
};
|
||||
} catch (err) {
|
||||
state.novavps = {
|
||||
@@ -163,15 +177,18 @@ async function runParser(): Promise<void> {
|
||||
outboundsCount: xray.length,
|
||||
synced: false,
|
||||
syncedOutbounds: [],
|
||||
pingResults: panelStateKey === "xray1" ? [] : undefined,
|
||||
allPingsOk: panelStateKey === "xray1" ? false : undefined,
|
||||
timestamp: new Date().toISOString(),
|
||||
error: null,
|
||||
};
|
||||
} catch (err) {
|
||||
const errorState = {
|
||||
status: "error" as const,
|
||||
const errorState: import("./data-store").PanelStatus = {
|
||||
status: "error",
|
||||
outboundsCount: 0,
|
||||
synced: false,
|
||||
syncedOutbounds: [],
|
||||
allPingsOk: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
};
|
||||
@@ -193,6 +210,29 @@ async function runParser(): Promise<void> {
|
||||
const panelStateKey = panel.label as "xray1" | "xray2";
|
||||
state[panelStateKey].synced = true;
|
||||
state[panelStateKey].syncedOutbounds = syncedTags;
|
||||
|
||||
if (panel.label === "xray1") {
|
||||
state.currentStep = "pinging";
|
||||
await saveState(state);
|
||||
|
||||
const pingResults = [];
|
||||
for (let i = 0; i < valid.length; i++) {
|
||||
const conn = valid[i];
|
||||
const tag = `novavps${i + 1}`;
|
||||
const port = parseInt(conn.port) || 443;
|
||||
const latency = await tcpPing(conn.address, port);
|
||||
pingResults.push({
|
||||
tag,
|
||||
name: conn.name,
|
||||
address: conn.address,
|
||||
port: conn.port || String(port),
|
||||
latency,
|
||||
error: latency === null ? "Таймаут или ошибка" : null,
|
||||
});
|
||||
}
|
||||
state.xray1.pingResults = pingResults;
|
||||
state.xray1.allPingsOk = pingResults.every((p) => p.latency !== null);
|
||||
}
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? err.message : String(err);
|
||||
if (panel.label === "xray1") {
|
||||
@@ -203,6 +243,31 @@ async function runParser(): Promise<void> {
|
||||
}
|
||||
await saveState(state);
|
||||
}
|
||||
|
||||
if (panel.label === "xray2" && valid.length > 0 && state.xray1?.allPingsOk === true) {
|
||||
state.currentStep = "xray2_syncing";
|
||||
await saveState(state);
|
||||
|
||||
try {
|
||||
await syncNovavpsToXray(valid, panel.url, panel.username, panel.password, panel.label);
|
||||
state.xray2.synced = true;
|
||||
state.xray2.syncedOutbounds = valid.map((_, idx) => `novavps${idx + 1}`);
|
||||
} catch (err) {
|
||||
state.xray2.error = err instanceof Error ? err.message : String(err);
|
||||
}
|
||||
await saveState(state);
|
||||
} else if (panel.label === "xray2" && valid.length > 0) {
|
||||
console.log("[xray2] Пропускаю синхронизацию: не все пинги успешны");
|
||||
state.xray2 = {
|
||||
status: "error",
|
||||
outboundsCount: state.xray2.outboundsCount || 0,
|
||||
synced: false,
|
||||
syncedOutbounds: [],
|
||||
timestamp: new Date().toISOString(),
|
||||
error: "Пинг не пройден — синхронизация пропущена",
|
||||
};
|
||||
await saveState(state);
|
||||
}
|
||||
}
|
||||
|
||||
state.status = "completed";
|
||||
@@ -300,6 +365,15 @@ app.get("/api/data/xray", authMiddleware, async (_req: Request, res: Response) =
|
||||
(async () => {
|
||||
const config = await loadConfig(process.env.NOVAVPS_URL || "");
|
||||
|
||||
// Сброс stale-состояния "running" после перезапуска сервера
|
||||
const state = await loadState();
|
||||
if (state.status === "running") {
|
||||
state.status = "idle";
|
||||
state.currentStep = "";
|
||||
await saveState(state);
|
||||
console.log("[server] Сброшено состояние 'running' после перезапуска");
|
||||
}
|
||||
|
||||
app.listen(PORT, "0.0.0.0", () => {
|
||||
console.log(`[server] Web interface running on http://0.0.0.0:${PORT}`);
|
||||
console.log(`[server] NOVAVPS_URL: ${config.novavpsUrl || "(not set)"}`);
|
||||
|
||||
Reference in New Issue
Block a user