feat(NetworkConfigManager): implement code copying functionality for individual blocks and all generated MikroTik code; update server grouping display for improved clarity
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m39s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m39s
This commit is contained in:
@@ -751,6 +751,19 @@ function NetworkConfigManager() {
|
||||
}
|
||||
};
|
||||
|
||||
// === Копирование отдельного блока кода ===
|
||||
const handleCopyBlock = async (code) => {
|
||||
await handleCopyMikrotikCode(code);
|
||||
};
|
||||
|
||||
// === Копирование всего кода ===
|
||||
const handleCopyAllMikrotikCode = async () => {
|
||||
const fullCode = Array.isArray(generatedMikrotikCode) && generatedMikrotikCode.length > 0
|
||||
? generatedMikrotikCode.map(block => block.code).join('\n\n')
|
||||
: '';
|
||||
await handleCopyMikrotikCode(fullCode);
|
||||
};
|
||||
|
||||
// === Группировка блоков по серверам ===
|
||||
const groupedMikrotikCode = useMemo(() => {
|
||||
if (!Array.isArray(generatedMikrotikCode) || generatedMikrotikCode.length === 0) {
|
||||
@@ -759,7 +772,7 @@ function NetworkConfigManager() {
|
||||
|
||||
const grouped = {};
|
||||
generatedMikrotikCode.forEach(block => {
|
||||
const serverName = block.serverName || '__unknown__';
|
||||
const serverName = block.serverName || '__unassigned__';
|
||||
if (!grouped[serverName]) {
|
||||
grouped[serverName] = {
|
||||
server: block.server,
|
||||
@@ -2042,15 +2055,94 @@ function NetworkConfigManager() {
|
||||
Конфигурация MikroTik RouterOS для рекурсивных маршрутов и IP адресов интерфейсов
|
||||
</div>
|
||||
|
||||
{Array.isArray(generatedMikrotikCode) && generatedMikrotikCode.length > 0 ? (
|
||||
<div className="d-flex flex-column gap-3">
|
||||
{generatedMikrotikCode.map((block, index) => (
|
||||
<div key={index} className="bg-dark text-white p-3 rounded">
|
||||
<pre className="mb-0" style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
||||
<code className="text-white">{block.code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
))}
|
||||
{Object.keys(groupedMikrotikCode).length > 0 ? (
|
||||
<div className="d-flex flex-column gap-4">
|
||||
{Object.entries(groupedMikrotikCode)
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([serverName, serverData]) => {
|
||||
const server = serverData.server;
|
||||
const isUnassigned = serverName === '__unassigned__';
|
||||
|
||||
return (
|
||||
<div key={serverName} className="border rounded p-3">
|
||||
{/* Заголовок сервера */}
|
||||
<div className="d-flex align-items-center mb-3 pb-2 border-bottom">
|
||||
<div className="d-flex align-items-center flex-fill">
|
||||
{isUnassigned ? (
|
||||
<>
|
||||
<span className="avatar avatar-sm bg-secondary-lt me-2">
|
||||
<IconServer size={16} className="text-muted" />
|
||||
</span>
|
||||
<span className="text-muted">Без привязки к серверу</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="avatar avatar-sm bg-primary-lt me-2">
|
||||
<IconServer size={16} className="text-primary" />
|
||||
</span>
|
||||
<div>
|
||||
<div className="fw-bold">{serverName}</div>
|
||||
{server && (
|
||||
<div className="text-muted small">
|
||||
{server.provider && <span>{server.provider}</span>}
|
||||
{server.provider && server.country && <span className="mx-1">•</span>}
|
||||
{server.country && (
|
||||
<span>{countryToFlag(server.country)} {server.country}</span>
|
||||
)}
|
||||
{server.ip && server.ip !== serverName && (
|
||||
<>
|
||||
{(server.provider || server.country) && <span className="mx-1">•</span>}
|
||||
<span>{server.ip}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Блоки кода для этого сервера */}
|
||||
<div className="d-flex flex-column gap-3">
|
||||
{serverData.blocks.map((block, blockIndex) => {
|
||||
const blockTitle = block.type === 'recursive-routes'
|
||||
? 'Рекурсивные маршруты'
|
||||
: block.type === 'interface-addresses'
|
||||
? 'IP адреса интерфейсов'
|
||||
: 'Конфигурация';
|
||||
|
||||
return (
|
||||
<div key={blockIndex} className="border rounded overflow-hidden">
|
||||
{/* Заголовок блока */}
|
||||
<div className="bg-secondary-lt px-3 py-2 d-flex align-items-center justify-content-between">
|
||||
<div className="d-flex align-items-center">
|
||||
<IconCode size={18} className="text-secondary me-2" />
|
||||
<span className="fw-medium">{blockTitle}</span>
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
onClick={() => handleCopyBlock(block.code)}
|
||||
title="Копировать этот блок"
|
||||
>
|
||||
<IconCopy size={16} className="me-1" />
|
||||
Копировать
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Код блока */}
|
||||
<div className="bg-dark text-white p-3">
|
||||
<pre className="mb-0" style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontSize: '0.875rem' }}>
|
||||
<code className="text-white">{block.code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="alert alert-warning mb-0">
|
||||
@@ -2075,11 +2167,11 @@ function NetworkConfigManager() {
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={handleCopyMikrotikCode}
|
||||
onClick={handleCopyAllMikrotikCode}
|
||||
disabled={!Array.isArray(generatedMikrotikCode) || generatedMikrotikCode.length === 0}
|
||||
>
|
||||
<IconCopy className="me-2" />
|
||||
Копировать в буфер
|
||||
Копировать всё
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user