mirror of
https://github.com/andrey271192/amnezia_web-PRO.git
synced 2026-09-20 14:42:00 +00:00
fix: dedupe duplicate awg instances
This commit is contained in:
@@ -28,6 +28,10 @@
|
|||||||
3. Ручной JSON нужен только при нестандартных путях внутри AWG-контейнеров.
|
3. Ручной JSON нужен только при нестандартных путях внутри AWG-контейнеров.
|
||||||
4. После правок обновите страницу с **жёстким сбросом кэша** (Ctrl+Shift+R).
|
4. После правок обновите страницу с **жёстким сбросом кэша** (Ctrl+Shift+R).
|
||||||
|
|
||||||
|
## Инстансы задублировались
|
||||||
|
|
||||||
|
После переустановки старый `AWG_PROFILES` и сохранённые managed-инстансы могли указывать на один и тот же контейнер/конфиг с разными `id`. Начиная с **v1.2.32** панель дедуплицирует профили по `container + confPath + iface + clientsPath`, а также чистит дубли при чтении/сохранении `instances.json`. Обновите панель из GitHub и сделайте жёсткое обновление страницы.
|
||||||
|
|
||||||
## Лендинг не поднимается (порт 80 занят)
|
## Лендинг не поднимается (порт 80 занят)
|
||||||
|
|
||||||
При ошибке bind `:80` используйте при установке **`SKIP_LANDING=1`** или **`LANDING_PORT=8081`** — админка на `HOST_PORT` (например 8080) от этого не зависит.
|
При ошибке bind `:80` используйте при установке **`SKIP_LANDING=1`** или **`LANDING_PORT=8081`** — админка на `HOST_PORT` (например 8080) от этого не зависит.
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "amnezia-admin",
|
"name": "amnezia-admin",
|
||||||
"version": "1.2.31",
|
"version": "1.2.32",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "amnezia-admin",
|
"name": "amnezia-admin",
|
||||||
"version": "1.2.31",
|
"version": "1.2.32",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.21.2"
|
"express": "^4.21.2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "amnezia-admin",
|
"name": "amnezia-admin",
|
||||||
"version": "1.2.31",
|
"version": "1.2.32",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "amnezia_web-PRO — веб-панель AmneziaWG: несколько инстансов, WARP, каскад/Endpoint, экспорт .conf (Docker)",
|
"description": "amnezia_web-PRO — веб-панель AmneziaWG: несколько инстансов, WARP, каскад/Endpoint, экспорт .conf (Docker)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
48
server.js
48
server.js
@@ -164,7 +164,7 @@ function loadManagedProfiles() {
|
|||||||
const raw = fs.readFileSync(INSTANCES_FILE, "utf-8");
|
const raw = fs.readFileSync(INSTANCES_FILE, "utf-8");
|
||||||
const arr = JSON.parse(raw);
|
const arr = JSON.parse(raw);
|
||||||
if (!Array.isArray(arr)) return [];
|
if (!Array.isArray(arr)) return [];
|
||||||
return arr.map((m) => ({
|
const mapped = arr.map((m) => ({
|
||||||
id: String(m.id),
|
id: String(m.id),
|
||||||
label: String(m.label || m.id),
|
label: String(m.label || m.id),
|
||||||
container: String(m.container || m.id),
|
container: String(m.container || m.id),
|
||||||
@@ -181,6 +181,13 @@ function loadManagedProfiles() {
|
|||||||
variant: String(m.variant || "awg2"),
|
variant: String(m.variant || "awg2"),
|
||||||
port: Number(m.port) || null,
|
port: Number(m.port) || null,
|
||||||
}));
|
}));
|
||||||
|
const seen = new Set();
|
||||||
|
return mapped.filter((p) => {
|
||||||
|
const key = profileIdentityKey(p);
|
||||||
|
if (seen.has(key)) return false;
|
||||||
|
seen.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -188,15 +195,40 @@ function loadManagedProfiles() {
|
|||||||
|
|
||||||
function saveManagedProfiles(list) {
|
function saveManagedProfiles(list) {
|
||||||
fs.mkdirSync(path.dirname(INSTANCES_FILE), { recursive: true });
|
fs.mkdirSync(path.dirname(INSTANCES_FILE), { recursive: true });
|
||||||
fs.writeFileSync(INSTANCES_FILE, JSON.stringify(list, null, 2));
|
const seen = new Set();
|
||||||
|
const deduped = [];
|
||||||
|
for (const p of list) {
|
||||||
|
const key = profileIdentityKey(p);
|
||||||
|
if (seen.has(key)) continue;
|
||||||
|
seen.add(key);
|
||||||
|
deduped.push(p);
|
||||||
|
}
|
||||||
|
fs.writeFileSync(INSTANCES_FILE, JSON.stringify(deduped, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Effective profile set = env profiles + managed instances (deduped by id).
|
function profileIdentityKey(p) {
|
||||||
|
return [
|
||||||
|
String(p.container || "").trim(),
|
||||||
|
String(p.confPath || "").trim(),
|
||||||
|
String(p.iface || "").trim(),
|
||||||
|
String(p.clientsPath || "").trim(),
|
||||||
|
].join("|");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Effective profile set = env profiles + managed instances (deduped by id and target files).
|
||||||
function getProfiles() {
|
function getProfiles() {
|
||||||
const managed = loadManagedProfiles();
|
const managed = loadManagedProfiles();
|
||||||
const seen = new Set(ENV_PROFILES.map((p) => p.id));
|
const seenIds = new Set();
|
||||||
const out = [...ENV_PROFILES];
|
const seenTargets = new Set();
|
||||||
for (const m of managed) if (!seen.has(m.id)) { out.push(m); seen.add(m.id); }
|
const out = [];
|
||||||
|
for (const p of [...ENV_PROFILES, ...managed]) {
|
||||||
|
const id = String(p.id || "").trim();
|
||||||
|
const target = profileIdentityKey(p);
|
||||||
|
if ((id && seenIds.has(id)) || (target !== "|||" && seenTargets.has(target))) continue;
|
||||||
|
out.push(p);
|
||||||
|
if (id) seenIds.add(id);
|
||||||
|
if (target !== "|||") seenTargets.add(target);
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2873,6 +2905,7 @@ app.get("/api/instances", requireAuth, async (_req, res) => {
|
|||||||
const profiles = getProfiles();
|
const profiles = getProfiles();
|
||||||
const running = await listRunningContainerNames();
|
const running = await listRunningContainerNames();
|
||||||
const items = [];
|
const items = [];
|
||||||
|
const seenRuntimeTargets = new Set();
|
||||||
for (const prof of profiles) {
|
for (const prof of profiles) {
|
||||||
const isManaged = managedIds.has(prof.id) || prof.managed === true;
|
const isManaged = managedIds.has(prof.id) || prof.managed === true;
|
||||||
let container = prof.container;
|
let container = prof.container;
|
||||||
@@ -2894,6 +2927,9 @@ app.get("/api/instances", requireAuth, async (_req, res) => {
|
|||||||
} catch {
|
} catch {
|
||||||
runningNow = container ? running.includes(container) : false;
|
runningNow = container ? running.includes(container) : false;
|
||||||
}
|
}
|
||||||
|
const runtimeTarget = [container, prof.confPath, prof.iface, prof.clientsPath].join("|");
|
||||||
|
if (seenRuntimeTargets.has(runtimeTarget)) continue;
|
||||||
|
seenRuntimeTargets.add(runtimeTarget);
|
||||||
items.push({
|
items.push({
|
||||||
id: prof.id,
|
id: prof.id,
|
||||||
label: prof.label,
|
label: prof.label,
|
||||||
|
|||||||
Reference in New Issue
Block a user