feat(rbac): RBAC-Audit abschliessen — Viewer-Sperre für alle verbleibenden Seiten (1.1.93)

Domains, Backends (inkl. ServerPanel), Backups/History und Alerts:
isViewer-Check + disabled Switches, Tooltip-wrapped Add-Buttons,
bedingte Delete/Restore-Render (Viewer → disabled Tooltip statt Popconfirm).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-24 16:39:24 +02:00
parent d3a3b93c1b
commit 0856c2fc10
8 changed files with 132 additions and 90 deletions

View File

@@ -16,6 +16,7 @@ import { useTranslation } from 'react-i18next'
import dayjs from 'dayjs'
import apiClient, { isEnvelope } from '../../api/client'
import { useAuthStore } from '../../stores/auth'
const { Text } = Typography
@@ -51,6 +52,7 @@ function fmtDuration(start: string, end: string): string {
export default function HistoryTab() {
const { t } = useTranslation()
const qc = useQueryClient()
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
const [msg, msgCtx] = message.useMessage()
const list = useQuery({
@@ -223,30 +225,30 @@ export default function HistoryTab() {
disabled={row.status !== 'success'}
>{t('backups.download')}</Button>
</Tooltip>
<Popconfirm
title={t('backups.confirmRestoreTitle')}
description={t('backups.confirmRestoreDesc', { file: row.file })}
okText={t('backups.restoreOk')}
okButtonProps={{ danger: true }}
cancelText={t('common.cancel')}
onConfirm={() => startRestore(row)}
disabled={row.status !== 'success'}
>
<Button
size="small"
icon={<UndoOutlined />}
disabled={row.status !== 'success'}
>{t('backups.restore')}</Button>
</Popconfirm>
<Popconfirm
title={t('backups.confirmDelete', { file: row.file })}
onConfirm={() => del.mutate(row.id)}
okButtonProps={{ danger: true }}
>
<Button size="small" danger icon={<DeleteOutlined />} loading={busyDelete === row.id}>
{t('common.delete')}
</Button>
</Popconfirm>
{isViewer
? <Tooltip title={t('auth.viewerBadge')}><Button size="small" icon={<UndoOutlined />} disabled>{t('backups.restore')}</Button></Tooltip>
: <Popconfirm
title={t('backups.confirmRestoreTitle')}
description={t('backups.confirmRestoreDesc', { file: row.file })}
okText={t('backups.restoreOk')}
okButtonProps={{ danger: true }}
cancelText={t('common.cancel')}
onConfirm={() => startRestore(row)}
disabled={row.status !== 'success'}
>
<Button size="small" icon={<UndoOutlined />} disabled={row.status !== 'success'}>{t('backups.restore')}</Button>
</Popconfirm>
}
{isViewer
? <Tooltip title={t('auth.viewerBadge')}><Button size="small" danger icon={<DeleteOutlined />} disabled>{t('common.delete')}</Button></Tooltip>
: <Popconfirm
title={t('backups.confirmDelete', { file: row.file })}
onConfirm={() => del.mutate(row.id)}
okButtonProps={{ danger: true }}
>
<Button size="small" danger icon={<DeleteOutlined />} loading={busyDelete === row.id}>{t('common.delete')}</Button>
</Popconfirm>
}
</Space>
),
},
@@ -320,14 +322,17 @@ export default function HistoryTab() {
{t('common.refresh')}
</Button>
</Tooltip>
<Button
type="primary"
icon={<CloudUploadOutlined />}
loading={trigger.isPending}
onClick={() => trigger.mutate()}
>
{t('backups.runNow')}
</Button>
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
<Button
type="primary"
icon={<CloudUploadOutlined />}
loading={trigger.isPending}
disabled={isViewer}
onClick={() => trigger.mutate()}
>
{t('backups.runNow')}
</Button>
</Tooltip>
</Space>
<Card className="mb-16">