refactor(NetworkConfigManager): remove provider and country from gateway configuration; integrate server details for improved filtering and UI display
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m38s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m38s
This commit is contained in:
@@ -4,8 +4,6 @@
|
||||
"id": "gw-cloudflare-swe",
|
||||
"name": "SWE-IHOR",
|
||||
"ip": "94.142.140.1",
|
||||
"provider": "cloudflare",
|
||||
"country": "SWE",
|
||||
"description": "Cloudflare Sweden - основной выход",
|
||||
"serverId": "SWE-HIPHOST"
|
||||
},
|
||||
@@ -13,8 +11,6 @@
|
||||
"id": "gw-bunny-swe",
|
||||
"name": "SWE-BUNNY",
|
||||
"ip": "94.142.140.2",
|
||||
"provider": "bunny",
|
||||
"country": "SWE",
|
||||
"description": "Bunny CDN Sweden",
|
||||
"serverId": "SWE-HIPHOST"
|
||||
},
|
||||
@@ -22,8 +18,6 @@
|
||||
"id": "gw-telegram-nl",
|
||||
"name": "TG-PROXY-1",
|
||||
"ip": "149.154.167.50",
|
||||
"provider": "telegram",
|
||||
"country": "NL",
|
||||
"description": "Telegram DC2",
|
||||
"serverId": "NL-AMSTERDAM"
|
||||
},
|
||||
@@ -31,8 +25,6 @@
|
||||
"id": "gw-hetzner-de",
|
||||
"name": "DE-HETZNER",
|
||||
"ip": "195.100.30.21",
|
||||
"provider": "hetzner",
|
||||
"country": "DE",
|
||||
"description": "Hetzner Frankfurt",
|
||||
"serverId": "DE-FRANKFURT"
|
||||
},
|
||||
@@ -40,8 +32,6 @@
|
||||
"id": "gw-fastly-us",
|
||||
"name": "US-FASTLY",
|
||||
"ip": "151.101.1.1",
|
||||
"provider": "fastly",
|
||||
"country": "US",
|
||||
"description": "Fastly US East",
|
||||
"serverId": "US-NEWYORK"
|
||||
}
|
||||
|
||||
@@ -55,8 +55,6 @@ const getEmptyGateway = () => ({
|
||||
id: `gw-${Date.now()}-${Math.random().toString(16).slice(2, 6)}`,
|
||||
name: '',
|
||||
ip: '',
|
||||
provider: 'custom',
|
||||
country: '',
|
||||
description: '',
|
||||
serverId: '',
|
||||
});
|
||||
@@ -198,7 +196,11 @@ function NetworkConfigManager() {
|
||||
let result = [...(config.gateways || [])];
|
||||
|
||||
if (providerFilter) {
|
||||
result = result.filter(g => g.provider === providerFilter);
|
||||
// Фильтруем по провайдеру сервера
|
||||
result = result.filter(g => {
|
||||
const server = getServerInfo(g.serverId);
|
||||
return server?.provider === providerFilter;
|
||||
});
|
||||
}
|
||||
|
||||
if (serverFilter) {
|
||||
@@ -207,13 +209,17 @@ function NetworkConfigManager() {
|
||||
|
||||
if (searchTerm) {
|
||||
const term = searchTerm.toLowerCase();
|
||||
result = result.filter(g =>
|
||||
g.name?.toLowerCase().includes(term) ||
|
||||
g.ip?.toLowerCase().includes(term) ||
|
||||
g.country?.toLowerCase().includes(term) ||
|
||||
g.description?.toLowerCase().includes(term) ||
|
||||
getServerLabel(g.serverId).toLowerCase().includes(term)
|
||||
);
|
||||
result = result.filter(g => {
|
||||
const server = getServerInfo(g.serverId);
|
||||
return (
|
||||
g.name?.toLowerCase().includes(term) ||
|
||||
g.ip?.toLowerCase().includes(term) ||
|
||||
server?.country?.toLowerCase().includes(term) ||
|
||||
server?.provider?.toLowerCase().includes(term) ||
|
||||
g.description?.toLowerCase().includes(term) ||
|
||||
getServerLabel(g.serverId).toLowerCase().includes(term)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -442,8 +448,19 @@ function NetworkConfigManager() {
|
||||
|
||||
// === Получение цвета провайдера ===
|
||||
const getProviderColor = (provider) => {
|
||||
const p = GATEWAY_PROVIDERS.find(gp => gp.value === provider);
|
||||
return p?.color || 'secondary';
|
||||
if (!provider) return 'secondary';
|
||||
// Ищем точное совпадение
|
||||
const exact = GATEWAY_PROVIDERS.find(gp =>
|
||||
gp.value === provider ||
|
||||
gp.label.toLowerCase() === provider.toLowerCase()
|
||||
);
|
||||
if (exact) return exact.color;
|
||||
// Проверяем частичное совпадение
|
||||
const partial = GATEWAY_PROVIDERS.find(gp =>
|
||||
provider.toLowerCase().includes(gp.value.toLowerCase()) ||
|
||||
provider.toLowerCase().includes(gp.label.toLowerCase())
|
||||
);
|
||||
return partial?.color || 'secondary';
|
||||
};
|
||||
|
||||
const getInterfaceTypeColor = (type) => {
|
||||
@@ -501,66 +518,72 @@ function NetworkConfigManager() {
|
||||
};
|
||||
|
||||
// === Рендер карточки Gateway ===
|
||||
const renderGatewayCard = (gateway) => (
|
||||
<div key={gateway.id} className="col-sm-6 col-lg-4">
|
||||
<div className="card card-sm">
|
||||
<div className="card-body">
|
||||
<div className="d-flex align-items-center mb-2">
|
||||
<span className={`avatar avatar-sm bg-${getProviderColor(gateway.provider)}-lt me-2`}>
|
||||
<IconWorld size={16} className={`text-${getProviderColor(gateway.provider)}`} />
|
||||
</span>
|
||||
<div className="flex-fill">
|
||||
<div className="fw-medium">{gateway.name || '—'}</div>
|
||||
<div className="text-muted small">
|
||||
{GATEWAY_PROVIDERS.find(p => p.value === gateway.provider)?.label || gateway.provider}
|
||||
const renderGatewayCard = (gateway) => {
|
||||
const server = getServerInfo(gateway.serverId);
|
||||
const provider = server?.provider || '';
|
||||
const country = server?.country || '';
|
||||
|
||||
return (
|
||||
<div key={gateway.id} className="col-sm-6 col-lg-4">
|
||||
<div className="card card-sm">
|
||||
<div className="card-body">
|
||||
<div className="d-flex align-items-center mb-2">
|
||||
<span className={`avatar avatar-sm bg-${getProviderColor(provider)}-lt me-2`}>
|
||||
<IconWorld size={16} className={`text-${getProviderColor(provider)}`} />
|
||||
</span>
|
||||
<div className="flex-fill">
|
||||
<div className="fw-medium">{gateway.name || '—'}</div>
|
||||
<div className="text-muted small">
|
||||
{provider || '—'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="dropdown">
|
||||
<button className="btn btn-ghost-secondary btn-icon btn-sm" data-bs-toggle="dropdown">
|
||||
<IconEdit size={16} />
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
<button className="dropdown-item" onClick={() => handleEditGateway(gateway)}>
|
||||
<IconEdit size={16} className="me-2" />Редактировать
|
||||
</button>
|
||||
<button className="dropdown-item text-danger" onClick={() => handleDeleteGateway(gateway)}>
|
||||
<IconTrash size={16} className="me-2" />Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dropdown">
|
||||
<button className="btn btn-ghost-secondary btn-icon btn-sm" data-bs-toggle="dropdown">
|
||||
<IconEdit size={16} />
|
||||
</button>
|
||||
<div className="dropdown-menu dropdown-menu-end">
|
||||
<button className="dropdown-item" onClick={() => handleEditGateway(gateway)}>
|
||||
<IconEdit size={16} className="me-2" />Редактировать
|
||||
|
||||
<div className="d-flex align-items-center gap-2 mb-2">
|
||||
<code className="flex-fill text-truncate" title={gateway.ip}>
|
||||
{gateway.ip || '—'}
|
||||
</code>
|
||||
{gateway.ip && (
|
||||
<button
|
||||
className="btn btn-ghost-secondary btn-icon btn-sm"
|
||||
onClick={() => copyToClipboard(gateway.ip)}
|
||||
title="Копировать IP"
|
||||
>
|
||||
<IconCopy size={14} />
|
||||
</button>
|
||||
<button className="dropdown-item text-danger" onClick={() => handleDeleteGateway(gateway)}>
|
||||
<IconTrash size={16} className="me-2" />Удалить
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="d-flex align-items-center gap-2 mb-2">
|
||||
<code className="flex-fill text-truncate" title={gateway.ip}>
|
||||
{gateway.ip || '—'}
|
||||
</code>
|
||||
{gateway.ip && (
|
||||
<button
|
||||
className="btn btn-ghost-secondary btn-icon btn-sm"
|
||||
onClick={() => copyToClipboard(gateway.ip)}
|
||||
title="Копировать IP"
|
||||
>
|
||||
<IconCopy size={14} />
|
||||
</button>
|
||||
|
||||
{country && (
|
||||
<div className="d-flex align-items-center gap-1 text-muted small">
|
||||
<span>{countryToFlag(country)}</span>
|
||||
<span>{country}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{gateway.description && (
|
||||
<div className="text-muted small mt-1 text-truncate" title={gateway.description}>
|
||||
{gateway.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{gateway.country && (
|
||||
<div className="d-flex align-items-center gap-1 text-muted small">
|
||||
<span>{countryToFlag(gateway.country)}</span>
|
||||
<span>{gateway.country}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{gateway.description && (
|
||||
<div className="text-muted small mt-1 text-truncate" title={gateway.description}>
|
||||
{gateway.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
// === Рендер карточки Interface ===
|
||||
const renderInterfaceCard = (iface) => (
|
||||
@@ -739,7 +762,7 @@ function NetworkConfigManager() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Provider filter for gateways */}
|
||||
{/* Provider filter for gateways - берем уникальные провайдеры из серверов */}
|
||||
{activeTab === 'gateways' && (
|
||||
<div className="col-6 col-md-3 col-lg-2">
|
||||
<select
|
||||
@@ -748,8 +771,8 @@ function NetworkConfigManager() {
|
||||
onChange={(e) => setProviderFilter(e.target.value)}
|
||||
>
|
||||
<option value="">Все провайдеры</option>
|
||||
{GATEWAY_PROVIDERS.map(p => (
|
||||
<option key={p.value} value={p.value}>{p.label}</option>
|
||||
{Array.from(new Set(servers.map(s => s.provider).filter(Boolean))).map(provider => (
|
||||
<option key={provider} value={provider}>{provider}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@@ -887,12 +910,15 @@ function NetworkConfigManager() {
|
||||
<tbody>
|
||||
{filteredGateways.map(gateway => {
|
||||
const server = getServerInfo(gateway.serverId);
|
||||
const provider = server?.provider || '';
|
||||
const country = server?.country || '';
|
||||
|
||||
return (
|
||||
<tr key={gateway.id}>
|
||||
<td>
|
||||
<div className="d-flex align-items-center">
|
||||
<span className={`avatar avatar-xs bg-${getProviderColor(gateway.provider)}-lt me-2`}>
|
||||
<IconCircleFilled size={8} className={`text-${getProviderColor(gateway.provider)}`} />
|
||||
<span className={`avatar avatar-xs bg-${getProviderColor(provider)}-lt me-2`}>
|
||||
<IconCircleFilled size={8} className={`text-${getProviderColor(provider)}`} />
|
||||
</span>
|
||||
<strong>{gateway.name || '—'}</strong>
|
||||
</div>
|
||||
@@ -912,9 +938,11 @@ function NetworkConfigManager() {
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
<span className={`badge bg-${getProviderColor(gateway.provider)}-lt text-${getProviderColor(gateway.provider)}`}>
|
||||
{GATEWAY_PROVIDERS.find(p => p.value === gateway.provider)?.label || gateway.provider}
|
||||
</span>
|
||||
{provider ? (
|
||||
<span className={`badge bg-${getProviderColor(provider)}-lt text-${getProviderColor(provider)}`}>
|
||||
{provider}
|
||||
</span>
|
||||
) : '—'}
|
||||
</td>
|
||||
<td>
|
||||
{server ? (
|
||||
@@ -929,8 +957,8 @@ function NetworkConfigManager() {
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{gateway.country ? (
|
||||
<span>{countryToFlag(gateway.country)} {gateway.country}</span>
|
||||
{country ? (
|
||||
<span>{countryToFlag(country)} {country}</span>
|
||||
) : '—'}
|
||||
</td>
|
||||
<td className="text-end">
|
||||
@@ -1173,14 +1201,16 @@ function NetworkConfigManager() {
|
||||
{editingGateway && (
|
||||
<div className="row g-3">
|
||||
<div className="col-12">
|
||||
<label className="form-label">Сервер</label>
|
||||
<label className="form-label required">Сервер</label>
|
||||
<ServerAutocompleteInput
|
||||
value={editingGateway.serverId}
|
||||
onChange={(val) => setEditingGateway({ ...editingGateway, serverId: val })}
|
||||
servers={servers}
|
||||
placeholder="Выберите сервер..."
|
||||
/>
|
||||
<div className="form-text">Gateway будет привязан к выбранному серверу</div>
|
||||
<div className="form-text">
|
||||
Страна и провайдер будут автоматически взяты из данных сервера
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<FormField
|
||||
@@ -1201,25 +1231,6 @@ function NetworkConfigManager() {
|
||||
placeholder="94.142.140.1"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<FormField
|
||||
label="Провайдер"
|
||||
name="provider"
|
||||
type="select"
|
||||
value={editingGateway.provider}
|
||||
onChange={(val) => setEditingGateway({ ...editingGateway, provider: val })}
|
||||
options={GATEWAY_PROVIDERS.map(p => ({ value: p.value, label: p.label }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<FormField
|
||||
label="Страна"
|
||||
name="country"
|
||||
value={editingGateway.country}
|
||||
onChange={(val) => setEditingGateway({ ...editingGateway, country: val })}
|
||||
placeholder="SWE"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<FormField
|
||||
label="Описание"
|
||||
@@ -1229,6 +1240,23 @@ function NetworkConfigManager() {
|
||||
placeholder="Основной выход через Cloudflare"
|
||||
/>
|
||||
</div>
|
||||
{editingGateway.serverId && (() => {
|
||||
const server = getServerInfo(editingGateway.serverId);
|
||||
return server ? (
|
||||
<div className="col-12">
|
||||
<div className="alert alert-info mb-0">
|
||||
<div className="d-flex align-items-center">
|
||||
<IconServer size={16} className="me-2" />
|
||||
<div>
|
||||
<strong>{server.dns || server.ip}</strong>
|
||||
{server.provider && <span className="ms-2">• {server.provider}</span>}
|
||||
{server.country && <span className="ms-2">• {countryToFlag(server.country)} {server.country}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
</FormModal>
|
||||
|
||||
Reference in New Issue
Block a user