feat: Implement background BGP update proxy endpoint and update documentation; refactor frontend components to utilize new API endpoint
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 19m20s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 19m20s
This commit is contained in:
@@ -10,10 +10,10 @@
|
|||||||
- Доступы AWS: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `S3_BUCKET_NAME`, `AWS_REGION`
|
- Доступы AWS: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `S3_BUCKET_NAME`, `AWS_REGION`
|
||||||
|
|
||||||
### Запуск backend
|
### Запуск backend
|
||||||
```bash
|
```powershell
|
||||||
cd backend
|
cd backend
|
||||||
npm i
|
npm i
|
||||||
cp .env.example .env # заполните переменные
|
# создайте .env и заполните (пример ниже)
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
Сервис поднимется на `http://localhost:3001`.
|
Сервис поднимется на `http://localhost:3001`.
|
||||||
@@ -34,6 +34,27 @@ Frontend доступен на `http://localhost:5173` (по умолчанию)
|
|||||||
- Генерация конфигурации MikroTik из фильтров (`/api/filters/generate-config`) и экспорт в S3.
|
- Генерация конфигурации MikroTik из фильтров (`/api/filters/generate-config`) и экспорт в S3.
|
||||||
- Метрики Prometheus: `/metrics`, health/ready: `/health`, `/ready`.
|
- Метрики 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)
|
## API (backend)
|
||||||
|
|
||||||
Все ответы об ошибке имеют единый формат:
|
Все ответы об ошибке имеют единый формат:
|
||||||
@@ -81,6 +102,7 @@ Frontend доступен на `http://localhost:5173` (по умолчанию)
|
|||||||
- POST `/api/auto-urls/process` — обработать авто-URL и добавить IP в `bgp_data/ips.txt`.
|
- POST `/api/auto-urls/process` — обработать авто-URL и добавить IP в `bgp_data/ips.txt`.
|
||||||
- GET `/api/servers/availability?ttlSeconds=60` — быстрый TCP‑чек доступности нод.
|
- GET `/api/servers/availability?ttlSeconds=60` — быстрый TCP‑чек доступности нод.
|
||||||
- GET `/api/s3/last-modified` — метаданные S3 (etag/lastModified/contentLength) по ключевым файлам.
|
- 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`.
|
- Locks: GET `/api/locks/:resource`, POST `/api/locks/:resource`, DELETE `/api/locks/:resource`.
|
||||||
- History: GET `/api/history/:resource`, POST `/api/history/:resource/rollback`.
|
- History: GET `/api/history/:resource`, POST `/api/history/:resource/rollback`.
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ const rateLimit = require('express-rate-limit');
|
|||||||
const pino = require('pino');
|
const pino = require('pino');
|
||||||
const pinoHttp = require('pino-http');
|
const pinoHttp = require('pino-http');
|
||||||
const promClient = require('prom-client');
|
const promClient = require('prom-client');
|
||||||
|
const http = require('http');
|
||||||
|
const https = require('https');
|
||||||
|
const { URL } = require('url');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = Number(process.env.PORT) || 3001;
|
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
|
// The "catchall" handler: for any request that doesn't
|
||||||
// match one above, send back React's index.html file.
|
// match one above, send back React's index.html file.
|
||||||
app.get('*', (req, res) => {
|
app.get('*', (req, res) => {
|
||||||
|
|||||||
@@ -414,7 +414,7 @@ function ASNsNewManager() {
|
|||||||
onOnlineUpdate={() => setWsOpen(true)}
|
onOnlineUpdate={() => setWsOpen(true)}
|
||||||
onBackgroundUpdate={async () => {
|
onBackgroundUpdate={async () => {
|
||||||
try {
|
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(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
if (!res.ok || data.ok === false) {
|
if (!res.ok || data.ok === false) {
|
||||||
throw new Error(data?.message || `HTTP ${res.status}`);
|
throw new Error(data?.message || `HTTP ${res.status}`);
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ function DomainsNewManager() {
|
|||||||
onOnlineUpdate={() => setWsOpen(true)}
|
onOnlineUpdate={() => setWsOpen(true)}
|
||||||
onBackgroundUpdate={async () => {
|
onBackgroundUpdate={async () => {
|
||||||
try {
|
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(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
if (!res.ok || data.ok === false) {
|
if (!res.ok || data.ok === false) {
|
||||||
throw new Error(data?.message || `HTTP ${res.status}`);
|
throw new Error(data?.message || `HTTP ${res.status}`);
|
||||||
|
|||||||
@@ -430,7 +430,7 @@ function IPRangesManager() {
|
|||||||
onOnlineUpdate={() => setWsOpen(true)}
|
onOnlineUpdate={() => setWsOpen(true)}
|
||||||
onBackgroundUpdate={async () => {
|
onBackgroundUpdate={async () => {
|
||||||
try {
|
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(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
if (!res.ok || data.ok === false) {
|
if (!res.ok || data.ok === false) {
|
||||||
throw new Error(data?.message || `HTTP ${res.status}`);
|
throw new Error(data?.message || `HTTP ${res.status}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user