diff --git a/backend/server.js b/backend/server.js index 12d2f07..bd40bb6 100644 --- a/backend/server.js +++ b/backend/server.js @@ -195,18 +195,89 @@ app.post('/api/servers', async (req, res) => { } }); +// --- Filters Routes (JSON format) --- + +// Get filters from S3 +app.get('/api/filters', async (req, res) => { + const params = { + Bucket: BUCKET_NAME, + Key: 'filters.json', + }; + + try { + const data = await s3.getObject(params).promise(); + const fileContent = data.Body.toString('utf-8'); + let filters = []; + + try { + filters = JSON.parse(fileContent); + // Ensure it's an array + if (!Array.isArray(filters)) { + filters = []; + } + } catch (parseError) { + console.error('Error parsing filters.json:', parseError); + filters = []; + } + + res.json(filters); + } catch (error) { + if (error.code === 'NoSuchKey') { + res.json([]); // Return empty array if file does not exist + } else { + console.error(error); + res.status(500).send('Error reading from S3'); + } + } +}); + +// Update filters in S3 +app.post('/api/filters', async (req, res) => { + const { domains: filters } = req.body; // Keep name 'domains' for consistency + + // Validate filters structure + if (!Array.isArray(filters)) { + return res.status(400).send('Filters must be an array'); + } + + // Validate each filter has required fields + for (let i = 0; i < filters.length; i++) { + const filter = filters[i]; + if (!filter.community || !filter.gateway) { + return res.status(400).send(`Filter at index ${i} is missing required fields`); + } + } + + const params = { + Bucket: BUCKET_NAME, + Key: 'filters.json', + Body: JSON.stringify(filters, null, 2), // Pretty print JSON + ContentType: 'application/json', + }; + + try { + await s3.putObject(params).promise(); + res.send('File updated successfully'); + } catch (error) { + console.error(error); + res.status(500).send('Error writing to S3'); + } +}); + // Новый эндпоинт для получения дат последнего изменения файлов S3 app.get('/api/s3/last-modified', async (req, res) => { try { - const [domainsHead, asnsHead, serversHead] = await Promise.all([ + const [domainsHead, asnsHead, serversHead, filtersHead] = await Promise.all([ s3.headObject({ Bucket: BUCKET_NAME, Key: 'domains.txt' }).promise(), s3.headObject({ Bucket: BUCKET_NAME, Key: 'asns.txt' }).promise(), - s3.headObject({ Bucket: BUCKET_NAME, Key: 'servers.json' }).promise() + s3.headObject({ Bucket: BUCKET_NAME, Key: 'servers.json' }).promise(), + s3.headObject({ Bucket: BUCKET_NAME, Key: 'filters.json' }).promise() ]); res.json({ domainsLastModified: domainsHead.LastModified ? domainsHead.LastModified.toISOString() : null, asnsLastModified: asnsHead.LastModified ? asnsHead.LastModified.toISOString() : null, - serversLastModified: serversHead.LastModified ? serversHead.LastModified.toISOString() : null + serversLastModified: serversHead.LastModified ? serversHead.LastModified.toISOString() : null, + filtersLastModified: filtersHead.LastModified ? filtersHead.LastModified.toISOString() : null }); } catch (error) { console.error(error); @@ -214,6 +285,132 @@ app.get('/api/s3/last-modified', async (req, res) => { } }); +// Generate MikroTik configuration from filters +app.get('/api/filters/generate-config', async (req, res) => { + const params = { + Bucket: BUCKET_NAME, + Key: 'filters.json', + }; + + try { + const data = await s3.getObject(params).promise(); + const fileContent = data.Body.toString('utf-8'); + let filters = []; + + try { + filters = JSON.parse(fileContent); + if (!Array.isArray(filters)) { + filters = []; + } + } catch (parseError) { + console.error('Error parsing filters.json:', parseError); + filters = []; + } + + if (filters.length === 0) { + return res.json({ config: '// Нет фильтров для генерации конфигурации' }); + } + + // Group filters by gateway + const gatewayGroups = {}; + filters.forEach(filter => { + if (!gatewayGroups[filter.gateway]) { + gatewayGroups[filter.gateway] = []; + } + gatewayGroups[filter.gateway].push(filter.community); + }); + + let config = '// Конфигурация фильтра frouting для MikroTik 7.14+\n'; + config += '// Сгенерировано автоматически\n\n'; + + Object.entries(gatewayGroups).forEach(([gateway, communities]) => { + config += `if (\n`; + communities.forEach((community, index) => { + config += `(bgp-communities includes ${community})`; + if (index < communities.length - 1) { + config += ` \nor `; + } + }); + config += `\n)\n{\n set gw ${gateway}; accept;\n}\nelse\n{\n reject;\n}\n\n`; + }); + + res.json({ config }); + } catch (error) { + if (error.code === 'NoSuchKey') { + res.json({ config: '// Файл filters.json не найден' }); + } else { + console.error(error); + res.status(500).send('Error generating configuration'); + } + } +}); + +// Export MikroTik configuration to S3 +app.post('/api/filters/export-config', async (req, res) => { + const params = { + Bucket: BUCKET_NAME, + Key: 'filters.json', + }; + + try { + const data = await s3.getObject(params).promise(); + const fileContent = data.Body.toString('utf-8'); + let filters = []; + + try { + filters = JSON.parse(fileContent); + if (!Array.isArray(filters)) { + filters = []; + } + } catch (parseError) { + console.error('Error parsing filters.json:', parseError); + filters = []; + } + + if (filters.length === 0) { + return res.json({ success: false, message: 'Нет фильтров для экспорта' }); + } + + // Group filters by gateway + const gatewayGroups = {}; + filters.forEach(filter => { + if (!gatewayGroups[filter.gateway]) { + gatewayGroups[filter.gateway] = []; + } + gatewayGroups[filter.gateway].push(filter.community); + }); + + let config = '// Конфигурация фильтра frouting для MikroTik 7.14+\n'; + config += '// Сгенерировано автоматически\n'; + config += `// Дата: ${new Date().toISOString()}\n\n`; + + Object.entries(gatewayGroups).forEach(([gateway, communities]) => { + config += `if (\n`; + communities.forEach((community, index) => { + config += `(bgp-communities includes ${community})`; + if (index < communities.length - 1) { + config += ` \nor `; + } + }); + config += `\n)\n{\n set gw ${gateway}; accept;\n}\nelse\n{\n reject;\n}\n\n`; + }); + + // Save configuration to S3 + const exportParams = { + Bucket: BUCKET_NAME, + Key: 'mikrotik-frouting-config.txt', + Body: config, + ContentType: 'text/plain', + }; + + await s3.putObject(exportParams).promise(); + res.json({ success: true, message: 'Конфигурация экспортирована в S3' }); + } catch (error) { + console.error(error); + res.status(500).send('Error exporting configuration'); + } +}); + // The "catchall" handler: for any request that doesn't // match one above, send back React's index.html file. app.get('*', (req, res) => { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 67715a5..15a2069 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -16,10 +16,12 @@ import { IconDatabase, IconAlertTriangle, IconAlertCircle, - IconServer + IconServer, + IconFilter } from '@tabler/icons-react'; import DataManager from './DataManager'; import ServerManager from './ServerManager'; +import FilterManager from './FilterManager'; import './App.css'; import axios from 'axios'; import { @@ -50,6 +52,7 @@ function MainLayout() { { id: 'domains', title: 'Домены', icon: IconWorld, path: '/domains' }, { id: 'asns', title: 'AS', icon: IconNetwork, path: '/asns' }, { id: 'servers', title: 'Серверы', icon: IconServer, path: '/servers' }, + { id: 'filters', title: 'Фильтры', icon: IconFilter, path: '/filters' }, { id: 'files', title: 'Файлы', icon: IconFileText, path: '/files' }, { id: 'cloud', title: 'Облако', icon: IconCloud, path: '/cloud' }, { id: 'security', title: 'Безопасность', icon: IconShield, path: '/security' }, @@ -70,7 +73,7 @@ function MainLayout() { S3 Lists Manager