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