feat: Introduce cursor utility class for pointer interactions and refactor action buttons into PageHeaderActions component across multiple managers for improved UI consistency
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m54s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m54s
This commit is contained in:
@@ -15,11 +15,13 @@ import {
|
||||
IconDeviceFloppy,
|
||||
IconHash,
|
||||
IconClock,
|
||||
IconFileText
|
||||
IconFileText,
|
||||
IconEye
|
||||
} from '@tabler/icons-react';
|
||||
import LockBanner from './components/LockBanner.jsx';
|
||||
import TableSkeleton from './components/TableSkeleton.jsx';
|
||||
import S3MetaBar from './components/S3MetaBar.jsx';
|
||||
import PageHeaderActions from './components/PageHeaderActions.jsx';
|
||||
import ConfirmDiffModal from './components/ConfirmDiffModal.jsx';
|
||||
import HistoryModal from './components/HistoryModal.jsx';
|
||||
|
||||
@@ -393,9 +395,21 @@ function ASNsNewManager() {
|
||||
<div className="page-pretitle">Главная / Данные / ASNs</div>
|
||||
</div>
|
||||
<div className="col-auto ms-auto d-print-none">
|
||||
<div className="btn-list">
|
||||
<button className="btn btn-outline-secondary" onClick={() => setHistoryOpen(true)}>История</button>
|
||||
</div>
|
||||
<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}
|
||||
onSave={handleSaveChanges}
|
||||
disableSave={loading}
|
||||
onHistory={() => setHistoryOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -467,73 +481,7 @@ function ASNsNewManager() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions Card */}
|
||||
<div className="card card-md">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title">
|
||||
<IconDatabase className="icon me-2" />
|
||||
Действия
|
||||
</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="d-grid gap-2">
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={handleSaveChanges}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<span className="spinner-border spinner-border-sm me-2" role="status"></span>
|
||||
Сохранение...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<IconDeviceFloppy className="icon me-2" />
|
||||
Сохранить в S3
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={fetchItems}
|
||||
disabled={loading}
|
||||
>
|
||||
<IconRefresh className="icon me-2" />
|
||||
Обновить
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-primary"
|
||||
onClick={handleImport}
|
||||
>
|
||||
<IconUpload className="icon me-2" />
|
||||
Импорт из буфера
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-primary"
|
||||
onClick={handleExport}
|
||||
disabled={items.length === 0}
|
||||
>
|
||||
<IconDownload className="icon me-2" />
|
||||
Экспорт CSV
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={handlePreviewDiff}
|
||||
disabled={loading}
|
||||
>
|
||||
Предпросмотр изменений
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={clearInvalid}
|
||||
disabled={items.length === 0}
|
||||
>
|
||||
Очистить пустые/невалидные
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Карточка действий больше не нужна — действия перенесены в page-header */}
|
||||
</div>
|
||||
|
||||
<div className="col-lg-9">
|
||||
@@ -572,7 +520,7 @@ function ASNsNewManager() {
|
||||
<table className="table card-table table-vcenter table-nowrap mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('asn')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('asn')}>
|
||||
Номер AS
|
||||
{sortField === 'asn' && (
|
||||
<span className="ms-1">
|
||||
@@ -580,7 +528,7 @@ function ASNsNewManager() {
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('community')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('community')}>
|
||||
Community
|
||||
{sortField === 'community' && (
|
||||
<span className="ms-1">
|
||||
|
||||
@@ -219,6 +219,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Utility: pointer cursor */
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Custom responsive adjustments */
|
||||
@media (max-width: 992px) {
|
||||
.page-wrapper {
|
||||
|
||||
@@ -683,64 +683,43 @@ function BillingManager() {
|
||||
<table className="table card-table table-vcenter table-nowrap mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleSort('hostName')}
|
||||
>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('hostName')}>
|
||||
Имя хостера
|
||||
{sortField === 'hostName' && (
|
||||
<span className="ms-1">{sortOrder === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</th>
|
||||
<th
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleSort('purpose')}
|
||||
>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('purpose')}>
|
||||
Назначение
|
||||
{sortField === 'purpose' && (
|
||||
<span className="ms-1">{sortOrder === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</th>
|
||||
<th
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleSort('country')}
|
||||
>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('country')}>
|
||||
Страна
|
||||
{sortField === 'country' && (
|
||||
<span className="ms-1">{sortOrder === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</th>
|
||||
<th
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleSort('provider')}
|
||||
>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('provider')}>
|
||||
Провайдер
|
||||
{sortField === 'provider' && (
|
||||
<span className="ms-1">{sortOrder === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</th>
|
||||
<th
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleSort('monthlyCost')}
|
||||
>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('monthlyCost')}>
|
||||
Месячная стоимость
|
||||
{sortField === 'monthlyCost' && (
|
||||
<span className="ms-1">{sortOrder === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</th>
|
||||
<th
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleSort('nextPaymentDate')}
|
||||
>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('nextPaymentDate')}>
|
||||
Следующий платеж
|
||||
{sortField === 'nextPaymentDate' && (
|
||||
<span className="ms-1">{sortOrder === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</th>
|
||||
<th
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleSort('status')}
|
||||
>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('status')}>
|
||||
Статус
|
||||
{sortField === 'status' && (
|
||||
<span className="ms-1">{sortOrder === 'asc' ? '↑' : '↓'}</span>
|
||||
|
||||
@@ -260,8 +260,8 @@ function CommunitiesManager() {
|
||||
<table className="table card-table table-vcenter table-nowrap mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{cursor:'pointer'}} onClick={()=>changeSort('value')}>Value {sortField==='value' && <span className="ms-1">{sortOrder==='asc'?'▲':'▼'}</span>}</th>
|
||||
<th style={{cursor:'pointer'}} onClick={()=>changeSort('name')}>Название {sortField==='name' && <span className="ms-1">{sortOrder==='asc'?'▲':'▼'}</span>}</th>
|
||||
<th className="cursor-pointer" onClick={()=>changeSort('value')}>Value {sortField==='value' && <span className="ms-1">{sortOrder==='asc'?'▲':'▼'}</span>}</th>
|
||||
<th className="cursor-pointer" onClick={()=>changeSort('name')}>Название {sortField==='name' && <span className="ms-1">{sortOrder==='asc'?'▲':'▼'}</span>}</th>
|
||||
<th>Описание</th>
|
||||
<th>Теги</th>
|
||||
<th>Gateway</th>
|
||||
|
||||
@@ -364,7 +364,7 @@ function DataManager({ entityName, entityKey, placeholder }) {
|
||||
<table className="table card-table table-vcenter table-nowrap mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('domain')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('domain')}>
|
||||
Домен
|
||||
{sortField === 'domain' && (
|
||||
<span className="ms-1">
|
||||
@@ -372,7 +372,7 @@ function DataManager({ entityName, entityKey, placeholder }) {
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('type')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('type')}>
|
||||
Шлюз
|
||||
{sortField === 'type' && (
|
||||
<span className="ms-1">
|
||||
|
||||
@@ -15,11 +15,13 @@ import {
|
||||
IconDeviceFloppy,
|
||||
IconHash,
|
||||
IconClock,
|
||||
IconFileText
|
||||
IconFileText,
|
||||
IconEye
|
||||
} from '@tabler/icons-react';
|
||||
import LockBanner from './components/LockBanner.jsx';
|
||||
import TableSkeleton from './components/TableSkeleton.jsx';
|
||||
import S3MetaBar from './components/S3MetaBar.jsx';
|
||||
import PageHeaderActions from './components/PageHeaderActions.jsx';
|
||||
import ConfirmDiffModal from './components/ConfirmDiffModal.jsx';
|
||||
import HistoryModal from './components/HistoryModal.jsx';
|
||||
|
||||
@@ -400,9 +402,21 @@ function DomainsNewManager() {
|
||||
<div className="page-pretitle">Главная / Данные / Домены</div>
|
||||
</div>
|
||||
<div className="col-auto ms-auto d-print-none">
|
||||
<div className="btn-list">
|
||||
<button className="btn btn-outline-secondary" onClick={() => setHistoryOpen(true)}>История</button>
|
||||
</div>
|
||||
<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}
|
||||
onSave={handleSaveChanges}
|
||||
disableSave={loading}
|
||||
onHistory={() => setHistoryOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -465,73 +479,7 @@ function DomainsNewManager() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions Card */}
|
||||
<div className="card card-md">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title">
|
||||
<IconDatabase className="icon me-2" />
|
||||
Действия
|
||||
</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="d-grid gap-2">
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={handleSaveChanges}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<span className="spinner-border spinner-border-sm me-2" role="status"></span>
|
||||
Сохранение...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<IconDeviceFloppy className="icon me-2" />
|
||||
Сохранить в S3
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={handlePreviewDiff}
|
||||
disabled={loading}
|
||||
>
|
||||
Предпросмотр изменений
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={fetchItems}
|
||||
disabled={loading}
|
||||
>
|
||||
<IconRefresh className="icon me-2" />
|
||||
Обновить
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-primary"
|
||||
onClick={handleImport}
|
||||
>
|
||||
<IconUpload className="icon me-2" />
|
||||
Импорт из буфера
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-primary"
|
||||
onClick={handleExport}
|
||||
disabled={items.length === 0}
|
||||
>
|
||||
<IconDownload className="icon me-2" />
|
||||
Экспорт CSV
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={clearInvalid}
|
||||
disabled={items.length === 0}
|
||||
>
|
||||
Очистить пустые/невалидные
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Карточка действий больше не нужна — действия перенесены в page-header */}
|
||||
{/* Drag & Drop импорт */}
|
||||
<div
|
||||
className="card card-md border-dashed"
|
||||
@@ -580,7 +528,7 @@ function DomainsNewManager() {
|
||||
<table className="table card-table table-vcenter table-nowrap mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('domain')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('domain')}>
|
||||
Домен
|
||||
{sortField === 'domain' && (
|
||||
<span className="ms-1">
|
||||
@@ -588,7 +536,7 @@ function DomainsNewManager() {
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('community')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('community')}>
|
||||
Community
|
||||
{sortField === 'community' && (
|
||||
<span className="ms-1">
|
||||
|
||||
@@ -1218,15 +1218,15 @@ function FilterManager() {
|
||||
onChange={toggleSelectAllProcessed}
|
||||
/>
|
||||
</th>
|
||||
<th style={{ width: '25%', cursor: 'pointer' }} onClick={() => toggleSort('community')}>
|
||||
<th style={{ width: '25%' }} className="cursor-pointer" onClick={() => toggleSort('community')}>
|
||||
<span className="me-1">COMMUNITY</span>
|
||||
{sortBy === 'community' && (sortDir === 'asc' ? <IconSortAscending size={16} /> : <IconSortDescending size={16} />)}
|
||||
</th>
|
||||
<th style={{ width: '25%', cursor: 'pointer' }} onClick={() => toggleSort('gateway')}>
|
||||
<th style={{ width: '25%' }} className="cursor-pointer" onClick={() => toggleSort('gateway')}>
|
||||
<span className="me-1">GATEWAY</span>
|
||||
{sortBy === 'gateway' && (sortDir === 'asc' ? <IconSortAscending size={16} /> : <IconSortDescending size={16} />)}
|
||||
</th>
|
||||
<th style={{ width: '35%', cursor: 'pointer' }} onClick={() => toggleSort('description')}>
|
||||
<th style={{ width: '35%' }} className="cursor-pointer" onClick={() => toggleSort('description')}>
|
||||
<span className="me-1">ОПИСАНИЕ</span>
|
||||
{sortBy === 'description' && (sortDir === 'asc' ? <IconSortAscending size={16} /> : <IconSortDescending size={16} />)}
|
||||
</th>
|
||||
|
||||
@@ -15,11 +15,13 @@ import {
|
||||
IconDeviceFloppy,
|
||||
IconHash,
|
||||
IconClock,
|
||||
IconFileText
|
||||
IconFileText,
|
||||
IconEye
|
||||
} from '@tabler/icons-react';
|
||||
import LockBanner from './components/LockBanner.jsx';
|
||||
import TableSkeleton from './components/TableSkeleton.jsx';
|
||||
import S3MetaBar from './components/S3MetaBar.jsx';
|
||||
import PageHeaderActions from './components/PageHeaderActions.jsx';
|
||||
import ConfirmDiffModal from './components/ConfirmDiffModal.jsx';
|
||||
import HistoryModal from './components/HistoryModal.jsx';
|
||||
|
||||
@@ -409,9 +411,21 @@ function IPRangesManager() {
|
||||
<div className="page-pretitle">Главная / Данные / IP ranges</div>
|
||||
</div>
|
||||
<div className="col-auto ms-auto d-print-none">
|
||||
<div className="btn-list">
|
||||
<button className="btn btn-outline-secondary" onClick={() => setHistoryOpen(true)}>История</button>
|
||||
</div>
|
||||
<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}
|
||||
onSave={handleSaveChanges}
|
||||
disableSave={loading}
|
||||
onHistory={() => setHistoryOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -483,73 +497,7 @@ function IPRangesManager() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions Card */}
|
||||
<div className="card card-md">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title">
|
||||
<IconDatabase className="icon me-2" />
|
||||
Действия
|
||||
</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="d-grid gap-2">
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={handleSaveChanges}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<span className="spinner-border spinner-border-sm me-2" role="status"></span>
|
||||
Сохранение...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<IconDeviceFloppy className="icon me-2" />
|
||||
Сохранить в S3
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={fetchItems}
|
||||
disabled={loading}
|
||||
>
|
||||
<IconRefresh className="icon me-2" />
|
||||
Обновить
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-primary"
|
||||
onClick={handleImport}
|
||||
>
|
||||
<IconUpload className="icon me-2" />
|
||||
Импорт из буфера
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-primary"
|
||||
onClick={handleExport}
|
||||
disabled={items.length === 0}
|
||||
>
|
||||
<IconDownload className="icon me-2" />
|
||||
Экспорт CSV
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={handlePreviewDiff}
|
||||
disabled={loading}
|
||||
>
|
||||
Предпросмотр изменений
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={clearInvalid}
|
||||
disabled={items.length === 0}
|
||||
>
|
||||
Очистить пустые/невалидные
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Карточка действий больше не нужна — действия перенесены в page-header */}
|
||||
</div>
|
||||
|
||||
<div className="col-lg-9">
|
||||
@@ -588,7 +536,7 @@ function IPRangesManager() {
|
||||
<table className="table card-table table-vcenter table-nowrap mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('ipRange')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('ipRange')}>
|
||||
IP-диапазон
|
||||
{sortField === 'ipRange' && (
|
||||
<span className="ms-1">
|
||||
@@ -596,7 +544,7 @@ function IPRangesManager() {
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('community')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('community')}>
|
||||
Community
|
||||
{sortField === 'community' && (
|
||||
<span className="ms-1">
|
||||
|
||||
@@ -558,7 +558,7 @@ function ServerManager() {
|
||||
<table className="table card-table table-vcenter table-nowrap mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('ip')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('ip')}>
|
||||
IP адрес
|
||||
{sortField === 'ip' && (
|
||||
<span className="ms-1">
|
||||
@@ -566,7 +566,7 @@ function ServerManager() {
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('dns')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('dns')}>
|
||||
DNS имя
|
||||
{sortField === 'dns' && (
|
||||
<span className="ms-1">
|
||||
@@ -574,7 +574,7 @@ function ServerManager() {
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('country')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('country')}>
|
||||
Страна
|
||||
{sortField === 'country' && (
|
||||
<span className="ms-1">
|
||||
@@ -582,7 +582,7 @@ function ServerManager() {
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('provider')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('provider')}>
|
||||
Провайдер
|
||||
{sortField === 'provider' && (
|
||||
<span className="ms-1">
|
||||
@@ -590,7 +590,7 @@ function ServerManager() {
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('tunnel')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('tunnel')}>
|
||||
Туннель
|
||||
{sortField === 'tunnel' && (
|
||||
<span className="ms-1">
|
||||
@@ -598,7 +598,7 @@ function ServerManager() {
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
<th style={{cursor:'pointer'}} onClick={() => handleSort('gateway')}>
|
||||
<th className="cursor-pointer" onClick={() => handleSort('gateway')}>
|
||||
Основной шлюз
|
||||
{sortField === 'gateway' && (
|
||||
<span className="ms-1">
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
IconEye,
|
||||
IconRefresh,
|
||||
IconUpload,
|
||||
IconDownload,
|
||||
IconDeviceFloppy
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
function PageHeaderActions({
|
||||
loading = false,
|
||||
onPreview,
|
||||
disablePreview = false,
|
||||
onRefresh,
|
||||
disableRefresh = false,
|
||||
onImport,
|
||||
onExport,
|
||||
disableExport = false,
|
||||
onClear,
|
||||
disableClear = false,
|
||||
onSave,
|
||||
disableSave = false,
|
||||
onHistory,
|
||||
}) {
|
||||
return (
|
||||
<div className="btn-list">
|
||||
{onPreview && (
|
||||
<button className="btn btn-outline-secondary" onClick={onPreview} disabled={disablePreview} title="Предпросмотр изменений">
|
||||
<IconEye className="me-1" /> Предпросмотр
|
||||
</button>
|
||||
)}
|
||||
{onRefresh && (
|
||||
<button className="btn btn-outline-secondary" onClick={onRefresh} disabled={disableRefresh} title="Обновить данные">
|
||||
<IconRefresh className={loading ? 'spin me-1' : 'me-1'} /> Обновить
|
||||
</button>
|
||||
)}
|
||||
{onImport && (
|
||||
<button className="btn btn-outline-primary" onClick={onImport} title="Импорт">
|
||||
<IconUpload className="me-1" /> Импорт
|
||||
</button>
|
||||
)}
|
||||
{onExport && (
|
||||
<button className="btn btn-outline-primary" onClick={onExport} disabled={disableExport} title="Экспорт">
|
||||
<IconDownload className="me-1" /> Экспорт
|
||||
</button>
|
||||
)}
|
||||
{onClear && (
|
||||
<button className="btn btn-outline-secondary" onClick={onClear} disabled={disableClear} title="Очистить пустые/невалидные">
|
||||
Очистить
|
||||
</button>
|
||||
)}
|
||||
{onSave && (
|
||||
<button className="btn btn-primary" onClick={onSave} disabled={disableSave} title="Сохранить">
|
||||
{loading ? (
|
||||
<>
|
||||
<span className="spinner-border spinner-border-sm me-2" role="status"></span>
|
||||
Сохранение...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<IconDeviceFloppy className="me-1" /> Сохранить
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
{onHistory && (
|
||||
<button className="btn btn-outline-secondary" onClick={onHistory} title="История версий">История</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageHeaderActions;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user