refactor(FilterManager): streamline server selection UI and enhance button actions for improved user experience
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m55s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m55s
This commit is contained in:
@@ -14,24 +14,18 @@ import {
|
|||||||
IconTrash,
|
IconTrash,
|
||||||
IconCheck,
|
IconCheck,
|
||||||
IconX,
|
IconX,
|
||||||
IconDatabase,
|
|
||||||
IconRefresh,
|
IconRefresh,
|
||||||
IconDownload,
|
|
||||||
IconUpload,
|
|
||||||
IconAlertTriangle,
|
IconAlertTriangle,
|
||||||
IconFilter,
|
IconFilter,
|
||||||
IconChevronDown,
|
IconChevronDown,
|
||||||
IconLink,
|
|
||||||
IconCopy,
|
IconCopy,
|
||||||
IconEye,
|
IconEye,
|
||||||
IconServer,
|
IconServer,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
IconDeviceFloppy,
|
IconDeviceFloppy,
|
||||||
IconHash,
|
IconHash,
|
||||||
IconRocket,
|
|
||||||
IconSortAscending,
|
IconSortAscending,
|
||||||
IconSortDescending,
|
IconSortDescending,
|
||||||
IconWorld,
|
|
||||||
IconFileText,
|
IconFileText,
|
||||||
IconGripVertical
|
IconGripVertical
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
@@ -53,15 +47,6 @@ import {
|
|||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { normalizeGateways, countryToFlag } from './utils/serverUtils.js';
|
import { normalizeGateways, countryToFlag } from './utils/serverUtils.js';
|
||||||
import { usePing } from './contexts/PingContext.jsx';
|
import { usePing } from './contexts/PingContext.jsx';
|
||||||
import {
|
|
||||||
AddFilterModal,
|
|
||||||
EditFilterModal,
|
|
||||||
DeleteFilterModal,
|
|
||||||
PreviewConfigModal,
|
|
||||||
AddFilterServerModal
|
|
||||||
} from './components/filter/index.js';
|
|
||||||
|
|
||||||
const API_URL = '/api';
|
|
||||||
|
|
||||||
// Компонент автокомплита для выбора Community
|
// Компонент автокомплита для выбора Community
|
||||||
function CommunityAutocomplete({ label, value, onChange, communities = [], required = false, placeholder = 'Введите или выберите community...', baseAS = '65001' }) {
|
function CommunityAutocomplete({ label, value, onChange, communities = [], required = false, placeholder = 'Введите или выберите community...', baseAS = '65001' }) {
|
||||||
@@ -1759,122 +1744,6 @@ function FilterManager() {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title="Управление фильтрами frouting"
|
title="Управление фильтрами frouting"
|
||||||
icon={<IconFilter size={24} />}
|
icon={<IconFilter size={24} />}
|
||||||
actions={
|
|
||||||
<div className="btn-list">
|
|
||||||
<div className="dropdown">
|
|
||||||
<button
|
|
||||||
className="btn btn-primary dropdown-toggle"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
<IconServer className="icon" />
|
|
||||||
{selectedServer ? selectedServer.name : 'Выбрать сервер'}
|
|
||||||
</button>
|
|
||||||
<div className="dropdown-menu dropdown-menu-end" style={{ minWidth: '300px' }}>
|
|
||||||
<div className="px-3 py-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control form-control-sm"
|
|
||||||
placeholder="Поиск серверов..."
|
|
||||||
value={serverSearch}
|
|
||||||
onChange={(e) => setServerSearch(e.target.value)}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="dropdown-divider"></div>
|
|
||||||
{activeServers.length > 0 && (
|
|
||||||
<>
|
|
||||||
<h6 className="dropdown-header">Активные серверы</h6>
|
|
||||||
{activeServers.map(server => (
|
|
||||||
<a
|
|
||||||
key={server.id}
|
|
||||||
className={`dropdown-item ${selectedServer?.id === server.id ? 'active' : ''}`}
|
|
||||||
href="#"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setSelectedServer(server);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="d-flex align-items-center">
|
|
||||||
<IconServer size={16} className="me-2" />
|
|
||||||
<span className="flex-grow-1">{server.name}</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{inactiveServers.length > 0 && (
|
|
||||||
<>
|
|
||||||
<div className="dropdown-divider"></div>
|
|
||||||
<h6 className="dropdown-header">Неактивные серверы</h6>
|
|
||||||
{inactiveServers.map(server => (
|
|
||||||
<a
|
|
||||||
key={server.id}
|
|
||||||
className={`dropdown-item ${selectedServer?.id === server.id ? 'active' : ''}`}
|
|
||||||
href="#"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setSelectedServer(server);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="d-flex align-items-center text-muted">
|
|
||||||
<IconServer size={16} className="me-2" />
|
|
||||||
<span className="flex-grow-1">{server.name}</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<div className="dropdown-divider"></div>
|
|
||||||
<button
|
|
||||||
className="dropdown-item"
|
|
||||||
onClick={() => setServerListModalOpen(true)}
|
|
||||||
>
|
|
||||||
<IconSettings size={16} className="me-2" />
|
|
||||||
Управление серверами
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{selectedServer && (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
className="btn btn-outline-primary"
|
|
||||||
onClick={handlePreviewConfig}
|
|
||||||
disabled={serverFilters.length === 0}
|
|
||||||
>
|
|
||||||
<IconEye className="icon" />
|
|
||||||
Просмотр
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-outline-primary"
|
|
||||||
onClick={copyConfigToClipboard}
|
|
||||||
disabled={serverFilters.length === 0}
|
|
||||||
>
|
|
||||||
<IconCopy className="icon" />
|
|
||||||
Копировать
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-success"
|
|
||||||
onClick={() => handleSaveServerConfig(selectedServer.id)}
|
|
||||||
disabled={loading || serverFilters.length === 0}
|
|
||||||
>
|
|
||||||
<IconDeviceFloppy className="icon" />
|
|
||||||
Сохранить
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-outline-primary"
|
|
||||||
onClick={handleBgpUpdate}
|
|
||||||
disabled={bgpUpdateLoading}
|
|
||||||
title="Запустить /system script run update_bgp_filter на выбранном MikroTik"
|
|
||||||
>
|
|
||||||
<IconRefresh className={`icon ${bgpUpdateLoading ? 'spin' : ''}`} />
|
|
||||||
{bgpUpdateLoading ? 'Запуск...' : 'Обновить BGP фильтры'}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="page-body">
|
<div className="page-body">
|
||||||
@@ -1977,6 +1846,15 @@ function FilterManager() {
|
|||||||
<IconDeviceFloppy className="icon" />
|
<IconDeviceFloppy className="icon" />
|
||||||
Сохранить
|
Сохранить
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-outline-primary"
|
||||||
|
onClick={handleBgpUpdate}
|
||||||
|
disabled={!selectedServer || bgpUpdateLoading}
|
||||||
|
title="Запустить /system script run update_bgp_filter на выбранном MikroTik"
|
||||||
|
>
|
||||||
|
<IconRefresh className={`icon ${bgpUpdateLoading ? 'spin' : ''}`} />
|
||||||
|
{bgpUpdateLoading ? 'Запуск...' : 'Обновить BGP фильтры'}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user