feat: Refactor EasySwitchManager to streamline data loading and enhance community and gateway management, including improved error handling and UI updates for better user experience.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s

This commit is contained in:
2025-12-07 04:42:38 +07:00
parent b13f412b79
commit 489bdedfbb
2 changed files with 139 additions and 68 deletions
+15
View File
@@ -186,6 +186,21 @@ GET /api/servers // Метаданные серверов
- activeTab: string // Активная вкладка (proxies/providers)
```
## Важные изменения (v2)
### ✅ Исправлено в версии 2.0
- **Все communities отображаются для всех серверов** - больше нет различий в количестве communities между серверами
- **Загрузка названий communities** из справочника `/api/communities`
- **Все gateways сервера** отображаются для каждого community, а не только те, что настроены в filters
- **Бейдж "настроен"** показывает, для каких communities уже есть фильтры
- **Активный gateway берется из filters**, если настроен, иначе выбирается первый
### Логика работы v2
1. Загружается справочник communities → **все одинаковые для всех серверов**
2. Для каждого сервера загружаются его gateways (из `/api/servers` или `server-configs`)
3. Для каждого community показываются **все** gateways этого сервера
4. Из filters берется информация только об **активном** gateway для данного community
## Roadmap
### Планируется добавить
+124 -68
View File
@@ -30,6 +30,7 @@ function EasySwitchManager() {
// Серверы и фильтры
const [servers, setServers] = useState([]);
const [inventoryServers, setInventoryServers] = useState([]);
const [communitiesDirectory, setCommunitiesDirectory] = useState([]); // Справочник communities
// Поиск и фильтры
const [searchQuery, setSearchQuery] = useState('');
@@ -41,20 +42,29 @@ function EasySwitchManager() {
const [hasChanges, setHasChanges] = useState(false);
useEffect(() => {
loadData();
loadInventoryServers();
const initData = async () => {
try {
// Загружаем communities
const commRes = await api.get(`/communities`);
const communities = Array.isArray(commRes.data) ? commRes.data : [];
setCommunitiesDirectory(communities);
// Загружаем inventory servers
const invRes = await api.get(`/servers`);
const inventory = Array.isArray(invRes.data) ? invRes.data : [];
setInventoryServers(inventory);
// Загружаем данные серверов и фильтров
await loadDataWithParams(communities, inventory);
} catch (e) {
console.error('Error initializing data:', e);
setError('Ошибка загрузки данных');
}
};
initData();
}, []);
const loadInventoryServers = async () => {
try {
const res = await api.get(`/servers`);
setInventoryServers(Array.isArray(res.data) ? res.data : []);
} catch (e) {
console.error('Error loading inventory servers:', e);
}
};
const loadData = async () => {
const loadDataWithParams = async (communities, inventory) => {
setLoading(true);
setError('');
@@ -63,60 +73,84 @@ function EasySwitchManager() {
const serversResponse = await api.get(`/server-configs`);
const serversList = Array.isArray(serversResponse.data) ? serversResponse.data : [];
// Загружаем фильтры для каждого сервера
const serversWithFilters = await Promise.all(
// Загружаем фильтры для каждого сервера (для определения активных gateways)
const serversWithData = await Promise.all(
serversList.map(async (server) => {
try {
const response = await api.get(`/server-filters/${server.id}`);
const filters = Array.isArray(response.data) ? response.data : [];
// Загружаем filters для сервера
const filtersResponse = await api.get(`/server-filters/${server.id}`);
const filters = Array.isArray(filtersResponse.data) ? filtersResponse.data : [];
// Группируем фильтры по community внутри сервера
const communitiesMap = new Map();
// Получаем gateways сервера из inventory или server-configs
const matchedInventoryServer = inventory.find(srv =>
String(srv.dns || '').trim() === server.name ||
String(srv.hostName || '').trim() === server.name ||
String(srv.ip || '').trim() === server.name
);
let serverGateways = [];
// Берем gateways из inventory
if (matchedInventoryServer && Array.isArray(matchedInventoryServer.gateways)) {
serverGateways = matchedInventoryServer.gateways
.filter(gw => gw.name && gw.name !== server.name)
.map(gw => ({
name: gw.name,
ip: gw.ip || '',
comment: gw.comment || '',
primary: gw.primary || false
}));
}
// Если нет в inventory, берем из server-configs
if (serverGateways.length === 0 && Array.isArray(server.gateways)) {
serverGateways = server.gateways
.filter(gw => gw.name && gw.name !== server.name)
.map(gw => ({
name: gw.name,
ip: gw.ip || '',
comment: gw.comment || '',
primary: gw.primary || false
}));
}
// Создаем map фильтров по community для быстрого поиска
const filtersMap = new Map();
filters.forEach(filter => {
const comm = filter.community;
if (!communitiesMap.has(comm)) {
communitiesMap.set(comm, {
community: comm,
description: filter.description || '',
gateways: []
});
}
const communityData = communitiesMap.get(comm);
// Добавляем gateway, если его еще нет
if (!communityData.gateways.find(g => g.name === filter.gateway)) {
communityData.gateways.push({
name: filter.gateway,
description: filter.description
});
}
filtersMap.set(filter.community, filter.gateway);
});
return {
...server,
communities: Array.from(communitiesMap.values())
gateways: serverGateways,
filtersMap: filtersMap
};
} catch (e) {
console.error(`Error loading filters for ${server.id}:`, e);
console.error(`Error loading data for ${server.id}:`, e);
return {
...server,
communities: []
gateways: [],
filtersMap: new Map()
};
}
})
);
// Фильтруем серверы, у которых есть communities
const serversWithCommunities = serversWithFilters.filter(s => s.communities.length > 0);
setServers(serversWithCommunities);
// Фильтруем серверы, у которых есть gateways
const serversWithGateways = serversWithData.filter(s => s.gateways.length > 0);
setServers(serversWithGateways);
// Устанавливаем активные шлюзы (первый gateway для каждого community на каждом сервере)
// Устанавливаем активные шлюзы из filters или первый gateway
const initialActive = {};
serversWithCommunities.forEach(server => {
server.communities.forEach(comm => {
if (comm.gateways.length > 0) {
const key = `${server.id}:${comm.community}`;
initialActive[key] = comm.gateways[0].name;
serversWithGateways.forEach(server => {
communities.forEach(comm => {
const key = `${server.id}:${comm.value}`;
// Берем gateway из filters, если есть, иначе первый доступный
const gatewayFromFilter = server.filtersMap.get(comm.value);
if (gatewayFromFilter && server.gateways.find(g => g.name === gatewayFromFilter)) {
initialActive[key] = gatewayFromFilter;
} else if (server.gateways.length > 0) {
initialActive[key] = server.gateways[0].name;
}
});
});
@@ -146,9 +180,24 @@ function EasySwitchManager() {
// Здесь можно добавить логику обновления фильтров на серверах
};
const handleRefresh = () => {
loadData();
loadInventoryServers();
const handleRefresh = async () => {
try {
// Загружаем communities
const commRes = await api.get(`/communities`);
const communities = Array.isArray(commRes.data) ? commRes.data : [];
setCommunitiesDirectory(communities);
// Загружаем inventory servers
const invRes = await api.get(`/servers`);
const inventory = Array.isArray(invRes.data) ? invRes.data : [];
setInventoryServers(inventory);
// Загружаем данные серверов и фильтров
await loadDataWithParams(communities, inventory);
} catch (e) {
console.error('Error refreshing data:', e);
setError('Ошибка обновления данных');
}
};
// Получаем метаданные сервера из inventory
@@ -172,14 +221,15 @@ function EasySwitchManager() {
if (serverName.includes(search) || serverDesc.includes(search)) return true;
// Поиск по communities и gateways
return server.communities.some(comm => {
const commValue = (comm.community || '').toLowerCase();
return communitiesDirectory.some(comm => {
const commValue = (comm.value || '').toLowerCase();
const commName = (comm.name || '').toLowerCase();
const commDesc = (comm.description || '').toLowerCase();
return commValue.includes(search) ||
commDesc.includes(search) ||
comm.gateways.some(g => (g.name || '').toLowerCase().includes(search));
});
commName.includes(search) ||
commDesc.includes(search);
}) || server.gateways.some(g => (g.name || '').toLowerCase().includes(search));
});
if (loading && servers.length === 0) {
@@ -220,7 +270,7 @@ function EasySwitchManager() {
Easy Switch
</h2>
<div className="text-muted mt-1">
Быстрое переключение шлюзов для {servers.length} {servers.length === 1 ? 'сервера' : 'серверов'}
Быстрое переключение шлюзов {servers.length} {servers.length === 1 ? 'сервер' : 'серверов'} {communitiesDirectory.length} communities
</div>
</div>
<div className="col-auto">
@@ -256,7 +306,7 @@ function EasySwitchManager() {
onClick={() => setActiveTab('proxies')}
type="button"
>
Прокси ({servers.reduce((sum, s) => sum + s.communities.length, 0)})
Прокси ({communitiesDirectory.length})
</button>
</li>
<li className="nav-item" role="presentation">
@@ -363,33 +413,39 @@ function EasySwitchManager() {
<span className="text-muted small"> {server.description}</span>
)}
<span className="badge bg-blue-lt text-blue ms-auto">
{server.communities.length} {server.communities.length === 1 ? 'community' : 'communities'}
{communitiesDirectory.length} communities {server.gateways.length} gateways
</span>
</div>
</div>
<div className="card-body p-3">
{/* Communities для этого сервера */}
{server.communities.map((comm) => {
const activeKey = `${server.id}:${comm.community}`;
{/* ВСЕ Communities для этого сервера */}
{communitiesDirectory.map((comm) => {
const activeKey = `${server.id}:${comm.value}`;
const activeGw = activeGateways[activeKey];
const hasFilterForComm = server.filtersMap.has(comm.value);
return (
<div key={comm.community} className="mb-3">
<div key={comm.value} className="mb-3">
{/* Заголовок community */}
<div className="mb-2">
<span className="badge bg-blue-lt text-blue me-2" style={{ fontSize: '0.8rem' }}>
{comm.community}
{comm.value}
</span>
<span className="fw-medium">{comm.description}</span>
<span className="fw-medium">{comm.name || comm.description || ''}</span>
{hasFilterForComm && (
<span className="badge bg-green-lt text-green ms-2" style={{ fontSize: '0.7rem' }}>
настроен
</span>
)}
<span className="text-muted small ms-2">
({comm.gateways.length})
({server.gateways.length})
</span>
</div>
{/* Сетка карточек шлюзов */}
{/* Сетка карточек ВСЕх шлюзов сервера */}
<div className="row g-2">
{comm.gateways.map((gw, idx) => {
{server.gateways.map((gw, idx) => {
const isActive = activeGw === gw.name;
const isFastest = idx === 0;
const ping = 20 + idx * 10 + Math.floor(Math.random() * 15);
@@ -405,7 +461,7 @@ function EasySwitchManager() {
borderRadius: '6px',
boxShadow: isActive ? '0 3px 8px rgba(32, 107, 196, 0.3)' : 'none'
}}
onClick={() => handleGatewaySelect(server.id, comm.community, gw.name)}
onClick={() => handleGatewaySelect(server.id, comm.value, gw.name)}
onMouseEnter={(e) => {
if (!isActive) {
e.currentTarget.style.boxShadow = '0 2px 6px rgba(0,0,0,0.1)';