feat: Implement auto URL management in server.js and integrate AutoUrlManager in App.jsx for enhanced URL processing and IP updates
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 21m16s

This commit is contained in:
2025-07-21 14:11:41 +07:00
parent 9ca9d3f2df
commit 7642d86a82
3 changed files with 446 additions and 1 deletions
+5 -1
View File
@@ -17,7 +17,8 @@ import {
IconAlertTriangle,
IconAlertCircle,
IconServer,
IconFilter
IconFilter,
IconDownload
} from '@tabler/icons-react';
import DataManager from './DataManager';
import ServerManager from './ServerManager';
@@ -25,6 +26,7 @@ import FilterManager from './FilterManager';
import DomainsNewManager from './DomainsNewManager';
import IPRangesManager from './IPRangesManager';
import ASNsNewManager from './ASNsNewManager';
import AutoUrlManager from './AutoUrlManager';
import './App.css';
import axios from 'axios';
import {
@@ -56,6 +58,7 @@ function MainLayout() {
{ id: 'domains-new', title: 'Домены New', icon: IconWorld, path: '/domains-new' },
{ id: 'ip-ranges', title: 'IP-диапазоны', icon: IconNetwork, path: '/ip-ranges' },
{ id: 'asns', title: 'AS', icon: IconNetwork, path: '/asns' },
{ id: 'auto-urls', title: 'Авто URL', icon: IconDownload, path: '/auto-urls' },
{ id: 'servers', title: 'Серверы', icon: IconServer, path: '/servers' },
{ id: 'filters', title: 'Фильтры', icon: IconFilter, path: '/filters' },
{ id: 'files', title: 'Файлы', icon: IconFileText, path: '/files' },
@@ -103,6 +106,7 @@ function MainLayout() {
<Route path="/domains-new" element={<DomainsNewManager />} />
<Route path="/ip-ranges" element={<IPRangesManager />} />
<Route path="/asns" element={<ASNsNewManager />} />
<Route path="/auto-urls" element={<AutoUrlManager />} />
<Route path="/servers" element={<ServerManager />} />
<Route path="/filters" element={<FilterManager />} />
<Route path="/" element={<Navigate to="/domains" replace />} />
+275
View File
@@ -0,0 +1,275 @@
import { useState, useEffect } from 'react';
import axios from 'axios';
import {
IconPlus,
IconTrash,
IconDownload,
IconAlertCircle,
IconCheck,
IconLoader
} from '@tabler/icons-react';
function AutoUrlManager() {
const [urls, setUrls] = useState([]);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [processing, setProcessing] = useState(false);
const [message, setMessage] = useState('');
const [messageType, setMessageType] = useState('');
useEffect(() => {
fetchUrls();
}, []);
const fetchUrls = async () => {
try {
setLoading(true);
const response = await axios.get('/api/auto-urls');
setUrls(response.data);
} catch (error) {
console.error('Error fetching URLs:', error);
setMessage('Ошибка при загрузке URL-адресов');
setMessageType('error');
} finally {
setLoading(false);
}
};
const addUrl = () => {
setUrls([...urls, { url: '', community: '' }]);
};
const removeUrl = (index) => {
setUrls(urls.filter((_, i) => i !== index));
};
const updateUrl = (index, field, value) => {
const newUrls = [...urls];
newUrls[index][field] = value;
setUrls(newUrls);
};
const saveUrls = async () => {
try {
setSaving(true);
setMessage('');
// Validate URLs
const validUrls = urls.filter(u => u.url.trim() && u.community.trim());
if (validUrls.length === 0) {
setMessage('Добавьте хотя бы один URL с community');
setMessageType('error');
return;
}
await axios.post('/api/auto-urls', { urls: validUrls });
setUrls(validUrls);
setMessage('URL-адреса сохранены успешно');
setMessageType('success');
} catch (error) {
console.error('Error saving URLs:', error);
setMessage('Ошибка при сохранении URL-адресов');
setMessageType('error');
} finally {
setSaving(false);
}
};
const processUrls = async () => {
try {
setProcessing(true);
setMessage('');
const response = await axios.post('/api/auto-urls/process');
setMessage(response.data.message);
setMessageType('success');
} catch (error) {
console.error('Error processing URLs:', error);
setMessage(error.response?.data || 'Ошибка при обработке URL-адресов');
setMessageType('error');
} finally {
setProcessing(false);
}
};
if (loading) {
return (
<div className="d-flex justify-content-center align-items-center" style={{ minHeight: '200px' }}>
<IconLoader className="animate-spin" size={32} />
</div>
);
}
return (
<div>
<div className="page-header d-print-none mb-4">
<div className="row align-items-center">
<div className="col">
<h2 className="page-title">Автоматические URL</h2>
<div className="page-pretitle">Управление автоматическими URL-адресами для загрузки IP-списков</div>
</div>
</div>
</div>
{message && (
<div className={`alert alert-${messageType === 'error' ? 'danger' : 'success'} alert-dismissible`} role="alert">
<div className="d-flex">
{messageType === 'error' ? <IconAlertCircle className="me-2" /> : <IconCheck className="me-2" />}
{message}
</div>
<button type="button" className="btn-close" onClick={() => setMessage('')}></button>
</div>
)}
<div className="card">
<div className="card-header">
<h3 className="card-title">URL-адреса для автоматической загрузки</h3>
<div className="card-actions">
<button
className="btn btn-primary btn-sm"
onClick={addUrl}
disabled={saving || processing}
>
<IconPlus className="me-1" />
Добавить URL
</button>
</div>
</div>
<div className="card-body">
{urls.length === 0 ? (
<div className="text-center text-muted py-4">
<IconAlertCircle size={48} className="mb-3" />
<p>Нет добавленных URL-адресов</p>
<button
className="btn btn-primary"
onClick={addUrl}
disabled={saving || processing}
>
<IconPlus className="me-1" />
Добавить первый URL
</button>
</div>
) : (
<div className="table-responsive">
<table className="table table-vcenter">
<thead>
<tr>
<th>URL</th>
<th>Community</th>
<th width="100">Действия</th>
</tr>
</thead>
<tbody>
{urls.map((url, index) => (
<tr key={index}>
<td>
<input
type="text"
className="form-control"
placeholder="https://example.com/ips.txt"
value={url.url}
onChange={(e) => updateUrl(index, 'url', e.target.value)}
disabled={saving || processing}
/>
</td>
<td>
<input
type="text"
className="form-control"
placeholder="555"
value={url.community}
onChange={(e) => updateUrl(index, 'community', e.target.value)}
disabled={saving || processing}
/>
</td>
<td>
<button
className="btn btn-outline-danger btn-sm"
onClick={() => removeUrl(index)}
disabled={saving || processing}
>
<IconTrash size={16} />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
<div className="card-footer">
<div className="d-flex justify-content-between align-items-center">
<div className="text-muted">
{urls.length > 0 && (
<span>Всего URL-адресов: {urls.length}</span>
)}
</div>
<div>
<button
className="btn btn-outline-primary me-2"
onClick={saveUrls}
disabled={saving || processing || urls.length === 0}
>
{saving ? (
<>
<IconLoader className="animate-spin me-1" />
Сохранение...
</>
) : (
'Сохранить'
)}
</button>
<button
className="btn btn-primary"
onClick={processUrls}
disabled={saving || processing || urls.length === 0}
>
{processing ? (
<>
<IconLoader className="animate-spin me-1" />
Обработка...
</>
) : (
<>
<IconDownload className="me-1" />
Загрузить IP-списки
</>
)}
</button>
</div>
</div>
</div>
</div>
<div className="card mt-4">
<div className="card-header">
<h3 className="card-title">Информация</h3>
</div>
<div className="card-body">
<div className="row">
<div className="col-md-6">
<h4>Как это работает:</h4>
<ul>
<li>Добавьте URL-адреса, которые содержат списки IP-адресов</li>
<li>Укажите community для каждого URL</li>
<li>Нажмите "Загрузить IP-списки" для обработки всех URL</li>
<li>IP-адреса будут добавлены в файл bgp_data/ips.txt</li>
</ul>
</div>
<div className="col-md-6">
<h4>Формат файла:</h4>
<p>Файл сохраняется в формате:</p>
<pre className="bg-light p-2 rounded">
{`https://test.com/ips.txt | 555
https://example.com/blacklist.txt | 666`}
</pre>
</div>
</div>
</div>
</div>
</div>
);
}
export default AutoUrlManager;