Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 6m7s
1188 lines
38 KiB
JavaScript
1188 lines
38 KiB
JavaScript
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');
|
|
}
|
|
});
|
|
|
|
// --- Billing Routes ---
|
|
|
|
// Get billing data from S3
|
|
app.get('/api/billing', async (req, res) => {
|
|
const params = {
|
|
Bucket: BUCKET_NAME,
|
|
Key: 'servers-billing.json',
|
|
};
|
|
|
|
try {
|
|
const data = await s3.getObject(params).promise();
|
|
const fileContent = data.Body.toString('utf-8');
|
|
let billingData = [];
|
|
|
|
try {
|
|
billingData = JSON.parse(fileContent);
|
|
// Ensure it's an array
|
|
if (!Array.isArray(billingData)) {
|
|
billingData = [];
|
|
}
|
|
} catch (parseError) {
|
|
console.error('Error parsing servers-billing.json:', parseError);
|
|
billingData = [];
|
|
}
|
|
|
|
res.json(billingData);
|
|
} 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 billing data in S3
|
|
app.post('/api/billing', async (req, res) => {
|
|
const { domains: billingData } = req.body; // Keep name 'domains' for consistency
|
|
|
|
// Validate billing data structure
|
|
if (!Array.isArray(billingData)) {
|
|
return res.status(400).send('Billing data must be an array');
|
|
}
|
|
|
|
// Validate each billing item has required fields
|
|
for (let i = 0; i < billingData.length; i++) {
|
|
const item = billingData[i];
|
|
if (!item.hostName || !item.nodeName || !item.country || !item.provider) {
|
|
return res.status(400).send(`Billing item at index ${i} is missing required fields`);
|
|
}
|
|
}
|
|
|
|
const params = {
|
|
Bucket: BUCKET_NAME,
|
|
Key: 'servers-billing.json',
|
|
Body: JSON.stringify(billingData, 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`;
|
|
config += '/routing filter bgp-in-tmp {\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 += ` or \n`;
|
|
}
|
|
});
|
|
config += `\n )\n {\n set gw ${gateway}; accept;\n }\n else\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 += ` or \n`;
|
|
}
|
|
});
|
|
config += `\n )\n {\n set gw ${gateway}; accept;\n }`;
|
|
|
|
if (gatewayIndex === gatewayEntries.length - 1) {
|
|
config += `\n else\n {\n reject;\n }\n`;
|
|
} else {
|
|
config += `\n`;
|
|
}
|
|
});
|
|
}
|
|
|
|
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`;
|
|
config += '/routing filter bgp-in-tmp {\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 += ` or \n`;
|
|
}
|
|
});
|
|
config += `\n )\n {\n set gw ${gateway}; accept;\n }\n else\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 += ` or \n`;
|
|
}
|
|
});
|
|
config += `\n )\n {\n set gw ${gateway}; accept;\n }`;
|
|
|
|
if (gatewayIndex === gatewayEntries.length - 1) {
|
|
config += `\n else\n {\n reject;\n }\n`;
|
|
} else {
|
|
config += `\n`;
|
|
}
|
|
});
|
|
}
|
|
|
|
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`;
|
|
config += '/routing filter bgp-in-tmp {\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 += ` or \n`;
|
|
}
|
|
});
|
|
config += `\n )\n {\n set gw ${gateway}; accept;\n }\n else\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 += ` or \n`;
|
|
}
|
|
});
|
|
config += `\n )\n {\n set gw ${gateway}; accept;\n }`;
|
|
|
|
if (gatewayIndex === gatewayEntries.length - 1) {
|
|
config += `\n else\n {\n reject;\n }\n`;
|
|
} else {
|
|
config += `\n`;
|
|
}
|
|
});
|
|
}
|
|
|
|
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');
|
|
}
|
|
});
|
|
|
|
// --- Auto URL Routes ---
|
|
|
|
// Get auto URLs from S3
|
|
app.get('/api/auto-urls', async (req, res) => {
|
|
const params = {
|
|
Bucket: BUCKET_NAME,
|
|
Key: 'bgp_data/auto_url/urls.txt',
|
|
};
|
|
|
|
try {
|
|
const data = await s3.getObject(params).promise();
|
|
const fileContent = data.Body.toString('utf-8');
|
|
const urls = fileContent.split('\n').filter(line => line).map(line => {
|
|
const parts = line.trim().split(/\s+/);
|
|
const url = parts[0] || '';
|
|
const community = parts[1] || '';
|
|
return { url, community };
|
|
});
|
|
res.json(urls);
|
|
} 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 auto URLs from S3');
|
|
}
|
|
}
|
|
});
|
|
|
|
// Update auto URLs in S3
|
|
app.post('/api/auto-urls', async (req, res) => {
|
|
const { urls } = req.body;
|
|
const fileContent = urls.map(u => `${u.url} ${u.community}`).join('\n');
|
|
|
|
const params = {
|
|
Bucket: BUCKET_NAME,
|
|
Key: 'bgp_data/auto_url/urls.txt',
|
|
Body: fileContent,
|
|
ContentType: 'text/plain',
|
|
};
|
|
|
|
try {
|
|
await s3.putObject(params).promise();
|
|
res.send('Auto URLs updated successfully');
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).send('Error writing auto URLs to S3');
|
|
}
|
|
});
|
|
|
|
// Process auto URLs and update IPs
|
|
app.post('/api/auto-urls/process', async (req, res) => {
|
|
const https = require('https');
|
|
const http = require('http');
|
|
|
|
try {
|
|
// Get current auto URLs
|
|
const urlsParams = {
|
|
Bucket: BUCKET_NAME,
|
|
Key: 'bgp_data/auto_url/urls.txt',
|
|
};
|
|
|
|
let urls = [];
|
|
try {
|
|
const urlsData = await s3.getObject(urlsParams).promise();
|
|
const urlsContent = urlsData.Body.toString('utf-8');
|
|
urls = urlsContent.split('\n').filter(line => line).map(line => {
|
|
const parts = line.trim().split(/\s+/);
|
|
return { url: parts[0] || '', community: parts[1] || '' };
|
|
});
|
|
} catch (error) {
|
|
if (error.code !== 'NoSuchKey') {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
if (urls.length === 0) {
|
|
return res.status(400).send('No URLs to process');
|
|
}
|
|
|
|
// Get current IPs
|
|
const ipsParams = {
|
|
Bucket: BUCKET_NAME,
|
|
Key: 'bgp_data/ips.txt',
|
|
};
|
|
|
|
let currentIps = [];
|
|
try {
|
|
const ipsData = await s3.getObject(ipsParams).promise();
|
|
const ipsContent = ipsData.Body.toString('utf-8');
|
|
currentIps = ipsContent.split('\n').filter(line => line).map(line => {
|
|
const parts = line.trim().split(/\s+/);
|
|
return { ipRange: parts[0] || '', community: parts[1] || '' };
|
|
});
|
|
} catch (error) {
|
|
if (error.code !== 'NoSuchKey') {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Process each URL
|
|
const newIps = [];
|
|
for (const urlData of urls) {
|
|
try {
|
|
const url = urlData.url.trim();
|
|
const community = urlData.community.trim();
|
|
|
|
if (!url || !community) continue;
|
|
|
|
// Download content from URL
|
|
const content = await new Promise((resolve, reject) => {
|
|
const protocol = url.startsWith('https:') ? https : http;
|
|
const req = protocol.get(url, (res) => {
|
|
let data = '';
|
|
res.on('data', (chunk) => data += chunk);
|
|
res.on('end', () => resolve(data));
|
|
});
|
|
req.on('error', reject);
|
|
req.setTimeout(10000, () => req.destroy());
|
|
});
|
|
|
|
// Parse IPs from content
|
|
const lines = content.split('\n');
|
|
for (const line of lines) {
|
|
const ip = line.trim();
|
|
if (ip && (ip.includes('.') || ip.includes(':'))) {
|
|
newIps.push({ ipRange: ip, community });
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error(`Error processing URL ${urlData.url}:`, error);
|
|
}
|
|
}
|
|
|
|
// Merge with existing IPs (avoid duplicates)
|
|
const existingIpRanges = new Set(currentIps.map(ip => ip.ipRange));
|
|
const uniqueNewIps = newIps.filter(ip => !existingIpRanges.has(ip.ipRange));
|
|
|
|
const allIps = [...currentIps, ...uniqueNewIps];
|
|
|
|
// Save updated IPs
|
|
const updatedIpsContent = allIps.map(ip => `${ip.ipRange} ${ip.community}`).join('\n');
|
|
const updateParams = {
|
|
Bucket: BUCKET_NAME,
|
|
Key: 'bgp_data/ips.txt',
|
|
Body: updatedIpsContent,
|
|
ContentType: 'text/plain',
|
|
};
|
|
|
|
await s3.putObject(updateParams).promise();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `Обработано ${urls.length} URL, добавлено ${uniqueNewIps.length} новых IP-адресов`,
|
|
processedUrls: urls.length,
|
|
newIpsCount: uniqueNewIps.length,
|
|
totalIpsCount: allIps.length
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error processing auto URLs:', error);
|
|
res.status(500).send('Error processing auto URLs');
|
|
}
|
|
});
|
|
|
|
// 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}`);
|
|
});
|