diff --git a/backend/server.js b/backend/server.js index 990d2ef..7823ad7 100644 --- a/backend/server.js +++ b/backend/server.js @@ -516,6 +516,138 @@ app.post('/api/filters/export-config', async (req, res) => { } }); +// --- 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: `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: `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: `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'); + } +}); + // 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/FilterManager.jsx b/frontend/src/FilterManager.jsx index ea7dce5..a426e2b 100644 --- a/frontend/src/FilterManager.jsx +++ b/frontend/src/FilterManager.jsx @@ -16,7 +16,10 @@ import { IconChevronDown, IconLink, IconCopy, - IconEye + IconEye, + IconServer, + IconSettings, + IconSave } from '@tabler/icons-react'; const API_URL = '/api'; @@ -53,8 +56,16 @@ function FilterManager() { // Состояние для модального окна добавления фильтра const [addFilterModalOpen, setAddFilterModalOpen] = useState(false); + // Новые состояния для управления серверами + const [servers, setServers] = useState([]); + const [selectedServer, setSelectedServer] = useState(null); + const [serverConfigModalOpen, setServerConfigModalOpen] = useState(false); + const [newServer, setNewServer] = useState({ name: '', description: '', enabled: true }); + const [serverConfigs, setServerConfigs] = useState({}); + useEffect(() => { fetchFilters(); + fetchServers(); }, []); const fetchFilters = async () => { @@ -71,6 +82,89 @@ function FilterManager() { } }; + // Функции для управления серверами + const fetchServers = async () => { + try { + const response = await axios.get(`${API_URL}/server-configs`); + setServers(response.data); + } catch (error) { + console.error('Error fetching servers:', error); + // Если файл не существует, создаем пустой массив + setServers([]); + } + }; + + const handleAddServer = async () => { + if (!newServer.name.trim()) { + setError('Имя сервера обязательно для заполнения.'); + return; + } + + try { + const serverToAdd = { + id: Date.now().toString(), + ...newServer, + createdAt: new Date().toISOString() + }; + + const updatedServers = [...servers, serverToAdd]; + await axios.post(`${API_URL}/server-configs`, { servers: updatedServers }); + + setServers(updatedServers); + setNewServer({ name: '', description: '', enabled: true }); + setServerConfigModalOpen(false); + setSuccess('Сервер успешно добавлен!'); + setTimeout(() => setSuccess(''), 3000); + } catch (error) { + console.error('Error adding server:', error); + setError('Не удалось добавить сервер.'); + } + }; + + const handleDeleteServer = async (serverId) => { + try { + const updatedServers = servers.filter(s => s.id !== serverId); + await axios.post(`${API_URL}/server-configs`, { servers: updatedServers }); + + // Также удаляем файл конфигурации сервера + await axios.delete(`${API_URL}/server-configs/${serverId}`); + + setServers(updatedServers); + if (selectedServer?.id === serverId) { + setSelectedServer(null); + } + setSuccess('Сервер успешно удален!'); + setTimeout(() => setSuccess(''), 3000); + } catch (error) { + console.error('Error deleting server:', error); + setError('Не удалось удалить сервер.'); + } + }; + + const handleSaveServerConfig = async (serverId) => { + try { + const config = await generateMikrotikConfig(); + await axios.post(`${API_URL}/server-configs/${serverId}`, { config }); + + setSuccess(`Конфигурация для сервера "${servers.find(s => s.id === serverId)?.name}" сохранена!`); + setTimeout(() => setSuccess(''), 3000); + } catch (error) { + console.error('Error saving server config:', error); + setError('Не удалось сохранить конфигурацию сервера.'); + } + }; + + const handleLoadServerConfig = async (serverId) => { + try { + const response = await axios.get(`${API_URL}/server-configs/${serverId}`); + setGeneratedConfig(response.data.config); + setPreviewModalOpen(true); + } catch (error) { + console.error('Error loading server config:', error); + setError('Не удалось загрузить конфигурацию сервера.'); + } + }; + const handleAddFilter = () => { if (!newFilter.community.trim() || !newFilter.gateway.trim()) { setError('Поля Community и Gateway должны быть заполнены.'); @@ -355,6 +449,102 @@ function FilterManager() { )} + {/* Секция управления серверами */} +
Нет добавленных серверов
+ +{server.description}
+ )} +