feat: добавлены компоненты для управления конфигурацией и логами

Добавлены новые компоненты для отображения и управления конфигурацией, логами и правилами маршрутизации. Реализована логика для работы с API, включая запросы на получение и сохранение данных. Также добавлены шаблоны правил и утилиты для валидации.

Refs: None
This commit is contained in:
2026-05-08 18:23:29 +03:00
parent 7d41dd86e7
commit 8789496ae6
24 changed files with 2987 additions and 364 deletions

31
src/web/utils/format.js Normal file
View File

@@ -0,0 +1,31 @@
export function formatBytes(value) {
if (!value) return '0 Б';
const units = ['Б', 'КБ', 'МБ', 'ГБ', 'ТБ'];
let size = value;
let index = 0;
while (size >= 1024 && index < units.length - 1) {
size /= 1024;
index += 1;
}
return `${size.toFixed(index === 0 ? 0 : 1)} ${units[index]}`;
}
export function formatRelative(iso) {
if (!iso) return '';
const ts = new Date(iso).getTime();
if (Number.isNaN(ts)) return '';
const diff = Math.max(0, Date.now() - ts);
const sec = Math.floor(diff / 1000);
if (sec < 60) return `${sec} с назад`;
const min = Math.floor(sec / 60);
if (min < 60) return `${min} мин назад`;
const hr = Math.floor(min / 60);
if (hr < 24) return `${hr} ч назад`;
const days = Math.floor(hr / 24);
return `${days} дн назад`;
}
export function formatTime(iso) {
if (!iso) return '';
return new Date(iso).toLocaleTimeString('ru-RU', { hour12: false });
}

View File

@@ -0,0 +1,54 @@
// Простые валидаторы для полей правил роутинга. Возвращают массив ошибочных строк.
const IPV4 = /^((25[0-5]|2[0-4]\d|[01]?\d?\d)\.){3}(25[0-5]|2[0-4]\d|[01]?\d?\d)$/;
const IPV6 = /^[0-9a-f:]+$/i;
const DOMAIN = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i;
export function invalidCidrs(values) {
return (values || []).filter((value) => !isValidCidr(value));
}
export function isValidCidr(value) {
const trimmed = String(value || '').trim();
if (!trimmed) return false;
const [addr, mask] = trimmed.split('/');
if (!addr) return false;
if (IPV4.test(addr)) {
if (mask === undefined) return true;
const m = Number(mask);
return Number.isInteger(m) && m >= 0 && m <= 32;
}
if (IPV6.test(addr) && addr.includes(':')) {
if (mask === undefined) return true;
const m = Number(mask);
return Number.isInteger(m) && m >= 0 && m <= 128;
}
return false;
}
export function invalidPorts(values) {
return (values || []).filter((value) => !isValidPort(value));
}
export function isValidPort(value) {
const n = Number.parseInt(String(value).trim(), 10);
return Number.isInteger(n) && n > 0 && n <= 65535;
}
export function invalidDomains(values) {
return (values || []).filter((value) => !DOMAIN.test(String(value).trim()));
}
export function ruleErrors(rule) {
return {
domains: invalidDomains(rule.domains),
domainSuffixes: invalidDomains(rule.domainSuffixes),
ipCidrs: invalidCidrs(rule.ipCidrs),
ports: invalidPorts(rule.ports),
};
}
export function hasErrors(errors) {
return Object.values(errors).some((arr) => arr.length > 0);
}