refactor: Simplify FilterManager layout by conditionally rendering server statistics and search section based on advanced mode, and remove custom hover effects in App.css for cleaner UI
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m47s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m47s
This commit is contained in:
@@ -22,12 +22,6 @@
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* Custom hover effects */
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* Custom focus states */
|
||||
.btn:focus,
|
||||
.form-control:focus,
|
||||
|
||||
+199
-195
@@ -662,230 +662,234 @@ function FilterManager() {
|
||||
)}
|
||||
|
||||
{/* Панель статистики */}
|
||||
<div className="row mb-4">
|
||||
<div className="col-md-3">
|
||||
<div className="card card-sm">
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-auto">
|
||||
<span className="bg-primary text-white avatar">
|
||||
<IconServer />
|
||||
</span>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="font-weight-medium">{totalServers}</div>
|
||||
<div className="text-muted">Всего серверов</div>
|
||||
{mode === 'advanced' && (
|
||||
<div className="row mb-4">
|
||||
<div className="col-md-3">
|
||||
<div className="card card-sm">
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-auto">
|
||||
<span className="bg-primary text-white avatar">
|
||||
<IconServer />
|
||||
</span>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="font-weight-medium">{totalServers}</div>
|
||||
<div className="text-muted">Всего серверов</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<div className="card card-sm">
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-auto">
|
||||
<span className="bg-green text-white avatar">
|
||||
<IconCheck />
|
||||
</span>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="font-weight-medium">{enabledServers}</div>
|
||||
<div className="text-muted">Активных серверов</div>
|
||||
<div className="col-md-3">
|
||||
<div className="card card-sm">
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-auto">
|
||||
<span className="bg-green text-white avatar">
|
||||
<IconCheck />
|
||||
</span>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="font-weight-medium">{enabledServers}</div>
|
||||
<div className="text-muted">Активных серверов</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<div className="card card-sm">
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-auto">
|
||||
<span className="bg-blue text-white avatar">
|
||||
<IconFilter />
|
||||
</span>
|
||||
<div className="col-md-3">
|
||||
<div className="card card-sm">
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-auto">
|
||||
<span className="bg-blue text-white avatar">
|
||||
<IconFilter />
|
||||
</span>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="font-weight-medium">{selectedServer ? serverFilters.length : '-'}</div>
|
||||
<div className="text-muted">
|
||||
{selectedServer ? 'Фильтров в выбранном сервере' : 'Выберите сервер'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="font-weight-medium">{selectedServer ? serverFilters.length : '-'}</div>
|
||||
<div className="text-muted">
|
||||
{selectedServer ? 'Фильтров в выбранном сервере' : 'Выберите сервер'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<div className="card card-sm">
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-auto">
|
||||
<span className="bg-orange text-white avatar">
|
||||
<IconDeviceFloppy />
|
||||
</span>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="font-weight-medium">
|
||||
{!selectedServer ? 'Не выбран' : serverFilters.length > 0 ? 'Готов' : 'Не готов'}
|
||||
</div>
|
||||
<div className="text-muted">Статус конфигурации</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-3">
|
||||
<div className="card card-sm">
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-auto">
|
||||
<span className="bg-orange text-white avatar">
|
||||
<IconDeviceFloppy />
|
||||
</span>
|
||||
</div>
|
||||
<div className="col">
|
||||
<div className="font-weight-medium">
|
||||
{!selectedServer ? 'Не выбран' : serverFilters.length > 0 ? 'Готов' : 'Не готов'}
|
||||
</div>
|
||||
<div className="text-muted">Статус конфигурации</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Секция управления серверами */}
|
||||
<div className="row mb-4">
|
||||
<div className="col-12">
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title">
|
||||
<IconServer className="me-2" />
|
||||
Готовые конфигурации серверов
|
||||
</h3>
|
||||
<div className="card-actions">
|
||||
<div className="input-group" style={{ maxWidth: 400 }}>
|
||||
<span className="input-group-text">
|
||||
<IconSearch />
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Поиск серверов..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
type="button"
|
||||
onClick={() => setServerConfigModalOpen(true)}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: 6 }}
|
||||
>
|
||||
<IconPlus className="me-1" />
|
||||
Добавить сервер
|
||||
</button>
|
||||
{mode === 'advanced' && (
|
||||
<div className="row mb-4">
|
||||
<div className="col-12">
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title">
|
||||
<IconServer className="me-2" />
|
||||
Готовые конфигурации серверов
|
||||
</h3>
|
||||
<div className="card-actions">
|
||||
<div className="input-group" style={{ maxWidth: 400 }}>
|
||||
<span className="input-group-text">
|
||||
<IconSearch />
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Поиск серверов..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
type="button"
|
||||
onClick={() => setServerConfigModalOpen(true)}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: 6 }}
|
||||
>
|
||||
<IconPlus className="me-1" />
|
||||
Добавить сервер
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{servers.length === 0 ? (
|
||||
<div className="text-center text-muted py-4">
|
||||
<IconServer className="icon-lg mb-2" />
|
||||
<p>Нет добавленных серверов</p>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => setServerConfigModalOpen(true)}
|
||||
>
|
||||
<IconPlus className="me-2" />
|
||||
Добавить первый сервер
|
||||
</button>
|
||||
</div>
|
||||
) : filteredServers.length === 0 ? (
|
||||
<div className="text-center text-muted py-4">
|
||||
<IconSearch className="icon-lg mb-2" />
|
||||
<p>Серверы не найдены по запросу "{searchTerm}"</p>
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={() => setSearchTerm('')}
|
||||
>
|
||||
Очистить поиск
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="row g-3">
|
||||
{filteredServers.map(server => (
|
||||
<div key={server.id} className="col-md-6 col-lg-4">
|
||||
<div className={`card ${selectedServer?.id === server.id ? 'border-primary' : ''} h-100`}>
|
||||
<div className="card-body">
|
||||
<div className="d-flex justify-content-between align-items-start mb-3">
|
||||
<div className="flex-grow-1">
|
||||
<h4 className="card-title mb-1 d-flex align-items-center">
|
||||
{server.name}
|
||||
{selectedServer?.id === server.id && (
|
||||
<span className="badge bg-blue-lt text-blue ms-2">Выбран</span>
|
||||
)}
|
||||
</h4>
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<span className={`badge ${server.enabled ? 'bg-green-lt text-green' : 'bg-red-lt text-red'}`}>
|
||||
{server.enabled ? 'Активен' : 'Неактивен'}
|
||||
</span>
|
||||
{server.id === selectedServer?.id && (
|
||||
<span className="badge bg-blue-lt text-blue">
|
||||
{serverFilters.length} фильтров
|
||||
<div className="card-body">
|
||||
{servers.length === 0 ? (
|
||||
<div className="text-center text-muted py-4">
|
||||
<IconServer className="icon-lg mb-2" />
|
||||
<p>Нет добавленных серверов</p>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => setServerConfigModalOpen(true)}
|
||||
>
|
||||
<IconPlus className="me-2" />
|
||||
Добавить первый сервер
|
||||
</button>
|
||||
</div>
|
||||
) : filteredServers.length === 0 ? (
|
||||
<div className="text-center text-muted py-4">
|
||||
<IconSearch className="icon-lg mb-2" />
|
||||
<p>Серверы не найдены по запросу "{searchTerm}"</p>
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={() => setSearchTerm('')}
|
||||
>
|
||||
Очистить поиск
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="row g-3">
|
||||
{filteredServers.map(server => (
|
||||
<div key={server.id} className="col-md-6 col-lg-4">
|
||||
<div className={`card ${selectedServer?.id === server.id ? 'border-primary' : ''} h-100`}>
|
||||
<div className="card-body">
|
||||
<div className="d-flex justify-content-between align-items-start mb-3">
|
||||
<div className="flex-grow-1">
|
||||
<h4 className="card-title mb-1 d-flex align-items-center">
|
||||
{server.name}
|
||||
{selectedServer?.id === server.id && (
|
||||
<span className="badge bg-blue-lt text-blue ms-2">Выбран</span>
|
||||
)}
|
||||
</h4>
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<span className={`badge ${server.enabled ? 'bg-green-lt text-green' : 'bg-red-lt text-red'}`}>
|
||||
{server.enabled ? 'Активен' : 'Неактивен'}
|
||||
</span>
|
||||
)}
|
||||
{server.id === selectedServer?.id && (
|
||||
<span className="badge bg-blue-lt text-blue">
|
||||
{serverFilters.length} фильтров
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-check form-switch">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={server.enabled}
|
||||
onChange={async () => {
|
||||
const updatedServers = servers.map(s =>
|
||||
s.id === server.id ? { ...s, enabled: !s.enabled } : s
|
||||
);
|
||||
try {
|
||||
await axios.post(`${API_URL}/server-configs`, { servers: updatedServers });
|
||||
setServers(updatedServers);
|
||||
} catch (error) {
|
||||
setError('Не удалось обновить статус сервера');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-check form-switch">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={server.enabled}
|
||||
onChange={async () => {
|
||||
const updatedServers = servers.map(s =>
|
||||
s.id === server.id ? { ...s, enabled: !s.enabled } : s
|
||||
);
|
||||
try {
|
||||
await axios.post(`${API_URL}/server-configs`, { servers: updatedServers });
|
||||
setServers(updatedServers);
|
||||
} catch (error) {
|
||||
setError('Не удалось обновить статус сервера');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{server.description && (
|
||||
<p className="text-muted small mb-3">{server.description}</p>
|
||||
)}
|
||||
|
||||
<div className="d-flex gap-2 flex-wrap">
|
||||
<button
|
||||
className={`btn btn-sm ${selectedServer?.id === server.id ? 'btn-primary' : 'btn-outline-primary'}`}
|
||||
onClick={() => setSelectedServer(server)}
|
||||
>
|
||||
<IconFilter className="me-1" />
|
||||
{selectedServer?.id === server.id ? 'Выбран' : 'Выбрать'}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary btn-sm"
|
||||
onClick={() => handleEditServer(server)}
|
||||
title="Редактировать сервер"
|
||||
>
|
||||
<IconEdit className="me-1" />
|
||||
Редактировать
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-warning btn-sm"
|
||||
onClick={() => handleCopyConfigToAllServers(server.id)}
|
||||
disabled={loading || servers.filter(s => s.id !== server.id && s.enabled).length === 0}
|
||||
title="Скопировать конфигурацию на все остальные серверы"
|
||||
>
|
||||
<IconCopy className="me-1" />
|
||||
Копировать на все
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-danger btn-sm"
|
||||
onClick={() => handleDeleteServer(server.id)}
|
||||
>
|
||||
<IconTrash className="me-1" />
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{server.description && (
|
||||
<p className="text-muted small mb-3">{server.description}</p>
|
||||
)}
|
||||
|
||||
<div className="d-flex gap-2 flex-wrap">
|
||||
<button
|
||||
className={`btn btn-sm ${selectedServer?.id === server.id ? 'btn-primary' : 'btn-outline-primary'}`}
|
||||
onClick={() => setSelectedServer(server)}
|
||||
>
|
||||
<IconFilter className="me-1" />
|
||||
{selectedServer?.id === server.id ? 'Выбран' : 'Выбрать'}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary btn-sm"
|
||||
onClick={() => handleEditServer(server)}
|
||||
title="Редактировать сервер"
|
||||
>
|
||||
<IconEdit className="me-1" />
|
||||
Редактировать
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-warning btn-sm"
|
||||
onClick={() => handleCopyConfigToAllServers(server.id)}
|
||||
disabled={loading || servers.filter(s => s.id !== server.id && s.enabled).length === 0}
|
||||
title="Скопировать конфигурацию на все остальные серверы"
|
||||
>
|
||||
<IconCopy className="me-1" />
|
||||
Копировать на все
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-danger btn-sm"
|
||||
onClick={() => handleDeleteServer(server.id)}
|
||||
>
|
||||
<IconTrash className="me-1" />
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Секция фильтров выбранного сервера */}
|
||||
{selectedServer && (
|
||||
|
||||
Reference in New Issue
Block a user