- {items.map((c, idx) => {
+ {items.map((c, _idx) => {
const globalIdx = filteredCommunities.indexOf(c);
return (
setSuccess(''), 3000);
return;
}
- } catch (legacyError) {
+ } catch (_legacyError) {
// Фильтров по legacyId тоже нет - это нормально
console.log('No legacy filters found');
}
@@ -1165,7 +1165,7 @@ function FilterManager() {
}
};
- const handleLoadServerConfig = async (serverId, legacyId = null) => {
+ const _handleLoadServerConfig = async (serverId, legacyId = null) => {
try {
const response = await api.get(`/server-configs/${serverId}`);
const config = response.data?.config;
@@ -1192,7 +1192,7 @@ function FilterManager() {
setTimeout(() => setSuccess(''), 3000);
return;
}
- } catch (legacyError) {
+ } catch (_legacyError) {
console.log('No legacy config found');
}
}
@@ -1299,7 +1299,7 @@ function FilterManager() {
}
};
- const handleDeployToAllServers = async () => {
+ const _handleDeployToAllServers = async () => {
if (!selectedServer) {
setError('Сначала выберите сервер с фильтрами для развертывания.');
return;
@@ -1521,7 +1521,7 @@ function FilterManager() {
setEditServerModalOpen(false);
setSuccess('Сервер успешно обновлён!');
setTimeout(() => setSuccess(''), 3000);
- } catch (error) {
+ } catch (_error) {
setEditServerError('Не удалось сохранить изменения.');
}
};
@@ -1576,7 +1576,7 @@ function FilterManager() {
await navigator.clipboard.writeText(config);
setSuccess('Конфигурация скопирована в буфер обмена!');
setTimeout(() => setSuccess(''), 3000);
- } catch (err) {
+ } catch (_err) {
setError('Не удалось скопировать конфигурацию');
}
} else {
@@ -1590,7 +1590,7 @@ function FilterManager() {
document.execCommand('copy');
setSuccess('Конфигурация скопирована в буфер обмена!');
setTimeout(() => setSuccess(''), 3000);
- } catch (err) {
+ } catch (_err) {
setError('Не удалось скопировать конфигурацию');
}
document.body.removeChild(textArea);
@@ -1598,7 +1598,7 @@ function FilterManager() {
};
// Функция для экспорта конфигурации в S3
- const exportConfigToS3 = async () => {
+ const _exportConfigToS3 = async () => {
setLoading(true);
try {
const response = await api.post(`/filters/export-config`);
@@ -2561,7 +2561,7 @@ function FilterManager() {
try {
await api.post(`/server-configs`, { servers: updatedServers });
setServers(updatedServers);
- } catch (error) {
+ } catch (_error) {
setError('Не удалось обновить статус сервера');
}
}}
diff --git a/frontend/src/FirewallPage.jsx b/frontend/src/FirewallPage.jsx
index 6ba84ef..ad281a8 100644
--- a/frontend/src/FirewallPage.jsx
+++ b/frontend/src/FirewallPage.jsx
@@ -250,7 +250,7 @@ export default function FirewallPage() {
const res = await api.get('/servers');
const list = Array.isArray(res.data) ? res.data : [];
if (!cancelled) setServers(list);
- } catch (e) {
+ } catch (_e) {
if (!cancelled) setError('Не удалось загрузить список серверов.');
} finally {
if (!cancelled) setLoadingServers(false);
@@ -319,7 +319,7 @@ export default function FirewallPage() {
[s.subnet]: { org: info.org, country: info.country, city: info.city },
}));
}
- } catch (_) {
+ } catch {
if (!cancelled) setIpInfoMap((prev) => ({ ...prev, [s.subnet]: null }));
}
if (i < slice.length - 1) await new Promise((r) => setTimeout(r, 180));
diff --git a/frontend/src/GraphView.jsx b/frontend/src/GraphView.jsx
index 08eea2c..c5fa7cc 100644
--- a/frontend/src/GraphView.jsx
+++ b/frontend/src/GraphView.jsx
@@ -5,7 +5,6 @@ import {
MiniMap,
useEdgesState,
useNodesState,
- addEdge,
MarkerType,
Handle,
Position,
@@ -224,7 +223,7 @@ function GraphView({ servers, connections, onCreateConnection, pingMap }) {
});
setSelectedNodeId(nodeId);
setTimeout(() => setSelectedNodeId(null), 2000);
- } catch {}
+ } catch { /* no-op */ }
}, []);
// Сброс раскладки: очищаем localStorage и пересоздаём ноды
@@ -232,7 +231,7 @@ function GraphView({ servers, connections, onCreateConnection, pingMap }) {
if (typeof window !== 'undefined') {
try {
window.localStorage.removeItem(STORAGE_KEY);
- } catch {}
+ } catch { /* no-op */ }
}
setNodes((current) => {
return current.map((n, i) => ({
@@ -244,7 +243,7 @@ function GraphView({ servers, connections, onCreateConnection, pingMap }) {
if (instanceRef.current) {
try {
instanceRef.current.fitView({ padding: 0.2 });
- } catch {}
+ } catch { /* no-op */ }
}
}, 100);
}, [setNodes, computeGridPosition]);
@@ -378,7 +377,7 @@ function GraphView({ servers, connections, onCreateConnection, pingMap }) {
requestAnimationFrame(() => {
try {
inst.fitView({ padding: 0.2, includeHiddenNodes: true, minZoom: 0.2, maxZoom: 2 });
- } catch {}
+ } catch { /* no-op */ }
});
}, []);
@@ -389,7 +388,7 @@ function GraphView({ servers, connections, onCreateConnection, pingMap }) {
const t = setTimeout(() => {
try {
i.fitView({ padding: 0.2, includeHiddenNodes: true, minZoom: 0.2, maxZoom: 2 });
- } catch {}
+ } catch { /* no-op */ }
}, 50);
return () => clearTimeout(t);
}, [servers, connections]);
@@ -403,7 +402,7 @@ function GraphView({ servers, connections, onCreateConnection, pingMap }) {
setTimeout(() => {
try {
instanceRef.current.fitView({ padding: 0.2 });
- } catch {}
+ } catch { /* no-op */ }
}, 60);
}
};
diff --git a/frontend/src/IPRangesManager.jsx b/frontend/src/IPRangesManager.jsx
index 5701671..59f1619 100644
--- a/frontend/src/IPRangesManager.jsx
+++ b/frontend/src/IPRangesManager.jsx
@@ -52,13 +52,13 @@ function IPRangesManager() {
const [originalItems, setOriginalItems] = useState([]);
const [etag, setEtag] = useState('');
const [lastModified, setLastModified] = useState('');
- const [contentLength, setContentLength] = useState(null);
+ const [, setContentLength] = useState(null);
const [newItem, setNewItem] = useState({ ipRange: '', community: '' });
- const [newInvalid, setNewInvalid] = useState({ ipRange: false, community: false });
+ const [, setNewInvalid] = useState({ ipRange: false, community: false });
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const [searchTerm, setSearchTerm] = useState('');
- const searchTimer = useRef(null);
+ const _searchTimer = useRef(null);
const didInit = useRef(false);
const [editingValue, setEditingValue] = useState('');
const [editModalOpen, setEditModalOpen] = useState(false);
@@ -82,7 +82,7 @@ function IPRangesManager() {
try {
const res = await api.get(`/communities`);
setCommunities(Array.isArray(res.data) ? res.data : []);
- } catch (e) {
+ } catch (_e) {
// тихо игнорируем
}
})();
@@ -268,7 +268,7 @@ function IPRangesManager() {
});
};
- const duplicateItem = (item) => {
+ const _duplicateItem = (item) => {
setItems([...items, { ...item, ipRange: `${item.ipRange.split('/')[0]}/24` }]);
window.notify?.success?.('Запись продублирована');
};
@@ -316,19 +316,19 @@ function IPRangesManager() {
return { added, removed, changed };
};
- const [showDiff, setShowDiff] = useState(false);
+ const [_showDiff, setShowDiff] = useState(false);
const [diff, setDiff] = useState({ added: [], removed: [], changed: [] });
const [confirmSaveOpen, setConfirmSaveOpen] = useState(false);
const [historyOpen, setHistoryOpen] = useState(false);
const [clearCommunitiesOpen, setClearCommunitiesOpen] = useState(false);
// Аналитика
- const [analyzeOpen, setAnalyzeOpen] = useState(false);
- const [analyzeConfirmOpen, setAnalyzeConfirmOpen] = useState(false);
- const [analysisResultsOpen, setAnalysisResultsOpen] = useState(false);
+ const [_analyzeOpen, _setAnalyzeOpen] = useState(false);
+ const [_analyzeConfirmOpen, _setAnalyzeConfirmOpen] = useState(false);
+ const [_analysisResultsOpen, setAnalysisResultsOpen] = useState(false);
const [analysisPreview, setAnalysisPreview] = useState(null);
- const [overwriteConfirmOpen, setOverwriteConfirmOpen] = useState(false);
- const [analyzeFilters, setAnalyzeFilters] = useState({ community: '', minMask: 0, maxMask: 32, type: 'any', supernet16: false });
+ const [_overwriteConfirmOpen, setOverwriteConfirmOpen] = useState(false);
+ const [analyzeFilters, _setAnalyzeFilters] = useState({ community: '', minMask: 0, maxMask: 32, type: 'any', supernet16: false });
const handlePreviewDiff = () => {
const valid = items.filter(i => i != null && isValidCidr(i.ipRange) && isValidCommunity(i.community))
@@ -426,7 +426,7 @@ function IPRangesManager() {
};
// Drag&Drop импорт (оставим как быстрый путь)
- const onDropImport = async (e) => {
+ const _onDropImport = async (e) => {
e.preventDefault();
const file = e.dataTransfer?.files?.[0];
if (!file) return;
@@ -582,7 +582,7 @@ function IPRangesManager() {
return 'public';
};
- const formatBigInt = (n) => {
+ const _formatBigInt = (n) => {
try {
const s = (n ?? 0n).toString();
return s.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
@@ -591,7 +591,7 @@ function IPRangesManager() {
}
};
- const buildAnalysisConfirmText = () => {
+ const _buildAnalysisConfirmText = () => {
const parts = [];
parts.push(`Community: ${analyzeFilters.community || 'не выбрано'}`);
parts.push(`Маска: /${analyzeFilters.minMask}..../${analyzeFilters.maxMask}`);
@@ -705,7 +705,7 @@ function IPRangesManager() {
return Array.from(new Set(out));
};
- const runAnalysis = () => {
+ const _runAnalysis = () => {
const community = String(analyzeFilters.community || '').trim();
if (!community) { window.notify?.error?.('Выберите community'); return; }
// исходные для community
@@ -744,7 +744,7 @@ function IPRangesManager() {
setAnalysisResultsOpen(true);
};
- const applyAnalysisToItems = () => {
+ const _applyAnalysisToItems = () => {
if (!analysisPreview) return;
const { community, afterCidrs, unchangedCidrs } = analysisPreview;
const keptOthers = items.filter(i => i != null && i.community !== community);
@@ -770,10 +770,14 @@ function IPRangesManager() {
window.notify?.success?.('Диапазоны перезаписаны по результатам анализа. Не забудьте сохранить изменения.');
};
- const exportAnalysisCsv = () => {
+ const _exportAnalysisCsv = () => {
+ if (!analysisPreview) return;
+ const { community, afterCidrs } = analysisPreview;
+ const totalAddr = afterCidrs?.reduce((s, c) => s + countAddresses(parseMask(c)), 0n) ?? 0n;
const header = ['community', 'ranges', 'totalAddresses'];
- const lines = [header, ...analysisResult.map(r => [r.community, r.ranges, r.totalAddresses.toString()])]
- .map(r => r.map(x => `"${(x ?? '').toString().replace(/"/g, '""')}"`).join(','))
+ const rows = [[community, String(afterCidrs?.length ?? 0), totalAddr.toString()]];
+ const lines = [header, ...rows]
+ .map(r => (Array.isArray(r) ? r : []).map(x => `"${(x ?? '').toString().replace(/"/g, '""')}"`).join(','))
.join('\n');
const blob = new Blob([lines], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
diff --git a/frontend/src/InterfaceSpeedTest.jsx b/frontend/src/InterfaceSpeedTest.jsx
index 6cee9cf..eb4aad9 100644
--- a/frontend/src/InterfaceSpeedTest.jsx
+++ b/frontend/src/InterfaceSpeedTest.jsx
@@ -83,7 +83,7 @@ export default function InterfaceSpeedTest() {
return () => controller.abort();
}, [notify]);
- const gateways = useMemo(
+ const _gateways = useMemo(
() =>
networkConfig?.gateways && Array.isArray(networkConfig.gateways)
? networkConfig.gateways
diff --git a/frontend/src/MikrotikTools.jsx b/frontend/src/MikrotikTools.jsx
index 12aa5a8..25f6f58 100644
--- a/frontend/src/MikrotikTools.jsx
+++ b/frontend/src/MikrotikTools.jsx
@@ -82,7 +82,7 @@ function MikrotikTools() {
[networkConfig]
);
- const handleSelectGatewayMeta = (meta) => {
+ const _handleSelectGatewayMeta = (meta) => {
setGatewayMeta(meta);
// Если цель не задана — подставляем IP gateway как target
if (!target && meta && meta.ip) {
@@ -214,7 +214,7 @@ function MikrotikTools() {
[jumphostServers, serverId]
);
- const interfacesForServer = useMemo(() => {
+ const _interfacesForServer = useMemo(() => {
if (!serverId || !interfaces.length) return [];
const ids = new Set(
[serverId, currentServer?.id, currentServer?.ip, currentServer?.dns]
@@ -391,7 +391,7 @@ function MikrotikTools() {
{/* Цели — карточки в стиле сервера/gateway */}
- {POPULAR_TARGETS.map(({ id: tid, name, target: tgt, Icon, color }) => {
+ {POPULAR_TARGETS.map(({ id: tid, name, target: tgt, Icon: _Icon, color }) => {
const isSelected = target === tgt;
const bgClass = color === 'red' ? 'bg-red-lt' : color === 'blue' ? 'bg-blue-lt' : 'bg-orange-lt';
const textClass = color === 'red' ? 'text-red' : color === 'blue' ? 'text-blue' : 'text-orange';
@@ -404,7 +404,7 @@ function MikrotikTools() {
>
-
+ <_Icon size={20} stroke={1.5} />
{name}
diff --git a/frontend/src/NetworkConfigManager.jsx b/frontend/src/NetworkConfigManager.jsx
index 5dc500b..fa9baca 100644
--- a/frontend/src/NetworkConfigManager.jsx
+++ b/frontend/src/NetworkConfigManager.jsx
@@ -468,7 +468,7 @@ function NetworkConfigManager() {
if (searchTerm) {
const term = searchTerm.toLowerCase();
result = result.filter(p => {
- const server = getServerInfo(p.serverId);
+ const _server = getServerInfo(p.serverId);
return (
p.name?.toLowerCase().includes(term) ||
p.cidr?.toLowerCase().includes(term) ||
@@ -913,7 +913,7 @@ function NetworkConfigManager() {
return;
}
- const invalidPairs = templateServerPairs.filter((pair, index) => {
+ const invalidPairs = templateServerPairs.filter((pair, _index) => {
if (!pair.server1 || !pair.server2) {
return true;
}
@@ -1274,7 +1274,7 @@ function NetworkConfigManager() {
};
// === Генерация IP из пула ===
- const generateIpFromPool = (cidr, usedIps, excludeIp = null, forRemote = false, pairedLocalIp = null) => {
+ const generateIpFromPool = (cidr, usedIps, _excludeIp = null, forRemote = false, pairedLocalIp = null) => {
// Парсим CIDR (например, "10.10.0.0/24")
const [network, prefixLength] = cidr.split('/');
if (!network || !prefixLength) {
@@ -1650,7 +1650,7 @@ function NetworkConfigManager() {
};
// === CRUD для IPSec Passwords ===
- const handleAddIpsecPassword = () => {
+ const _handleAddIpsecPassword = () => {
setEditingIpsecPassword({ name: '', password: '', description: '' });
setIpsecPasswordModalMode('add');
setIpsecPasswordModalOpen(true);
@@ -2021,7 +2021,7 @@ function NetworkConfigManager() {
};
// === Генерация кода MikroTik для рекурсивных маршрутов ===
- const generateMikrotikRecursiveRoutes = async () => {
+ const _generateMikrotikRecursiveRoutes = async () => {
const recursiveGateways = (config.gateways || []).filter(gw => gw.type === 'recursive');
const blocks = [];
@@ -2073,7 +2073,7 @@ function NetworkConfigManager() {
}
// Обрабатываем каждый родительский gateway
- parentGatewaysList.forEach((parentRef, parentIndex) => {
+ parentGatewaysList.forEach((parentRef, _parentIndex) => {
const parent = getParentGateway(parentRef.id);
if (!parent) {
@@ -2264,7 +2264,7 @@ function NetworkConfigManager() {
if (obj.t === 'log') console.log('[MikroTik apply]', obj.msg, obj);
else if (obj.t === 'result') finalData = obj;
else if (obj.t === 'error') finalData = { ok: false, error: obj.error, mikrotikRequests: obj.mikrotikRequests };
- } catch (_) {}
+ } catch { /* no-op */ }
}
}
if (buffer.trim()) {
@@ -2272,7 +2272,7 @@ function NetworkConfigManager() {
const obj = JSON.parse(buffer);
if (obj.t === 'result') finalData = obj;
else if (obj.t === 'error') finalData = { ok: false, error: obj.error, mikrotikRequests: obj.mikrotikRequests };
- } catch (_) {}
+ } catch { /* no-op */ }
}
data = finalData || {};
} else {
@@ -2403,7 +2403,7 @@ function NetworkConfigManager() {
let validRoutesCount = 0;
- parentGatewaysList.forEach((parentRef, parentIndex) => {
+ parentGatewaysList.forEach((parentRef, _parentIndex) => {
const parent = getParentGateway(parentRef.id);
if (!parent) {
@@ -2542,7 +2542,7 @@ function NetworkConfigManager() {
try {
document.execCommand('copy');
notify.success('Код скопирован в буфер обмена!');
- } catch (err) {
+ } catch (_err) {
notify.error('Не удалось скопировать код');
}
document.body.removeChild(textArea);
@@ -2589,7 +2589,7 @@ function NetworkConfigManager() {
}, [generatedMikrotikCode]);
// === Получение названия типа блока ===
- const getBlockTypeLabel = (type) => {
+ const _getBlockTypeLabel = (type) => {
switch (type) {
case 'recursive-routes':
return 'Рекурсивные маршруты';
@@ -2967,11 +2967,11 @@ function NetworkConfigManager() {
// === Рендер карточки Gateway ===
const renderGatewayCard = (gateway) => {
const server = getServerInfo(gateway.serverId);
- const provider = server?.provider || '';
+ const _provider = server?.provider || '';
const serverCountry = server?.country || '';
const gatewayCountry = gateway.country || '';
const displayCountry = gatewayCountry || serverCountry;
- const gatewayType = GATEWAY_TYPES.find(t => t.value === gateway.type) || GATEWAY_TYPES[0];
+ const _gatewayType = GATEWAY_TYPES.find(t => t.value === gateway.type) || GATEWAY_TYPES[0];
// Поддержка старого формата (parentGatewayId) и нового (parentGateways)
const parentGatewaysList = gateway.type === 'recursive'
@@ -3135,7 +3135,7 @@ function NetworkConfigManager() {
const interfaceTypeColor = getInterfaceTypeColor(iface.type);
// Определяем отображаемый IP для зеленого блока (remote IP другого сервера)
- const displayIp = displayRemoteIp !== '—' ? displayRemoteIp : displayLocalIp;
+ const _displayIp = displayRemoteIp !== '—' ? displayRemoteIp : displayLocalIp;
return (
@@ -5531,7 +5531,7 @@ function NetworkConfigManager() {
{GATEWAY_TEMPLATES.map(template => {
// Генерируем предпросмотр IP адресов если есть базовый IP и сервер
- const previewIps = template.gateways.map((gw, idx) => {
+ const previewIps = template.gateways.map((gw, _idx) => {
// Для прямых gateways показываем IP сервера, если выбран
if (gw.type === 'direct' && templateServerId) {
const server = getServerInfo(templateServerId);
@@ -5709,7 +5709,7 @@ function NetworkConfigManager() {
Родительские gateway/интерфейсы
{(gw.parentGateways || []).map((parent, parentIndex) => {
- const parentInfo = getParentGateway(parent.id, templateGateways);
+ const _parentInfo = getParentGateway(parent.id, templateGateways);
return (
diff --git a/frontend/src/NetworkMapDashboard.jsx b/frontend/src/NetworkMapDashboard.jsx
index 033ce8d..6ec3df0 100644
--- a/frontend/src/NetworkMapDashboard.jsx
+++ b/frontend/src/NetworkMapDashboard.jsx
@@ -309,7 +309,7 @@ export default function NetworkMapDashboard() {
* Если передать force=true, принудительно выполняет новый speed-test (игнорируя кеш на бэкенде).
*/
const requestSpeeds = useCallback(async (options = {}) => {
- const { connections: targetConnections, force = false } = options;
+ const { connections: targetConnections, force: _force = false } = options;
const sourceConnections =
Array.isArray(targetConnections) && targetConnections.length > 0 ? targetConnections : connections;
const withSpeed = sourceConnections.filter(
diff --git a/frontend/src/NetworkMapUnifi.jsx b/frontend/src/NetworkMapUnifi.jsx
index 40dd66a..3e56d44 100644
--- a/frontend/src/NetworkMapUnifi.jsx
+++ b/frontend/src/NetworkMapUnifi.jsx
@@ -213,10 +213,10 @@ export default function NetworkMapUnifi({
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (raw) saved = JSON.parse(raw) || {};
- } catch {}
+ } catch { /* no-op */ }
const initial = computeInitialLayout(servers, size);
const merged = {};
- servers.forEach((s, i) => {
+ servers.forEach((s, _i) => {
const ip = String(s.ip);
if (saved[ip] && typeof saved[ip].x === 'number' && typeof saved[ip].y === 'number') {
merged[ip] = { x: saved[ip].x, y: saved[ip].y };
@@ -227,11 +227,11 @@ export default function NetworkMapUnifi({
setPositions(merged);
}, [servers.length, size.w, size.h]);
- const savePositions = useCallback((next) => {
+ const _savePositions = useCallback((next) => {
setPositions(next);
try {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
- } catch {}
+ } catch { /* no-op */ }
}, []);
const getSvgCoords = useCallback((clientX, clientY) => {
@@ -255,7 +255,7 @@ export default function NetworkMapUnifi({
const next = { ...prev, [drag.nodeId]: { x: drag.startX + (p.x - drag.mouseX), y: drag.startY + (p.y - drag.mouseY) } };
try {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
- } catch {}
+ } catch { /* no-op */ }
return next;
});
};
@@ -273,7 +273,7 @@ export default function NetworkMapUnifi({
setPositions(initial);
try {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(initial));
- } catch {}
+ } catch { /* no-op */ }
}, [servers, size]);
const onNodeMouseDown = useCallback(
diff --git a/frontend/src/OspfToolsPage.jsx b/frontend/src/OspfToolsPage.jsx
index eb4099b..ed8b81c 100644
--- a/frontend/src/OspfToolsPage.jsx
+++ b/frontend/src/OspfToolsPage.jsx
@@ -68,7 +68,7 @@ function buildOspfTemplatesFromRouterResults(routerResults = [], servers = []) {
interfaceName,
area,
cost,
- active: !Boolean(tpl?.disabled),
+ active: !tpl?.disabled,
});
});
});
@@ -116,7 +116,7 @@ function buildOptimizerProbabilityMap(routeOptimizerData, servers = []) {
uniqueIface.push(candidate);
});
- uniqueIface.forEach((candidate, idx) => {
+ uniqueIface.forEach((candidate, _idx) => {
const iface = String(candidate?.interfaceName || '').trim();
const hintKey = `${identity.key}::${iface.toUpperCase()}`;
const probabilityOptimal = Number(candidate?.probabilityOptimal || 0);
diff --git a/frontend/src/PingServicesManager.jsx b/frontend/src/PingServicesManager.jsx
index 218dd98..c11a60d 100644
--- a/frontend/src/PingServicesManager.jsx
+++ b/frontend/src/PingServicesManager.jsx
@@ -308,12 +308,12 @@ export default function PingServicesManager() {
) : (
filtered.map((item) => {
- const iconInfo = getIconById(item.icon);
+ const { Icon } = getIconById(item.icon);
return (
-
+
{item.id}
diff --git a/frontend/src/ServerManager.jsx b/frontend/src/ServerManager.jsx
index 4ecccab..f6b92eb 100644
--- a/frontend/src/ServerManager.jsx
+++ b/frontend/src/ServerManager.jsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useRef, useMemo } from 'react';
+import { useState, useEffect, useMemo } from 'react';
import api from './lib/api.js';
import ConfirmDialog from './components/ConfirmDialog.jsx';
import ConfirmModal from './components/ConfirmModal.jsx';
@@ -501,7 +501,7 @@ function ServerManager() {
return `${urlSettings.baseUrl}?${params.toString()}`;
};
- const handleGenerateLink = (server) => {
+ const _handleGenerateLink = (server) => {
setLinkGeneratorServer(server);
setLinkGeneratorOpen(true);
};
@@ -511,7 +511,7 @@ function ServerManager() {
setLinkGeneratorServer(null);
};
- const applyPreset = (name) => {
+ const _applyPreset = (name) => {
const p = presets[name];
if (!p) return;
setUrlSettings(prev => ({ ...prev, ...p }));
@@ -529,7 +529,7 @@ function ServerManager() {
try {
document.execCommand('copy');
notify.success('Скопировано в буфер обмена!');
- } catch (err) {
+ } catch (_err) {
notify.error('Не удалось скопировать');
}
document.body.removeChild(textArea);
@@ -541,14 +541,14 @@ function ServerManager() {
await navigator.clipboard.writeText(text);
notify.success('Скопировано в буфер обмена!');
return;
- } catch (err) {
+ } catch (_err) {
// fallback
}
}
fallbackCopyToClipboard(text);
};
- const quickCopyLink = async (server) => {
+ const _quickCopyLink = async (server) => {
const url = generateServerUrl(server);
await copyToClipboard(url);
};
diff --git a/frontend/src/UptimeMonitorPage.jsx b/frontend/src/UptimeMonitorPage.jsx
index d641b91..f129ac9 100644
--- a/frontend/src/UptimeMonitorPage.jsx
+++ b/frontend/src/UptimeMonitorPage.jsx
@@ -34,14 +34,14 @@ function loadHistoryFromStorage() {
if (!raw) return {};
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === 'object') return parsed;
- } catch (_) {}
+ } catch { /* no-op */ }
return {};
}
function saveHistoryToStorage(historyMap) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(historyMap));
- } catch (_) {}
+ } catch { /* no-op */ }
}
/** Группировка истории по дням: массив { day, dayLabel, incidents } */
diff --git a/frontend/src/components/BulkActionsBar.jsx b/frontend/src/components/BulkActionsBar.jsx
index b9a2e1b..e6d59d3 100644
--- a/frontend/src/components/BulkActionsBar.jsx
+++ b/frontend/src/components/BulkActionsBar.jsx
@@ -8,14 +8,14 @@ import FloatingBulkActionsBar from './FloatingBulkActionsBar.jsx';
*/
function BulkActionsBar({
selectedCount = 0,
- totalCount = 0,
- onSelectAll = () => {},
+ totalCount: _totalCount = 0,
+ onSelectAll: _onSelectAll = () => {},
onDeselectAll = () => {},
onDelete = null,
onEdit = null,
onExport = null,
customActions = [],
- className = '',
+ className: _className = '',
}) {
const actions = [];
diff --git a/frontend/src/components/DataTable.jsx b/frontend/src/components/DataTable.jsx
index c8e88c2..c648c0e 100644
--- a/frontend/src/components/DataTable.jsx
+++ b/frontend/src/components/DataTable.jsx
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import React from 'react';
import { IconArrowUp, IconArrowDown, IconEdit, IconTrash, IconCopy, IconCheck, IconX } from '@tabler/icons-react';
import TableSkeleton, { TableEmpty } from './TableSkeleton.jsx';
import EmptyState from './EmptyState.jsx';
diff --git a/frontend/src/components/ErrorBoundary.jsx b/frontend/src/components/ErrorBoundary.jsx
index 08112f7..df02074 100644
--- a/frontend/src/components/ErrorBoundary.jsx
+++ b/frontend/src/components/ErrorBoundary.jsx
@@ -16,7 +16,7 @@ class ErrorBoundary extends Component {
};
}
- static getDerivedStateFromError(error) {
+ static getDerivedStateFromError(_error) {
return { hasError: true };
}
@@ -93,7 +93,7 @@ class ErrorBoundary extends Component {
{this.state.error?.message || 'Что-то пошло не так. Попробуйте обновить страницу.'}
- {process.env.NODE_ENV === 'development' && this.state.errorInfo && (
+ {import.meta.env.DEV && this.state.errorInfo && (
Детали ошибки (только в dev режиме)
diff --git a/frontend/src/components/HistoryModal.jsx b/frontend/src/components/HistoryModal.jsx
index 4c67888..74b8098 100644
--- a/frontend/src/components/HistoryModal.jsx
+++ b/frontend/src/components/HistoryModal.jsx
@@ -40,7 +40,7 @@ export default function HistoryModal({ resource, show, onClose, onRolledBack })
notify.success('Откат выполнен успешно');
onRolledBack?.(res.data || {});
onClose?.();
- } catch (e) {
+ } catch (_e) {
notify.error('Не удалось выполнить откат');
} finally {
setLoading(false);
diff --git a/frontend/src/components/IconPicker.jsx b/frontend/src/components/IconPicker.jsx
index 856ae5c..8ebfaab 100644
--- a/frontend/src/components/IconPicker.jsx
+++ b/frontend/src/components/IconPicker.jsx
@@ -1,3 +1,4 @@
+/* eslint-disable react-refresh/only-export-components */
import { useState, useMemo, useRef, useEffect } from 'react';
import BRAND_ICONS, { getIconById } from '../lib/brandIcons.js';
import { IconSearch, IconMinus } from '@tabler/icons-react';
diff --git a/frontend/src/components/MobileCardView.jsx b/frontend/src/components/MobileCardView.jsx
index d8b9f7e..d4ed9db 100644
--- a/frontend/src/components/MobileCardView.jsx
+++ b/frontend/src/components/MobileCardView.jsx
@@ -19,12 +19,12 @@ function MobileCardView({
// минимальная дистанция свайпа в px
const minSwipeDistance = 50
- const onTouchStart = (e, item) => {
+ const onTouchStart = (e, _item) => {
setTouchEnd(null)
setTouchStart(e.targetTouches[0].clientX)
}
- const onTouchMove = (e, item) => {
+ const onTouchMove = (e, _item) => {
setTouchEnd(e.targetTouches[0].clientX)
}
diff --git a/frontend/src/components/NotifyProvider.jsx b/frontend/src/components/NotifyProvider.jsx
index 975d860..e0ee3bd 100644
--- a/frontend/src/components/NotifyProvider.jsx
+++ b/frontend/src/components/NotifyProvider.jsx
@@ -1,3 +1,4 @@
+/* eslint-disable react-refresh/only-export-components */
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { IconCheck, IconAlertTriangle, IconInfoCircle, IconX } from '@tabler/icons-react';
@@ -98,7 +99,7 @@ export function notifyMutationSuccess(message, details) {
if (forceToast && typeof window !== 'undefined' && window.notify?.success) {
window.notify.success(text, details);
}
- } catch {}
+ } catch { /* no-op */ }
}
function colorByType(type) {
diff --git a/frontend/src/components/SavedFilters.jsx b/frontend/src/components/SavedFilters.jsx
index 1baaded..c418950 100644
--- a/frontend/src/components/SavedFilters.jsx
+++ b/frontend/src/components/SavedFilters.jsx
@@ -14,7 +14,7 @@ function SavedFilters({
const [savedFilters, setSavedFilters] = useState([])
const [showSaveDialog, setShowSaveDialog] = useState(false)
const [filterName, setFilterName] = useState('')
- const [showList, setShowList] = useState(false)
+ const [_showList, setShowList] = useState(false)
const storageKey = `savedFilters_${pageKey}`
diff --git a/frontend/src/components/SettingsModal.jsx b/frontend/src/components/SettingsModal.jsx
index 9407e90..70fa3d4 100644
--- a/frontend/src/components/SettingsModal.jsx
+++ b/frontend/src/components/SettingsModal.jsx
@@ -96,7 +96,7 @@ export default function SettingsModal({ open, onClose }) {
const e = settingsRes?.headers?.etag || settingsRes?.headers?.ETag || '';
setEtag(e ? String(e) : '');
setServersList(Array.isArray(serversRes?.data) ? serversRes.data : []);
- } catch (e) {
+ } catch (_e) {
setError('Не удалось загрузить настройки');
} finally {
setLoading(false);
diff --git a/frontend/src/components/ToastContainer.jsx b/frontend/src/components/ToastContainer.jsx
index 7bc1adf..09ad5e0 100644
--- a/frontend/src/components/ToastContainer.jsx
+++ b/frontend/src/components/ToastContainer.jsx
@@ -1,3 +1,4 @@
+/* eslint-disable react-refresh/only-export-components */
import { useState, useEffect, createContext, useContext } from 'react'
import {
IconCheck,
diff --git a/frontend/src/components/TopNStats.jsx b/frontend/src/components/TopNStats.jsx
index fa9a236..b60971d 100644
--- a/frontend/src/components/TopNStats.jsx
+++ b/frontend/src/components/TopNStats.jsx
@@ -7,7 +7,7 @@ import { IconTrophy, IconMapPin, IconCloud, IconHash, IconChartBar } from '@tabl
function TopNStats({ data, loading }) {
const [topCountries, setTopCountries] = useState([]);
const [topProviders, setTopProviders] = useState([]);
- const [topCommunities, setTopCommunities] = useState([]);
+ const [_topCommunities, _setTopCommunities] = useState([]);
useEffect(() => {
if (!data || !Array.isArray(data.servers)) return;
diff --git a/frontend/src/components/ValidatedInput.jsx b/frontend/src/components/ValidatedInput.jsx
index ffb22f4..6379734 100644
--- a/frontend/src/components/ValidatedInput.jsx
+++ b/frontend/src/components/ValidatedInput.jsx
@@ -58,7 +58,7 @@ function ValidatedInput({
message: validationResult.message || '',
isValidating: false
})
- } catch (error) {
+ } catch (_error) {
setValidationState({
valid: false,
message: 'Ошибка валидации',
@@ -84,7 +84,7 @@ function ValidatedInput({
// Запускаем валидацию сразу при потере фокуса
if (validate && !validateOnChange) {
const result = validate(value)
- const validationResult = result instanceof Promise ? result.then(r => {
+ const _validationResult = result instanceof Promise ? result.then(r => {
setValidationState({
valid: r.valid,
message: r.message || '',
diff --git a/frontend/src/components/VirtualizedTable.jsx b/frontend/src/components/VirtualizedTable.jsx
index da252be..f81b634 100644
--- a/frontend/src/components/VirtualizedTable.jsx
+++ b/frontend/src/components/VirtualizedTable.jsx
@@ -1,4 +1,4 @@
-import { useRef, useMemo } from 'react';
+import { useRef } from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
import { IconArrowUp, IconArrowDown, IconEdit, IconTrash, IconCopy } from '@tabler/icons-react';
import TableSkeleton from './TableSkeleton.jsx';
diff --git a/frontend/src/components/WsUpdateModal.jsx b/frontend/src/components/WsUpdateModal.jsx
index c49bbdf..9e47ed5 100644
--- a/frontend/src/components/WsUpdateModal.jsx
+++ b/frontend/src/components/WsUpdateModal.jsx
@@ -64,12 +64,12 @@ function WsUpdateModal({ show, url, onClose }) {
ws.onerror = () => setStatus('error');
ws.onclose = () => setStatus('closed');
- } catch (e) {
+ } catch (_e) {
setStatus('error');
}
return () => {
- try { wsRef.current?.close(); } catch {}
+ try { wsRef.current?.close(); } catch { /* no-op */ }
wsRef.current = null;
};
}, [show, url]);
@@ -96,7 +96,7 @@ function WsUpdateModal({ show, url, onClose }) {
const copyLog = async () => {
try {
await navigator.clipboard.writeText(plainLog);
- } catch {}
+ } catch { /* no-op */ }
};
const clearLog = () => {
diff --git a/frontend/src/components/server/ServerSidebar.jsx b/frontend/src/components/server/ServerSidebar.jsx
index 563782c..fa99f98 100644
--- a/frontend/src/components/server/ServerSidebar.jsx
+++ b/frontend/src/components/server/ServerSidebar.jsx
@@ -27,7 +27,7 @@ function countryToFlag(isoCode) {
return code.replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt()));
}
-function FilterSection({ title, icon: Icon, iconColor, expanded, onToggle, children }) {
+function FilterSection({ title, icon: _Icon, iconColor, expanded, onToggle, children }) {
return (
-
+ <_Icon size={16} className={`me-2 ${iconColor || 'text-muted'}`} />
{title}
{expanded ? : }
diff --git a/frontend/src/contexts/AlertsContext.jsx b/frontend/src/contexts/AlertsContext.jsx
index 7519d40..bc0abcf 100644
--- a/frontend/src/contexts/AlertsContext.jsx
+++ b/frontend/src/contexts/AlertsContext.jsx
@@ -1,3 +1,4 @@
+/* eslint-disable react-refresh/only-export-components */
import { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react';
import api from '../lib/api.js';
diff --git a/frontend/src/contexts/PingContext.jsx b/frontend/src/contexts/PingContext.jsx
index 122b179..d733ab8 100644
--- a/frontend/src/contexts/PingContext.jsx
+++ b/frontend/src/contexts/PingContext.jsx
@@ -1,3 +1,4 @@
+/* eslint-disable react-refresh/only-export-components */
import { createContext, useContext, useState, useCallback, useRef } from 'react';
import api from '../lib/api.js';
@@ -97,11 +98,11 @@ export function PingProvider({ children }) {
const cancelScope = useCallback((scope) => {
const scopeName = String(scope || '');
if (!scopeName) return;
- let canceled = 0;
+ let _canceled = 0;
controllersRef.current.forEach((controller, key) => {
if (keyScopesRef.current.get(key) === scopeName) {
controller.abort();
- canceled += 1;
+ _canceled += 1;
}
});
}, []);
diff --git a/frontend/src/examples/ErrorHandlingExample.jsx b/frontend/src/examples/ErrorHandlingExample.jsx
index b2f6be3..dc70fa8 100644
--- a/frontend/src/examples/ErrorHandlingExample.jsx
+++ b/frontend/src/examples/ErrorHandlingExample.jsx
@@ -81,7 +81,7 @@ function SimpleErrorHandlingExample() {
*/
function RetryButtonExample() {
const [attempts, setAttempts] = useState(0);
- const { handleError, handleSuccess } = useErrorHandler();
+ const { handleError: _handleError, handleSuccess } = useErrorHandler();
const unreliableOperation = async () => {
setAttempts(prev => prev + 1);
diff --git a/frontend/src/lib/api.js b/frontend/src/lib/api.js
index e3a44f5..8951fb3 100644
--- a/frontend/src/lib/api.js
+++ b/frontend/src/lib/api.js
@@ -57,7 +57,7 @@ api.interceptors.response.use(
responseCache.set(key, { etag, data: response.data, headers: response.headers });
}
}
- } catch {}
+ } catch { /* no-op */ }
return response;
},
async (error) => {
@@ -174,7 +174,7 @@ api.interceptors.request.use((config) => {
config.headers = config.headers || {};
config.headers['If-Match'] = String(config.data.etag);
}
- } catch {}
+ } catch { /* no-op */ }
return config;
});
diff --git a/frontend/src/lib/asn.js b/frontend/src/lib/asn.js
index 4a884f6..42cfd5d 100644
--- a/frontend/src/lib/asn.js
+++ b/frontend/src/lib/asn.js
@@ -17,7 +17,7 @@ function readCache() {
function writeCache(cache) {
try {
localStorage.setItem(CACHE_KEY, JSON.stringify(cache));
- } catch {}
+ } catch { /* no-op */ }
}
function getCached(asn) {
diff --git a/package.json b/package.json
index a805977..4a2255b 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,9 @@
"main": "index.js",
"scripts": {
"dev": "concurrently \"cd backend && npm run dev\" \"cd frontend && npm run dev\"",
- "test": "echo \"Error: no test specified\" && exit 1"
+ "lint": "cd frontend && npm run lint",
+ "test": "cd frontend && npm run build",
+ "test:backend": "cd backend && npm run test"
},
"keywords": [],
"author": "",