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

This commit is contained in:
2025-07-14 15:00:39 +07:00
parent fd769dc81a
commit e6a9c17aa0
2 changed files with 199 additions and 201 deletions
-6
View File
@@ -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
View File
@@ -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 && (