feat(MikrotikBackupsManager): enhance backup management UI with detailed backup table and improved server selection settings
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 3m27s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 3m27s
This commit is contained in:
@@ -518,12 +518,100 @@ function MikrotikBackupsManager() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Блок выбора серверов для автобэкапа */}
|
{/* Таблица бэкапов для выбранного сервера */}
|
||||||
<div className="card mb-4">
|
{loadingServers ? (
|
||||||
|
<TableSkeleton rows={5} columns={4} />
|
||||||
|
) : !selectedServerId ? (
|
||||||
|
<EmptyState
|
||||||
|
title="Нет доступных Jumphost-серверов"
|
||||||
|
description='Создайте хотя бы один сервер типа "jumphost" в разделе Серверы, чтобы включить бэкапы.'
|
||||||
|
/>
|
||||||
|
) : loadingBackups ? (
|
||||||
|
<TableSkeleton rows={5} columns={4} />
|
||||||
|
) : backups.length === 0 ? (
|
||||||
|
<EmptyState
|
||||||
|
title="Для этого сервера бэкапов ещё нет"
|
||||||
|
description="Автоматические бэкапы создаются планировщиком на backend. Можно также отправить бэкап вручную через API."
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-header d-flex align-items-center justify-content-between">
|
||||||
|
<div>
|
||||||
|
<div className="card-title mb-0">Бэкапы для {currentServerLabel}</div>
|
||||||
|
<div className="text-muted small">
|
||||||
|
Всего: {backups.length}. Выбрано для diff: {selectedKeys.length}/2.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-muted small d-flex align-items-center">
|
||||||
|
<IconAlertTriangle size={16} className="me-1 text-warning" />
|
||||||
|
Восстановление выполняется вручную через Winbox/SSH, используя экспортированный .rsc
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="table-responsive">
|
||||||
|
<table className="table table-vcenter table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{ width: 40 }}></th>
|
||||||
|
<th>Создан</th>
|
||||||
|
<th>Размер</th>
|
||||||
|
<th>Ключ S3</th>
|
||||||
|
<th style={{ width: 220 }}>Действия</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{backups.map((b) => {
|
||||||
|
const active = selectedKeys.includes(b.key);
|
||||||
|
const shortKey = b.key.split('/').slice(-1)[0] || b.key;
|
||||||
|
return (
|
||||||
|
<tr key={b.key} className={active ? 'table-active' : ''}>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-check-input"
|
||||||
|
checked={active}
|
||||||
|
onChange={() => handleSelectKey(b.key)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{formatDateTime(b.createdAt)}</td>
|
||||||
|
<td>{bytesToHuman(b.size)}</td>
|
||||||
|
<td className="text-truncate" style={{ maxWidth: 260 }} title={b.key}>
|
||||||
|
{shortKey}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className="btn-list">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-outline-secondary btn-sm"
|
||||||
|
onClick={() => handleViewConfig(b.key)}
|
||||||
|
>
|
||||||
|
<IconEye size={16} className="me-1" />
|
||||||
|
Просмотр
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-outline-secondary btn-sm"
|
||||||
|
onClick={() => handleDownload(b.key)}
|
||||||
|
>
|
||||||
|
<IconDownload size={16} className="me-1" />
|
||||||
|
Скачать
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Блок выбора серверов для автобэкапа – ниже как "настройки" */}
|
||||||
|
<div className="card mb-4 mt-4">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<div className="d-flex flex-wrap justify-content-between align-items-center gap-2">
|
<div className="d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||||
<div>
|
<div>
|
||||||
<div className="card-title mb-0">Сервера для автоматических бэкапов</div>
|
<div className="card-title mb-0">Настройки автоматических бэкапов</div>
|
||||||
<div className="text-muted small">
|
<div className="text-muted small">
|
||||||
Отмеченные здесь Jumphost-серверы будут периодически бэкапиться планировщиком на backend.
|
Отмеченные здесь Jumphost-серверы будут периодически бэкапиться планировщиком на backend.
|
||||||
</div>
|
</div>
|
||||||
@@ -642,93 +730,6 @@ function MikrotikBackupsManager() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loadingServers ? (
|
|
||||||
<TableSkeleton rows={5} columns={4} />
|
|
||||||
) : !selectedServerId ? (
|
|
||||||
<EmptyState
|
|
||||||
title="Нет доступных Jumphost-серверов"
|
|
||||||
description='Создайте хотя бы один сервер типа "jumphost" в разделе Серверы, чтобы включить бэкапы.'
|
|
||||||
/>
|
|
||||||
) : loadingBackups ? (
|
|
||||||
<TableSkeleton rows={5} columns={4} />
|
|
||||||
) : backups.length === 0 ? (
|
|
||||||
<EmptyState
|
|
||||||
title="Для этого сервера бэкапов ещё нет"
|
|
||||||
description="Автоматические бэкапы создаются планировщиком на backend. Можно также отправить бэкап вручную через API."
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="card">
|
|
||||||
<div className="card-header d-flex align-items-center justify-content-between">
|
|
||||||
<div>
|
|
||||||
<div className="card-title mb-0">Бэкапы для {currentServerLabel}</div>
|
|
||||||
<div className="text-muted small">
|
|
||||||
Всего: {backups.length}. Выбрано для diff: {selectedKeys.length}/2.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-muted small d-flex align-items-center">
|
|
||||||
<IconAlertTriangle size={16} className="me-1 text-warning" />
|
|
||||||
Восстановление выполняется вручную через Winbox/SSH, используя экспортированный .rsc
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="table-responsive">
|
|
||||||
<table className="table table-vcenter table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style={{ width: 40 }}></th>
|
|
||||||
<th>Создан</th>
|
|
||||||
<th>Размер</th>
|
|
||||||
<th>Ключ S3</th>
|
|
||||||
<th style={{ width: 220 }}>Действия</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{backups.map((b) => {
|
|
||||||
const active = selectedKeys.includes(b.key);
|
|
||||||
const shortKey = b.key.split('/').slice(-1)[0] || b.key;
|
|
||||||
return (
|
|
||||||
<tr key={b.key} className={active ? 'table-active' : ''}>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input"
|
|
||||||
checked={active}
|
|
||||||
onChange={() => handleSelectKey(b.key)}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>{formatDateTime(b.createdAt)}</td>
|
|
||||||
<td>{bytesToHuman(b.size)}</td>
|
|
||||||
<td className="text-truncate" style={{ maxWidth: 260 }} title={b.key}>
|
|
||||||
{shortKey}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div className="btn-list">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-outline-secondary btn-sm"
|
|
||||||
onClick={() => handleViewConfig(b.key)}
|
|
||||||
>
|
|
||||||
<IconEye size={16} className="me-1" />
|
|
||||||
Просмотр
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-outline-secondary btn-sm"
|
|
||||||
onClick={() => handleDownload(b.key)}
|
|
||||||
>
|
|
||||||
<IconDownload size={16} className="me-1" />
|
|
||||||
Скачать
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(viewConfigKey || diffResult) && (
|
{(viewConfigKey || diffResult) && (
|
||||||
<div className="row mt-4">
|
<div className="row mt-4">
|
||||||
{viewConfigKey && (
|
{viewConfigKey && (
|
||||||
|
|||||||
Reference in New Issue
Block a user