diff --git a/README.md b/README.md index 8a77807..f574347 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ - Доступы AWS: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `S3_BUCKET_NAME`, `AWS_REGION` ### Запуск backend -```bash +```powershell cd backend npm i -cp .env.example .env # заполните переменные +# создайте .env и заполните (пример ниже) npm start ``` Сервис поднимется на `http://localhost:3001`. @@ -34,6 +34,27 @@ Frontend доступен на `http://localhost:5173` (по умолчанию) - Генерация конфигурации MikroTik из фильтров (`/api/filters/generate-config`) и экспорт в S3. - Метрики Prometheus: `/metrics`, health/ready: `/health`, `/ready`. +### Важные переменные окружения +- `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `S3_BUCKET_NAME` — доступ к Object Storage +- `CORS_ORIGINS` — список разрешённых Origin через запятую (если пусто — разрешены все) +- `LOG_LEVEL` — уровень логов pino (`info` по умолчанию) +- `PORT` — порт backend (по умолчанию 3001) +- `BGP_BACKGROUND_URL` — адрес фонового обновления BGP, например: + - `http://77.232.38.173:8080/api/update_bgp/background?api_key=...` + - Используется эндпоинтом прокси `POST /api/update-bgp/background` для обхода CORS + +Пример `.env`: +```dotenv +PORT=3001 +LOG_LEVEL=info +AWS_ACCESS_KEY_ID=... +AWS_SECRET_ACCESS_KEY=... +AWS_REGION=ru-central1 +S3_BUCKET_NAME=... +CORS_ORIGINS=http://localhost:5173 +BGP_BACKGROUND_URL=http://77.232.38.173:8080/api/update_bgp/background?api_key=denozord2502 +``` + ## API (backend) Все ответы об ошибке имеют единый формат: @@ -81,6 +102,7 @@ Frontend доступен на `http://localhost:5173` (по умолчанию) - POST `/api/auto-urls/process` — обработать авто-URL и добавить IP в `bgp_data/ips.txt`. - GET `/api/servers/availability?ttlSeconds=60` — быстрый TCP‑чек доступности нод. - GET `/api/s3/last-modified` — метаданные S3 (etag/lastModified/contentLength) по ключевым файлам. +- POST `/api/update-bgp/background` — прокси к фоновой задаче обновления BGP. Требует `BGP_BACKGROUND_URL` в `.env`. - Locks: GET `/api/locks/:resource`, POST `/api/locks/:resource`, DELETE `/api/locks/:resource`. - History: GET `/api/history/:resource`, POST `/api/history/:resource/rollback`. diff --git a/backend/server.js b/backend/server.js index a1d8deb..a9d7e8b 100644 --- a/backend/server.js +++ b/backend/server.js @@ -13,6 +13,9 @@ const rateLimit = require('express-rate-limit'); const pino = require('pino'); const pinoHttp = require('pino-http'); const promClient = require('prom-client'); +const http = require('http'); +const https = require('https'); +const { URL } = require('url'); const app = express(); const port = Number(process.env.PORT) || 3001; @@ -1835,6 +1838,58 @@ app.post('/api/auto-urls/process', async (req, res) => { } }); +// --- Proxy: Background BGP Update (avoids CORS from browser) --- +app.post('/api/update-bgp/background', async (req, res) => { + try { + const targetUrl = process.env.BGP_BACKGROUND_URL; + if (!targetUrl) { + return sendError(res, 500, 'BGP_BACKGROUND_URL is not configured', 'E_CONFIG'); + } + const u = new URL(targetUrl); + const client = u.protocol === 'https:' ? https : http; + const options = { + method: 'POST', + hostname: u.hostname, + port: u.port || (u.protocol === 'https:' ? 443 : 80), + path: `${u.pathname}${u.search || ''}`, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + timeout: 15000, + }; + + const body = req.body && Object.keys(req.body).length ? JSON.stringify(req.body) : ''; + + const upstream = client.request(options, (r) => { + let data = ''; + r.setEncoding('utf8'); + r.on('data', (chunk) => { data += chunk; }); + r.on('end', () => { + const status = r.statusCode || 502; + // Try to parse JSON; fallback to text + try { + const json = data ? JSON.parse(data) : {}; + return res.status(status).json(json); + } catch (_) { + return res.status(status).json({ ok: status >= 200 && status < 300, data }); + } + }); + }); + upstream.on('timeout', () => { + try { upstream.destroy(); } catch {} + return sendError(res, 504, 'Upstream timeout', 'E_UPSTREAM_TIMEOUT'); + }); + upstream.on('error', (e) => { + return sendError(res, 502, 'Upstream error', 'E_UPSTREAM', { error: String(e?.message || e) }); + }); + if (body) upstream.write(body); + upstream.end(); + } catch (e) { + return sendError(res, 500, 'Proxy error', 'E_PROXY', { error: String(e?.message || e) }); + } +}); + // 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/ASNsNewManager.jsx b/frontend/src/ASNsNewManager.jsx index dbe20ab..684aacc 100644 --- a/frontend/src/ASNsNewManager.jsx +++ b/frontend/src/ASNsNewManager.jsx @@ -414,7 +414,7 @@ function ASNsNewManager() { onOnlineUpdate={() => setWsOpen(true)} onBackgroundUpdate={async () => { try { - const res = await fetch('http://77.232.38.173:8080/api/update_bgp/background?api_key=denozord2502', { method: 'POST' }); + const res = await fetch('/api/update-bgp/background', { method: 'POST' }); const data = await res.json().catch(() => ({})); if (!res.ok || data.ok === false) { throw new Error(data?.message || `HTTP ${res.status}`); diff --git a/frontend/src/DomainsNewManager.jsx b/frontend/src/DomainsNewManager.jsx index 0233cab..b2aa5e1 100644 --- a/frontend/src/DomainsNewManager.jsx +++ b/frontend/src/DomainsNewManager.jsx @@ -421,7 +421,7 @@ function DomainsNewManager() { onOnlineUpdate={() => setWsOpen(true)} onBackgroundUpdate={async () => { try { - const res = await fetch('http://77.232.38.173:8080/api/update_bgp/background?api_key=denozord2502', { method: 'POST' }); + const res = await fetch('/api/update-bgp/background', { method: 'POST' }); const data = await res.json().catch(() => ({})); if (!res.ok || data.ok === false) { throw new Error(data?.message || `HTTP ${res.status}`); diff --git a/frontend/src/IPRangesManager.jsx b/frontend/src/IPRangesManager.jsx index 9a594a5..e909295 100644 --- a/frontend/src/IPRangesManager.jsx +++ b/frontend/src/IPRangesManager.jsx @@ -430,7 +430,7 @@ function IPRangesManager() { onOnlineUpdate={() => setWsOpen(true)} onBackgroundUpdate={async () => { try { - const res = await fetch('http://77.232.38.173:8080/api/update_bgp/background?api_key=denozord2502', { method: 'POST' }); + const res = await fetch('/api/update-bgp/background', { method: 'POST' }); const data = await res.json().catch(() => ({})); if (!res.ok || data.ok === false) { throw new Error(data?.message || `HTTP ${res.status}`);