From 4d0b6b2ac559081ad684ed9d355bc25e489f2655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 25 Feb 2026 17:33:59 +0100 Subject: [PATCH 01/13] fix how any_address toggle is handled --- .../defguard_core/src/enterprise/firewall/mod.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall/mod.rs b/crates/defguard_core/src/enterprise/firewall/mod.rs index afe826623e..442750b8a0 100644 --- a/crates/defguard_core/src/enterprise/firewall/mod.rs +++ b/crates/defguard_core/src/enterprise/firewall/mod.rs @@ -238,7 +238,11 @@ async fn get_manual_destination_rules( } // prepare destination addresses - let (dest_addrs_v4, dest_addrs_v6) = process_destination_addrs(&addresses, &address_ranges); + let (dest_addrs_v4, dest_addrs_v6) = if any_address { + (Vec::new(), Vec::new()) + } else { + process_destination_addrs(&addresses, &address_ranges) + }; // prepare destination ports let destination_ports = if any_port { @@ -319,8 +323,11 @@ async fn get_predefined_destination_rules( let alias_destination_ranges = destination.get_destination_ranges(&mut *conn).await?; // combine destination addrs - let (dest_addrs_v4, dest_addrs_v6) = - process_alias_destination_addrs(&destination.addresses, &alias_destination_ranges); + let (dest_addrs_v4, dest_addrs_v6) = if destination.any_address { + (Vec::new(), Vec::new()) + } else { + process_alias_destination_addrs(&destination.addresses, &alias_destination_ranges) + }; // process alias ports let destination_ports = if destination.any_port { @@ -430,6 +437,7 @@ fn create_rules( debug!("ALLOW rule generated from ACL: {rule:?}"); Some(rule) }; + // prepare DENY rule // it should specify only the destination addrs to block all remaining traffic let deny = FirewallRule { From 51c4da5e3ad939ce72bb7f26b77e10f554453dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 25 Feb 2026 17:34:08 +0100 Subject: [PATCH 02/13] fix test --- crates/defguard_core/src/enterprise/firewall/tests/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/defguard_core/src/enterprise/firewall/tests/mod.rs b/crates/defguard_core/src/enterprise/firewall/tests/mod.rs index 40e6d233ba..78c8ca896d 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests/mod.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests/mod.rs @@ -1801,6 +1801,7 @@ async fn test_alias_kinds(_: PgPoolOptions, options: PgConnectOptions) { addresses: vec!["192.168.1.0/24".parse().unwrap()], allow_all_users: true, use_manual_destination_settings: true, + any_address: false, ..Default::default() } .save(&pool) From 83d64393520681d27b64c63aa10c9b0bd7972b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 25 Feb 2026 17:34:24 +0100 Subject: [PATCH 03/13] add test to confirm rule toggle works --- .../enterprise/firewall/tests/destination.rs | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall/tests/destination.rs b/crates/defguard_core/src/enterprise/firewall/tests/destination.rs index fd8e62bab7..0794ac17de 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests/destination.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests/destination.rs @@ -1,11 +1,19 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; -use defguard_proto::enterprise::firewall::{IpAddress, IpRange, ip_address::Address}; +use defguard_common::db::{NoId, models::WireguardNetwork, setup_pool}; +use defguard_proto::enterprise::firewall::{ + FirewallPolicy, IpAddress, IpRange, ip_address::Address, +}; +use rand::thread_rng; +use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use crate::enterprise::{ - db::models::acl::AclRuleDestinationRange, firewall::process_destination_addrs, + db::models::acl::{AclRule, AclRuleDestinationRange, RuleState}, + firewall::{process_destination_addrs, try_get_location_firewall_config}, }; +use super::{create_acl_rule, create_test_users_and_devices, set_test_license_business}; + #[test] fn test_process_destination_addrs_v4() { // Test data with mixed IPv4 and IPv6 networks @@ -115,3 +123,76 @@ fn test_process_destination_addrs_v6() { let ipv4_only = process_destination_addrs(&["192.168.1.0/24".parse().unwrap()], &[]); assert!(ipv4_only.1.is_empty()); } + +#[sqlx::test] +async fn test_any_address_overwrites_manual_destination( + _: PgPoolOptions, + options: PgConnectOptions, +) { + set_test_license_business(); + let pool = setup_pool(options).await; + + let mut rng = thread_rng(); + + let location = WireguardNetwork { + id: NoId, + acl_enabled: true, + address: vec!["10.0.0.0/16".parse().unwrap()], + ..Default::default() + } + .save(&pool) + .await + .unwrap(); + + create_test_users_and_devices(&mut rng, &pool, vec![&location]).await; + + let acl_rule = AclRule { + id: NoId, + name: "any destination rule".to_string(), + state: RuleState::Applied, + allow_all_users: true, + any_address: true, + addresses: vec!["192.168.1.0/24".parse().unwrap()], + use_manual_destination_settings: true, + ..Default::default() + }; + + create_acl_rule( + &pool, + acl_rule, + vec![location.id], + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + vec![ + ( + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10)), + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 20)), + ), + ], + Vec::new(), + ) + .await; + + let mut conn = pool.acquire().await.unwrap(); + let generated_firewall_rules = try_get_location_firewall_config(&location, &mut conn) + .await + .unwrap() + .unwrap() + .rules; + + assert_eq!(generated_firewall_rules.len(), 2); + + let allow_rule = &generated_firewall_rules[0]; + assert_eq!(allow_rule.verdict, i32::from(FirewallPolicy::Allow)); + assert!(!allow_rule.source_addrs.is_empty()); + assert!(allow_rule.destination_addrs.is_empty()); + + let deny_rule = &generated_firewall_rules[1]; + assert_eq!(deny_rule.verdict, i32::from(FirewallPolicy::Deny)); + assert!(deny_rule.source_addrs.is_empty()); + assert!(deny_rule.destination_addrs.is_empty()); +} From ffc17b38906ca36affacd5abc055973903c193fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 25 Feb 2026 17:48:23 +0100 Subject: [PATCH 04/13] also test any_address toggle in destination aliases --- .../enterprise/firewall/tests/destination.rs | 127 +++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall/tests/destination.rs b/crates/defguard_core/src/enterprise/firewall/tests/destination.rs index 0794ac17de..98cadc882c 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests/destination.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests/destination.rs @@ -8,7 +8,10 @@ use rand::thread_rng; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use crate::enterprise::{ - db::models::acl::{AclRule, AclRuleDestinationRange, RuleState}, + db::models::acl::{ + AclAlias, AclAliasDestinationRange, AclRule, AclRuleDestinationRange, AliasKind, + RuleState, + }, firewall::{process_destination_addrs, try_get_location_firewall_config}, }; @@ -184,11 +187,131 @@ async fn test_any_address_overwrites_manual_destination( .unwrap() .rules; + let expected_source_addrs = [ + IpAddress { + address: Some(Address::IpRange(IpRange { + start: "10.0.1.1".to_string(), + end: "10.0.1.2".to_string(), + })), + }, + IpAddress { + address: Some(Address::IpRange(IpRange { + start: "10.0.2.1".to_string(), + end: "10.0.2.2".to_string(), + })), + }, + ]; + + assert_eq!(generated_firewall_rules.len(), 2); + + let allow_rule = &generated_firewall_rules[0]; + assert_eq!(allow_rule.verdict, i32::from(FirewallPolicy::Allow)); + assert_eq!(allow_rule.source_addrs, expected_source_addrs); + assert!(allow_rule.destination_addrs.is_empty()); + + let deny_rule = &generated_firewall_rules[1]; + assert_eq!(deny_rule.verdict, i32::from(FirewallPolicy::Deny)); + assert!(deny_rule.source_addrs.is_empty()); + assert!(deny_rule.destination_addrs.is_empty()); +} + +#[sqlx::test] +async fn test_any_address_overwrites_destination_alias_addrs( + _: PgPoolOptions, + options: PgConnectOptions, +) { + set_test_license_business(); + let pool = setup_pool(options).await; + + let mut rng = thread_rng(); + + let location = WireguardNetwork { + id: NoId, + acl_enabled: true, + address: vec!["10.0.0.0/16".parse().unwrap()], + ..Default::default() + } + .save(&pool) + .await + .unwrap(); + + create_test_users_and_devices(&mut rng, &pool, vec![&location]).await; + + let destination_alias = AclAlias { + id: NoId, + name: "any destination alias".to_string(), + kind: AliasKind::Destination, + any_address: true, + any_port: true, + any_protocol: true, + addresses: vec!["10.1.0.0/24".parse().unwrap()], + ..Default::default() + } + .save(&pool) + .await + .unwrap(); + + AclAliasDestinationRange { + id: NoId, + alias_id: destination_alias.id, + start: IpAddr::V4(Ipv4Addr::new(10, 2, 0, 10)), + end: IpAddr::V4(Ipv4Addr::new(10, 2, 0, 20)), + } + .save(&pool) + .await + .unwrap(); + + let acl_rule = AclRule { + id: NoId, + name: "any destination alias rule".to_string(), + state: RuleState::Applied, + allow_all_users: true, + use_manual_destination_settings: false, + ..Default::default() + }; + + create_acl_rule( + &pool, + acl_rule, + vec![location.id], + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + vec![destination_alias.id], + ) + .await; + + let mut conn = pool.acquire().await.unwrap(); + let generated_firewall_rules = try_get_location_firewall_config(&location, &mut conn) + .await + .unwrap() + .unwrap() + .rules; + + let expected_source_addrs = [ + IpAddress { + address: Some(Address::IpRange(IpRange { + start: "10.0.1.1".to_string(), + end: "10.0.1.2".to_string(), + })), + }, + IpAddress { + address: Some(Address::IpRange(IpRange { + start: "10.0.2.1".to_string(), + end: "10.0.2.2".to_string(), + })), + }, + ]; + assert_eq!(generated_firewall_rules.len(), 2); let allow_rule = &generated_firewall_rules[0]; assert_eq!(allow_rule.verdict, i32::from(FirewallPolicy::Allow)); - assert!(!allow_rule.source_addrs.is_empty()); + assert_eq!(allow_rule.source_addrs, expected_source_addrs); assert!(allow_rule.destination_addrs.is_empty()); let deny_rule = &generated_firewall_rules[1]; From 1be97f9deb2f012063955383aa47bf7afee5a23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 25 Feb 2026 22:13:15 +0100 Subject: [PATCH 05/13] restore access restrictions section --- web/src/pages/CERulePage/CERulePage.tsx | 243 ++++++++++++++++++------ web/src/pages/CERulePage/style.scss | 34 +++- 2 files changed, 209 insertions(+), 68 deletions(-) diff --git a/web/src/pages/CERulePage/CERulePage.tsx b/web/src/pages/CERulePage/CERulePage.tsx index 9ac7a5a167..27307c9e6a 100644 --- a/web/src/pages/CERulePage/CERulePage.tsx +++ b/web/src/pages/CERulePage/CERulePage.tsx @@ -58,6 +58,7 @@ import { import { aclDestinationValidator, aclPortsValidator } from '../../shared/validators'; import aliasesEmptyImage from './assets/aliases-empty-icon.png'; import emptyDestinationIconSrc from './assets/empty-destinations-icon.png'; +import { Checkbox } from '../../shared/defguard-ui/components/Checkbox/Checkbox'; const getProtocolName = (key: AclProtocolValue) => AclProtocolName[key]; @@ -253,7 +254,22 @@ const Content = ({ rule: initialRule }: Props) => { return []; }, [networkDevices]); - const [_restrictionsPresent, _setRestrictionsPresent] = useState(false); + const [restrictUsers, setRestrictUsers] = useState(() => + isPresent(initialRule) + ? initialRule.deny_all_users || initialRule.denied_users.length > 0 + : false, + ); + const [restrictGroups, setRestrictGroups] = useState(() => + isPresent(initialRule) + ? initialRule.deny_all_groups || initialRule.denied_groups.length > 0 + : false, + ); + const [restrictDevices, setRestrictDevices] = useState(() => + isPresent(initialRule) + ? initialRule.deny_all_network_devices || + initialRule.denied_network_devices.length > 0 + : false, + ); // const [manualDestination, setManualDestination] = useState(false); const formSchema = useMemo( @@ -417,13 +433,18 @@ const Content = ({ rule: initialRule }: Props) => { }, onSubmit: async ({ value }) => { const toSend = cloneDeep(value); - // FIXME: When restrictions section is reworked - toSend.deny_all_network_devices = false; - toSend.deny_all_users = false; - toSend.deny_all_groups = false; - toSend.denied_network_devices = []; - toSend.denied_groups = []; - toSend.denied_users = []; + if (!restrictUsers) { + toSend.deny_all_users = false; + toSend.denied_users = []; + } + if (!restrictGroups) { + toSend.deny_all_groups = false; + toSend.denied_groups = []; + } + if (!restrictDevices) { + toSend.deny_all_network_devices = false; + toSend.denied_network_devices = []; + } if (isPresent(initialRule)) { await editRule({ ...toSend, @@ -797,66 +818,162 @@ const Content = ({ rule: initialRule }: Props) => { )} - {/* + {`Restrictions`} - -

