From e419eceab8dac8af9e9aa2b488b288199b4a7ec5 Mon Sep 17 00:00:00 2001 From: Debian Date: Sun, 24 May 2026 11:09:48 +0200 Subject: [PATCH] =?UTF-8?q?feat(ui):=20Quick-Toggles=20=C3=BCberall=20verv?= =?UTF-8?q?ollst=C3=A4ndigt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NTP-Pools, User-Accounts, Backend-Server im Detail-Panel und Routing-Rules im Domain-Detail-Panel — alle aktiv-Felder sind jetzt inline schaltbar, kein Modale nötig. Co-Authored-By: Claude Sonnet 4.6 --- management-ui/src/pages/Backends/Detail.tsx | 18 +++++++++++++++-- management-ui/src/pages/Domains/Detail.tsx | 15 +++++++++++++- management-ui/src/pages/NTP/index.tsx | 22 ++++++++++++++++++--- management-ui/src/pages/Users/index.tsx | 18 ++++++++++++++--- 4 files changed, 64 insertions(+), 9 deletions(-) diff --git a/management-ui/src/pages/Backends/Detail.tsx b/management-ui/src/pages/Backends/Detail.tsx index 90b4d48..b8ae25f 100644 --- a/management-ui/src/pages/Backends/Detail.tsx +++ b/management-ui/src/pages/Backends/Detail.tsx @@ -12,7 +12,6 @@ import { useTranslation } from 'react-i18next' import apiClient, { isEnvelope } from '../../api/client' import PageHeader from '../../components/PageHeader' import ActionButtons from '../../components/ActionButtons' -import StatusDot from '../../components/StatusDot' const { Text } = Typography @@ -275,6 +274,14 @@ function ServerPanel({ backendID, haproxyStats }: { backendID: number; haproxySt }, onError: (e: Error) => message.error(e.message), }) + const quickToggle = useMutation({ + mutationFn: async ({ id, row, checked }: { id: number; row: BackendServer; checked: boolean }) => { + const { id: _id, ...body } = row + await apiClient.put(`/backend-servers/${id}`, { ...body, active: checked }) + }, + onSuccess: () => { void qc.invalidateQueries({ queryKey: ['backend-servers', backendID] }) }, + onError: (e: Error) => message.error(e.message), + }) const cols: ColumnsType = [ { @@ -292,7 +299,14 @@ function ServerPanel({ backendID, haproxyStats }: { backendID: number; haproxySt }, { title: t('backends.active'), dataIndex: 'active', width: 80, - render: (v: boolean) => , + render: (v: boolean, r: BackendServer) => ( + quickToggle.mutate({ id: r.id, row: r, checked })} + /> + ), }, { title: 'Live', key: 'live', width: 160, diff --git a/management-ui/src/pages/Domains/Detail.tsx b/management-ui/src/pages/Domains/Detail.tsx index c9e1960..d26ac5b 100644 --- a/management-ui/src/pages/Domains/Detail.tsx +++ b/management-ui/src/pages/Domains/Detail.tsx @@ -406,6 +406,12 @@ function RoutingRulesPanel({ domainID, domainName }: { domainID: number; domainN onSuccess: invalidate, onError: (e: Error) => message.error(e.message), }) + const quickToggle = useMutation({ + mutationFn: async ({ id, row, checked }: { id: number; row: RoutingRule; checked: boolean }) => + apiClient.put(`/routing-rules/${id}`, { ...row, active: checked }), + onSuccess: invalidate, + onError: (e: Error) => message.error(e.message), + }) const backendLabel = (id: number) => { const b = backends?.find(x => x.id === id) @@ -418,7 +424,14 @@ function RoutingRulesPanel({ domainID, domainName }: { domainID: number; domainN { title: t('routing.priority'), dataIndex: 'priority', key: 'priority', width: 90 }, { title: t('routing.active'), dataIndex: 'active', key: 'active', width: 80, - render: (v: boolean) => , + render: (v: boolean, r: RoutingRule) => ( + quickToggle.mutate({ id: r.id, row: r, checked })} + /> + ), }, { title: t('common.actions'), key: 'a', width: 90, diff --git a/management-ui/src/pages/NTP/index.tsx b/management-ui/src/pages/NTP/index.tsx index 5837d3c..86ad0ce 100644 --- a/management-ui/src/pages/NTP/index.tsx +++ b/management-ui/src/pages/NTP/index.tsx @@ -12,7 +12,6 @@ import DataTable from '../../components/DataTable' import EmptyState from '../../components/EmptyState' import PageHeader from '../../components/PageHeader' import ActionButtons from '../../components/ActionButtons' -import StatusDot from '../../components/StatusDot' const { Text } = Typography @@ -184,6 +183,14 @@ function PoolsTab() { onSuccess: () => { void qc.invalidateQueries({ queryKey: ['ntp', 'pools'] }) }, onError: (e: Error) => message.error(e.message), }) + const quickToggle = useMutation({ + mutationFn: async ({ id, row, checked }: { id: number; row: Pool; checked: boolean }) => { + const { id: _id, ...body } = row + await apiClient.put(`/ntp/pools/${id}`, { ...body, active: checked }) + }, + onSuccess: () => { void qc.invalidateQueries({ queryKey: ['ntp', 'pools'] }) }, + onError: (e: Error) => message.error(e.message), + }) const cols: ColumnsType = [ { title: t('ntp.pool.kind'), dataIndex: 'kind', key: 'kind', @@ -201,8 +208,17 @@ function PoolsTab() { ) }, { title: t('ntp.pool.description'), dataIndex: 'description', key: 'description', render: (v?: string | null) => v ?? '—' }, - { title: t('common.active'), dataIndex: 'active', key: 'active', - render: (v: boolean) => }, + { + title: t('common.active'), dataIndex: 'active', key: 'active', width: 80, + render: (v: boolean, row: Pool) => ( + quickToggle.mutate({ id: row.id, row, checked })} + /> + ), + }, { title: t('common.actions'), key: 'actions', render: (_, row) => ( diff --git a/management-ui/src/pages/Users/index.tsx b/management-ui/src/pages/Users/index.tsx index c1374b0..5667e16 100644 --- a/management-ui/src/pages/Users/index.tsx +++ b/management-ui/src/pages/Users/index.tsx @@ -13,7 +13,6 @@ import PageHeader from '../../components/PageHeader' import DataTable from '../../components/DataTable' import EmptyState from '../../components/EmptyState' import ActionButtons from '../../components/ActionButtons' -import StatusDot from '../../components/StatusDot' const { Text } = Typography @@ -74,6 +73,12 @@ export default function UsersPage() { onSuccess: invalidate, onError: (e: Error) => message.error(e.message), }) + const quickToggle = useMutation({ + mutationFn: ({ id, row, checked }: { id: number; row: User; checked: boolean }) => + apiClient.put(`/users/${id}`, { email: row.email, role: row.role, active: checked }), + onSuccess: invalidate, + onError: (e: Error) => message.error(e.message), + }) const roleOptions = [ { value: 'admin', label: t('users.roleAdmin') }, @@ -99,8 +104,15 @@ export default function UsersPage() { ), }, { - title: t('users.active'), dataIndex: 'active', key: 'active', width: 70, - render: (v: boolean) => , + title: t('users.active'), dataIndex: 'active', key: 'active', width: 80, + render: (v: boolean, row: User) => ( + quickToggle.mutate({ id: row.id, row, checked })} + /> + ), }, { title: t('users.lastLogin'), key: 'lastLogin', width: 160,