refactor(tests): update package.json scripts for linting and testing; enhance CI workflows for linting and testing integration
Frontend CI / frontend (push) Successful in 10m38s
Frontend CI / frontend (push) Successful in 10m38s
This commit is contained in:
@@ -15,7 +15,7 @@ export default defineConfig([
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
globals: { ...globals.browser, process: 'readonly' },
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: { jsx: true },
|
||||
@@ -23,7 +23,8 @@ export default defineConfig([
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
||||
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]', argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }],
|
||||
'no-empty': ['error', { allowEmptyCatch: true }],
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
@@ -51,13 +51,13 @@ function ASNsNewManager() {
|
||||
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({ asn: '', community: '' });
|
||||
const [newInvalid, setNewInvalid] = useState({ asn: false, community: false });
|
||||
const [, setNewInvalid] = useState({ asn: 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 ASNsNewManager() {
|
||||
try {
|
||||
const res = await api.get(`/communities`);
|
||||
setCommunities(Array.isArray(res.data) ? res.data : []);
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
// тихо игнорируем
|
||||
}
|
||||
})();
|
||||
@@ -298,7 +298,7 @@ function ASNsNewManager() {
|
||||
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);
|
||||
@@ -401,7 +401,7 @@ function ASNsNewManager() {
|
||||
};
|
||||
|
||||
// Drag&Drop импорт (быстрый путь)
|
||||
const onDropImport = async (e) => {
|
||||
const _onDropImport = async (e) => {
|
||||
e.preventDefault();
|
||||
const file = e.dataTransfer?.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
@@ -158,7 +158,7 @@ function AutoUrlManager() {
|
||||
}
|
||||
setSuccess('Пример формата скопирован');
|
||||
setTimeout(() => setSuccess(''), 2000);
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
setError('Не удалось скопировать пример');
|
||||
setTimeout(() => setError(''), 2000);
|
||||
}
|
||||
@@ -619,7 +619,7 @@ function AutoUrlManager() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredAndSortedUrls.map((url, displayIndex) => {
|
||||
{filteredAndSortedUrls.map((url, _displayIndex) => {
|
||||
const originalIndex = urls.findIndex(u => u === url);
|
||||
const v = getRowValidity(url);
|
||||
const isValid = v.url && v.community;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import api from './lib/api.js';
|
||||
import axios from 'axios';
|
||||
import FormModal from './components/FormModal.jsx';
|
||||
@@ -51,7 +51,7 @@ function BillingManager() {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [sortField, setSortField] = useState('nextPaymentDate');
|
||||
const [sortOrder, setSortOrder] = useState('asc');
|
||||
const [filterStatus, setFilterStatus] = useState('');
|
||||
const [filterStatus, _setFilterStatus] = useState('');
|
||||
const [filterUrgency, setFilterUrgency] = useState('');
|
||||
const [viewMode, setViewMode] = useState('cards'); // 'cards' | 'table'
|
||||
const [activeTab, setActiveTab] = useState('subscriptions'); // 'subscriptions' | 'payments'
|
||||
@@ -76,14 +76,14 @@ function BillingManager() {
|
||||
const [editingPayment, setEditingPayment] = useState(null);
|
||||
const [showDeletePaymentModal, setShowDeletePaymentModal] = useState(false);
|
||||
const [paymentToDelete, setPaymentToDelete] = useState(null);
|
||||
const [futureDateConfirm, setFutureDateConfirm] = useState(false);
|
||||
const [_futureDateConfirm, _setFutureDateConfirm] = useState(false);
|
||||
|
||||
// История платежей
|
||||
const [paymentSearchTerm, setPaymentSearchTerm] = useState('');
|
||||
const [paymentSortField, setPaymentSortField] = useState('date');
|
||||
const [paymentSortOrder, setPaymentSortOrder] = useState('desc');
|
||||
const [selectedPayments, setSelectedPayments] = useState(new Set());
|
||||
const [paymentPage, setPaymentPage] = useState(1);
|
||||
const [paymentSearchTerm, _setPaymentSearchTerm] = useState('');
|
||||
const [_paymentSortField, _setPaymentSortField] = useState('date');
|
||||
const [_paymentSortOrder, _setPaymentSortOrder] = useState('desc');
|
||||
const [_selectedPayments, _setSelectedPayments] = useState(new Set());
|
||||
const [_paymentPage, _setPaymentPage] = useState(1);
|
||||
const paymentPageSize = 10;
|
||||
|
||||
// Курсы валют
|
||||
@@ -92,7 +92,7 @@ function BillingManager() {
|
||||
EUR: 1,
|
||||
RUB: 1
|
||||
});
|
||||
const [ratesLoading, setRatesLoading] = useState(false);
|
||||
const [_ratesLoading, setRatesLoading] = useState(false);
|
||||
|
||||
// Новый элемент
|
||||
const [newItem, setNewItem] = useState({
|
||||
@@ -273,7 +273,7 @@ function BillingManager() {
|
||||
setLoading(true);
|
||||
try {
|
||||
// Убираем служебное поле _external перед сохранением
|
||||
const payload = billingData.map(({ _external, ...rest }) => rest);
|
||||
const payload = billingData.map(({ _external: _ext, ...rest }) => rest);
|
||||
await api.post(`/billing`, { domains: payload });
|
||||
// После сохранения снимаем флаг _external со всех записей
|
||||
setBillingData(prev => prev.map(item => ({ ...item, _external: false })));
|
||||
|
||||
@@ -190,7 +190,7 @@ function CommunitiesManager() {
|
||||
setLoading(true);
|
||||
try {
|
||||
// не отправляем служебное поле _external
|
||||
const payload = data.map(({ _external, ...rest }) => rest);
|
||||
const payload = data.map(({ _external: _, ...rest }) => rest);
|
||||
await api.post(`/communities`, { communities: payload });
|
||||
setSuccess('Справочник сохранён!');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
@@ -302,7 +302,7 @@ function CommunitiesManager() {
|
||||
});
|
||||
return Array.from(map.values());
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
setError('Неверный JSON.');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -33,12 +33,12 @@ import Tooltip from './components/Tooltip.jsx';
|
||||
import Sparkline from './components/Sparkline.jsx';
|
||||
import { useAlerts } from './contexts/AlertsContext.jsx';
|
||||
|
||||
function StatCard({ icon: Icon, color, value, title, subtitle, to, trend, previousValue }) {
|
||||
function StatCard({ icon: _Icon, color, value, title, subtitle, to, trend, previousValue }) {
|
||||
return (
|
||||
<div className="card h-100 position-relative card-hover">
|
||||
<div className="card-body d-flex align-items-center">
|
||||
<span className={`avatar avatar-lg me-3 bg-${color}-lt text-${color} border-0`}>
|
||||
<Icon size={32} />
|
||||
<_Icon size={32} />
|
||||
</span>
|
||||
<div className="flex-grow-1">
|
||||
<div className="d-flex align-items-baseline gap-2 mb-1">
|
||||
@@ -64,12 +64,12 @@ function StatCard({ icon: Icon, color, value, title, subtitle, to, trend, previo
|
||||
);
|
||||
}
|
||||
|
||||
function MetricCard({ title, value, icon: Icon, color, description }) {
|
||||
function MetricCard({ title, value, icon: _Icon, color, description }) {
|
||||
return (
|
||||
<div className="card h-100 position-relative">
|
||||
<div className="card-body d-flex align-items-center">
|
||||
<span className={`avatar avatar-lg me-3 bg-${color}-lt text-${color} border-0`}>
|
||||
<Icon size={32} />
|
||||
<_Icon size={32} />
|
||||
</span>
|
||||
<div className="flex-grow-1">
|
||||
<div className="h3 mb-0 fw-bold">
|
||||
|
||||
@@ -58,13 +58,13 @@ function DomainsNewManager() {
|
||||
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({ domain: '', community: '' });
|
||||
const [newInvalid, setNewInvalid] = useState({ domain: false, community: false });
|
||||
const [, setNewInvalid] = useState({ domain: 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);
|
||||
@@ -92,16 +92,16 @@ function DomainsNewManager() {
|
||||
try {
|
||||
const res = await api.get(`/communities`, { signal });
|
||||
setCommunities(Array.isArray(res.data) ? res.data : []);
|
||||
} catch (e) {
|
||||
if (e?.name === 'CanceledError' || e?.name === 'AbortError' || e?.code === 'ERR_CANCELED') return;
|
||||
} catch (err) {
|
||||
if (err?.name === 'CanceledError' || err?.name === 'AbortError' || err?.code === 'ERR_CANCELED') return;
|
||||
}
|
||||
})();
|
||||
(async () => {
|
||||
try {
|
||||
const r = await api.get('/ws/url', { signal });
|
||||
setWsUrl(String(r.data?.url || ''));
|
||||
} catch (e) {
|
||||
if (e?.name === 'CanceledError' || e?.name === 'AbortError' || e?.code === 'ERR_CANCELED') return;
|
||||
} catch (err) {
|
||||
if (err?.name === 'CanceledError' || err?.name === 'AbortError' || err?.code === 'ERR_CANCELED') return;
|
||||
}
|
||||
})();
|
||||
return () => controller.abort();
|
||||
@@ -339,7 +339,7 @@ function DomainsNewManager() {
|
||||
};
|
||||
|
||||
// Drag&Drop импорт
|
||||
const onDropImport = async (e) => {
|
||||
const _onDropImport = async (e) => {
|
||||
e.preventDefault();
|
||||
const file = e.dataTransfer?.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
@@ -42,7 +42,7 @@ function EasySwitchManager() {
|
||||
|
||||
// Поиск и фильтры
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedRule, setSelectedRule] = useState('all'); // all | by-community
|
||||
const [_selectedRule, _setSelectedRule] = useState('all'); // all | by-community
|
||||
const [activeTab, setActiveTab] = useState('proxies'); // proxies | providers
|
||||
const [showOnlyConfigured, setShowOnlyConfigured] = useState(true); // Показывать только настроенные
|
||||
|
||||
@@ -275,7 +275,7 @@ function EasySwitchManager() {
|
||||
try {
|
||||
console.log('[EasySwitch] serversWithGateways:', serversWithGateways);
|
||||
console.log('[EasySwitch] communitiesDirectory:', communities);
|
||||
} catch (_) {}
|
||||
} catch { /* no-op */ }
|
||||
|
||||
// Автоматически разворачиваем первый сервер
|
||||
if (serversWithGateways.length > 0 && expandedServers.size === 0) {
|
||||
@@ -300,7 +300,7 @@ function EasySwitchManager() {
|
||||
|
||||
try {
|
||||
console.log('[EasySwitch] initialActiveGateways:', initialActive);
|
||||
} catch (_) {}
|
||||
} catch { /* no-op */ }
|
||||
|
||||
} catch (err) {
|
||||
if (err?.name === 'CanceledError' || err?.name === 'AbortError' || err?.code === 'ERR_CANCELED') return;
|
||||
@@ -381,7 +381,7 @@ function EasySwitchManager() {
|
||||
const currentFilters = Array.isArray(response.data) ? response.data : [];
|
||||
|
||||
// Создаем Set существующих communities в фильтрах
|
||||
const existingCommunitiesSet = new Set(currentFilters.map(f => f.community));
|
||||
const _existingCommunitiesSet = new Set(currentFilters.map(f => f.community));
|
||||
|
||||
// Обновляем существующие фильтры и добавляем новые
|
||||
const updatedFilters = [];
|
||||
|
||||
@@ -79,7 +79,7 @@ function CommunityAutocomplete({ label, value, onChange, communities = [], requi
|
||||
// 3. Если ввели "65001:120", ищем "120" в справочнике
|
||||
if (search.includes(':')) {
|
||||
const searchParts = search.split(':');
|
||||
const searchAS = searchParts[0];
|
||||
const _searchAS = searchParts[0];
|
||||
const searchNum = searchParts[1];
|
||||
|
||||
// Точное совпадение числовой части
|
||||
@@ -246,7 +246,7 @@ function CommunityAutocomplete({ label, value, onChange, communities = [], requi
|
||||
{Object.entries(groupedCommunities).map(([category, items]) => (
|
||||
<div key={category}>
|
||||
<div className="dropdown-header small text-muted bg-light">{category}</div>
|
||||
{items.map((c, idx) => {
|
||||
{items.map((c, _idx) => {
|
||||
const globalIdx = filteredCommunities.indexOf(c);
|
||||
return (
|
||||
<div
|
||||
@@ -990,7 +990,7 @@ function FilterManager() {
|
||||
setTimeout(() => 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('Не удалось обновить статус сервера');
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */}
|
||||
<div className="row row-cards g-1 mt-2">
|
||||
{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() {
|
||||
>
|
||||
<div className="card-body py-2 px-2 d-flex align-items-center gap-2">
|
||||
<span className={`avatar avatar-sm ${bgClass} ${textClass}`}>
|
||||
<Icon size={20} stroke={1.5} />
|
||||
<_Icon size={20} stroke={1.5} />
|
||||
</span>
|
||||
<div className="flex-grow-1 min-w-0">
|
||||
<div className="fw-semibold text-truncate small">{name}</div>
|
||||
|
||||
@@ -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 (
|
||||
<div className="card" style={{ borderRadius: '12px', maxWidth: '100%' }}>
|
||||
@@ -5531,7 +5531,7 @@ function NetworkConfigManager() {
|
||||
<div className="row g-2">
|
||||
{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() {
|
||||
<label className="form-label small">Родительские gateway/интерфейсы</label>
|
||||
<div className="d-flex flex-column gap-2">
|
||||
{(gw.parentGateways || []).map((parent, parentIndex) => {
|
||||
const parentInfo = getParentGateway(parent.id, templateGateways);
|
||||
const _parentInfo = getParentGateway(parent.id, templateGateways);
|
||||
return (
|
||||
<div key={parentIndex} className="d-flex align-items-center gap-2">
|
||||
<div className="flex-grow-1">
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -308,12 +308,12 @@ export default function PingServicesManager() {
|
||||
</tr>
|
||||
) : (
|
||||
filtered.map((item) => {
|
||||
const iconInfo = getIconById(item.icon);
|
||||
const { Icon } = getIconById(item.icon);
|
||||
return (
|
||||
<tr key={item.id}>
|
||||
<td>
|
||||
<span className={`avatar avatar-sm bg-${item.color}-lt text-${item.color} rounded d-inline-flex align-items-center justify-content-center`}>
|
||||
<iconInfo.Icon size={18} stroke={1.5} />
|
||||
<Icon size={18} stroke={1.5} />
|
||||
</span>
|
||||
</td>
|
||||
<td><code className="small">{item.id}</code></td>
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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 } */
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 || 'Что-то пошло не так. Попробуйте обновить страницу.'}
|
||||
</p>
|
||||
|
||||
{process.env.NODE_ENV === 'development' && this.state.errorInfo && (
|
||||
{import.meta.env.DEV && this.state.errorInfo && (
|
||||
<div className="card mt-3">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">Детали ошибки (только в dev режиме)</h3>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}`
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-refresh/only-export-components */
|
||||
import { useState, useEffect, createContext, useContext } from 'react'
|
||||
import {
|
||||
IconCheck,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 || '',
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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 (
|
||||
<div className="filter-section border-bottom">
|
||||
<button
|
||||
@@ -37,7 +37,7 @@ function FilterSection({ title, icon: Icon, iconColor, expanded, onToggle, child
|
||||
aria-expanded={expanded}
|
||||
>
|
||||
<span className="d-flex align-items-center">
|
||||
<Icon size={16} className={`me-2 ${iconColor || 'text-muted'}`} />
|
||||
<_Icon size={16} className={`me-2 ${iconColor || 'text-muted'}`} />
|
||||
<span>{title}</span>
|
||||
</span>
|
||||
{expanded ? <IconChevronDown size={16} className="text-muted" /> : <IconChevronRight size={16} className="text-muted" />}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ function readCache() {
|
||||
function writeCache(cache) {
|
||||
try {
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify(cache));
|
||||
} catch {}
|
||||
} catch { /* no-op */ }
|
||||
}
|
||||
|
||||
function getCached(asn) {
|
||||
|
||||
Reference in New Issue
Block a user