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
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s
This commit is contained in:
@@ -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
|
||||
|
||||
### Планируется добавить
|
||||
|
||||
@@ -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)';
|
||||
|
||||
Reference in New Issue
Block a user