From b93f4e63615fe684bad06f6b8a558a102f1a3f9c Mon Sep 17 00:00:00 2001 From: Debian Date: Sun, 24 May 2026 11:06:19 +0200 Subject: [PATCH] =?UTF-8?q?feat(wireguard):=20Quick-Toggles=20f=C3=BCr=20I?= =?UTF-8?q?nterfaces=20+=20Peers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inline Switch statt read-only StatusDot — konsistent mit allen anderen Seiten. Server-Interfaces, Client-Interfaces und Peers im Peer-Drawer. Co-Authored-By: Claude Sonnet 4.6 --- management-ui/src/pages/Wireguard/Clients.tsx | 21 ++++++++- management-ui/src/pages/Wireguard/Servers.tsx | 43 ++++++++++++++++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/management-ui/src/pages/Wireguard/Clients.tsx b/management-ui/src/pages/Wireguard/Clients.tsx index 9f07992..a467c5f 100644 --- a/management-ui/src/pages/Wireguard/Clients.tsx +++ b/management-ui/src/pages/Wireguard/Clients.tsx @@ -12,7 +12,6 @@ import apiClient, { isEnvelope } from '../../api/client' import DataTable from '../../components/DataTable' import EmptyState from '../../components/EmptyState' import ActionButtons from '../../components/ActionButtons' -import StatusDot from '../../components/StatusDot' import type { WGInterface } from './types' const { Text } = Typography @@ -76,6 +75,14 @@ export default function ClientsTab() { onSuccess: () => { void qc.invalidateQueries({ queryKey: ['wg', 'clients'] }) }, onError: (e: Error) => message.error(e.message), }) + const quickToggle = useMutation({ + mutationFn: async ({ id, row, checked }: { id: number; row: WGInterface; checked: boolean }) => { + const { id: _id, created_at: _ca, updated_at: _ua, ...body } = row + await apiClient.put(`/wireguard/interfaces/${id}`, { ...body, active: checked }) + }, + onSuccess: () => { void qc.invalidateQueries({ queryKey: ['wg', 'clients'] }) }, + onError: (e: Error) => message.error(e.message), + }) const cols: ColumnsType = [ { title: t('wg.iface.name'), dataIndex: 'name', key: 'name', render: (s: string) => {s} }, @@ -86,7 +93,17 @@ export default function ClientsTab() { render: (k?: string | null) => k ? {k.slice(0, 16)}… : '—', }, { title: t('wg.iface.zone'), dataIndex: 'role', key: 'role', render: (r: string) => {r} }, - { 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: WGInterface) => ( + quickToggle.mutate({ id: row.id, row, checked })} + /> + ), + }, { title: t('common.actions'), key: 'actions', render: (_, row) => ( diff --git a/management-ui/src/pages/Wireguard/Servers.tsx b/management-ui/src/pages/Wireguard/Servers.tsx index 772b203..12120b7 100644 --- a/management-ui/src/pages/Wireguard/Servers.tsx +++ b/management-ui/src/pages/Wireguard/Servers.tsx @@ -128,6 +128,14 @@ export default function ServersTab() { onSuccess: () => { void qc.invalidateQueries({ queryKey: ['wg', 'servers'] }) }, onError: (e: Error) => message.error(e.message), }) + const quickToggle = useMutation({ + mutationFn: async ({ id, row, checked }: { id: number; row: WGInterface; checked: boolean }) => { + const { id: _id, created_at: _ca, updated_at: _ua, ...body } = row + await apiClient.put(`/wireguard/interfaces/${id}`, { ...body, active: checked }) + }, + onSuccess: () => { void qc.invalidateQueries({ queryKey: ['wg', 'servers'] }) }, + onError: (e: Error) => message.error(e.message), + }) const cols: ColumnsType = [ { title: t('wg.iface.name'), dataIndex: 'name', key: 'name', render: (s: string) => {s} }, @@ -138,7 +146,17 @@ export default function ServersTab() { render: (k: string) => {k.slice(0, 16)}…, }, { title: t('wg.iface.zone'), dataIndex: 'role', key: 'role', render: (r: string) => {r} }, - { 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: WGInterface) => ( + quickToggle.mutate({ id: row.id, row, checked })} + /> + ), + }, { title: t('common.actions'), key: 'actions', render: (_, row) => ( @@ -354,6 +372,17 @@ function PeerDrawer({ iface, onClose }: PeerDrawerProps) { onSuccess: () => { void qc.invalidateQueries({ queryKey: ['wg', 'peers', ifaceID] }) }, onError: (e: Error) => message.error(e.message), }) + const peerToggle = useMutation({ + mutationFn: async ({ id, row, checked }: { id: number; row: WGPeer; checked: boolean }) => { + await apiClient.put(`/wireguard/peers/${id}`, { + name: row.name, allowed_ips: row.allowed_ips, + keepalive: row.keepalive, enabled: checked, + description: row.description, + }) + }, + onSuccess: () => { void qc.invalidateQueries({ queryKey: ['wg', 'peers', ifaceID] }) }, + onError: (e: Error) => message.error(e.message), + }) const cols: ColumnsType = [ { title: t('wg.peer.name'), dataIndex: 'name', key: 'name' }, @@ -390,7 +419,17 @@ function PeerDrawer({ iface, onClose }: PeerDrawerProps) { return ▼{fmtBytes(live.transfer_rx)} ▲{fmtBytes(live.transfer_tx)} }, }, - { title: t('common.active'), dataIndex: 'enabled', key: 'enabled', render: (v: boolean) => }, + { + title: t('common.active'), dataIndex: 'enabled', key: 'enabled', width: 80, + render: (v: boolean, row: WGPeer) => ( + peerToggle.mutate({ id: row.id, row, checked })} + /> + ), + }, { title: t('common.actions'), key: 'actions', render: (_, row) => (