feat(MikrotikConfig): add API endpoint for applying MikroTik configuration; update frontend to support configuration application with dry-run option and modal interface
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m56s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m56s
This commit is contained in:
@@ -14,6 +14,7 @@ const {
|
||||
} = require('../utils/mikrotikInterfaceGenerator');
|
||||
const { readS3TextObject } = require('../services/s3Service');
|
||||
const { RouterOSAPI } = require('node-routeros');
|
||||
const { applyBlock } = require('../services/mikrotikApplyService');
|
||||
|
||||
async function fetchJsonFromS3(key, defaultValue = null) {
|
||||
try {
|
||||
@@ -227,9 +228,106 @@ async function testMikrotikConnection(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mikrotik/apply
|
||||
* Body: { serverId: string, type?: 'interfaces'|'recursive'|'all', dryRun?: boolean }
|
||||
* Применяет конфигурацию (интерфейсы, маршруты) на MikroTik через RouterOS API.
|
||||
* dryRun=true — только показать план, не выполнять.
|
||||
*/
|
||||
async function applyMikrotikConfig(req, res) {
|
||||
try {
|
||||
const { serverId, type = 'all', dryRun = true } = req.body || {};
|
||||
if (!serverId) {
|
||||
return sendError(res, 400, 'serverId is required', 'E_BAD_REQUEST');
|
||||
}
|
||||
|
||||
const servers = await fetchJsonFromS3('servers.json', []);
|
||||
const server = servers.find(s => s.id === serverId || s.dns === serverId || s.ip === serverId);
|
||||
if (!server) {
|
||||
return sendError(res, 404, 'Server not found', 'E_NOT_FOUND');
|
||||
}
|
||||
if (server.type !== 'jumphost') {
|
||||
return sendError(res, 400, 'Only jumphost servers support apply via API', 'E_BAD_REQUEST');
|
||||
}
|
||||
if (!server.encryptedMikrotikPassword) {
|
||||
return sendError(res, 400, 'MikroTik password not configured. Add credentials in Server settings.', 'E_BAD_REQUEST');
|
||||
}
|
||||
|
||||
let password;
|
||||
try {
|
||||
password = decrypt(server.encryptedMikrotikPassword);
|
||||
} catch (decErr) {
|
||||
return sendError(res, 500, 'Failed to decrypt MikroTik password', 'E_DECRYPT');
|
||||
}
|
||||
|
||||
const host = server.mikrotikHost || server.ip || server.dns;
|
||||
const port = parseInt(server.mikrotikPort || '8728', 10) || 8728;
|
||||
const user = server.mikrotikUser || 'admin';
|
||||
|
||||
const config = await fetchJsonFromS3('network-config.json', { gateways: [], tunnelInterfaces: [] });
|
||||
const passwordIds = (config.tunnelInterfaces || [])
|
||||
.filter(i => i.ipsecPasswordId && String(i.ipsecPasswordId).trim() !== '')
|
||||
.map(i => i.ipsecPasswordId.trim());
|
||||
const passwordMap = await fetchIpsecPasswordMap(passwordIds);
|
||||
|
||||
const includeInterfaces = type === 'interfaces' || type === 'all';
|
||||
const includeRecursive = type === 'recursive' || type === 'all';
|
||||
const blocks = await buildMikrotikConfig(config, servers, passwordMap, {
|
||||
format: 'json',
|
||||
serverId,
|
||||
includeInterfaces,
|
||||
includeRecursive,
|
||||
});
|
||||
|
||||
const conn = new RouterOSAPI({
|
||||
host: String(host),
|
||||
user: String(user),
|
||||
password: String(password),
|
||||
port: Number(port) || 8728,
|
||||
});
|
||||
|
||||
await conn.connect();
|
||||
|
||||
const allResults = [];
|
||||
for (const block of blocks) {
|
||||
const blockResults = await applyBlock(conn, block, !!dryRun);
|
||||
allResults.push({
|
||||
blockType: block.type,
|
||||
serverName: block.serverName,
|
||||
results: blockResults,
|
||||
});
|
||||
}
|
||||
|
||||
conn.close();
|
||||
|
||||
const summary = {
|
||||
created: allResults.flatMap(b => b.results).filter(r => r.status === 'created' || r.status === 'would_create').length,
|
||||
updated: allResults.flatMap(b => b.results).filter(r => r.status === 'updated' || r.status === 'would_update').length,
|
||||
skipped: allResults.flatMap(b => b.results).filter(r => r.status === 'skip').length,
|
||||
errors: allResults.flatMap(b => b.results).filter(r => r.status === 'error'),
|
||||
};
|
||||
|
||||
res.json({
|
||||
ok: true,
|
||||
dryRun: !!dryRun,
|
||||
summary,
|
||||
results: allResults,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error applying MikroTik config:', error);
|
||||
const msg = error.message || String(error);
|
||||
return res.status(500).json({
|
||||
ok: false,
|
||||
error: msg.includes('ECONNREFUSED') ? 'Соединение отклонено' :
|
||||
msg.includes('Authentication') || msg.includes('login') ? 'Неверный логин или пароль' : msg,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateMikrotikConfig,
|
||||
generateInterfaces,
|
||||
generateRecursiveRoutes,
|
||||
testMikrotikConnection,
|
||||
applyMikrotikConfig,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user