fix(wireguard): поддержать IPv6 и исключить выключенные серверы
This commit is contained in:
1 parent
f3cbc32720
commit
4d3cb659c1
30 files changed
+2001
-82
No files matched your search
@@ -18,13 +18,13 @@
|
||||
"test:probes": "tsx src/routes/probes.test.ts && tsx src/routes/probes.cancel.test.ts",
|
||||
"test:exec": "tsx src/services/mikrotik-logs.test.ts && tsx src/routes/exec.test.ts && tsx src/plugins/auth.exec.test.ts",
|
||||
"test:auth": "tsx src/lib/permissions.test.ts && tsx src/plugins/auth.smoke.test.ts && tsx src/plugins/auth.ospf-optimizer.test.ts",
|
||||
"test:wireguard": "npx tsx src/services/wireguard-config.test.ts && npx tsx --test src/services/wireguard-import.test.ts src/services/wireguard-import-apply.test.ts src/services/wireguard-import-revision.test.ts && npx tsx src/routes/wireguard-import.test.ts",
|
||||
"test:wireguard": "npx tsx src/services/wireguard-config.test.ts && npx tsx src/services/wireguard-ipv6.test.ts && npx tsx --test src/services/wireguard-import.test.ts src/services/wireguard-import-apply.test.ts src/services/wireguard-import-revision.test.ts src/services/wireguard-delete.test.ts && npx tsx src/routes/wireguard-import.test.ts",
|
||||
"test:traffic-rate": "tsx src/services/traffic-rate.test.ts",
|
||||
"test:traffic-flow": "tsx src/routes/traffic-flow-query.test.ts && tsx src/services/traffic-flow-month.test.ts && tsx src/services/traffic-flow-parse.test.ts && tsx src/services/traffic-flow-ipv6-pipeline.test.ts && tsx src/services/traffic-flow-map-exporter.test.ts && tsx src/services/traffic-flow-ifaces.test.ts && tsx src/services/traffic-flow-ifindex.test.ts && tsx src/services/traffic-flow-dedup.test.ts && tsx src/services/traffic-flow-planes.test.ts && tsx src/services/traffic-flow-ip.test.ts && tsx src/services/traffic-flow-dest.test.ts && tsx src/services/traffic-flow-classify.test.ts && tsx src/services/traffic-flow-ripe.test.ts && tsx src/services/traffic-flow-brands.test.ts && tsx src/services/traffic-flow-ingest.test.ts && tsx src/services/traffic-flow-analytics.test.ts && tsx src/services/traffic-flow-map-hops.test.ts && tsx src/services/traffic-flow-purge.test.ts && tsx src/services/traffic-flow-geoip.test.ts && tsx src/services/traffic-flow-facts.test.ts && tsx src/services/traffic-flow-facts-filter.test.ts && tsx src/services/traffic-flow-facts-rebuild.test.ts && tsx src/services/statistics-aggregate.test.ts",
|
||||
"test:users": "tsx src/modules/users/iface-type.test.ts && tsx src/modules/users/bindings.test.ts && tsx src/modules/users/interface-inventory.test.ts && tsx src/routes/users-catalog.test.ts",
|
||||
"test:pg": "tsx src/db/sql-bind.test.ts && tsx src/db/sqlite-json.test.ts && tsx src/db/traffic-flags.test.ts && tsx src/db/pg-schema.test.ts && tsx src/services/config-revisions.test.ts",
|
||||
"test:recursive-routes": "tsx src/routes/recursive-routes.test.ts && tsx src/services/recursive-route-catalog.test.ts && tsx src/services/recursive-apply.test.ts",
|
||||
"test:config-sync": "tsx src/services/config-apply-plan.test.ts && tsx src/services/entity-snapshots.test.ts && tsx src/services/gre-ipv6.test.ts && tsx src/routes/gre-ipv6-validation.test.ts",
|
||||
"test:config-sync": "tsx src/services/config-apply-plan.test.ts && tsx src/services/entity-snapshots.test.ts && tsx src/services/gre-ipv6.test.ts && tsx src/routes/gre-ipv6-validation.test.ts && tsx src/services/network-disabled-server.test.ts",
|
||||
"test:ros-file": "tsx src/services/ros-file-contents.test.ts && tsx src/services/mikrotik-response.test.ts",
|
||||
"test:scheduler": "tsx src/services/scheduler-running.test.ts && tsx src/services/scheduler-recorded-run.test.ts && tsx src/routes/scheduler.test.ts",
|
||||
"test:backups": "tsx src/services/backup-scheduler-run.test.ts && tsx src/services/backup-restore.test.ts && tsx src/services/backup-deletion.test.ts && tsx src/services/backup-dto.test.ts && tsx src/services/backup-storage-update.test.ts && tsx src/services/backup-job-execution.test.ts && tsx src/routes/backups-operations.test.ts && tsx src/services/s3-backup-client.test.ts",
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
loadRevisionForRestore,
|
||||
type ConfigRevisionSource,
|
||||
} from "../services/config-revisions.js"
|
||||
import { parseWireguardSnapshot, planWireguardRestore } from "../services/entity-snapshots.js"
|
||||
import { parseWireguardSnapshot, planWireguardRestore, planStaticInterfaceAddresses, interfaceAddressPath, InterfaceAddressConflict } from "../services/entity-snapshots.js"
|
||||
import { executeRosOps } from "../services/ros-ops.js"
|
||||
import { parseDbServerId } from "../utils/server-id.js"
|
||||
import { z } from "zod"
|
||||
@@ -190,6 +190,17 @@ const wireguardRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
const client = MikrotikClient.fromServer(server)
|
||||
const d = parsed.data
|
||||
try {
|
||||
let addressOps: Parameters<typeof executeRosOps>[1] = []
|
||||
if (d.address !== undefined) {
|
||||
const state = await fetchWireguardRestoreState(server)
|
||||
const live = state.ifaces.find(i => i.rosId === rosIdParam(rosId))
|
||||
if (!live) return reply.status(404).send({ error: "Интерфейс не найден" })
|
||||
if (d.name && d.name !== live.name) return reply.status(409).send({ error: "Изменяйте имя и адрес WireGuard отдельными операциями" })
|
||||
const original = state.snapshot.interfaces.find(i => i.name === live.name)!
|
||||
const desired = original.innerAddresses!.filter(a => a.address !== original.address).map(a => ({ ...a }))
|
||||
if (d.address && !desired.some(a => a.address === d.address)) desired.push({ address: d.address, disabled: false, ...(interfaceAddressPath(d.address) === "/ipv6/address" ? { advertise: false } : {}) })
|
||||
addressOps = planStaticInterfaceAddresses(live.name, d.address, desired, state.addrs)
|
||||
}
|
||||
await client.patch(
|
||||
`/interface/wireguard/${encodeURIComponent(rosIdParam(rosId))}`,
|
||||
toRosBody({
|
||||
@@ -201,11 +212,12 @@ const wireguardRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
disabled: d.disabled === true ? "yes" : d.disabled === false ? "no" : undefined,
|
||||
}),
|
||||
)
|
||||
await executeRosOps(client, addressOps)
|
||||
await recordWireguard(server, "apply")
|
||||
return reply.send({ ok: true })
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
return reply.status(502).send({ error: `RouterOS: ${msg}` })
|
||||
return reply.status(e instanceof InterfaceAddressConflict ? 409 : 502).send({ error: `RouterOS: ${msg}` })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -220,7 +232,7 @@ const wireguardRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
return reply.send({ ok: true })
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
return reply.status(502).send({ error: `RouterOS: ${msg}` })
|
||||
return reply.status(e instanceof InterfaceAddressConflict ? 409 : 502).send({ error: `RouterOS: ${msg}` })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -344,6 +356,16 @@ const wireguardRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
const iface = await findIface(list.interfaces, String(server.id), body.interfaceName)
|
||||
if (!iface) return reply.status(404).send({ error: "Интерфейс не найден" })
|
||||
|
||||
if (body.format === "rsc" || body.format === "conf") {
|
||||
const state = await fetchWireguardRestoreState(server)
|
||||
const addresses = state.snapshot.interfaces.find(i => i.name === iface.name)?.innerAddresses
|
||||
if (!addresses) return reply.status(409).send({ error: "Интерфейс изменился. Обновите список перед экспортом" })
|
||||
if (addresses.length > 1 || addresses.some(a => a.disabled || a.advertise || a.noDad || a.fromPool || a.eui64)) {
|
||||
return reply.status(409).send({ error: "Экспорт не воспроизводит несколько адресов или специальные параметры. Используйте экспорт RouterOS" })
|
||||
}
|
||||
if ((addresses[0]?.address ?? "") !== (iface.address ?? "")) return reply.status(409).send({ error: "Адрес изменился. Обновите список перед экспортом" })
|
||||
}
|
||||
|
||||
if (body.format === "rsc") {
|
||||
const content = generateMikrotikRsc({
|
||||
name: iface.name,
|
||||
@@ -465,7 +487,7 @@ const wireguardRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
return reply.send({ ok: true, interfaces: list.interfaces })
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
return reply.status(502).send({ error: `RouterOS: ${msg}` })
|
||||
return reply.status(e instanceof InterfaceAddressConflict ? 409 : 502).send({ error: `RouterOS: ${msg}` })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ export interface WgSnapshotIface {
|
||||
mtu: number
|
||||
privateKey: string
|
||||
address: string
|
||||
innerAddresses?: GreSnapshotAddress[]
|
||||
comment: string
|
||||
disabled: boolean
|
||||
peers: WgSnapshotPeer[]
|
||||
@@ -119,11 +120,7 @@ export interface WgLivePeer {
|
||||
clientEndpoint: string
|
||||
}
|
||||
|
||||
export interface WgLiveAddr {
|
||||
rosId: string
|
||||
interfaceName: string
|
||||
address: string
|
||||
}
|
||||
export interface WgLiveAddr extends GreLiveAddr {}
|
||||
|
||||
export interface GreSnapshotTunnel {
|
||||
name: string
|
||||
@@ -184,13 +181,14 @@ export interface GreSnapshotAddress {
|
||||
eui64?: boolean
|
||||
}
|
||||
|
||||
export class GreRestoreConflict extends Error {}
|
||||
export class InterfaceAddressConflict extends Error {}
|
||||
export const GreRestoreConflict = InterfaceAddressConflict
|
||||
|
||||
export function greAddressPath(address: string): "/ip/address" | "/ipv6/address" {
|
||||
const parts = address.split("/")
|
||||
const family = isIP(parts[0] ?? "")
|
||||
if (!family || address.includes("%") || parts.length !== 2 || !/^\d+$/.test(parts[1] ?? "") || Number(parts[1]) > (family === 6 ? 128 : 32)) {
|
||||
throw new GreRestoreConflict("Некорректный внутренний адрес GRE: требуется IPv4/IPv6 с префиксом")
|
||||
throw new InterfaceAddressConflict("Некорректный адрес интерфейса: требуется IPv4/IPv6 с префиксом")
|
||||
}
|
||||
return family === 6 ? "/ipv6/address" : "/ip/address"
|
||||
}
|
||||
@@ -201,6 +199,9 @@ export function greAddressKey(address: string): string {
|
||||
return `${path === "/ipv6/address" ? new URL(`http://[${ip}]`).hostname : ip}/${Number(prefix)}`
|
||||
}
|
||||
|
||||
export const interfaceAddressPath = greAddressPath
|
||||
export const interfaceAddressKey = greAddressKey
|
||||
|
||||
// ── IPsec / IKEv2 (managed-объекты, маркер MikrotikManager:ipsec) ────────────
|
||||
|
||||
export interface IpsecSnapshotPeer {
|
||||
@@ -520,6 +521,7 @@ export function canonicalWireguardSnapshot(input: {
|
||||
mtu: num(iface.mtu, 1420),
|
||||
privateKey: str(iface.privateKey),
|
||||
address: str(iface.address),
|
||||
...(iface.innerAddresses === undefined ? {} : { innerAddresses: canonicalGreSnapshot({ tunnels: [{ name: iface.name, innerAddresses: iface.innerAddresses }] }).tunnels[0]?.innerAddresses ?? [] }),
|
||||
comment: str(iface.comment),
|
||||
disabled: Boolean(iface.disabled),
|
||||
peers: (iface.peers ?? []).map(canonicalPeer).sort((a, b) => a.publicKey.localeCompare(b.publicKey)),
|
||||
@@ -576,8 +578,8 @@ export function planWireguardRestore(
|
||||
}
|
||||
|
||||
for (const addr of current.addrs) {
|
||||
if (!wantedNames.has(addr.interfaceName)) {
|
||||
ops.push({ op: "delete", path: `/ip/address/${addr.rosId}` })
|
||||
if (!wantedNames.has(addr.interfaceName) && !addr.dynamic) {
|
||||
ops.push({ op: "delete", path: `${interfaceAddressPath(addr.address)}/${addr.rosId}` })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,17 +609,7 @@ export function planWireguardRestore(
|
||||
})
|
||||
}
|
||||
|
||||
const liveAddr = current.addrs.find((a) => a.interfaceName === want.name)
|
||||
if (want.address) {
|
||||
if (!liveAddr) {
|
||||
ops.push({ op: "put", path: "/ip/address", body: { address: want.address, interface: want.name } })
|
||||
} else if (liveAddr.address !== want.address) {
|
||||
ops.push({ op: "delete", path: `/ip/address/${liveAddr.rosId}` })
|
||||
ops.push({ op: "put", path: "/ip/address", body: { address: want.address, interface: want.name } })
|
||||
}
|
||||
} else if (liveAddr) {
|
||||
ops.push({ op: "delete", path: `/ip/address/${liveAddr.rosId}` })
|
||||
}
|
||||
ops.push(...planStaticInterfaceAddresses(want.name, want.address, want.innerAddresses, current.addrs))
|
||||
|
||||
for (const peer of want.peers) {
|
||||
if (!peer.publicKey) continue
|
||||
@@ -1014,26 +1006,32 @@ export function planGreRestore(
|
||||
ops.push({ op: "patch", path: `/interface/gre/${live.rosId}`, body })
|
||||
}
|
||||
|
||||
const liveAddrs = current.addrs.filter(a => a.interfaceName === want.name && !a.dynamic)
|
||||
if (liveAddrs.some(a => a.fromPool || a.eui64)) throw new GreRestoreConflict("GRE содержит адрес из IPv6-пула или EUI-64. Измените его отдельно в RouterOS")
|
||||
if (want.innerAddresses === undefined && liveAddrs.length > 1) {
|
||||
throw new GreRestoreConflict("Старая ревизия GRE не описывает все адреса интерфейса. Восстановление остановлено")
|
||||
ops.push(...planStaticInterfaceAddresses(want.name, want.localInnerIp, want.innerAddresses, current.addrs))
|
||||
}
|
||||
const addresses = want.innerAddresses ?? (want.localInnerIp ? [{ address: want.localInnerIp, disabled: false }] : [])
|
||||
|
||||
return ops
|
||||
}
|
||||
|
||||
export function planStaticInterfaceAddresses(name: string, primary: string, desired: GreSnapshotAddress[] | undefined, current: GreLiveAddr[]): RosWriteOp[] {
|
||||
const ops: RosWriteOp[] = []
|
||||
const liveAddrs = current.filter(a => a.interfaceName === name && !a.dynamic)
|
||||
if (liveAddrs.some(a => a.fromPool || a.eui64)) throw new InterfaceAddressConflict("Интерфейс содержит адрес из IPv6-пула или EUI-64. Измените его отдельно в RouterOS")
|
||||
if (desired === undefined && liveAddrs.length > 1) {
|
||||
throw new InterfaceAddressConflict("Старая ревизия не описывает все адреса интерфейса. Восстановление остановлено")
|
||||
}
|
||||
const addresses = desired ?? (primary ? [{ address: primary, disabled: false }] : [])
|
||||
const keys = new Set(addresses.map(a => greAddressKey(a.address)))
|
||||
if (keys.size !== addresses.length) throw new GreRestoreConflict("В ревизии GRE повторяются внутренние адреса")
|
||||
if (keys.size !== addresses.length) throw new InterfaceAddressConflict("В ревизии повторяются внутренние адреса")
|
||||
for (const addr of liveAddrs) {
|
||||
if (!keys.has(greAddressKey(addr.address))) ops.push({ op: "delete", path: `${greAddressPath(addr.address)}/${addr.rosId}` })
|
||||
}
|
||||
for (const addr of addresses) {
|
||||
const existing = liveAddrs.find(a => greAddressKey(a.address) === greAddressKey(addr.address))
|
||||
if (!existing) ops.push({ op: "put", path: greAddressPath(addr.address), body: greAddressBody(addr, want.name) })
|
||||
if (!existing) ops.push({ op: "put", path: greAddressPath(addr.address), body: greAddressBody(addr, name) })
|
||||
else if (Boolean(existing.disabled) !== addr.disabled || (addr.comment !== undefined && str(existing.comment) !== addr.comment) || (greAddressPath(addr.address) === "/ipv6/address" && (Boolean(existing.advertise) !== Boolean(addr.advertise) || (addr.noDad !== undefined && Boolean(existing.noDad) !== addr.noDad)))) {
|
||||
ops.push({ op: "patch", path: `${greAddressPath(addr.address)}/${existing.rosId}`, body: greAddressBody(addr, want.name) })
|
||||
ops.push({ op: "patch", path: `${greAddressPath(addr.address)}/${existing.rosId}`, body: greAddressBody(addr, name) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ops
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { getEnabledServerById } from "./wireguard-live.js"
|
||||
import { MikrotikClient } from "./mikrotik.js"
|
||||
import { pool, closePool } from "../db/index.js"
|
||||
import { initDatabase } from "../db/bootstrap.js"
|
||||
import { buildApp } from "../index.js"
|
||||
|
||||
try {
|
||||
for (const id of ["srv1", "1abc", "", "0", "-1", "1.5", "9007199254740993", "2147483648"]) assert.equal(await getEnabledServerById(id), null)
|
||||
const own = process.env.MMAPP_OWN_TEST_DB
|
||||
if (own) {
|
||||
assert(/^mm_accept_152_\d+$/.test(own))
|
||||
assert.equal((await pool.query("SELECT current_database() AS name")).rows[0].name, own)
|
||||
await initDatabase()
|
||||
const id = Number((await pool.query("INSERT INTO servers (name,host,enabled) VALUES ('disabled-network-fixture','127.0.0.1',false) RETURNING id")).rows[0].id)
|
||||
const app = await buildApp({ logger: false, startScheduler: false })
|
||||
const original = MikrotikClient.fromServer
|
||||
let contacts = 0, checks = 0
|
||||
try {
|
||||
MikrotikClient.fromServer = () => { contacts++; throw new Error("Disabled server was contacted") }
|
||||
const key = Buffer.alloc(32, 7).toString("base64")
|
||||
for (const type of ["home-router", "jump-host", "exit-node"]) {
|
||||
await pool.query("UPDATE servers SET type=$1 WHERE id=$2", [type, id])
|
||||
assert.equal(await getEnabledServerById(id), null)
|
||||
for (const path of ["/wireguard", "/gre/tunnels"]) {
|
||||
const r = await app.inject({ method: "GET", url: `/api${path}?serverId=${id}` })
|
||||
assert.equal(r.statusCode, 200)
|
||||
assert.deepEqual(r.json().failures, [])
|
||||
assert.deepEqual(path === "/wireguard" ? r.json().interfaces : r.json().tunnels, [])
|
||||
checks++
|
||||
}
|
||||
const cases = [
|
||||
{ method: "POST", path: "/wireguard/interfaces", payload: { serverId: id, name: "own", address: "fd00::1/64" } },
|
||||
{ method: "PATCH", path: `/wireguard/interfaces/${id}/*none`, payload: { address: "fd00::1/64" } },
|
||||
{ method: "DELETE", path: `/wireguard/interfaces/${id}/*none`, payload: undefined },
|
||||
{ method: "POST", path: "/wireguard/peers", payload: { serverId: id, interfaceName: "own", publicKey: key, allowedAddresses: ["fd00::2/128"] } },
|
||||
{ method: "POST", path: "/wireguard/export", payload: { serverId: id, interfaceName: "own", format: "rsc" } },
|
||||
{ method: "POST", path: "/wireguard/import", payload: { serverId: id, format: "conf", content: `[Interface]\nPrivateKey = ${key}\nAddress = fd00::1/64\n` } },
|
||||
{ method: "POST", path: "/gre/tunnels", payload: { serverId: String(id), name: "own", remoteAddress: "192.0.2.1", localInnerIp: "fd00::1/64" } },
|
||||
{ method: "PATCH", path: "/gre/tunnels", payload: { serverId: String(id), name: "own", localInnerIp: "fd00::1/64" } },
|
||||
{ method: "DELETE", path: "/gre/tunnels", payload: { serverId: String(id), name: "own" } },
|
||||
] as const
|
||||
for (const item of cases) {
|
||||
const r = await app.inject({ method: item.method, url: "/api" + item.path, payload: item.payload })
|
||||
assert.equal(r.statusCode, 404, type + " " + item.path + " " + r.body)
|
||||
checks++
|
||||
}
|
||||
}
|
||||
assert.equal(checks, 33)
|
||||
assert.equal(contacts, 0)
|
||||
await pool.query("UPDATE servers SET enabled=true WHERE id=$1", [id])
|
||||
assert.equal((await getEnabledServerById(id))?.id, id)
|
||||
console.log("network-disabled-server.test.ts: 33 API/SQL checks for three types, zero RouterOS contacts, enabled positive control passed")
|
||||
} finally { MikrotikClient.fromServer = original; await app.close() }
|
||||
} else console.log("network-disabled-server.test.ts: invalid IDs passed; own SQL/disabled API checks skipped (MMAPP_OWN_TEST_DB required)")
|
||||
} finally { await closePool() }
|
||||
@@ -225,8 +225,11 @@ export function parseMikrotikRsc(content: string): WgParsedConfig {
|
||||
continue
|
||||
}
|
||||
|
||||
if (lower.startsWith("/ip address add") || lower.startsWith("/ip/address add")) {
|
||||
if (lower.startsWith("/ip address add") || lower.startsWith("/ip/address add") || lower.startsWith("/ipv6 address add") || lower.startsWith("/ipv6/address add")) {
|
||||
const kv = parseKvLine(line)
|
||||
if (iface.address) throw new Error("Импорт нескольких адресов пока не поддерживается")
|
||||
if (kv.interface && kv.interface !== iface.name) throw new Error("Адрес относится к другому интерфейсу")
|
||||
if (kv["from-pool"] || kv["eui-64"] === "yes" || kv["no-dad"] === "yes" || kv.disabled === "yes" || kv.advertise === "yes") throw new Error("Импорт специальных параметров адреса не поддерживается")
|
||||
if (kv.address) iface.address = kv.address
|
||||
continue
|
||||
}
|
||||
@@ -327,8 +330,9 @@ export function generateMikrotikRsc(iface: WgExportIface): string {
|
||||
lines.push(``)
|
||||
|
||||
if (iface.address) {
|
||||
lines.push(`/ip address add \\`)
|
||||
lines.push(`${iface.address.includes(":") ? "/ipv6" : "/ip"} address add \\`)
|
||||
lines.push(` address=${iface.address} \\`)
|
||||
if (iface.address.includes(":")) lines.push(` advertise=no \\`)
|
||||
lines.push(` interface=${iface.name}`)
|
||||
lines.push(``)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ function fixture(opts: { failRead?: string; failDelete?: string; keep?: string;
|
||||
'/interface/wireguard': [{ '.id': '*A/1', name: 'wg-own' }, { '.id': '*B', name: 'wg-foreign' }],
|
||||
'/interface/wireguard/peers': [{ '.id': '*P', interface: 'wg-own' }, { '.id': '*Q', interface: '*A/1' }, { '.id': '*F', interface: 'wg-foreign' }],
|
||||
'/ip/address': [{ '.id': '*I', interface: 'wg-own' }, { '.id': '*J', interface: 'wg-foreign' }],
|
||||
'/ipv6/address': [{ '.id': '*I', interface: 'wg-own' }, { '.id': '*J', interface: 'wg-foreign' }, { '.id': '*D', interface: 'wg-own', dynamic: 'true' } as Row],
|
||||
}
|
||||
if (opts.missing) rows['/interface/wireguard'] = rows['/interface/wireguard'].filter(r => r.name !== 'wg-own')
|
||||
const deletes: string[] = []
|
||||
@@ -28,12 +29,12 @@ function fixture(opts: { failRead?: string; failDelete?: string; keep?: string;
|
||||
|
||||
test('removes named/id-associated peers and addresses before interface; preserves foreign objects', async () => {
|
||||
const f = fixture(); await deleteWireguardInterface(f.client, '*A/1')
|
||||
assert.deepEqual(f.deletes, ['/interface/wireguard/peers/*P', '/interface/wireguard/peers/*Q', '/ip/address/*I', '/interface/wireguard/*A%2F1'])
|
||||
assert.deepEqual(f.deletes, ['/interface/wireguard/peers/*P', '/interface/wireguard/peers/*Q', '/ip/address/*I', '/ipv6/address/*I', '/interface/wireguard/*A%2F1'])
|
||||
assert.deepEqual(f.rows['/interface/wireguard'], [{ '.id': '*B', name: 'wg-foreign' }])
|
||||
assert.deepEqual(f.rows['/interface/wireguard/peers'], [{ '.id': '*F', interface: 'wg-foreign' }])
|
||||
assert.deepEqual(f.rows['/ip/address'], [{ '.id': '*J', interface: 'wg-foreign' }])
|
||||
})
|
||||
for (const path of ['/interface/wireguard', '/interface/wireguard/peers', '/ip/address']) {
|
||||
for (const path of ['/interface/wireguard', '/interface/wireguard/peers', '/ip/address', '/ipv6/address']) {
|
||||
test(`read failure ${path} prevents every write`, async () => {
|
||||
const f = fixture({ failRead: path }); await assert.rejects(deleteWireguardInterface(f.client, '*A/1'), /Read forbidden/); assert.deepEqual(f.deletes, [])
|
||||
})
|
||||
@@ -47,7 +48,7 @@ test('partial failure is surfaced and interface remains available for retry', as
|
||||
await deleteWireguardInterface({ ...f.client, async delete(path) { const split = path.lastIndexOf('/'), base = path.slice(0, split), id = decodeURIComponent(path.slice(split + 1)); f.rows[base] = f.rows[base].filter(r => r['.id'] !== id) } }, '*A/1')
|
||||
assert.deepEqual(f.rows['/ip/address'], [{ '.id': '*J', interface: 'wg-foreign' }])
|
||||
})
|
||||
for (const path of ['/interface/wireguard', '/interface/wireguard/peers', '/ip/address']) {
|
||||
for (const path of ['/interface/wireguard', '/interface/wireguard/peers', '/ip/address', '/ipv6/address']) {
|
||||
test(`unconfirmed removal ${path} cannot report success`, async () => {
|
||||
const f = fixture({ keep: path }); await assert.rejects(deleteWireguardInterface(f.client, '*A/1'), /не подтвердил/)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { MikrotikClient } from "./mikrotik.js"
|
||||
|
||||
type RosRow = { ".id"?: string; name?: string; interface?: string }
|
||||
type RosRow = { ".id"?: string; name?: string; interface?: string; dynamic?: string }
|
||||
type Client = Pick<MikrotikClient, "get" | "delete">
|
||||
|
||||
/** RouterOS retains peer/address records when their WireGuard interface is removed. */
|
||||
@@ -10,13 +10,17 @@ export async function deleteWireguardInterface(client: Client, rosId: string): P
|
||||
if (!target?.name) throw new Error("Интерфейс WireGuard не найден; удаление не выполнено")
|
||||
const name = target.name
|
||||
const belongs = (row: RosRow) => row.interface === name || row.interface === rosId
|
||||
const [peers, addresses] = await Promise.all([
|
||||
const [peers, addresses, ipv6Addresses] = await Promise.all([
|
||||
client.get<RosRow[]>("/interface/wireguard/peers"),
|
||||
client.get<RosRow[]>("/ip/address"),
|
||||
client.get<RosRow[]>("/ipv6/address"),
|
||||
])
|
||||
if (![peers, addresses, ipv6Addresses].every(Array.isArray)) throw new Error("Не удалось прочитать все связанные объекты WireGuard")
|
||||
const staticAddress = (row: RosRow) => row.dynamic !== "true" && row.dynamic !== "yes"
|
||||
const groups = [
|
||||
{ path: "/interface/wireguard/peers", rows: peers.filter(belongs) },
|
||||
{ path: "/ip/address", rows: addresses.filter(belongs) },
|
||||
{ path: "/ipv6/address", rows: ipv6Addresses.filter(staticAddress).filter(belongs) },
|
||||
]
|
||||
// Finish all reads and validate identities before the first destructive request.
|
||||
if (groups.some(group => group.rows.some(row => !row[".id"]))) {
|
||||
@@ -26,15 +30,18 @@ export async function deleteWireguardInterface(client: Client, rosId: string): P
|
||||
for (const row of group.rows) await client.delete(`${group.path}/${encodeURIComponent(row[".id"]!)}`)
|
||||
}
|
||||
await client.delete(`/interface/wireguard/${encodeURIComponent(rosId)}`)
|
||||
const [remainingInterfaces, remainingPeers, remainingAddresses] = await Promise.all([
|
||||
const [remainingInterfaces, remainingPeers, remainingAddresses, remainingIpv6Addresses] = await Promise.all([
|
||||
client.get<RosRow[]>("/interface/wireguard"),
|
||||
client.get<RosRow[]>("/interface/wireguard/peers"),
|
||||
client.get<RosRow[]>("/ip/address"),
|
||||
client.get<RosRow[]>("/ipv6/address"),
|
||||
])
|
||||
if (![remainingInterfaces, remainingPeers, remainingAddresses, remainingIpv6Addresses].every(Array.isArray)) throw new Error("RouterOS не подтвердил полное удаление WireGuard")
|
||||
const hasRemaining = (rows: RosRow[], original: RosRow[]) => rows.some(row => belongs(row) || original.some(before => before[".id"] === row[".id"]))
|
||||
if (remainingInterfaces.some(row => row[".id"] === rosId || row.name === name)
|
||||
|| hasRemaining(remainingPeers, groups[0].rows)
|
||||
|| hasRemaining(remainingAddresses, groups[1].rows)) {
|
||||
|| hasRemaining(remainingAddresses, groups[1].rows)
|
||||
|| hasRemaining(remainingIpv6Addresses.filter(staticAddress), groups[2].rows)) {
|
||||
throw new Error("RouterOS не подтвердил полное удаление интерфейса WireGuard и связанных объектов")
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ function fixture() {
|
||||
const rows: Record<string, Row[]> = {
|
||||
[iface]: [{ ".id": "*F", name: "foreign-wg" }],
|
||||
[addresses]: [{ ".id": "*A", interface: "foreign-wg", address: "192.0.2.1/24" }],
|
||||
"/ipv6/address": [],
|
||||
[peers]: [{ ".id": "*P", interface: "foreign-wg", "public-key": "foreign-peer" }],
|
||||
}
|
||||
const baseline = structuredClone(rows), deletes: string[] = []
|
||||
@@ -35,6 +36,38 @@ function fixture() {
|
||||
}
|
||||
const state = (expected: WireguardImportApplyError["state"]) => (error: unknown) => error instanceof WireguardImportApplyError && error.state === expected
|
||||
|
||||
test("IPv6 import confirms normalized receipt, ignores automatic link-local, preserves IPv4 with identical ID", async () => {
|
||||
const f = fixture(), put = f.client.put
|
||||
f.rows[addresses].push({ ".id": "*OWN/2", interface: "foreign-wg", address: "192.0.2.8/24" })
|
||||
f.client.put = async (path, value) => {
|
||||
const result = await put(path, value)
|
||||
if (path === "/ipv6/address") f.rows[path].at(-1)!.address = "fd00:152::1/64"
|
||||
if (path === iface) f.rows["/ipv6/address"].push({ ".id": "*D", interface: "own-wg", address: "fe80::1/64", dynamic: "true" })
|
||||
return result
|
||||
}
|
||||
await applyWireguardImport(f.client, { ...parsed, interface: { ...parsed.interface, address: "fd00:0152:0:0:0:0:0:1/64" } }, body)
|
||||
assert(f.rows["/ipv6/address"].some(row => row.address === "fd00:152::1/64" && row.advertise === "no"))
|
||||
assert(f.rows[addresses].some(row => row[".id"] === "*OWN/2" && row.interface === "foreign-wg"))
|
||||
assert.deepEqual(f.deletes, [])
|
||||
})
|
||||
|
||||
test("IPv6 read failure prevents every import mutation", async () => {
|
||||
const f = fixture(), get = f.client.get
|
||||
f.client.get = async path => { if (path === "/ipv6/address") throw new Error("IPv6 denied"); return get(path) }
|
||||
await assert.rejects(applyWireguardImport(f.client, parsed, body), /IPv6 denied/)
|
||||
assert.equal(f.puts(), 0)
|
||||
})
|
||||
|
||||
test("rejected peer compensates only owned IPv6 address, not colliding IPv4 ID", async () => {
|
||||
const f = fixture(), put = f.client.put
|
||||
f.rows[addresses].push({ ".id": "*OWN/2", interface: "foreign-wg", address: "192.0.2.8/24" })
|
||||
f.client.put = async (path, value) => { if (path === peers) throw new MikrotikError(400, path, "rejected"); return put(path, value) }
|
||||
await assert.rejects(applyWireguardImport(f.client, { ...parsed, interface: { ...parsed.interface, address: "fd00:152::1/64" } }, body), state("rolled-back"))
|
||||
assert(f.deletes.includes("/ipv6/address/*OWN%2F2"))
|
||||
assert(!f.deletes.some(path => path.startsWith(addresses)))
|
||||
assert(f.rows[addresses].some(row => row[".id"] === "*OWN/2"))
|
||||
})
|
||||
|
||||
test("baseline read failure prevents every mutation", async () => {
|
||||
const f = fixture()
|
||||
f.client.get = async () => { throw new Error("Read unavailable") }
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { MikrotikError, type MikrotikClient } from "./mikrotik.js"
|
||||
import type { WgParsedConfig, WgParsedPeer } from "./wireguard-config.js"
|
||||
import { asRosArray, toRosBody } from "./wireguard-ros.js"
|
||||
import { interfaceAddressPath, interfaceAddressKey } from "./entity-snapshots.js"
|
||||
|
||||
type Client = Pick<MikrotikClient, "get" | "put" | "delete">
|
||||
type Row = Record<string, string>
|
||||
const paths = ["/interface/wireguard", "/ip/address", "/interface/wireguard/peers"] as const
|
||||
const paths = ["/interface/wireguard", "/ip/address", "/interface/wireguard/peers", "/ipv6/address"] as const
|
||||
type Created = { path: typeof paths[number]; id: string; identity: Record<string, string> }
|
||||
|
||||
export class WireguardImportApplyError extends Error {
|
||||
@@ -24,12 +25,17 @@ export async function applyWireguardImport(
|
||||
peerBody: (peer: WgParsedPeer, interfaceName: string) => Record<string, string>,
|
||||
): Promise<{ interfaceName: string; peersCreated: number }> {
|
||||
const name = parsed.interface.name
|
||||
const read = async () => Object.fromEntries(await Promise.all(paths.map(async path => [path, asRosArray<Row>(await client.get(path))]))) as Record<typeof paths[number], Row[]>
|
||||
const addressPath = parsed.interface.address ? interfaceAddressPath(parsed.interface.address) : undefined
|
||||
const read = async () => Object.fromEntries(await Promise.all(paths.map(async path => {
|
||||
const rows = await client.get(path)
|
||||
if (!Array.isArray(rows)) throw new Error("Неполный каталог WireGuard для подтверждения импорта")
|
||||
return [path, (rows as Row[]).filter(row => !path.endsWith("/address") || (row.dynamic !== "true" && row.dynamic !== "yes"))]
|
||||
}))) as Record<typeof paths[number], Row[]>
|
||||
const baseline = await read()
|
||||
if (baseline[paths[0]].some(row => row.name === name)) throw new WireguardImportApplyError("conflict")
|
||||
const created: Created[] = []
|
||||
let unconfirmed = false
|
||||
const owns = (row: Row | undefined, item: Created) => !!row && Object.entries(item.identity).every(([key, value]) => row[key] === value)
|
||||
const owns = (row: Row | undefined, item: Created) => !!row && Object.entries(item.identity).every(([key, value]) => key === "address" ? !!row.address && interfaceAddressKey(row.address) === interfaceAddressKey(value) : row[key] === value)
|
||||
const put = async (path: typeof paths[number], body: Record<string, string>, identity: Record<string, string>) => {
|
||||
let receipt: unknown
|
||||
try {
|
||||
@@ -49,8 +55,9 @@ export async function applyWireguardImport(
|
||||
}
|
||||
try {
|
||||
await put(paths[0], toRosBody({ name, "listen-port": String(parsed.interface.listenPort ?? 13231), mtu: String(parsed.interface.mtu ?? 1420), "private-key": parsed.interface.privateKey, comment: parsed.interface.comment, disabled: parsed.interface.disabled ? "yes" : undefined }), { name })
|
||||
if (parsed.interface.address) {
|
||||
await put(paths[1], { address: parsed.interface.address, interface: name }, { address: parsed.interface.address, interface: name })
|
||||
if (parsed.interface.address && addressPath) {
|
||||
const path = addressPath
|
||||
await put(path, { address: parsed.interface.address, interface: name, ...(path === "/ipv6/address" ? { advertise: "no" } : {}) }, { address: parsed.interface.address, interface: name })
|
||||
}
|
||||
for (const peer of parsed.peers) {
|
||||
await put(paths[2], peerBody(peer, name), { interface: name, "public-key": peer.publicKey })
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { MikrotikClient } from "./mikrotik.js"
|
||||
import { fetchWireguardRestoreState } from "./wireguard-live.js"
|
||||
import { putIpAddress } from "./wireguard-ros.js"
|
||||
import { canonicalWireguardSnapshot, planWireguardRestore, InterfaceAddressConflict } from "./entity-snapshots.js"
|
||||
import { parseWireguardImport } from "./wireguard-import.js"
|
||||
import { generateMikrotikRsc } from "./wireguard-config.js"
|
||||
import { wgCreateInterfaceSchema, wgPatchInterfaceSchema } from "@mmapp/contracts/wireguard"
|
||||
|
||||
const original = MikrotikClient.fromServer
|
||||
const calls: string[] = []
|
||||
const raw: Record<string, unknown[]> = {
|
||||
"/interface/wireguard": [{ ".id": "*w", name: "wg-own", "private-key": "" }],
|
||||
"/interface/wireguard/peers": [],
|
||||
"/ip/address": [{ ".id": "*1", interface: "wg-own", address: "10.1.0.1/30" }],
|
||||
"/ipv6/address": [{ ".id": "*1", interface: "wg-own", address: "fd00:152::1/64", advertise: "true", comment: "own", "no-dad": "true" }, { ".id": "*2", interface: "wg-own", address: "fd00:152::2/64", disabled: "true" }, { ".id": "*3", interface: "wg-own", address: "fe80::1/64", dynamic: "true" }],
|
||||
}
|
||||
try {
|
||||
MikrotikClient.fromServer = () => ({ get: async (path: string) => { calls.push(path); return structuredClone(raw[path]) } }) as unknown as MikrotikClient
|
||||
const state = await fetchWireguardRestoreState({ id: 1 } as Parameters<typeof fetchWireguardRestoreState>[0])
|
||||
assert.equal(state.addrs.length, 3)
|
||||
assert.equal(state.snapshot.interfaces[0]?.innerAddresses?.length, 3)
|
||||
assert.equal(planWireguardRestore(state.snapshot, state).filter(op => op.path.includes("/address")).length, 0)
|
||||
const legacy = canonicalWireguardSnapshot({ interfaces: [{ name: "wg-own", address: "fd00:152::1/64" }] })
|
||||
assert.throws(() => planWireguardRestore(legacy, state), InterfaceAddressConflict)
|
||||
const desired = structuredClone(state.snapshot)
|
||||
desired.interfaces[0]!.innerAddresses = [{ address: "fd00:152::9/64", disabled: false }]
|
||||
const ops = planWireguardRestore(desired, state)
|
||||
assert(ops.some(op => op.op === "delete" && op.path === "/ip/address/*1"))
|
||||
assert(ops.some(op => op.op === "delete" && op.path === "/ipv6/address/*1"))
|
||||
assert(ops.some(op => op.op === "delete" && op.path === "/ipv6/address/*2"))
|
||||
assert(!ops.some(op => op.path.endsWith("/*3")))
|
||||
assert(ops.some(op => op.op === "put" && op.path === "/ipv6/address" && op.body.advertise === "no"))
|
||||
await putIpAddress({ put: async (path: string, body: Record<string, string>) => { assert.equal(path, "/ipv6/address"); assert.equal(body.advertise, "no") } } as unknown as MikrotikClient, "fd00::1/64", "wg-own")
|
||||
MikrotikClient.fromServer = () => ({ get: async (path: string) => { if (path === "/ipv6/address") throw new Error("Unauthorized"); return [] } }) as unknown as MikrotikClient
|
||||
await assert.rejects(fetchWireguardRestoreState({ id: 1 } as Parameters<typeof fetchWireguardRestoreState>[0]), /Unauthorized/)
|
||||
} finally { MikrotikClient.fromServer = original }
|
||||
const key = Buffer.alloc(32, 7).toString("base64")
|
||||
const rsc = generateMikrotikRsc({ name: "wg-own", mtu: 1400, address: "fd00:152::1/64", privateKey: key, peers: [] })
|
||||
assert(rsc.includes("/ipv6 address add"))
|
||||
assert(rsc.includes("advertise=no"))
|
||||
assert.equal(parseWireguardImport(rsc, "rsc").interface.address, "fd00:152::1/64")
|
||||
assert.throws(() => parseWireguardImport(rsc.replace("interface=wg-own", "interface=foreign"), "rsc"), /другому интерфейсу/)
|
||||
assert.throws(() => parseWireguardImport(rsc + "\n/ipv6 address add address=fd00::2/64 interface=wg-own", "rsc"), /нескольких/)
|
||||
assert.throws(() => parseWireguardImport(rsc.replace("advertise=no", "advertise=yes"), "rsc"), /специальных/)
|
||||
for (const address of ["fd00::1", "fd00::1/129", "bad/64", "10.0.0.1/33"]) {
|
||||
assert(!wgCreateInterfaceSchema.safeParse({ serverId: 1, name: "own", address }).success)
|
||||
assert(!wgPatchInterfaceSchema.safeParse({ address }).success)
|
||||
}
|
||||
assert(wgCreateInterfaceSchema.safeParse({ serverId: 1, name: "own", address: "fd00::1/64" }).success)
|
||||
assert(wgPatchInterfaceSchema.safeParse({ address: "" }).success)
|
||||
console.log("WireGuard IPv6: dual-stack static snapshot, family restore, legacy conflict, failed read, native IPv6 PUT, RSC roundtrip and contracts passed")
|
||||
@@ -1,8 +1,9 @@
|
||||
import { parseWireGuardBytes as parseBytes } from "./wireguard-observed-values.js"
|
||||
import {boundedCompleteCount,completeCollectionCount} from "./complete-count.js"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { and, eq } from "drizzle-orm"
|
||||
import { db } from "../db/index.js"
|
||||
import { servers } from "../db/schema.js"
|
||||
import { parseDbServerId } from "../utils/server-id.js"
|
||||
import { MikrotikClient } from "./mikrotik.js"
|
||||
import type { WgIfaceDto, WgPeerDto } from "@mmapp/contracts/wireguard"
|
||||
import {
|
||||
@@ -52,6 +53,12 @@ interface RosIpAddress {
|
||||
address?: string
|
||||
interface?: string
|
||||
disabled?: string
|
||||
dynamic?: string
|
||||
advertise?: string
|
||||
comment?: string
|
||||
"no-dad"?: string
|
||||
"from-pool"?: string
|
||||
"eui-64"?: string
|
||||
}
|
||||
|
||||
function mapPeer(p: RosWireGuardPeer, idx: number): WgPeerDto {
|
||||
@@ -121,11 +128,14 @@ async function fetchForServer(
|
||||
includePrivateKey: boolean,
|
||||
): Promise<WgIfaceDto[]> {
|
||||
const client = MikrotikClient.fromServer(server)
|
||||
const [ifacesRaw, peersRaw, addrsRaw] = await Promise.all([
|
||||
const [ifacesRaw, peersRaw, v4Raw, v6Raw] = await Promise.all([
|
||||
client.get<RosWireGuard[]>("/interface/wireguard"),
|
||||
client.get<RosWireGuardPeer[]>("/interface/wireguard/peers"),
|
||||
client.get<RosIpAddress[]>("/ip/address").catch(() => [] as RosIpAddress[]),
|
||||
client.get<RosIpAddress[]>("/ip/address"),
|
||||
client.get<RosIpAddress[]>("/ipv6/address"),
|
||||
])
|
||||
if (![ifacesRaw, peersRaw, v4Raw, v6Raw].every(Array.isArray)) throw new Error("RouterOS: неполный каталог WireGuard/адресов")
|
||||
const addrsRaw = [...v4Raw, ...v6Raw].sort((a, b) => (a.address ?? "").localeCompare(b.address ?? ""))
|
||||
|
||||
const peersByIface = new Map<string, WgPeerDto[]>()
|
||||
peersRaw.forEach((p, idx) => {
|
||||
@@ -138,7 +148,7 @@ async function fetchForServer(
|
||||
|
||||
const addrByIface = new Map<string, string>()
|
||||
for (const a of addrsRaw) {
|
||||
if (a.disabled === "true" || a.disabled === "yes") continue
|
||||
if (a.disabled === "true" || a.disabled === "yes" || a.dynamic === "true" || a.dynamic === "yes") continue
|
||||
const iface = (a.interface ?? "").trim()
|
||||
const addr = (a.address ?? "").trim()
|
||||
if (iface && addr && !addrByIface.has(iface)) addrByIface.set(iface, addr)
|
||||
@@ -164,11 +174,14 @@ export async function fetchWireguardRestoreState(server: ServerRow): Promise<{
|
||||
snapshot: WgSnapshot
|
||||
}> {
|
||||
const client = MikrotikClient.fromServer(server)
|
||||
const [ifacesRaw, peersRaw, addrsRaw] = await Promise.all([
|
||||
const [ifacesRaw, peersRaw, v4Raw, v6Raw] = await Promise.all([
|
||||
client.get<RosWireGuard[]>("/interface/wireguard"),
|
||||
client.get<RosWireGuardPeer[]>("/interface/wireguard/peers"),
|
||||
client.get<RosIpAddress[]>("/ip/address").catch(() => [] as RosIpAddress[]),
|
||||
client.get<RosIpAddress[]>("/ip/address"),
|
||||
client.get<RosIpAddress[]>("/ipv6/address"),
|
||||
])
|
||||
if (![ifacesRaw, peersRaw, v4Raw, v6Raw].every(Array.isArray)) throw new Error("RouterOS: неполный каталог WireGuard/адресов")
|
||||
const addrsRaw = [...v4Raw, ...v6Raw].sort((a, b) => Number(a.disabled === "true" || a.disabled === "yes") - Number(b.disabled === "true" || b.disabled === "yes") || (a.address ?? "").localeCompare(b.address ?? ""))
|
||||
|
||||
const ifaces: WgLiveIface[] = (Array.isArray(ifacesRaw) ? ifacesRaw : []).map((w) => ({
|
||||
name: (w.name ?? "").trim(),
|
||||
@@ -205,13 +218,15 @@ export async function fetchWireguardRestoreState(server: ServerRow): Promise<{
|
||||
|
||||
const addrs: WgLiveAddr[] = []
|
||||
for (const a of Array.isArray(addrsRaw) ? addrsRaw : []) {
|
||||
if (a.disabled === "true" || a.disabled === "yes") continue
|
||||
if (a.dynamic === "true" || a.dynamic === "yes") continue
|
||||
const iface = (a.interface ?? "").trim()
|
||||
const address = (a.address ?? "").trim()
|
||||
const rosId = String(a[".id"] ?? "")
|
||||
if (!iface || !address || !rosId) continue
|
||||
if (!ifaces.some((i) => i.name === iface)) continue
|
||||
addrs.push({ rosId, interfaceName: iface, address })
|
||||
addrs.push({ rosId, interfaceName: iface, address, disabled: a.disabled === "true" || a.disabled === "yes", comment: a.comment ?? "",
|
||||
...(address.includes(":") ? { advertise: a.advertise === "true" || a.advertise === "yes", noDad: a["no-dad"] === "true" || a["no-dad"] === "yes", fromPool: a["from-pool"] ?? "", eui64: a["eui-64"] === "true" || a["eui-64"] === "yes" } : {}),
|
||||
})
|
||||
}
|
||||
|
||||
const snapshot = canonicalWireguardSnapshot({
|
||||
@@ -221,6 +236,7 @@ export async function fetchWireguardRestoreState(server: ServerRow): Promise<{
|
||||
mtu: iface.mtu,
|
||||
privateKey: iface.privateKey,
|
||||
address: addrs.find((a) => a.interfaceName === iface.name)?.address ?? "",
|
||||
innerAddresses: addrs.filter(a => a.interfaceName === iface.name).map(a => ({ address: a.address, disabled: Boolean(a.disabled), comment: a.comment, ...(a.advertise === undefined ? {} : { advertise: a.advertise, noDad: a.noDad, fromPool: a.fromPool, eui64: a.eui64 }) })),
|
||||
comment: iface.comment,
|
||||
disabled: iface.disabled,
|
||||
peers: peers.filter((p) => p.interfaceName === iface.name),
|
||||
@@ -247,11 +263,11 @@ export async function listWireGuardInterfaces(opts?: {
|
||||
const includePrivateKey = opts?.includePrivateKey === true
|
||||
let serverRows: ServerRow[]
|
||||
if (opts?.serverId) {
|
||||
const id = Number.parseInt(String(opts.serverId), 10)
|
||||
if (!Number.isFinite(id)) {
|
||||
const id = parseDbServerId(opts.serverId)
|
||||
if (id === null || !Number.isSafeInteger(id) || id > 2147483647) {
|
||||
return { interfaces: [], failures: [{ serverId: String(opts.serverId), error: "Некорректный serverId" }] }
|
||||
}
|
||||
const row = (await db.select().from(servers).where(eq(servers.id, id)).limit(1))[0]
|
||||
const row = (await db.select().from(servers).where(and(eq(servers.id, id), eq(servers.enabled, true))).limit(1))[0]
|
||||
serverRows = row ? [row] : []
|
||||
} else {
|
||||
serverRows = await db.select().from(servers).where(eq(servers.enabled, true))
|
||||
@@ -278,9 +294,9 @@ export async function listWireGuardInterfaces(opts?: {
|
||||
export async function countWireGuardInterfaces():Promise<number|undefined>{return boundedCompleteCount(()=>listWireGuardInterfaces({ includePrivateKey: false }),value=>completeCollectionCount(value,"interfaces"))}
|
||||
|
||||
export async function getEnabledServerById(serverId: string | number) {
|
||||
const id = typeof serverId === "number" ? serverId : Number.parseInt(String(serverId), 10)
|
||||
if (!Number.isFinite(id)) return null
|
||||
return (await db.select().from(servers).where(eq(servers.id, id)).limit(1))[0] ?? null
|
||||
const id = parseDbServerId(serverId)
|
||||
if (id === null || !Number.isSafeInteger(id) || id > 2147483647) return null
|
||||
return (await db.select().from(servers).where(and(eq(servers.id, id), eq(servers.enabled, true))).limit(1))[0] ?? null
|
||||
}
|
||||
|
||||
export type CatalogWgPeer = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { MikrotikClient } from "./mikrotik.js"
|
||||
import { interfaceAddressPath } from "./entity-snapshots.js"
|
||||
|
||||
/** Общие PUT iface / peer / address для `/wireguard` и traffic-flow overlay. */
|
||||
export function toRosBody(obj: Record<string, string | undefined>): Record<string, string> {
|
||||
@@ -31,7 +32,8 @@ export async function putIpAddress(
|
||||
address: string,
|
||||
iface: string,
|
||||
): Promise<void> {
|
||||
await client.put("/ip/address", { address, interface: iface })
|
||||
const path = interfaceAddressPath(address)
|
||||
await client.put(path, { address, interface: iface, ...(path === "/ipv6/address" ? { advertise: "no" } : {}) })
|
||||
}
|
||||
|
||||
export async function putWireguardPeer(
|
||||
|
||||
@@ -157,10 +157,10 @@ function WgCreateSheet({
|
||||
|
||||
{form.showAdvanced && (
|
||||
<div className="flex flex-col gap-4">
|
||||
<FormField label="IP на интерфейсе" hint="/ip address add, например 10.210.0.1/30">
|
||||
<FormField label="IP на интерфейсе" hint="IPv4 или IPv6 с префиксом, например fd00::1/64">
|
||||
<Input
|
||||
className="font-mono"
|
||||
placeholder="10.210.0.1/30"
|
||||
placeholder="IPv4/IPv6 с префиксом"
|
||||
aria-label="IP на интерфейсе" value={form.address}
|
||||
onChange={(e) => set("address", e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# UX/UI: протокол внешней приёмки 12.5
|
||||
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.151 — GRE IPv6 static-address lifecycle через приложение: чтение/create/PATCH/history restore/delete, дополнительные/выключенные адреса, family-safe IDs и legacy409.15 профильных E2E, source types/ESLint, обе production сборки, 984 compiled parity и real native API/SQL/Ping3/3×2/cleanup прошли. Runtime backend/frontend ux-gre151 healthy;12.5 открыт. Следующий152 — application WireGuard IPv6 address lifecycle, включая импорт/историю/удаление; работать укрупнённым разделом без повторения принятого GRE.
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.152 — WireGuard IPv6 static lifecycle/create/PATCH/history/delete и single-address RSC/conf export/import.29 real API cases, native handshake/Ping3/3×2,33 disabled-server API/SQL cases всех трёх типов с zero ROS contacts,56 backend tests/профили GRE и контейнеров/live maps,10 E2E/types/ESLint/builds/984 parity/live PC/mobile passed. Runtime backend/frontend ux-wg152 healthy;12.5 открыт. Следующий153 — OSPF/BFD sustained gate: BFD-only loss при сохранённых OSPF Hello на собственных GRE, восстановление и independent cleanup.
|
||||
|
||||
IPsec78: реальный PSK responder established, mode-config198.18.78.10 и online=true подтверждены API; точные собственные объекты удалены. Сертификатный/DN online, late failure и history restore остаются открытыми. [Отчёт](ux-ui/acceptance/ipsec-mode-config-2026-10-09.json), [журнал](ux-ui-progress.md).
|
||||
|
||||
@@ -696,7 +696,7 @@ flowchart LR
|
||||
|
||||
## 12.5.77 — основной блок IPsec (2026-10-09)
|
||||
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.151 — GRE IPv6 static-address lifecycle через приложение: чтение/create/PATCH/history restore/delete, дополнительные/выключенные адреса, family-safe IDs и legacy409.15 профильных E2E, source types/ESLint, обе production сборки, 984 compiled parity и real native API/SQL/Ping3/3×2/cleanup прошли. Runtime backend/frontend ux-gre151 healthy;12.5 открыт. Следующий152 — application WireGuard IPv6 address lifecycle, включая импорт/историю/удаление; работать укрупнённым разделом без повторения принятого GRE.
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.152 — WireGuard IPv6 static lifecycle/create/PATCH/history/delete и single-address RSC/conf export/import.29 real API cases, native handshake/Ping3/3×2,33 disabled-server API/SQL cases всех трёх типов с zero ROS contacts,56 backend tests/профили GRE и контейнеров/live maps,10 E2E/types/ESLint/builds/984 parity/live PC/mobile passed. Runtime backend/frontend ux-wg152 healthy;12.5 открыт. Следующий153 — OSPF/BFD sustained gate: BFD-only loss при сохранённых OSPF Hello на собственных GRE, восстановление и independent cleanup.
|
||||
|
||||
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
|
||||
|
||||
@@ -1649,3 +1649,23 @@ Create/PATCH/restore/delete выбирают /ip/address или /ipv6/address п
|
||||
Протоколы: [native/cleanup](ux-ui/acceptance/gre-ipv6-native-2026-10-11.json), [API/history](ux-ui/acceptance/gre-ipv6-api-history-2026-10-11.json), [первый запуск](ux-ui/acceptance/gre-ipv6-initial-cleanup-2026-10-11.json), [compiled parity](ux-ui/acceptance/gre-ipv6-compiled-2026-10-11.json), [live UI](ux-ui/acceptance/gre-ipv6-smoke-2026-10-11.json).
|
||||
|
||||
Границы: IPv6 ULA inner по IPv4 GRE outer; IPv6 outer/public WAN, scale, длительное восстановление и from-pool/EUI-64 не приняты. Полная регрессия после139 не повторялась: выполнен профиль изменённых областей. Следующий152 — WireGuard IPv6 lifecycle;12.5 остаётся открытым.
|
||||
|
||||
## 12.5.152 — IPv6 lifecycle WireGuard и общий допуск выключенных серверов (2026-10-11)
|
||||
|
||||
WireGuard читает оба address catalogs; ошибки/не-массивы не превращаются в пустые данные. Catalog показывает активный статический адрес, новые snapshots сохраняют все статические адреса с disabled/advertise/comment/no-dad. Dynamic link-local исключён из управления. Create и PATCH address, history restore и delete выбирают семейство по CIDR; дополнительные адреса сохранены, legacy snapshot с несколькими текущими адресами409. Общий адресный planner переиспользован GRE/WireGuard; constructor alias GreRestoreConflict совместим. PATCH не переименовывает одновременно интерфейс и адрес.
|
||||
|
||||
Import подтверждает receipts в /ip/address и /ipv6/address раздельно, normalized IPv6 identities принимаются, automatic link-local не считается чужим static child. Lost/unconfirmed receipt не разрешает удаление по одному имени; rollback не удаляет IPv4 с совпавшим IPv6 ID. IPv6 RSC export использует /ipv6 address add +advertise=no, parser распознаёт IPv6 address line. Multiple/special address import и export явно отклоняются вместо тихой потери данных. Это single-address literal import/export, не произвольный RouterOS configuration codec. Существующий ReUI create sheet получил подсказку IPv4/IPv6; структура/Frame/base-nova сохранены.
|
||||
|
||||
При native приёмке обнаружено: getEnabledServerById ранее проверял только ID. Теперь общий getter допускает только enabled=true и полный корректный DB ID; WireGuard selected catalog также исключает disabled. Это влияет на использующих getter GRE/WireGuard/firewall/containers/traffic/overlay. Постоянный opt-in test network-disabled-server.test.ts делает33 API/SQL cases: три типа Home Router/Jump Host/Exit Node ×11 calls, factory любого ROS client запрещена, contacts=0, enabled positive control проходит.8 malformed IDs проверяются до SQL, включая unsafe/выход за integer DB range. Без MMAPP_OWN_TEST_DB SQL часть честно skipped; в собственном PG стенде выполнена и база удалена.
|
||||
|
||||
Проверки:56 node:test cases import/rollback/revision/delete, service/config/HTTP scripts, GRE config-sync и14 invalid GRE requests, containers/live-maps profiles;10 E2E (WireGuard IPv6/error-retained input PC/mobile, tables/menus и GRE smoke после общего planner), source types/ESLint; обе production сборки,984 emitted outputs совпали с окончательным backend.
|
||||
|
||||
Native изолированная PG + production handlers + оба CHR:29 API cases, native handshake0s и Ping3/3 в обе стороны после IPv6 invalid=false; create/PATCH/IPv6→IPv4/clear, multiple-address history restore, legacy409, export multiple409, RSC/conf single IPv6 export и реальный import/delete, disabled GET empty/PATCH404 с forbidden factory. После каждого запуска9 static collections×2 совпали, собственные интерфейсы/peers/адреса и PG базы удалены. Native source/cleanup не меняли пароли/public/default routes. Background polling главного приложения продолжался; отсутствие всех background SQL writes не заявляется.
|
||||
|
||||
Первый сценарий остановился из-за ошибки теста: ожидал200 от POST peer, фактический корректный201. После исправления сценарий прошёл весь native lifecycle, но завершающий disabled PATCH вернул500 из-за реального дефекта общего getter. Оба отрицательных протокола сохранены; getter исправлен,33 isolated API/SQL cases и полный native сценарий на финальном образе прошли. Frontend повторно не собирался из-за backend-only gate delta.
|
||||
|
||||
Runtime backend/frontend ux-wg152 healthy; deployed live read-only catalog/create-sheet на1440/320 passed после cleanup. Browser mutations покрыты controlled E2E, реальные RouterOS mutations — production handlers в выделенной базе. IPv6 ULA inner по IPv4 WireGuard outer, не IPv6 public WAN/client OS/throughput/scale/lost ACK physical acceptance. From-pool/EUI-64/multiple-address import/export остаются явными ограничениями; full regression после139 не повторялась.
|
||||
|
||||
Протоколы: [native/cleanup](ux-ui/acceptance/wireguard-ipv6-native-2026-10-11.json), [API/history/import/export](ux-ui/acceptance/wireguard-ipv6-api-history-2026-10-11.json), [33 SQL/API gates](ux-ui/acceptance/network-disabled-sql-2026-10-11.json), [до исправления getter](ux-ui/acceptance/wireguard-ipv6-before-gate-api-2026-10-11.json), [compiled](ux-ui/acceptance/wireguard-ipv6-compiled-2026-10-11.json), [live UI](ux-ui/acceptance/wireguard-ipv6-smoke-2026-10-11.json).
|
||||
|
||||
Следующий153 — sustained OSPF/BFD-only gate и recovery на собственных GRE;12.5 остаётся открытым.
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
# План реализации UX/UI по пятичасовым окнам
|
||||
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.151 — GRE IPv6 static-address lifecycle через приложение: чтение/create/PATCH/history restore/delete, дополнительные/выключенные адреса, family-safe IDs и legacy409.15 профильных E2E, source types/ESLint, обе production сборки, 984 compiled parity и real native API/SQL/Ping3/3×2/cleanup прошли. Runtime backend/frontend ux-gre151 healthy;12.5 открыт. Следующий152 — application WireGuard IPv6 address lifecycle, включая импорт/историю/удаление; работать укрупнённым разделом без повторения принятого GRE.
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.152 — WireGuard IPv6 static lifecycle/create/PATCH/history/delete и single-address RSC/conf export/import.29 real API cases, native handshake/Ping3/3×2,33 disabled-server API/SQL cases всех трёх типов с zero ROS contacts,56 backend tests/профили GRE и контейнеров/live maps,10 E2E/types/ESLint/builds/984 parity/live PC/mobile passed. Runtime backend/frontend ux-wg152 healthy;12.5 открыт. Следующий153 — OSPF/BFD sustained gate: BFD-only loss при сохранённых OSPF Hello на собственных GRE, восстановление и independent cleanup.
|
||||
|
||||
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-10. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
|
||||
|
||||
## Актуальная точка продолжения — 2026-10-10
|
||||
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.151 — GRE IPv6 static-address lifecycle через приложение: чтение/create/PATCH/history restore/delete, дополнительные/выключенные адреса, family-safe IDs и legacy409.15 профильных E2E, source types/ESLint, обе production сборки, 984 compiled parity и real native API/SQL/Ping3/3×2/cleanup прошли. Runtime backend/frontend ux-gre151 healthy;12.5 открыт. Следующий152 — application WireGuard IPv6 address lifecycle, включая импорт/историю/удаление; работать укрупнённым разделом без повторения принятого GRE.
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.152 — WireGuard IPv6 static lifecycle/create/PATCH/history/delete и single-address RSC/conf export/import.29 real API cases, native handshake/Ping3/3×2,33 disabled-server API/SQL cases всех трёх типов с zero ROS contacts,56 backend tests/профили GRE и контейнеров/live maps,10 E2E/types/ESLint/builds/984 parity/live PC/mobile passed. Runtime backend/frontend ux-wg152 healthy;12.5 открыт. Следующий153 — OSPF/BFD sustained gate: BFD-only loss при сохранённых OSPF Hello на собственных GRE, восстановление и independent cleanup.
|
||||
|
||||
Этапы0–11 завершены в локальных границах журнала. История выполненных внешних блоков и доказательства сохранены в [журнале](ux-ui-stage-12.md) и [протоколе](ux-ui-external-acceptance.md). Реальная Telegram доставка, Portal SSO, multi-instance и оставшиеся профильные сетевые ограничения открыты.
|
||||
|
||||
@@ -371,6 +371,6 @@ flowchart LR
|
||||
|
||||
## 12.5.77 — основной блок IPsec (2026-10-09)
|
||||
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.151 — GRE IPv6 static-address lifecycle через приложение: чтение/create/PATCH/history restore/delete, дополнительные/выключенные адреса, family-safe IDs и legacy409.15 профильных E2E, source types/ESLint, обе production сборки, 984 compiled parity и real native API/SQL/Ping3/3×2/cleanup прошли. Runtime backend/frontend ux-gre151 healthy;12.5 открыт. Следующий152 — application WireGuard IPv6 address lifecycle, включая импорт/историю/удаление; работать укрупнённым разделом без повторения принятого GRE.
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.152 — WireGuard IPv6 static lifecycle/create/PATCH/history/delete и single-address RSC/conf export/import.29 real API cases, native handshake/Ping3/3×2,33 disabled-server API/SQL cases всех трёх типов с zero ROS contacts,56 backend tests/профили GRE и контейнеров/live maps,10 E2E/types/ESLint/builds/984 parity/live PC/mobile passed. Runtime backend/frontend ux-wg152 healthy;12.5 открыт. Следующий153 — OSPF/BFD sustained gate: BFD-only loss при сохранённых OSPF Hello на собственных GRE, восстановление и independent cleanup.
|
||||
|
||||
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
|
||||
+22
-2
@@ -1,6 +1,6 @@
|
||||
# UX/UI: журнал реализации
|
||||
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.151 — GRE IPv6 static-address lifecycle через приложение: чтение/create/PATCH/history restore/delete, дополнительные/выключенные адреса, family-safe IDs и legacy409.15 профильных E2E, source types/ESLint, обе production сборки, 984 compiled parity и real native API/SQL/Ping3/3×2/cleanup прошли. Runtime backend/frontend ux-gre151 healthy;12.5 открыт. Следующий152 — application WireGuard IPv6 address lifecycle, включая импорт/историю/удаление; работать укрупнённым разделом без повторения принятого GRE.
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.152 — WireGuard IPv6 static lifecycle/create/PATCH/history/delete и single-address RSC/conf export/import.29 real API cases, native handshake/Ping3/3×2,33 disabled-server API/SQL cases всех трёх типов с zero ROS contacts,56 backend tests/профили GRE и контейнеров/live maps,10 E2E/types/ESLint/builds/984 parity/live PC/mobile passed. Runtime backend/frontend ux-wg152 healthy;12.5 открыт. Следующий153 — OSPF/BFD sustained gate: BFD-only loss при сохранённых OSPF Hello на собственных GRE, восстановление и independent cleanup.
|
||||
|
||||
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
|
||||
|
||||
@@ -36,7 +36,27 @@
|
||||
| 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 |
|
||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 |
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–151 и ограничения ниже. Runtime backend ux-month149/frontend ux-sessions150. |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–152 и ограничения ниже. Runtime backend ux-month149/frontend ux-sessions150. |
|
||||
|
||||
## 12.5.152 — IPv6 lifecycle WireGuard и общий допуск выключенных серверов (2026-10-11)
|
||||
|
||||
WireGuard читает оба address catalogs; ошибки/не-массивы не превращаются в пустые данные. Catalog показывает активный статический адрес, новые snapshots сохраняют все статические адреса с disabled/advertise/comment/no-dad. Dynamic link-local исключён из управления. Create и PATCH address, history restore и delete выбирают семейство по CIDR; дополнительные адреса сохранены, legacy snapshot с несколькими текущими адресами409. Общий адресный planner переиспользован GRE/WireGuard; constructor alias GreRestoreConflict совместим. PATCH не переименовывает одновременно интерфейс и адрес.
|
||||
|
||||
Import подтверждает receipts в /ip/address и /ipv6/address раздельно, normalized IPv6 identities принимаются, automatic link-local не считается чужим static child. Lost/unconfirmed receipt не разрешает удаление по одному имени; rollback не удаляет IPv4 с совпавшим IPv6 ID. IPv6 RSC export использует /ipv6 address add +advertise=no, parser распознаёт IPv6 address line. Multiple/special address import и export явно отклоняются вместо тихой потери данных. Это single-address literal import/export, не произвольный RouterOS configuration codec. Существующий ReUI create sheet получил подсказку IPv4/IPv6; структура/Frame/base-nova сохранены.
|
||||
|
||||
При native приёмке обнаружено: getEnabledServerById ранее проверял только ID. Теперь общий getter допускает только enabled=true и полный корректный DB ID; WireGuard selected catalog также исключает disabled. Это влияет на использующих getter GRE/WireGuard/firewall/containers/traffic/overlay. Постоянный opt-in test network-disabled-server.test.ts делает33 API/SQL cases: три типа Home Router/Jump Host/Exit Node ×11 calls, factory любого ROS client запрещена, contacts=0, enabled positive control проходит.8 malformed IDs проверяются до SQL, включая unsafe/выход за integer DB range. Без MMAPP_OWN_TEST_DB SQL часть честно skipped; в собственном PG стенде выполнена и база удалена.
|
||||
|
||||
Проверки:56 node:test cases import/rollback/revision/delete, service/config/HTTP scripts, GRE config-sync и14 invalid GRE requests, containers/live-maps profiles;10 E2E (WireGuard IPv6/error-retained input PC/mobile, tables/menus и GRE smoke после общего planner), source types/ESLint; обе production сборки,984 emitted outputs совпали с окончательным backend.
|
||||
|
||||
Native изолированная PG + production handlers + оба CHR:29 API cases, native handshake0s и Ping3/3 в обе стороны после IPv6 invalid=false; create/PATCH/IPv6→IPv4/clear, multiple-address history restore, legacy409, export multiple409, RSC/conf single IPv6 export и реальный import/delete, disabled GET empty/PATCH404 с forbidden factory. После каждого запуска9 static collections×2 совпали, собственные интерфейсы/peers/адреса и PG базы удалены. Native source/cleanup не меняли пароли/public/default routes. Background polling главного приложения продолжался; отсутствие всех background SQL writes не заявляется.
|
||||
|
||||
Первый сценарий остановился из-за ошибки теста: ожидал200 от POST peer, фактический корректный201. После исправления сценарий прошёл весь native lifecycle, но завершающий disabled PATCH вернул500 из-за реального дефекта общего getter. Оба отрицательных протокола сохранены; getter исправлен,33 isolated API/SQL cases и полный native сценарий на финальном образе прошли. Frontend повторно не собирался из-за backend-only gate delta.
|
||||
|
||||
Runtime backend/frontend ux-wg152 healthy; deployed live read-only catalog/create-sheet на1440/320 passed после cleanup. Browser mutations покрыты controlled E2E, реальные RouterOS mutations — production handlers в выделенной базе. IPv6 ULA inner по IPv4 WireGuard outer, не IPv6 public WAN/client OS/throughput/scale/lost ACK physical acceptance. From-pool/EUI-64/multiple-address import/export остаются явными ограничениями; full regression после139 не повторялась.
|
||||
|
||||
Протоколы: [native/cleanup](ux-ui/acceptance/wireguard-ipv6-native-2026-10-11.json), [API/history/import/export](ux-ui/acceptance/wireguard-ipv6-api-history-2026-10-11.json), [33 SQL/API gates](ux-ui/acceptance/network-disabled-sql-2026-10-11.json), [до исправления getter](ux-ui/acceptance/wireguard-ipv6-before-gate-api-2026-10-11.json), [compiled](ux-ui/acceptance/wireguard-ipv6-compiled-2026-10-11.json), [live UI](ux-ui/acceptance/wireguard-ipv6-smoke-2026-10-11.json).
|
||||
|
||||
Следующий153 — sustained OSPF/BFD-only gate и recovery на собственных GRE;12.5 остаётся открытым.
|
||||
|
||||
## 12.5.151 — управление статическими IPv6-адресами GRE (2026-10-11)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Оставшаяся внешняя приёмка — 2026-10-11
|
||||
|
||||
Это актуальная матрица после 12.5.151. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
|
||||
Это актуальная матрица после 12.5.152. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
|
||||
|
||||
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-gre151`, frontend `ux-gre151`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
|
||||
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-wg152`, frontend `ux-wg152`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
|
||||
|
||||
| Раздел | Подтверждено | Осталось / условие завершения |
|
||||
| --- | --- | --- |
|
||||
@@ -10,7 +10,7 @@
|
||||
| Speed/uptime | TCP transmit/receive/both32–33, короткий UDP34, auth failure25, transport timeout27, интерфейс after-ping28–29, snapshot/history race23–24 | Busy/abort/physical WAN disconnect; HTTP receipt socket disconnect принят97; независимое доказательство multi-WAN source/path; Home→JH/JH↔Exit с асимметричными каналами ; ROS-10 POST/SQL/GET и три часовых пояса приняты96. Короткие тесты не доказывают maximum throughput |
|
||||
| Outbox | Production PG claim/dedupe/settlement/retry/JWT/browser checkpoint/a11y35–49 | Настоящий Telegram API/чат, физический timeout после возможной отправки, несколько sender процессов. Нужны тестовый бот/чат и прямое разрешение на конкретное сообщение; подменённый ответ не доказывает доставку |
|
||||
| GRE | Native lifecycle, metadata, live формы/меню/URL/Ping53–57; GRE используется в OSPF/BGP/IPsec последующих блоков; native IPv6 ULA inner109; application static IPv6 lifecycle151 | IPv6 outer, from-pool/EUI-64, независимые WAN, масштаб и длительное восстановление; принятие GRE не означает приёмку всех сценариев автоматического расписания |
|
||||
| WireGuard | Native handshake/data plane58, deletion60, live create/peer/disable/enable/delete62, mobile64, export/import67–74; native IPv6 ULA inner/allowedIps/handshake109 | Клиентские ОС и произвольные конфигурации, IPv6 outer/scale и application IPv6 interface address lifecycle, физический lost ACK и устойчивое восстановление; `.conf` с placeholders — шаблон, не принятый клиентский tunnel |
|
||||
| WireGuard | Native handshake/data plane58, deletion60, live create/peer/disable/enable/delete62, mobile64, export/import67–74; native IPv6 ULA inner/allowedIps/handshake109; application static IPv6 lifecycle152 | Клиентские ОС и произвольные конфигурации, IPv6 outer/scale, from-pool/EUI-64 и multiple-address import/export, физический lost ACK и устойчивое восстановление; `.conf` с placeholders — шаблон, не принятый клиентский tunnel |
|
||||
| VXLAN | Собственная пара/private Ping/catalog/URL/export75–76; bridge/FDB/port loss/recovery107; IPv6 ULA inner по IPv4 outer108 | API приложения read-only: create/delete проверены native. IPv6 outer, масштаб/производительность, профильные mutation RBAC/concurrency отдельно не приняты |
|
||||
| IPsec | PSK и сертификатное шифрование77–82, active peer/online/mode-config, scoped NAT/group, partial/unknown/applied receipts, restore conflicts, JWT; full DN/специальные символы/online110; literal CN namespace guards и explicit FQDN111; certificate/PSK shared ID unknown/recovery112; multiple sessions controlled snapshots113; shared certificate rows/target guards114; preserve-certificate UI/native DELETE115; explicit deletion binding/PSK collision116; legacy export binding118; real duplicate DN sessions/Linux Ping/UI119; DN DER/ASCII tag и FQDN SAN native120; implicit unique DNS SAN/mixed IDs/API/UI121; короткий CHILD/IKE rekey/reconnect122; server pin и unbound same-CA certificate refusal123; own RSA Root/Intermediate/client chain/CRLs/IKE/reauth138; external revoked CRL new-handshake refusal/existing-session boundary137; certificate-bound malformed/signed expired CRL IKE refusal/recovery136; certificate-bound missing-CDP404/IKE refusal and recovery135; external HTTP CRL download/404/cache preservation134; isolated diagnostic buffers132; own ECDSA P-256/Ping/API export131 and final18×2 inventory; isolated same-key/CA time boundary with explicit AUTH_FAILED130; valid-renewal control timeout/clock/log observation129; original/renewed controls and time-invalid timeout observation128; CRL-issued cert/CA no-write delete guards127; own CRL-enabled valid/revoked control126; local revoked flag/CRL-disabled reconnect observation125; certificate reauth/CHILD absence boundary/UI124 | Реальные duplicate certificate-derived DN sessions приняты119; arbitrary custom PKI/OIDs/policies/path-length and missing/intermediate-revoked chain; native intermediate chain accepted138; ECDSA P-256 accepted131;128/129 timeout cause still unknown,130 time boundary accepted; periodic refresh; existing IKE survives short external-revocation observation137, automatic termination not guaranteed; direct CHR-to-CHR CRL HTTP failure cause134; reauth/CHILD loss принят124; длительный rekey/переходная потеря пакетов; прочие ID типы/ADDR4/manual DN/custom PKI; native edit identical matching rules rejected; клиентские Windows/macOS/iOS/Android, прочие DN форматы/custom OIDs/IKE ID namespaces, полная PKI и policy/profile/proposal restore, физический disconnect, durable retry/multi-instance, IPv6/maximum throughput |
|
||||
| OSPF/BFD | Real Full/own routes84–85, раздельные наблюдения BFD86, effective disabled/optimizer cost/partial/RBAC87, alternate path90 и CIDR91 | Sustained BFD-only gate не принят: в86 сосед мог вновь статьFull при доступныхHello. В90 переходный Ping18/40 с55% loss: бесшовность не принята. REST timing — наблюдаемая граница, не SLA; IPv6/scale/independent WAN открыты |
|
||||
@@ -18,7 +18,7 @@
|
||||
| Recursive routes | Production PUT/change/own history restore88, disabled guards, partial/unknown/applied outcomes, live формы/контекст | Автоматический rollback/retry, durable receipts и несколько экземпляров отсутствуют; PUT не является idempotent noop. Полная произвольная legacy catalog миграция не принята |
|
||||
| Diagnostics/terminal | Allowlist/native reads, Ping/trace/route/MTU/DNS, actual avg-rtt, disabled guards, JWT/live PC/mobile89; real default memory tail/log limits/API/UI133 | DNS использует backend resolver; MTU — ограниченная оценка, не гарантированный максимум. Stop ожидания не доказывает остановку RouterOS команды; arbitrary CLI и physical disconnect не приняты |
|
||||
| Portal/интеграции | Локальные и isolated JWT guards; UI/API error states в границах журналов | Настоящий Portal issuer/login/отзыв прав; RIPE SDK two-IP/no-fetch passed140, native IPv6 ULA flow pipeline passed145 (IPv4 transport), public IPv6 WAN/local licensed MaxMind/S3/container/certificate jobs и Gitea delivery требуют отдельных тестовых endpoints/аккаунтов. Не отправлять сообщения и не публиковать релиз без соответствующего поручения |
|
||||
| Общая приёмка | Локальная12.3.83; UI1244/context221/compiled984 parity139; профильные build/E2E/live проверки последующих блоков | Traffic initial live mock-id gate passed141; IPv6 special ranges/cache passed142; unknown RIPE coordinates passed143; IPv6 synthetic UDP/worker/SQL passed144; native CHR ULA/IPFIX/API/UI passed145; ICMPv6/IPv6 endpoint labels passed146; HTTP DTO validation passed147; scoped flow catalog/serverId guards passed148; strict monthly calendar passed149; Sessions labels passed150; application GRE static IPv6 lifecycle151 passed; следующий WireGuard IPv6 lifecycle152; последующие изменения проверять отдельно; scoped axe не означает полную WCAG сертификацию |
|
||||
| Общая приёмка | Локальная12.3.83; UI1244/context221/compiled984 parity139; профильные build/E2E/live проверки последующих блоков | Traffic initial live mock-id gate passed141; IPv6 special ranges/cache passed142; unknown RIPE coordinates passed143; IPv6 synthetic UDP/worker/SQL passed144; native CHR ULA/IPFIX/API/UI passed145; ICMPv6/IPv6 endpoint labels passed146; HTTP DTO validation passed147; scoped flow catalog/serverId guards passed148; strict monthly calendar passed149; Sessions labels passed150; application GRE static IPv6 lifecycle151 passed; WireGuard static IPv6 lifecycle152 и disabled SQL/API gates passed; следующий sustained OSPF/BFD153; последующие изменения проверять отдельно; scoped axe не означает полную WCAG сертификацию |
|
||||
|
||||
Доказательства и точные ограничения: [полная история этапа12](ux-ui-stage-12.md), [внешний протокол](ux-ui-external-acceptance.md), [BGP93](ux-ui/acceptance/bgp-failover-2026-10-10.json), [паспорт стенда](ux-ui-test-server.md). Невыполненные пункты не являются все блокерами данного лабораторного сценария: для выпуска необходимо явно определить требуемые конфигурации и принять их либо согласовать ограничение.
|
||||
|
||||
@@ -564,3 +564,23 @@ Create/PATCH/restore/delete выбирают /ip/address или /ipv6/address п
|
||||
Протоколы: [native/cleanup](ux-ui/acceptance/gre-ipv6-native-2026-10-11.json), [API/history](ux-ui/acceptance/gre-ipv6-api-history-2026-10-11.json), [первый запуск](ux-ui/acceptance/gre-ipv6-initial-cleanup-2026-10-11.json), [compiled parity](ux-ui/acceptance/gre-ipv6-compiled-2026-10-11.json), [live UI](ux-ui/acceptance/gre-ipv6-smoke-2026-10-11.json).
|
||||
|
||||
Границы: IPv6 ULA inner по IPv4 GRE outer; IPv6 outer/public WAN, scale, длительное восстановление и from-pool/EUI-64 не приняты. Полная регрессия после139 не повторялась: выполнен профиль изменённых областей. Следующий152 — WireGuard IPv6 lifecycle;12.5 остаётся открытым.
|
||||
|
||||
## 12.5.152 — IPv6 lifecycle WireGuard и общий допуск выключенных серверов (2026-10-11)
|
||||
|
||||
WireGuard читает оба address catalogs; ошибки/не-массивы не превращаются в пустые данные. Catalog показывает активный статический адрес, новые snapshots сохраняют все статические адреса с disabled/advertise/comment/no-dad. Dynamic link-local исключён из управления. Create и PATCH address, history restore и delete выбирают семейство по CIDR; дополнительные адреса сохранены, legacy snapshot с несколькими текущими адресами409. Общий адресный planner переиспользован GRE/WireGuard; constructor alias GreRestoreConflict совместим. PATCH не переименовывает одновременно интерфейс и адрес.
|
||||
|
||||
Import подтверждает receipts в /ip/address и /ipv6/address раздельно, normalized IPv6 identities принимаются, automatic link-local не считается чужим static child. Lost/unconfirmed receipt не разрешает удаление по одному имени; rollback не удаляет IPv4 с совпавшим IPv6 ID. IPv6 RSC export использует /ipv6 address add +advertise=no, parser распознаёт IPv6 address line. Multiple/special address import и export явно отклоняются вместо тихой потери данных. Это single-address literal import/export, не произвольный RouterOS configuration codec. Существующий ReUI create sheet получил подсказку IPv4/IPv6; структура/Frame/base-nova сохранены.
|
||||
|
||||
При native приёмке обнаружено: getEnabledServerById ранее проверял только ID. Теперь общий getter допускает только enabled=true и полный корректный DB ID; WireGuard selected catalog также исключает disabled. Это влияет на использующих getter GRE/WireGuard/firewall/containers/traffic/overlay. Постоянный opt-in test network-disabled-server.test.ts делает33 API/SQL cases: три типа Home Router/Jump Host/Exit Node ×11 calls, factory любого ROS client запрещена, contacts=0, enabled positive control проходит.8 malformed IDs проверяются до SQL, включая unsafe/выход за integer DB range. Без MMAPP_OWN_TEST_DB SQL часть честно skipped; в собственном PG стенде выполнена и база удалена.
|
||||
|
||||
Проверки:56 node:test cases import/rollback/revision/delete, service/config/HTTP scripts, GRE config-sync и14 invalid GRE requests, containers/live-maps profiles;10 E2E (WireGuard IPv6/error-retained input PC/mobile, tables/menus и GRE smoke после общего planner), source types/ESLint; обе production сборки,984 emitted outputs совпали с окончательным backend.
|
||||
|
||||
Native изолированная PG + production handlers + оба CHR:29 API cases, native handshake0s и Ping3/3 в обе стороны после IPv6 invalid=false; create/PATCH/IPv6→IPv4/clear, multiple-address history restore, legacy409, export multiple409, RSC/conf single IPv6 export и реальный import/delete, disabled GET empty/PATCH404 с forbidden factory. После каждого запуска9 static collections×2 совпали, собственные интерфейсы/peers/адреса и PG базы удалены. Native source/cleanup не меняли пароли/public/default routes. Background polling главного приложения продолжался; отсутствие всех background SQL writes не заявляется.
|
||||
|
||||
Первый сценарий остановился из-за ошибки теста: ожидал200 от POST peer, фактический корректный201. После исправления сценарий прошёл весь native lifecycle, но завершающий disabled PATCH вернул500 из-за реального дефекта общего getter. Оба отрицательных протокола сохранены; getter исправлен,33 isolated API/SQL cases и полный native сценарий на финальном образе прошли. Frontend повторно не собирался из-за backend-only gate delta.
|
||||
|
||||
Runtime backend/frontend ux-wg152 healthy; deployed live read-only catalog/create-sheet на1440/320 passed после cleanup. Browser mutations покрыты controlled E2E, реальные RouterOS mutations — production handlers в выделенной базе. IPv6 ULA inner по IPv4 WireGuard outer, не IPv6 public WAN/client OS/throughput/scale/lost ACK physical acceptance. From-pool/EUI-64/multiple-address import/export остаются явными ограничениями; full regression после139 не повторялась.
|
||||
|
||||
Протоколы: [native/cleanup](ux-ui/acceptance/wireguard-ipv6-native-2026-10-11.json), [API/history/import/export](ux-ui/acceptance/wireguard-ipv6-api-history-2026-10-11.json), [33 SQL/API gates](ux-ui/acceptance/network-disabled-sql-2026-10-11.json), [до исправления getter](ux-ui/acceptance/wireguard-ipv6-before-gate-api-2026-10-11.json), [compiled](ux-ui/acceptance/wireguard-ipv6-compiled-2026-10-11.json), [live UI](ux-ui/acceptance/wireguard-ipv6-smoke-2026-10-11.json).
|
||||
|
||||
Следующий153 — sustained OSPF/BFD-only gate и recovery на собственных GRE;12.5 остаётся открытым.
|
||||
+21
-1
@@ -1,6 +1,6 @@
|
||||
# Этап 12: итоговая приёмка UX/UI
|
||||
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.151 — GRE IPv6 static-address lifecycle через приложение: чтение/create/PATCH/history restore/delete, дополнительные/выключенные адреса, family-safe IDs и legacy409.15 профильных E2E, source types/ESLint, обе production сборки, 984 compiled parity и real native API/SQL/Ping3/3×2/cleanup прошли. Runtime backend/frontend ux-gre151 healthy;12.5 открыт. Следующий152 — application WireGuard IPv6 address lifecycle, включая импорт/историю/удаление; работать укрупнённым разделом без повторения принятого GRE.
|
||||
Актуальное продолжение 2026-10-11: завершён12.5.152 — WireGuard IPv6 static lifecycle/create/PATCH/history/delete и single-address RSC/conf export/import.29 real API cases, native handshake/Ping3/3×2,33 disabled-server API/SQL cases всех трёх типов с zero ROS contacts,56 backend tests/профили GRE и контейнеров/live maps,10 E2E/types/ESLint/builds/984 parity/live PC/mobile passed. Runtime backend/frontend ux-wg152 healthy;12.5 открыт. Следующий153 — OSPF/BFD sustained gate: BFD-only loss при сохранённых OSPF Hello на собственных GRE, восстановление и independent cleanup.
|
||||
|
||||
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
|
||||
|
||||
@@ -2954,3 +2954,23 @@ Create/PATCH/restore/delete выбирают /ip/address или /ipv6/address п
|
||||
Протоколы: [native/cleanup](ux-ui/acceptance/gre-ipv6-native-2026-10-11.json), [API/history](ux-ui/acceptance/gre-ipv6-api-history-2026-10-11.json), [первый запуск](ux-ui/acceptance/gre-ipv6-initial-cleanup-2026-10-11.json), [compiled parity](ux-ui/acceptance/gre-ipv6-compiled-2026-10-11.json), [live UI](ux-ui/acceptance/gre-ipv6-smoke-2026-10-11.json).
|
||||
|
||||
Границы: IPv6 ULA inner по IPv4 GRE outer; IPv6 outer/public WAN, scale, длительное восстановление и from-pool/EUI-64 не приняты. Полная регрессия после139 не повторялась: выполнен профиль изменённых областей. Следующий152 — WireGuard IPv6 lifecycle;12.5 остаётся открытым.
|
||||
|
||||
## 12.5.152 — IPv6 lifecycle WireGuard и общий допуск выключенных серверов (2026-10-11)
|
||||
|
||||
WireGuard читает оба address catalogs; ошибки/не-массивы не превращаются в пустые данные. Catalog показывает активный статический адрес, новые snapshots сохраняют все статические адреса с disabled/advertise/comment/no-dad. Dynamic link-local исключён из управления. Create и PATCH address, history restore и delete выбирают семейство по CIDR; дополнительные адреса сохранены, legacy snapshot с несколькими текущими адресами409. Общий адресный planner переиспользован GRE/WireGuard; constructor alias GreRestoreConflict совместим. PATCH не переименовывает одновременно интерфейс и адрес.
|
||||
|
||||
Import подтверждает receipts в /ip/address и /ipv6/address раздельно, normalized IPv6 identities принимаются, automatic link-local не считается чужим static child. Lost/unconfirmed receipt не разрешает удаление по одному имени; rollback не удаляет IPv4 с совпавшим IPv6 ID. IPv6 RSC export использует /ipv6 address add +advertise=no, parser распознаёт IPv6 address line. Multiple/special address import и export явно отклоняются вместо тихой потери данных. Это single-address literal import/export, не произвольный RouterOS configuration codec. Существующий ReUI create sheet получил подсказку IPv4/IPv6; структура/Frame/base-nova сохранены.
|
||||
|
||||
При native приёмке обнаружено: getEnabledServerById ранее проверял только ID. Теперь общий getter допускает только enabled=true и полный корректный DB ID; WireGuard selected catalog также исключает disabled. Это влияет на использующих getter GRE/WireGuard/firewall/containers/traffic/overlay. Постоянный opt-in test network-disabled-server.test.ts делает33 API/SQL cases: три типа Home Router/Jump Host/Exit Node ×11 calls, factory любого ROS client запрещена, contacts=0, enabled positive control проходит.8 malformed IDs проверяются до SQL, включая unsafe/выход за integer DB range. Без MMAPP_OWN_TEST_DB SQL часть честно skipped; в собственном PG стенде выполнена и база удалена.
|
||||
|
||||
Проверки:56 node:test cases import/rollback/revision/delete, service/config/HTTP scripts, GRE config-sync и14 invalid GRE requests, containers/live-maps profiles;10 E2E (WireGuard IPv6/error-retained input PC/mobile, tables/menus и GRE smoke после общего planner), source types/ESLint; обе production сборки,984 emitted outputs совпали с окончательным backend.
|
||||
|
||||
Native изолированная PG + production handlers + оба CHR:29 API cases, native handshake0s и Ping3/3 в обе стороны после IPv6 invalid=false; create/PATCH/IPv6→IPv4/clear, multiple-address history restore, legacy409, export multiple409, RSC/conf single IPv6 export и реальный import/delete, disabled GET empty/PATCH404 с forbidden factory. После каждого запуска9 static collections×2 совпали, собственные интерфейсы/peers/адреса и PG базы удалены. Native source/cleanup не меняли пароли/public/default routes. Background polling главного приложения продолжался; отсутствие всех background SQL writes не заявляется.
|
||||
|
||||
Первый сценарий остановился из-за ошибки теста: ожидал200 от POST peer, фактический корректный201. После исправления сценарий прошёл весь native lifecycle, но завершающий disabled PATCH вернул500 из-за реального дефекта общего getter. Оба отрицательных протокола сохранены; getter исправлен,33 isolated API/SQL cases и полный native сценарий на финальном образе прошли. Frontend повторно не собирался из-за backend-only gate delta.
|
||||
|
||||
Runtime backend/frontend ux-wg152 healthy; deployed live read-only catalog/create-sheet на1440/320 passed после cleanup. Browser mutations покрыты controlled E2E, реальные RouterOS mutations — production handlers в выделенной базе. IPv6 ULA inner по IPv4 WireGuard outer, не IPv6 public WAN/client OS/throughput/scale/lost ACK physical acceptance. From-pool/EUI-64/multiple-address import/export остаются явными ограничениями; full regression после139 не повторялась.
|
||||
|
||||
Протоколы: [native/cleanup](ux-ui/acceptance/wireguard-ipv6-native-2026-10-11.json), [API/history/import/export](ux-ui/acceptance/wireguard-ipv6-api-history-2026-10-11.json), [33 SQL/API gates](ux-ui/acceptance/network-disabled-sql-2026-10-11.json), [до исправления getter](ux-ui/acceptance/wireguard-ipv6-before-gate-api-2026-10-11.json), [compiled](ux-ui/acceptance/wireguard-ipv6-compiled-2026-10-11.json), [live UI](ux-ui/acceptance/wireguard-ipv6-smoke-2026-10-11.json).
|
||||
|
||||
Следующий153 — sustained OSPF/BFD-only gate и recovery на собственных GRE;12.5 остаётся открытым.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Тестовый сервер UX/UI — 2026-10-10
|
||||
|
||||
Сервер161.104.46.222: завершён12.5.151 — GRE IPv6 static-address lifecycle через приложение: чтение/create/PATCH/history restore/delete, дополнительные/выключенные адреса, family-safe IDs и legacy409.15 профильных E2E, source types/ESLint, обе production сборки, 984 compiled parity и real native API/SQL/Ping3/3×2/cleanup прошли. Runtime backend/frontend ux-gre151 healthy;12.5 открыт. Следующий152 — application WireGuard IPv6 address lifecycle, включая импорт/историю/удаление; работать укрупнённым разделом без повторения принятого GRE.
|
||||
Сервер161.104.46.222: завершён12.5.152 — WireGuard IPv6 static lifecycle/create/PATCH/history/delete и single-address RSC/conf export/import.29 real API cases, native handshake/Ping3/3×2,33 disabled-server API/SQL cases всех трёх типов с zero ROS contacts,56 backend tests/профили GRE и контейнеров/live maps,10 E2E/types/ESLint/builds/984 parity/live PC/mobile passed. Runtime backend/frontend ux-wg152 healthy;12.5 открыт. Следующий153 — OSPF/BFD sustained gate: BFD-only loss при сохранённых OSPF Hello на собственных GRE, восстановление и independent cleanup.
|
||||
|
||||
| Параметр | Проверенное состояние |
|
||||
| --- | --- |
|
||||
@@ -14,7 +14,7 @@
|
||||
| PostgreSQL | postgres:18-alpine,18.6;mmapp-test-postgres healthy;SELECT version() passed |
|
||||
| Данные | Отдельный Compose project mmapp-test,том mmapp-test_mmapp-pgdata и каталог ./data |
|
||||
| Доступ | PostgreSQL не публикуется;frontend3000/backend8000 привязаны к127.0.0.1 после запуска |
|
||||
| Образы приложения | mmapp-test/backend:ux-gre151 и mmapp-test/frontend:ux-gre151; запущены |
|
||||
| Образы приложения | mmapp-test/backend:ux-wg152 и mmapp-test/frontend:ux-wg152; запущены |
|
||||
|
||||
Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example).
|
||||
|
||||
@@ -1380,3 +1380,23 @@ Create/PATCH/restore/delete выбирают /ip/address или /ipv6/address п
|
||||
Протоколы: [native/cleanup](ux-ui/acceptance/gre-ipv6-native-2026-10-11.json), [API/history](ux-ui/acceptance/gre-ipv6-api-history-2026-10-11.json), [первый запуск](ux-ui/acceptance/gre-ipv6-initial-cleanup-2026-10-11.json), [compiled parity](ux-ui/acceptance/gre-ipv6-compiled-2026-10-11.json), [live UI](ux-ui/acceptance/gre-ipv6-smoke-2026-10-11.json).
|
||||
|
||||
Границы: IPv6 ULA inner по IPv4 GRE outer; IPv6 outer/public WAN, scale, длительное восстановление и from-pool/EUI-64 не приняты. Полная регрессия после139 не повторялась: выполнен профиль изменённых областей. Следующий152 — WireGuard IPv6 lifecycle;12.5 остаётся открытым.
|
||||
|
||||
## 12.5.152 — IPv6 lifecycle WireGuard и общий допуск выключенных серверов (2026-10-11)
|
||||
|
||||
WireGuard читает оба address catalogs; ошибки/не-массивы не превращаются в пустые данные. Catalog показывает активный статический адрес, новые snapshots сохраняют все статические адреса с disabled/advertise/comment/no-dad. Dynamic link-local исключён из управления. Create и PATCH address, history restore и delete выбирают семейство по CIDR; дополнительные адреса сохранены, legacy snapshot с несколькими текущими адресами409. Общий адресный planner переиспользован GRE/WireGuard; constructor alias GreRestoreConflict совместим. PATCH не переименовывает одновременно интерфейс и адрес.
|
||||
|
||||
Import подтверждает receipts в /ip/address и /ipv6/address раздельно, normalized IPv6 identities принимаются, automatic link-local не считается чужим static child. Lost/unconfirmed receipt не разрешает удаление по одному имени; rollback не удаляет IPv4 с совпавшим IPv6 ID. IPv6 RSC export использует /ipv6 address add +advertise=no, parser распознаёт IPv6 address line. Multiple/special address import и export явно отклоняются вместо тихой потери данных. Это single-address literal import/export, не произвольный RouterOS configuration codec. Существующий ReUI create sheet получил подсказку IPv4/IPv6; структура/Frame/base-nova сохранены.
|
||||
|
||||
При native приёмке обнаружено: getEnabledServerById ранее проверял только ID. Теперь общий getter допускает только enabled=true и полный корректный DB ID; WireGuard selected catalog также исключает disabled. Это влияет на использующих getter GRE/WireGuard/firewall/containers/traffic/overlay. Постоянный opt-in test network-disabled-server.test.ts делает33 API/SQL cases: три типа Home Router/Jump Host/Exit Node ×11 calls, factory любого ROS client запрещена, contacts=0, enabled positive control проходит.8 malformed IDs проверяются до SQL, включая unsafe/выход за integer DB range. Без MMAPP_OWN_TEST_DB SQL часть честно skipped; в собственном PG стенде выполнена и база удалена.
|
||||
|
||||
Проверки:56 node:test cases import/rollback/revision/delete, service/config/HTTP scripts, GRE config-sync и14 invalid GRE requests, containers/live-maps profiles;10 E2E (WireGuard IPv6/error-retained input PC/mobile, tables/menus и GRE smoke после общего planner), source types/ESLint; обе production сборки,984 emitted outputs совпали с окончательным backend.
|
||||
|
||||
Native изолированная PG + production handlers + оба CHR:29 API cases, native handshake0s и Ping3/3 в обе стороны после IPv6 invalid=false; create/PATCH/IPv6→IPv4/clear, multiple-address history restore, legacy409, export multiple409, RSC/conf single IPv6 export и реальный import/delete, disabled GET empty/PATCH404 с forbidden factory. После каждого запуска9 static collections×2 совпали, собственные интерфейсы/peers/адреса и PG базы удалены. Native source/cleanup не меняли пароли/public/default routes. Background polling главного приложения продолжался; отсутствие всех background SQL writes не заявляется.
|
||||
|
||||
Первый сценарий остановился из-за ошибки теста: ожидал200 от POST peer, фактический корректный201. После исправления сценарий прошёл весь native lifecycle, но завершающий disabled PATCH вернул500 из-за реального дефекта общего getter. Оба отрицательных протокола сохранены; getter исправлен,33 isolated API/SQL cases и полный native сценарий на финальном образе прошли. Frontend повторно не собирался из-за backend-only gate delta.
|
||||
|
||||
Runtime backend/frontend ux-wg152 healthy; deployed live read-only catalog/create-sheet на1440/320 passed после cleanup. Browser mutations покрыты controlled E2E, реальные RouterOS mutations — production handlers в выделенной базе. IPv6 ULA inner по IPv4 WireGuard outer, не IPv6 public WAN/client OS/throughput/scale/lost ACK physical acceptance. From-pool/EUI-64/multiple-address import/export остаются явными ограничениями; full regression после139 не повторялась.
|
||||
|
||||
Протоколы: [native/cleanup](ux-ui/acceptance/wireguard-ipv6-native-2026-10-11.json), [API/history/import/export](ux-ui/acceptance/wireguard-ipv6-api-history-2026-10-11.json), [33 SQL/API gates](ux-ui/acceptance/network-disabled-sql-2026-10-11.json), [до исправления getter](ux-ui/acceptance/wireguard-ipv6-before-gate-api-2026-10-11.json), [compiled](ux-ui/acceptance/wireguard-ipv6-compiled-2026-10-11.json), [live UI](ux-ui/acceptance/wireguard-ipv6-smoke-2026-10-11.json).
|
||||
|
||||
Следующий153 — sustained OSPF/BFD-only gate и recovery на собственных GRE;12.5 остаётся открытым.
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"date": "2026-10-11",
|
||||
"name": "mm_accept_152_1791731008028",
|
||||
"tests": [
|
||||
{
|
||||
"module": "network-disabled152",
|
||||
"passed": true,
|
||||
"output": "network-disabled-server.test.ts: 33 API/SQL checks for three types, zero RouterOS contacts, enabled positive control passed\n"
|
||||
}
|
||||
],
|
||||
"databaseRemoved": true
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
{
|
||||
"date": "2026-10-11",
|
||||
"block": "12.5.152",
|
||||
"name": "mm-wg152-1791731017548",
|
||||
"cases": [
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/interfaces",
|
||||
"status": 201
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/interfaces",
|
||||
"status": 201
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/peers",
|
||||
"status": 201
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/peers",
|
||||
"status": 201
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard/revisions?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/wireguard/interfaces/1/*97",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard/revisions?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/export",
|
||||
"status": 409
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/revisions/legacy-mm_accept_152_1791731017548/restore",
|
||||
"status": 409
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/revisions/1de6597b-eceb-4f17-8d16-2600471cb08f/restore",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/revisions/780d0a98-d237-4822-8502-9410b8f1e0fc/restore",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/revisions/1de6597b-eceb-4f17-8d16-2600471cb08f/restore",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/wireguard/interfaces/1/*97",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/wireguard/interfaces/1/*97",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/revisions/1de6597b-eceb-4f17-8d16-2600471cb08f/restore",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/export",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/export",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/wireguard/interfaces/1/*97",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/wireguard/interfaces/2/*76",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/import",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/wireguard/interfaces/1/*98",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/import",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/wireguard/interfaces/1/*99",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/wireguard/interfaces/1/*97",
|
||||
"status": 404
|
||||
}
|
||||
],
|
||||
"pings": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"sent": 3,
|
||||
"received": 3
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"sent": 3,
|
||||
"received": 3
|
||||
}
|
||||
],
|
||||
"ready": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"addressInvalid": "false"
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"addressInvalid": "false"
|
||||
}
|
||||
],
|
||||
"handshakes": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"lastHandshake": "0s"
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"lastHandshake": "0s"
|
||||
}
|
||||
],
|
||||
"passed": true
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
{
|
||||
"date": "2026-10-11",
|
||||
"block": "12.5.152",
|
||||
"name": "mm-wg152-1791730555296",
|
||||
"cases": [
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/interfaces",
|
||||
"status": 201
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/interfaces",
|
||||
"status": 201
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/peers",
|
||||
"status": 201
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/peers",
|
||||
"status": 201
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard/revisions?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/wireguard/interfaces/1/*94",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard/revisions?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/export",
|
||||
"status": 409
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/revisions/legacy-mm_accept_152_1791730555296/restore",
|
||||
"status": 409
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/revisions/420f83f6-f319-4bf9-9eb8-d37b8acb49d5/restore",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/revisions/52635299-be9a-4d64-b7e0-e2e268af5811/restore",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/revisions/420f83f6-f319-4bf9-9eb8-d37b8acb49d5/restore",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/wireguard/interfaces/1/*94",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/wireguard/interfaces/1/*94",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/revisions/420f83f6-f319-4bf9-9eb8-d37b8acb49d5/restore",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/export",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/export",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/wireguard/interfaces/1/*94",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/wireguard/interfaces/2/*75",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/import",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/wireguard/interfaces/1/*95",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/import",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard?serverId=1",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/wireguard/interfaces/1/*96",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/wireguard?serverId=1",
|
||||
"status": 200
|
||||
}
|
||||
],
|
||||
"pings": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"sent": 3,
|
||||
"received": 3
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"sent": 3,
|
||||
"received": 3
|
||||
}
|
||||
],
|
||||
"ready": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"addressInvalid": "false"
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"addressInvalid": "false"
|
||||
}
|
||||
],
|
||||
"handshakes": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"lastHandshake": "0s"
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"lastHandshake": "0s"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"date": "2026-10-11",
|
||||
"block": "12.5.152",
|
||||
"name": "mm-wg152-1791730555296",
|
||||
"devices": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"host": "194.226.121.150",
|
||||
"version": "7.24.5 (stable)",
|
||||
"reachable": true
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"host": "104.165.244.7",
|
||||
"version": "7.24.5 (stable)",
|
||||
"reachable": true
|
||||
}
|
||||
],
|
||||
"cleanup": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"/interface/wireguard/peers": true,
|
||||
"/interface/wireguard": true,
|
||||
"/ip/address": true,
|
||||
"/ipv6/address": true,
|
||||
"/ip/route": true,
|
||||
"/ipv6/route": true,
|
||||
"/ip/firewall/filter": true,
|
||||
"/ip/firewall/nat": true,
|
||||
"/ipv6/firewall/filter": true
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"/interface/wireguard/peers": true,
|
||||
"/interface/wireguard": true,
|
||||
"/ip/address": true,
|
||||
"/ipv6/address": true,
|
||||
"/ip/route": true,
|
||||
"/ipv6/route": true,
|
||||
"/ip/firewall/filter": true,
|
||||
"/ip/firewall/nat": true,
|
||||
"/ipv6/firewall/filter": true
|
||||
}
|
||||
],
|
||||
"childExit": 1,
|
||||
"error": "Child failed: inspect private log\n\n1 !== 0\n",
|
||||
"databaseRemoved": true,
|
||||
"passed": false
|
||||
}
|
||||
@@ -0,0 +1,994 @@
|
||||
{
|
||||
"date": "2026-10-11",
|
||||
"block": "12.5.152",
|
||||
"backendVersion": "1.0.0-test.wg152",
|
||||
"normalization": "CRLF to LF; emitted files derived from tracked non-test source inputs",
|
||||
"matched": [
|
||||
"backend/dist/config.js",
|
||||
"backend/dist/config.js.map",
|
||||
"backend/dist/config.d.ts",
|
||||
"backend/dist/config.d.ts.map",
|
||||
"backend/dist/constants/ping-probe.js",
|
||||
"backend/dist/constants/ping-probe.js.map",
|
||||
"backend/dist/constants/ping-probe.d.ts",
|
||||
"backend/dist/constants/ping-probe.d.ts.map",
|
||||
"backend/dist/db/bootstrap.js",
|
||||
"backend/dist/db/bootstrap.js.map",
|
||||
"backend/dist/db/bootstrap.d.ts",
|
||||
"backend/dist/db/bootstrap.d.ts.map",
|
||||
"backend/dist/db/index.js",
|
||||
"backend/dist/db/index.js.map",
|
||||
"backend/dist/db/index.d.ts",
|
||||
"backend/dist/db/index.d.ts.map",
|
||||
"backend/dist/db/json.js",
|
||||
"backend/dist/db/json.js.map",
|
||||
"backend/dist/db/json.d.ts",
|
||||
"backend/dist/db/json.d.ts.map",
|
||||
"backend/dist/db/migrate.js",
|
||||
"backend/dist/db/migrate.js.map",
|
||||
"backend/dist/db/migrate.d.ts",
|
||||
"backend/dist/db/migrate.d.ts.map",
|
||||
"backend/dist/db/operation-lock.js",
|
||||
"backend/dist/db/operation-lock.js.map",
|
||||
"backend/dist/db/operation-lock.d.ts",
|
||||
"backend/dist/db/operation-lock.d.ts.map",
|
||||
"backend/dist/db/partitions.js",
|
||||
"backend/dist/db/partitions.js.map",
|
||||
"backend/dist/db/partitions.d.ts",
|
||||
"backend/dist/db/partitions.d.ts.map",
|
||||
"backend/dist/db/schema.js",
|
||||
"backend/dist/db/schema.js.map",
|
||||
"backend/dist/db/schema.d.ts",
|
||||
"backend/dist/db/schema.d.ts.map",
|
||||
"backend/dist/db/sql-bind.js",
|
||||
"backend/dist/db/sql-bind.js.map",
|
||||
"backend/dist/db/sql-bind.d.ts",
|
||||
"backend/dist/db/sql-bind.d.ts.map",
|
||||
"backend/dist/db/sqlite-import.js",
|
||||
"backend/dist/db/sqlite-import.js.map",
|
||||
"backend/dist/db/sqlite-import.d.ts",
|
||||
"backend/dist/db/sqlite-import.d.ts.map",
|
||||
"backend/dist/db/traffic-flags.js",
|
||||
"backend/dist/db/traffic-flags.js.map",
|
||||
"backend/dist/db/traffic-flags.d.ts",
|
||||
"backend/dist/db/traffic-flags.d.ts.map",
|
||||
"backend/dist/index.js",
|
||||
"backend/dist/index.js.map",
|
||||
"backend/dist/index.d.ts",
|
||||
"backend/dist/index.d.ts.map",
|
||||
"backend/dist/lib/permissions.js",
|
||||
"backend/dist/lib/permissions.js.map",
|
||||
"backend/dist/lib/permissions.d.ts",
|
||||
"backend/dist/lib/permissions.d.ts.map",
|
||||
"backend/dist/managed-markers.js",
|
||||
"backend/dist/managed-markers.js.map",
|
||||
"backend/dist/managed-markers.d.ts",
|
||||
"backend/dist/managed-markers.d.ts.map",
|
||||
"backend/dist/modules/events/repository/events-repository.js",
|
||||
"backend/dist/modules/events/repository/events-repository.js.map",
|
||||
"backend/dist/modules/events/repository/events-repository.d.ts",
|
||||
"backend/dist/modules/events/repository/events-repository.d.ts.map",
|
||||
"backend/dist/modules/events/service/events-service.js",
|
||||
"backend/dist/modules/events/service/events-service.js.map",
|
||||
"backend/dist/modules/events/service/events-service.d.ts",
|
||||
"backend/dist/modules/events/service/events-service.d.ts.map",
|
||||
"backend/dist/modules/servers/mapper/servers-mapper.js",
|
||||
"backend/dist/modules/servers/mapper/servers-mapper.js.map",
|
||||
"backend/dist/modules/servers/mapper/servers-mapper.d.ts",
|
||||
"backend/dist/modules/servers/mapper/servers-mapper.d.ts.map",
|
||||
"backend/dist/modules/servers/repository/servers-repository.js",
|
||||
"backend/dist/modules/servers/repository/servers-repository.js.map",
|
||||
"backend/dist/modules/servers/repository/servers-repository.d.ts",
|
||||
"backend/dist/modules/servers/repository/servers-repository.d.ts.map",
|
||||
"backend/dist/modules/servers/service/servers-service.js",
|
||||
"backend/dist/modules/servers/service/servers-service.js.map",
|
||||
"backend/dist/modules/servers/service/servers-service.d.ts",
|
||||
"backend/dist/modules/servers/service/servers-service.d.ts.map",
|
||||
"backend/dist/modules/users/iface-type.js",
|
||||
"backend/dist/modules/users/iface-type.js.map",
|
||||
"backend/dist/modules/users/iface-type.d.ts",
|
||||
"backend/dist/modules/users/iface-type.d.ts.map",
|
||||
"backend/dist/modules/users/interface-inventory.js",
|
||||
"backend/dist/modules/users/interface-inventory.js.map",
|
||||
"backend/dist/modules/users/interface-inventory.d.ts",
|
||||
"backend/dist/modules/users/interface-inventory.d.ts.map",
|
||||
"backend/dist/modules/users/peer-bind.js",
|
||||
"backend/dist/modules/users/peer-bind.js.map",
|
||||
"backend/dist/modules/users/peer-bind.d.ts",
|
||||
"backend/dist/modules/users/peer-bind.d.ts.map",
|
||||
"backend/dist/modules/users/repository/users-repository.js",
|
||||
"backend/dist/modules/users/repository/users-repository.js.map",
|
||||
"backend/dist/modules/users/repository/users-repository.d.ts",
|
||||
"backend/dist/modules/users/repository/users-repository.d.ts.map",
|
||||
"backend/dist/modules/users/service/users-service.js",
|
||||
"backend/dist/modules/users/service/users-service.js.map",
|
||||
"backend/dist/modules/users/service/users-service.d.ts",
|
||||
"backend/dist/modules/users/service/users-service.d.ts.map",
|
||||
"backend/dist/plugins/auth.js",
|
||||
"backend/dist/plugins/auth.js.map",
|
||||
"backend/dist/plugins/auth.d.ts",
|
||||
"backend/dist/plugins/auth.d.ts.map",
|
||||
"backend/dist/plugins/database-request-gate.js",
|
||||
"backend/dist/plugins/database-request-gate.js.map",
|
||||
"backend/dist/plugins/database-request-gate.d.ts",
|
||||
"backend/dist/plugins/database-request-gate.d.ts.map",
|
||||
"backend/dist/routes/alerts.js",
|
||||
"backend/dist/routes/alerts.js.map",
|
||||
"backend/dist/routes/alerts.d.ts",
|
||||
"backend/dist/routes/alerts.d.ts.map",
|
||||
"backend/dist/routes/backups.js",
|
||||
"backend/dist/routes/backups.js.map",
|
||||
"backend/dist/routes/backups.d.ts",
|
||||
"backend/dist/routes/backups.d.ts.map",
|
||||
"backend/dist/routes/bgp.js",
|
||||
"backend/dist/routes/bgp.js.map",
|
||||
"backend/dist/routes/bgp.d.ts",
|
||||
"backend/dist/routes/bgp.d.ts.map",
|
||||
"backend/dist/routes/certificates.js",
|
||||
"backend/dist/routes/certificates.js.map",
|
||||
"backend/dist/routes/certificates.d.ts",
|
||||
"backend/dist/routes/certificates.d.ts.map",
|
||||
"backend/dist/routes/containers.js",
|
||||
"backend/dist/routes/containers.js.map",
|
||||
"backend/dist/routes/containers.d.ts",
|
||||
"backend/dist/routes/containers.d.ts.map",
|
||||
"backend/dist/routes/events.js",
|
||||
"backend/dist/routes/events.js.map",
|
||||
"backend/dist/routes/events.d.ts",
|
||||
"backend/dist/routes/events.d.ts.map",
|
||||
"backend/dist/routes/evobgp-catalog.js",
|
||||
"backend/dist/routes/evobgp-catalog.js.map",
|
||||
"backend/dist/routes/evobgp-catalog.d.ts",
|
||||
"backend/dist/routes/evobgp-catalog.d.ts.map",
|
||||
"backend/dist/routes/evobgp-settings.js",
|
||||
"backend/dist/routes/evobgp-settings.js.map",
|
||||
"backend/dist/routes/evobgp-settings.d.ts",
|
||||
"backend/dist/routes/evobgp-settings.d.ts.map",
|
||||
"backend/dist/routes/evobgp.js",
|
||||
"backend/dist/routes/evobgp.js.map",
|
||||
"backend/dist/routes/evobgp.d.ts",
|
||||
"backend/dist/routes/evobgp.d.ts.map",
|
||||
"backend/dist/routes/exec.js",
|
||||
"backend/dist/routes/exec.js.map",
|
||||
"backend/dist/routes/exec.d.ts",
|
||||
"backend/dist/routes/exec.d.ts.map",
|
||||
"backend/dist/routes/filters.js",
|
||||
"backend/dist/routes/filters.js.map",
|
||||
"backend/dist/routes/filters.d.ts",
|
||||
"backend/dist/routes/filters.d.ts.map",
|
||||
"backend/dist/routes/firewall.js",
|
||||
"backend/dist/routes/firewall.js.map",
|
||||
"backend/dist/routes/firewall.d.ts",
|
||||
"backend/dist/routes/firewall.d.ts.map",
|
||||
"backend/dist/routes/geoip.js",
|
||||
"backend/dist/routes/geoip.js.map",
|
||||
"backend/dist/routes/geoip.d.ts",
|
||||
"backend/dist/routes/geoip.d.ts.map",
|
||||
"backend/dist/routes/gre.js",
|
||||
"backend/dist/routes/gre.js.map",
|
||||
"backend/dist/routes/gre.d.ts",
|
||||
"backend/dist/routes/gre.d.ts.map",
|
||||
"backend/dist/routes/internet-path.js",
|
||||
"backend/dist/routes/internet-path.js.map",
|
||||
"backend/dist/routes/internet-path.d.ts",
|
||||
"backend/dist/routes/internet-path.d.ts.map",
|
||||
"backend/dist/routes/ipsec.js",
|
||||
"backend/dist/routes/ipsec.js.map",
|
||||
"backend/dist/routes/ipsec.d.ts",
|
||||
"backend/dist/routes/ipsec.d.ts.map",
|
||||
"backend/dist/routes/network.js",
|
||||
"backend/dist/routes/network.js.map",
|
||||
"backend/dist/routes/network.d.ts",
|
||||
"backend/dist/routes/network.d.ts.map",
|
||||
"backend/dist/routes/ospf.js",
|
||||
"backend/dist/routes/ospf.js.map",
|
||||
"backend/dist/routes/ospf.d.ts",
|
||||
"backend/dist/routes/ospf.d.ts.map",
|
||||
"backend/dist/routes/ping-probes-write.js",
|
||||
"backend/dist/routes/ping-probes-write.js.map",
|
||||
"backend/dist/routes/ping-probes-write.d.ts",
|
||||
"backend/dist/routes/ping-probes-write.d.ts.map",
|
||||
"backend/dist/routes/probes.js",
|
||||
"backend/dist/routes/probes.js.map",
|
||||
"backend/dist/routes/probes.d.ts",
|
||||
"backend/dist/routes/probes.d.ts.map",
|
||||
"backend/dist/routes/recursive-routes.js",
|
||||
"backend/dist/routes/recursive-routes.js.map",
|
||||
"backend/dist/routes/recursive-routes.d.ts",
|
||||
"backend/dist/routes/recursive-routes.d.ts.map",
|
||||
"backend/dist/routes/scheduler.js",
|
||||
"backend/dist/routes/scheduler.js.map",
|
||||
"backend/dist/routes/scheduler.d.ts",
|
||||
"backend/dist/routes/scheduler.d.ts.map",
|
||||
"backend/dist/routes/servers-api-ping.js",
|
||||
"backend/dist/routes/servers-api-ping.js.map",
|
||||
"backend/dist/routes/servers-api-ping.d.ts",
|
||||
"backend/dist/routes/servers-api-ping.d.ts.map",
|
||||
"backend/dist/routes/servers.js",
|
||||
"backend/dist/routes/servers.js.map",
|
||||
"backend/dist/routes/servers.d.ts",
|
||||
"backend/dist/routes/servers.d.ts.map",
|
||||
"backend/dist/routes/sidebar-counts.js",
|
||||
"backend/dist/routes/sidebar-counts.js.map",
|
||||
"backend/dist/routes/sidebar-counts.d.ts",
|
||||
"backend/dist/routes/sidebar-counts.d.ts.map",
|
||||
"backend/dist/routes/speed-probes-write.js",
|
||||
"backend/dist/routes/speed-probes-write.js.map",
|
||||
"backend/dist/routes/speed-probes-write.d.ts",
|
||||
"backend/dist/routes/speed-probes-write.d.ts.map",
|
||||
"backend/dist/routes/speed-test.js",
|
||||
"backend/dist/routes/speed-test.js.map",
|
||||
"backend/dist/routes/speed-test.d.ts",
|
||||
"backend/dist/routes/speed-test.d.ts.map",
|
||||
"backend/dist/routes/statistics.js",
|
||||
"backend/dist/routes/statistics.js.map",
|
||||
"backend/dist/routes/statistics.d.ts",
|
||||
"backend/dist/routes/statistics.d.ts.map",
|
||||
"backend/dist/routes/system-database.js",
|
||||
"backend/dist/routes/system-database.js.map",
|
||||
"backend/dist/routes/system-database.d.ts",
|
||||
"backend/dist/routes/system-database.d.ts.map",
|
||||
"backend/dist/routes/traffic-flow-purge.js",
|
||||
"backend/dist/routes/traffic-flow-purge.js.map",
|
||||
"backend/dist/routes/traffic-flow-purge.d.ts",
|
||||
"backend/dist/routes/traffic-flow-purge.d.ts.map",
|
||||
"backend/dist/routes/traffic-flow-settings.js",
|
||||
"backend/dist/routes/traffic-flow-settings.js.map",
|
||||
"backend/dist/routes/traffic-flow-settings.d.ts",
|
||||
"backend/dist/routes/traffic-flow-settings.d.ts.map",
|
||||
"backend/dist/routes/traffic-flow.js",
|
||||
"backend/dist/routes/traffic-flow.js.map",
|
||||
"backend/dist/routes/traffic-flow.d.ts",
|
||||
"backend/dist/routes/traffic-flow.d.ts.map",
|
||||
"backend/dist/routes/traffic.js",
|
||||
"backend/dist/routes/traffic.js.map",
|
||||
"backend/dist/routes/traffic.d.ts",
|
||||
"backend/dist/routes/traffic.d.ts.map",
|
||||
"backend/dist/routes/uptime.js",
|
||||
"backend/dist/routes/uptime.js.map",
|
||||
"backend/dist/routes/uptime.d.ts",
|
||||
"backend/dist/routes/uptime.d.ts.map",
|
||||
"backend/dist/routes/users.js",
|
||||
"backend/dist/routes/users.js.map",
|
||||
"backend/dist/routes/users.d.ts",
|
||||
"backend/dist/routes/users.d.ts.map",
|
||||
"backend/dist/routes/vxlan.js",
|
||||
"backend/dist/routes/vxlan.js.map",
|
||||
"backend/dist/routes/vxlan.d.ts",
|
||||
"backend/dist/routes/vxlan.d.ts.map",
|
||||
"backend/dist/routes/wireguard.js",
|
||||
"backend/dist/routes/wireguard.js.map",
|
||||
"backend/dist/routes/wireguard.d.ts",
|
||||
"backend/dist/routes/wireguard.d.ts.map",
|
||||
"backend/dist/scripts/migrate-sqlite-to-pg.js",
|
||||
"backend/dist/scripts/migrate-sqlite-to-pg.js.map",
|
||||
"backend/dist/scripts/migrate-sqlite-to-pg.d.ts",
|
||||
"backend/dist/scripts/migrate-sqlite-to-pg.d.ts.map",
|
||||
"backend/dist/scripts/rebuild-flow-facts.js",
|
||||
"backend/dist/scripts/rebuild-flow-facts.js.map",
|
||||
"backend/dist/scripts/rebuild-flow-facts.d.ts",
|
||||
"backend/dist/scripts/rebuild-flow-facts.d.ts.map",
|
||||
"backend/dist/services/acme-cloudflare.js",
|
||||
"backend/dist/services/acme-cloudflare.js.map",
|
||||
"backend/dist/services/acme-cloudflare.d.ts",
|
||||
"backend/dist/services/acme-cloudflare.d.ts.map",
|
||||
"backend/dist/services/alert-collector-hooks.js",
|
||||
"backend/dist/services/alert-collector-hooks.js.map",
|
||||
"backend/dist/services/alert-collector-hooks.d.ts",
|
||||
"backend/dist/services/alert-collector-hooks.d.ts.map",
|
||||
"backend/dist/services/alert-engine/confirm-stability.js",
|
||||
"backend/dist/services/alert-engine/confirm-stability.js.map",
|
||||
"backend/dist/services/alert-engine/confirm-stability.d.ts",
|
||||
"backend/dist/services/alert-engine/confirm-stability.d.ts.map",
|
||||
"backend/dist/services/alert-engine/cooldown.js",
|
||||
"backend/dist/services/alert-engine/cooldown.js.map",
|
||||
"backend/dist/services/alert-engine/cooldown.d.ts",
|
||||
"backend/dist/services/alert-engine/cooldown.d.ts.map",
|
||||
"backend/dist/services/alert-engine/decision-engine.js",
|
||||
"backend/dist/services/alert-engine/decision-engine.js.map",
|
||||
"backend/dist/services/alert-engine/decision-engine.d.ts",
|
||||
"backend/dist/services/alert-engine/decision-engine.d.ts.map",
|
||||
"backend/dist/services/alert-engine/evaluate-rule.js",
|
||||
"backend/dist/services/alert-engine/evaluate-rule.js.map",
|
||||
"backend/dist/services/alert-engine/evaluate-rule.d.ts",
|
||||
"backend/dist/services/alert-engine/evaluate-rule.d.ts.map",
|
||||
"backend/dist/services/alert-engine/groups.js",
|
||||
"backend/dist/services/alert-engine/groups.js.map",
|
||||
"backend/dist/services/alert-engine/groups.d.ts",
|
||||
"backend/dist/services/alert-engine/groups.d.ts.map",
|
||||
"backend/dist/services/alert-engine/outbox-execution.js",
|
||||
"backend/dist/services/alert-engine/outbox-execution.js.map",
|
||||
"backend/dist/services/alert-engine/outbox-execution.d.ts",
|
||||
"backend/dist/services/alert-engine/outbox-execution.d.ts.map",
|
||||
"backend/dist/services/alert-engine/outbox.js",
|
||||
"backend/dist/services/alert-engine/outbox.js.map",
|
||||
"backend/dist/services/alert-engine/outbox.d.ts",
|
||||
"backend/dist/services/alert-engine/outbox.d.ts.map",
|
||||
"backend/dist/services/alert-engine/prev-live-store.js",
|
||||
"backend/dist/services/alert-engine/prev-live-store.js.map",
|
||||
"backend/dist/services/alert-engine/prev-live-store.d.ts",
|
||||
"backend/dist/services/alert-engine/prev-live-store.d.ts.map",
|
||||
"backend/dist/services/alert-engine/rule-matcher.js",
|
||||
"backend/dist/services/alert-engine/rule-matcher.js.map",
|
||||
"backend/dist/services/alert-engine/rule-matcher.d.ts",
|
||||
"backend/dist/services/alert-engine/rule-matcher.d.ts.map",
|
||||
"backend/dist/services/alert-engine/run-once.js",
|
||||
"backend/dist/services/alert-engine/run-once.js.map",
|
||||
"backend/dist/services/alert-engine/run-once.d.ts",
|
||||
"backend/dist/services/alert-engine/run-once.d.ts.map",
|
||||
"backend/dist/services/alert-engine/signal-ingestor.js",
|
||||
"backend/dist/services/alert-engine/signal-ingestor.js.map",
|
||||
"backend/dist/services/alert-engine/signal-ingestor.d.ts",
|
||||
"backend/dist/services/alert-engine/signal-ingestor.d.ts.map",
|
||||
"backend/dist/services/alert-engine/signals-from-collectors.js",
|
||||
"backend/dist/services/alert-engine/signals-from-collectors.js.map",
|
||||
"backend/dist/services/alert-engine/signals-from-collectors.d.ts",
|
||||
"backend/dist/services/alert-engine/signals-from-collectors.d.ts.map",
|
||||
"backend/dist/services/alert-engine/snapshot.js",
|
||||
"backend/dist/services/alert-engine/snapshot.js.map",
|
||||
"backend/dist/services/alert-engine/snapshot.d.ts",
|
||||
"backend/dist/services/alert-engine/snapshot.d.ts.map",
|
||||
"backend/dist/services/alert-engine/source-watermark.js",
|
||||
"backend/dist/services/alert-engine/source-watermark.js.map",
|
||||
"backend/dist/services/alert-engine/source-watermark.d.ts",
|
||||
"backend/dist/services/alert-engine/source-watermark.d.ts.map",
|
||||
"backend/dist/services/alert-engine/state-machine.js",
|
||||
"backend/dist/services/alert-engine/state-machine.js.map",
|
||||
"backend/dist/services/alert-engine/state-machine.d.ts",
|
||||
"backend/dist/services/alert-engine/state-machine.d.ts.map",
|
||||
"backend/dist/services/alert-engine/telegram-emoji.js",
|
||||
"backend/dist/services/alert-engine/telegram-emoji.js.map",
|
||||
"backend/dist/services/alert-engine/telegram-emoji.d.ts",
|
||||
"backend/dist/services/alert-engine/telegram-emoji.d.ts.map",
|
||||
"backend/dist/services/alert-engine/types.js",
|
||||
"backend/dist/services/alert-engine/types.js.map",
|
||||
"backend/dist/services/alert-engine/types.d.ts",
|
||||
"backend/dist/services/alert-engine/types.d.ts.map",
|
||||
"backend/dist/services/alerts-config-execution.js",
|
||||
"backend/dist/services/alerts-config-execution.js.map",
|
||||
"backend/dist/services/alerts-config-execution.d.ts",
|
||||
"backend/dist/services/alerts-config-execution.d.ts.map",
|
||||
"backend/dist/services/alerts-outbox-overview.js",
|
||||
"backend/dist/services/alerts-outbox-overview.js.map",
|
||||
"backend/dist/services/alerts-outbox-overview.d.ts",
|
||||
"backend/dist/services/alerts-outbox-overview.d.ts.map",
|
||||
"backend/dist/services/alerts-outbox-retry.js",
|
||||
"backend/dist/services/alerts-outbox-retry.js.map",
|
||||
"backend/dist/services/alerts-outbox-retry.d.ts",
|
||||
"backend/dist/services/alerts-outbox-retry.d.ts.map",
|
||||
"backend/dist/services/alerts-overview.js",
|
||||
"backend/dist/services/alerts-overview.js.map",
|
||||
"backend/dist/services/alerts-overview.d.ts",
|
||||
"backend/dist/services/alerts-overview.d.ts.map",
|
||||
"backend/dist/services/alerts-revision.js",
|
||||
"backend/dist/services/alerts-revision.js.map",
|
||||
"backend/dist/services/alerts-revision.d.ts",
|
||||
"backend/dist/services/alerts-revision.d.ts.map",
|
||||
"backend/dist/services/alerts-service.js",
|
||||
"backend/dist/services/alerts-service.js.map",
|
||||
"backend/dist/services/alerts-service.d.ts",
|
||||
"backend/dist/services/alerts-service.d.ts.map",
|
||||
"backend/dist/services/alerts-telegram-delivery.js",
|
||||
"backend/dist/services/alerts-telegram-delivery.js.map",
|
||||
"backend/dist/services/alerts-telegram-delivery.d.ts",
|
||||
"backend/dist/services/alerts-telegram-delivery.d.ts.map",
|
||||
"backend/dist/services/alerts-telegram-send.js",
|
||||
"backend/dist/services/alerts-telegram-send.js.map",
|
||||
"backend/dist/services/alerts-telegram-send.d.ts",
|
||||
"backend/dist/services/alerts-telegram-send.d.ts.map",
|
||||
"backend/dist/services/alerts-telegram-settings.js",
|
||||
"backend/dist/services/alerts-telegram-settings.js.map",
|
||||
"backend/dist/services/alerts-telegram-settings.d.ts",
|
||||
"backend/dist/services/alerts-telegram-settings.d.ts.map",
|
||||
"backend/dist/services/backup-deletion.js",
|
||||
"backend/dist/services/backup-deletion.js.map",
|
||||
"backend/dist/services/backup-deletion.d.ts",
|
||||
"backend/dist/services/backup-deletion.d.ts.map",
|
||||
"backend/dist/services/backup-dto.js",
|
||||
"backend/dist/services/backup-dto.js.map",
|
||||
"backend/dist/services/backup-dto.d.ts",
|
||||
"backend/dist/services/backup-dto.d.ts.map",
|
||||
"backend/dist/services/backup-job-execution.js",
|
||||
"backend/dist/services/backup-job-execution.js.map",
|
||||
"backend/dist/services/backup-job-execution.d.ts",
|
||||
"backend/dist/services/backup-job-execution.d.ts.map",
|
||||
"backend/dist/services/backup-prune.js",
|
||||
"backend/dist/services/backup-prune.js.map",
|
||||
"backend/dist/services/backup-prune.d.ts",
|
||||
"backend/dist/services/backup-prune.d.ts.map",
|
||||
"backend/dist/services/backup-restore.js",
|
||||
"backend/dist/services/backup-restore.js.map",
|
||||
"backend/dist/services/backup-restore.d.ts",
|
||||
"backend/dist/services/backup-restore.d.ts.map",
|
||||
"backend/dist/services/backup-scheduler-collector.js",
|
||||
"backend/dist/services/backup-scheduler-collector.js.map",
|
||||
"backend/dist/services/backup-scheduler-collector.d.ts",
|
||||
"backend/dist/services/backup-scheduler-collector.d.ts.map",
|
||||
"backend/dist/services/backup-scheduler-run.js",
|
||||
"backend/dist/services/backup-scheduler-run.js.map",
|
||||
"backend/dist/services/backup-scheduler-run.d.ts",
|
||||
"backend/dist/services/backup-scheduler-run.d.ts.map",
|
||||
"backend/dist/services/backup-service.js",
|
||||
"backend/dist/services/backup-service.js.map",
|
||||
"backend/dist/services/backup-service.d.ts",
|
||||
"backend/dist/services/backup-service.d.ts.map",
|
||||
"backend/dist/services/backup-storage-update.js",
|
||||
"backend/dist/services/backup-storage-update.js.map",
|
||||
"backend/dist/services/backup-storage-update.d.ts",
|
||||
"backend/dist/services/backup-storage-update.d.ts.map",
|
||||
"backend/dist/services/bandwidth-test-rates.js",
|
||||
"backend/dist/services/bandwidth-test-rates.js.map",
|
||||
"backend/dist/services/bandwidth-test-rates.d.ts",
|
||||
"backend/dist/services/bandwidth-test-rates.d.ts.map",
|
||||
"backend/dist/services/bandwidth-test-status.js",
|
||||
"backend/dist/services/bandwidth-test-status.js.map",
|
||||
"backend/dist/services/bandwidth-test-status.d.ts",
|
||||
"backend/dist/services/bandwidth-test-status.d.ts.map",
|
||||
"backend/dist/services/bgp-catalog-poll.js",
|
||||
"backend/dist/services/bgp-catalog-poll.js.map",
|
||||
"backend/dist/services/bgp-catalog-poll.d.ts",
|
||||
"backend/dist/services/bgp-catalog-poll.d.ts.map",
|
||||
"backend/dist/services/bgp-parse-sessions.js",
|
||||
"backend/dist/services/bgp-parse-sessions.js.map",
|
||||
"backend/dist/services/bgp-parse-sessions.d.ts",
|
||||
"backend/dist/services/bgp-parse-sessions.d.ts.map",
|
||||
"backend/dist/services/bgp-peers-live.js",
|
||||
"backend/dist/services/bgp-peers-live.js.map",
|
||||
"backend/dist/services/bgp-peers-live.d.ts",
|
||||
"backend/dist/services/bgp-peers-live.d.ts.map",
|
||||
"backend/dist/services/btest-node-locks.js",
|
||||
"backend/dist/services/btest-node-locks.js.map",
|
||||
"backend/dist/services/btest-node-locks.d.ts",
|
||||
"backend/dist/services/btest-node-locks.d.ts.map",
|
||||
"backend/dist/services/certificate-issue-dto.js",
|
||||
"backend/dist/services/certificate-issue-dto.js.map",
|
||||
"backend/dist/services/certificate-issue-dto.d.ts",
|
||||
"backend/dist/services/certificate-issue-dto.d.ts.map",
|
||||
"backend/dist/services/certificate-issue-execution.js",
|
||||
"backend/dist/services/certificate-issue-execution.js.map",
|
||||
"backend/dist/services/certificate-issue-execution.d.ts",
|
||||
"backend/dist/services/certificate-issue-execution.d.ts.map",
|
||||
"backend/dist/services/certificate-issue-request.js",
|
||||
"backend/dist/services/certificate-issue-request.js.map",
|
||||
"backend/dist/services/certificate-issue-request.d.ts",
|
||||
"backend/dist/services/certificate-issue-request.d.ts.map",
|
||||
"backend/dist/services/certificate-issue-runner.js",
|
||||
"backend/dist/services/certificate-issue-runner.js.map",
|
||||
"backend/dist/services/certificate-issue-runner.d.ts",
|
||||
"backend/dist/services/certificate-issue-runner.d.ts.map",
|
||||
"backend/dist/services/certificate-parse.js",
|
||||
"backend/dist/services/certificate-parse.js.map",
|
||||
"backend/dist/services/certificate-parse.d.ts",
|
||||
"backend/dist/services/certificate-parse.d.ts.map",
|
||||
"backend/dist/services/certificate-renew-collector.js",
|
||||
"backend/dist/services/certificate-renew-collector.js.map",
|
||||
"backend/dist/services/certificate-renew-collector.d.ts",
|
||||
"backend/dist/services/certificate-renew-collector.d.ts.map",
|
||||
"backend/dist/services/certificates-service.js",
|
||||
"backend/dist/services/certificates-service.js.map",
|
||||
"backend/dist/services/certificates-service.d.ts",
|
||||
"backend/dist/services/certificates-service.d.ts.map",
|
||||
"backend/dist/services/certificates-state.js",
|
||||
"backend/dist/services/certificates-state.js.map",
|
||||
"backend/dist/services/certificates-state.d.ts",
|
||||
"backend/dist/services/certificates-state.d.ts.map",
|
||||
"backend/dist/services/complete-count.js",
|
||||
"backend/dist/services/complete-count.js.map",
|
||||
"backend/dist/services/complete-count.d.ts",
|
||||
"backend/dist/services/complete-count.d.ts.map",
|
||||
"backend/dist/services/config-apply-plan.js",
|
||||
"backend/dist/services/config-apply-plan.js.map",
|
||||
"backend/dist/services/config-apply-plan.d.ts",
|
||||
"backend/dist/services/config-apply-plan.d.ts.map",
|
||||
"backend/dist/services/config-revisions.js",
|
||||
"backend/dist/services/config-revisions.js.map",
|
||||
"backend/dist/services/config-revisions.d.ts",
|
||||
"backend/dist/services/config-revisions.d.ts.map",
|
||||
"backend/dist/services/containers-live.js",
|
||||
"backend/dist/services/containers-live.js.map",
|
||||
"backend/dist/services/containers-live.d.ts",
|
||||
"backend/dist/services/containers-live.d.ts.map",
|
||||
"backend/dist/services/containers-state.js",
|
||||
"backend/dist/services/containers-state.js.map",
|
||||
"backend/dist/services/containers-state.d.ts",
|
||||
"backend/dist/services/containers-state.d.ts.map",
|
||||
"backend/dist/services/database-background-work.js",
|
||||
"backend/dist/services/database-background-work.js.map",
|
||||
"backend/dist/services/database-background-work.d.ts",
|
||||
"backend/dist/services/database-background-work.d.ts.map",
|
||||
"backend/dist/services/database-request-gate.js",
|
||||
"backend/dist/services/database-request-gate.js.map",
|
||||
"backend/dist/services/database-request-gate.d.ts",
|
||||
"backend/dist/services/database-request-gate.d.ts.map",
|
||||
"backend/dist/services/entity-snapshots.js",
|
||||
"backend/dist/services/entity-snapshots.js.map",
|
||||
"backend/dist/services/entity-snapshots.d.ts",
|
||||
"backend/dist/services/entity-snapshots.d.ts.map",
|
||||
"backend/dist/services/evobgp-catalog.js",
|
||||
"backend/dist/services/evobgp-catalog.js.map",
|
||||
"backend/dist/services/evobgp-catalog.d.ts",
|
||||
"backend/dist/services/evobgp-catalog.d.ts.map",
|
||||
"backend/dist/services/firewall-live.js",
|
||||
"backend/dist/services/firewall-live.js.map",
|
||||
"backend/dist/services/firewall-live.d.ts",
|
||||
"backend/dist/services/firewall-live.d.ts.map",
|
||||
"backend/dist/services/firewall-poll.js",
|
||||
"backend/dist/services/firewall-poll.js.map",
|
||||
"backend/dist/services/firewall-poll.d.ts",
|
||||
"backend/dist/services/firewall-poll.d.ts.map",
|
||||
"backend/dist/services/geoip-settings.js",
|
||||
"backend/dist/services/geoip-settings.js.map",
|
||||
"backend/dist/services/geoip-settings.d.ts",
|
||||
"backend/dist/services/geoip-settings.d.ts.map",
|
||||
"backend/dist/services/geoip-update-collector.js",
|
||||
"backend/dist/services/geoip-update-collector.js.map",
|
||||
"backend/dist/services/geoip-update-collector.d.ts",
|
||||
"backend/dist/services/geoip-update-collector.d.ts.map",
|
||||
"backend/dist/services/gre-bgp-snapshot-collector.js",
|
||||
"backend/dist/services/gre-bgp-snapshot-collector.js.map",
|
||||
"backend/dist/services/gre-bgp-snapshot-collector.d.ts",
|
||||
"backend/dist/services/gre-bgp-snapshot-collector.d.ts.map",
|
||||
"backend/dist/services/gre-catalog-poll.js",
|
||||
"backend/dist/services/gre-catalog-poll.js.map",
|
||||
"backend/dist/services/gre-catalog-poll.d.ts",
|
||||
"backend/dist/services/gre-catalog-poll.d.ts.map",
|
||||
"backend/dist/services/gre-comment.js",
|
||||
"backend/dist/services/gre-comment.js.map",
|
||||
"backend/dist/services/gre-comment.d.ts",
|
||||
"backend/dist/services/gre-comment.d.ts.map",
|
||||
"backend/dist/services/gre-live.js",
|
||||
"backend/dist/services/gre-live.js.map",
|
||||
"backend/dist/services/gre-live.d.ts",
|
||||
"backend/dist/services/gre-live.d.ts.map",
|
||||
"backend/dist/services/gre-tunnels-live.js",
|
||||
"backend/dist/services/gre-tunnels-live.js.map",
|
||||
"backend/dist/services/gre-tunnels-live.d.ts",
|
||||
"backend/dist/services/gre-tunnels-live.d.ts.map",
|
||||
"backend/dist/services/internet-path-collector.js",
|
||||
"backend/dist/services/internet-path-collector.js.map",
|
||||
"backend/dist/services/internet-path-collector.d.ts",
|
||||
"backend/dist/services/internet-path-collector.d.ts.map",
|
||||
"backend/dist/services/internet-path-snapshot.js",
|
||||
"backend/dist/services/internet-path-snapshot.js.map",
|
||||
"backend/dist/services/internet-path-snapshot.d.ts",
|
||||
"backend/dist/services/internet-path-snapshot.d.ts.map",
|
||||
"backend/dist/services/ipsec-ca.js",
|
||||
"backend/dist/services/ipsec-ca.js.map",
|
||||
"backend/dist/services/ipsec-ca.d.ts",
|
||||
"backend/dist/services/ipsec-ca.d.ts.map",
|
||||
"backend/dist/services/ipsec-certificate-dn.js",
|
||||
"backend/dist/services/ipsec-certificate-dn.js.map",
|
||||
"backend/dist/services/ipsec-certificate-dn.d.ts",
|
||||
"backend/dist/services/ipsec-certificate-dn.d.ts.map",
|
||||
"backend/dist/services/ipsec-cleanup.js",
|
||||
"backend/dist/services/ipsec-cleanup.js.map",
|
||||
"backend/dist/services/ipsec-cleanup.d.ts",
|
||||
"backend/dist/services/ipsec-cleanup.d.ts.map",
|
||||
"backend/dist/services/ipsec-config.js",
|
||||
"backend/dist/services/ipsec-config.js.map",
|
||||
"backend/dist/services/ipsec-config.d.ts",
|
||||
"backend/dist/services/ipsec-config.d.ts.map",
|
||||
"backend/dist/services/ipsec-live.js",
|
||||
"backend/dist/services/ipsec-live.js.map",
|
||||
"backend/dist/services/ipsec-live.d.ts",
|
||||
"backend/dist/services/ipsec-live.d.ts.map",
|
||||
"backend/dist/services/ipsec-operation.js",
|
||||
"backend/dist/services/ipsec-operation.js.map",
|
||||
"backend/dist/services/ipsec-operation.d.ts",
|
||||
"backend/dist/services/ipsec-operation.d.ts.map",
|
||||
"backend/dist/services/ipsec-ros.js",
|
||||
"backend/dist/services/ipsec-ros.js.map",
|
||||
"backend/dist/services/ipsec-ros.d.ts",
|
||||
"backend/dist/services/ipsec-ros.d.ts.map",
|
||||
"backend/dist/services/mikrotik.js",
|
||||
"backend/dist/services/mikrotik.js.map",
|
||||
"backend/dist/services/mikrotik.d.ts",
|
||||
"backend/dist/services/mikrotik.d.ts.map",
|
||||
"backend/dist/services/monitoring-server-catalog.js",
|
||||
"backend/dist/services/monitoring-server-catalog.js.map",
|
||||
"backend/dist/services/monitoring-server-catalog.d.ts",
|
||||
"backend/dist/services/monitoring-server-catalog.d.ts.map",
|
||||
"backend/dist/services/ospf-catalog-poll.js",
|
||||
"backend/dist/services/ospf-catalog-poll.js.map",
|
||||
"backend/dist/services/ospf-catalog-poll.d.ts",
|
||||
"backend/dist/services/ospf-catalog-poll.d.ts.map",
|
||||
"backend/dist/services/ospf-effective-disabled.js",
|
||||
"backend/dist/services/ospf-effective-disabled.js.map",
|
||||
"backend/dist/services/ospf-effective-disabled.d.ts",
|
||||
"backend/dist/services/ospf-effective-disabled.d.ts.map",
|
||||
"backend/dist/services/ospf-observed-values.js",
|
||||
"backend/dist/services/ospf-observed-values.js.map",
|
||||
"backend/dist/services/ospf-observed-values.d.ts",
|
||||
"backend/dist/services/ospf-observed-values.d.ts.map",
|
||||
"backend/dist/services/ospf-optimizer-identity.js",
|
||||
"backend/dist/services/ospf-optimizer-identity.js.map",
|
||||
"backend/dist/services/ospf-optimizer-identity.d.ts",
|
||||
"backend/dist/services/ospf-optimizer-identity.d.ts.map",
|
||||
"backend/dist/services/ospf-optimizer-metrics.js",
|
||||
"backend/dist/services/ospf-optimizer-metrics.js.map",
|
||||
"backend/dist/services/ospf-optimizer-metrics.d.ts",
|
||||
"backend/dist/services/ospf-optimizer-metrics.d.ts.map",
|
||||
"backend/dist/services/ospf-optimizer-plan.js",
|
||||
"backend/dist/services/ospf-optimizer-plan.js.map",
|
||||
"backend/dist/services/ospf-optimizer-plan.d.ts",
|
||||
"backend/dist/services/ospf-optimizer-plan.d.ts.map",
|
||||
"backend/dist/services/ospf-route-parse.js",
|
||||
"backend/dist/services/ospf-route-parse.js.map",
|
||||
"backend/dist/services/ospf-route-parse.d.ts",
|
||||
"backend/dist/services/ospf-route-parse.d.ts.map",
|
||||
"backend/dist/services/ping-probe-replacement.js",
|
||||
"backend/dist/services/ping-probe-replacement.js.map",
|
||||
"backend/dist/services/ping-probe-replacement.d.ts",
|
||||
"backend/dist/services/ping-probe-replacement.d.ts.map",
|
||||
"backend/dist/services/poller.js",
|
||||
"backend/dist/services/poller.js.map",
|
||||
"backend/dist/services/poller.d.ts",
|
||||
"backend/dist/services/poller.d.ts.map",
|
||||
"backend/dist/services/recursive-apply.js",
|
||||
"backend/dist/services/recursive-apply.js.map",
|
||||
"backend/dist/services/recursive-apply.d.ts",
|
||||
"backend/dist/services/recursive-apply.d.ts.map",
|
||||
"backend/dist/services/recursive-route-catalog.js",
|
||||
"backend/dist/services/recursive-route-catalog.js.map",
|
||||
"backend/dist/services/recursive-route-catalog.d.ts",
|
||||
"backend/dist/services/recursive-route-catalog.d.ts.map",
|
||||
"backend/dist/services/recursive-route-validation.js",
|
||||
"backend/dist/services/recursive-route-validation.js.map",
|
||||
"backend/dist/services/recursive-route-validation.d.ts",
|
||||
"backend/dist/services/recursive-route-validation.d.ts.map",
|
||||
"backend/dist/services/ros-metric-parse.js",
|
||||
"backend/dist/services/ros-metric-parse.js.map",
|
||||
"backend/dist/services/ros-metric-parse.d.ts",
|
||||
"backend/dist/services/ros-metric-parse.d.ts.map",
|
||||
"backend/dist/services/ros-ops.js",
|
||||
"backend/dist/services/ros-ops.js.map",
|
||||
"backend/dist/services/ros-ops.d.ts",
|
||||
"backend/dist/services/ros-ops.d.ts.map",
|
||||
"backend/dist/services/s3-backup-client.js",
|
||||
"backend/dist/services/s3-backup-client.js.map",
|
||||
"backend/dist/services/s3-backup-client.d.ts",
|
||||
"backend/dist/services/s3-backup-client.d.ts.map",
|
||||
"backend/dist/services/scheduler-recorded-run.js",
|
||||
"backend/dist/services/scheduler-recorded-run.js.map",
|
||||
"backend/dist/services/scheduler-recorded-run.d.ts",
|
||||
"backend/dist/services/scheduler-recorded-run.d.ts.map",
|
||||
"backend/dist/services/scheduler-running.js",
|
||||
"backend/dist/services/scheduler-running.js.map",
|
||||
"backend/dist/services/scheduler-running.d.ts",
|
||||
"backend/dist/services/scheduler-running.d.ts.map",
|
||||
"backend/dist/services/scheduler.js",
|
||||
"backend/dist/services/scheduler.js.map",
|
||||
"backend/dist/services/scheduler.d.ts",
|
||||
"backend/dist/services/scheduler.d.ts.map",
|
||||
"backend/dist/services/serial-operation.js",
|
||||
"backend/dist/services/serial-operation.js.map",
|
||||
"backend/dist/services/serial-operation.d.ts",
|
||||
"backend/dist/services/serial-operation.d.ts.map",
|
||||
"backend/dist/services/servers-rest-ping-collector.js",
|
||||
"backend/dist/services/servers-rest-ping-collector.js.map",
|
||||
"backend/dist/services/servers-rest-ping-collector.d.ts",
|
||||
"backend/dist/services/servers-rest-ping-collector.d.ts.map",
|
||||
"backend/dist/services/speed-history-dto.js",
|
||||
"backend/dist/services/speed-history-dto.js.map",
|
||||
"backend/dist/services/speed-history-dto.d.ts",
|
||||
"backend/dist/services/speed-history-dto.d.ts.map",
|
||||
"backend/dist/services/speed-probe-dto.js",
|
||||
"backend/dist/services/speed-probe-dto.js.map",
|
||||
"backend/dist/services/speed-probe-dto.d.ts",
|
||||
"backend/dist/services/speed-probe-dto.d.ts.map",
|
||||
"backend/dist/services/speed-probe-replacement.js",
|
||||
"backend/dist/services/speed-probe-replacement.js.map",
|
||||
"backend/dist/services/speed-probe-replacement.d.ts",
|
||||
"backend/dist/services/speed-probe-replacement.d.ts.map",
|
||||
"backend/dist/services/speed-run-error-message.js",
|
||||
"backend/dist/services/speed-run-error-message.js.map",
|
||||
"backend/dist/services/speed-run-error-message.d.ts",
|
||||
"backend/dist/services/speed-run-error-message.d.ts.map",
|
||||
"backend/dist/services/speed-run-persistence.js",
|
||||
"backend/dist/services/speed-run-persistence.js.map",
|
||||
"backend/dist/services/speed-run-persistence.d.ts",
|
||||
"backend/dist/services/speed-run-persistence.d.ts.map",
|
||||
"backend/dist/services/statistics-aggregate.js",
|
||||
"backend/dist/services/statistics-aggregate.js.map",
|
||||
"backend/dist/services/statistics-aggregate.d.ts",
|
||||
"backend/dist/services/statistics-aggregate.d.ts.map",
|
||||
"backend/dist/services/system-database-backup.js",
|
||||
"backend/dist/services/system-database-backup.js.map",
|
||||
"backend/dist/services/system-database-backup.d.ts",
|
||||
"backend/dist/services/system-database-backup.d.ts.map",
|
||||
"backend/dist/services/system-database-cache.js",
|
||||
"backend/dist/services/system-database-cache.js.map",
|
||||
"backend/dist/services/system-database-cache.d.ts",
|
||||
"backend/dist/services/system-database-cache.d.ts.map",
|
||||
"backend/dist/services/system-database-operation.js",
|
||||
"backend/dist/services/system-database-operation.js.map",
|
||||
"backend/dist/services/system-database-operation.d.ts",
|
||||
"backend/dist/services/system-database-operation.d.ts.map",
|
||||
"backend/dist/services/system-database-partition-restore.js",
|
||||
"backend/dist/services/system-database-partition-restore.js.map",
|
||||
"backend/dist/services/system-database-partition-restore.d.ts",
|
||||
"backend/dist/services/system-database-partition-restore.d.ts.map",
|
||||
"backend/dist/services/system-database-restore-execution.js",
|
||||
"backend/dist/services/system-database-restore-execution.js.map",
|
||||
"backend/dist/services/system-database-restore-execution.d.ts",
|
||||
"backend/dist/services/system-database-restore-execution.d.ts.map",
|
||||
"backend/dist/services/traffic-collector.js",
|
||||
"backend/dist/services/traffic-collector.js.map",
|
||||
"backend/dist/services/traffic-collector.d.ts",
|
||||
"backend/dist/services/traffic-collector.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-analytics.js",
|
||||
"backend/dist/services/traffic-flow-analytics.js.map",
|
||||
"backend/dist/services/traffic-flow-analytics.d.ts",
|
||||
"backend/dist/services/traffic-flow-analytics.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-apps.js",
|
||||
"backend/dist/services/traffic-flow-apps.js.map",
|
||||
"backend/dist/services/traffic-flow-apps.d.ts",
|
||||
"backend/dist/services/traffic-flow-apps.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-brands.js",
|
||||
"backend/dist/services/traffic-flow-brands.js.map",
|
||||
"backend/dist/services/traffic-flow-brands.d.ts",
|
||||
"backend/dist/services/traffic-flow-brands.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-classify.js",
|
||||
"backend/dist/services/traffic-flow-classify.js.map",
|
||||
"backend/dist/services/traffic-flow-classify.d.ts",
|
||||
"backend/dist/services/traffic-flow-classify.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-collector-ipc.js",
|
||||
"backend/dist/services/traffic-flow-collector-ipc.js.map",
|
||||
"backend/dist/services/traffic-flow-collector-ipc.d.ts",
|
||||
"backend/dist/services/traffic-flow-collector-ipc.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-collector-worker.js",
|
||||
"backend/dist/services/traffic-flow-collector-worker.js.map",
|
||||
"backend/dist/services/traffic-flow-collector-worker.d.ts",
|
||||
"backend/dist/services/traffic-flow-collector-worker.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-dedup.js",
|
||||
"backend/dist/services/traffic-flow-dedup.js.map",
|
||||
"backend/dist/services/traffic-flow-dedup.d.ts",
|
||||
"backend/dist/services/traffic-flow-dedup.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-dest.js",
|
||||
"backend/dist/services/traffic-flow-dest.js.map",
|
||||
"backend/dist/services/traffic-flow-dest.d.ts",
|
||||
"backend/dist/services/traffic-flow-dest.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-engine.js",
|
||||
"backend/dist/services/traffic-flow-engine.js.map",
|
||||
"backend/dist/services/traffic-flow-engine.d.ts",
|
||||
"backend/dist/services/traffic-flow-engine.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-facts-filter.js",
|
||||
"backend/dist/services/traffic-flow-facts-filter.js.map",
|
||||
"backend/dist/services/traffic-flow-facts-filter.d.ts",
|
||||
"backend/dist/services/traffic-flow-facts-filter.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-facts-rebuild.js",
|
||||
"backend/dist/services/traffic-flow-facts-rebuild.js.map",
|
||||
"backend/dist/services/traffic-flow-facts-rebuild.d.ts",
|
||||
"backend/dist/services/traffic-flow-facts-rebuild.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-facts.js",
|
||||
"backend/dist/services/traffic-flow-facts.js.map",
|
||||
"backend/dist/services/traffic-flow-facts.d.ts",
|
||||
"backend/dist/services/traffic-flow-facts.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-geoip.js",
|
||||
"backend/dist/services/traffic-flow-geoip.js.map",
|
||||
"backend/dist/services/traffic-flow-geoip.d.ts",
|
||||
"backend/dist/services/traffic-flow-geoip.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-host-file-builders.js",
|
||||
"backend/dist/services/traffic-flow-host-file-builders.js.map",
|
||||
"backend/dist/services/traffic-flow-host-file-builders.d.ts",
|
||||
"backend/dist/services/traffic-flow-host-file-builders.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-host-files.js",
|
||||
"backend/dist/services/traffic-flow-host-files.js.map",
|
||||
"backend/dist/services/traffic-flow-host-files.d.ts",
|
||||
"backend/dist/services/traffic-flow-host-files.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-ifaces.js",
|
||||
"backend/dist/services/traffic-flow-ifaces.js.map",
|
||||
"backend/dist/services/traffic-flow-ifaces.d.ts",
|
||||
"backend/dist/services/traffic-flow-ifaces.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-ifindex.js",
|
||||
"backend/dist/services/traffic-flow-ifindex.js.map",
|
||||
"backend/dist/services/traffic-flow-ifindex.d.ts",
|
||||
"backend/dist/services/traffic-flow-ifindex.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-ingest.js",
|
||||
"backend/dist/services/traffic-flow-ingest.js.map",
|
||||
"backend/dist/services/traffic-flow-ingest.d.ts",
|
||||
"backend/dist/services/traffic-flow-ingest.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-ip.js",
|
||||
"backend/dist/services/traffic-flow-ip.js.map",
|
||||
"backend/dist/services/traffic-flow-ip.d.ts",
|
||||
"backend/dist/services/traffic-flow-ip.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-key-execution.js",
|
||||
"backend/dist/services/traffic-flow-key-execution.js.map",
|
||||
"backend/dist/services/traffic-flow-key-execution.d.ts",
|
||||
"backend/dist/services/traffic-flow-key-execution.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-map-exporter.js",
|
||||
"backend/dist/services/traffic-flow-map-exporter.js.map",
|
||||
"backend/dist/services/traffic-flow-map-exporter.d.ts",
|
||||
"backend/dist/services/traffic-flow-map-exporter.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-map-hops.js",
|
||||
"backend/dist/services/traffic-flow-map-hops.js.map",
|
||||
"backend/dist/services/traffic-flow-map-hops.d.ts",
|
||||
"backend/dist/services/traffic-flow-map-hops.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-overlay.js",
|
||||
"backend/dist/services/traffic-flow-overlay.js.map",
|
||||
"backend/dist/services/traffic-flow-overlay.d.ts",
|
||||
"backend/dist/services/traffic-flow-overlay.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-parse.js",
|
||||
"backend/dist/services/traffic-flow-parse.js.map",
|
||||
"backend/dist/services/traffic-flow-parse.d.ts",
|
||||
"backend/dist/services/traffic-flow-parse.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-planes.js",
|
||||
"backend/dist/services/traffic-flow-planes.js.map",
|
||||
"backend/dist/services/traffic-flow-planes.d.ts",
|
||||
"backend/dist/services/traffic-flow-planes.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-purge-execution.js",
|
||||
"backend/dist/services/traffic-flow-purge-execution.js.map",
|
||||
"backend/dist/services/traffic-flow-purge-execution.d.ts",
|
||||
"backend/dist/services/traffic-flow-purge-execution.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-ripe.js",
|
||||
"backend/dist/services/traffic-flow-ripe.js.map",
|
||||
"backend/dist/services/traffic-flow-ripe.d.ts",
|
||||
"backend/dist/services/traffic-flow-ripe.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-settings.js",
|
||||
"backend/dist/services/traffic-flow-settings.js.map",
|
||||
"backend/dist/services/traffic-flow-settings.d.ts",
|
||||
"backend/dist/services/traffic-flow-settings.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-topology.js",
|
||||
"backend/dist/services/traffic-flow-topology.js.map",
|
||||
"backend/dist/services/traffic-flow-topology.d.ts",
|
||||
"backend/dist/services/traffic-flow-topology.d.ts.map",
|
||||
"backend/dist/services/traffic-flow-worker-shutdown.js",
|
||||
"backend/dist/services/traffic-flow-worker-shutdown.js.map",
|
||||
"backend/dist/services/traffic-flow-worker-shutdown.d.ts",
|
||||
"backend/dist/services/traffic-flow-worker-shutdown.d.ts.map",
|
||||
"backend/dist/services/traffic-rate.js",
|
||||
"backend/dist/services/traffic-rate.js.map",
|
||||
"backend/dist/services/traffic-rate.d.ts",
|
||||
"backend/dist/services/traffic-rate.d.ts.map",
|
||||
"backend/dist/services/traffic-users.js",
|
||||
"backend/dist/services/traffic-users.js.map",
|
||||
"backend/dist/services/traffic-users.d.ts",
|
||||
"backend/dist/services/traffic-users.d.ts.map",
|
||||
"backend/dist/services/uptime-collector.js",
|
||||
"backend/dist/services/uptime-collector.js.map",
|
||||
"backend/dist/services/uptime-collector.d.ts",
|
||||
"backend/dist/services/uptime-collector.d.ts.map",
|
||||
"backend/dist/services/uptime-probe-observations.js",
|
||||
"backend/dist/services/uptime-probe-observations.js.map",
|
||||
"backend/dist/services/uptime-probe-observations.d.ts",
|
||||
"backend/dist/services/uptime-probe-observations.d.ts.map",
|
||||
"backend/dist/services/uptime-speed-service.js",
|
||||
"backend/dist/services/uptime-speed-service.js.map",
|
||||
"backend/dist/services/uptime-speed-service.d.ts",
|
||||
"backend/dist/services/uptime-speed-service.d.ts.map",
|
||||
"backend/dist/services/vxlan-live.js",
|
||||
"backend/dist/services/vxlan-live.js.map",
|
||||
"backend/dist/services/vxlan-live.d.ts",
|
||||
"backend/dist/services/vxlan-live.d.ts.map",
|
||||
"backend/dist/services/wg-keys.js",
|
||||
"backend/dist/services/wg-keys.js.map",
|
||||
"backend/dist/services/wg-keys.d.ts",
|
||||
"backend/dist/services/wg-keys.d.ts.map",
|
||||
"backend/dist/services/wireguard-config.js",
|
||||
"backend/dist/services/wireguard-config.js.map",
|
||||
"backend/dist/services/wireguard-config.d.ts",
|
||||
"backend/dist/services/wireguard-config.d.ts.map",
|
||||
"backend/dist/services/wireguard-delete.js",
|
||||
"backend/dist/services/wireguard-delete.js.map",
|
||||
"backend/dist/services/wireguard-delete.d.ts",
|
||||
"backend/dist/services/wireguard-delete.d.ts.map",
|
||||
"backend/dist/services/wireguard-import-apply.js",
|
||||
"backend/dist/services/wireguard-import-apply.js.map",
|
||||
"backend/dist/services/wireguard-import-apply.d.ts",
|
||||
"backend/dist/services/wireguard-import-apply.d.ts.map",
|
||||
"backend/dist/services/wireguard-import-revision.js",
|
||||
"backend/dist/services/wireguard-import-revision.js.map",
|
||||
"backend/dist/services/wireguard-import-revision.d.ts",
|
||||
"backend/dist/services/wireguard-import-revision.d.ts.map",
|
||||
"backend/dist/services/wireguard-import.js",
|
||||
"backend/dist/services/wireguard-import.js.map",
|
||||
"backend/dist/services/wireguard-import.d.ts",
|
||||
"backend/dist/services/wireguard-import.d.ts.map",
|
||||
"backend/dist/services/wireguard-live.js",
|
||||
"backend/dist/services/wireguard-live.js.map",
|
||||
"backend/dist/services/wireguard-live.d.ts",
|
||||
"backend/dist/services/wireguard-live.d.ts.map",
|
||||
"backend/dist/services/wireguard-observed-values.js",
|
||||
"backend/dist/services/wireguard-observed-values.js.map",
|
||||
"backend/dist/services/wireguard-observed-values.d.ts",
|
||||
"backend/dist/services/wireguard-observed-values.d.ts.map",
|
||||
"backend/dist/services/wireguard-ros.js",
|
||||
"backend/dist/services/wireguard-ros.js.map",
|
||||
"backend/dist/services/wireguard-ros.d.ts",
|
||||
"backend/dist/services/wireguard-ros.d.ts.map",
|
||||
"backend/dist/test/pg.js",
|
||||
"backend/dist/test/pg.js.map",
|
||||
"backend/dist/test/pg.d.ts",
|
||||
"backend/dist/test/pg.d.ts.map",
|
||||
"backend/dist/types/scheduler-run-snapshot.js",
|
||||
"backend/dist/types/scheduler-run-snapshot.js.map",
|
||||
"backend/dist/types/scheduler-run-snapshot.d.ts",
|
||||
"backend/dist/types/scheduler-run-snapshot.d.ts.map",
|
||||
"backend/dist/types/server.js",
|
||||
"backend/dist/types/server.js.map",
|
||||
"backend/dist/types/server.d.ts",
|
||||
"backend/dist/types/server.d.ts.map",
|
||||
"backend/dist/utils/abort-signals.js",
|
||||
"backend/dist/utils/abort-signals.js.map",
|
||||
"backend/dist/utils/abort-signals.d.ts",
|
||||
"backend/dist/utils/abort-signals.d.ts.map",
|
||||
"backend/dist/utils/ros-src-address.js",
|
||||
"backend/dist/utils/ros-src-address.js.map",
|
||||
"backend/dist/utils/ros-src-address.d.ts",
|
||||
"backend/dist/utils/ros-src-address.d.ts.map",
|
||||
"backend/dist/utils/server-id.js",
|
||||
"backend/dist/utils/server-id.js.map",
|
||||
"backend/dist/utils/server-id.d.ts",
|
||||
"backend/dist/utils/server-id.d.ts.map",
|
||||
"packages/contracts/dist/alerts.js",
|
||||
"packages/contracts/dist/alerts.js.map",
|
||||
"packages/contracts/dist/alerts.d.ts",
|
||||
"packages/contracts/dist/alerts.d.ts.map",
|
||||
"packages/contracts/dist/backups.js",
|
||||
"packages/contracts/dist/backups.js.map",
|
||||
"packages/contracts/dist/backups.d.ts",
|
||||
"packages/contracts/dist/backups.d.ts.map",
|
||||
"packages/contracts/dist/certificates.js",
|
||||
"packages/contracts/dist/certificates.js.map",
|
||||
"packages/contracts/dist/certificates.d.ts",
|
||||
"packages/contracts/dist/certificates.d.ts.map",
|
||||
"packages/contracts/dist/containers.js",
|
||||
"packages/contracts/dist/containers.js.map",
|
||||
"packages/contracts/dist/containers.d.ts",
|
||||
"packages/contracts/dist/containers.d.ts.map",
|
||||
"packages/contracts/dist/events.js",
|
||||
"packages/contracts/dist/events.js.map",
|
||||
"packages/contracts/dist/events.d.ts",
|
||||
"packages/contracts/dist/events.d.ts.map",
|
||||
"packages/contracts/dist/evobgp.js",
|
||||
"packages/contracts/dist/evobgp.js.map",
|
||||
"packages/contracts/dist/evobgp.d.ts",
|
||||
"packages/contracts/dist/evobgp.d.ts.map",
|
||||
"packages/contracts/dist/geoip.js",
|
||||
"packages/contracts/dist/geoip.js.map",
|
||||
"packages/contracts/dist/geoip.d.ts",
|
||||
"packages/contracts/dist/geoip.d.ts.map",
|
||||
"packages/contracts/dist/index.js",
|
||||
"packages/contracts/dist/index.js.map",
|
||||
"packages/contracts/dist/index.d.ts",
|
||||
"packages/contracts/dist/index.d.ts.map",
|
||||
"packages/contracts/dist/ipsec.js",
|
||||
"packages/contracts/dist/ipsec.js.map",
|
||||
"packages/contracts/dist/ipsec.d.ts",
|
||||
"packages/contracts/dist/ipsec.d.ts.map",
|
||||
"packages/contracts/dist/ospf-optimizer.js",
|
||||
"packages/contracts/dist/ospf-optimizer.js.map",
|
||||
"packages/contracts/dist/ospf-optimizer.d.ts",
|
||||
"packages/contracts/dist/ospf-optimizer.d.ts.map",
|
||||
"packages/contracts/dist/servers.js",
|
||||
"packages/contracts/dist/servers.js.map",
|
||||
"packages/contracts/dist/servers.d.ts",
|
||||
"packages/contracts/dist/servers.d.ts.map",
|
||||
"packages/contracts/dist/statistics.js",
|
||||
"packages/contracts/dist/statistics.js.map",
|
||||
"packages/contracts/dist/statistics.d.ts",
|
||||
"packages/contracts/dist/statistics.d.ts.map",
|
||||
"packages/contracts/dist/traffic-flow.js",
|
||||
"packages/contracts/dist/traffic-flow.js.map",
|
||||
"packages/contracts/dist/traffic-flow.d.ts",
|
||||
"packages/contracts/dist/traffic-flow.d.ts.map",
|
||||
"packages/contracts/dist/uptime-probes.js",
|
||||
"packages/contracts/dist/uptime-probes.js.map",
|
||||
"packages/contracts/dist/uptime-probes.d.ts",
|
||||
"packages/contracts/dist/uptime-probes.d.ts.map",
|
||||
"packages/contracts/dist/uptime-speed.js",
|
||||
"packages/contracts/dist/uptime-speed.js.map",
|
||||
"packages/contracts/dist/uptime-speed.d.ts",
|
||||
"packages/contracts/dist/uptime-speed.d.ts.map",
|
||||
"packages/contracts/dist/users.js",
|
||||
"packages/contracts/dist/users.js.map",
|
||||
"packages/contracts/dist/users.d.ts",
|
||||
"packages/contracts/dist/users.d.ts.map",
|
||||
"packages/contracts/dist/wireguard.js",
|
||||
"packages/contracts/dist/wireguard.js.map",
|
||||
"packages/contracts/dist/wireguard.d.ts",
|
||||
"packages/contracts/dist/wireguard.d.ts.map"
|
||||
],
|
||||
"missing": [],
|
||||
"different": []
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"date": "2026-10-11",
|
||||
"block": "12.5.152",
|
||||
"name": "mm-wg152-1791730354586",
|
||||
"cases": [
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/interfaces",
|
||||
"status": 201
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/wireguard/interfaces",
|
||||
"status": 201
|
||||
}
|
||||
],
|
||||
"pings": []
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"date": "2026-10-11",
|
||||
"block": "12.5.152",
|
||||
"name": "mm-wg152-1791730354586",
|
||||
"devices": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"host": "194.226.121.150",
|
||||
"version": "7.24.5 (stable)",
|
||||
"reachable": true
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"host": "104.165.244.7",
|
||||
"version": "7.24.5 (stable)",
|
||||
"reachable": true
|
||||
}
|
||||
],
|
||||
"cleanup": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"/interface/wireguard/peers": true,
|
||||
"/interface/wireguard": true,
|
||||
"/ip/address": true,
|
||||
"/ipv6/address": true,
|
||||
"/ip/route": true,
|
||||
"/ipv6/route": true,
|
||||
"/ip/firewall/filter": true,
|
||||
"/ip/firewall/nat": true,
|
||||
"/ipv6/firewall/filter": true
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"/interface/wireguard/peers": true,
|
||||
"/interface/wireguard": true,
|
||||
"/ip/address": true,
|
||||
"/ipv6/address": true,
|
||||
"/ip/route": true,
|
||||
"/ipv6/route": true,
|
||||
"/ip/firewall/filter": true,
|
||||
"/ip/firewall/nat": true,
|
||||
"/ipv6/firewall/filter": true
|
||||
}
|
||||
],
|
||||
"childExit": 1,
|
||||
"error": "Child failed: inspect private log\n\n1 !== 0\n",
|
||||
"databaseRemoved": true,
|
||||
"passed": false
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"date": "2026-10-11",
|
||||
"block": "12.5.152",
|
||||
"name": "mm-wg152-1791731017548",
|
||||
"devices": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"host": "194.226.121.150",
|
||||
"version": "7.24.5 (stable)",
|
||||
"reachable": true
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"host": "104.165.244.7",
|
||||
"version": "7.24.5 (stable)",
|
||||
"reachable": true
|
||||
}
|
||||
],
|
||||
"cleanup": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"/interface/wireguard/peers": true,
|
||||
"/interface/wireguard": true,
|
||||
"/ip/address": true,
|
||||
"/ipv6/address": true,
|
||||
"/ip/route": true,
|
||||
"/ipv6/route": true,
|
||||
"/ip/firewall/filter": true,
|
||||
"/ip/firewall/nat": true,
|
||||
"/ipv6/firewall/filter": true
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"/interface/wireguard/peers": true,
|
||||
"/interface/wireguard": true,
|
||||
"/ip/address": true,
|
||||
"/ipv6/address": true,
|
||||
"/ip/route": true,
|
||||
"/ipv6/route": true,
|
||||
"/ip/firewall/filter": true,
|
||||
"/ip/firewall/nat": true,
|
||||
"/ipv6/firewall/filter": true
|
||||
}
|
||||
],
|
||||
"childExit": 0,
|
||||
"databaseRemoved": true,
|
||||
"passed": true
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"date": "2026-10-11",
|
||||
"block": "12.5.152",
|
||||
"runtime": {
|
||||
"backend": "ux-wg152",
|
||||
"frontend": "ux-wg152"
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"width": 1440,
|
||||
"status": 200,
|
||||
"api": [
|
||||
{
|
||||
"path": "/api/servers",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/wireguard",
|
||||
"status": 200
|
||||
}
|
||||
],
|
||||
"pageErrors": [],
|
||||
"noOverflow": true,
|
||||
"ipv6AddressHint": true
|
||||
},
|
||||
{
|
||||
"width": 320,
|
||||
"status": 200,
|
||||
"api": [
|
||||
{
|
||||
"path": "/api/servers",
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"path": "/api/wireguard",
|
||||
"status": 200
|
||||
}
|
||||
],
|
||||
"pageErrors": [],
|
||||
"noOverflow": true,
|
||||
"ipv6AddressHint": true
|
||||
}
|
||||
],
|
||||
"bounds": [
|
||||
"Read-only live catalog/new-form UI smoke after native cleanup; no deployed UI mutation",
|
||||
"Native real RouterOS mutations/history/SQL accepted via dedicated database with production Fastify handlers",
|
||||
"IPv6 ULA inner over IPv4 WireGuard outer; no IPv6 outer/independent WAN/throughput/scale claim"
|
||||
]
|
||||
}
|
||||
@@ -12,6 +12,7 @@ export function resolveWgClientEndpoint(clientEndpoint: string | undefined, serv
|
||||
}
|
||||
|
||||
export const wgStatusSchema = z.enum(["up", "down"])
|
||||
export const wgInterfaceAddressSchema = z.union([z.literal(""), z.cidrv4(), z.cidrv6()])
|
||||
|
||||
export const wgPeerDtoSchema = z.object({
|
||||
id: z.string().min(1),
|
||||
@@ -85,12 +86,13 @@ export const wgCreateInterfaceSchema = z.object({
|
||||
mtu: z.number().int().positive().default(1420),
|
||||
comment: z.string().optional(),
|
||||
privateKey: z.string().min(1).optional(),
|
||||
address: z.string().optional(),
|
||||
address: wgInterfaceAddressSchema.optional(),
|
||||
disabled: z.boolean().optional(),
|
||||
peer: wgCreatePeerSchema.optional(),
|
||||
})
|
||||
|
||||
export const wgPatchInterfaceSchema = z.object({
|
||||
address: wgInterfaceAddressSchema.optional(),
|
||||
name: z.string().min(1).max(64).optional(),
|
||||
listenPort: z.number().int().positive().optional(),
|
||||
mtu: z.number().int().positive().optional(),
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
for (const width of [1440, 320]) {
|
||||
test(`WireGuard IPv6: форма сохраняет адрес после ошибки API, ширина ${width}`, async ({ page }) => {
|
||||
await page.setViewportSize({ width, height: 1000 })
|
||||
let attempts = 0
|
||||
const errors: string[] = []
|
||||
page.on("pageerror", e => errors.push(e.message))
|
||||
const iface = { id: "1:*152", rosId: "*152", name: "wg-ipv6", serverId: "1", serverName: "CHR", listenPort: 13231, mtu: 1420, address: "fd00:152::1/64", comment: "", enabled: true, status: "up", peers: [] }
|
||||
await page.route("**/health", route => route.fulfill({ json: { ok: true } }))
|
||||
await page.route("**/api/**", route => {
|
||||
const path = new URL(route.request().url()).pathname
|
||||
if (path === "/api/wireguard/interfaces" && route.request().method() === "POST") {
|
||||
expect(route.request().postDataJSON()).toMatchObject({ name: "wg-ipv6", address: "fd00:152::1/64", serverId: "1" })
|
||||
attempts++
|
||||
return attempts === 1 ? route.fulfill({ status: 403, json: { error: "Запись запрещена тест" } }) : route.fulfill({ status: 201, json: iface })
|
||||
}
|
||||
return route.fulfill({ json: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? [{ id: 1, name: "CHR", host: "192.0.2.1", enabled: true, type: "exit-node", country: "RU", status: "online" }] : path === "/api/wireguard" ? { interfaces: attempts > 1 ? [iface] : [] } : {} })
|
||||
})
|
||||
await page.goto("/wireguard")
|
||||
await page.getByRole("button", { name: "Новый интерфейс", exact: true }).first().click()
|
||||
const dialog = page.getByRole("dialog")
|
||||
await dialog.getByLabel("Сервер", { exact: true }).selectOption("1")
|
||||
await dialog.getByLabel("Имя интерфейса", { exact: true }).fill("wg-ipv6")
|
||||
await dialog.getByRole("button", { name: "Дополнительно", exact: true }).click()
|
||||
await dialog.getByLabel("IP на интерфейсе", { exact: true }).fill("fd00:152::1/64")
|
||||
await expect(dialog.getByLabel("IP на интерфейсе", { exact: true })).toHaveAttribute("placeholder", "IPv4/IPv6 с префиксом")
|
||||
await dialog.getByRole("button", { name: "Создать туннель", exact: true }).click()
|
||||
await expect(dialog.getByRole("alert")).toContainText("Запись запрещена тест")
|
||||
await expect(dialog.getByLabel("IP на интерфейсе", { exact: true })).toHaveValue("fd00:152::1/64")
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
|
||||
await dialog.getByRole("button", { name: "Создать туннель", exact: true }).click()
|
||||
await expect(dialog).toHaveCount(0)
|
||||
expect(attempts).toBe(2)
|
||||
expect(errors).toEqual([])
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user