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

@@ -13,6 +13,7 @@ import dayjs from 'dayjs'
import apiClient, { isEnvelope } from '../../api/client'
import PageHeader from '../../components/PageHeader'
import EmptyState from '../../components/EmptyState'
import { useAuthStore } from '../../stores/auth'
const { Text } = Typography
@@ -66,6 +67,7 @@ function sevTag(s: AlertEvent['severity']) {
export default function AlertsPage() {
const { t } = useTranslation()
const qc = useQueryClient()
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
const channels = useQuery({
queryKey: ['alerts', 'channels'],
@@ -172,6 +174,7 @@ export default function AlertsPage() {
<Switch
size="small"
checked={v}
disabled={isViewer}
loading={quickToggle.isPending && quickToggle.variables?.id === r.id}
onChange={(checked) => quickToggle.mutate({ id: r.id, row: r, checked })}
/>
@@ -181,23 +184,29 @@ export default function AlertsPage() {
title: t('common.actions'), key: 'a', width: 180,
render: (_, r) => (
<Space size={4}>
<Button size="small" onClick={() => {
setEdit(r)
const settings = (r.settings ?? {}) as Record<string, unknown>
form.setFieldsValue({
name: r.name, kind: r.kind, target: r.target, active: r.active,
smtp_host: settings.smtp_host as string,
smtp_port: settings.smtp_port as number,
username: settings.username as string,
password: settings.password as string,
from: settings.from as string,
use_tls: settings.use_tls as boolean,
})
}}>{t('common.edit')}</Button>
<Popconfirm title={t('alerts.confirmDelete', { name: r.name })}
onConfirm={() => del.mutate(r.id)}>
<Button size="small" danger>{t('common.delete')}</Button>
</Popconfirm>
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
<Button size="small" disabled={isViewer} onClick={() => {
if (isViewer) return
setEdit(r)
const settings = (r.settings ?? {}) as Record<string, unknown>
form.setFieldsValue({
name: r.name, kind: r.kind, target: r.target, active: r.active,
smtp_host: settings.smtp_host as string,
smtp_port: settings.smtp_port as number,
username: settings.username as string,
password: settings.password as string,
from: settings.from as string,
use_tls: settings.use_tls as boolean,
})
}}>{t('common.edit')}</Button>
</Tooltip>
{isViewer
? <Tooltip title={t('auth.viewerBadge')}><Button size="small" danger disabled>{t('common.delete')}</Button></Tooltip>
: <Popconfirm title={t('alerts.confirmDelete', { name: r.name })}
onConfirm={() => del.mutate(r.id)}>
<Button size="small" danger>{t('common.delete')}</Button>
</Popconfirm>
}
</Space>
),
},
@@ -284,10 +293,12 @@ export default function AlertsPage() {
label: t('alerts.tabs.channels'),
children: (
<Card size="small" extra={
<Button type="primary" size="small" icon={<PlusOutlined />}
onClick={openChannelCreate}>
{t('alerts.add')}
</Button>
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
<Button type="primary" size="small" icon={<PlusOutlined />}
onClick={openChannelCreate} disabled={isViewer}>
{t('alerts.add')}
</Button>
</Tooltip>
}>
<Table size="small" rowKey="id" loading={channels.isFetching}
dataSource={channels.data ?? []} columns={chanColumns}
@@ -298,9 +309,11 @@ export default function AlertsPage() {
title={t('alerts.emptyChannelsTitle')}
description={t('alerts.emptyChannelsDesc')}
action={
<Button type="primary" icon={<PlusOutlined />} onClick={openChannelCreate}>
{t('alerts.add')}
</Button>
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
<Button type="primary" icon={<PlusOutlined />} onClick={openChannelCreate} disabled={isViewer}>
{t('alerts.add')}
</Button>
</Tooltip>
}
/>
) }} />