diff --git a/web/messages/en/settings.json b/web/messages/en/settings.json index ab83c11a69..5e66fb9f6e 100644 --- a/web/messages/en/settings.json +++ b/web/messages/en/settings.json @@ -3,16 +3,17 @@ "settings_page_title": "Settings", "settings_breadcrumb_general": "General", "settings_breadcrumb_instance": "Instance settings", - "settings_breadcrumb_vpn_stats": "VPN stats", "settings_breadcrumb_enrollment": "Enrollment", "settings_breadcrumb_client_behavior": "Client behavior", "settings_instance_title": "Instance settings", "settings_instance_subtitle": "Here you can configure general instance parameters.", + "settings_instance_section_core": "General", + "settings_instance_section_core_description": "Configure the primary instance identity, URLs and session lifetime used across the platform.", + "settings_instance_section_data_retention": "VPN stats retention", + "settings_instance_section_data_retention_description": "Control if VPN statistics are purged automatically and how long they are kept.", "settings_instance_label_name": "Instance name", "settings_instance_label_public_proxy_url": "Public Edge Component URL", "settings_instance_label_session_duration": "Session duration", - "settings_vpn_stats_title": "VPN stats", - "settings_vpn_stats_subtitle": "Configure statistics purge behavior for VPN data.", "settings_vpn_stats_toggle_disable_title": "Disable stats purge", "settings_vpn_stats_label_purge_frequency": "Stats purge frequency", "settings_vpn_stats_label_purge_threshold": "Stats purge threshold", @@ -24,8 +25,7 @@ "settings_enrollment_label_password_reset_session_expires_in": "Password reset session expires in", "settings_general_section_instance_content": "Configure your instance name and branding settings. Add a logo to personalize the interface and make it easily recognizable to your users.", "settings_general_section_client_behavior_content": "Manage how users interact with the Defguard client. Control device management permissions, configuration access, and traffic routing options.", - "settings_general_section_vpn_stats_content": "Configure VPN statistics cleanup behavior, including purge frequency and retention threshold.", - "settings_general_section_enrollment_content": "Set enrollment and password reset token and session timeout values.", + "settings_general_section_enrollment_content": "Configure enrollment settings to define how users are invited, registered, and onboarded into your instance.", "settings_duration_one_day": "1 day", "settings_duration_days": "{days} days", "settings_duration_one_hour": "1 hour", diff --git a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsGeneralTab.tsx b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsGeneralTab.tsx index 000e5a8111..51318a9884 100644 --- a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsGeneralTab.tsx +++ b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsGeneralTab.tsx @@ -5,9 +5,6 @@ import { businessBadgeProps } from '../../../../shared/components/badges/Busines import { SettingsLayout } from '../../../../shared/components/SettingsLayout/SettingsLayout'; import { SectionSelect } from '../../../../shared/defguard-ui/components/SectionSelect/SectionSelect'; 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 { ThemeSpacing } from '../../../../shared/defguard-ui/types'; import { getLicenseInfoQueryOptions } from '../../../../shared/query'; @@ -26,31 +23,9 @@ export const SettingsGeneralTab = () => { /> - - - - - - {`Not implemented`} - - - - - - - diff --git a/web/src/pages/settings/SettingsInstancePage/SettingsInstancePage.tsx b/web/src/pages/settings/SettingsInstancePage/SettingsInstancePage.tsx index 781370c35f..314e3e50b6 100644 --- a/web/src/pages/settings/SettingsInstancePage/SettingsInstancePage.tsx +++ b/web/src/pages/settings/SettingsInstancePage/SettingsInstancePage.tsx @@ -12,6 +12,9 @@ import { SettingsCard } from '../../../shared/components/SettingsCard/SettingsCa import { SettingsHeader } from '../../../shared/components/SettingsHeader/SettingsHeader'; import { SettingsLayout } from '../../../shared/components/SettingsLayout/SettingsLayout'; import { Button } from '../../../shared/defguard-ui/components/Button/Button'; +import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider'; +import { MarkedSection } from '../../../shared/defguard-ui/components/MarkedSection/MarkedSection'; +import { MarkedSectionHeader } from '../../../shared/defguard-ui/components/MarkedSectionHeader/MarkedSectionHeader'; 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'; @@ -76,6 +79,9 @@ const formSchema = z.object({ .url(m.initial_setup_general_config_error_public_proxy_url_invalid()) .min(1, m.initial_setup_general_config_error_public_proxy_url_required()), authentication_period_days: z.number().min(1, m.form_error_invalid()), + disable_stats_purge: z.boolean(), + stats_purge_frequency_hours: z.number(m.form_error_required()).int().min(1), + stats_purge_threshold_days: z.number(m.form_error_required()).int().min(1), }); type FormFields = z.infer; @@ -92,6 +98,23 @@ const sessionDurationOptions = createNumericSelectOptions({ const sessionDurationFallbackUnit = 'days'; +const statsPurgeFrequencyOptions = createNumericSelectOptions({ + 1: m.settings_duration_one_hour(), + 12: m.settings_duration_hours({ hours: 12 }), + 24: m.settings_duration_one_day(), + 48: m.settings_duration_days({ days: 2 }), + 168: m.settings_duration_one_week(), + 720: m.settings_duration_one_month(), +}); + +const statsPurgeThresholdOptions = createNumericSelectOptions({ + 1: m.settings_duration_one_day(), + 7: m.settings_duration_days({ days: 7 }), + 14: m.settings_duration_days({ days: 14 }), + 30: m.settings_duration_days({ days: 30 }), + 90: m.settings_duration_days({ days: 90 }), +}); + const Content = ({ settings }: { settings: Settings }) => { const { mutateAsync } = useMutation({ mutationFn: api.settings.patchSettings, @@ -111,11 +134,17 @@ const Content = ({ settings }: { settings: Settings }) => { instance_name: settings.instance_name ?? '', public_proxy_url: settings.public_proxy_url ?? '', authentication_period_days: settings.authentication_period_days ?? 7, + disable_stats_purge: settings.disable_stats_purge ?? false, + stats_purge_frequency_hours: settings.stats_purge_frequency_hours ?? 24, + stats_purge_threshold_days: settings.stats_purge_threshold_days ?? 30, }), [ settings.instance_name, settings.public_proxy_url, settings.authentication_period_days, + settings.disable_stats_purge, + settings.stats_purge_frequency_hours, + settings.stats_purge_threshold_days, ], ); @@ -129,6 +158,26 @@ const Content = ({ settings }: { settings: Settings }) => { [defaultValues.authentication_period_days], ); + const statsPurgeFrequencySelectOptions = useMemo( + () => + withNumericFallbackOption( + statsPurgeFrequencyOptions, + defaultValues.stats_purge_frequency_hours, + 'hours', + ), + [defaultValues.stats_purge_frequency_hours], + ); + + const statsPurgeThresholdSelectOptions = useMemo( + () => + withNumericFallbackOption( + statsPurgeThresholdOptions, + defaultValues.stats_purge_threshold_days, + 'days', + ), + [defaultValues.stats_purge_threshold_days], + ); + const form = useAppForm({ defaultValues, validationLogic: formChangeLogic, @@ -151,30 +200,71 @@ const Content = ({ settings }: { settings: Settings }) => { }} > - - {(field) => ( - - )} - - - - {(field) => ( - - )} - - - - {(field) => ( - - )} - + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + ({ diff --git a/web/src/pages/settings/SettingsVpnStatsPage/SettingsVpnStatsPage.tsx b/web/src/pages/settings/SettingsVpnStatsPage/SettingsVpnStatsPage.tsx deleted file mode 100644 index 2f17b6f308..0000000000 --- a/web/src/pages/settings/SettingsVpnStatsPage/SettingsVpnStatsPage.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { useMutation, useQuery } from '@tanstack/react-query'; -import { Link } from '@tanstack/react-router'; -import { useMemo } from 'react'; -import z from 'zod'; -import { m } from '../../../paraglide/messages'; -import api from '../../../shared/api/api'; -import type { Settings } from '../../../shared/api/types'; -import { Breadcrumbs } from '../../../shared/components/Breadcrumbs/Breadcrumbs'; -import { Controls } from '../../../shared/components/Controls/Controls'; -import { Page } from '../../../shared/components/Page/Page'; -import { SettingsCard } from '../../../shared/components/SettingsCard/SettingsCard'; -import { SettingsHeader } from '../../../shared/components/SettingsHeader/SettingsHeader'; -import { SettingsLayout } from '../../../shared/components/SettingsLayout/SettingsLayout'; -import { Button } from '../../../shared/defguard-ui/components/Button/Button'; -import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; -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 { getSettingsQueryOptions } from '../../../shared/query'; -import { - createNumericSelectOptions, - withNumericFallbackOption, -} from '../../../shared/utils/numericSelectOptions'; - -const breadcrumbs = [ - - {m.settings_breadcrumb_general()} - , - - {m.settings_breadcrumb_vpn_stats()} - , -]; - -export const SettingsVpnStatsPage = () => { - const { data: settings } = useQuery(getSettingsQueryOptions); - return ( - - - - - {isPresent(settings) && ( - - - - )} - - - ); -}; - -const formSchema = z.object({ - disable_stats_purge: z.boolean(), - stats_purge_frequency_hours: z.number(m.form_error_required()).int().min(1), - stats_purge_threshold_days: z.number(m.form_error_required()).int().min(1), -}); - -type FormFields = z.infer; - -const statsPurgeFrequencyBaseOptions = createNumericSelectOptions({ - 1: m.settings_duration_one_hour(), - 12: m.settings_duration_hours({ hours: 12 }), - 24: m.settings_duration_one_day(), - 48: m.settings_duration_days({ days: 2 }), - 168: m.settings_duration_one_week(), - 720: m.settings_duration_one_month(), -}); - -const statsPurgeThresholdBaseOptions = createNumericSelectOptions({ - 1: m.settings_duration_one_day(), - 7: m.settings_duration_days({ days: 7 }), - 14: m.settings_duration_days({ days: 14 }), - 30: m.settings_duration_days({ days: 30 }), - 90: m.settings_duration_days({ days: 90 }), -}); - -const Content = ({ settings }: { settings: Settings }) => { - const { mutateAsync } = useMutation({ - mutationFn: api.settings.patchSettings, - meta: { - invalidate: ['settings'], - }, - }); - - const defaultValues = useMemo( - (): FormFields => ({ - disable_stats_purge: settings.disable_stats_purge ?? false, - stats_purge_frequency_hours: settings.stats_purge_frequency_hours ?? 24, - stats_purge_threshold_days: settings.stats_purge_threshold_days ?? 30, - }), - [settings], - ); - - const statsPurgeFrequencyOptions = useMemo( - () => - withNumericFallbackOption( - statsPurgeFrequencyBaseOptions, - defaultValues.stats_purge_frequency_hours, - 'hours', - ), - [defaultValues.stats_purge_frequency_hours], - ); - - const statsPurgeThresholdOptions = useMemo( - () => - withNumericFallbackOption( - statsPurgeThresholdBaseOptions, - defaultValues.stats_purge_threshold_days, - 'days', - ), - [defaultValues.stats_purge_threshold_days], - ); - - const form = useAppForm({ - defaultValues, - validationLogic: formChangeLogic, - validators: { - onSubmit: formSchema, - onChange: formSchema, - }, - onSubmit: async ({ value }) => { - await mutateAsync(value); - form.reset(value); - }, - }); - - return ( - { - e.stopPropagation(); - e.preventDefault(); - form.handleSubmit(); - }} - > - - - {(field) => ( - - )} - - - - {(field) => ( - - )} - - - - {(field) => ( - - )} - - - ({ - isDefault: s.isDefaultValue || s.isPristine, - isSubmitting: s.isSubmitting, - })} - > - {({ isDefault, isSubmitting }) => ( - - - { - form.handleSubmit(); - }} - /> - - - )} - - - ); -}; diff --git a/web/src/routeTree.gen.ts b/web/src/routeTree.gen.ts index cff2c469e9..312aaad574 100644 --- a/web/src/routeTree.gen.ts +++ b/web/src/routeTree.gen.ts @@ -45,7 +45,6 @@ import { Route as AuthorizedDefaultSettingsIndexRouteImport } from './routes/_au import { Route as AuthorizedDefaultLocationsIndexRouteImport } from './routes/_authorized/_default/locations/index' import { Route as AuthorizedDefaultVpnOverviewLocationIdRouteImport } from './routes/_authorized/_default/vpn-overview/$locationId' import { Route as AuthorizedDefaultUserUsernameRouteImport } from './routes/_authorized/_default/user/$username' -import { Route as AuthorizedDefaultSettingsVpnStatsRouteImport } from './routes/_authorized/_default/settings/vpn-stats' import { Route as AuthorizedDefaultSettingsSmtpRouteImport } from './routes/_authorized/_default/settings/smtp' import { Route as AuthorizedDefaultSettingsOpenidRouteImport } from './routes/_authorized/_default/settings/openid' import { Route as AuthorizedDefaultSettingsLdapRouteImport } from './routes/_authorized/_default/settings/ldap' @@ -257,12 +256,6 @@ const AuthorizedDefaultUserUsernameRoute = path: '/user/$username', getParentRoute: () => AuthorizedDefaultRoute, } as any) -const AuthorizedDefaultSettingsVpnStatsRoute = - AuthorizedDefaultSettingsVpnStatsRouteImport.update({ - id: '/settings/vpn-stats', - path: '/settings/vpn-stats', - getParentRoute: () => AuthorizedDefaultRoute, - } as any) const AuthorizedDefaultSettingsSmtpRoute = AuthorizedDefaultSettingsSmtpRouteImport.update({ id: '/settings/smtp', @@ -432,7 +425,6 @@ export interface FileRoutesByFullPath { '/settings/ldap': typeof AuthorizedDefaultSettingsLdapRoute '/settings/openid': typeof AuthorizedDefaultSettingsOpenidRoute '/settings/smtp': typeof AuthorizedDefaultSettingsSmtpRoute - '/settings/vpn-stats': typeof AuthorizedDefaultSettingsVpnStatsRoute '/user/$username': typeof AuthorizedDefaultUserUsernameRoute '/vpn-overview/$locationId': typeof AuthorizedDefaultVpnOverviewLocationIdRoute '/locations/': typeof AuthorizedDefaultLocationsIndexRoute @@ -489,7 +481,6 @@ export interface FileRoutesByTo { '/settings/ldap': typeof AuthorizedDefaultSettingsLdapRoute '/settings/openid': typeof AuthorizedDefaultSettingsOpenidRoute '/settings/smtp': typeof AuthorizedDefaultSettingsSmtpRoute - '/settings/vpn-stats': typeof AuthorizedDefaultSettingsVpnStatsRoute '/user/$username': typeof AuthorizedDefaultUserUsernameRoute '/vpn-overview/$locationId': typeof AuthorizedDefaultVpnOverviewLocationIdRoute '/locations': typeof AuthorizedDefaultLocationsIndexRoute @@ -549,7 +540,6 @@ export interface FileRoutesById { '/_authorized/_default/settings/ldap': typeof AuthorizedDefaultSettingsLdapRoute '/_authorized/_default/settings/openid': typeof AuthorizedDefaultSettingsOpenidRoute '/_authorized/_default/settings/smtp': typeof AuthorizedDefaultSettingsSmtpRoute - '/_authorized/_default/settings/vpn-stats': typeof AuthorizedDefaultSettingsVpnStatsRoute '/_authorized/_default/user/$username': typeof AuthorizedDefaultUserUsernameRoute '/_authorized/_default/vpn-overview/$locationId': typeof AuthorizedDefaultVpnOverviewLocationIdRoute '/_authorized/_default/locations/': typeof AuthorizedDefaultLocationsIndexRoute @@ -609,7 +599,6 @@ export interface FileRouteTypes { | '/settings/ldap' | '/settings/openid' | '/settings/smtp' - | '/settings/vpn-stats' | '/user/$username' | '/vpn-overview/$locationId' | '/locations/' @@ -666,7 +655,6 @@ export interface FileRouteTypes { | '/settings/ldap' | '/settings/openid' | '/settings/smtp' - | '/settings/vpn-stats' | '/user/$username' | '/vpn-overview/$locationId' | '/locations' @@ -725,7 +713,6 @@ export interface FileRouteTypes { | '/_authorized/_default/settings/ldap' | '/_authorized/_default/settings/openid' | '/_authorized/_default/settings/smtp' - | '/_authorized/_default/settings/vpn-stats' | '/_authorized/_default/user/$username' | '/_authorized/_default/vpn-overview/$locationId' | '/_authorized/_default/locations/' @@ -1003,13 +990,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthorizedDefaultUserUsernameRouteImport parentRoute: typeof AuthorizedDefaultRoute } - '/_authorized/_default/settings/vpn-stats': { - id: '/_authorized/_default/settings/vpn-stats' - path: '/settings/vpn-stats' - fullPath: '/settings/vpn-stats' - preLoaderRoute: typeof AuthorizedDefaultSettingsVpnStatsRouteImport - parentRoute: typeof AuthorizedDefaultRoute - } '/_authorized/_default/settings/smtp': { id: '/_authorized/_default/settings/smtp' path: '/settings/smtp' @@ -1178,7 +1158,6 @@ interface AuthorizedDefaultRouteChildren { AuthorizedDefaultSettingsLdapRoute: typeof AuthorizedDefaultSettingsLdapRoute AuthorizedDefaultSettingsOpenidRoute: typeof AuthorizedDefaultSettingsOpenidRoute AuthorizedDefaultSettingsSmtpRoute: typeof AuthorizedDefaultSettingsSmtpRoute - AuthorizedDefaultSettingsVpnStatsRoute: typeof AuthorizedDefaultSettingsVpnStatsRoute AuthorizedDefaultUserUsernameRoute: typeof AuthorizedDefaultUserUsernameRoute AuthorizedDefaultVpnOverviewLocationIdRoute: typeof AuthorizedDefaultVpnOverviewLocationIdRoute AuthorizedDefaultLocationsIndexRoute: typeof AuthorizedDefaultLocationsIndexRoute @@ -1220,8 +1199,6 @@ const AuthorizedDefaultRouteChildren: AuthorizedDefaultRouteChildren = { AuthorizedDefaultSettingsLdapRoute: AuthorizedDefaultSettingsLdapRoute, AuthorizedDefaultSettingsOpenidRoute: AuthorizedDefaultSettingsOpenidRoute, AuthorizedDefaultSettingsSmtpRoute: AuthorizedDefaultSettingsSmtpRoute, - AuthorizedDefaultSettingsVpnStatsRoute: - AuthorizedDefaultSettingsVpnStatsRoute, AuthorizedDefaultUserUsernameRoute: AuthorizedDefaultUserUsernameRoute, AuthorizedDefaultVpnOverviewLocationIdRoute: AuthorizedDefaultVpnOverviewLocationIdRoute, diff --git a/web/src/routes/_authorized/_default/settings/vpn-stats.tsx b/web/src/routes/_authorized/_default/settings/vpn-stats.tsx deleted file mode 100644 index 83a8d03b85..0000000000 --- a/web/src/routes/_authorized/_default/settings/vpn-stats.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router'; -import { SettingsVpnStatsPage } from '../../../../pages/settings/SettingsVpnStatsPage/SettingsVpnStatsPage'; - -export const Route = createFileRoute('/_authorized/_default/settings/vpn-stats')({ - component: SettingsVpnStatsPage, -}); diff --git a/web/src/shared/defguard-ui b/web/src/shared/defguard-ui index 5724f94474..61d017e5e5 160000 --- a/web/src/shared/defguard-ui +++ b/web/src/shared/defguard-ui @@ -1 +1 @@ -Subproject commit 5724f94474ea9700dc6391fe0458b49b03af5006 +Subproject commit 61d017e5e5ac75b687b73ec0b180df274de21a19
{`Not implemented`}