diff --git a/backend/routes/mikrotikConfigRoutes.js b/backend/routes/mikrotikConfigRoutes.js index 1aff825..9c04831 100644 --- a/backend/routes/mikrotikConfigRoutes.js +++ b/backend/routes/mikrotikConfigRoutes.js @@ -1477,6 +1477,7 @@ async function getAddressLists(req, res) { const ADDRESS_LIST_PATH = 'ip/firewall/address-list'; const ADDRESS_LIST_REMOVE_CHUNK_SIZE = 100; const ADDRESS_LIST_ADD_CONCURRENCY = 8; +const ADDRESS_LIST_ADD_EXECUTE_CHUNK_SIZE = 80; function chunkArray(items, size) { if (!Array.isArray(items) || items.length === 0) return []; @@ -1529,8 +1530,8 @@ async function removeAddressListIds(client, ids) { return removed; } -async function addAddressListEntries(client, entries) { - const normalized = [...new Map( +function normalizeAddressListEntries(entries) { + return [...new Map( (Array.isArray(entries) ? entries : []) .map((entry) => { const address = String(entry?.address || '').trim(); @@ -1544,14 +1545,36 @@ async function addAddressListEntries(client, entries) { }) .filter(Boolean) ).values()]; +} - if (normalized.length === 0) return { added: 0, failed: 0 }; +function escapeRouterOsString(value) { + return `"${String(value || '') + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"')}"`; +} +function buildAddressListAddScript(entriesChunk) { + return entriesChunk + .map((params) => { + const parts = [ + '/ip/firewall/address-list/add', + `address=${escapeRouterOsString(params.address)}`, + `list=${escapeRouterOsString(params.list)}`, + ]; + if (params.comment != null && String(params.comment).trim()) { + parts.push(`comment=${escapeRouterOsString(params.comment)}`); + } + return parts.join(' '); + }) + .join(';\n'); +} + +async function addAddressListEntriesParallel(client, normalized) { + if (!Array.isArray(normalized) || normalized.length === 0) return { added: 0, failed: 0 }; let index = 0; let added = 0; let failed = 0; const workersCount = Math.min(ADDRESS_LIST_ADD_CONCURRENCY, normalized.length); - const workers = Array.from({ length: workersCount }, async () => { while (true) { const current = index; @@ -1567,11 +1590,40 @@ async function addAddressListEntries(client, entries) { } } }); - await Promise.all(workers); return { added, failed }; } +async function addAddressListEntries(client, entries) { + const normalized = normalizeAddressListEntries(entries); + if (normalized.length === 0) return { added: 0, failed: 0 }; + + let added = 0; + let failed = 0; + const chunks = chunkArray(normalized, ADDRESS_LIST_ADD_EXECUTE_CHUNK_SIZE); + + for (const chunk of chunks) { + const script = buildAddressListAddScript(chunk); + try { + // Один execute на пачку резко снижает HTTP overhead. + await client.command('execute', { script }); + added += chunk.length; + continue; + } catch (err) { + console.warn( + `[address-lists] execute add fallback (${chunk.length} entries):`, + err?.message || String(err) + ); + } + + const fallbackResult = await addAddressListEntriesParallel(client, chunk); + added += fallbackResult.added; + failed += fallbackResult.failed; + } + + return { added, failed }; +} + /** * POST /api/mikrotik/address-lists/apply-summary * Body: { serverId, removeIds: string[], addEntries: { address, list, comment? }[] }