fix(rbac): Viewer-Rolle in Detail-Pages + ActionButtons — Edit ebenfalls sperren

- ActionButtons: Edit-Button wird für Viewer wie Delete gesperrt (Tooltip zeigt Reason)
- Domains/Detail: isViewer-Flag an alle Sub-Panels weitergegeben; Save-, TLS-Cert-,
  Routing-Rules- und Headers-Buttons für Viewer disabled
- Backends/Detail: Save-Button + ServerPanel Add-Button für Viewer disabled
- i18n: domains.backendUp/backendDown Keys (waren noch hardkodiert)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-24 13:10:29 +02:00
parent 9708e4441b
commit 2d027b3044
9 changed files with 133 additions and 86 deletions

View File

@@ -12,6 +12,7 @@ import { useTranslation } from 'react-i18next'
import apiClient, { isEnvelope } from '../../api/client'
import PageHeader from '../../components/PageHeader'
import ActionButtons from '../../components/ActionButtons'
import { useAuthStore } from '../../stores/auth'
const { Text } = Typography
@@ -90,6 +91,7 @@ export default function BackendDetailPage() {
const navigate = useNavigate()
const qc = useQueryClient()
const backendID = Number(id)
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
const { data: backend, isLoading } = useQuery({
queryKey: ['backend', backendID],
@@ -213,9 +215,11 @@ export default function BackendDetailPage() {
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={update.isPending}>
{t('common.save')}
</Button>
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
<Button type="primary" htmlType="submit" loading={update.isPending} disabled={isViewer}>
{t('common.save')}
</Button>
</Tooltip>
</Form.Item>
</Form>
</Card>
@@ -223,7 +227,7 @@ export default function BackendDetailPage() {
<Col xs={24} lg={14}>
<Card size="small" title={t('backends.serversIn', { name: backend.name })}>
<ServerPanel backendID={backendID} haproxyStats={haproxyStats ?? []} />
<ServerPanel backendID={backendID} haproxyStats={haproxyStats ?? []} isViewer={isViewer} />
</Card>
</Col>
</Row>
@@ -231,7 +235,7 @@ export default function BackendDetailPage() {
)
}
function ServerPanel({ backendID, haproxyStats }: { backendID: number; haproxyStats: HAProxyStat[] }) {
function ServerPanel({ backendID, haproxyStats, isViewer }: { backendID: number; haproxyStats: HAProxyStat[]; isViewer: boolean }) {
const { t } = useTranslation()
const qc = useQueryClient()
const [open, setOpen] = useState(false)
@@ -347,12 +351,14 @@ function ServerPanel({ backendID, haproxyStats }: { backendID: number; haproxySt
return (
<>
<div className="mb-8" style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button type="primary" icon={<PlusOutlined />} size="small" 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 type="primary" icon={<PlusOutlined />} size="small" 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" rowKey="id" loading={isLoading}