{`If needed, you may exclude specific users, groups, or devices from accessing this location.`}

+ +

{`Choose who or what should be blocked from accessing this location.`}

- { - setRestrictionsPresent((s) => !s); - }} - text="Add restriction settings" - /> - - - {isPresent(usersOptions) && ( - - {(field) => ( - `Users ${counter}`} - editText={`Edit users`} - modalTitle="Select restricted users" - options={usersOptions} - /> - )} - - )} - - {isPresent(groupsOptions) && ( - - {(field) => ( - `Groups ${counter}`} - editText="Edit groups" - modalTitle="Select restricted groups" - toggleText="Exclude specific groups" - /> - )} - - )} - - {isPresent(networkDevicesOptions) && ( - - {(field) => ( - `Devices ${counter}`} - editText="Edit devices" - modalTitle="Select restricted devices" - toggleText="Exclude specific network devices" - /> - )} - - )} - -
*/} + {isPresent(usersOptions) && ( +
+
+ { + setRestrictUsers((current) => !current); + }} + text="Limit access for users" + /> +
+ +
+
+ + {(field) => } + + + {(field) => ( + + )} + +
+ s.values.deny_all_users === false && restrictUsers} + > + {(open) => ( + + {isPresent(usersOptions) && ( + + {(field) => ( + `Users ${counter}`} + editText="Edit users" + modalTitle="Select restricted users" + options={usersOptions} + /> + )} + + )} + + )} + +
+
+
+ )} + + {isPresent(groupsOptions) && ( +
+
+ { + setRestrictGroups((current) => !current); + }} + text="Limit access for groups" + /> +
+ +
+
+ + {(field) => } + + + {(field) => ( + + )} + +
+ s.values.deny_all_groups === false && restrictGroups} + > + {(open) => ( + + {isPresent(groupsOptions) && ( + + {(field) => ( + `Groups ${counter}`} + editText="Edit groups" + modalTitle="Select restricted groups" + options={groupsOptions} + /> + )} + + )} + + )} + +
+
+ +
+ )} + {isPresent(networkDevicesOptions) && ( +
+
+ { + setRestrictDevices((current) => !current); + }} + text="Limit access for devices" + /> +
+ +
+
+ + {(field) => ( + + )} + + + {(field) => ( + + )} + +
+ + s.values.deny_all_network_devices === false && restrictDevices + } + > + {(open) => ( + + {isPresent(networkDevicesOptions) && ( + + {(field) => ( + `Devices ${counter}`} + editText="Edit devices" + modalTitle="Select restricted devices" + options={networkDevicesOptions} + /> + )} + + )} + + )} + +
+
+
+ )} + ({ isSubmitting: s.isSubmitting })}> {({ isSubmitting }) => ( diff --git a/web/src/pages/CERulePage/style.scss b/web/src/pages/CERulePage/style.scss index 7e0c4db899..612c2849d2 100644 --- a/web/src/pages/CERulePage/style.scss +++ b/web/src/pages/CERulePage/style.scss @@ -19,7 +19,7 @@ .selected-destinations { padding-top: var(--spacing-2xl); - & > .top { + &>.top { padding-bottom: var(--spacing-sm); user-select: none; @@ -29,7 +29,7 @@ } } - & > .items-track { + &>.items-track { display: flex; flex-flow: column; row-gap: var(--spacing-md); @@ -39,7 +39,7 @@ .alias-data-block { padding-top: var(--spacing-lg); - & > .top { + &>.top { padding-bottom: var(--spacing-sm); user-select: none; @@ -49,12 +49,12 @@ } } - & > .content-track { + &>.content-track { display: flex; flex-flow: row; gap: var(--spacing-sm); - & > button { + &>button { background-color: transparent; border: 0; border-radius: 0; @@ -75,6 +75,30 @@ } } } + + .restriction-block { + display: flex; + flex-direction: column; + gap: var(--spacing-lg); + } + + .restriction-toggle { + display: flex; + align-items: center; + } + + .restriction-body { + display: flex; + flex-direction: column; + gap: var(--spacing-md); + } + + .restriction-radio { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); + } + } .selection-section .item .destination-selection-item { From 0dcc4b5e28c635defc3d234ab49ab26ee5d4e537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 25 Feb 2026 22:54:10 +0100 Subject: [PATCH 06/13] formatting --- web/src/pages/CERulePage/CERulePage.tsx | 12 ++++++++---- web/src/pages/CERulePage/style.scss | 11 +++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/web/src/pages/CERulePage/CERulePage.tsx b/web/src/pages/CERulePage/CERulePage.tsx index 27307c9e6a..5aee7ce82b 100644 --- a/web/src/pages/CERulePage/CERulePage.tsx +++ b/web/src/pages/CERulePage/CERulePage.tsx @@ -30,6 +30,7 @@ import type { import { AppText } from '../../shared/defguard-ui/components/AppText/AppText'; import { Button } from '../../shared/defguard-ui/components/Button/Button'; import { ButtonsGroup } from '../../shared/defguard-ui/components/ButtonsGroup/ButtonsGroup'; +import { Checkbox } from '../../shared/defguard-ui/components/Checkbox/Checkbox'; import { CheckboxIndicator } from '../../shared/defguard-ui/components/CheckboxIndicator/CheckboxIndicator'; import { Chip } from '../../shared/defguard-ui/components/Chip/Chip'; import { Divider } from '../../shared/defguard-ui/components/Divider/Divider'; @@ -58,7 +59,6 @@ import { import { aclDestinationValidator, aclPortsValidator } from '../../shared/validators'; import aliasesEmptyImage from './assets/aliases-empty-icon.png'; import emptyDestinationIconSrc from './assets/empty-destinations-icon.png'; -import { Checkbox } from '../../shared/defguard-ui/components/Checkbox/Checkbox'; const getProtocolName = (key: AclProtocolValue) => AclProtocolName[key]; @@ -267,7 +267,7 @@ const Content = ({ rule: initialRule }: Props) => { const [restrictDevices, setRestrictDevices] = useState(() => isPresent(initialRule) ? initialRule.deny_all_network_devices || - initialRule.denied_network_devices.length > 0 + initialRule.denied_network_devices.length > 0 : false, ); // const [manualDestination, setManualDestination] = useState(false); @@ -841,7 +841,9 @@ const Content = ({ rule: initialRule }: Props) => {
- {(field) => } + {(field) => ( + + )} {(field) => ( @@ -889,7 +891,9 @@ const Content = ({ rule: initialRule }: Props) => {
- {(field) => } + {(field) => ( + + )} {(field) => ( diff --git a/web/src/pages/CERulePage/style.scss b/web/src/pages/CERulePage/style.scss index 612c2849d2..153c1853ff 100644 --- a/web/src/pages/CERulePage/style.scss +++ b/web/src/pages/CERulePage/style.scss @@ -19,7 +19,7 @@ .selected-destinations { padding-top: var(--spacing-2xl); - &>.top { + & > .top { padding-bottom: var(--spacing-sm); user-select: none; @@ -29,7 +29,7 @@ } } - &>.items-track { + & > .items-track { display: flex; flex-flow: column; row-gap: var(--spacing-md); @@ -39,7 +39,7 @@ .alias-data-block { padding-top: var(--spacing-lg); - &>.top { + & > .top { padding-bottom: var(--spacing-sm); user-select: none; @@ -49,12 +49,12 @@ } } - &>.content-track { + & > .content-track { display: flex; flex-flow: row; gap: var(--spacing-sm); - &>button { + & > button { background-color: transparent; border: 0; border-radius: 0; @@ -98,7 +98,6 @@ flex-direction: column; gap: var(--spacing-sm); } - } .selection-section .item .destination-selection-item { From 30f6e5f997bf8217e7b27559c6dd9ccd1b4a34d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 25 Feb 2026 23:05:51 +0100 Subject: [PATCH 07/13] add missing props --- web/src/pages/CERulePage/CERulePage.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/src/pages/CERulePage/CERulePage.tsx b/web/src/pages/CERulePage/CERulePage.tsx index 5aee7ce82b..4b0d3a7b1d 100644 --- a/web/src/pages/CERulePage/CERulePage.tsx +++ b/web/src/pages/CERulePage/CERulePage.tsx @@ -860,6 +860,8 @@ const Content = ({ rule: initialRule }: Props) => { {(field) => ( {}} counterText={(counter) => `Users ${counter}`} editText="Edit users" modalTitle="Select restricted users" @@ -910,6 +912,8 @@ const Content = ({ rule: initialRule }: Props) => { {(field) => ( {}} counterText={(counter) => `Groups ${counter}`} editText="Edit groups" modalTitle="Select restricted groups" @@ -962,6 +966,8 @@ const Content = ({ rule: initialRule }: Props) => { {(field) => ( {}} counterText={(counter) => `Devices ${counter}`} editText="Edit devices" modalTitle="Select restricted devices" From 90f751e6523cf5bbda2c7215f2dc8ed1f3222e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 26 Feb 2026 16:27:35 +0100 Subject: [PATCH 08/13] split form sections into separate components --- web/src/pages/CERulePage/CERulePage.tsx | 663 +----------------- .../components/DestinationSection.tsx | 387 ++++++++++ .../components/GeneralSettingsSection.tsx | 107 +++ .../components/PermissionsSection.tsx | 123 ++++ .../components/RestrictionsSection.tsx | 236 +++++++ web/src/pages/CERulePage/types.ts | 44 ++ 6 files changed, 935 insertions(+), 625 deletions(-) create mode 100644 web/src/pages/CERulePage/components/DestinationSection.tsx create mode 100644 web/src/pages/CERulePage/components/GeneralSettingsSection.tsx create mode 100644 web/src/pages/CERulePage/components/PermissionsSection.tsx create mode 100644 web/src/pages/CERulePage/components/RestrictionsSection.tsx create mode 100644 web/src/pages/CERulePage/types.ts diff --git a/web/src/pages/CERulePage/CERulePage.tsx b/web/src/pages/CERulePage/CERulePage.tsx index 4b0d3a7b1d..1d6ab92c1f 100644 --- a/web/src/pages/CERulePage/CERulePage.tsx +++ b/web/src/pages/CERulePage/CERulePage.tsx @@ -3,51 +3,22 @@ import { useStore } from '@tanstack/react-form'; import { useMutation, useQuery } from '@tanstack/react-query'; import { useRouter } from '@tanstack/react-router'; import { intersection } from 'lodash-es'; -import { cloneDeep, flat, omit } from 'radashi'; +import { cloneDeep, omit } from 'radashi'; import { useMemo, useState } from 'react'; import z from 'zod'; import { m } from '../../paraglide/messages'; import api from '../../shared/api/api'; -import { - type AclDestination, - AclProtocolName, - type AclProtocolValue, - type AclRule, - aclProtocolValues, - type NetworkLocation, -} from '../../shared/api/types'; -import { Card } from '../../shared/components/Card/Card'; +import type { AclRule } from '../../shared/api/types'; import { Controls } from '../../shared/components/Controls/Controls'; -import { DescriptionBlock } from '../../shared/components/DescriptionBlock/DescriptionBlock'; -import { DestinationDismissibleBox } from '../../shared/components/DestinationDismissibleBox/DestinationDismissibleBox'; -import { DestinationLabel } from '../../shared/components/DestinationLabel/DestinationLabel'; import { EditPage } from '../../shared/components/EditPage/EditPage'; -import { useSelectionModal } from '../../shared/components/modals/SelectionModal/useSelectionModal'; -import type { - SelectionOption, - SelectionSectionCustomRender, -} from '../../shared/components/SelectionSection/type'; -import { AppText } from '../../shared/defguard-ui/components/AppText/AppText'; +import type { SelectionOption } from '../../shared/components/SelectionSection/type'; import { Button } from '../../shared/defguard-ui/components/Button/Button'; -import { ButtonsGroup } from '../../shared/defguard-ui/components/ButtonsGroup/ButtonsGroup'; -import { Checkbox } from '../../shared/defguard-ui/components/Checkbox/Checkbox'; -import { CheckboxIndicator } from '../../shared/defguard-ui/components/CheckboxIndicator/CheckboxIndicator'; -import { Chip } from '../../shared/defguard-ui/components/Chip/Chip'; import { Divider } from '../../shared/defguard-ui/components/Divider/Divider'; -import { Fold } from '../../shared/defguard-ui/components/Fold/Fold'; -import { Icon, type IconKindValue } from '../../shared/defguard-ui/components/Icon'; -import { MarkedSection } from '../../shared/defguard-ui/components/MarkedSection/MarkedSection'; -import { SizedBox } from '../../shared/defguard-ui/components/SizedBox/SizedBox'; import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar'; -import { TooltipContent } from '../../shared/defguard-ui/providers/tooltip/TooltipContent'; -import { TooltipProvider } from '../../shared/defguard-ui/providers/tooltip/TooltipContext'; -import { TooltipTrigger } from '../../shared/defguard-ui/providers/tooltip/TooltipTrigger'; -import { TextStyle, ThemeSpacing, ThemeVariable } from '../../shared/defguard-ui/types'; +import { ThemeSpacing } from '../../shared/defguard-ui/types'; import { isPresent } from '../../shared/defguard-ui/utils/isPresent'; import { useAppForm } from '../../shared/form'; import { formChangeLogic } from '../../shared/formLogic'; -import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; -import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; import { getAppliedAliasesQueryOptions, getAppliedDestinationsQueryOptions, @@ -59,62 +30,11 @@ import { import { aclDestinationValidator, aclPortsValidator } from '../../shared/validators'; import aliasesEmptyImage from './assets/aliases-empty-icon.png'; import emptyDestinationIconSrc from './assets/empty-destinations-icon.png'; - -const getProtocolName = (key: AclProtocolValue) => AclProtocolName[key]; - -const renderDestinationSelectionItem: SelectionSectionCustomRender< - number, - AclDestination -> = ({ active, onClick, option }) => ( -
- - {isPresent(option.meta) && ( - AclProtocolName[protocol]) - .join(',')} - /> - )} -
-); - -const renderLocationSelectionItem: SelectionSectionCustomRender< - number, - NetworkLocation -> = ({ active, onClick, option }) => { - const icon: IconKindValue = 'check'; - return ( -
- - {isPresent(option.meta) && ( - <> -
-

{option.meta?.name}

-
- - - - - - {!option.meta.acl_enabled && ( -

{`Location access unmanaged (ACL disabled)`}

- )} - {option.meta.acl_enabled && option.meta.acl_default_allow && ( -

{`Location access allowed by default - network traffic not explicitly defined by the rules will be passed.`}

- )} - {option.meta.acl_enabled && !option.meta.acl_default_allow && ( -

{`Location access denied by default - network traffic not explicitly defined by the rules will be blocked.`}

- )} -
-
- - )} -
- ); -}; +import { DestinationSection } from './components/DestinationSection'; +import { GeneralSettingsSection } from './components/GeneralSettingsSection'; +import { PermissionsSection } from './components/PermissionsSection'; +import { RestrictionsSection } from './components/RestrictionsSection'; +import type { CERuleFormValues } from './types'; type Props = { rule?: AclRule; @@ -381,7 +301,7 @@ const Content = ({ rule: initialRule }: Props) => { [], ); - type FormFields = z.infer; + type FormFields = CERuleFormValues; const defaultValues = useMemo((): FormFields => { if (isPresent(initialRule)) { @@ -478,512 +398,37 @@ const Content = ({ rule: initialRule }: Props) => { }} > - - {`General settings`} - - - {(field) => } - - - -

{`Specify which locations this rule applies to. You can select all available locations or choose specific ones based on your requirements.`}

-
- - s.values.all_locations}> - {(allValue) => ( - - {(field) => ( - `Locations ${counter}`} - editText="Edit locations" - modalTitle="Select locations" - toggleText="Include all locations" - selectionCustomItemRender={renderLocationSelectionItem} - toggleValue={allValue} - onToggleChange={(value) => { - form.setFieldValue('all_locations', value); - }} - /> - )} - - )} - -
+ - - {`Destination`} - - - {`You can add additional destinations to this rule to extend its scope. These destinations are configured separately in the 'Destinations' section.`} - - - {isPresent(destinations) && destinations.length === 0 && ( -
-
- -
-

{`You don't have any predefined destinations yet — add them in the 'Destinations' section to create reusable elements for defining destinations across multiple firewall ACL rules.`}

-
- )} - {isPresent(destinations) && destinations.length > 0 && ( - - {(field) => { - const selectedDestinations = - destinations?.filter((destination) => - field.state.value.has(destination.id), - ) ?? []; - return ( - <> - - )} -
-
- ); -}; diff --git a/web/src/pages/CERulePage/components/DestinationSection.tsx b/web/src/pages/CERulePage/components/DestinationSection.tsx new file mode 100644 index 0000000000..470fbf0610 --- /dev/null +++ b/web/src/pages/CERulePage/components/DestinationSection.tsx @@ -0,0 +1,387 @@ +import { flat } from 'radashi'; +import type { + AclAlias, + AclDestination, + AclProtocolValue, +} from '../../../shared/api/types'; +import { AclProtocolName, aclProtocolValues } from '../../../shared/api/types'; +import { Card } from '../../../shared/components/Card/Card'; +import { DescriptionBlock } from '../../../shared/components/DescriptionBlock/DescriptionBlock'; +import { DestinationDismissibleBox } from '../../../shared/components/DestinationDismissibleBox/DestinationDismissibleBox'; +import { DestinationLabel } from '../../../shared/components/DestinationLabel/DestinationLabel'; +import { useSelectionModal } from '../../../shared/components/modals/SelectionModal/useSelectionModal'; +import type { + SelectionOption, + SelectionSectionCustomRender, +} from '../../../shared/components/SelectionSection/type'; +import { AppText } from '../../../shared/defguard-ui/components/AppText/AppText'; +import { Button } from '../../../shared/defguard-ui/components/Button/Button'; +import { ButtonsGroup } from '../../../shared/defguard-ui/components/ButtonsGroup/ButtonsGroup'; +import { CheckboxIndicator } from '../../../shared/defguard-ui/components/CheckboxIndicator/CheckboxIndicator'; +import { Chip } from '../../../shared/defguard-ui/components/Chip/Chip'; +import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider'; +import { Fold } from '../../../shared/defguard-ui/components/Fold/Fold'; +import { MarkedSection } from '../../../shared/defguard-ui/components/MarkedSection/MarkedSection'; +import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { + TextStyle, + ThemeSpacing, + ThemeVariable, +} from '../../../shared/defguard-ui/types'; +import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; +import { openModal } from '../../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../../shared/hooks/modalControls/modalTypes'; +import type { CERuleFormApi, FormFieldRenderer } from '../types'; + +const getProtocolName = (key: AclProtocolValue) => AclProtocolName[key]; + +const renderDestinationSelectionItem: SelectionSectionCustomRender< + number, + AclDestination +> = ({ active, onClick, option }) => ( +
+ + {isPresent(option.meta) && ( + AclProtocolName[protocol]) + .join(',')} + /> + )} +
+); + +type Props = { + form: unknown; + destinations: AclDestination[] | undefined; + destinationsOptions: SelectionOption[] | undefined; + aliasesOptions: SelectionOption[] | undefined; + selectedAliases: AclAlias[]; + aliasesEmptyImage: string; + emptyDestinationIconSrc: string; +}; + +export const DestinationSection = ({ + form, + destinations, + destinationsOptions, + aliasesOptions, + selectedAliases, + aliasesEmptyImage, + emptyDestinationIconSrc, +}: Props) => { + const Form = form as CERuleFormApi; + + return ( + + {`Destination`} + + + {`You can add additional destinations to this rule to extend its scope. These destinations are configured separately in the 'Destinations' section.`} + + + {isPresent(destinations) && destinations.length === 0 && ( +
+
+ +
+

{`You don't have any predefined destinations yet — add them in the 'Destinations' section to create reusable elements for defining destinations across multiple firewall ACL rules.`}

+
+ )} + {isPresent(destinations) && destinations.length > 0 && ( + + {(field) => { + const destinationField = field as { + state: { value: Set }; + handleChange: (value: Set) => void; + }; + const selectedDestinations = + destinations?.filter((destination) => + destinationField.state.value.has(destination.id), + ) ?? []; + return ( + <> + + )} +
+
+ ); +}; diff --git a/web/src/pages/CERulePage/components/GeneralSettingsSection.tsx b/web/src/pages/CERulePage/components/GeneralSettingsSection.tsx new file mode 100644 index 0000000000..cb042cdc48 --- /dev/null +++ b/web/src/pages/CERulePage/components/GeneralSettingsSection.tsx @@ -0,0 +1,107 @@ +import type { NetworkLocation } from '../../../shared/api/types'; +import { DescriptionBlock } from '../../../shared/components/DescriptionBlock/DescriptionBlock'; +import type { + SelectionOption, + SelectionSectionCustomRender, +} from '../../../shared/components/SelectionSection/type'; +import { AppText } from '../../../shared/defguard-ui/components/AppText/AppText'; +import { CheckboxIndicator } from '../../../shared/defguard-ui/components/CheckboxIndicator/CheckboxIndicator'; +import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider'; +import { Icon, type IconKindValue } from '../../../shared/defguard-ui/components/Icon'; +import { MarkedSection } from '../../../shared/defguard-ui/components/MarkedSection/MarkedSection'; +import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { TooltipContent } from '../../../shared/defguard-ui/providers/tooltip/TooltipContent'; +import { TooltipProvider } from '../../../shared/defguard-ui/providers/tooltip/TooltipContext'; +import { TooltipTrigger } from '../../../shared/defguard-ui/providers/tooltip/TooltipTrigger'; +import { TextStyle, ThemeSpacing } from '../../../shared/defguard-ui/types'; +import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; +import type { CERuleFormApi, FormFieldRenderer } from '../types'; + +const renderLocationSelectionItem: SelectionSectionCustomRender< + number, + NetworkLocation +> = ({ active, onClick, option }) => { + const icon: IconKindValue = 'check'; + return ( +
+ + {isPresent(option.meta) && ( + <> +
+

{option.meta?.name}

+
+ + + + + + {!option.meta.acl_enabled && ( +

{`Location access unmanaged (ACL disabled)`}

+ )} + {option.meta.acl_enabled && option.meta.acl_default_allow && ( +

{`Location access allowed by default - network traffic not explicitly defined by the rules will be passed.`}

+ )} + {option.meta.acl_enabled && !option.meta.acl_default_allow && ( +

{`Location access denied by default - network traffic not explicitly defined by the rules will be blocked.`}

+ )} +
+
+ + )} +
+ ); +}; + +type Props = { + form: unknown; + locationsOptions: SelectionOption[]; +}; + +export const GeneralSettingsSection = ({ form, locationsOptions }: Props) => { + const Form = form as CERuleFormApi; + + return ( + + {`General settings`} + + + {(field) => { + const Field = field as FormFieldRenderer; + return ; + }} + + + +

{`Specify which locations this rule applies to. You can select all available locations or choose specific ones based on your requirements.`}

+
+ + + (s as { values: { all_locations: boolean } }).values.all_locations + } + > + {(allValue) => ( + + {(field) => { + const Field = field as FormFieldRenderer; + return ( + `Locations ${counter}`} + editText="Edit locations" + modalTitle="Select locations" + toggleText="Include all locations" + selectionCustomItemRender={renderLocationSelectionItem} + toggleValue={allValue} + onToggleChange={(value: boolean) => { + Form.setFieldValue('all_locations', value); + }} + /> + ); + }} + + )} + +
+ ); +}; diff --git a/web/src/pages/CERulePage/components/PermissionsSection.tsx b/web/src/pages/CERulePage/components/PermissionsSection.tsx new file mode 100644 index 0000000000..dcbb717e44 --- /dev/null +++ b/web/src/pages/CERulePage/components/PermissionsSection.tsx @@ -0,0 +1,123 @@ +import { DescriptionBlock } from '../../../shared/components/DescriptionBlock/DescriptionBlock'; +import type { SelectionOption } from '../../../shared/components/SelectionSection/type'; +import { AppText } from '../../../shared/defguard-ui/components/AppText/AppText'; +import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider'; +import { MarkedSection } from '../../../shared/defguard-ui/components/MarkedSection/MarkedSection'; +import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { TextStyle, ThemeSpacing } from '../../../shared/defguard-ui/types'; +import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; +import type { CERuleFormApi, FormFieldRenderer } from '../types'; + +type Props = { + form: unknown; + usersOptions: SelectionOption[] | undefined; + groupsOptions: SelectionOption[] | undefined; + networkDevicesOptions: SelectionOption[] | undefined; +}; + +export const PermissionsSection = ({ + form, + usersOptions, + groupsOptions, + networkDevicesOptions, +}: Props) => { + const Form = form as CERuleFormApi; + + return ( + + {`Permissions`} + + +

{`Define who should be granted access. Only the entities you list here will be allowed through.`}

+
+ + {isPresent(usersOptions) && ( + + (s as { values: { allow_all_users: boolean } }).values.allow_all_users + } + > + {(allowAllValue) => ( + + {(field) => { + const Field = field as FormFieldRenderer; + return ( + `Users ${counter}`} + editText={`Edit users`} + modalTitle="Select allowed users" + options={usersOptions} + onToggleChange={(value: boolean) => { + Form.setFieldValue('allow_all_users', value); + }} + /> + ); + }} + + )} + + )} + + {isPresent(groupsOptions) && ( + + (s as { values: { allow_all_groups: boolean } }).values.allow_all_groups + } + > + {(allAllowedValue) => ( + + {(field) => { + const Field = field as FormFieldRenderer; + return ( + { + Form.setFieldValue('allow_all_groups', value); + }} + options={groupsOptions} + counterText={(counter: number) => `Groups ${counter}`} + editText="Edit groups" + modalTitle="Select allowed groups" + toggleText="All groups have access" + /> + ); + }} + + )} + + )} + + {isPresent(networkDevicesOptions) && ( + + (s as { values: { allow_all_network_devices: boolean } }).values + .allow_all_network_devices + } + > + {(allowAllValue) => ( + + {(field) => { + const Field = field as FormFieldRenderer; + return ( + { + Form.setFieldValue('allow_all_network_devices', value); + }} + options={networkDevicesOptions} + counterText={(counter: number) => `Devices ${counter}`} + editText="Edit devices" + modalTitle="Select allowed devices" + toggleText="All network devices have access" + /> + ); + }} + + )} + + )} +
+ ); +}; diff --git a/web/src/pages/CERulePage/components/RestrictionsSection.tsx b/web/src/pages/CERulePage/components/RestrictionsSection.tsx new file mode 100644 index 0000000000..fafe8a9def --- /dev/null +++ b/web/src/pages/CERulePage/components/RestrictionsSection.tsx @@ -0,0 +1,236 @@ +import type { Dispatch, SetStateAction } from 'react'; +import { DescriptionBlock } from '../../../shared/components/DescriptionBlock/DescriptionBlock'; +import type { SelectionOption } from '../../../shared/components/SelectionSection/type'; +import { AppText } from '../../../shared/defguard-ui/components/AppText/AppText'; +import { Checkbox } from '../../../shared/defguard-ui/components/Checkbox/Checkbox'; +import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider'; +import { Fold } from '../../../shared/defguard-ui/components/Fold/Fold'; +import { MarkedSection } from '../../../shared/defguard-ui/components/MarkedSection/MarkedSection'; +import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { TextStyle, ThemeSpacing } from '../../../shared/defguard-ui/types'; +import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; +import type { CERuleFormApi, FormFieldRenderer } from '../types'; + +type Props = { + form: unknown; + usersOptions: SelectionOption[] | undefined; + groupsOptions: SelectionOption[] | undefined; + networkDevicesOptions: SelectionOption[] | undefined; + restrictUsers: boolean; + restrictGroups: boolean; + restrictDevices: boolean; + setRestrictUsers: Dispatch>; + setRestrictGroups: Dispatch>; + setRestrictDevices: Dispatch>; +}; + +export const RestrictionsSection = ({ + form, + usersOptions, + groupsOptions, + networkDevicesOptions, + restrictUsers, + restrictGroups, + restrictDevices, + setRestrictUsers, + setRestrictGroups, + setRestrictDevices, +}: Props) => { + const Form = form as CERuleFormApi; + + return ( + + {`Restrictions`} + + +

{`Choose who or what should be blocked from accessing this location.`}

+
+ + {isPresent(usersOptions) && ( +
+
+ { + setRestrictUsers((current) => !current); + }} + text="Limit access for users" + /> +
+ +
+
+ + {(field) => { + const Field = field as FormFieldRenderer; + return ; + }} + + + {(field) => { + const Field = field as FormFieldRenderer; + return ( + + ); + }} + +
+ { + const state = s as { values: { deny_all_users: boolean } }; + return state.values.deny_all_users === false && restrictUsers; + }} + > + {(open) => ( + + {isPresent(usersOptions) && ( + + {(field) => { + const Field = field as FormFieldRenderer; + return ( + {}} + counterText={(counter: number) => `Users ${counter}`} + editText="Edit users" + modalTitle="Select restricted users" + options={usersOptions} + /> + ); + }} + + )} + + )} + +
+
+
+ )} + + {isPresent(groupsOptions) && ( +
+
+ { + setRestrictGroups((current) => !current); + }} + text="Limit access for groups" + /> +
+ +
+
+ + {(field) => { + const Field = field as FormFieldRenderer; + return ; + }} + + + {(field) => { + const Field = field as FormFieldRenderer; + return ( + + ); + }} + +
+ + (s as { values: { deny_all_groups: boolean } }).values + .deny_all_groups === false && restrictGroups + } + > + {(open) => ( + + {isPresent(groupsOptions) && ( + + {(field) => { + const Field = field as FormFieldRenderer; + return ( + {}} + counterText={(counter: number) => `Groups ${counter}`} + editText="Edit groups" + modalTitle="Select restricted groups" + options={groupsOptions} + /> + ); + }} + + )} + + )} + +
+
+ +
+ )} + {isPresent(networkDevicesOptions) && ( +
+
+ { + setRestrictDevices((current) => !current); + }} + text="Limit access for devices" + /> +
+ +
+
+ + {(field) => { + const Field = field as FormFieldRenderer; + return ; + }} + + + {(field) => { + const Field = field as FormFieldRenderer; + return ( + + ); + }} + +
+ + (s as { values: { deny_all_network_devices: boolean } }).values + .deny_all_network_devices === false && restrictDevices + } + > + {(open) => ( + + {isPresent(networkDevicesOptions) && ( + + {(field) => { + const Field = field as FormFieldRenderer; + return ( + {}} + counterText={(counter: number) => `Devices ${counter}`} + editText="Edit devices" + modalTitle="Select restricted devices" + options={networkDevicesOptions} + /> + ); + }} + + )} + + )} + +
+
+
+ )} +
+ ); +}; diff --git a/web/src/pages/CERulePage/types.ts b/web/src/pages/CERulePage/types.ts new file mode 100644 index 0000000000..d82d166fe9 --- /dev/null +++ b/web/src/pages/CERulePage/types.ts @@ -0,0 +1,44 @@ +import type { ComponentType, ReactNode } from 'react'; + +export type CERuleFormValues = { + name: string; + locations: number[]; + expires: string | null; + enabled: boolean; + all_locations: boolean; + allow_all_users: boolean; + deny_all_users: boolean; + allow_all_groups: boolean; + deny_all_groups: boolean; + allow_all_network_devices: boolean; + deny_all_network_devices: boolean; + allowed_users: number[]; + denied_users: number[]; + allowed_groups: number[]; + denied_groups: number[]; + allowed_network_devices: number[]; + denied_network_devices: number[]; + addresses: string; + ports: string; + protocols: Set; + any_address: boolean; + any_port: boolean; + any_protocol: boolean; + destinations: Set; + aliases: Set; + use_manual_destination_settings: boolean; +}; + +export type CERuleFormApi = { + AppField: ComponentType<{ + name: keyof CERuleFormValues; + children: (field: unknown) => ReactNode; + }>; + Subscribe: ComponentType<{ + selector: (state: unknown) => unknown; + children: (value: unknown) => ReactNode; + }>; + setFieldValue: (field: keyof CERuleFormValues, value: unknown) => void; +}; + +export type FormFieldRenderer = Record>>; From 77abce8d768dffa2797d7f8a36d75d2311a6d614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 26 Feb 2026 16:49:30 +0100 Subject: [PATCH 09/13] formatting --- .../src/enterprise/firewall/tests/destination.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/defguard_core/src/enterprise/firewall/tests/destination.rs b/crates/defguard_core/src/enterprise/firewall/tests/destination.rs index 98cadc882c..19f4d68add 100644 --- a/crates/defguard_core/src/enterprise/firewall/tests/destination.rs +++ b/crates/defguard_core/src/enterprise/firewall/tests/destination.rs @@ -9,8 +9,7 @@ use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use crate::enterprise::{ db::models::acl::{ - AclAlias, AclAliasDestinationRange, AclRule, AclRuleDestinationRange, AliasKind, - RuleState, + AclAlias, AclAliasDestinationRange, AclRule, AclRuleDestinationRange, AliasKind, RuleState, }, firewall::{process_destination_addrs, try_get_location_firewall_config}, }; @@ -170,12 +169,10 @@ async fn test_any_address_overwrites_manual_destination( Vec::new(), Vec::new(), Vec::new(), - vec![ - ( - IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10)), - IpAddr::V4(Ipv4Addr::new(192, 168, 1, 20)), - ), - ], + vec![( + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10)), + IpAddr::V4(Ipv4Addr::new(192, 168, 1, 20)), + )], Vec::new(), ) .await; From 9fc08afa4db3034c41bba5a3b6452c152421e6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 26 Feb 2026 16:49:44 +0100 Subject: [PATCH 10/13] adjust spacing --- web/src/pages/CERulePage/CERulePage.tsx | 1 - .../CERulePage/components/RestrictionsSection.tsx | 10 +++++----- web/src/pages/CERulePage/style.scss | 3 +-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/web/src/pages/CERulePage/CERulePage.tsx b/web/src/pages/CERulePage/CERulePage.tsx index 1d6ab92c1f..91b76f3072 100644 --- a/web/src/pages/CERulePage/CERulePage.tsx +++ b/web/src/pages/CERulePage/CERulePage.tsx @@ -190,7 +190,6 @@ const Content = ({ rule: initialRule }: Props) => { initialRule.denied_network_devices.length > 0 : false, ); - // const [manualDestination, setManualDestination] = useState(false); const formSchema = useMemo( () => diff --git a/web/src/pages/CERulePage/components/RestrictionsSection.tsx b/web/src/pages/CERulePage/components/RestrictionsSection.tsx index fafe8a9def..21bcc91b61 100644 --- a/web/src/pages/CERulePage/components/RestrictionsSection.tsx +++ b/web/src/pages/CERulePage/components/RestrictionsSection.tsx @@ -76,10 +76,10 @@ export const RestrictionsSection = ({ { - const state = s as { values: { deny_all_users: boolean } }; - return state.values.deny_all_users === false && restrictUsers; - }} + selector={(s) => + (s as { values: { deny_all_users: boolean } }).values.deny_all_users === + false && restrictUsers + } > {(open) => ( @@ -167,9 +167,9 @@ export const RestrictionsSection = ({ - )} + {isPresent(networkDevicesOptions) && (
diff --git a/web/src/pages/CERulePage/style.scss b/web/src/pages/CERulePage/style.scss index 153c1853ff..574ae5ce53 100644 --- a/web/src/pages/CERulePage/style.scss +++ b/web/src/pages/CERulePage/style.scss @@ -79,7 +79,7 @@ .restriction-block { display: flex; flex-direction: column; - gap: var(--spacing-lg); + gap: var(--spacing-sm); } .restriction-toggle { @@ -90,7 +90,6 @@ .restriction-body { display: flex; flex-direction: column; - gap: var(--spacing-md); } .restriction-radio { From c7baa2def7ee85cd7fe0de10c4b44cd0d1a27166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 26 Feb 2026 17:00:07 +0100 Subject: [PATCH 11/13] fix for visible chips --- web/src/pages/CERulePage/style.scss | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/web/src/pages/CERulePage/style.scss b/web/src/pages/CERulePage/style.scss index 574ae5ce53..7db047d241 100644 --- a/web/src/pages/CERulePage/style.scss +++ b/web/src/pages/CERulePage/style.scss @@ -79,7 +79,18 @@ .restriction-block { display: flex; flex-direction: column; - gap: var(--spacing-sm); + + .fold.folded .fold-content { + padding: 0; + } + + & > .fold { + margin-top: var(--spacing-sm); + } + + & > .fold.folded { + margin-top: 0; + } } .restriction-toggle { From 9c92910b5967e52d5ac5a941175939f920959d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 27 Feb 2026 11:43:28 +0100 Subject: [PATCH 12/13] Revert "split form sections into separate components" This reverts commit 90f751e6523cf5bbda2c7215f2dc8ed1f3222e59. --- web/src/pages/CERulePage/CERulePage.tsx | 663 +++++++++++++++++- .../components/DestinationSection.tsx | 387 ---------- .../components/GeneralSettingsSection.tsx | 107 --- .../components/PermissionsSection.tsx | 123 ---- web/src/pages/CERulePage/types.ts | 44 -- 5 files changed, 625 insertions(+), 699 deletions(-) delete mode 100644 web/src/pages/CERulePage/components/DestinationSection.tsx delete mode 100644 web/src/pages/CERulePage/components/GeneralSettingsSection.tsx delete mode 100644 web/src/pages/CERulePage/components/PermissionsSection.tsx delete mode 100644 web/src/pages/CERulePage/types.ts diff --git a/web/src/pages/CERulePage/CERulePage.tsx b/web/src/pages/CERulePage/CERulePage.tsx index 91b76f3072..57dd5b4522 100644 --- a/web/src/pages/CERulePage/CERulePage.tsx +++ b/web/src/pages/CERulePage/CERulePage.tsx @@ -3,22 +3,51 @@ import { useStore } from '@tanstack/react-form'; import { useMutation, useQuery } from '@tanstack/react-query'; import { useRouter } from '@tanstack/react-router'; import { intersection } from 'lodash-es'; -import { cloneDeep, omit } from 'radashi'; +import { cloneDeep, flat, omit } from 'radashi'; import { useMemo, useState } from 'react'; import z from 'zod'; import { m } from '../../paraglide/messages'; import api from '../../shared/api/api'; -import type { AclRule } from '../../shared/api/types'; +import { + type AclDestination, + AclProtocolName, + type AclProtocolValue, + type AclRule, + aclProtocolValues, + type NetworkLocation, +} from '../../shared/api/types'; +import { Card } from '../../shared/components/Card/Card'; import { Controls } from '../../shared/components/Controls/Controls'; +import { DescriptionBlock } from '../../shared/components/DescriptionBlock/DescriptionBlock'; +import { DestinationDismissibleBox } from '../../shared/components/DestinationDismissibleBox/DestinationDismissibleBox'; +import { DestinationLabel } from '../../shared/components/DestinationLabel/DestinationLabel'; import { EditPage } from '../../shared/components/EditPage/EditPage'; -import type { SelectionOption } from '../../shared/components/SelectionSection/type'; +import { useSelectionModal } from '../../shared/components/modals/SelectionModal/useSelectionModal'; +import type { + SelectionOption, + SelectionSectionCustomRender, +} from '../../shared/components/SelectionSection/type'; +import { AppText } from '../../shared/defguard-ui/components/AppText/AppText'; import { Button } from '../../shared/defguard-ui/components/Button/Button'; +import { ButtonsGroup } from '../../shared/defguard-ui/components/ButtonsGroup/ButtonsGroup'; +import { Checkbox } from '../../shared/defguard-ui/components/Checkbox/Checkbox'; +import { CheckboxIndicator } from '../../shared/defguard-ui/components/CheckboxIndicator/CheckboxIndicator'; +import { Chip } from '../../shared/defguard-ui/components/Chip/Chip'; import { Divider } from '../../shared/defguard-ui/components/Divider/Divider'; +import { Fold } from '../../shared/defguard-ui/components/Fold/Fold'; +import { Icon, type IconKindValue } from '../../shared/defguard-ui/components/Icon'; +import { MarkedSection } from '../../shared/defguard-ui/components/MarkedSection/MarkedSection'; +import { SizedBox } from '../../shared/defguard-ui/components/SizedBox/SizedBox'; import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar'; -import { ThemeSpacing } from '../../shared/defguard-ui/types'; +import { TooltipContent } from '../../shared/defguard-ui/providers/tooltip/TooltipContent'; +import { TooltipProvider } from '../../shared/defguard-ui/providers/tooltip/TooltipContext'; +import { TooltipTrigger } from '../../shared/defguard-ui/providers/tooltip/TooltipTrigger'; +import { TextStyle, ThemeSpacing, ThemeVariable } from '../../shared/defguard-ui/types'; import { isPresent } from '../../shared/defguard-ui/utils/isPresent'; import { useAppForm } from '../../shared/form'; import { formChangeLogic } from '../../shared/formLogic'; +import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; import { getAppliedAliasesQueryOptions, getAppliedDestinationsQueryOptions, @@ -30,11 +59,62 @@ import { import { aclDestinationValidator, aclPortsValidator } from '../../shared/validators'; import aliasesEmptyImage from './assets/aliases-empty-icon.png'; import emptyDestinationIconSrc from './assets/empty-destinations-icon.png'; -import { DestinationSection } from './components/DestinationSection'; -import { GeneralSettingsSection } from './components/GeneralSettingsSection'; -import { PermissionsSection } from './components/PermissionsSection'; -import { RestrictionsSection } from './components/RestrictionsSection'; -import type { CERuleFormValues } from './types'; + +const getProtocolName = (key: AclProtocolValue) => AclProtocolName[key]; + +const renderDestinationSelectionItem: SelectionSectionCustomRender< + number, + AclDestination +> = ({ active, onClick, option }) => ( +
+ + {isPresent(option.meta) && ( + AclProtocolName[protocol]) + .join(',')} + /> + )} +
+); + +const renderLocationSelectionItem: SelectionSectionCustomRender< + number, + NetworkLocation +> = ({ active, onClick, option }) => { + const icon: IconKindValue = 'check'; + return ( +
+ + {isPresent(option.meta) && ( + <> +
+

{option.meta?.name}

+
+ + + + + + {!option.meta.acl_enabled && ( +

{`Location access unmanaged (ACL disabled)`}

+ )} + {option.meta.acl_enabled && option.meta.acl_default_allow && ( +

{`Location access allowed by default - network traffic not explicitly defined by the rules will be passed.`}

+ )} + {option.meta.acl_enabled && !option.meta.acl_default_allow && ( +

{`Location access denied by default - network traffic not explicitly defined by the rules will be blocked.`}

+ )} +
+
+ + )} +
+ ); +}; type Props = { rule?: AclRule; @@ -300,7 +380,7 @@ const Content = ({ rule: initialRule }: Props) => { [], ); - type FormFields = CERuleFormValues; + type FormFields = z.infer; const defaultValues = useMemo((): FormFields => { if (isPresent(initialRule)) { @@ -397,37 +477,512 @@ const Content = ({ rule: initialRule }: Props) => { }} > - + + {`General settings`} + + + {(field) => } + + + +

{`Specify which locations this rule applies to. You can select all available locations or choose specific ones based on your requirements.`}

+
+ + s.values.all_locations}> + {(allValue) => ( + + {(field) => ( + `Locations ${counter}`} + editText="Edit locations" + modalTitle="Select locations" + toggleText="Include all locations" + selectionCustomItemRender={renderLocationSelectionItem} + toggleValue={allValue} + onToggleChange={(value) => { + form.setFieldValue('all_locations', value); + }} + /> + )} + + )} + +
- + + {`Destination`} + + + {`You can add additional destinations to this rule to extend its scope. These destinations are configured separately in the 'Destinations' section.`} + + + {isPresent(destinations) && destinations.length === 0 && ( +
+
+ +
+

{`You don't have any predefined destinations yet — add them in the 'Destinations' section to create reusable elements for defining destinations across multiple firewall ACL rules.`}

+
+ )} + {isPresent(destinations) && destinations.length > 0 && ( + + {(field) => { + const selectedDestinations = + destinations?.filter((destination) => + field.state.value.has(destination.id), + ) ?? []; + return ( + <> + + )} +
+
+ ); +}; diff --git a/web/src/pages/CERulePage/components/DestinationSection.tsx b/web/src/pages/CERulePage/components/DestinationSection.tsx deleted file mode 100644 index 470fbf0610..0000000000 --- a/web/src/pages/CERulePage/components/DestinationSection.tsx +++ /dev/null @@ -1,387 +0,0 @@ -import { flat } from 'radashi'; -import type { - AclAlias, - AclDestination, - AclProtocolValue, -} from '../../../shared/api/types'; -import { AclProtocolName, aclProtocolValues } from '../../../shared/api/types'; -import { Card } from '../../../shared/components/Card/Card'; -import { DescriptionBlock } from '../../../shared/components/DescriptionBlock/DescriptionBlock'; -import { DestinationDismissibleBox } from '../../../shared/components/DestinationDismissibleBox/DestinationDismissibleBox'; -import { DestinationLabel } from '../../../shared/components/DestinationLabel/DestinationLabel'; -import { useSelectionModal } from '../../../shared/components/modals/SelectionModal/useSelectionModal'; -import type { - SelectionOption, - SelectionSectionCustomRender, -} from '../../../shared/components/SelectionSection/type'; -import { AppText } from '../../../shared/defguard-ui/components/AppText/AppText'; -import { Button } from '../../../shared/defguard-ui/components/Button/Button'; -import { ButtonsGroup } from '../../../shared/defguard-ui/components/ButtonsGroup/ButtonsGroup'; -import { CheckboxIndicator } from '../../../shared/defguard-ui/components/CheckboxIndicator/CheckboxIndicator'; -import { Chip } from '../../../shared/defguard-ui/components/Chip/Chip'; -import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider'; -import { Fold } from '../../../shared/defguard-ui/components/Fold/Fold'; -import { MarkedSection } from '../../../shared/defguard-ui/components/MarkedSection/MarkedSection'; -import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; -import { - TextStyle, - ThemeSpacing, - ThemeVariable, -} from '../../../shared/defguard-ui/types'; -import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; -import { openModal } from '../../../shared/hooks/modalControls/modalsSubjects'; -import { ModalName } from '../../../shared/hooks/modalControls/modalTypes'; -import type { CERuleFormApi, FormFieldRenderer } from '../types'; - -const getProtocolName = (key: AclProtocolValue) => AclProtocolName[key]; - -const renderDestinationSelectionItem: SelectionSectionCustomRender< - number, - AclDestination -> = ({ active, onClick, option }) => ( -
- - {isPresent(option.meta) && ( - AclProtocolName[protocol]) - .join(',')} - /> - )} -
-); - -type Props = { - form: unknown; - destinations: AclDestination[] | undefined; - destinationsOptions: SelectionOption[] | undefined; - aliasesOptions: SelectionOption[] | undefined; - selectedAliases: AclAlias[]; - aliasesEmptyImage: string; - emptyDestinationIconSrc: string; -}; - -export const DestinationSection = ({ - form, - destinations, - destinationsOptions, - aliasesOptions, - selectedAliases, - aliasesEmptyImage, - emptyDestinationIconSrc, -}: Props) => { - const Form = form as CERuleFormApi; - - return ( - - {`Destination`} - - - {`You can add additional destinations to this rule to extend its scope. These destinations are configured separately in the 'Destinations' section.`} - - - {isPresent(destinations) && destinations.length === 0 && ( -
-
- -
-

{`You don't have any predefined destinations yet — add them in the 'Destinations' section to create reusable elements for defining destinations across multiple firewall ACL rules.`}

-
- )} - {isPresent(destinations) && destinations.length > 0 && ( - - {(field) => { - const destinationField = field as { - state: { value: Set }; - handleChange: (value: Set) => void; - }; - const selectedDestinations = - destinations?.filter((destination) => - destinationField.state.value.has(destination.id), - ) ?? []; - return ( - <> - - )} - - - ); -}; diff --git a/web/src/pages/CERulePage/components/GeneralSettingsSection.tsx b/web/src/pages/CERulePage/components/GeneralSettingsSection.tsx deleted file mode 100644 index cb042cdc48..0000000000 --- a/web/src/pages/CERulePage/components/GeneralSettingsSection.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import type { NetworkLocation } from '../../../shared/api/types'; -import { DescriptionBlock } from '../../../shared/components/DescriptionBlock/DescriptionBlock'; -import type { - SelectionOption, - SelectionSectionCustomRender, -} from '../../../shared/components/SelectionSection/type'; -import { AppText } from '../../../shared/defguard-ui/components/AppText/AppText'; -import { CheckboxIndicator } from '../../../shared/defguard-ui/components/CheckboxIndicator/CheckboxIndicator'; -import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider'; -import { Icon, type IconKindValue } from '../../../shared/defguard-ui/components/Icon'; -import { MarkedSection } from '../../../shared/defguard-ui/components/MarkedSection/MarkedSection'; -import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; -import { TooltipContent } from '../../../shared/defguard-ui/providers/tooltip/TooltipContent'; -import { TooltipProvider } from '../../../shared/defguard-ui/providers/tooltip/TooltipContext'; -import { TooltipTrigger } from '../../../shared/defguard-ui/providers/tooltip/TooltipTrigger'; -import { TextStyle, ThemeSpacing } from '../../../shared/defguard-ui/types'; -import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; -import type { CERuleFormApi, FormFieldRenderer } from '../types'; - -const renderLocationSelectionItem: SelectionSectionCustomRender< - number, - NetworkLocation -> = ({ active, onClick, option }) => { - const icon: IconKindValue = 'check'; - return ( -
- - {isPresent(option.meta) && ( - <> -
-

{option.meta?.name}

-
- - - - - - {!option.meta.acl_enabled && ( -

{`Location access unmanaged (ACL disabled)`}

- )} - {option.meta.acl_enabled && option.meta.acl_default_allow && ( -

{`Location access allowed by default - network traffic not explicitly defined by the rules will be passed.`}

- )} - {option.meta.acl_enabled && !option.meta.acl_default_allow && ( -

{`Location access denied by default - network traffic not explicitly defined by the rules will be blocked.`}

- )} -
-
- - )} -
- ); -}; - -type Props = { - form: unknown; - locationsOptions: SelectionOption[]; -}; - -export const GeneralSettingsSection = ({ form, locationsOptions }: Props) => { - const Form = form as CERuleFormApi; - - return ( - - {`General settings`} - - - {(field) => { - const Field = field as FormFieldRenderer; - return ; - }} - - - -

{`Specify which locations this rule applies to. You can select all available locations or choose specific ones based on your requirements.`}

-
- - - (s as { values: { all_locations: boolean } }).values.all_locations - } - > - {(allValue) => ( - - {(field) => { - const Field = field as FormFieldRenderer; - return ( - `Locations ${counter}`} - editText="Edit locations" - modalTitle="Select locations" - toggleText="Include all locations" - selectionCustomItemRender={renderLocationSelectionItem} - toggleValue={allValue} - onToggleChange={(value: boolean) => { - Form.setFieldValue('all_locations', value); - }} - /> - ); - }} - - )} - -
- ); -}; diff --git a/web/src/pages/CERulePage/components/PermissionsSection.tsx b/web/src/pages/CERulePage/components/PermissionsSection.tsx deleted file mode 100644 index dcbb717e44..0000000000 --- a/web/src/pages/CERulePage/components/PermissionsSection.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { DescriptionBlock } from '../../../shared/components/DescriptionBlock/DescriptionBlock'; -import type { SelectionOption } from '../../../shared/components/SelectionSection/type'; -import { AppText } from '../../../shared/defguard-ui/components/AppText/AppText'; -import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider'; -import { MarkedSection } from '../../../shared/defguard-ui/components/MarkedSection/MarkedSection'; -import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; -import { TextStyle, ThemeSpacing } from '../../../shared/defguard-ui/types'; -import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; -import type { CERuleFormApi, FormFieldRenderer } from '../types'; - -type Props = { - form: unknown; - usersOptions: SelectionOption[] | undefined; - groupsOptions: SelectionOption[] | undefined; - networkDevicesOptions: SelectionOption[] | undefined; -}; - -export const PermissionsSection = ({ - form, - usersOptions, - groupsOptions, - networkDevicesOptions, -}: Props) => { - const Form = form as CERuleFormApi; - - return ( - - {`Permissions`} - - -

