feat: Добавить API для управления настройками пользовательского интерфейса, включая получение и обновление конфигурации из S3; интегрировать модальное окно настроек в интерфейс
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m33s

This commit is contained in:
2025-08-26 17:07:26 +07:00
parent f11374880c
commit 03d24e8746
3 changed files with 204 additions and 1 deletions
+66 -1
View File
@@ -1049,7 +1049,8 @@ app.get('/api/s3/last-modified', async (req, res) => {
{ name: 'asns', key: 'bgp_data/asns.txt' },
{ name: 'servers', key: 'servers.json' },
{ name: 'filters', key: 'filters.json' },
{ name: 'ipRanges', key: 'bgp_data/ips.txt' }
{ name: 'ipRanges', key: 'bgp_data/ips.txt' },
{ name: 'uiSettings', key: 'bgp_data/rt_ui_settings.json' }
];
const results = await Promise.allSettled(
keys.map(k => s3.headObject({ Bucket: BUCKET_NAME, Key: k.key }).promise())
@@ -1625,6 +1626,70 @@ app.get('/api/auto-urls', async (req, res) => {
}
});
// --- UI Settings (rt_ui_settings.json in bgp_data) ---
// Get UI settings
app.get('/api/ui-settings', async (req, res) => {
const params = {
Bucket: BUCKET_NAME,
Key: 'bgp_data/rt_ui_settings.json',
};
try {
const data = await s3.getObject(params).promise();
const jsonText = data.Body.toString('utf-8');
let settings = {};
try {
const parsed = JSON.parse(jsonText);
if (parsed && typeof parsed === 'object') settings = parsed;
} catch (parseError) {
settings = {};
}
if (data.ETag) res.set('ETag', String(data.ETag));
if (data.LastModified) res.set('Last-Modified', new Date(data.LastModified).toUTCString());
if (typeof data.ContentLength === 'number') res.set('Content-Length-Source', String(data.ContentLength));
return res.json(settings);
} catch (error) {
if (error.code === 'NoSuchKey') {
return res.json({});
}
console.error('Error reading ui settings from S3:', error);
return sendError(res, 500, 'Error reading UI settings from S3', 'E_S3');
}
});
// Update UI settings
app.post('/api/ui-settings', async (req, res) => {
const { settings, etag } = req.body || {};
const payload = (settings && typeof settings === 'object') ? settings : {};
try {
// optimistic concurrency if ETag provided (or If-Match header)
let current = null;
const ifMatch = req.headers['if-match'] ? String(req.headers['if-match']).replace(/\"/g,'"') : null;
try { current = await headS3ObjectEtag('bgp_data/rt_ui_settings.json'); } catch {}
if (current && (ifMatch || etag) && current !== String(ifMatch || etag)) {
const meta = await headMeta('bgp_data/rt_ui_settings.json');
return sendError(res, 412, 'Precondition Failed: ETag mismatch', 'E_ETAG_MISMATCH', { currentEtag: current, meta });
}
} catch {}
const params = {
Bucket: BUCKET_NAME,
Key: 'bgp_data/rt_ui_settings.json',
Body: JSON.stringify(payload, null, 2),
ContentType: 'application/json',
};
try {
await s3.putObject(params).promise();
const meta = await headMeta('bgp_data/rt_ui_settings.json');
return sendOk(res, meta);
} catch (error) {
console.error('Error writing UI settings to S3:', error);
return sendError(res, 500, 'Error writing UI settings to S3', 'E_S3', { error: String(error?.message || error) });
}
});
// --- Servers availability check ---
// Optimized TCP check: parallelize ports and hosts, cap per-host time, add in-memory TTL cache
function tcpCheck(host, port, timeoutMs) {