mirror of
https://github.com/andrey271192/amnezia_web-PRO.git
synced 2026-09-20 14:42:00 +00:00
Даты «Задать дату» (lastDisconnectedAt) и отключение по расписанию (scheduledTunnelDisconnectAt) тоже живут в clientsTable и терялись при затирании. Снимок хранит и их, но возвращает только когда запись реально затёрли (пропал creationDate) — иначе воскресало бы снятое в панели расписание. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
88 lines
4.0 KiB
JavaScript
88 lines
4.0 KiB
JavaScript
/**
|
||
* Страховка от затирания clientsTable приложением AmneziaVPN.
|
||
*
|
||
* Приложение при переподключении/перезагрузке сервера пересобирает clientsTable
|
||
* из awg0.conf: остаются только clientId и дефолтные имена «Client N», а имена,
|
||
* даты создания и last_config, которые ведёт панель, пропадают.
|
||
*
|
||
* Панель держит свою копию этих полей в DATA_DIR (том /data переживает
|
||
* пересоздание контейнера) и возвращает их на место при следующем чтении.
|
||
*/
|
||
import fs from "fs";
|
||
import path from "path";
|
||
|
||
/** Поля, которые панель сама никогда не стирает: пустое значение = потеря. */
|
||
export const STABLE_FIELDS = ["clientName", "creationDate", "last_config", "allowedIps"];
|
||
|
||
/**
|
||
* Даты, которые панель штатно снимает («Задать дату», отключение по расписанию).
|
||
* Их возвращаем только когда запись действительно затёрли, иначе воскресим
|
||
* снятое расписание.
|
||
*/
|
||
export const DATE_FIELDS = ["lastDisconnectedAt", "scheduledTunnelDisconnectAt"];
|
||
|
||
export const META_FIELDS = [...STABLE_FIELDS, ...DATE_FIELDS];
|
||
|
||
/** «Client 17» — дефолтное имя от приложения, оно не ценнее сохранённого. */
|
||
export function isPlaceholderName(name) {
|
||
return /^Client\s*\d+$/i.test(String(name || "").trim());
|
||
}
|
||
|
||
function isMeaningful(field, value) {
|
||
if (value == null || value === "") return false;
|
||
return field === "clientName" ? !isPlaceholderName(value) : true;
|
||
}
|
||
|
||
function metaFile(dataDir, profileId) {
|
||
const safe = String(profileId || "default").replace(/[^\w.-]/g, "_");
|
||
return path.join(dataDir, "clients-meta", `${safe}.json`);
|
||
}
|
||
|
||
export function readClientsMeta(dataDir, profileId) {
|
||
try {
|
||
const data = JSON.parse(fs.readFileSync(metaFile(dataDir, profileId), "utf8"));
|
||
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
||
} catch {
|
||
return {};
|
||
}
|
||
}
|
||
|
||
export function writeClientsMeta(dataDir, profileId, meta) {
|
||
const file = metaFile(dataDir, profileId);
|
||
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||
fs.writeFileSync(file, `${JSON.stringify(meta, null, 2)}\n`);
|
||
}
|
||
|
||
/**
|
||
* Возвращает клиентов с восстановленными полями и число восстановленных значений.
|
||
* Заодно обновляет снимок метаданных теми значениями, что сейчас на сервере;
|
||
* metaChanged=false — снимок писать не нужно (панель читает состояние часто).
|
||
*/
|
||
export function healClientsMeta(clients, meta) {
|
||
let restored = 0;
|
||
const before = JSON.stringify(meta);
|
||
const nextMeta = { ...meta };
|
||
const healed = clients.map((row) => {
|
||
const id = row?.clientId;
|
||
if (!id) return row;
|
||
const userData = { ...(row.userData || {}) };
|
||
const saved = nextMeta[id] || {};
|
||
// Приложение сносит creationDate целиком — по нему и опознаём затирание.
|
||
const wiped = !isMeaningful("creationDate", userData.creationDate) && isMeaningful("creationDate", saved.creationDate);
|
||
for (const field of wiped ? META_FIELDS : STABLE_FIELDS) {
|
||
if (!isMeaningful(field, userData[field]) && isMeaningful(field, saved[field])) {
|
||
userData[field] = saved[field];
|
||
restored++;
|
||
}
|
||
}
|
||
const keep = {};
|
||
for (const field of META_FIELDS) {
|
||
if (isMeaningful(field, userData[field])) keep[field] = userData[field];
|
||
}
|
||
if (Object.keys(keep).length) nextMeta[id] = keep;
|
||
else delete nextMeta[id];
|
||
return { ...row, userData };
|
||
});
|
||
return { clients: healed, restored, meta: nextMeta, metaChanged: JSON.stringify(nextMeta) !== before };
|
||
}
|