{`Define who should be granted access. Only the entities you list here will be allowed through.`}

-
- - {isPresent(usersOptions) && ( - - (s as { values: { allow_all_users: boolean } }).values.allow_all_users - } - > - {(allowAllValue) => ( - - {(field) => { - const Field = field as FormFieldRenderer; - return ( - `Users ${counter}`} - editText={`Edit users`} - modalTitle="Select allowed users" - options={usersOptions} - onToggleChange={(value: boolean) => { - Form.setFieldValue('allow_all_users', value); - }} - /> - ); - }} - - )} - - )} - - {isPresent(groupsOptions) && ( - - (s as { values: { allow_all_groups: boolean } }).values.allow_all_groups - } - > - {(allAllowedValue) => ( - - {(field) => { - const Field = field as FormFieldRenderer; - return ( - { - Form.setFieldValue('allow_all_groups', value); - }} - options={groupsOptions} - counterText={(counter: number) => `Groups ${counter}`} - editText="Edit groups" - modalTitle="Select allowed groups" - toggleText="All groups have access" - /> - ); - }} - - )} - - )} - - {isPresent(networkDevicesOptions) && ( - - (s as { values: { allow_all_network_devices: boolean } }).values - .allow_all_network_devices - } - > - {(allowAllValue) => ( - - {(field) => { - const Field = field as FormFieldRenderer; - return ( - { - Form.setFieldValue('allow_all_network_devices', value); - }} - options={networkDevicesOptions} - counterText={(counter: number) => `Devices ${counter}`} - editText="Edit devices" - modalTitle="Select allowed devices" - toggleText="All network devices have access" - /> - ); - }} - - )} - - )} -
- ); -}; diff --git a/web/src/pages/CERulePage/types.ts b/web/src/pages/CERulePage/types.ts deleted file mode 100644 index d82d166fe9..0000000000 --- a/web/src/pages/CERulePage/types.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { ComponentType, ReactNode } from 'react'; - -export type CERuleFormValues = { - name: string; - locations: number[]; - expires: string | null; - enabled: boolean; - all_locations: boolean; - allow_all_users: boolean; - deny_all_users: boolean; - allow_all_groups: boolean; - deny_all_groups: boolean; - allow_all_network_devices: boolean; - deny_all_network_devices: boolean; - allowed_users: number[]; - denied_users: number[]; - allowed_groups: number[]; - denied_groups: number[]; - allowed_network_devices: number[]; - denied_network_devices: number[]; - addresses: string; - ports: string; - protocols: Set; - any_address: boolean; - any_port: boolean; - any_protocol: boolean; - destinations: Set; - aliases: Set; - use_manual_destination_settings: boolean; -}; - -export type CERuleFormApi = { - AppField: ComponentType<{ - name: keyof CERuleFormValues; - children: (field: unknown) => ReactNode; - }>; - Subscribe: ComponentType<{ - selector: (state: unknown) => unknown; - children: (value: unknown) => ReactNode; - }>; - setFieldValue: (field: keyof CERuleFormValues, value: unknown) => void; -}; - -export type FormFieldRenderer = Record>>; From ed2588e5496756239bd0b48534f35e7427a9e58b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Fri, 27 Feb 2026 12:03:03 +0100 Subject: [PATCH 13/13] remove unnecessary component --- .../components/RestrictionsSection.tsx | 236 ------------------ 1 file changed, 236 deletions(-) delete mode 100644 web/src/pages/CERulePage/components/RestrictionsSection.tsx diff --git a/web/src/pages/CERulePage/components/RestrictionsSection.tsx b/web/src/pages/CERulePage/components/RestrictionsSection.tsx deleted file mode 100644 index 21bcc91b61..0000000000 --- a/web/src/pages/CERulePage/components/RestrictionsSection.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import type { Dispatch, SetStateAction } from 'react'; -import { DescriptionBlock } from '../../../shared/components/DescriptionBlock/DescriptionBlock'; -import type { SelectionOption } from '../../../shared/components/SelectionSection/type'; -import { AppText } from '../../../shared/defguard-ui/components/AppText/AppText'; -import { Checkbox } from '../../../shared/defguard-ui/components/Checkbox/Checkbox'; -import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider'; -import { Fold } from '../../../shared/defguard-ui/components/Fold/Fold'; -import { MarkedSection } from '../../../shared/defguard-ui/components/MarkedSection/MarkedSection'; -import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; -import { TextStyle, ThemeSpacing } from '../../../shared/defguard-ui/types'; -import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; -import type { CERuleFormApi, FormFieldRenderer } from '../types'; - -type Props = { - form: unknown; - usersOptions: SelectionOption[] | undefined; - groupsOptions: SelectionOption[] | undefined; - networkDevicesOptions: SelectionOption[] | undefined; - restrictUsers: boolean; - restrictGroups: boolean; - restrictDevices: boolean; - setRestrictUsers: Dispatch>; - setRestrictGroups: Dispatch>; - setRestrictDevices: Dispatch>; -}; - -export const RestrictionsSection = ({ - form, - usersOptions, - groupsOptions, - networkDevicesOptions, - restrictUsers, - restrictGroups, - restrictDevices, - setRestrictUsers, - setRestrictGroups, - setRestrictDevices, -}: Props) => { - const Form = form as CERuleFormApi; - - return ( - - {`Restrictions`} - - -

