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;
|
animation: fadeIn 0.3s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom hover effects */
|
|
||||||
.btn:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom focus states */
|
/* Custom focus states */
|
||||||
.btn:focus,
|
.btn:focus,
|
||||||
.form-control:focus,
|
.form-control:focus,
|
||||||
|
|||||||
+199
-195
@@ -662,230 +662,234 @@ function FilterManager() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Панель статистики */}
|
{/* Панель статистики */}
|
||||||
<div className="row mb-4">
|
{mode === 'advanced' && (
|
||||||
<div className="col-md-3">
|
<div className="row mb-4">
|
||||||
<div className="card card-sm">
|
<div className="col-md-3">
|
||||||
<div className="card-body">
|
<div className="card card-sm">
|
||||||
<div className="row align-items-center">
|
<div className="card-body">
|
||||||
<div className="col-auto">
|
<div className="row align-items-center">
|
||||||
<span className="bg-primary text-white avatar">
|
<div className="col-auto">
|
||||||
<IconServer />
|
<span className="bg-primary text-white avatar">
|
||||||
</span>
|
<IconServer />
|
||||||
</div>
|
</span>
|
||||||
<div className="col">
|
</div>
|
||||||
<div className="font-weight-medium">{totalServers}</div>
|
<div className="col">
|
||||||
<div className="text-muted">Всего серверов</div>
|
<div className="font-weight-medium">{totalServers}</div>
|
||||||
|
<div className="text-muted">Всего серверов</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="col-md-3">
|
||||||
<div className="col-md-3">
|
<div className="card card-sm">
|
||||||
<div className="card card-sm">
|
<div className="card-body">
|
||||||
<div className="card-body">
|
<div className="row align-items-center">
|
||||||
<div className="row align-items-center">
|
<div className="col-auto">
|
||||||
<div className="col-auto">
|
<span className="bg-green text-white avatar">
|
||||||
<span className="bg-green text-white avatar">
|
<IconCheck />
|
||||||
<IconCheck />
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
<div className="col">
|
||||||
<div className="col">
|
<div className="font-weight-medium">{enabledServers}</div>
|
||||||
<div className="font-weight-medium">{enabledServers}</div>
|
<div className="text-muted">Активных серверов</div>
|
||||||
<div className="text-muted">Активных серверов</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="col-md-3">
|
||||||
<div className="col-md-3">
|
<div className="card card-sm">
|
||||||
<div className="card card-sm">
|
<div className="card-body">
|
||||||
<div className="card-body">
|
<div className="row align-items-center">
|
||||||
<div className="row align-items-center">
|
<div className="col-auto">
|
||||||
<div className="col-auto">
|
<span className="bg-blue text-white avatar">
|
||||||
<span className="bg-blue text-white avatar">
|
<IconFilter />
|
||||||
<IconFilter />
|
</span>
|
||||||
</span>
|
</div>
|
||||||
|
<div className="col">
|
||||||
|
<div className="font-weight-medium">{selectedServer ? serverFilters.length : '-'}</div>
|
||||||
|
<div className="text-muted">
|
||||||
|
{selectedServer ? 'Фильтров в выбранном сервере' : 'Выберите сервер'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col">
|
</div>
|
||||||
<div className="font-weight-medium">{selectedServer ? serverFilters.length : '-'}</div>
|
</div>
|
||||||
<div className="text-muted">
|
</div>
|
||||||
{selectedServer ? 'Фильтров в выбранном сервере' : 'Выберите сервер'}
|
<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>
|
</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">
|
{mode === 'advanced' && (
|
||||||
<div className="col-12">
|
<div className="row mb-4">
|
||||||
<div className="card">
|
<div className="col-12">
|
||||||
<div className="card-header">
|
<div className="card">
|
||||||
<h3 className="card-title">
|
<div className="card-header">
|
||||||
<IconServer className="me-2" />
|
<h3 className="card-title">
|
||||||
Готовые конфигурации серверов
|
<IconServer className="me-2" />
|
||||||
</h3>
|
Готовые конфигурации серверов
|
||||||
<div className="card-actions">
|
</h3>
|
||||||
<div className="input-group" style={{ maxWidth: 400 }}>
|
<div className="card-actions">
|
||||||
<span className="input-group-text">
|
<div className="input-group" style={{ maxWidth: 400 }}>
|
||||||
<IconSearch />
|
<span className="input-group-text">
|
||||||
</span>
|
<IconSearch />
|
||||||
<input
|
</span>
|
||||||
type="text"
|
<input
|
||||||
className="form-control"
|
type="text"
|
||||||
placeholder="Поиск серверов..."
|
className="form-control"
|
||||||
value={searchTerm}
|
placeholder="Поиск серверов..."
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
value={searchTerm}
|
||||||
/>
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
<button
|
/>
|
||||||
className="btn btn-primary"
|
<button
|
||||||
type="button"
|
className="btn btn-primary"
|
||||||
onClick={() => setServerConfigModalOpen(true)}
|
type="button"
|
||||||
style={{ display: 'flex', alignItems: 'center', gap: 6 }}
|
onClick={() => setServerConfigModalOpen(true)}
|
||||||
>
|
style={{ display: 'flex', alignItems: 'center', gap: 6 }}
|
||||||
<IconPlus className="me-1" />
|
>
|
||||||
Добавить сервер
|
<IconPlus className="me-1" />
|
||||||
</button>
|
Добавить сервер
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="card-body">
|
||||||
<div className="card-body">
|
{servers.length === 0 ? (
|
||||||
{servers.length === 0 ? (
|
<div className="text-center text-muted py-4">
|
||||||
<div className="text-center text-muted py-4">
|
<IconServer className="icon-lg mb-2" />
|
||||||
<IconServer className="icon-lg mb-2" />
|
<p>Нет добавленных серверов</p>
|
||||||
<p>Нет добавленных серверов</p>
|
<button
|
||||||
<button
|
className="btn btn-primary"
|
||||||
className="btn btn-primary"
|
onClick={() => setServerConfigModalOpen(true)}
|
||||||
onClick={() => setServerConfigModalOpen(true)}
|
>
|
||||||
>
|
<IconPlus className="me-2" />
|
||||||
<IconPlus className="me-2" />
|
Добавить первый сервер
|
||||||
Добавить первый сервер
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
) : filteredServers.length === 0 ? (
|
||||||
) : filteredServers.length === 0 ? (
|
<div className="text-center text-muted py-4">
|
||||||
<div className="text-center text-muted py-4">
|
<IconSearch className="icon-lg mb-2" />
|
||||||
<IconSearch className="icon-lg mb-2" />
|
<p>Серверы не найдены по запросу "{searchTerm}"</p>
|
||||||
<p>Серверы не найдены по запросу "{searchTerm}"</p>
|
<button
|
||||||
<button
|
className="btn btn-outline-secondary"
|
||||||
className="btn btn-outline-secondary"
|
onClick={() => setSearchTerm('')}
|
||||||
onClick={() => setSearchTerm('')}
|
>
|
||||||
>
|
Очистить поиск
|
||||||
Очистить поиск
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
) : (
|
<div className="row g-3">
|
||||||
<div className="row g-3">
|
{filteredServers.map(server => (
|
||||||
{filteredServers.map(server => (
|
<div key={server.id} className="col-md-6 col-lg-4">
|
||||||
<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 ${selectedServer?.id === server.id ? 'border-primary' : ''} h-100`}>
|
<div className="card-body">
|
||||||
<div className="card-body">
|
<div className="d-flex justify-content-between align-items-start mb-3">
|
||||||
<div className="d-flex justify-content-between align-items-start mb-3">
|
<div className="flex-grow-1">
|
||||||
<div className="flex-grow-1">
|
<h4 className="card-title mb-1 d-flex align-items-center">
|
||||||
<h4 className="card-title mb-1 d-flex align-items-center">
|
{server.name}
|
||||||
{server.name}
|
{selectedServer?.id === server.id && (
|
||||||
{selectedServer?.id === server.id && (
|
<span className="badge bg-blue-lt text-blue ms-2">Выбран</span>
|
||||||
<span className="badge bg-blue-lt text-blue ms-2">Выбран</span>
|
)}
|
||||||
)}
|
</h4>
|
||||||
</h4>
|
<div className="d-flex align-items-center gap-2">
|
||||||
<div className="d-flex align-items-center gap-2">
|
<span className={`badge ${server.enabled ? 'bg-green-lt text-green' : 'bg-red-lt text-red'}`}>
|
||||||
<span className={`badge ${server.enabled ? 'bg-green-lt text-green' : 'bg-red-lt text-red'}`}>
|
{server.enabled ? 'Активен' : 'Неактивен'}
|
||||||
{server.enabled ? 'Активен' : 'Неактивен'}
|
|
||||||
</span>
|
|
||||||
{server.id === selectedServer?.id && (
|
|
||||||
<span className="badge bg-blue-lt text-blue">
|
|
||||||
{serverFilters.length} фильтров
|
|
||||||
</span>
|
</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>
|
</div>
|
||||||
<div className="form-check form-switch">
|
|
||||||
<input
|
{server.description && (
|
||||||
className="form-check-input"
|
<p className="text-muted small mb-3">{server.description}</p>
|
||||||
type="checkbox"
|
)}
|
||||||
checked={server.enabled}
|
|
||||||
onChange={async () => {
|
<div className="d-flex gap-2 flex-wrap">
|
||||||
const updatedServers = servers.map(s =>
|
<button
|
||||||
s.id === server.id ? { ...s, enabled: !s.enabled } : s
|
className={`btn btn-sm ${selectedServer?.id === server.id ? 'btn-primary' : 'btn-outline-primary'}`}
|
||||||
);
|
onClick={() => setSelectedServer(server)}
|
||||||
try {
|
>
|
||||||
await axios.post(`${API_URL}/server-configs`, { servers: updatedServers });
|
<IconFilter className="me-1" />
|
||||||
setServers(updatedServers);
|
{selectedServer?.id === server.id ? 'Выбран' : 'Выбрать'}
|
||||||
} catch (error) {
|
</button>
|
||||||
setError('Не удалось обновить статус сервера');
|
<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>
|
||||||
|
|
||||||
{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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{/* Секция фильтров выбранного сервера */}
|
{/* Секция фильтров выбранного сервера */}
|
||||||
{selectedServer && (
|
{selectedServer && (
|
||||||
|
|||||||
Reference in New Issue
Block a user