feat(NetworkConfig): add network configuration routes and validation; integrate into server and frontend for managing IP, interfaces, and gateways
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m27s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m27s
This commit is contained in:
@@ -9,8 +9,15 @@ const { streamToString } = require('../services/s3Service');
|
||||
|
||||
/**
|
||||
* Создать GET эндпоинт для JSON данных
|
||||
* @param {string} s3Key - Ключ в S3
|
||||
* @param {Object} options - Опции
|
||||
* @param {boolean} options.singleObject - Если true, возвращает объект вместо массива
|
||||
* @param {any} options.defaultValue - Значение по умолчанию ([] для массива, {} для объекта)
|
||||
*/
|
||||
function createJsonDataGET(s3Key) {
|
||||
function createJsonDataGET(s3Key, options = {}) {
|
||||
const { singleObject = false, defaultValue } = options;
|
||||
const fallback = defaultValue !== undefined ? defaultValue : (singleObject ? {} : []);
|
||||
|
||||
return async (req, res) => {
|
||||
try {
|
||||
const head = await s3.send(new HeadObjectCommand({ Bucket: BUCKET_NAME, Key: s3Key })).catch(() => null);
|
||||
@@ -22,22 +29,26 @@ function createJsonDataGET(s3Key) {
|
||||
|
||||
const data = await s3.send(new GetObjectCommand({ Bucket: BUCKET_NAME, Key: s3Key }));
|
||||
const fileContent = await streamToString(data.Body);
|
||||
let items = [];
|
||||
let items = fallback;
|
||||
|
||||
try {
|
||||
items = JSON.parse(fileContent);
|
||||
if (!Array.isArray(items)) {
|
||||
items = [];
|
||||
const parsed = JSON.parse(fileContent);
|
||||
if (singleObject) {
|
||||
// Для одиночного объекта
|
||||
items = (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) ? parsed : fallback;
|
||||
} else {
|
||||
// Для массива
|
||||
items = Array.isArray(parsed) ? parsed : fallback;
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error(`Error parsing ${s3Key}:`, parseError);
|
||||
items = [];
|
||||
items = fallback;
|
||||
}
|
||||
|
||||
res.json(items);
|
||||
} catch (error) {
|
||||
if (error?.code === 'NoSuchKey' || error?.$metadata?.httpStatusCode === 404) {
|
||||
res.json([]);
|
||||
res.json(fallback);
|
||||
} else {
|
||||
console.error(error);
|
||||
return sendError(res, 500, 'Error reading from S3', 'E_S3');
|
||||
@@ -48,23 +59,45 @@ function createJsonDataGET(s3Key) {
|
||||
|
||||
/**
|
||||
* Создать POST эндпоинт для JSON данных
|
||||
* @param {string} s3Key - Ключ в S3
|
||||
* @param {Function} validateItem - Функция валидации (для массива - валидирует каждый элемент, для объекта - весь объект)
|
||||
* @param {Object} options - Опции
|
||||
* @param {boolean} options.singleObject - Если true, ожидает объект вместо массива
|
||||
*/
|
||||
function createJsonDataPOST(s3Key, validateItem = null) {
|
||||
function createJsonDataPOST(s3Key, validateItem = null, options = {}) {
|
||||
const { singleObject = false } = options;
|
||||
|
||||
return async (req, res) => {
|
||||
const { domains: items } = req.body; // Используем 'domains' для обратной совместимости
|
||||
|
||||
if (!Array.isArray(items)) {
|
||||
return sendError(res, 400, 'Data must be an array', 'E_BAD_REQUEST');
|
||||
}
|
||||
|
||||
// Валидация элементов, если предоставлена
|
||||
if (validateItem) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const error = validateItem(items[i], i);
|
||||
if (singleObject) {
|
||||
// Для одиночного объекта
|
||||
if (typeof items !== 'object' || items === null || Array.isArray(items)) {
|
||||
return sendError(res, 400, 'Data must be an object', 'E_BAD_REQUEST');
|
||||
}
|
||||
|
||||
// Валидация объекта целиком
|
||||
if (validateItem) {
|
||||
const error = validateItem(items);
|
||||
if (error) {
|
||||
return sendError(res, 400, error, 'E_SCHEMA');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Для массива
|
||||
if (!Array.isArray(items)) {
|
||||
return sendError(res, 400, 'Data must be an array', 'E_BAD_REQUEST');
|
||||
}
|
||||
|
||||
// Валидация каждого элемента массива
|
||||
if (validateItem) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const error = validateItem(items[i], i);
|
||||
if (error) {
|
||||
return sendError(res, 400, error, 'E_SCHEMA');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -79,11 +112,16 @@ function createJsonDataPOST(s3Key, validateItem = null) {
|
||||
|
||||
/**
|
||||
* Создать роуты для JSON данных (GET + POST)
|
||||
* @param {string} s3Key - Ключ в S3
|
||||
* @param {Function} validateItem - Функция валидации
|
||||
* @param {Object} options - Опции
|
||||
* @param {boolean} options.singleObject - Если true, работает с объектом вместо массива
|
||||
* @param {any} options.defaultValue - Значение по умолчанию
|
||||
*/
|
||||
function createJsonDataRoutes(s3Key, validateItem = null) {
|
||||
function createJsonDataRoutes(s3Key, validateItem = null, options = {}) {
|
||||
return {
|
||||
get: createJsonDataGET(s3Key),
|
||||
post: createJsonDataPOST(s3Key, validateItem)
|
||||
get: createJsonDataGET(s3Key, options),
|
||||
post: createJsonDataPOST(s3Key, validateItem, options)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -393,6 +393,30 @@ const simpleFiltersRoutes = createJsonDataRoutes('filter-manager/simple-filters.
|
||||
app.get('/api/simple-filters', simpleFiltersRoutes.get);
|
||||
app.post('/api/simple-filters', simpleFiltersRoutes.post);
|
||||
|
||||
// Network Config (справочник IP, интерфейсов и gateway)
|
||||
const networkConfigRoutes = createJsonDataRoutes('network-config.json', (config) => {
|
||||
// Валидация структуры конфига
|
||||
if (typeof config !== 'object' || config === null) {
|
||||
return 'Network config must be an object';
|
||||
}
|
||||
// Валидация gateways
|
||||
if (config.gateways && !Array.isArray(config.gateways)) {
|
||||
return 'gateways must be an array';
|
||||
}
|
||||
// Валидация tunnelInterfaces
|
||||
if (config.tunnelInterfaces && !Array.isArray(config.tunnelInterfaces)) {
|
||||
return 'tunnelInterfaces must be an array';
|
||||
}
|
||||
// Валидация ipPools
|
||||
if (config.ipPools && !Array.isArray(config.ipPools)) {
|
||||
return 'ipPools must be an array';
|
||||
}
|
||||
return null;
|
||||
}, { singleObject: true });
|
||||
|
||||
app.get('/api/network-config', networkConfigRoutes.get);
|
||||
app.post('/api/network-config', networkConfigRoutes.post);
|
||||
|
||||
// === COMMUNITIES ROUTES ===
|
||||
app.get('/api/communities', communitiesRoutes.getCommunities);
|
||||
app.post('/api/communities', writeLimiter, communitiesRoutes.postCommunities);
|
||||
|
||||
Reference in New Issue
Block a user