feat(ui): Quick-Toggles, Config-Preview, Dashboard-Alerts, Domain-Detail-Health

Quick-Toggle-Switches (kein Modal nötig) für: Backends, Backend-Server,
DNS-Zonen, DNS-Records, Domains (active), Firewall-Rules, NAT-Rules,
Forward-Proxy ACLs, Routing-Rules.

Dashboard: Alert-Banner für komplett ausgefallene Backends (HAProxy-Stats)
und Domains im Maintenance-Mode.

Domain-Detail: HAProxy-Live-Health-Badge (15s Polling), TLS-Cert
ausstellen/erneuern direkt aus dem Detail, Routing-Rules-Panel inline.

Config-Preview (Settings): alle 4 Generatoren (haproxy, nftables, squid,
unbound) rendern via RenderToString ohne Disk-Write — GET /system/config-preview.

ActionButtons: Viewer-Rolle blendet Delete aus (RBAC-Ergänzung).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-24 08:55:22 +02:00
parent 386972366b
commit 99982ef9d1
23 changed files with 755 additions and 63 deletions

View File

@@ -1,6 +1,7 @@
import { Button, Popconfirm, Space, Tooltip } from 'antd'
import { DeleteOutlined, EditOutlined } from '@ant-design/icons'
import { useTranslation } from 'react-i18next'
import { useAuthStore } from '../stores/auth'
// ActionButtons is the standard "Edit / Delete" pair used at the
// end of every CRUD table row. Centralising it means we only style
@@ -8,6 +9,9 @@ import { useTranslation } from 'react-i18next'
//
// Either prop may be omitted to suppress that button — useful for
// rows that aren't editable (e.g. builtin services).
// Viewer-role accounts automatically get delete disabled — mutations
// are blocked at the API level too, but hiding the button avoids
// confusing "access denied" errors for read-only users.
interface ActionButtonsProps {
onEdit?: () => void
onDelete?: () => void
@@ -28,6 +32,14 @@ export default function ActionButtons({
editDisabledReason, deleteDisabledReason,
}: ActionButtonsProps) {
const { t } = useTranslation()
const role = useAuthStore((s) => s.user?.role)
const isViewer = role === 'viewer'
const delDisabled = deleteDisabled || isViewer
const delDisabledReason = isViewer
? t('auth.viewerBadge')
: deleteDisabledReason
return (
<Space size={4}>
{onEdit && (
@@ -42,8 +54,8 @@ export default function ActionButtons({
</Tooltip>
)}
{onDelete && (
deleteDisabled ? (
<Tooltip title={deleteDisabledReason ?? t('common.delete')}>
delDisabled ? (
<Tooltip title={delDisabledReason ?? t('common.delete')}>
<Button type="text" size="small" danger icon={<DeleteOutlined />} disabled />
</Tooltip>
) : (