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

This commit is contained in:
2026-01-22 12:16:08 +07:00
parent d3bf307d6c
commit 64f4b76d3f
2 changed files with 63 additions and 37 deletions
+8 -5
View File
@@ -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>}
+55 -32
View File
@@ -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>