refactor(Managers): integrate LastSaved component and optimize button layout for enhanced UI consistency and user experience
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m2s

This commit is contained in:
2026-02-16 17:22:48 +07:00
parent cfcb44d5f0
commit eca744999c
2 changed files with 89 additions and 86 deletions
+41 -41
View File
@@ -42,6 +42,7 @@ import Tooltip from './components/Tooltip.jsx';
import Pagination from './components/Pagination.jsx';
import AddItemModal from './components/AddItemModal.jsx';
import { getAsnName, getAsnNameSync } from './lib/asn.js';
import LastSaved from './components/LastSaved.jsx';
const API_URL = '/api';
@@ -523,47 +524,39 @@ function ASNsNewManager() {
<PageHeader
title="ASNs"
meta={<LastSaved timestamp={lastModified} variant="compact" />}
actions={(
<div className="d-flex align-items-center gap-2">
<button
className="btn btn-primary"
onClick={() => setAddModalOpen(true)}
title="Добавить новый ASN"
>
<IconPlus className="me-1" size={18} />
Добавить
</button>
<PageHeaderActions
loading={loading}
onPreview={handlePreviewDiff}
disablePreview={loading || items.length === 0}
onRefresh={fetchItems}
disableRefresh={loading}
onImport={handleImport}
onExport={handleExport}
disableExport={items.length === 0}
onClear={clearInvalid}
disableClear={items.length === 0}
onClearCommunities={() => { if (!filterCommunity) { setError('Выберите community в фильтре для очистки'); return; } setClearCommunitiesOpen(true); }}
disableClearCommunities={items.length === 0 || !filterCommunity}
onSave={handleSaveChanges}
disableSave={loading}
onHistory={() => setHistoryOpen(true)}
onOnlineUpdate={() => setWsOpen(true)}
onBackgroundUpdate={async () => {
try {
const res = await fetch('/api/update-bgp/background', { method: 'POST' });
const data = await res.json().catch(() => ({}));
if (!res.ok || data.ok === false) {
throw new Error(data?.message || `HTTP ${res.status}`);
}
notifyMutationSuccess('Фоновое обновление запущено', data);
} catch (e) {
window.notify?.error('Не удалось запустить фоновое обновление', String(e));
<PageHeaderActions
loading={loading}
onPreview={handlePreviewDiff}
disablePreview={loading || items.length === 0}
onRefresh={fetchItems}
disableRefresh={loading}
onImport={handleImport}
onExport={handleExport}
disableExport={items.length === 0}
onClear={clearInvalid}
disableClear={items.length === 0}
onClearCommunities={() => { if (!filterCommunity) { setError('Выберите community в фильтре для очистки'); return; } setClearCommunitiesOpen(true); }}
disableClearCommunities={items.length === 0 || !filterCommunity}
onSave={handleSaveChanges}
disableSave={loading}
onHistory={() => setHistoryOpen(true)}
lastModified={lastModified}
onOnlineUpdate={() => setWsOpen(true)}
onBackgroundUpdate={async () => {
try {
const res = await fetch('/api/update-bgp/background', { method: 'POST' });
const data = await res.json().catch(() => ({}));
if (!res.ok || data.ok === false) {
throw new Error(data?.message || `HTTP ${res.status}`);
}
}}
/>
</div>
notifyMutationSuccess('Фоновое обновление запущено', data);
} catch (e) {
window.notify?.error('Не удалось запустить фоновое обновление', String(e));
}
}}
/>
)}
/>
@@ -595,7 +588,7 @@ function ASNsNewManager() {
<div className="card">
<div className="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
<h3 className="card-title mb-0">Список ASN <span className="badge bg-blue-lt text-blue ms-2">{items.length}</span></h3>
<div className="d-flex gap-2 flex-wrap">
<div className="d-flex gap-2 flex-wrap align-items-center">
<SavedFilters
pageKey="asns"
currentFilters={{ searchTerm, filterCommunity, sortField, sortOrder }}
@@ -663,8 +656,15 @@ function ASNsNewManager() {
</button>
)}
</div>
<button
className="btn btn-primary"
onClick={() => setAddModalOpen(true)}
title="Добавить новый ASN"
>
<IconPlus className="me-1" size={18} />
Добавить
</button>
</div>
<div></div>
</div>
</div>
+48 -45
View File
@@ -43,6 +43,7 @@ import BulkActionsBar from './components/BulkActionsBar.jsx';
import Tooltip from './components/Tooltip.jsx';
import Pagination from './components/Pagination.jsx';
import AddItemModal from './components/AddItemModal.jsx';
import LastSaved from './components/LastSaved.jsx';
const API_URL = '/api';
@@ -783,47 +784,39 @@ function IPRangesManager() {
<PageHeader
title="IP-диапазоны"
meta={<LastSaved timestamp={lastModified} variant="compact" />}
actions={(
<div className="d-flex align-items-center gap-2">
<button
className="btn btn-primary"
onClick={() => setAddModalOpen(true)}
title="Добавить новый IP-диапазон"
>
<IconPlus className="me-1" size={18} />
Добавить
</button>
<PageHeaderActions
loading={loading}
onPreview={handlePreviewDiff}
disablePreview={loading || items.length === 0}
onRefresh={fetchItems}
disableRefresh={loading}
onImport={handleImport}
onExport={handleExport}
disableExport={items.length === 0}
onClear={clearInvalid}
disableClear={items.length === 0}
onClearCommunities={() => { if (!filterCommunity) { setError('Выберите community в фильтре для очистки'); return; } setClearCommunitiesOpen(true); }}
disableClearCommunities={items.length === 0 || !filterCommunity}
onSave={handleSaveChanges}
disableSave={loading}
onHistory={() => setHistoryOpen(true)}
onOnlineUpdate={() => setWsOpen(true)}
onBackgroundUpdate={async () => {
try {
const res = await fetch('/api/update-bgp/background', { method: 'POST' });
const data = await res.json().catch(() => ({}));
if (!res.ok || data.ok === false) {
throw new Error(data?.message || `HTTP ${res.status}`);
}
notifyMutationSuccess('Фоновое обновление запущено', data);
} catch (e) {
window.notify?.error('Не удалось запустить фоновое обновление', String(e));
<PageHeaderActions
loading={loading}
onPreview={handlePreviewDiff}
disablePreview={loading || items.length === 0}
onRefresh={fetchItems}
disableRefresh={loading}
onImport={handleImport}
onExport={handleExport}
disableExport={items.length === 0}
onClear={clearInvalid}
disableClear={items.length === 0}
onClearCommunities={() => { if (!filterCommunity) { setError('Выберите community в фильтре для очистки'); return; } setClearCommunitiesOpen(true); }}
disableClearCommunities={items.length === 0 || !filterCommunity}
onSave={handleSaveChanges}
disableSave={loading}
onHistory={() => setHistoryOpen(true)}
lastModified={lastModified}
onOnlineUpdate={() => setWsOpen(true)}
onBackgroundUpdate={async () => {
try {
const res = await fetch('/api/update-bgp/background', { method: 'POST' });
const data = await res.json().catch(() => ({}));
if (!res.ok || data.ok === false) {
throw new Error(data?.message || `HTTP ${res.status}`);
}
}}
/>
</div>
notifyMutationSuccess('Фоновое обновление запущено', data);
} catch (e) {
window.notify?.error('Не удалось запустить фоновое обновление', String(e));
}
}}
/>
)}
/>
@@ -855,7 +848,7 @@ function IPRangesManager() {
<div className="card">
<div className="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
<h3 className="card-title mb-0">Список IP-диапазонов <span className="badge bg-blue-lt text-blue ms-2">{items.length}</span></h3>
<div className="d-flex gap-2 flex-wrap">
<div className="d-flex gap-2 flex-wrap align-items-center">
<SavedFilters
pageKey="ip-ranges"
currentFilters={{ searchTerm, filterCommunity, sortField, sortOrder }}
@@ -907,12 +900,22 @@ function IPRangesManager() {
/>
</div>
</div>
<Tooltip content="Анализ и суммаризация IP-диапазонов">
<button className="btn btn-outline-primary" onClick={() => setAnalyzeOpen(true)}>
<IconDatabase className="icon me-2" />
Анализ
<div className="d-flex gap-2">
<Tooltip content="Анализ и суммаризация IP-диапазонов">
<button className="btn btn-outline-primary" onClick={() => setAnalyzeOpen(true)}>
<IconDatabase className="icon me-2" />
Анализ
</button>
</Tooltip>
<button
className="btn btn-primary"
onClick={() => setAddModalOpen(true)}
title="Добавить новый IP-диапазон"
>
<IconPlus className="me-1" size={18} />
Добавить
</button>
</Tooltip>
</div>
</div>
</div>
</div>