diff --git a/.sqlx/query-160d23b882d0465fbc8c5453b7dba68521649ba86985d45049487ae50d7dfde8.json b/.sqlx/query-160d23b882d0465fbc8c5453b7dba68521649ba86985d45049487ae50d7dfde8.json new file mode 100644 index 0000000000..3b31770567 --- /dev/null +++ b/.sqlx/query-160d23b882d0465fbc8c5453b7dba68521649ba86985d45049487ae50d7dfde8.json @@ -0,0 +1,43 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT admin_device_management, client_traffic_policy \"client_traffic_policy: ClientTrafficPolicy\", only_client_activation FROM \"enterprisesettings\" WHERE id = 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "admin_device_management", + "type_info": "Bool" + }, + { + "ordinal": 1, + "name": "client_traffic_policy: ClientTrafficPolicy", + "type_info": { + "Custom": { + "name": "client_traffic_policy", + "kind": { + "Enum": [ + "none", + "disable_all_traffic", + "force_all_traffic" + ] + } + } + } + }, + { + "ordinal": 2, + "name": "only_client_activation", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "160d23b882d0465fbc8c5453b7dba68521649ba86985d45049487ae50d7dfde8" +} diff --git a/.sqlx/query-283e1c3d082f1388fc2b806bdcab715db1c1df67da573b0f132fea265e42b416.json b/.sqlx/query-283e1c3d082f1388fc2b806bdcab715db1c1df67da573b0f132fea265e42b416.json deleted file mode 100644 index c1696c5b03..0000000000 --- a/.sqlx/query-283e1c3d082f1388fc2b806bdcab715db1c1df67da573b0f132fea265e42b416.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT admin_device_management, disable_all_traffic, only_client_activation FROM \"enterprisesettings\" WHERE id = 1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "admin_device_management", - "type_info": "Bool" - }, - { - "ordinal": 1, - "name": "disable_all_traffic", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "only_client_activation", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "283e1c3d082f1388fc2b806bdcab715db1c1df67da573b0f132fea265e42b416" -} diff --git a/.sqlx/query-a644507ebcfb9ef04883ad8b07bb3dbb0fc747ae0843baa001e47ea328e49c25.json b/.sqlx/query-a644507ebcfb9ef04883ad8b07bb3dbb0fc747ae0843baa001e47ea328e49c25.json new file mode 100644 index 0000000000..509af4943e --- /dev/null +++ b/.sqlx/query-a644507ebcfb9ef04883ad8b07bb3dbb0fc747ae0843baa001e47ea328e49c25.json @@ -0,0 +1,27 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE \"enterprisesettings\" SET admin_device_management = $1, client_traffic_policy = $2, only_client_activation = $3 WHERE id = 1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Bool", + { + "Custom": { + "name": "client_traffic_policy", + "kind": { + "Enum": [ + "none", + "disable_all_traffic", + "force_all_traffic" + ] + } + } + }, + "Bool" + ] + }, + "nullable": [] + }, + "hash": "a644507ebcfb9ef04883ad8b07bb3dbb0fc747ae0843baa001e47ea328e49c25" +} diff --git a/.sqlx/query-ccd62ea7526078c9db47812e7f6a5e7829eae217ad9f7f3b0b03aa02f8808dc2.json b/.sqlx/query-ccd62ea7526078c9db47812e7f6a5e7829eae217ad9f7f3b0b03aa02f8808dc2.json deleted file mode 100644 index 787a75d7d9..0000000000 --- a/.sqlx/query-ccd62ea7526078c9db47812e7f6a5e7829eae217ad9f7f3b0b03aa02f8808dc2.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE \"enterprisesettings\" SET admin_device_management = $1, disable_all_traffic = $2, only_client_activation = $3 WHERE id = 1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Bool", - "Bool", - "Bool" - ] - }, - "nullable": [] - }, - "hash": "ccd62ea7526078c9db47812e7f6a5e7829eae217ad9f7f3b0b03aa02f8808dc2" -} diff --git a/crates/defguard_core/src/enterprise/db/models/enterprise_settings.rs b/crates/defguard_core/src/enterprise/db/models/enterprise_settings.rs index f2e85ced8b..d1c9be350b 100644 --- a/crates/defguard_core/src/enterprise/db/models/enterprise_settings.rs +++ b/crates/defguard_core/src/enterprise/db/models/enterprise_settings.rs @@ -1,4 +1,4 @@ -use sqlx::{PgExecutor, query, query_as}; +use sqlx::{PgExecutor, Type, query, query_as}; use struct_patch::Patch; use crate::enterprise::is_enterprise_enabled; @@ -6,11 +6,11 @@ use crate::enterprise::is_enterprise_enabled; #[derive(Debug, Deserialize, Patch, Serialize)] #[patch(attribute(derive(Deserialize, Serialize)))] pub struct EnterpriseSettings { - // If true, only admins can manage devices + /// If true, only admins can manage devices pub admin_device_management: bool, - // If true, the option to route all traffic through the vpn is disabled in the client - pub disable_all_traffic: bool, - // If true, manual WireGuard setup is disabled + /// Describes allowed routing options for clients connecting to the instance. + pub client_traffic_policy: ClientTrafficPolicy, + /// If true, manual WireGuard setup is disabled pub only_client_activation: bool, } @@ -20,8 +20,8 @@ impl Default for EnterpriseSettings { fn default() -> Self { Self { admin_device_management: false, - disable_all_traffic: false, only_client_activation: false, + client_traffic_policy: ClientTrafficPolicy::default(), } } } @@ -39,7 +39,8 @@ impl EnterpriseSettings { let settings = query_as!( Self, "SELECT admin_device_management, \ - disable_all_traffic, only_client_activation \ + client_traffic_policy \"client_traffic_policy: ClientTrafficPolicy\", \ + only_client_activation \ FROM \"enterprisesettings\" WHERE id = 1", ) .fetch_optional(executor) @@ -57,11 +58,11 @@ impl EnterpriseSettings { query!( "UPDATE \"enterprisesettings\" SET \ admin_device_management = $1, \ - disable_all_traffic = $2, \ + client_traffic_policy = $2, \ only_client_activation = $3 \ WHERE id = 1", self.admin_device_management, - self.disable_all_traffic, + self.client_traffic_policy as ClientTrafficPolicy, self.only_client_activation, ) .execute(executor) @@ -70,3 +71,17 @@ impl EnterpriseSettings { Ok(()) } } + +/// Describes allowed traffic options for clients connecting to the instance. +#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Type, Debug, Default, Copy)] +#[sqlx(type_name = "client_traffic_policy", rename_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum ClientTrafficPolicy { + /// No restrictions + #[default] + None, + /// Clients are not allowed to route all traffic through the VPN. + DisableAllTraffic, + /// Clients are forced to route all traffic through the VPN. + ForceAllTraffic, +} diff --git a/crates/defguard_core/src/grpc/mod.rs b/crates/defguard_core/src/grpc/mod.rs index 96dbd24e69..a4c4ba3dcf 100644 --- a/crates/defguard_core/src/grpc/mod.rs +++ b/crates/defguard_core/src/grpc/mod.rs @@ -50,7 +50,10 @@ use crate::{ models::enrollment::{ENROLLMENT_TOKEN_TYPE, Token}, }, enterprise::{ - db::models::{enterprise_settings::EnterpriseSettings, openid_provider::OpenIdProvider}, + db::models::{ + enterprise_settings::{ClientTrafficPolicy, EnterpriseSettings}, + openid_provider::OpenIdProvider, + }, directory_sync::sync_user_groups_if_configured, grpc::polling::PollingServer, handlers::openid_login::{ @@ -806,7 +809,7 @@ pub struct InstanceInfo { url: Url, proxy_url: Url, username: String, - disable_all_traffic: bool, + client_traffic_policy: ClientTrafficPolicy, enterprise_enabled: bool, openid_display_name: Option, } @@ -829,7 +832,7 @@ impl InstanceInfo { url: config.url.clone(), proxy_url: config.enrollment_url.clone(), username: username.into(), - disable_all_traffic: enterprise_settings.disable_all_traffic, + client_traffic_policy: enterprise_settings.client_traffic_policy, enterprise_enabled: is_enterprise_enabled(), openid_display_name, } @@ -844,7 +847,11 @@ impl From for defguard_proto::proxy::InstanceInfo { url: instance.url.to_string(), proxy_url: instance.proxy_url.to_string(), username: instance.username, - disable_all_traffic: instance.disable_all_traffic, + // Ensure backwards compatibility. + #[allow(deprecated)] + disable_all_traffic: instance.client_traffic_policy + == ClientTrafficPolicy::DisableAllTraffic, + client_traffic_policy: Some(instance.client_traffic_policy as i32), enterprise_enabled: instance.enterprise_enabled, openid_display_name: instance.openid_display_name, } diff --git a/crates/defguard_core/tests/integration/api/enterprise_settings.rs b/crates/defguard_core/tests/integration/api/enterprise_settings.rs index f24582a659..c065dde6cc 100644 --- a/crates/defguard_core/tests/integration/api/enterprise_settings.rs +++ b/crates/defguard_core/tests/integration/api/enterprise_settings.rs @@ -1,6 +1,6 @@ use defguard_core::{ enterprise::{ - db::models::enterprise_settings::EnterpriseSettings, + db::models::enterprise_settings::{ClientTrafficPolicy, EnterpriseSettings}, license::{get_cached_license, set_cached_license}, }, handlers::Auth, @@ -33,7 +33,7 @@ async fn test_only_enterprise_can_modify_enterpise_settings( // try to patch enterprise settings let settings = EnterpriseSettings { admin_device_management: false, - disable_all_traffic: false, + client_traffic_policy: ClientTrafficPolicy::None, only_client_activation: false, }; @@ -81,7 +81,7 @@ async fn test_admin_devices_management_is_enforced(_: PgPoolOptions, options: Pg // setup admin devices management let settings = EnterpriseSettings { admin_device_management: true, - disable_all_traffic: false, + client_traffic_policy: ClientTrafficPolicy::None, only_client_activation: false, }; let response = client @@ -177,7 +177,7 @@ async fn test_regular_user_device_management(_: PgPoolOptions, options: PgConnec // setup admin devices management let settings = EnterpriseSettings { admin_device_management: false, - disable_all_traffic: false, + client_traffic_policy: ClientTrafficPolicy::None, only_client_activation: false, }; let response = client @@ -265,7 +265,7 @@ async fn dg25_12_test_enforce_client_activation_only(_: PgPoolOptions, options: // disable manual device management let settings = EnterpriseSettings { admin_device_management: false, - disable_all_traffic: false, + client_traffic_policy: ClientTrafficPolicy::None, only_client_activation: true, }; let response = client @@ -346,7 +346,7 @@ async fn dg25_13_test_disable_device_config(_: PgPoolOptions, options: PgConnect // disable manual device management let settings = EnterpriseSettings { admin_device_management: false, - disable_all_traffic: false, + client_traffic_policy: ClientTrafficPolicy::None, only_client_activation: true, }; let response = client diff --git a/migrations/20251119122424_client_traffic_policy.down.sql b/migrations/20251119122424_client_traffic_policy.down.sql new file mode 100644 index 0000000000..db730678d6 --- /dev/null +++ b/migrations/20251119122424_client_traffic_policy.down.sql @@ -0,0 +1,13 @@ +-- restore boolean `mfa_enabled` column +ALTER TABLE enterprisesettings ADD COLUMN "disable_all_traffic" BOOLEAN NOT NULL DEFAULT false; + +-- populate based on client traffic policy +UPDATE enterprisesettings +SET disable_all_traffic = CASE + WHEN client_traffic_policy = 'disable_all_traffic'::client_traffic_policy THEN true + ELSE false +END; + +-- drop new column and type +ALTER TABLE enterprisesettings DROP COLUMN "client_traffic_policy"; +DROP TYPE client_traffic_policy; diff --git a/migrations/20251119122424_client_traffic_policy.up.sql b/migrations/20251119122424_client_traffic_policy.up.sql new file mode 100644 index 0000000000..a5bfb8d8ac --- /dev/null +++ b/migrations/20251119122424_client_traffic_policy.up.sql @@ -0,0 +1,19 @@ +-- add enum representing client traffic policy +CREATE TYPE client_traffic_policy AS ENUM ( + 'none', + 'disable_all_traffic', + 'force_all_traffic' +); + +-- add column to `enterprisesettings` table +ALTER TABLE enterprisesettings ADD COLUMN "client_traffic_policy" client_traffic_policy NOT NULL DEFAULT 'none'; + +-- populate new column based on value in `disable_all_traffic` column +UPDATE enterprisesettings +SET client_traffic_policy = CASE + WHEN disable_all_traffic = true THEN 'disable_all_traffic'::client_traffic_policy + ELSE 'none'::client_traffic_policy +END; + +-- drop the `disable_all_traffic` column since it's no longer needed +ALTER TABLE enterprisesettings DROP COLUMN "disable_all_traffic"; diff --git a/proto b/proto index 96249ebde0..74d60d9171 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 96249ebde0556f4ae8c47eebc6015efb04ed0104 +Subproject commit 74d60d9171048ba0ccaf8a21b05950fb7a673f09 diff --git a/web/src/i18n/en/index.ts b/web/src/i18n/en/index.ts index f4b94015e6..ce572bb6bb 100644 --- a/web/src/i18n/en/index.ts +++ b/web/src/i18n/en/index.ts @@ -1708,16 +1708,29 @@ Licensing information: [https://docs.defguard.net/enterprise/license](https://do helper: "When this option is enabled, only users in the Admin group can manage devices in user profile (it's disabled for all other users)", }, - disableAllTraffic: { - label: 'Disable the option to route all traffic through VPN', - helper: - 'When this option is enabled, users will not be able to route all traffic through the VPN using the defguard client.', - }, manualConfig: { label: "Disable users' ability to manually configure WireGuard client", helper: "When this option is enabled, users won't be able to view or download configuration for the manual WireGuard client setup. Only the Defguard desktop client configuration will be available.", }, + clientTrafficPolicy: { + header: 'Client traffic policy', + none: { + label: 'None', + helper: + 'When this option is enabled, users will be able to select all routing options.', + }, + disableAllTraffic: { + label: 'Disable the option to route all traffic through VPN', + helper: + 'When this option is enabled, users will not be able to route all traffic through the VPN.', + }, + forceAllTraffic: { + label: 'Force the clients to route all traffic through VPN', + helper: + 'When this option is enabled, the users will always route all traffic through the VPN.', + }, + }, }, }, gatewayNotifications: { diff --git a/web/src/i18n/i18n-types.ts b/web/src/i18n/i18n-types.ts index 2313f93c1c..517c50734c 100644 --- a/web/src/i18n/i18n-types.ts +++ b/web/src/i18n/i18n-types.ts @@ -4082,16 +4082,6 @@ type RootTranslation = { */ helper: string } - disableAllTraffic: { - /** - * D​i​s​a​b​l​e​ ​t​h​e​ ​o​p​t​i​o​n​ ​t​o​ ​r​o​u​t​e​ ​a​l​l​ ​t​r​a​f​f​i​c​ ​t​h​r​o​u​g​h​ ​V​P​N - */ - label: string - /** - * W​h​e​n​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​u​s​e​r​s​ ​w​i​l​l​ ​n​o​t​ ​b​e​ ​a​b​l​e​ ​t​o​ ​r​o​u​t​e​ ​a​l​l​ ​t​r​a​f​f​i​c​ ​t​h​r​o​u​g​h​ ​t​h​e​ ​V​P​N​ ​u​s​i​n​g​ ​t​h​e​ ​d​e​f​g​u​a​r​d​ ​c​l​i​e​n​t​. - */ - helper: string - } manualConfig: { /** * D​i​s​a​b​l​e​ ​u​s​e​r​s​'​ ​a​b​i​l​i​t​y​ ​t​o​ ​m​a​n​u​a​l​l​y​ ​c​o​n​f​i​g​u​r​e​ ​W​i​r​e​G​u​a​r​d​ ​c​l​i​e​n​t @@ -4102,6 +4092,42 @@ type RootTranslation = { */ helper: string } + clientTrafficPolicy: { + /** + * C​l​i​e​n​t​ ​t​r​a​f​f​i​c​ ​p​o​l​i​c​y + */ + header: string + none: { + /** + * N​o​n​e + */ + label: string + /** + * W​h​e​n​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​u​s​e​r​s​ ​w​i​l​l​ ​b​e​ ​a​b​l​e​ ​t​o​ ​s​e​l​e​c​t​ ​a​l​l​ ​r​o​u​t​i​n​g​ ​o​p​t​i​o​n​s​. + */ + helper: string + } + disableAllTraffic: { + /** + * D​i​s​a​b​l​e​ ​t​h​e​ ​o​p​t​i​o​n​ ​t​o​ ​r​o​u​t​e​ ​a​l​l​ ​t​r​a​f​f​i​c​ ​t​h​r​o​u​g​h​ ​V​P​N + */ + label: string + /** + * W​h​e​n​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​u​s​e​r​s​ ​w​i​l​l​ ​n​o​t​ ​b​e​ ​a​b​l​e​ ​t​o​ ​r​o​u​t​e​ ​a​l​l​ ​t​r​a​f​f​i​c​ ​t​h​r​o​u​g​h​ ​t​h​e​ ​V​P​N​. + */ + helper: string + } + forceAllTraffic: { + /** + * F​o​r​c​e​ ​t​h​e​ ​c​l​i​e​n​t​s​ ​t​o​ ​r​o​u​t​e​ ​a​l​l​ ​t​r​a​f​f​i​c​ ​t​h​r​o​u​g​h​ ​V​P​N + */ + label: string + /** + * W​h​e​n​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​t​h​e​ ​u​s​e​r​s​ ​w​i​l​l​ ​a​l​w​a​y​s​ ​r​o​u​t​e​ ​a​l​l​ ​t​r​a​f​f​i​c​ ​t​h​r​o​u​g​h​ ​t​h​e​ ​V​P​N​. + */ + helper: string + } + } } } gatewayNotifications: { @@ -10813,16 +10839,6 @@ export type TranslationFunctions = { */ helper: () => LocalizedString } - disableAllTraffic: { - /** - * Disable the option to route all traffic through VPN - */ - label: () => LocalizedString - /** - * When this option is enabled, users will not be able to route all traffic through the VPN using the defguard client. - */ - helper: () => LocalizedString - } manualConfig: { /** * Disable users' ability to manually configure WireGuard client @@ -10833,6 +10849,42 @@ export type TranslationFunctions = { */ helper: () => LocalizedString } + clientTrafficPolicy: { + /** + * Client traffic policy + */ + header: () => LocalizedString + none: { + /** + * None + */ + label: () => LocalizedString + /** + * When this option is enabled, users will be able to select all routing options. + */ + helper: () => LocalizedString + } + disableAllTraffic: { + /** + * Disable the option to route all traffic through VPN + */ + label: () => LocalizedString + /** + * When this option is enabled, users will not be able to route all traffic through the VPN. + */ + helper: () => LocalizedString + } + forceAllTraffic: { + /** + * Force the clients to route all traffic through VPN + */ + label: () => LocalizedString + /** + * When this option is enabled, the users will always route all traffic through the VPN. + */ + helper: () => LocalizedString + } + } } } gatewayNotifications: { diff --git a/web/src/i18n/pl/index.ts b/web/src/i18n/pl/index.ts index 6525901519..7356101f64 100644 --- a/web/src/i18n/pl/index.ts +++ b/web/src/i18n/pl/index.ts @@ -1494,16 +1494,29 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe helper: 'Kiedy ta opcja jest włączona, tylko użytkownicy w grupie "Admin" mogą zarządzać urządzeniami w profilu użytkownika', }, - disableAllTraffic: { - label: 'Zablokuj możliwość przekierowania całego ruchu przez VPN', - helper: - 'Kiedy ta opcja jest włączona, użytkownicy nie będą mogli przekierować całego ruchu przez VPN za pomocą klienta Defguard.', - }, manualConfig: { label: 'Wyłącz manualną konfigurację WireGuard', helper: 'Kiedy ta opcja jest włączona, użytkownicy nie będą mogli pobrać ani wyświetlić danych do manualnej konfiguracji WireGuard. Możliwe będzie wyłącznie skonfigurowanie klienta Defguard.', }, + clientTrafficPolicy: { + header: 'Polityka przekierowania ruchu klientów', + none: { + label: 'Brak', + helper: + 'Kiedy ta opcja jest włączona, użytkownicy mogą wybierać dowolny typ przekierowania ruchu.', + }, + disableAllTraffic: { + label: 'Zablokuj możliwość przekierowania całego ruchu przez VPN', + helper: + 'Kiedy ta opcja jest włączona, użytkownicy nie będą mogli przekierować całego ruchu przez VPN.', + }, + forceAllTraffic: { + label: 'Wymuś przekierowanie całego ruchu przez VPN', + helper: + 'Kiedy ta opcja jest włączona, użytkownicy będą zawsze przekierowywać cały ruch przez VPN.', + }, + } }, }, gatewayNotifications: { diff --git a/web/src/pages/settings/components/EnterpriseSettings/components/EnterpriseForm.tsx b/web/src/pages/settings/components/EnterpriseSettings/components/EnterpriseForm.tsx index d7df932dec..c8bcdc885d 100644 --- a/web/src/pages/settings/components/EnterpriseSettings/components/EnterpriseForm.tsx +++ b/web/src/pages/settings/components/EnterpriseSettings/components/EnterpriseForm.tsx @@ -12,6 +12,7 @@ import useApi from '../../../../../shared/hooks/useApi'; import { useToaster } from '../../../../../shared/hooks/useToaster'; import { MutationKeys } from '../../../../../shared/mutations'; import { QueryKeys } from '../../../../../shared/queries'; +import { ClientTrafficPolicySelect } from './TrafficPolicySelect/TrafficPolicySelect'; export const EnterpriseForm = () => { const { LL } = useI18nContext(); @@ -77,17 +78,10 @@ export const EnterpriseForm = () => {
- - mutate({ disable_all_traffic: !settings.disable_all_traffic }) - } + mutate({ client_traffic_policy: value })} + fieldValue={settings.client_traffic_policy} /> - - {parse(LL.settingsPage.enterprise.fields.disableAllTraffic.helper())} -
diff --git a/web/src/pages/settings/components/EnterpriseSettings/components/TrafficPolicySelect/TrafficPolicySelect.tsx b/web/src/pages/settings/components/EnterpriseSettings/components/TrafficPolicySelect/TrafficPolicySelect.tsx new file mode 100644 index 0000000000..43fa5f3f9c --- /dev/null +++ b/web/src/pages/settings/components/EnterpriseSettings/components/TrafficPolicySelect/TrafficPolicySelect.tsx @@ -0,0 +1,77 @@ +import './style.scss'; +import clsx from 'clsx'; +import parse from 'html-react-parser'; +import { useMemo } from 'react'; +import { useI18nContext } from '../../../../../../i18n/i18n-react'; +import { Helper } from '../../../../../../shared/defguard-ui/components/Layout/Helper/Helper'; +import { RadioButton } from '../../../../../../shared/defguard-ui/components/Layout/RadioButton/Radiobutton'; +import type { SelectOption } from '../../../../../../shared/defguard-ui/components/Layout/Select/types'; +import { ClientTrafficPolicy } from '../../../../../../shared/types'; + +type Props = { + onChange: (event: ClientTrafficPolicy) => void; + fieldValue: ClientTrafficPolicy; +}; + +export const ClientTrafficPolicySelect = ({ onChange, fieldValue }: Props) => { + const { LL } = useI18nContext(); + const options = useMemo( + (): SelectOption[] => [ + { + key: ClientTrafficPolicy.NONE, + value: ClientTrafficPolicy.NONE, + label: LL.settingsPage.enterprise.fields.clientTrafficPolicy.none.label(), + meta: LL.settingsPage.enterprise.fields.clientTrafficPolicy.none.helper(), + }, + { + key: ClientTrafficPolicy.DISABLE_ALL_TRAFFIC, + value: ClientTrafficPolicy.DISABLE_ALL_TRAFFIC, + label: + LL.settingsPage.enterprise.fields.clientTrafficPolicy.disableAllTraffic.label(), + meta: LL.settingsPage.enterprise.fields.clientTrafficPolicy.disableAllTraffic.helper(), + }, + { + key: ClientTrafficPolicy.FORCE_ALL_TRAFFIC, + value: ClientTrafficPolicy.FORCE_ALL_TRAFFIC, + label: + LL.settingsPage.enterprise.fields.clientTrafficPolicy.forceAllTraffic.label(), + meta: LL.settingsPage.enterprise.fields.clientTrafficPolicy.forceAllTraffic.helper(), + }, + ], + [ + LL.settingsPage.enterprise.fields.clientTrafficPolicy.none.label, + LL.settingsPage.enterprise.fields.clientTrafficPolicy.none.helper, + LL.settingsPage.enterprise.fields.clientTrafficPolicy.forceAllTraffic.label, + LL.settingsPage.enterprise.fields.clientTrafficPolicy.forceAllTraffic.helper, + LL.settingsPage.enterprise.fields.clientTrafficPolicy.disableAllTraffic.label, + LL.settingsPage.enterprise.fields.clientTrafficPolicy.disableAllTraffic.helper, + ], + ); + + return ( +
+ + {options.map(({ key, value, label, meta, disabled = false }) => { + const active = fieldValue === value; + return ( +
{ + if (!disabled) { + onChange(value); + } + }} + > +

{label}

+ + {parse(meta)} +
+ ); + })} +
+ ); +}; diff --git a/web/src/pages/settings/components/EnterpriseSettings/components/TrafficPolicySelect/style.scss b/web/src/pages/settings/components/EnterpriseSettings/components/TrafficPolicySelect/style.scss new file mode 100644 index 0000000000..692d1123b8 --- /dev/null +++ b/web/src/pages/settings/components/EnterpriseSettings/components/TrafficPolicySelect/style.scss @@ -0,0 +1,59 @@ +.client-traffic-policy-select { + display: flex; + flex-flow: column; + row-gap: var(--spacing-s); + margin-bottom: 25px; + + .client-traffic-policy { + display: flex; + align-items: center; + justify-content: space-between; + column-gap: var(--spacing-xs); + min-height: 30px; + border: 1px solid var(--border-primary); + padding: var(--spacing-xs) var(--spacing-s); + border-radius: 10px; + cursor: pointer; + user-select: none; + transition-property: border-color, opacity; + @include animate-standard; + + &:not(.active) { + &:hover { + border-color: var(--border-separator); + } + } + + &.active { + border-color: var(--surface-main-primary); + } + + &.active, + &:hover { + .label { + color: var(--text-body-primary); + } + } + + &.disabled { + opacity: 0.6; + cursor: not-allowed; + background-color: var(--surface-secondary); + + .label { + color: var(--text-body-disabled); + } + + &:hover { + border-color: var(--border-primary); + } + } + + .label { + color: var(--text-body-secondary); + transition-property: color; + @include typography(app-modal-1); + @include animate-standard; + } + } +} diff --git a/web/src/shared/types.ts b/web/src/shared/types.ts index 3c181cb11e..33258f66b6 100644 --- a/web/src/shared/types.ts +++ b/web/src/shared/types.ts @@ -1121,9 +1121,15 @@ export type SettingsGatewayNotifications = { gateway_disconnect_notifications_reconnect_notification_enabled: boolean; }; +export enum ClientTrafficPolicy { + NONE = 'none', + DISABLE_ALL_TRAFFIC = 'disable_all_traffic', + FORCE_ALL_TRAFFIC = 'force_all_traffic', +} + export type SettingsEnterprise = { admin_device_management: boolean; - disable_all_traffic: boolean; + client_traffic_policy: ClientTrafficPolicy; only_client_activation: boolean; };