require('dotenv').config(); const express = require('express'); const AWS = require('aws-sdk'); const cors = require('cors'); const path = require('path'); const app = express(); const port = 3001; app.use(cors()); app.use(express.json()); // Serve static files from the React app app.use(express.static(path.join(__dirname, 'public'))); // Configure AWS S3 const s3 = new AWS.S3({ endpoint: 'https://storage.yandexcloud.net', region: process.env.AWS_REGION, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, } }); const BUCKET_NAME = process.env.S3_BUCKET_NAME; const FILE_KEY = 'bgp_data/domains.txt'; // Get domains from S3 app.get('/api/domains', async (req, res) => { const params = { Bucket: BUCKET_NAME, Key: FILE_KEY, }; try { const data = await s3.getObject(params).promise(); const fileContent = data.Body.toString('utf-8'); const domains = fileContent.split('\n').filter(line => line).map(line => { // Trim the line to remove any leading/trailing whitespace, including \r // And split by any whitespace to be more robust const parts = line.trim().split(/\s+/); const domain = parts[0] || ''; const type = parts[1] || ''; return { domain, type }; }); res.json(domains); } 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 domains in S3 app.post('/api/domains', async (req, res) => { const { domains } = req.body; const fileContent = domains.map(d => `${d.domain} ${d.type}`).join('\n'); const params = { Bucket: BUCKET_NAME, Key: FILE_KEY, Body: fileContent, ContentType: 'text/plain', }; try { await s3.putObject(params).promise(); res.send('File updated successfully'); } catch (error) { console.error(error); res.status(500).send('Error writing to S3'); } }); // --- ASNs Routes --- // Get ASNs from S3 app.get('/api/asns', async (req, res) => { const params = { Bucket: BUCKET_NAME, Key: 'bgp_data/asns.txt', }; try { const data = await s3.getObject(params).promise(); const fileContent = data.Body.toString('utf-8'); const asns = fileContent.split('\n').filter(line => line).map(line => { const parts = line.trim().split(/\s+/); const domain = parts[0] || ''; // Keep name 'domain' for consistency in component const type = parts[1] || ''; return { domain, type }; }); res.json(asns); } catch (error) { if (error.code === 'NoSuchKey') { res.json([]); } else { console.error(error); res.status(500).send('Error reading from S3'); } } }); // Update ASNs in S3 app.post('/api/asns', async (req, res) => { const { domains: asns } = req.body; // Keep name 'domains' for consistency const fileContent = asns.map(a => `${a.domain} ${a.type}`).join('\n'); const params = { Bucket: BUCKET_NAME, Key: 'bgp_data/asns.txt', Body: fileContent, ContentType: 'text/plain', }; try { await s3.putObject(params).promise(); res.send('File updated successfully'); } catch (error) { console.error(error); res.status(500).send('Error writing to S3'); } }); // --- Domains New Routes --- // Get domains-new from S3 app.get('/api/domains-new', async (req, res) => { const params = { Bucket: BUCKET_NAME, Key: 'bgp_data/domains_community.txt', }; try { const data = await s3.getObject(params).promise(); const fileContent = data.Body.toString('utf-8'); const domains = fileContent.split('\n').filter(line => line).map(line => { const parts = line.trim().split(/\s+/); const domain = parts[0] || ''; const community = parts[1] || ''; return { domain, community }; }); res.json(domains); } 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 domains-new in S3 app.post('/api/domains-new', async (req, res) => { const { domains } = req.body; const fileContent = domains.map(d => `${d.domain} ${d.community}`).join('\n'); const params = { Bucket: BUCKET_NAME, Key: 'bgp_data/domains_community.txt', Body: fileContent, ContentType: 'text/plain', }; try { await s3.putObject(params).promise(); res.send('File updated successfully'); } catch (error) { console.error(error); res.status(500).send('Error writing to S3'); } }); // --- IP Ranges Routes --- // Get IP ranges from S3 app.get('/api/ip-ranges', async (req, res) => { const params = { Bucket: BUCKET_NAME, Key: 'bgp_data/ips.txt', }; try { const data = await s3.getObject(params).promise(); const fileContent = data.Body.toString('utf-8'); const ipRanges = fileContent.split('\n').filter(line => line).map(line => { const parts = line.trim().split(/\s+/); const ipRange = parts[0] || ''; const community = parts[1] || ''; return { ipRange, community }; }); res.json(ipRanges); } 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 IP ranges in S3 app.post('/api/ip-ranges', async (req, res) => { const { ipRanges } = req.body; const fileContent = ipRanges.map(ip => `${ip.ipRange} ${ip.community}`).join('\n'); const params = { Bucket: BUCKET_NAME, Key: 'bgp_data/ips.txt', Body: fileContent, ContentType: 'text/plain', }; try { await s3.putObject(params).promise(); res.send('File updated successfully'); } catch (error) { console.error(error); res.status(500).send('Error writing to S3'); } }); // --- Servers Routes (JSON format) --- // Get servers from S3 app.get('/api/servers', async (req, res) => { const params = { Bucket: BUCKET_NAME, Key: 'servers.json', }; try { const data = await s3.getObject(params).promise(); const fileContent = data.Body.toString('utf-8'); let servers = []; try { servers = JSON.parse(fileContent); // Ensure it's an array if (!Array.isArray(servers)) { servers = []; } } catch (parseError) { console.error('Error parsing servers.json:', parseError); servers = []; } res.json(servers); } 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 servers in S3 app.post('/api/servers', async (req, res) => { const { domains: servers } = req.body; // Keep name 'domains' for consistency // Validate servers structure if (!Array.isArray(servers)) { return res.status(400).send('Servers must be an array'); } // Validate each server has required fields for (let i = 0; i < servers.length; i++) { const server = servers[i]; if (!server.ip || !server.dns || !server.country || !server.provider || !server.tunnel) { return res.status(400).send(`Server at index ${i} is missing required fields`); } } const params = { Bucket: BUCKET_NAME, Key: 'servers.json', Body: JSON.stringify(servers, 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'); } }); // --- 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, domainsNewHead, asnsHead, serversHead, filtersHead] = await Promise.all([ s3.headObject({ Bucket: BUCKET_NAME, Key: 'bgp_data/domains.txt' }).promise(), s3.headObject({ Bucket: BUCKET_NAME, Key: 'bgp_data/domains_community.txt' }).promise(), s3.headObject({ Bucket: BUCKET_NAME, Key: 'bgp_data/asns.txt' }).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, domainsNewLastModified: domainsNewHead.LastModified ? domainsNewHead.LastModified.toISOString() : null, asnsLastModified: asnsHead.LastModified ? asnsHead.LastModified.toISOString() : null, serversLastModified: serversHead.LastModified ? serversHead.LastModified.toISOString() : null, filtersLastModified: filtersHead.LastModified ? filtersHead.LastModified.toISOString() : null }); } catch (error) { console.error(error); res.status(500).send('Error fetching last modified dates from S3'); } }); // 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: '// No filters to generate configuration' }); } // 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 filter configuration for MikroTik 7.14+\n'; config += '// Generated automatically\n'; config += `// Date: ${new Date().toISOString()}\n\n`; // Если есть только один gateway, создаем простую конфигурацию if (Object.keys(gatewayGroups).length === 1) { const [gateway, communities] = Object.entries(gatewayGroups)[0]; 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`; } else { // Если несколько gateway, создаем каскадную структуру const gatewayEntries = Object.entries(gatewayGroups); gatewayEntries.forEach(([gateway, communities], gatewayIndex) => { if (gatewayIndex === 0) { config += `if (\n`; } else { config += `else 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}`; if (gatewayIndex === gatewayEntries.length - 1) { config += `\nelse\n{\n reject;\n}\n`; } else { config += `\n`; } }); } res.json({ config }); } catch (error) { if (error.code === 'NoSuchKey') { res.json({ config: '// filters.json file not found' }); } 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 filter configuration for MikroTik 7.14+\n'; config += '// Generated automatically\n'; config += `// Date: ${new Date().toISOString()}\n\n`; // Если есть только один gateway, создаем простую конфигурацию if (Object.keys(gatewayGroups).length === 1) { const [gateway, communities] = Object.entries(gatewayGroups)[0]; 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`; } else { // Если несколько gateway, создаем каскадную структуру const gatewayEntries = Object.entries(gatewayGroups); gatewayEntries.forEach(([gateway, communities], gatewayIndex) => { if (gatewayIndex === 0) { config += `if (\n`; } else { config += `else 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}`; if (gatewayIndex === gatewayEntries.length - 1) { config += `\nelse\n{\n reject;\n}\n`; } else { config += `\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'); } }); // --- Server Configs Routes --- // Get server configs list app.get('/api/server-configs', async (req, res) => { const params = { Bucket: BUCKET_NAME, Key: 'server-configs.json', }; try { const data = await s3.getObject(params).promise(); const fileContent = data.Body.toString('utf-8'); let servers = []; try { servers = JSON.parse(fileContent); if (!Array.isArray(servers)) { servers = []; } } catch (parseError) { console.error('Error parsing server-configs.json:', parseError); servers = []; } res.json(servers); } 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 server configs from S3'); } } }); // Update server configs list app.post('/api/server-configs', async (req, res) => { const { servers } = req.body; // Validate servers structure if (!Array.isArray(servers)) { return res.status(400).send('Servers must be an array'); } // Validate each server has required fields for (let i = 0; i < servers.length; i++) { const server = servers[i]; if (!server.id || !server.name) { return res.status(400).send(`Server at index ${i} is missing required fields`); } } const params = { Bucket: BUCKET_NAME, Key: 'server-configs.json', Body: JSON.stringify(servers, null, 2), // Pretty print JSON ContentType: 'application/json', }; try { await s3.putObject(params).promise(); res.send('Server configs updated successfully'); } catch (error) { console.error(error); res.status(500).send('Error writing server configs to S3'); } }); // Get specific server config app.get('/api/server-configs/:serverId', async (req, res) => { const { serverId } = req.params; const params = { Bucket: BUCKET_NAME, Key: `filter-manager/config-${serverId}.txt`, }; try { const data = await s3.getObject(params).promise(); const config = data.Body.toString('utf-8'); res.json({ config }); } catch (error) { if (error.code === 'NoSuchKey') { res.json({ config: '// Конфигурация не найдена' }); } else { console.error(error); res.status(500).send('Error reading server config from S3'); } } }); // Save specific server config app.post('/api/server-configs/:serverId', async (req, res) => { const { serverId } = req.params; const { config } = req.body; if (!config) { return res.status(400).send('Config is required'); } const params = { Bucket: BUCKET_NAME, Key: `filter-manager/config-${serverId}.txt`, Body: config, ContentType: 'text/plain', }; try { await s3.putObject(params).promise(); res.send('Server config saved successfully'); } catch (error) { console.error(error); res.status(500).send('Error writing server config to S3'); } }); // Delete specific server config app.delete('/api/server-configs/:serverId', async (req, res) => { const { serverId } = req.params; const params = { Bucket: BUCKET_NAME, Key: `filter-manager/config-${serverId}.txt`, }; try { await s3.deleteObject(params).promise(); res.send('Server config deleted successfully'); } catch (error) { console.error(error); res.status(500).send('Error deleting server config from S3'); } }); // Delete server completely (config + filters) app.delete('/api/server-configs/:serverId/complete', async (req, res) => { const { serverId } = req.params; try { // Удаляем конфигурацию сервера const configParams = { Bucket: BUCKET_NAME, Key: `filter-manager/config-${serverId}.txt`, }; // Удаляем фильтры сервера const filtersParams = { Bucket: BUCKET_NAME, Key: `filter-manager/server-filters-${serverId}.json`, }; // Удаляем оба файла параллельно await Promise.allSettled([ s3.deleteObject(configParams).promise(), s3.deleteObject(filtersParams).promise() ]); res.send('Server and all associated files deleted successfully'); } catch (error) { console.error(error); res.status(500).send('Error deleting server files from S3'); } }); // --- Server Filters Routes --- // Get server filters app.get('/api/server-filters/:serverId', async (req, res) => { const { serverId } = req.params; const params = { Bucket: BUCKET_NAME, Key: `filter-manager/server-filters-${serverId}.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 server filters:', 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 server filters from S3'); } } }); // Generate MikroTik configuration from server filters app.post('/api/server-filters/generate-config', async (req, res) => { console.log('Received generate-config request with filters:', req.body); const { filters } = req.body; if (!Array.isArray(filters) || filters.length === 0) { console.log('No filters provided, returning empty config'); return res.json({ config: '// No filters to generate configuration' }); } // Group filters by gateway const gatewayGroups = {}; filters.forEach(filter => { if (!gatewayGroups[filter.gateway]) { gatewayGroups[filter.gateway] = []; } gatewayGroups[filter.gateway].push(filter.community); }); console.log('Grouped filters by gateway:', gatewayGroups); let config = '// Frouting filter configuration for MikroTik 7.14+\n'; config += '// Generated automatically\n'; config += `// Date: ${new Date().toISOString()}\n\n`; // Если есть только один gateway, создаем простую конфигурацию if (Object.keys(gatewayGroups).length === 1) { const [gateway, communities] = Object.entries(gatewayGroups)[0]; 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`; } else { // Если несколько gateway, создаем каскадную структуру const gatewayEntries = Object.entries(gatewayGroups); gatewayEntries.forEach(([gateway, communities], gatewayIndex) => { if (gatewayIndex === 0) { config += `if (\n`; } else { config += `else 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}`; if (gatewayIndex === gatewayEntries.length - 1) { config += `\nelse\n{\n reject;\n}\n`; } else { config += `\n`; } }); } console.log('Generated config:', config); console.log('Sending response:', { config }); res.json({ config }); }); // Update server filters app.post('/api/server-filters/:serverId', async (req, res) => { const { serverId } = req.params; const { filters } = req.body; // 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: `filter-manager/server-filters-${serverId}.json`, Body: JSON.stringify(filters, null, 2), // Pretty print JSON ContentType: 'application/json', }; try { await s3.putObject(params).promise(); res.send('Server filters updated successfully'); } catch (error) { console.error(error); res.status(500).send('Error writing server filters to S3'); } }); // --- Simple Filters Routes --- // Get simple filters app.get('/api/simple-filters', async (req, res) => { const params = { Bucket: BUCKET_NAME, Key: 'filter-manager/simple-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 simple filters:', 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 simple filters from S3'); } } }); // Update simple filters app.post('/api/simple-filters', async (req, res) => { const { filters } = req.body; // 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: 'filter-manager/simple-filters.json', Body: JSON.stringify(filters, null, 2), // Pretty print JSON ContentType: 'application/json', }; try { await s3.putObject(params).promise(); res.send('Simple filters updated successfully'); } catch (error) { console.error(error); res.status(500).send('Error writing simple filters to S3'); } }); // The "catchall" handler: for any request that doesn't // match one above, send back React's index.html file. app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); });