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

@@ -14,6 +14,7 @@ import PageHeader from '../../components/PageHeader'
import ActionButtons from '../../components/ActionButtons'
import apiClient, { isEnvelope } from '../../api/client'
import { useAuthStore } from '../../stores/auth'
const { Text } = Typography
@@ -119,6 +120,7 @@ export default function BackendsPage() {
const { t } = useTranslation()
const navigate = useNavigate()
const qc = useQueryClient()
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
const { data, isLoading } = useQuery({ queryKey: ['backends'], queryFn: listBackends })
const { data: domains } = useQuery({ queryKey: ['domains'], queryFn: listDomains })
@@ -286,6 +288,7 @@ export default function BackendsPage() {
<Switch
size="small"
checked={v}
disabled={isViewer}
loading={quickToggle.isPending && quickToggle.variables?.id === row.id}
onChange={(checked) => quickToggle.mutate({ id: row.id, row, checked })}
/>
@@ -336,9 +339,11 @@ export default function BackendsPage() {
rowExpandable: (record) => !!record.id,
}}
extraActions={
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
{t('backends.addBackend')}
</Button>
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate} disabled={isViewer}>
{t('backends.addBackend')}
</Button>
</Tooltip>
}
emptyContent={
<EmptyState
@@ -346,9 +351,11 @@ export default function BackendsPage() {
title={t('backends.emptyTitle')}
description={t('backends.emptyDesc')}
action={
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
{t('backends.addBackend')}
</Button>
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate} disabled={isViewer}>
{t('backends.addBackend')}
</Button>
</Tooltip>
}
/>
}
@@ -396,6 +403,7 @@ export default function BackendsPage() {
function ServerPanel({ backendID }: { backendID: number }) {
const { t } = useTranslation()
const qc = useQueryClient()
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
const [open, setOpen] = useState(false)
const [editing, setEditing] = useState<BackendServer | null>(null)
const [form] = Form.useForm<ServerFormValues>()
@@ -462,6 +470,7 @@ function ServerPanel({ backendID }: { backendID: number }) {
<Switch
size="small"
checked={v}
disabled={isViewer}
loading={quickToggle.isPending && quickToggle.variables?.id === row.id}
onChange={(checked) => quickToggle.mutate({ id: row.id, row, checked })}
/>
@@ -471,17 +480,23 @@ function ServerPanel({ backendID }: { backendID: number }) {
title: t('common.actions'), key: 'a', width: 160,
render: (_, r) => (
<Space size={4}>
<Button size="small" onClick={() => {
setEditing(r)
form.setFieldsValue({
name: r.name, address: r.address, port: r.port,
weight: r.weight, backup: r.backup, active: r.active,
})
}}>{t('common.edit')}</Button>
<Popconfirm title={t('backends.server.deleteConfirm', { 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
setEditing(r)
form.setFieldsValue({
name: r.name, address: r.address, port: r.port,
weight: r.weight, backup: r.backup, active: r.active,
})
}}>{t('common.edit')}</Button>
</Tooltip>
{isViewer
? <Tooltip title={t('auth.viewerBadge')}><Button size="small" danger disabled>{t('common.delete')}</Button></Tooltip>
: <Popconfirm title={t('backends.server.deleteConfirm', { name: r.name })}
onConfirm={() => del.mutate(r.id)}>
<Button size="small" danger>{t('common.delete')}</Button>
</Popconfirm>
}
</Space>
),
},
@@ -491,13 +506,15 @@ function ServerPanel({ backendID }: { backendID: number }) {
<div>
<div className="mb-8" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Text type="secondary">{t('backends.server.intro')}</Text>
<Button size="small" type="primary" icon={<PlusOutlined />} onClick={() => {
setOpen(true)
form.resetFields()
form.setFieldsValue({ weight: 100, backup: false, active: true, port: 8080 })
}}>
{t('backends.server.add')}
</Button>
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
<Button size="small" type="primary" icon={<PlusOutlined />} disabled={isViewer} onClick={() => {
setOpen(true)
form.resetFields()
form.setFieldsValue({ weight: 100, backup: false, active: true, port: 8080 })
}}>
{t('backends.server.add')}
</Button>
</Tooltip>
</div>
<Table
size="small"