{`Choose who or what should be blocked from accessing this location.`}

-
- - {isPresent(usersOptions) && ( -
-
- { - setRestrictUsers((current) => !current); - }} - text="Limit access for users" - /> -
- -
-
- - {(field) => { - const Field = field as FormFieldRenderer; - return ; - }} - - - {(field) => { - const Field = field as FormFieldRenderer; - return ( - - ); - }} - -
- - (s as { values: { deny_all_users: boolean } }).values.deny_all_users === - false && restrictUsers - } - > - {(open) => ( - - {isPresent(usersOptions) && ( - - {(field) => { - const Field = field as FormFieldRenderer; - return ( - {}} - counterText={(counter: number) => `Users ${counter}`} - editText="Edit users" - modalTitle="Select restricted users" - options={usersOptions} - /> - ); - }} - - )} - - )} - -
-
-
- )} - - {isPresent(groupsOptions) && ( -
-
- { - setRestrictGroups((current) => !current); - }} - text="Limit access for groups" - /> -
- -
-
- - {(field) => { - const Field = field as FormFieldRenderer; - return ; - }} - - - {(field) => { - const Field = field as FormFieldRenderer; - return ( - - ); - }} - -
- - (s as { values: { deny_all_groups: boolean } }).values - .deny_all_groups === false && restrictGroups - } - > - {(open) => ( - - {isPresent(groupsOptions) && ( - - {(field) => { - const Field = field as FormFieldRenderer; - return ( - {}} - counterText={(counter: number) => `Groups ${counter}`} - editText="Edit groups" - modalTitle="Select restricted groups" - options={groupsOptions} - /> - ); - }} - - )} - - )} - -
-
-
- )} - - {isPresent(networkDevicesOptions) && ( -
-
- { - setRestrictDevices((current) => !current); - }} - text="Limit access for devices" - /> -
- -
-
- - {(field) => { - const Field = field as FormFieldRenderer; - return ; - }} - - - {(field) => { - const Field = field as FormFieldRenderer; - return ( - - ); - }} - -
- - (s as { values: { deny_all_network_devices: boolean } }).values - .deny_all_network_devices === false && restrictDevices - } - > - {(open) => ( - - {isPresent(networkDevicesOptions) && ( - - {(field) => { - const Field = field as FormFieldRenderer; - return ( - {}} - counterText={(counter: number) => `Devices ${counter}`} - editText="Edit devices" - modalTitle="Select restricted devices" - options={networkDevicesOptions} - /> - ); - }} - - )} - - )} - -
-
-
- )} -
- ); -};