feat(NetworkConfigManager, FilterManager): enhance gateway display and validation; update UI to show comments and improve IP handling in gateway configuration
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m43s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m43s
This commit is contained in:
@@ -482,12 +482,12 @@ function GatewayAutocomplete({ label, value, onChange, gateways = [], required =
|
||||
</span>
|
||||
<div className="flex-grow-1 min-w-0">
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<span className="fw-bold">{gw.name}</span>
|
||||
{gw.ip && <span className="text-muted">({gw.ip})</span>}
|
||||
<code className="fw-bold">{gw.name}</code>
|
||||
{gw.comment && <span className="text-muted">({gw.comment})</span>}
|
||||
{gw.serverDns && <span className="text-muted">— {gw.serverDns}</span>}
|
||||
</div>
|
||||
<div className="text-muted small">
|
||||
{[gw.country, gw.comment].filter(Boolean).join(' · ') || 'Шлюз'}
|
||||
{[gw.country].filter(Boolean).join(' · ') || 'Шлюз'}
|
||||
</div>
|
||||
</div>
|
||||
{gw.primary && <span className="badge bg-green-lt text-green">Основной</span>}
|
||||
@@ -508,9 +508,12 @@ function GatewayAutocomplete({ label, value, onChange, gateways = [], required =
|
||||
{selectedGateway.country ? countryToFlag(selectedGateway.country) : <IconServer size={14} />}
|
||||
</span>
|
||||
<div className="flex-grow-1">
|
||||
<div className="fw-bold">{selectedGateway.name}</div>
|
||||
<div className="fw-bold">
|
||||
<code>{selectedGateway.name}</code>
|
||||
{selectedGateway.comment && <span className="text-muted ms-2">({selectedGateway.comment})</span>}
|
||||
</div>
|
||||
<div className="text-muted small">
|
||||
{[selectedGateway.ip, selectedGateway.serverDns, selectedGateway.comment].filter(Boolean).join(' · ')}
|
||||
{[selectedGateway.serverDns].filter(Boolean).join(' · ')}
|
||||
</div>
|
||||
</div>
|
||||
{selectedGateway.primary && <span className="badge bg-green-lt text-green">Основной</span>}
|
||||
|
||||
@@ -329,13 +329,39 @@ function NetworkConfigManager() {
|
||||
};
|
||||
|
||||
const handleSaveGateway = (gatewayData) => {
|
||||
// Валидация: IP адрес обязателен
|
||||
if (!gatewayData.ip || !gatewayData.ip.trim()) {
|
||||
notify.error('IP адрес обязателен');
|
||||
return;
|
||||
}
|
||||
|
||||
if (gatewayModalMode === 'add') {
|
||||
// Проверка на дубликат IP
|
||||
const existingGateway = config.gateways.find(g => g.ip === gatewayData.ip.trim());
|
||||
if (existingGateway) {
|
||||
notify.error(`Gateway с IP ${gatewayData.ip} уже существует`);
|
||||
return;
|
||||
}
|
||||
setConfig(prev => ({
|
||||
...prev,
|
||||
gateways: [...prev.gateways, gatewayData],
|
||||
}));
|
||||
notify.success('Gateway добавлен');
|
||||
} else {
|
||||
// При редактировании ищем по ID, но проверяем, не занят ли новый IP другим gateway
|
||||
const currentGateway = config.gateways.find(g => g.id === gatewayData.id);
|
||||
if (!currentGateway) {
|
||||
notify.error('Gateway не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
// Если IP изменился, проверяем, не занят ли он другим gateway
|
||||
if (gatewayData.ip.trim() !== currentGateway.ip &&
|
||||
config.gateways.some(g => g.id !== gatewayData.id && g.ip === gatewayData.ip.trim())) {
|
||||
notify.error(`Gateway с IP ${gatewayData.ip} уже существует`);
|
||||
return;
|
||||
}
|
||||
|
||||
setConfig(prev => ({
|
||||
...prev,
|
||||
gateways: prev.gateways.map(g => g.id === gatewayData.id ? gatewayData : g),
|
||||
@@ -701,7 +727,7 @@ function NetworkConfigManager() {
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-muted small">
|
||||
{provider || '—'}
|
||||
{gateway.description || provider || '—'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="dropdown">
|
||||
@@ -756,11 +782,6 @@ function NetworkConfigManager() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{gateway.description && (
|
||||
<div className="text-muted small mt-1 text-truncate" title={gateway.description}>
|
||||
{gateway.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1096,9 +1117,8 @@ function NetworkConfigManager() {
|
||||
<table className="table card-table table-vcenter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Имя</th>
|
||||
<th>Тип</th>
|
||||
<th>IP адрес</th>
|
||||
<th>Тип</th>
|
||||
<th>Провайдер</th>
|
||||
<th>Сервер</th>
|
||||
<th>Страна</th>
|
||||
@@ -1124,7 +1144,24 @@ function NetworkConfigManager() {
|
||||
<span className={`avatar avatar-xs bg-${gatewayType.color}-lt me-2`}>
|
||||
<IconCircleFilled size={8} className={`text-${gatewayType.color}`} />
|
||||
</span>
|
||||
<code className="fw-medium">{gateway.ip || '—'}</code>
|
||||
<div className="flex-fill">
|
||||
<div className="d-flex align-items-center gap-1">
|
||||
<code className="fw-medium">{gateway.ip || '—'}</code>
|
||||
{gateway.ip && (
|
||||
<button
|
||||
className="btn btn-ghost-secondary btn-icon p-0"
|
||||
onClick={() => copyToClipboard(gateway.ip)}
|
||||
style={{ width: 20, height: 20 }}
|
||||
title="Копировать IP"
|
||||
>
|
||||
<IconCopy size={12} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{gateway.description && (
|
||||
<div className="text-muted small">{gateway.description}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@@ -1140,20 +1177,6 @@ function NetworkConfigManager() {
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<code className="d-flex align-items-center gap-1">
|
||||
{gateway.ip || '—'}
|
||||
{gateway.ip && (
|
||||
<button
|
||||
className="btn btn-ghost-secondary btn-icon p-0"
|
||||
onClick={() => copyToClipboard(gateway.ip)}
|
||||
style={{ width: 20, height: 20 }}
|
||||
>
|
||||
<IconCopy size={12} />
|
||||
</button>
|
||||
)}
|
||||
</code>
|
||||
</td>
|
||||
<td>
|
||||
{provider ? (
|
||||
<span className={`badge bg-${getProviderColor(provider)}-lt text-${getProviderColor(provider)}`}>
|
||||
@@ -1442,22 +1465,22 @@ function NetworkConfigManager() {
|
||||
onChange={(val) => setEditingGateway({ ...editingGateway, ip: val })}
|
||||
placeholder="94.142.140.1"
|
||||
required
|
||||
disabled={gatewayModalMode === 'edit'}
|
||||
/>
|
||||
{gatewayModalMode === 'edit' && (
|
||||
<div className="form-text text-muted">
|
||||
IP адрес нельзя изменить после создания
|
||||
</div>
|
||||
)}
|
||||
<div className="form-text">
|
||||
IP адрес является главным идентификатором gateway
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<FormField
|
||||
label="Комментарий"
|
||||
label="Название (комментарий)"
|
||||
name="description"
|
||||
value={editingGateway.description}
|
||||
onChange={(val) => setEditingGateway({ ...editingGateway, description: val })}
|
||||
placeholder="Основной выход через Cloudflare"
|
||||
placeholder="SWE-IHOR или Основной выход через Cloudflare"
|
||||
/>
|
||||
<div className="form-text">
|
||||
Название gateway для удобства (отображается в фильтрах)
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<FormField
|
||||
@@ -1496,7 +1519,7 @@ function NetworkConfigManager() {
|
||||
.filter(g => g.id !== editingGateway.id && g.type === 'direct' && g.ip)
|
||||
.map(g => (
|
||||
<option key={g.id} value={g.id}>
|
||||
Gateway: {g.ip} {g.description ? `(${g.description})` : ''}
|
||||
Gateway: {g.ip} {g.description ? `- ${g.description}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
|
||||
Reference in New Issue
Block a user