refactor(MikrotikBackupsManager): optimize diff computation and synchronize scrolling between panels
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 3m25s
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 3m25s
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import * as Diff from 'diff';
|
import * as Diff from 'diff';
|
||||||
import api from './lib/api.js';
|
import api from './lib/api.js';
|
||||||
import { useNotify } from './components/NotifyProvider.jsx';
|
import { useNotify } from './components/NotifyProvider.jsx';
|
||||||
@@ -75,9 +75,9 @@ const diffAddedBg = {
|
|||||||
borderLeft: '3px solid rgba(63, 185, 80, 0.9)',
|
borderLeft: '3px solid rgba(63, 185, 80, 0.9)',
|
||||||
};
|
};
|
||||||
|
|
||||||
function DiffPanel({ lines, side }) {
|
function DiffPanel({ lines, side, innerRef, onScroll }) {
|
||||||
return (
|
return (
|
||||||
<pre style={diffPanelStyle}>
|
<pre style={diffPanelStyle} ref={innerRef} onScroll={onScroll}>
|
||||||
{lines.length === 0 ? (
|
{lines.length === 0 ? (
|
||||||
<span style={diffLineStyle}># пусто</span>
|
<span style={diffLineStyle}># пусто</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -141,6 +141,9 @@ function MikrotikBackupsManager() {
|
|||||||
const [diffResult, setDiffResult] = useState(null);
|
const [diffResult, setDiffResult] = useState(null);
|
||||||
const [diffLoading, setDiffLoading] = useState(false);
|
const [diffLoading, setDiffLoading] = useState(false);
|
||||||
const [diffModalOpen, setDiffModalOpen] = useState(false);
|
const [diffModalOpen, setDiffModalOpen] = useState(false);
|
||||||
|
const leftDiffRef = useRef(null);
|
||||||
|
const rightDiffRef = useRef(null);
|
||||||
|
const isSyncingScrollRef = useRef(false);
|
||||||
|
|
||||||
// UI-состояние выбора сервера для просмотра бэкапов (в стиле /filters)
|
// UI-состояние выбора сервера для просмотра бэкапов (в стиле /filters)
|
||||||
const [serverSearch, setServerSearch] = useState('');
|
const [serverSearch, setServerSearch] = useState('');
|
||||||
@@ -254,12 +257,41 @@ function MikrotikBackupsManager() {
|
|||||||
return filteredBackupServers.slice(start, start + backupPageSize);
|
return filteredBackupServers.slice(start, start + backupPageSize);
|
||||||
}, [filteredBackupServers, backupPage, backupTotalPages]);
|
}, [filteredBackupServers, backupPage, backupTotalPages]);
|
||||||
|
|
||||||
const diffLines = useMemo(
|
const diffComputed = useMemo(() => {
|
||||||
() => (diffResult?.configA != null && diffResult?.configB != null
|
if (diffResult?.configA == null || diffResult?.configB == null) {
|
||||||
? getDiffLines(diffResult.configA, diffResult.configB)
|
return {
|
||||||
: { left: [], right: [] }),
|
left: [],
|
||||||
[diffResult?.configA, diffResult?.configB],
|
right: [],
|
||||||
);
|
stats: { removed: 0, added: 0, total: 0 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const { left, right } = getDiffLines(diffResult.configA, diffResult.configB);
|
||||||
|
const removed = left.filter((l) => l.type === 'removed').length;
|
||||||
|
const added = right.filter((l) => l.type === 'added').length;
|
||||||
|
return {
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
stats: { removed, added, total: removed + added },
|
||||||
|
};
|
||||||
|
}, [diffResult?.configA, diffResult?.configB]);
|
||||||
|
|
||||||
|
const syncScroll = (source) => {
|
||||||
|
const leftEl = leftDiffRef.current;
|
||||||
|
const rightEl = rightDiffRef.current;
|
||||||
|
if (!leftEl || !rightEl) return;
|
||||||
|
const current = source === 'left' ? leftEl : rightEl;
|
||||||
|
const other = source === 'left' ? rightEl : leftEl;
|
||||||
|
|
||||||
|
if (isSyncingScrollRef.current) return;
|
||||||
|
isSyncingScrollRef.current = true;
|
||||||
|
|
||||||
|
const maxCurrent = current.scrollHeight - current.clientHeight;
|
||||||
|
const ratio = maxCurrent > 0 ? current.scrollTop / maxCurrent : 0;
|
||||||
|
const maxOther = other.scrollHeight - other.clientHeight;
|
||||||
|
other.scrollTop = ratio * (maxOther > 0 ? maxOther : 0);
|
||||||
|
|
||||||
|
isSyncingScrollRef.current = false;
|
||||||
|
};
|
||||||
|
|
||||||
const handleBackupSort = (field) => {
|
const handleBackupSort = (field) => {
|
||||||
setBackupSortField((prevField) => {
|
setBackupSortField((prevField) => {
|
||||||
@@ -891,20 +923,34 @@ function MikrotikBackupsManager() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{!diffResult.same && (
|
<div className="alert alert-info mb-3 small d-flex justify-content-between align-items-center">
|
||||||
<div className="alert alert-info mb-3 small">
|
<span>
|
||||||
<IconAlertTriangle size={16} className="me-1 text-warning" />
|
{!diffResult.same ? (
|
||||||
Конфигурации отличаются — слева A, справа B. Проверьте различия перед откатом.
|
<>
|
||||||
</div>
|
<IconAlertTriangle size={16} className="me-1 text-warning" />
|
||||||
)}
|
Конфигурации отличаются — слева A, справа B. Проверьте различия перед откатом.
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'Конфигурации идентичны — изменений нет.'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Всего изменений:{' '}
|
||||||
|
<span className="fw-semibold">{diffComputed.stats.total}</span>
|
||||||
|
<span className="text-success ms-2">+{diffComputed.stats.added}</span>
|
||||||
|
<span className="text-danger ms-2">−{diffComputed.stats.removed}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-6 mb-3 mb-md-0">
|
<div className="col-md-6 mb-3 mb-md-0">
|
||||||
<div className="mb-2 small text-muted text-truncate" title={diffResult.a?.key}>
|
<div className="mb-2 small text-muted text-truncate" title={diffResult.a?.key}>
|
||||||
A: {diffResult.a?.key}
|
A: {diffResult.a?.key}
|
||||||
</div>
|
</div>
|
||||||
<DiffPanel
|
<DiffPanel
|
||||||
lines={diffLines.left}
|
lines={diffComputed.left}
|
||||||
side="left"
|
side="left"
|
||||||
|
innerRef={leftDiffRef}
|
||||||
|
onScroll={() => syncScroll('left')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
@@ -912,8 +958,10 @@ function MikrotikBackupsManager() {
|
|||||||
B: {diffResult.b?.key}
|
B: {diffResult.b?.key}
|
||||||
</div>
|
</div>
|
||||||
<DiffPanel
|
<DiffPanel
|
||||||
lines={diffLines.right}
|
lines={diffComputed.right}
|
||||||
side="right"
|
side="right"
|
||||||
|
innerRef={rightDiffRef}
|
||||||
|
onScroll={() => syncScroll('right')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user