feat(NetworkConfigManager): add optional second interface name for dual server configurations; enhance UI to support interface naming for both servers
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m38s

This commit is contained in:
2026-01-22 13:18:46 +07:00
parent cf1dbfce37
commit fdc8b1f458
2 changed files with 87 additions and 25 deletions
+2
View File
@@ -54,6 +54,7 @@
{ {
"id": "if-gre-swe-de", "id": "if-gre-swe-de",
"name": "gre-swe-de", "name": "gre-swe-de",
"name2": "gre-de-swe",
"type": "GRE", "type": "GRE",
"localIp": "10.10.0.1", "localIp": "10.10.0.1",
"remoteIp": "10.10.0.2", "remoteIp": "10.10.0.2",
@@ -63,6 +64,7 @@
{ {
"id": "if-wg-swe-nl", "id": "if-wg-swe-nl",
"name": "wg0", "name": "wg0",
"name2": "wg1",
"type": "WireGuard", "type": "WireGuard",
"localIp": "10.20.0.1", "localIp": "10.20.0.1",
"remoteIp": "10.20.0.2", "remoteIp": "10.20.0.2",
+89 -29
View File
@@ -73,6 +73,7 @@ const getEmptyGateway = () => ({
const getEmptyInterface = () => ({ const getEmptyInterface = () => ({
id: `if-${Date.now()}-${Math.random().toString(16).slice(2, 6)}`, id: `if-${Date.now()}-${Math.random().toString(16).slice(2, 6)}`,
name: '', name: '',
name2: '', // Имя интерфейса на втором сервере (опционально)
type: 'GRE', type: 'GRE',
localIp: '', localIp: '',
remoteIp: '', remoteIp: '',
@@ -511,8 +512,10 @@ function NetworkConfigManager() {
return ''; return '';
} }
let code = '\n# === Настройка IP адресов интерфейсов на обоих серверах ===\n'; let code = '\n# ====================================================================\n';
code += '# Эти команды нужно выполнить на соответствующих серверах\n\n'; code += '# === НАСТРОЙКА IP АДРЕСОВ ИНТЕРФЕЙСОВ НА ОБОИХ СЕРВЕРАХ ===\n';
code += '# Эти команды нужно выполнить на соответствующих серверах\n';
code += '# ====================================================================\n\n';
// Группируем по серверам // Группируем по серверам
const interfacesByServer = {}; const interfacesByServer = {};
@@ -524,43 +527,84 @@ function NetworkConfigManager() {
const server1Name = server1.dns || server1.ip || iface.serverId; const server1Name = server1.dns || server1.ip || iface.serverId;
const server2Name = server2.dns || server2.ip || iface.serverId2; const server2Name = server2.dns || server2.ip || iface.serverId2;
const interfaceName = iface.name || `${iface.type}-tunnel`;
// Для первого сервера - используем localIp // Имя интерфейса для первого сервера
const interfaceName1 = iface.name || `${iface.type}-tunnel`;
// Имя интерфейса для второго сервера (может отличаться)
const interfaceName2 = iface.name2 || iface.name || `${iface.type}-tunnel`;
// Для первого сервера - используем localIp как local, remoteIp как remote
if (!interfacesByServer[server1Name]) { if (!interfacesByServer[server1Name]) {
interfacesByServer[server1Name] = []; interfacesByServer[server1Name] = {
serverInfo: server1,
interfaces: []
};
} }
interfacesByServer[server1Name].push({ interfacesByServer[server1Name].interfaces.push({
interfaceName, interfaceName: interfaceName1,
ip: iface.localIp, localIp: iface.localIp,
type: iface.type,
remoteIp: iface.remoteIp, remoteIp: iface.remoteIp,
server2Name
});
// Для второго сервера - используем remoteIp
if (!interfacesByServer[server2Name]) {
interfacesByServer[server2Name] = [];
}
interfacesByServer[server2Name].push({
interfaceName,
ip: iface.remoteIp,
type: iface.type, type: iface.type,
remoteIp: iface.localIp, // Для второго сервера remoteIp - это localIp первого server2Name,
server2Name: server1Name server2Info: server2
});
// Для второго сервера - МЕНЯЕМ МЕСТАМИ: remoteIp становится localIp, localIp становится remoteIp
if (!interfacesByServer[server2Name]) {
interfacesByServer[server2Name] = {
serverInfo: server2,
interfaces: []
};
}
interfacesByServer[server2Name].interfaces.push({
interfaceName: interfaceName2,
localIp: iface.remoteIp, // На втором сервере remoteIp первого становится localIp
remoteIp: iface.localIp, // На втором сервере localIp первого становится remoteIp
type: iface.type,
server2Name: server1Name,
server2Info: server1
}); });
}); });
Object.entries(interfacesByServer).forEach(([serverName, interfaces]) => { // Сортируем серверы по имени для консистентности
code += `\n# --- Команды для сервера: ${serverName} ---\n`; const sortedServers = Object.entries(interfacesByServer).sort(([a], [b]) => a.localeCompare(b));
interfaces.forEach(iface => {
code += `# Интерфейс: ${iface.interfaceName} (${iface.type}) - связь с ${iface.server2Name}\n`; sortedServers.forEach(([serverName, serverData], index) => {
code += `/ip address add address=${iface.ip}/32 interface="${iface.interfaceName}" comment="Interface: ${iface.interfaceName} (${iface.type}) to ${iface.server2Name}"\n`; const server = serverData.serverInfo;
// Отдельный блок для каждого сервера
code += '\n';
code += '# ' + '='.repeat(70) + '\n';
code += `# СЕРВЕР ${index + 1}: ${serverName}\n`;
if (server.provider) {
code += `# Провайдер: ${server.provider}\n`;
}
if (server.country) {
code += `# Страна: ${server.country}\n`;
}
if (server.ip && server.ip !== serverName) {
code += `# IP: ${server.ip}\n`;
}
code += '# ' + '='.repeat(70) + '\n';
code += '\n';
serverData.interfaces.forEach((iface, ifaceIndex) => {
code += `# Интерфейс ${ifaceIndex + 1}: ${iface.interfaceName} (${iface.type})\n`;
code += `# Связь с сервером: ${iface.server2Name}\n`;
code += `# Local IP: ${iface.localIp}\n`;
code += `# Remote IP: ${iface.remoteIp}\n`;
code += `/ip address add address=${iface.localIp}/32 interface="${iface.interfaceName}" comment="Interface: ${iface.interfaceName} (${iface.type}) to ${iface.server2Name}"\n`;
code += '\n';
}); });
code += '# Проверка настроенных адресов на этом сервере:\n';
code += '# /ip address print\n';
code += '\n';
}); });
code += '\n# Проверка настроенных адресов:\n'; code += '# ' + '='.repeat(70) + '\n';
code += '# /ip address print\n'; code += '# КОНЕЦ КОНФИГУРАЦИИ IP АДРЕСОВ ИНТЕРФЕЙСОВ\n';
code += '# ' + '='.repeat(70) + '\n';
return code; return code;
}; };
@@ -967,6 +1011,7 @@ function NetworkConfigManager() {
<IconServer size={12} className="text-muted" /> <IconServer size={12} className="text-muted" />
<span className="text-muted">Сервер 1:</span> <span className="text-muted">Сервер 1:</span>
<span>{server.dns || server.ip}</span> <span>{server.dns || server.ip}</span>
{iface.name && <span className="text-muted ms-1">({iface.name})</span>}
</div> </div>
)} )}
{server2 && ( {server2 && (
@@ -974,6 +1019,9 @@ function NetworkConfigManager() {
<IconServer size={12} className="text-muted" /> <IconServer size={12} className="text-muted" />
<span className="text-muted">Сервер 2:</span> <span className="text-muted">Сервер 2:</span>
<span>{server2.dns || server2.ip}</span> <span>{server2.dns || server2.ip}</span>
{(iface.name2 || iface.name) && (
<span className="text-muted ms-1">({iface.name2 || iface.name})</span>
)}
</div> </div>
)} )}
</div> </div>
@@ -1714,7 +1762,7 @@ function NetworkConfigManager() {
</div> </div>
<div className="col-md-6"> <div className="col-md-6">
<FormField <FormField
label="Имя интерфейса" label="Имя интерфейса (сервер 1)"
name="name" name="name"
value={editingInterface.name} value={editingInterface.name}
onChange={(val) => setEditingInterface({ ...editingInterface, name: val })} onChange={(val) => setEditingInterface({ ...editingInterface, name: val })}
@@ -1722,6 +1770,18 @@ function NetworkConfigManager() {
required required
/> />
</div> </div>
{editingInterface.serverId2 && (
<div className="col-md-6">
<FormField
label="Имя интерфейса (сервер 2)"
name="name2"
value={editingInterface.name2 || ''}
onChange={(val) => setEditingInterface({ ...editingInterface, name2: val })}
placeholder="gre-tunnel2"
/>
<div className="form-text">Если не указано, будет использовано имя с сервера 1</div>
</div>
)}
<div className="col-md-6"> <div className="col-md-6">
<FormField <FormField
label="Тип" label="Тип"