fix(ui): ограничить каталог бэкапов и вернуть фокус действий

This commit is contained in:
Denozordec committed 2026-10-04 05:01:29 +07:00
1 parent 5d9a6f3757
commit 611e542ea7
11 files changed
+123 -35

No files matched your search

+23 -3
View File
@@ -74,6 +74,24 @@ function BackupsContent() {
const canWrite = isLive && can("mm:backups:write")
const canReadServers = can("mm:servers:read")
const [resources, setResources] = useState<Record<Resource, ResourceState>>(() => ({ backups: unknownResource(), servers: unknownResource(), schedule: unknownResource(), storage: unknownResource() }))
const dialogTrigger = useRef<{ element: HTMLElement; rowId: string | null; label: string | null } | null>(null)
const refreshTrigger = useRef<HTMLButtonElement | null>(null)
const captureDialogFocus = () => {
const element = document.activeElement
dialogTrigger.current = element instanceof HTMLElement
? { element, rowId: element.closest("[data-row-id]")?.getAttribute("data-row-id") ?? null, label: element.getAttribute("aria-label") }
: null
}
const returnDialogFocus = () => {
const trigger = dialogTrigger.current
if (trigger?.element.isConnected) return trigger.element
// Cell renderers can remount when operation state changes; resolve the same row/action.
if (trigger?.rowId && trigger.label) {
const replacement = document.querySelector<HTMLElement>(`[data-row-id="${CSS.escape(trigger.rowId)}"] button[aria-label="${CSS.escape(trigger.label)}"]`)
if (replacement) return replacement
}
return refreshTrigger.current
}
const reader = useRef<AbortController | null>(null)
const revision = useRef(0)
const alive = useRef(true)
@@ -403,7 +421,7 @@ function BackupsContent() {
{!canRead && <p role="status">Нет права mm:backups:read</p>}
{isLive && !canReadServers && <p role="status">Каталог устройств недоступен без mm:servers:read.</p>}
{isLive && canRead && <div className="flex flex-wrap items-center gap-3 text-sm text-muted-foreground">
<Button type="button" variant="outline" size="sm" onClick={() => void loadLive()} disabled={loading || scheduleSaveBusy || opBusy}>Обновить</Button>
<Button type="button" variant="outline" size="sm" ref={refreshTrigger} onClick={() => void loadLive()} disabled={loading || scheduleSaveBusy || opBusy}>Обновить</Button>
<span role="status">{loading ? "Обновление данных…" : resources.backups.receivedAt ? "Получено: " + new Date(resources.backups.receivedAt).toLocaleString("ru-RU") : "Резервные копии пока неизвестны"}</span>
</div>}
{isLive && (Object.keys(resources) as Resource[]).filter(resource => resources[resource].error).map(resource => <Alert key={resource} variant="warning" aria-label={"Данные резервных копий: " + resource}>
@@ -493,8 +511,8 @@ function BackupsContent() {
filters={advancedFilters}
onFiltersChange={setAdvancedFilters}
onDownload={handleDownload}
onRestore={backup => { setOpError(null); setRestoreTarget(backup) }}
onDelete={backup => { setOpError(null); setDeleteTarget(backup) }}
onRestore={backup => { captureDialogFocus(); setOpError(null); setRestoreTarget(backup) }}
onDelete={backup => { captureDialogFocus(); setOpError(null); setDeleteTarget(backup) }}
onCreate={() => openCreate(false)}
canCreate={canCreate}
canWrite={canWrite && !opBusy && !scheduleSaveBusy && !backupJobId}
@@ -578,6 +596,7 @@ function BackupsContent() {
/>
<BackupDeleteDialog
returnFocus={returnDialogFocus}
backup={deleteTarget}
busy={opBusy}
error={opError}
@@ -585,6 +604,7 @@ function BackupsContent() {
onCancel={() => setDeleteTarget(null)}
/>
<BackupRestoreDialog
returnFocus={returnDialogFocus}
backup={restoreTarget}
busy={opBusy}
blocked={!restoreGuard.canStart}