feat(rbac): Viewer-Rolle auf alle verbleibenden CRUD-Seiten durchsetzen — v1.1.92
DNS, NTP, ForwardProxy, RoutingRules, Networks (Interfaces + Routes), IPAddresses, WireGuard (Servers + Clients + Peers), Backups/RemoteTargets, SSL: Add-Buttons, quickToggle-Switches und Settings-Save-Buttons für Viewer deaktiviert. ActionButtons-Komponente schützt Edit/Delete bereits zentral; hier wurden nur die verbleibenden Mutationspunkte (Add, Switch, Submit) nachgezogen. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import DataTable from '../../components/DataTable'
|
||||
import EmptyState from '../../components/EmptyState'
|
||||
|
||||
import apiClient, { isEnvelope } from '../../api/client'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
|
||||
interface TLSCert {
|
||||
id: number
|
||||
@@ -95,6 +96,7 @@ function relativeAgo(ms: number, t: (k: string, v?: Record<string, unknown>) =>
|
||||
export default function SSLPage() {
|
||||
const { t } = useTranslation()
|
||||
const qc = useQueryClient()
|
||||
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
|
||||
|
||||
const { data: certs, isLoading } = useQuery({ queryKey: ['tls-certs'], queryFn: listCerts })
|
||||
const { data: domains } = useQuery({ queryKey: ['domains'], queryFn: listDomains })
|
||||
@@ -219,20 +221,24 @@ export default function SSLPage() {
|
||||
render: (_, row) => (
|
||||
<Space size={4}>
|
||||
{row.issuer === 'letsencrypt' && (
|
||||
<Popconfirm
|
||||
title={t('ssl.renewConfirmTitle')}
|
||||
description={t('ssl.renewConfirmDesc', { domain: row.domain })}
|
||||
okText={t('common.yes')} cancelText={t('common.no')}
|
||||
onConfirm={() => renewMut.mutate(row.domain)}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<ReloadOutlined />}
|
||||
loading={renewMut.isPending && renewMut.variables === row.domain}
|
||||
>
|
||||
{t('ssl.renewBtn')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
isViewer
|
||||
? <Tooltip title={t('auth.viewerBadge')}>
|
||||
<Button size="small" icon={<ReloadOutlined />} disabled>{t('ssl.renewBtn')}</Button>
|
||||
</Tooltip>
|
||||
: <Popconfirm
|
||||
title={t('ssl.renewConfirmTitle')}
|
||||
description={t('ssl.renewConfirmDesc', { domain: row.domain })}
|
||||
okText={t('common.yes')} cancelText={t('common.no')}
|
||||
onConfirm={() => renewMut.mutate(row.domain)}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<ReloadOutlined />}
|
||||
loading={renewMut.isPending && renewMut.variables === row.domain}
|
||||
>
|
||||
{t('ssl.renewBtn')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
)}
|
||||
<ActionButtons
|
||||
onDelete={() => delMut.mutate(row.id)}
|
||||
@@ -263,9 +269,11 @@ export default function SSLPage() {
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit" loading={issueMut.isPending}>
|
||||
{t('ssl.issueButton')}
|
||||
</Button>
|
||||
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||
<Button type="primary" htmlType="submit" disabled={isViewer} loading={issueMut.isPending}>
|
||||
{t('ssl.issueButton')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Form>
|
||||
</Card>
|
||||
),
|
||||
@@ -290,9 +298,11 @@ export default function SSLPage() {
|
||||
<Input.TextArea rows={6} placeholder="-----BEGIN PRIVATE KEY-----..." style={{ fontFamily: 'monospace', fontSize: 12 }} />
|
||||
</Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit" loading={uploadMut.isPending}>
|
||||
{t('ssl.uploadButton')}
|
||||
</Button>
|
||||
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||
<Button type="primary" htmlType="submit" disabled={isViewer} loading={uploadMut.isPending}>
|
||||
{t('ssl.uploadButton')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Typography.Text type="secondary">{t('ssl.uploadHint')}</Typography.Text>
|
||||
</Space>
|
||||
</Form>
|
||||
|
||||
Reference in New Issue
Block a user