From 64676053213859ac3c796ef3b7105e5119c96510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Mon, 16 Mar 2026 11:55:27 +0100 Subject: [PATCH] add missing actions for rules table --- web/messages/en/common.json | 1 + web/src/pages/RulesPage/RulesTable.tsx | 95 +++++++++++++++---- .../pages/RulesPage/tabs/RulesDeployedTab.tsx | 1 + .../pages/RulesPage/tabs/RulesPendingTab.tsx | 1 + 4 files changed, 82 insertions(+), 16 deletions(-) diff --git a/web/messages/en/common.json b/web/messages/en/common.json index ee9d2ddaac..8af5a20a1d 100644 --- a/web/messages/en/common.json +++ b/web/messages/en/common.json @@ -42,6 +42,7 @@ "controls_verify_code": "Verify code", "controls_complete": "Complete", "controls_copy_clipboard": "Copy to clipboard", + "controls_deploy": "Deploy", "state_disabled": "Disabled", "state_warning": "Warning", "state_active": "Active", diff --git a/web/src/pages/RulesPage/RulesTable.tsx b/web/src/pages/RulesPage/RulesTable.tsx index 067608a952..fb4af4a27d 100644 --- a/web/src/pages/RulesPage/RulesTable.tsx +++ b/web/src/pages/RulesPage/RulesTable.tsx @@ -5,7 +5,7 @@ import { getCoreRowModel, useReactTable, } from '@tanstack/react-table'; -import { flat } from 'radashi'; +import { cloneDeep, flat } from 'radashi'; import { useCallback, useMemo, useState } from 'react'; import './RulesTable.scss'; import { m } from '../../paraglide/messages'; @@ -29,13 +29,17 @@ import { BadgeVariant } from '../../shared/defguard-ui/components/Badge/types'; import { Button } from '../../shared/defguard-ui/components/Button/Button'; import type { ButtonProps } from '../../shared/defguard-ui/components/Button/types'; import { EmptyStateFlexible } from '../../shared/defguard-ui/components/EmptyStateFlexible/EmptyStateFlexible'; -import type { MenuItemsGroup } from '../../shared/defguard-ui/components/Menu/types'; +import type { + MenuItemProps, + MenuItemsGroup, +} from '../../shared/defguard-ui/components/Menu/types'; import { Search } from '../../shared/defguard-ui/components/Search/Search'; import { tableEditColumnSize } from '../../shared/defguard-ui/components/table/consts'; import { TableBody } from '../../shared/defguard-ui/components/table/TableBody/TableBody'; import { TableCell } from '../../shared/defguard-ui/components/table/TableCell/TableCell'; import { TableEditCell } from '../../shared/defguard-ui/components/table/TableEditCell/TableEditCell'; import { TableTop } from '../../shared/defguard-ui/components/table/TableTop/TableTop'; +import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar'; import { isPresent } from '../../shared/defguard-ui/utils/isPresent'; import { canUseBusinessFeature, licenseActionCheck } from '../../shared/utils/license'; @@ -63,9 +67,16 @@ type Props = { data: AclRule[]; title: string; buttonProps: ButtonProps; + variant: 'deployed' | 'pending'; enableSearch?: boolean; }; +const toggleRulePromise = async (id: number) => { + const rule = cloneDeep((await api.acl.rule.getRule(id)).data); + rule.enabled = !rule.enabled; + return api.acl.rule.editRule(rule); +}; + export const RulesTable = ({ title, buttonProps, @@ -78,6 +89,7 @@ export const RulesTable = ({ locations, data, license, + variant, }: Props) => { const navigate = useNavigate(); @@ -88,6 +100,23 @@ export const RulesTable = ({ }, }); + const { mutate: toggleRule } = useMutation({ + mutationFn: toggleRulePromise, + meta: { + invalidate: ['acl'], + }, + }); + + const { mutate: deployRule } = useMutation({ + mutationFn: api.acl.rule.applyRules, + onSuccess: () => { + Snackbar.default(`Rule deployed`); + }, + meta: { + invalidate: ['acl'], + }, + }); + const [search, setSearch] = useState(''); const renderPermissionCell = useCallback( @@ -300,24 +329,55 @@ export const RulesTable = ({ enableResizing: false, cell: (info) => { const row = info.row.original; - const menuItems: MenuItemsGroup[] = [ + const topItems: MenuItemProps[] = [ { - items: [ - { - icon: 'edit', - text: m.controls_edit(), + icon: 'edit', + text: m.controls_edit(), + onClick: () => { + licenseActionCheck(canUseBusinessFeature(license), () => { + navigate({ + to: '/acl/edit-rule', + search: { + rule: row.id, + }, + }); + }); + }, + }, + ]; + switch (variant) { + case 'deployed': + if (row.enabled) { + topItems.push({ + icon: 'disabled', + text: m.controls_disable(), onClick: () => { - licenseActionCheck(canUseBusinessFeature(license), () => { - navigate({ - to: '/acl/edit-rule', - search: { - rule: row.id, - }, - }); - }); + toggleRule(row.id); }, + }); + } else { + topItems.push({ + icon: 'check', + text: m.controls_enable(), + onClick: () => { + toggleRule(row.id); + }, + }); + } + break; + case 'pending': + topItems.push({ + icon: 'deploy', + text: m.controls_deploy(), + onClick: () => { + deployRule([row.id]); }, - ], + }); + break; + } + const menuItems: MenuItemsGroup[] = [ + { + items: topItems, }, { items: [ @@ -347,6 +407,9 @@ export const RulesTable = ({ navigate, renderStatusCell, license, + variant, + toggleRule, + deployRule, ], ); diff --git a/web/src/pages/RulesPage/tabs/RulesDeployedTab.tsx b/web/src/pages/RulesPage/tabs/RulesDeployedTab.tsx index a4c52d50f0..8a4a0342cb 100644 --- a/web/src/pages/RulesPage/tabs/RulesDeployedTab.tsx +++ b/web/src/pages/RulesPage/tabs/RulesDeployedTab.tsx @@ -65,6 +65,7 @@ export const RulesDeployedTab = () => { isPresent(devices) && license !== undefined && ( { isPresent(devices) && license !== undefined && (