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:
@@ -60,7 +60,7 @@ import (
|
|||||||
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.92"
|
var version = "1.1.93"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.92"
|
var version = "1.1.93"
|
||||||
|
|
||||||
const usage = `edgeguard-ctl — EdgeGuard CLI
|
const usage = `edgeguard-ctl — EdgeGuard CLI
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.92"
|
var version = "1.1.93"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import dayjs from 'dayjs'
|
|||||||
import apiClient, { isEnvelope } from '../../api/client'
|
import apiClient, { isEnvelope } from '../../api/client'
|
||||||
import PageHeader from '../../components/PageHeader'
|
import PageHeader from '../../components/PageHeader'
|
||||||
import EmptyState from '../../components/EmptyState'
|
import EmptyState from '../../components/EmptyState'
|
||||||
|
import { useAuthStore } from '../../stores/auth'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@ function sevTag(s: AlertEvent['severity']) {
|
|||||||
export default function AlertsPage() {
|
export default function AlertsPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
|
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
|
||||||
|
|
||||||
const channels = useQuery({
|
const channels = useQuery({
|
||||||
queryKey: ['alerts', 'channels'],
|
queryKey: ['alerts', 'channels'],
|
||||||
@@ -172,6 +174,7 @@ export default function AlertsPage() {
|
|||||||
<Switch
|
<Switch
|
||||||
size="small"
|
size="small"
|
||||||
checked={v}
|
checked={v}
|
||||||
|
disabled={isViewer}
|
||||||
loading={quickToggle.isPending && quickToggle.variables?.id === r.id}
|
loading={quickToggle.isPending && quickToggle.variables?.id === r.id}
|
||||||
onChange={(checked) => quickToggle.mutate({ id: r.id, row: r, checked })}
|
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,
|
title: t('common.actions'), key: 'a', width: 180,
|
||||||
render: (_, r) => (
|
render: (_, r) => (
|
||||||
<Space size={4}>
|
<Space size={4}>
|
||||||
<Button size="small" onClick={() => {
|
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||||
setEdit(r)
|
<Button size="small" disabled={isViewer} onClick={() => {
|
||||||
const settings = (r.settings ?? {}) as Record<string, unknown>
|
if (isViewer) return
|
||||||
form.setFieldsValue({
|
setEdit(r)
|
||||||
name: r.name, kind: r.kind, target: r.target, active: r.active,
|
const settings = (r.settings ?? {}) as Record<string, unknown>
|
||||||
smtp_host: settings.smtp_host as string,
|
form.setFieldsValue({
|
||||||
smtp_port: settings.smtp_port as number,
|
name: r.name, kind: r.kind, target: r.target, active: r.active,
|
||||||
username: settings.username as string,
|
smtp_host: settings.smtp_host as string,
|
||||||
password: settings.password as string,
|
smtp_port: settings.smtp_port as number,
|
||||||
from: settings.from as string,
|
username: settings.username as string,
|
||||||
use_tls: settings.use_tls as boolean,
|
password: settings.password as string,
|
||||||
})
|
from: settings.from as string,
|
||||||
}}>{t('common.edit')}</Button>
|
use_tls: settings.use_tls as boolean,
|
||||||
<Popconfirm title={t('alerts.confirmDelete', { name: r.name })}
|
})
|
||||||
onConfirm={() => del.mutate(r.id)}>
|
}}>{t('common.edit')}</Button>
|
||||||
<Button size="small" danger>{t('common.delete')}</Button>
|
</Tooltip>
|
||||||
</Popconfirm>
|
{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>
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -284,10 +293,12 @@ export default function AlertsPage() {
|
|||||||
label: t('alerts.tabs.channels'),
|
label: t('alerts.tabs.channels'),
|
||||||
children: (
|
children: (
|
||||||
<Card size="small" extra={
|
<Card size="small" extra={
|
||||||
<Button type="primary" size="small" icon={<PlusOutlined />}
|
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||||
onClick={openChannelCreate}>
|
<Button type="primary" size="small" icon={<PlusOutlined />}
|
||||||
{t('alerts.add')}
|
onClick={openChannelCreate} disabled={isViewer}>
|
||||||
</Button>
|
{t('alerts.add')}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
}>
|
}>
|
||||||
<Table size="small" rowKey="id" loading={channels.isFetching}
|
<Table size="small" rowKey="id" loading={channels.isFetching}
|
||||||
dataSource={channels.data ?? []} columns={chanColumns}
|
dataSource={channels.data ?? []} columns={chanColumns}
|
||||||
@@ -298,9 +309,11 @@ export default function AlertsPage() {
|
|||||||
title={t('alerts.emptyChannelsTitle')}
|
title={t('alerts.emptyChannelsTitle')}
|
||||||
description={t('alerts.emptyChannelsDesc')}
|
description={t('alerts.emptyChannelsDesc')}
|
||||||
action={
|
action={
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={openChannelCreate}>
|
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||||
{t('alerts.add')}
|
<Button type="primary" icon={<PlusOutlined />} onClick={openChannelCreate} disabled={isViewer}>
|
||||||
</Button>
|
{t('alerts.add')}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) }} />
|
) }} />
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import PageHeader from '../../components/PageHeader'
|
|||||||
import ActionButtons from '../../components/ActionButtons'
|
import ActionButtons from '../../components/ActionButtons'
|
||||||
|
|
||||||
import apiClient, { isEnvelope } from '../../api/client'
|
import apiClient, { isEnvelope } from '../../api/client'
|
||||||
|
import { useAuthStore } from '../../stores/auth'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
@@ -119,6 +120,7 @@ export default function BackendsPage() {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
|
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
|
||||||
|
|
||||||
const { data, isLoading } = useQuery({ queryKey: ['backends'], queryFn: listBackends })
|
const { data, isLoading } = useQuery({ queryKey: ['backends'], queryFn: listBackends })
|
||||||
const { data: domains } = useQuery({ queryKey: ['domains'], queryFn: listDomains })
|
const { data: domains } = useQuery({ queryKey: ['domains'], queryFn: listDomains })
|
||||||
@@ -286,6 +288,7 @@ export default function BackendsPage() {
|
|||||||
<Switch
|
<Switch
|
||||||
size="small"
|
size="small"
|
||||||
checked={v}
|
checked={v}
|
||||||
|
disabled={isViewer}
|
||||||
loading={quickToggle.isPending && quickToggle.variables?.id === row.id}
|
loading={quickToggle.isPending && quickToggle.variables?.id === row.id}
|
||||||
onChange={(checked) => quickToggle.mutate({ id: row.id, row, checked })}
|
onChange={(checked) => quickToggle.mutate({ id: row.id, row, checked })}
|
||||||
/>
|
/>
|
||||||
@@ -336,9 +339,11 @@ export default function BackendsPage() {
|
|||||||
rowExpandable: (record) => !!record.id,
|
rowExpandable: (record) => !!record.id,
|
||||||
}}
|
}}
|
||||||
extraActions={
|
extraActions={
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||||
{t('backends.addBackend')}
|
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate} disabled={isViewer}>
|
||||||
</Button>
|
{t('backends.addBackend')}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
}
|
}
|
||||||
emptyContent={
|
emptyContent={
|
||||||
<EmptyState
|
<EmptyState
|
||||||
@@ -346,9 +351,11 @@ export default function BackendsPage() {
|
|||||||
title={t('backends.emptyTitle')}
|
title={t('backends.emptyTitle')}
|
||||||
description={t('backends.emptyDesc')}
|
description={t('backends.emptyDesc')}
|
||||||
action={
|
action={
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||||
{t('backends.addBackend')}
|
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate} disabled={isViewer}>
|
||||||
</Button>
|
{t('backends.addBackend')}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -396,6 +403,7 @@ export default function BackendsPage() {
|
|||||||
function ServerPanel({ backendID }: { backendID: number }) {
|
function ServerPanel({ backendID }: { backendID: number }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
|
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [editing, setEditing] = useState<BackendServer | null>(null)
|
const [editing, setEditing] = useState<BackendServer | null>(null)
|
||||||
const [form] = Form.useForm<ServerFormValues>()
|
const [form] = Form.useForm<ServerFormValues>()
|
||||||
@@ -462,6 +470,7 @@ function ServerPanel({ backendID }: { backendID: number }) {
|
|||||||
<Switch
|
<Switch
|
||||||
size="small"
|
size="small"
|
||||||
checked={v}
|
checked={v}
|
||||||
|
disabled={isViewer}
|
||||||
loading={quickToggle.isPending && quickToggle.variables?.id === row.id}
|
loading={quickToggle.isPending && quickToggle.variables?.id === row.id}
|
||||||
onChange={(checked) => quickToggle.mutate({ id: row.id, row, checked })}
|
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,
|
title: t('common.actions'), key: 'a', width: 160,
|
||||||
render: (_, r) => (
|
render: (_, r) => (
|
||||||
<Space size={4}>
|
<Space size={4}>
|
||||||
<Button size="small" onClick={() => {
|
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||||
setEditing(r)
|
<Button size="small" disabled={isViewer} onClick={() => {
|
||||||
form.setFieldsValue({
|
if (isViewer) return
|
||||||
name: r.name, address: r.address, port: r.port,
|
setEditing(r)
|
||||||
weight: r.weight, backup: r.backup, active: r.active,
|
form.setFieldsValue({
|
||||||
})
|
name: r.name, address: r.address, port: r.port,
|
||||||
}}>{t('common.edit')}</Button>
|
weight: r.weight, backup: r.backup, active: r.active,
|
||||||
<Popconfirm title={t('backends.server.deleteConfirm', { name: r.name })}
|
})
|
||||||
onConfirm={() => del.mutate(r.id)}>
|
}}>{t('common.edit')}</Button>
|
||||||
<Button size="small" danger>{t('common.delete')}</Button>
|
</Tooltip>
|
||||||
</Popconfirm>
|
{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>
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -491,13 +506,15 @@ function ServerPanel({ backendID }: { backendID: number }) {
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-8" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div className="mb-8" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<Text type="secondary">{t('backends.server.intro')}</Text>
|
<Text type="secondary">{t('backends.server.intro')}</Text>
|
||||||
<Button size="small" type="primary" icon={<PlusOutlined />} onClick={() => {
|
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||||
setOpen(true)
|
<Button size="small" type="primary" icon={<PlusOutlined />} disabled={isViewer} onClick={() => {
|
||||||
form.resetFields()
|
setOpen(true)
|
||||||
form.setFieldsValue({ weight: 100, backup: false, active: true, port: 8080 })
|
form.resetFields()
|
||||||
}}>
|
form.setFieldsValue({ weight: 100, backup: false, active: true, port: 8080 })
|
||||||
{t('backends.server.add')}
|
}}>
|
||||||
</Button>
|
{t('backends.server.add')}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Table
|
<Table
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import apiClient, { isEnvelope } from '../../api/client'
|
import apiClient, { isEnvelope } from '../../api/client'
|
||||||
|
import { useAuthStore } from '../../stores/auth'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ function fmtDuration(start: string, end: string): string {
|
|||||||
export default function HistoryTab() {
|
export default function HistoryTab() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
|
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
|
||||||
const [msg, msgCtx] = message.useMessage()
|
const [msg, msgCtx] = message.useMessage()
|
||||||
|
|
||||||
const list = useQuery({
|
const list = useQuery({
|
||||||
@@ -223,30 +225,30 @@ export default function HistoryTab() {
|
|||||||
disabled={row.status !== 'success'}
|
disabled={row.status !== 'success'}
|
||||||
>{t('backups.download')}</Button>
|
>{t('backups.download')}</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Popconfirm
|
{isViewer
|
||||||
title={t('backups.confirmRestoreTitle')}
|
? <Tooltip title={t('auth.viewerBadge')}><Button size="small" icon={<UndoOutlined />} disabled>{t('backups.restore')}</Button></Tooltip>
|
||||||
description={t('backups.confirmRestoreDesc', { file: row.file })}
|
: <Popconfirm
|
||||||
okText={t('backups.restoreOk')}
|
title={t('backups.confirmRestoreTitle')}
|
||||||
okButtonProps={{ danger: true }}
|
description={t('backups.confirmRestoreDesc', { file: row.file })}
|
||||||
cancelText={t('common.cancel')}
|
okText={t('backups.restoreOk')}
|
||||||
onConfirm={() => startRestore(row)}
|
okButtonProps={{ danger: true }}
|
||||||
disabled={row.status !== 'success'}
|
cancelText={t('common.cancel')}
|
||||||
>
|
onConfirm={() => startRestore(row)}
|
||||||
<Button
|
disabled={row.status !== 'success'}
|
||||||
size="small"
|
>
|
||||||
icon={<UndoOutlined />}
|
<Button size="small" icon={<UndoOutlined />} disabled={row.status !== 'success'}>{t('backups.restore')}</Button>
|
||||||
disabled={row.status !== 'success'}
|
</Popconfirm>
|
||||||
>{t('backups.restore')}</Button>
|
}
|
||||||
</Popconfirm>
|
{isViewer
|
||||||
<Popconfirm
|
? <Tooltip title={t('auth.viewerBadge')}><Button size="small" danger icon={<DeleteOutlined />} disabled>{t('common.delete')}</Button></Tooltip>
|
||||||
title={t('backups.confirmDelete', { file: row.file })}
|
: <Popconfirm
|
||||||
onConfirm={() => del.mutate(row.id)}
|
title={t('backups.confirmDelete', { file: row.file })}
|
||||||
okButtonProps={{ danger: true }}
|
onConfirm={() => del.mutate(row.id)}
|
||||||
>
|
okButtonProps={{ danger: true }}
|
||||||
<Button size="small" danger icon={<DeleteOutlined />} loading={busyDelete === row.id}>
|
>
|
||||||
{t('common.delete')}
|
<Button size="small" danger icon={<DeleteOutlined />} loading={busyDelete === row.id}>{t('common.delete')}</Button>
|
||||||
</Button>
|
</Popconfirm>
|
||||||
</Popconfirm>
|
}
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -320,14 +322,17 @@ export default function HistoryTab() {
|
|||||||
{t('common.refresh')}
|
{t('common.refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Button
|
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||||
type="primary"
|
<Button
|
||||||
icon={<CloudUploadOutlined />}
|
type="primary"
|
||||||
loading={trigger.isPending}
|
icon={<CloudUploadOutlined />}
|
||||||
onClick={() => trigger.mutate()}
|
loading={trigger.isPending}
|
||||||
>
|
disabled={isViewer}
|
||||||
{t('backups.runNow')}
|
onClick={() => trigger.mutate()}
|
||||||
</Button>
|
>
|
||||||
|
{t('backups.runNow')}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<Card className="mb-16">
|
<Card className="mb-16">
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import ActionButtons from '../../components/ActionButtons'
|
|||||||
import StatusDot from '../../components/StatusDot'
|
import StatusDot from '../../components/StatusDot'
|
||||||
|
|
||||||
import apiClient, { isEnvelope } from '../../api/client'
|
import apiClient, { isEnvelope } from '../../api/client'
|
||||||
|
import { useAuthStore } from '../../stores/auth'
|
||||||
|
|
||||||
interface Domain {
|
interface Domain {
|
||||||
id: number
|
id: number
|
||||||
@@ -95,6 +96,7 @@ export default function DomainsPage() {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const isViewer = useAuthStore((s) => s.user?.role) === 'viewer'
|
||||||
|
|
||||||
const { data, isLoading } = useQuery({
|
const { data, isLoading } = useQuery({
|
||||||
queryKey: ['domains'],
|
queryKey: ['domains'],
|
||||||
@@ -234,6 +236,7 @@ export default function DomainsPage() {
|
|||||||
<Switch
|
<Switch
|
||||||
size="small"
|
size="small"
|
||||||
checked={v}
|
checked={v}
|
||||||
|
disabled={isViewer}
|
||||||
loading={quickToggle.isPending && quickToggle.variables?.id === row.id}
|
loading={quickToggle.isPending && quickToggle.variables?.id === row.id}
|
||||||
onChange={(checked) => quickToggle.mutate({ id: row.id, row, checked })}
|
onChange={(checked) => quickToggle.mutate({ id: row.id, row, checked })}
|
||||||
/>
|
/>
|
||||||
@@ -322,9 +325,11 @@ export default function DomainsPage() {
|
|||||||
dataSource={data ?? []}
|
dataSource={data ?? []}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
extraActions={
|
extraActions={
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||||
{t('domains.addDomain')}
|
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate} disabled={isViewer}>
|
||||||
</Button>
|
{t('domains.addDomain')}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
}
|
}
|
||||||
emptyContent={
|
emptyContent={
|
||||||
<EmptyState
|
<EmptyState
|
||||||
@@ -332,9 +337,11 @@ export default function DomainsPage() {
|
|||||||
title={t('domains.emptyTitle')}
|
title={t('domains.emptyTitle')}
|
||||||
description={t('domains.emptyDesc')}
|
description={t('domains.emptyDesc')}
|
||||||
action={
|
action={
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
<Tooltip title={isViewer ? t('auth.viewerBadge') : undefined}>
|
||||||
{t('domains.addDomain')}
|
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate} disabled={isViewer}>
|
||||||
</Button>
|
{t('domains.addDomain')}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user