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

This commit is contained in:
2026-01-22 14:05:52 +07:00
parent 75a7778148
commit 0bd2a07dd2
+104 -12
View File
@@ -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(() => { const groupedMikrotikCode = useMemo(() => {
if (!Array.isArray(generatedMikrotikCode) || generatedMikrotikCode.length === 0) { if (!Array.isArray(generatedMikrotikCode) || generatedMikrotikCode.length === 0) {
@@ -759,7 +772,7 @@ function NetworkConfigManager() {
const grouped = {}; const grouped = {};
generatedMikrotikCode.forEach(block => { generatedMikrotikCode.forEach(block => {
const serverName = block.serverName || '__unknown__'; const serverName = block.serverName || '__unassigned__';
if (!grouped[serverName]) { if (!grouped[serverName]) {
grouped[serverName] = { grouped[serverName] = {
server: block.server, server: block.server,
@@ -2042,15 +2055,94 @@ function NetworkConfigManager() {
Конфигурация MikroTik RouterOS для рекурсивных маршрутов и IP адресов интерфейсов Конфигурация MikroTik RouterOS для рекурсивных маршрутов и IP адресов интерфейсов
</div> </div>
{Array.isArray(generatedMikrotikCode) && generatedMikrotikCode.length > 0 ? ( {Object.keys(groupedMikrotikCode).length > 0 ? (
<div className="d-flex flex-column gap-3"> <div className="d-flex flex-column gap-4">
{generatedMikrotikCode.map((block, index) => ( {Object.entries(groupedMikrotikCode)
<div key={index} className="bg-dark text-white p-3 rounded"> .sort(([a], [b]) => a.localeCompare(b))
<pre className="mb-0" style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}> .map(([serverName, serverData]) => {
<code className="text-white">{block.code}</code> const server = serverData.server;
</pre> const isUnassigned = serverName === '__unassigned__';
</div>
))} 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>
) : ( ) : (
<div className="alert alert-warning mb-0"> <div className="alert alert-warning mb-0">
@@ -2075,11 +2167,11 @@ function NetworkConfigManager() {
<button <button
type="button" type="button"
className="btn btn-primary" className="btn btn-primary"
onClick={handleCopyMikrotikCode} onClick={handleCopyAllMikrotikCode}
disabled={!Array.isArray(generatedMikrotikCode) || generatedMikrotikCode.length === 0} disabled={!Array.isArray(generatedMikrotikCode) || generatedMikrotikCode.length === 0}
> >
<IconCopy className="me-2" /> <IconCopy className="me-2" />
Копировать в буфер Копировать всё
</button> </button>
</div> </div>
</div> </div>