feat(NetworkConfigManager, FilterManager): update gateway configuration to use IP as name; enhance filtering logic and UI to accommodate changes in gateway structure
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m44s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m44s
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
"gateways": [
|
||||
{
|
||||
"id": "gw-cloudflare-swe",
|
||||
"name": "SWE-IHOR",
|
||||
"ip": "94.142.140.1",
|
||||
"description": "Cloudflare Sweden - основной выход",
|
||||
"serverId": "SWE-HIPHOST",
|
||||
@@ -11,7 +10,6 @@
|
||||
},
|
||||
{
|
||||
"id": "gw-bunny-swe",
|
||||
"name": "SWE-BUNNY",
|
||||
"ip": "94.142.140.2",
|
||||
"description": "Bunny CDN Sweden",
|
||||
"serverId": "SWE-HIPHOST",
|
||||
@@ -20,7 +18,6 @@
|
||||
},
|
||||
{
|
||||
"id": "gw-telegram-nl",
|
||||
"name": "TG-PROXY-1",
|
||||
"ip": "149.154.167.50",
|
||||
"description": "Telegram DC2",
|
||||
"serverId": "NL-AMSTERDAM",
|
||||
@@ -29,7 +26,6 @@
|
||||
},
|
||||
{
|
||||
"id": "gw-hetzner-de",
|
||||
"name": "DE-HETZNER",
|
||||
"ip": "195.100.30.21",
|
||||
"description": "Hetzner Frankfurt",
|
||||
"serverId": "DE-FRANKFURT",
|
||||
@@ -38,7 +34,6 @@
|
||||
},
|
||||
{
|
||||
"id": "gw-fastly-us",
|
||||
"name": "US-FASTLY",
|
||||
"ip": "151.101.1.1",
|
||||
"description": "Fastly US East",
|
||||
"serverId": "US-NEWYORK",
|
||||
@@ -47,9 +42,8 @@
|
||||
},
|
||||
{
|
||||
"id": "gw-recursive-swe",
|
||||
"name": "SWE-RECURSIVE",
|
||||
"ip": "10.0.0.1",
|
||||
"description": "Рекурсивный gateway через SWE-IHOR",
|
||||
"description": "Рекурсивный gateway через 94.142.140.1",
|
||||
"serverId": "SWE-HIPHOST",
|
||||
"country": "SWE",
|
||||
"type": "recursive",
|
||||
@@ -72,7 +66,6 @@
|
||||
"type": "WireGuard",
|
||||
"localIp": "10.20.0.1",
|
||||
"remoteIp": "10.20.0.2",
|
||||
"port": "51820",
|
||||
"serverId": "SWE-HIPHOST"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -789,9 +789,10 @@ function FilterManager() {
|
||||
});
|
||||
|
||||
networkGateways.forEach(gw => {
|
||||
if (!gw.ip) return; // Пропускаем gateway без IP
|
||||
gws.push({
|
||||
name: gw.name,
|
||||
ip: gw.ip || '',
|
||||
name: gw.ip, // Используем IP как имя для совместимости со старой логикой
|
||||
ip: gw.ip,
|
||||
comment: gw.description || '',
|
||||
serverDns: serverDns,
|
||||
country: country,
|
||||
|
||||
@@ -61,7 +61,6 @@ const GATEWAY_TYPES = [
|
||||
// Пустые объекты
|
||||
const getEmptyGateway = () => ({
|
||||
id: `gw-${Date.now()}-${Math.random().toString(16).slice(2, 6)}`,
|
||||
name: '',
|
||||
ip: '',
|
||||
description: '',
|
||||
serverId: '',
|
||||
@@ -76,7 +75,6 @@ const getEmptyInterface = () => ({
|
||||
type: 'GRE',
|
||||
localIp: '',
|
||||
remoteIp: '',
|
||||
port: '',
|
||||
serverId: '',
|
||||
});
|
||||
|
||||
@@ -232,7 +230,6 @@ function NetworkConfigManager() {
|
||||
result = result.filter(g => {
|
||||
const server = getServerInfo(g.serverId);
|
||||
return (
|
||||
g.name?.toLowerCase().includes(term) ||
|
||||
g.ip?.toLowerCase().includes(term) ||
|
||||
g.country?.toLowerCase().includes(term) ||
|
||||
server?.country?.toLowerCase().includes(term) ||
|
||||
@@ -501,37 +498,48 @@ function NetworkConfigManager() {
|
||||
code += `\n# === Рекурсивные маршруты для сервера: ${serverName} ===\n`;
|
||||
|
||||
gateways.forEach(gw => {
|
||||
const parentGateway = getParentGateway(gw.parentGatewayId);
|
||||
const parent = getParentGateway(gw.parentGatewayId);
|
||||
|
||||
if (!parentGateway) {
|
||||
code += `\n# Gateway "${gw.name}" - родительский gateway не найден, пропущен\n`;
|
||||
if (!parent) {
|
||||
code += `\n# Gateway "${gw.ip || 'без IP'}" - родительский gateway/интерфейс не найден, пропущен\n`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gw.ip) {
|
||||
code += `\n# Gateway "${gw.name}" - IP адрес не указан, пропущен\n`;
|
||||
code += `\n# Gateway без IP адреса - пропущен\n`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parentGateway.ip) {
|
||||
code += `\n# Gateway "${gw.name}" - родительский gateway "${parentGateway.name}" не имеет IP, пропущен\n`;
|
||||
// Определяем IP родителя в зависимости от типа
|
||||
const parentIp = parent.type === 'interface' ? parent.remoteIp : parent.ip;
|
||||
|
||||
if (!parentIp) {
|
||||
const parentType = parent.type === 'interface' ? 'интерфейс' : 'gateway';
|
||||
code += `\n# Gateway "${gw.ip}" - родительский ${parentType} не имеет IP, пропущен\n`;
|
||||
return;
|
||||
}
|
||||
|
||||
validRoutesCount++;
|
||||
|
||||
// Создаём маршрут для рекурсивного gateway
|
||||
// Маршрут к IP рекурсивного gateway направляется через родительский gateway
|
||||
code += `\n# Рекурсивный gateway: ${gw.name} (IP: ${gw.ip})\n`;
|
||||
code += `# Родительский gateway: ${parentGateway.name} (IP: ${parentGateway.ip})\n`;
|
||||
const parentLabel = parent.type === 'interface'
|
||||
? `интерфейс ${parent.name || parent.type} (${parent.remoteIp})`
|
||||
: `gateway ${parent.ip}`;
|
||||
|
||||
code += `\n# Рекурсивный gateway: ${gw.ip}\n`;
|
||||
code += `# Родительский: ${parentLabel}\n`;
|
||||
|
||||
// Создаём маршрут к IP рекурсивного gateway через родительский
|
||||
code += `/ip route add dst-address=${gw.ip}/32 gateway=${parentGateway.ip} comment="Recursive: ${gw.name} -> ${parentGateway.name}"\n`;
|
||||
const comment = gw.description
|
||||
? `${gw.description} -> ${parentIp}`
|
||||
: `Recursive: ${gw.ip} -> ${parentIp}`;
|
||||
code += `/ip route add dst-address=${gw.ip}/32 gateway=${parentIp} comment="${comment}"\n`;
|
||||
|
||||
// Если у рекурсивного gateway есть описание с указанием на default route
|
||||
if (gw.description && (gw.description.toLowerCase().includes('default') || gw.description.toLowerCase().includes('0.0.0.0'))) {
|
||||
code += `# Дополнительный default route через рекурсивный gateway\n`;
|
||||
code += `/ip route add dst-address=0.0.0.0/0 gateway=${parentGateway.ip} comment="Recursive default: ${gw.name} -> ${parentGateway.name}"\n`;
|
||||
const defaultComment = gw.description ? `${gw.description} (default)` : `Recursive default: ${gw.ip}`;
|
||||
code += `/ip route add dst-address=0.0.0.0/0 gateway=${parentIp} comment="${defaultComment}"\n`;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -650,10 +658,19 @@ function NetworkConfigManager() {
|
||||
);
|
||||
};
|
||||
|
||||
// === Получение родительского gateway ===
|
||||
const getParentGateway = (gatewayId) => {
|
||||
if (!gatewayId) return null;
|
||||
return config.gateways?.find(g => g.id === gatewayId);
|
||||
// === Получение родительского gateway или интерфейса ===
|
||||
const getParentGateway = (parentId) => {
|
||||
if (!parentId) return null;
|
||||
|
||||
// Сначала ищем в gateway
|
||||
const gateway = config.gateways?.find(g => g.id === parentId);
|
||||
if (gateway) return { type: 'gateway', ...gateway };
|
||||
|
||||
// Если не найден, ищем в интерфейсах
|
||||
const iface = config.tunnelInterfaces?.find(i => i.id === parentId);
|
||||
if (iface) return { type: 'interface', ...iface };
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// === Рендер карточки Gateway ===
|
||||
@@ -678,7 +695,7 @@ function NetworkConfigManager() {
|
||||
</span>
|
||||
<div className="flex-fill">
|
||||
<div className="d-flex align-items-center gap-1">
|
||||
<span className="fw-medium">{gateway.name || '—'}</span>
|
||||
<code className="fw-medium">{gateway.ip || '—'}</code>
|
||||
<span className={`badge bg-${gatewayType.color}-lt text-${gatewayType.color}`} style={{ fontSize: '0.65rem' }}>
|
||||
{gatewayType.label}
|
||||
</span>
|
||||
@@ -730,7 +747,12 @@ function NetworkConfigManager() {
|
||||
{parentGateway && (
|
||||
<div className="d-flex align-items-center gap-1 text-muted small mb-1">
|
||||
<span className="text-muted">→</span>
|
||||
<span>Родитель: {parentGateway.name}</span>
|
||||
<span>
|
||||
Родитель: {parentGateway.type === 'interface'
|
||||
? `Интерфейс ${parentGateway.name || parentGateway.type} (${parentGateway.remoteIp})`
|
||||
: `Gateway ${parentGateway.ip}`
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -756,12 +778,11 @@ function NetworkConfigManager() {
|
||||
</span>
|
||||
<div className="flex-fill">
|
||||
<div className="fw-medium">{iface.name || '—'}</div>
|
||||
<div className="text-muted small">
|
||||
<span className={`badge bg-${getInterfaceTypeColor(iface.type)}-lt text-${getInterfaceTypeColor(iface.type)}`}>
|
||||
{iface.type}
|
||||
</span>
|
||||
{iface.port && <span className="ms-1">:{iface.port}</span>}
|
||||
</div>
|
||||
<div className="text-muted small">
|
||||
<span className={`badge bg-${getInterfaceTypeColor(iface.type)}-lt text-${getInterfaceTypeColor(iface.type)}`}>
|
||||
{iface.type}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dropdown">
|
||||
<button className="btn btn-ghost-secondary btn-icon btn-sm" data-bs-toggle="dropdown">
|
||||
@@ -1103,7 +1124,7 @@ function NetworkConfigManager() {
|
||||
<span className={`avatar avatar-xs bg-${gatewayType.color}-lt me-2`}>
|
||||
<IconCircleFilled size={8} className={`text-${gatewayType.color}`} />
|
||||
</span>
|
||||
<strong>{gateway.name || '—'}</strong>
|
||||
<code className="fw-medium">{gateway.ip || '—'}</code>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@@ -1112,7 +1133,10 @@ function NetworkConfigManager() {
|
||||
</span>
|
||||
{parentGateway && (
|
||||
<div className="text-muted small mt-1">
|
||||
→ {parentGateway.name}
|
||||
→ {parentGateway.type === 'interface'
|
||||
? `Интерфейс ${parentGateway.name || parentGateway.type} (${parentGateway.remoteIp})`
|
||||
: `Gateway ${parentGateway.ip}`
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
@@ -1234,7 +1258,6 @@ function NetworkConfigManager() {
|
||||
<th>Тип</th>
|
||||
<th>Local IP</th>
|
||||
<th>Remote IP</th>
|
||||
<th>Порт</th>
|
||||
<th>Сервер</th>
|
||||
<th className="text-end" style={{ width: 100 }}>Действия</th>
|
||||
</tr>
|
||||
@@ -1265,7 +1288,6 @@ function NetworkConfigManager() {
|
||||
</code>
|
||||
</td>
|
||||
<td><code>{iface.remoteIp || '—'}</code></td>
|
||||
<td>{iface.port || '—'}</td>
|
||||
<td>
|
||||
{server ? (
|
||||
<div className="d-flex align-items-center">
|
||||
@@ -1420,7 +1442,13 @@ 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>
|
||||
<div className="col-md-6">
|
||||
<FormField
|
||||
@@ -1454,23 +1482,41 @@ function NetworkConfigManager() {
|
||||
</div>
|
||||
{editingGateway.type === 'recursive' && (
|
||||
<div className="col-12">
|
||||
<label className="form-label">Родительский gateway</label>
|
||||
<label className="form-label">Родительский gateway или интерфейс</label>
|
||||
<select
|
||||
className="form-select"
|
||||
value={editingGateway.parentGatewayId || ''}
|
||||
onChange={(e) => setEditingGateway({ ...editingGateway, parentGatewayId: e.target.value })}
|
||||
>
|
||||
<option value="">Выберите родительский gateway</option>
|
||||
{config.gateways
|
||||
.filter(g => g.id !== editingGateway.id && g.type === 'direct')
|
||||
.map(g => (
|
||||
<option key={g.id} value={g.id}>
|
||||
{g.ip || g.name} {g.description ? `(${g.description})` : ''}
|
||||
</option>
|
||||
))}
|
||||
<option value="">Выберите родительский gateway или интерфейс</option>
|
||||
|
||||
{/* Прямые gateway */}
|
||||
<optgroup label="Прямые gateway">
|
||||
{config.gateways
|
||||
.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})` : ''}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
|
||||
{/* Интерфейсы */}
|
||||
<optgroup label="Интерфейсы (по remote IP)">
|
||||
{config.tunnelInterfaces
|
||||
.filter(i => i.remoteIp)
|
||||
.map(i => {
|
||||
const server = getServerInfo(i.serverId);
|
||||
return (
|
||||
<option key={i.id} value={i.id}>
|
||||
Интерфейс: {i.remoteIp} ({i.name || i.type}) {server ? `- ${server.dns || server.ip}` : ''}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</optgroup>
|
||||
</select>
|
||||
<div className="form-text">
|
||||
Рекурсивный gateway ссылается на другой gateway (обычно прямой)
|
||||
Рекурсивный gateway может ссылаться на прямой gateway или на интерфейс (по его remote IP)
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1546,7 +1592,7 @@ function NetworkConfigManager() {
|
||||
placeholder="10.10.0.1"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<div className="col-md-6">
|
||||
<FormField
|
||||
label="Remote IP"
|
||||
name="remoteIp"
|
||||
@@ -1555,15 +1601,6 @@ function NetworkConfigManager() {
|
||||
placeholder="10.10.0.2"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-4">
|
||||
<FormField
|
||||
label="Порт"
|
||||
name="port"
|
||||
value={editingInterface.port}
|
||||
onChange={(val) => setEditingInterface({ ...editingInterface, port: val })}
|
||||
placeholder="51820"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</FormModal>
|
||||
@@ -1621,7 +1658,7 @@ function NetworkConfigManager() {
|
||||
message={
|
||||
itemToDelete ? (
|
||||
<>
|
||||
<p>Вы уверены, что хотите удалить <strong>{itemToDelete.name || itemToDelete.cidr || 'этот элемент'}</strong>?</p>
|
||||
<p>Вы уверены, что хотите удалить <strong>{itemToDelete.ip || itemToDelete.name || itemToDelete.cidr || 'этот элемент'}</strong>?</p>
|
||||
<p className="text-muted mb-0">Это действие нельзя отменить.</p>
|
||||
</>
|
||||
) : null
|
||||
|
||||
Reference in New Issue
Block a user