feat(NetworkConfigManager): refine tunnel preview functionality to support multiple server pairs; enhance IP address generation logic for better handling of local and remote IPs, ensuring uniqueness and proper display in the UI
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:
@@ -982,15 +982,13 @@ function NetworkConfigManager() {
|
||||
|
||||
// Проверяем, что IP входят в диапазон пула и отличаются
|
||||
if (!isIpInCidr(localIp, cidr)) continue;
|
||||
if (isIpInCidr(remoteIp, cidr) && localIp !== remoteIp) {
|
||||
if (prefix >= 30) {
|
||||
if (!usedIps.has(localIp) && !usedIps.has(remoteIp)) {
|
||||
return forRemote ? remoteIp : localIp;
|
||||
}
|
||||
} else {
|
||||
if (!usedIps.has(localIp)) {
|
||||
return localIp;
|
||||
}
|
||||
if (localIp === remoteIp) continue;
|
||||
|
||||
// Для всех префиксов проверяем, что оба IP свободны (для туннелей нужны пары)
|
||||
if (!usedIps.has(localIp) && !usedIps.has(remoteIp)) {
|
||||
// Проверяем, что Remote IP тоже входит в пул (для префиксов >= 30 это всегда true)
|
||||
if (prefix >= 30 || isIpInCidr(remoteIp, cidr)) {
|
||||
return forRemote ? remoteIp : localIp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3696,129 +3694,85 @@ function NetworkConfigManager() {
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
{templateServer1 && templateServer2 && templateServer1 !== templateServer2 && (() => {
|
||||
const server1 = getServerInfo(templateServer1);
|
||||
const server2 = getServerInfo(templateServer2);
|
||||
if (!server1 || !server2) return null;
|
||||
|
||||
const autoName1 = generateInterfaceName(server1, server2, templateType, templateNamePrefix);
|
||||
const autoName2 = generateInterfaceName(server2, server1, templateType, templateNamePrefix);
|
||||
const previewName1 = templateName1.trim() || autoName1;
|
||||
const previewName2 = templateName2.trim() || autoName2;
|
||||
const previewLocalIp = generateFreePrivateIp(null, false, null, templateIpPoolId || null);
|
||||
const previewRemoteIp = previewLocalIp ? generateFreePrivateIp(null, true, previewLocalIp, templateIpPoolId || null) : null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="col-md-6">
|
||||
<label className="form-label">
|
||||
Имя интерфейса (сервер 1)
|
||||
{!templateName1 && <span className="text-muted small ms-1">(авто: {autoName1})</span>}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={templateName1}
|
||||
onChange={(e) => setTemplateName1(e.target.value)}
|
||||
placeholder={autoName1}
|
||||
/>
|
||||
<div className="form-text">Оставьте пустым для автогенерации</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<label className="form-label">
|
||||
Имя интерфейса (сервер 2)
|
||||
{!templateName2 && <span className="text-muted small ms-1">(авто: {autoName2})</span>}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={templateName2}
|
||||
onChange={(e) => setTemplateName2(e.target.value)}
|
||||
placeholder={autoName2}
|
||||
/>
|
||||
<div className="form-text">Оставьте пустым для автогенерации </div>
|
||||
</div>
|
||||
{(templateType === 'IPSec' || templateType === 'GRE') && (
|
||||
<div className="col-12">
|
||||
<label className="form-label">IPSec пароль</label>
|
||||
<div className="input-group">
|
||||
<select
|
||||
className="form-select"
|
||||
value={templateIpsecPasswordId}
|
||||
onChange={(e) => setTemplateIpsecPasswordId(e.target.value)}
|
||||
>
|
||||
<option value="">Не выбран</option>
|
||||
{ipsecPasswords.map(pwd => (
|
||||
<option key={pwd.id} value={pwd.id}>
|
||||
{pwd.name} {pwd.description ? `(${pwd.description})` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-primary"
|
||||
onClick={() => {
|
||||
setIpsecPasswordModalMode('add');
|
||||
setEditingIpsecPassword({ name: '', password: '', description: '' });
|
||||
setIpsecPasswordModalOpen(true);
|
||||
}}
|
||||
title="Создать новый IPSec пароль"
|
||||
>
|
||||
<IconPlus size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="form-text">
|
||||
Выберите сохраненный IPSec пароль или создайте новый (опционально для GRE)
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="col-12">
|
||||
<label className="form-label">IP пул</label>
|
||||
<select
|
||||
className="form-select"
|
||||
value={templateIpPoolId}
|
||||
onChange={(e) => setTemplateIpPoolId(e.target.value)}
|
||||
>
|
||||
<option value="">Автоматический подбор (RFC 1918)</option>
|
||||
{config.ipPools?.map(pool => (
|
||||
<option key={pool.id} value={pool.id}>
|
||||
{pool.name} ({pool.cidr}) {pool.description ? `- ${pool.description}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="form-text">
|
||||
Выберите IP пул для подбора адресов или оставьте автоматический подбор
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="card card-sm border">
|
||||
<div className="card-body">
|
||||
<h6 className="card-title mb-3">Предпросмотр интерфейса:</h6>
|
||||
<div className="row g-2">
|
||||
<div className="col-md-6">
|
||||
<div className="small text-muted mb-1">Сервер 1 ({server1.dns || server1.ip}):</div>
|
||||
<div><strong>Имя:</strong> <code>{previewName1}</code></div>
|
||||
<div><strong>Local IP:</strong> <code>{previewLocalIp || '—'}</code></div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<div className="small text-muted mb-1">Сервер 2 ({server2.dns || server2.ip}):</div>
|
||||
<div><strong>Имя:</strong> <code>{previewName2}</code></div>
|
||||
<div><strong>Local IP:</strong> <code>{previewRemoteIp || '—'}</code></div>
|
||||
</div>
|
||||
{previewLocalIp && previewRemoteIp && (
|
||||
<div className="col-12 mt-2">
|
||||
<div className="badge bg-success-lt text-success">
|
||||
{previewLocalIp} ↔ {previewRemoteIp}
|
||||
{templateTunnelCount > 1 && templateServerPairs.length === templateTunnelCount && (
|
||||
<div className="col-12">
|
||||
<label className="form-label">Предпросмотр туннелей:</label>
|
||||
<div className="d-flex flex-column gap-3">
|
||||
{templateServerPairs.map((pair, index) => {
|
||||
const server1 = getServerInfo(pair.server1);
|
||||
const server2 = getServerInfo(pair.server2);
|
||||
if (!server1 || !server2 || !pair.server1 || !pair.server2 || pair.server1 === pair.server2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const baseName1 = generateInterfaceName(server1, server2, templateType, templateNamePrefix);
|
||||
const baseName2 = generateInterfaceName(server2, server1, templateType, templateNamePrefix);
|
||||
const previewName1 = pair.name1?.trim() || baseName1;
|
||||
const previewName2 = pair.name2?.trim() || baseName2;
|
||||
|
||||
// Используем настройки из пары или глобальные для предпросмотра
|
||||
const pairIpPoolId = pair.ipPoolId || templateIpPoolId;
|
||||
|
||||
// Генерируем предпросмотр IP (используем временный Set для предпросмотра)
|
||||
const previewUsedIps = new Set(getAllUsedIpsSet);
|
||||
// Добавляем IP из предыдущих туннелей в предпросмотре
|
||||
for (let i = 0; i < index; i++) {
|
||||
const prevPair = templateServerPairs[i];
|
||||
if (prevPair.server1 && prevPair.server2) {
|
||||
const prevPairIpPoolId = prevPair.ipPoolId || templateIpPoolId;
|
||||
const prevPreviewLocal = generateFreePrivateIp(null, false, null, prevPairIpPoolId || null, previewUsedIps);
|
||||
if (prevPreviewLocal) {
|
||||
const prevPreviewRemote = generateFreePrivateIp(null, true, prevPreviewLocal, prevPairIpPoolId || null, previewUsedIps);
|
||||
if (prevPreviewRemote && prevPreviewRemote !== prevPreviewLocal) {
|
||||
previewUsedIps.add(prevPreviewLocal);
|
||||
previewUsedIps.add(prevPreviewRemote);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const previewLocalIp = generateFreePrivateIp(null, false, null, pairIpPoolId || null, previewUsedIps);
|
||||
const previewRemoteIp = previewLocalIp ? generateFreePrivateIp(null, true, previewLocalIp, pairIpPoolId || null, previewUsedIps) : null;
|
||||
|
||||
return (
|
||||
<div key={index} className="card card-sm border">
|
||||
<div className="card-header bg-light">
|
||||
<strong>Туннель {index + 1}:</strong>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<div className="mb-2">
|
||||
<strong>Сервер 1 ({server1.dns || server1.ip}):</strong>
|
||||
<div className="ms-2">
|
||||
<div>Имя: {previewName1}</div>
|
||||
<div>Local IP: {previewLocalIp || '—'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="col-md-6">
|
||||
<div className="mb-2">
|
||||
<strong>Сервер 2 ({server2.dns || server2.ip}):</strong>
|
||||
<div className="ms-2">
|
||||
<div>Имя: {previewName2}</div>
|
||||
<div>Local IP: {previewRemoteIp || '—'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{previewLocalIp && previewRemoteIp && (
|
||||
<div className="col-12 text-center">
|
||||
<IconArrowsRightLeft size={20} className="text-muted" />
|
||||
<span className="ms-2 text-muted">{previewLocalIp} ↔ {previewRemoteIp}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</FormModal>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user