feat(MikrotikBackupsManager): add side-by-side diff functionality for configuration comparison
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 2m47s
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 2m47s
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import * as Diff from 'diff';
|
||||
import api from './lib/api.js';
|
||||
import { useNotify } from './components/NotifyProvider.jsx';
|
||||
import PageHeader from './components/PageHeader.jsx';
|
||||
@@ -17,6 +18,77 @@ import {
|
||||
IconSearch,
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
/**
|
||||
* Строит две колонки строк для side-by-side diff (как git diff).
|
||||
* leftLines/rightLines — массивы { text, type: 'unchanged'|'removed'|'added'|'empty' }.
|
||||
*/
|
||||
function getDiffLines(oldStr, newStr) {
|
||||
const left = [];
|
||||
const right = [];
|
||||
const changes = Diff.diffLines(oldStr || '', newStr || '');
|
||||
for (const part of changes) {
|
||||
const lines = (part.value || '').split('\n');
|
||||
if (part.value && part.value.endsWith('\n') && lines.length > 0) lines.pop();
|
||||
for (const line of lines) {
|
||||
if (part.added) {
|
||||
left.push({ text: '', type: 'empty' });
|
||||
right.push({ text: line, type: 'added' });
|
||||
} else if (part.removed) {
|
||||
left.push({ text: line, type: 'removed' });
|
||||
right.push({ text: '', type: 'empty' });
|
||||
} else {
|
||||
left.push({ text: line, type: 'unchanged' });
|
||||
right.push({ text: line, type: 'unchanged' });
|
||||
}
|
||||
}
|
||||
}
|
||||
return { left, right };
|
||||
}
|
||||
|
||||
const diffPanelStyle = {
|
||||
maxHeight: 360,
|
||||
overflow: 'auto',
|
||||
fontSize: 12,
|
||||
fontFamily: 'ui-monospace, monospace',
|
||||
margin: 0,
|
||||
padding: '0.5rem 0.75rem',
|
||||
borderRadius: 4,
|
||||
backgroundColor: 'var(--tblr-bg-surface-secondary, #1e293b)',
|
||||
};
|
||||
const diffLineStyle = {
|
||||
display: 'block',
|
||||
minHeight: '1.25em',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-all',
|
||||
padding: '0 2px',
|
||||
margin: 0,
|
||||
};
|
||||
const diffRemovedBg = { background: 'rgba(248, 81, 73, 0.25)' };
|
||||
const diffAddedBg = { background: 'rgba(63, 185, 80, 0.25)' };
|
||||
|
||||
function DiffPanel({ lines, side }) {
|
||||
return (
|
||||
<pre style={diffPanelStyle}>
|
||||
{lines.length === 0 ? (
|
||||
<span style={diffLineStyle}># пусто</span>
|
||||
) : (
|
||||
lines.map((item, i) => (
|
||||
<span
|
||||
key={i}
|
||||
style={{
|
||||
...diffLineStyle,
|
||||
...(item.type === 'removed' && side === 'left' ? diffRemovedBg : null),
|
||||
...(item.type === 'added' && side === 'right' ? diffAddedBg : null),
|
||||
}}
|
||||
>
|
||||
{item.type === 'empty' ? '\u00A0' : item.text || ''}
|
||||
</span>
|
||||
))
|
||||
)}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return '';
|
||||
try {
|
||||
@@ -172,6 +244,13 @@ function MikrotikBackupsManager() {
|
||||
return filteredBackupServers.slice(start, start + backupPageSize);
|
||||
}, [filteredBackupServers, backupPage, backupTotalPages]);
|
||||
|
||||
const diffLines = useMemo(
|
||||
() => (diffResult?.configA != null && diffResult?.configB != null
|
||||
? getDiffLines(diffResult.configA, diffResult.configB)
|
||||
: { left: [], right: [] }),
|
||||
[diffResult?.configA, diffResult?.configB],
|
||||
);
|
||||
|
||||
const handleBackupSort = (field) => {
|
||||
setBackupSortField((prevField) => {
|
||||
if (prevField === field) {
|
||||
@@ -780,6 +859,13 @@ function MikrotikBackupsManager() {
|
||||
{diffResult.same
|
||||
? 'Конфигурации идентичны'
|
||||
: 'Конфигурации отличаются — проверьте различия перед откатом.'}
|
||||
{!diffResult.same && (
|
||||
<span className="ms-2">
|
||||
<span style={{ background: 'rgba(248, 81, 73, 0.35)', padding: '0 4px', borderRadius: 2 }}> удалено (A)</span>
|
||||
{' · '}
|
||||
<span style={{ background: 'rgba(63, 185, 80, 0.35)', padding: '0 4px', borderRadius: 2 }}> добавлено (B)</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -792,23 +878,19 @@ function MikrotikBackupsManager() {
|
||||
<div className="mb-2 small text-muted text-truncate" title={diffResult.a?.key}>
|
||||
A: {diffResult.a?.key}
|
||||
</div>
|
||||
<pre
|
||||
className="mb-0"
|
||||
style={{ maxHeight: 360, overflow: 'auto', fontSize: 12 }}
|
||||
>
|
||||
{diffResult.configA || '# пусто'}
|
||||
</pre>
|
||||
<DiffPanel
|
||||
lines={diffLines.left}
|
||||
side="left"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<div className="mb-2 small text-muted text-truncate" title={diffResult.b?.key}>
|
||||
B: {diffResult.b?.key}
|
||||
</div>
|
||||
<pre
|
||||
className="mb-0"
|
||||
style={{ maxHeight: 360, overflow: 'auto', fontSize: 12 }}
|
||||
>
|
||||
{diffResult.configB || '# пусто'}
|
||||
</pre>
|
||||
<DiffPanel
|
||||
lines={diffLines.right}
|
||||
side="right"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user