From 8fcff6fa33166b09cbce0eb83752d3252f4e08b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Tue, 16 Jun 2026 14:48:42 +0200 Subject: [PATCH 1/4] squash 1 --- new-ui/package.json | 1 + new-ui/pnpm-lock.yaml | 30 ++++++++ .../CompactLocationsPage.tsx | 7 +- .../components/InstanceSwitcher.tsx | 8 +-- .../pages/full/OverviewPage/OverviewPage.tsx | 4 +- .../ConnectModalPostureCheckLoading.tsx | 11 +++ .../style.scss | 14 ++++ .../hooks/useConnectModalMfaOidc.ts | 4 +- .../components/ConnectModal/style.scss | 2 + .../ConnectModalMfaEmail.tsx | 11 ++- .../ConnectModalMfaMobile.tsx | 11 ++- .../ConnectModalMfaOidc.tsx | 15 +++- .../ConnectModalMfaSettings.tsx | 27 ++++++- .../ConnectModalMfaTotp.tsx | 11 ++- .../OverviewSelection/OverviewSelection.tsx | 8 +-- new-ui/src/routes/compact/index.tsx | 6 +- new-ui/src/routes/full/_default/overview.tsx | 6 +- new-ui/src/routes/index.tsx | 6 +- .../LocationCardMfaEdit.tsx | 14 +++- .../LocationCard/context/context.tsx | 16 ++--- .../LocationCardMfaSettings.tsx | 10 +-- .../OverviewLocationCard/style.scss | 1 + .../shared/providers/TauriEventProvider.tsx | 31 +++++--- new-ui/src/shared/rust-api/types.ts | 1 + new-ui/src/shared/store/useAppStore.tsx | 10 +-- new-ui/src/shared/store/useSharedStorage.tsx | 43 ++++++++++++ src-tauri/Cargo.lock | 70 +++++++++++++++++++ src-tauri/Cargo.toml | 1 + src-tauri/capabilities/zustand.json | 5 ++ src-tauri/core/src/events.rs | 2 + src-tauri/src/bin/defguard-client.rs | 2 + src-tauri/src/window_manager/mod.rs | 12 +++- 32 files changed, 328 insertions(+), 72 deletions(-) create mode 100644 new-ui/src/pages/full/OverviewPage/components/ConnectModal/components/ConnectModalPostureCheckLoading/ConnectModalPostureCheckLoading.tsx create mode 100644 new-ui/src/pages/full/OverviewPage/components/ConnectModal/components/ConnectModalPostureCheckLoading/style.scss create mode 100644 new-ui/src/shared/store/useSharedStorage.tsx create mode 100644 src-tauri/capabilities/zustand.json diff --git a/new-ui/package.json b/new-ui/package.json index d11f1ca6d..9f914f2f6 100644 --- a/new-ui/package.json +++ b/new-ui/package.json @@ -29,6 +29,7 @@ "@tauri-apps/plugin-log": "^2.8.0", "@tauri-apps/plugin-opener": "^2.5.4", "@tauri-apps/plugin-os": "^2.3.2", + "@tauri-store/zustand": "^1.2.0", "@uidotdev/usehooks": "^2.4.1", "byte-size": "^9.0.1", "chart.js": "^4.5.1", diff --git a/new-ui/pnpm-lock.yaml b/new-ui/pnpm-lock.yaml index e74ca43b1..fdd98e20a 100644 --- a/new-ui/pnpm-lock.yaml +++ b/new-ui/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: '@tauri-apps/plugin-os': specifier: ^2.3.2 version: 2.3.2 + '@tauri-store/zustand': + specifier: ^1.2.0 + version: 1.2.0(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7)) '@uidotdev/usehooks': specifier: ^2.4.1 version: 2.4.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -887,6 +890,12 @@ packages: '@tauri-apps/plugin-os@2.3.2': resolution: {integrity: sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A==} + '@tauri-store/shared@0.10.3': + resolution: {integrity: sha512-6S2SbYxZUBOLL/t+49cIF4gdZNxYnw9yitNlIUsgiPI5ApRtdXfj4JdQd7WmDwoQXXTOzlOYaqGpmICWhzmmRg==} + + '@tauri-store/zustand@1.2.0': + resolution: {integrity: sha512-P/QoSn7fHaYaKrSm8khuyRxm0rTQ6iqjG0fUenq+ul4LIqKi6yRPpDJNlzYKCLfH6KgVNjzboq+jQal+kPG9mg==} + '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} @@ -1141,6 +1150,9 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + es-toolkit@1.47.1: + resolution: {integrity: sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==} + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2692,6 +2704,22 @@ snapshots: dependencies: '@tauri-apps/api': 2.11.0 + '@tauri-store/shared@0.10.3': + dependencies: + '@tauri-apps/api': 2.11.0 + es-toolkit: 1.47.1 + + '@tauri-store/zustand@1.2.0(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7))': + dependencies: + '@tauri-apps/api': 2.11.0 + '@tauri-store/shared': 0.10.3 + zustand: 5.0.14(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7)) + transitivePeerDependencies: + - '@types/react' + - immer + - react + - use-sync-external-store + '@tybys/wasm-util@0.10.2': dependencies: tslib: 2.8.1 @@ -2902,6 +2930,8 @@ snapshots: dependencies: is-arrayish: 0.2.1 + es-toolkit@1.47.1: {} + escalade@3.2.0: {} estree-util-is-identifier-name@3.0.0: {} diff --git a/new-ui/src/pages/compact/CompactLocationsPage/CompactLocationsPage.tsx b/new-ui/src/pages/compact/CompactLocationsPage/CompactLocationsPage.tsx index 51b026702..7feffe4e9 100644 --- a/new-ui/src/pages/compact/CompactLocationsPage/CompactLocationsPage.tsx +++ b/new-ui/src/pages/compact/CompactLocationsPage/CompactLocationsPage.tsx @@ -15,13 +15,14 @@ import { getLocationsQueryOptions, } from '../../../shared/rust-api/query'; import { useAppStore } from '../../../shared/store/useAppStore'; +import { useSharedStorage } from '../../../shared/store/useSharedStorage'; import { ThemeSpacing } from '../../../shared/types'; import { isPresent } from '../../../shared/utils/isPresent'; import { CompactPage } from '../CompactPage/CompactPage'; import { InstanceSwitcher } from './components/InstanceSwitcher'; export const CompactLocationsPage = () => { - const selection = useAppStore((s) => s.compactViewSelection); + const selection = useSharedStorage((s) => s.viewSelection); const openLocation = useAppStore((s) => s.expandedLocation); const routeData = useLoaderData({ from: '/compact/' }); @@ -53,8 +54,8 @@ export const CompactLocationsPage = () => { useEffect(() => { if (selection === null || instanceInfo === undefined) { - useAppStore.setState({ - compactViewSelection: { kind: 'instance', data: routeData.instances[0] }, + useSharedStorage.setState({ + viewSelection: { kind: 'instance', data: routeData.instances[0] }, }); } }, [routeData.instances, instanceInfo, selection]); diff --git a/new-ui/src/pages/compact/CompactLocationsPage/components/InstanceSwitcher.tsx b/new-ui/src/pages/compact/CompactLocationsPage/components/InstanceSwitcher.tsx index c2f3a20e4..13e80420f 100644 --- a/new-ui/src/pages/compact/CompactLocationsPage/components/InstanceSwitcher.tsx +++ b/new-ui/src/pages/compact/CompactLocationsPage/components/InstanceSwitcher.tsx @@ -11,12 +11,12 @@ import { } from '../../../../shared/rust-api/query'; import { type CompactViewSelection, - useAppStore, -} from '../../../../shared/store/useAppStore'; + useSharedStorage, +} from '../../../../shared/store/useSharedStorage'; import { isPresent } from '../../../../shared/utils/isPresent'; export const InstanceSwitcher = () => { - const selectedInstance = useAppStore((s) => s.compactViewSelection); + const selectedInstance = useSharedStorage((s) => s.viewSelection); const { data: tunnels } = useQuery(getTunnelsQueryOptions); const { data: instances } = useQuery(getInstancesQueryOptions); @@ -77,7 +77,7 @@ export const InstanceSwitcher = () => { groups={groups} value={selectedOption as never} onChange={(option) => { - useAppStore.setState({ compactViewSelection: option.value }); + useSharedStorage.setState({ viewSelection: option.value }); }} /> ); diff --git a/new-ui/src/pages/full/OverviewPage/OverviewPage.tsx b/new-ui/src/pages/full/OverviewPage/OverviewPage.tsx index c2bea3309..1347497c3 100644 --- a/new-ui/src/pages/full/OverviewPage/OverviewPage.tsx +++ b/new-ui/src/pages/full/OverviewPage/OverviewPage.tsx @@ -11,7 +11,7 @@ import { FullPage } from '../../../shared/layouts/FullPage/FullPage'; import { useAppData } from '../../../shared/providers/AppDataContext'; import { getLocationsQueryOptions } from '../../../shared/rust-api/query'; import type { InstanceInfo } from '../../../shared/rust-api/types'; -import { useAppStore } from '../../../shared/store/useAppStore'; +import { useSharedStorage } from '../../../shared/store/useSharedStorage'; import { ThemeSpacing } from '../../../shared/types'; import { isPresent } from '../../../shared/utils/isPresent'; import { ConnectModal } from './components/ConnectModal/ConnectModal'; @@ -23,7 +23,7 @@ const isWindows = platform() === 'windows'; export const OverviewPage = () => { const [detailsOpen, setDetailsOpen] = useState(false); const { instances, tunnels } = useAppData(); - const selection = useAppStore((s) => s.compactViewSelection); + const selection = useSharedStorage((s) => s.viewSelection); const queryInstanceId = useMemo(() => { if (!isPresent(selection)) return instances[0].id; diff --git a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/components/ConnectModalPostureCheckLoading/ConnectModalPostureCheckLoading.tsx b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/components/ConnectModalPostureCheckLoading/ConnectModalPostureCheckLoading.tsx new file mode 100644 index 000000000..e8afb4028 --- /dev/null +++ b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/components/ConnectModalPostureCheckLoading/ConnectModalPostureCheckLoading.tsx @@ -0,0 +1,11 @@ +import './style.scss'; +import { LoaderSpinner } from '../../../../../../../shared/components/LoaderSpinner/LoaderSpinner'; + +export const ConnectModalPostureCheckLoading = () => { + return ( +
+ +

Checking device requirements...

+
+ ); +}; diff --git a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/components/ConnectModalPostureCheckLoading/style.scss b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/components/ConnectModalPostureCheckLoading/style.scss new file mode 100644 index 000000000..d0cdd68c1 --- /dev/null +++ b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/components/ConnectModalPostureCheckLoading/style.scss @@ -0,0 +1,14 @@ +.connect-modal-posture-check-loading { + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + row-gap: var(--spacing-lg); + min-height: 220px; + + p { + font: var(--t-body-xs-400); + color: var(--fg-white-100); + text-align: center; + } +} diff --git a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/hooks/useConnectModalMfaOidc.ts b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/hooks/useConnectModalMfaOidc.ts index 9c352a9bf..de844cd13 100644 --- a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/hooks/useConnectModalMfaOidc.ts +++ b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/hooks/useConnectModalMfaOidc.ts @@ -20,7 +20,7 @@ type MfaFinishResponse = { preshared_key: string }; type MfaErrorResponse = { error: string }; type Options = { - onPostureError?: () => void; + onPostureError?: (msg: string) => void; onSessionExpired?: () => void; }; @@ -146,7 +146,7 @@ export const useConnectModalMfaOidc = ({ startPolling(response.token, instance.proxy_url, headers); } catch (e) { if (shouldShowPostureError(e, location)) { - onPostureError?.(); + onPostureError?.(e.message); return; } setStartError( diff --git a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/style.scss b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/style.scss index 844e9a1ce..db7fbaece 100644 --- a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/style.scss +++ b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/style.scss @@ -6,6 +6,8 @@ } .controls { + width: 100%; + .full { width: 100%; } diff --git a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaEmail/ConnectModalMfaEmail.tsx b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaEmail/ConnectModalMfaEmail.tsx index 756c4c26f..009f7ee76 100644 --- a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaEmail/ConnectModalMfaEmail.tsx +++ b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaEmail/ConnectModalMfaEmail.tsx @@ -11,6 +11,7 @@ import { MfaStartMethod } from '../../../../../../../shared/components/LocationC import { useMfaConnect } from '../../../../../../../shared/components/LocationCard/hooks/useMfaConnect'; import type { LocationInfo } from '../../../../../../../shared/rust-api/types'; import { isPresent } from '../../../../../../../shared/utils/isPresent'; +import { ConnectModalPostureCheckLoading } from '../../components/ConnectModalPostureCheckLoading/ConnectModalPostureCheckLoading'; import { ConnectModalView } from '../../hooks/types'; import { useConnectModal } from '../../hooks/useConnectModal'; @@ -28,8 +29,10 @@ export const ConnectModalMfaEmail = () => { debounceMs: location?.posture_check_required ? MIN_POSTURE_LOADER_MS : 0, onSessionExpired: () => useConnectModal.getState().setView(perviousView ?? ConnectModalView.MfaSettings), - onPostureError: () => - useConnectModal.getState().setView(ConnectModalView.PostureCheckFail), + onPostureError: (msg) => { + useConnectModal.setState({ postureError: msg }); + useConnectModal.getState().setView(ConnectModalView.PostureCheckFail); + }, }, ); @@ -57,6 +60,10 @@ export const ConnectModalMfaEmail = () => { if (verifyError) setError(verifyError); }, [verifyError]); + if (isStarting && location?.posture_check_required && !startError) { + return ; + } + return (
{ const { start, isStarting, startError, qrValue, connectionError } = useMfaMobileConnect( location as LocationInfo, { - onPostureError: () => - useConnectModal.getState().setView(ConnectModalView.PostureCheckFail), + onPostureError: (msg) => { + useConnectModal.setState({ postureError: msg }); + useConnectModal.getState().setView(ConnectModalView.PostureCheckFail); + }, }, ); @@ -46,6 +49,10 @@ export const ConnectModalMfaMobile = () => { const errorMessage = startError ?? connectionError; + if (isStarting && location?.posture_check_required && !startError) { + return ; + } + return (

diff --git a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaOidc/ConnectModalMfaOidc.tsx b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaOidc/ConnectModalMfaOidc.tsx index 6c69b75ad..8b1769615 100644 --- a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaOidc/ConnectModalMfaOidc.tsx +++ b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaOidc/ConnectModalMfaOidc.tsx @@ -3,6 +3,7 @@ import { useShallow } from 'zustand/shallow'; import { Button } from '../../../../../../../shared/components/Button/Button'; import { ButtonVariant } from '../../../../../../../shared/components/Button/types'; import { Controls } from '../../../../../../../shared/components/Controls/Controls'; +import { ConnectModalPostureCheckLoading } from '../../components/ConnectModalPostureCheckLoading/ConnectModalPostureCheckLoading'; import { ConnectModalView } from '../../hooks/types'; import { useConnectModal } from '../../hooks/useConnectModal'; import { useConnectModalMfaOidc } from '../../hooks/useConnectModalMfaOidc'; @@ -10,13 +11,17 @@ import { useConnectModalMfaOidc } from '../../hooks/useConnectModalMfaOidc'; type Screen = 'idle' | 'polling' | 'error'; export const ConnectModalMfaOidc = () => { - const perviousView = useConnectModal(useShallow((s) => s.perviousView)); + const [perviousView, location] = useConnectModal( + useShallow((s) => [s.perviousView, s.location]), + ); const { start, isStarting, startError, isPolling, pollError } = useConnectModalMfaOidc({ onSessionExpired: () => useConnectModal.getState().setView(perviousView ?? ConnectModalView.MfaSettings), - onPostureError: () => - useConnectModal.getState().setView(ConnectModalView.PostureCheckFail), + onPostureError: (msg) => { + useConnectModal.setState({ postureError: msg }); + useConnectModal.getState().setView(ConnectModalView.PostureCheckFail); + }, }); const [screen, setScreen] = useState('idle'); @@ -36,6 +41,10 @@ export const ConnectModalMfaOidc = () => { const errorMessage = startError ?? pollError; + if (isStarting && location?.posture_check_required && !startError) { + return ; + } + return (

{screen === 'idle' && ( diff --git a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaSettings/ConnectModalMfaSettings.tsx b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaSettings/ConnectModalMfaSettings.tsx index 6ca736e7a..b4e559def 100644 --- a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaSettings/ConnectModalMfaSettings.tsx +++ b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaSettings/ConnectModalMfaSettings.tsx @@ -17,7 +17,9 @@ import { MfaMethod, type MfaMethodValue, } from '../../../../../../../shared/rust-api/types'; +import { useSharedStorage } from '../../../../../../../shared/store/useSharedStorage'; import { ThemeSpacing } from '../../../../../../../shared/types'; +import { ConnectModalView } from '../../hooks/types'; import { useConnectModal } from '../../hooks/useConnectModal'; export const ConnectModalMfaSettings = () => { @@ -33,7 +35,9 @@ export const ConnectModalMfaSettings = () => { const locationDefaultMfaMethod = location?.mfa_method ?? MfaMethod.Totp; const [selectedMethod, setSelectedMethod] = useState( - location?.mfa_method ?? MfaMethod.Totp, + location + ? useSharedStorage.getState().getLocationMethod(location.id) + : MfaMethod.Totp, ); const [setAsDefault, setSetAsDefault] = useState(true); @@ -45,13 +49,32 @@ export const ConnectModalMfaSettings = () => { }, [location?.location_mfa_mode]); const handleSubmit = () => { + if (!location) return; + useSharedStorage.getState().setLocationMethod(location.id, selectedMethod); if (setAsDefault && selectedMethod !== locationDefaultMfaMethod && location) { setMfaMethod({ locationId: location.id, mfaMethod: selectedMethod }); + } else { } if (perviousView === null) { useConnectModal.setState({ visible: false }); } else { - useConnectModal.getState().setView(perviousView); + switch (selectedMethod) { + case 'totp': + useConnectModal.setState({ view: ConnectModalView.MfaTotp }); + break; + case 'email': + useConnectModal.setState({ view: ConnectModalView.MfaEmail }); + break; + case 'mobileapprove': + useConnectModal.setState({ view: ConnectModalView.MfaMobile }); + break; + case 'oidc': + useConnectModal.setState({ view: ConnectModalView.MfaOidc }); + break; + default: + useConnectModal.setState({ visible: false }); + break; + } } }; diff --git a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaTotp/ConnectModalMfaTotp.tsx b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaTotp/ConnectModalMfaTotp.tsx index 9e51717d4..f3d920299 100644 --- a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaTotp/ConnectModalMfaTotp.tsx +++ b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaTotp/ConnectModalMfaTotp.tsx @@ -11,6 +11,7 @@ import { MfaStartMethod } from '../../../../../../../shared/components/LocationC import { useMfaConnect } from '../../../../../../../shared/components/LocationCard/hooks/useMfaConnect'; import type { LocationInfo } from '../../../../../../../shared/rust-api/types'; import { isPresent } from '../../../../../../../shared/utils/isPresent'; +import { ConnectModalPostureCheckLoading } from '../../components/ConnectModalPostureCheckLoading/ConnectModalPostureCheckLoading'; import { ConnectModalView } from '../../hooks/types'; import { useConnectModal } from '../../hooks/useConnectModal'; @@ -28,8 +29,10 @@ export const ConnectModalMfaTotp = () => { debounceMs: location?.posture_check_required ? MIN_POSTURE_LOADER_MS : 0, onSessionExpired: () => useConnectModal.getState().setView(perviousView ?? ConnectModalView.MfaSettings), - onPostureError: () => - useConnectModal.getState().setView(ConnectModalView.PostureCheckFail), + onPostureError: (err) => { + useConnectModal.setState({ postureError: err }); + useConnectModal.getState().setView(ConnectModalView.PostureCheckFail); + }, }, ); @@ -57,6 +60,10 @@ export const ConnectModalMfaTotp = () => { if (verifyError) setError(verifyError); }, [verifyError]); + if (isStarting && location?.posture_check_required && !startError) { + return ; + } + return (
( ); export const OverviewSelection = ({ instances, tunnels }: Props) => { - const selection = useAppStore((s) => s.compactViewSelection); + const selection = useSharedStorage((s) => s.viewSelection); const setSelection = (value: CompactViewSelection) => { - useAppStore.setState({ compactViewSelection: value }); + useSharedStorage.setState({ viewSelection: value }); }; const isSelected = (candidate: CompactViewSelection): boolean => { diff --git a/new-ui/src/routes/compact/index.tsx b/new-ui/src/routes/compact/index.tsx index 441551d7c..3f25c128d 100644 --- a/new-ui/src/routes/compact/index.tsx +++ b/new-ui/src/routes/compact/index.tsx @@ -6,7 +6,7 @@ import { getTunnelsQueryOptions, } from '../../shared/rust-api/query'; import type { LocationInfo } from '../../shared/rust-api/types'; -import { useAppStore } from '../../shared/store/useAppStore'; +import { useSharedStorage } from '../../shared/store/useSharedStorage'; export const Route = createFileRoute('/compact/')({ loader: async ({ context }) => { @@ -19,7 +19,7 @@ export const Route = createFileRoute('/compact/')({ throw redirect({ to: '/empty' }); } - const stored = useAppStore.getState().compactViewSelection; + const stored = useSharedStorage.getState().viewSelection; let storedIsValid: boolean; if (stored === null) { @@ -40,7 +40,7 @@ export const Route = createFileRoute('/compact/')({ } if (!storedIsValid) { - useAppStore.setState({ compactViewSelection: selected }); + useSharedStorage.setState({ viewSelection: selected }); } let locations: LocationInfo[]; diff --git a/new-ui/src/routes/full/_default/overview.tsx b/new-ui/src/routes/full/_default/overview.tsx index 711692515..a299c02af 100644 --- a/new-ui/src/routes/full/_default/overview.tsx +++ b/new-ui/src/routes/full/_default/overview.tsx @@ -4,7 +4,7 @@ import { getInstancesQueryOptions, getTunnelsQueryOptions, } from '../../../shared/rust-api/query'; -import { useAppStore } from '../../../shared/store/useAppStore'; +import { useSharedStorage } from '../../../shared/store/useSharedStorage'; export const Route = createFileRoute('/full/_default/overview')({ loader: async ({ context }) => { @@ -17,7 +17,7 @@ export const Route = createFileRoute('/full/_default/overview')({ throw redirect({ to: '/empty' }); } - const stored = useAppStore.getState().compactViewSelection; + const stored = useSharedStorage.getState().viewSelection; let storedIsValid: boolean; if (stored === null) { @@ -33,7 +33,7 @@ export const Route = createFileRoute('/full/_default/overview')({ instances.length > 0 ? { kind: 'instance' as const, data: instances[0] } : { kind: 'tunnel' as const, data: tunnels[0] }; - useAppStore.setState({ compactViewSelection: selected }); + useSharedStorage.setState({ viewSelection: selected }); } }, component: OverviewPage, diff --git a/new-ui/src/routes/index.tsx b/new-ui/src/routes/index.tsx index e3da6280b..d95afed73 100644 --- a/new-ui/src/routes/index.tsx +++ b/new-ui/src/routes/index.tsx @@ -6,7 +6,7 @@ import { getTunnelsQueryOptions, } from '../shared/rust-api/query'; import type { LocationInfo } from '../shared/rust-api/types'; -import { useAppStore } from '../shared/store/useAppStore'; +import { useSharedStorage } from '../shared/store/useSharedStorage'; export const Route = createFileRoute('/')({ loader: async ({ context }) => { @@ -19,7 +19,7 @@ export const Route = createFileRoute('/')({ throw redirect({ to: '/empty' }); } - const stored = useAppStore.getState().compactViewSelection; + const stored = useSharedStorage.getState().viewSelection; let storedIsValid: boolean; if (stored === null) { @@ -40,7 +40,7 @@ export const Route = createFileRoute('/')({ } if (!storedIsValid) { - useAppStore.setState({ compactViewSelection: selected }); + useSharedStorage.setState({ viewSelection: selected }); } let locations: LocationInfo[]; diff --git a/new-ui/src/shared/components/LocationCard/components/LocationCardMfaEdit/LocationCardMfaEdit.tsx b/new-ui/src/shared/components/LocationCard/components/LocationCardMfaEdit/LocationCardMfaEdit.tsx index b2ead6c68..22756f806 100644 --- a/new-ui/src/shared/components/LocationCard/components/LocationCardMfaEdit/LocationCardMfaEdit.tsx +++ b/new-ui/src/shared/components/LocationCard/components/LocationCardMfaEdit/LocationCardMfaEdit.tsx @@ -1,6 +1,8 @@ import './style.scss'; import clsx from 'clsx'; +import { useEffect, useMemo } from 'react'; import { type LocationInfo, MfaMethod } from '../../../../rust-api/types'; +import { useSharedStorage } from '../../../../store/useSharedStorage'; import { mfaToText } from '../../../../utils/mfa'; import { IconButton } from '../../../IconButton/IconButton'; import { IconButtonVariant } from '../../../IconButton/types'; @@ -12,7 +14,15 @@ interface Props { } export const LocationCardMfaEdit = ({ location, onEdit, variant }: Props) => { - const mfaMethod = location.mfa_method ?? MfaMethod.Totp; + const methodSelection = useSharedStorage((s) => s.locationMethodSelection); + const mfaMethod = useMemo( + () => methodSelection[location.id] ?? MfaMethod.Totp, + [methodSelection, location.id], + ); + + useEffect(() => { + console.log(mfaMethod, methodSelection); + }, [mfaMethod, methodSelection]); if (location.location_mfa_mode === 'disabled') return null; @@ -22,7 +32,7 @@ export const LocationCardMfaEdit = ({ location, onEdit, variant }: Props) => {

MFA

{mfaToText(mfaMethod)}

- {location.location_mfa_mode === 'internal' && ( + {location.location_mfa_mode === 'internal' && !location.active && ( void; setPostureError: (error: string | null) => void; startMfa: () => void; - localMfaMethod: MfaMethodValue; - setLocalMfaMethod: (method: MfaMethodValue) => void; } const LocationCardContext = createContext(null); @@ -42,9 +41,6 @@ export const LocationCardProvider = ({ const [currentView, setCurrentView] = useState( location.active ? LocationCardViews.Connected : LocationCardViews.Default, ); - const [localMfaMethod, setLocalMfaMethod] = useState( - location.mfa_method ?? MfaMethod.Totp, - ); const setView = useCallback( (view: LocationCardViewsValue) => { @@ -55,7 +51,9 @@ export const LocationCardProvider = ({ ); const startMfa = useCallback(() => { - switch (localMfaMethod) { + const mfaMethod = useSharedStorage.getState().getLocationMethod(location.id); + + switch (mfaMethod) { case MfaMethod.Totp: setView(LocationCardViews.MfaTotp); break; @@ -69,7 +67,7 @@ export const LocationCardProvider = ({ setView(LocationCardViews.MfaMobile); break; } - }, [localMfaMethod, setView]); + }, [setView, location.id]); return ( {children} diff --git a/new-ui/src/shared/components/LocationCard/views/LocationCardMfaSettings/LocationCardMfaSettings.tsx b/new-ui/src/shared/components/LocationCard/views/LocationCardMfaSettings/LocationCardMfaSettings.tsx index 04b066f63..99e83f3be 100644 --- a/new-ui/src/shared/components/LocationCard/views/LocationCardMfaSettings/LocationCardMfaSettings.tsx +++ b/new-ui/src/shared/components/LocationCard/views/LocationCardMfaSettings/LocationCardMfaSettings.tsx @@ -7,6 +7,7 @@ import { MfaMethod, type MfaMethodValue, } from '../../../../rust-api/types'; +import { useSharedStorage } from '../../../../store/useSharedStorage'; import { ThemeSpacing } from '../../../../types'; import { Button } from '../../../Button/Button'; import { ButtonVariant } from '../../../Button/types'; @@ -30,13 +31,14 @@ export const LocationCardMfaSettings = () => { }, }); - const { previousView, setView, location, localMfaMethod, setLocalMfaMethod } = - useLocationCardContext(); + const { previousView, setView, location } = useLocationCardContext(); const locationDefaultMfaMethod = location.mfa_method ?? MfaMethod.Totp; const [selectedMethod, setSelectedPref] = useState( - localMfaMethod ?? MfaMethod.Totp, + location + ? useSharedStorage.getState().getLocationMethod(location.id) + : MfaMethod.Totp, ); const isFromDefault = previousView === LocationCardViews.Default; @@ -50,7 +52,7 @@ export const LocationCardMfaSettings = () => { }, [location.location_mfa_mode]); const handleSubmit = () => { - setLocalMfaMethod(selectedMethod); + useSharedStorage.getState().setLocationMethod(location.id, selectedMethod); if ((isFromDefault || setAsDefault) && selectedMethod !== locationDefaultMfaMethod) { setMfaMethod({ locationId: location.id, diff --git a/new-ui/src/shared/components/OverviewLocationCard/style.scss b/new-ui/src/shared/components/OverviewLocationCard/style.scss index a5d15ae04..226f40488 100644 --- a/new-ui/src/shared/components/OverviewLocationCard/style.scss +++ b/new-ui/src/shared/components/OverviewLocationCard/style.scss @@ -29,5 +29,6 @@ display: flex; flex-flow: row nowrap; column-gap: var(--spacing-5xl); + min-height: 24px; } } diff --git a/new-ui/src/shared/providers/TauriEventProvider.tsx b/new-ui/src/shared/providers/TauriEventProvider.tsx index e9c1f4184..2f3661269 100644 --- a/new-ui/src/shared/providers/TauriEventProvider.tsx +++ b/new-ui/src/shared/providers/TauriEventProvider.tsx @@ -2,6 +2,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { useNavigate } from '@tanstack/react-router'; import { listen } from '@tauri-apps/api/event'; import { getCurrentWindow } from '@tauri-apps/api/window'; +import { debug } from '@tauri-apps/plugin-log'; import { Fragment, type PropsWithChildren, useEffect } from 'react'; import { WindowId } from '../consts'; import { @@ -18,7 +19,7 @@ export const TauriEventProvider = ({ children }: PropsWithChildren) => { useEffect(() => { const unlisteners = Promise.all([ listen(TauriEvent.AddInstance, (event) => { - console.log('[TauriEvent] AddInstance', event.payload); + void debug(`UI Received event AddInstance: ${JSON.stringify(event.payload)}`); const windowLabel = getCurrentWindow().label; if (windowLabel === WindowId.FullView) { const { token, url } = event.payload; @@ -32,7 +33,9 @@ export const TauriEventProvider = ({ children }: PropsWithChildren) => { } }), listen(TauriEvent.ConnectionChanged, (event) => { - console.log('[TauriEvent] ConnectionChanged', event.payload); + void debug( + `UI Received event ConnectionChanged: ${JSON.stringify(event.payload)}`, + ); void queryClient.invalidateQueries({ queryKey: ['alive-connection'] }); void queryClient.invalidateQueries({ queryKey: ['active-connection'] }); void queryClient.invalidateQueries({ queryKey: ['locations'] }); @@ -42,26 +45,26 @@ export const TauriEventProvider = ({ children }: PropsWithChildren) => { }), listen(TauriEvent.InstanceUpdate, (event) => { - console.log('[TauriEvent] InstanceUpdate', event.payload); + void debug(`UI Received event InstanceUpdate: ${JSON.stringify(event.payload)}`); void queryClient.invalidateQueries({ queryKey: ['instances'] }); void queryClient.invalidateQueries({ queryKey: ['locations'] }); void queryClient.invalidateQueries({ queryKey: ['has-any-visible-locations'] }); }), listen(TauriEvent.LocationUpdate, (event) => { - console.log('[TauriEvent] LocationUpdate', event.payload); + void debug(`UI Received event LocationUpdate: ${JSON.stringify(event.payload)}`); void queryClient.invalidateQueries({ queryKey: ['locations'] }); void queryClient.invalidateQueries({ queryKey: ['location-details'] }); void queryClient.invalidateQueries({ queryKey: ['has-any-visible-locations'] }); }), listen(TauriEvent.AppVersionFetch, (event) => { - console.log('[TauriEvent] AppVersionFetch', event.payload); + void debug(`UI Received event AppVersionFetch: ${JSON.stringify(event.payload)}`); void queryClient.invalidateQueries({ queryKey: ['latest-app-version'] }); }), listen(TauriEvent.ConfigChanged, (event) => { - console.log('[TauriEvent] ConfigChanged', event.payload); + void debug(`UI Received event ConfigChanged: ${JSON.stringify(event.payload)}`); void queryClient.invalidateQueries({ queryKey: ['settings'] }); void queryClient.invalidateQueries({ queryKey: ['provisioning-config'] }); void queryClient.invalidateQueries({ queryKey: ['instances'] }); @@ -69,7 +72,9 @@ export const TauriEventProvider = ({ children }: PropsWithChildren) => { }), listen(TauriEvent.DeadConnectionDropped, (event) => { - console.log('[TauriEvent] DeadConnectionDropped', event.payload); + void debug( + `UI Received event DeadConnectionDropped: ${JSON.stringify(event.payload)}`, + ); void queryClient.invalidateQueries({ queryKey: ['alive-connection'] }); void queryClient.invalidateQueries({ queryKey: ['active-connection'] }); void queryClient.invalidateQueries({ queryKey: ['locations'] }); @@ -79,7 +84,9 @@ export const TauriEventProvider = ({ children }: PropsWithChildren) => { listen( TauriEvent.DeadConnectionReconnected, (event) => { - console.log('[TauriEvent] DeadConnectionReconnected', event.payload); + void debug( + `UI Received event DeadConnectionReconnected: ${JSON.stringify(event.payload)}`, + ); void queryClient.invalidateQueries({ queryKey: ['alive-connection'] }); void queryClient.invalidateQueries({ queryKey: ['active-connection'] }); void queryClient.invalidateQueries({ queryKey: ['locations'] }); @@ -88,17 +95,19 @@ export const TauriEventProvider = ({ children }: PropsWithChildren) => { ), listen(TauriEvent.ApplicationConfigChanged, (event) => { - console.log('[TauriEvent] ApplicationConfigChanged', event.payload); + void debug( + `UI Received event ApplicationConfigChanged: ${JSON.stringify(event.payload)}`, + ); void queryClient.invalidateQueries({ queryKey: ['settings'] }); }), listen(TauriEvent.AddInstance, (event) => { - console.log('[TauriEvent] AddInstance (instances invalidation)', event.payload); + void debug(`UI Received event AddInstance: ${JSON.stringify(event.payload)}`); void queryClient.invalidateQueries({ queryKey: ['instances'] }); }), listen(TauriEvent.UuidMismatch, (event) => { - console.log('[TauriEvent] UuidMismatch', event.payload); + void debug(`UI Received event UuidMismatch: ${JSON.stringify(event.payload)}`); void queryClient.invalidateQueries({ queryKey: ['instances'] }); }), ]); diff --git a/new-ui/src/shared/rust-api/types.ts b/new-ui/src/shared/rust-api/types.ts index 67f019b02..03a0a2e75 100644 --- a/new-ui/src/shared/rust-api/types.ts +++ b/new-ui/src/shared/rust-api/types.ts @@ -142,6 +142,7 @@ export const TauriEvent = { VersionMismatch: 'version-mismatch', UuidMismatch: 'uuid-mismatch', GlobalLogUpdate: 'log-update-global', + WindowSwapped: 'window-swapped', } as const; export type TauriEventValue = (typeof TauriEvent)[keyof typeof TauriEvent]; diff --git a/new-ui/src/shared/store/useAppStore.tsx b/new-ui/src/shared/store/useAppStore.tsx index 99d839cb4..d013881c3 100644 --- a/new-ui/src/shared/store/useAppStore.tsx +++ b/new-ui/src/shared/store/useAppStore.tsx @@ -1,13 +1,8 @@ import { create } from 'zustand'; import { createJSONStorage, persist } from 'zustand/middleware'; -import type { InstanceInfo, LocationInfo } from '../rust-api/types'; - -export type CompactViewSelection = - | { kind: 'instance'; data: InstanceInfo } - | { kind: 'tunnel'; data: LocationInfo }; interface StoreValues { - compactViewSelection: CompactViewSelection | null; + // only used in compact mode expandedLocation: number | null; } @@ -16,13 +11,12 @@ interface Store extends StoreValues {} export const useAppStore = create()( persist( (_) => ({ - compactViewSelection: null, expandedLocation: null, }), { name: 'app-store', storage: createJSONStorage(() => localStorage), - version: 3, + version: 4, }, ), ); diff --git a/new-ui/src/shared/store/useSharedStorage.tsx b/new-ui/src/shared/store/useSharedStorage.tsx new file mode 100644 index 000000000..5855473fb --- /dev/null +++ b/new-ui/src/shared/store/useSharedStorage.tsx @@ -0,0 +1,43 @@ +import { createTauriStore } from '@tauri-store/zustand'; +import { clone } from 'radashi'; +import { create, type StoreApi } from 'zustand'; +import { + type InstanceInfo, + type LocationInfo, + MfaMethod, + type MfaMethodValue, +} from '../rust-api/types'; + +export type CompactViewSelection = + | { kind: 'instance'; data: InstanceInfo } + | { kind: 'tunnel'; data: LocationInfo }; + +interface StoreValues { + locationMethodSelection: Record; + viewSelection: CompactViewSelection | null; +} + +interface Store extends StoreValues { + setLocationMethod: (id: number, method: MfaMethodValue) => void; + getLocationMethod: (id: number) => MfaMethodValue; +} + +export const useSharedStorage = create()((set, get) => ({ + locationMethodSelection: {}, + viewSelection: null, + getLocationMethod: (locationId) => { + const selection = get().locationMethodSelection; + return selection[locationId] ?? MfaMethod.Totp; + }, + setLocationMethod: (locationId, method) => { + const selection = clone(get().locationMethodSelection); + selection[locationId] = method; + set({ locationMethodSelection: selection }); + }, +})); + +export const sharedStorageTauriHandler = createTauriStore( + 'shared-session-store', + useSharedStorage as unknown as StoreApi>, + { autoStart: true, syncStrategy: 'debounce', syncInterval: 500 }, +); diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index e93ac5df7..1f26d9f73 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1381,6 +1381,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-url" version = "0.3.2" @@ -1478,6 +1492,7 @@ dependencies = [ "tauri-plugin-process", "tauri-plugin-single-instance", "tauri-plugin-window-state", + "tauri-plugin-zustand", "thiserror 2.0.18", "time", "tokio", @@ -6954,6 +6969,18 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "tauri-plugin-zustand" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555b972b3adc737535a82c64b3fc425d2272162979a123ab6b7a7517d30405e8" +dependencies = [ + "serde", + "tauri", + "tauri-plugin", + "tauri-store", +] + [[package]] name = "tauri-runtime" version = "2.11.2" @@ -7005,6 +7032,49 @@ dependencies = [ "wry", ] +[[package]] +name = "tauri-store" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e2b75d2702cfa42b6994a187de814c691edf9e56dd049406cf5df8e915cf86" +dependencies = [ + "dashmap", + "futures", + "itertools", + "semver", + "serde", + "serde_json", + "tauri", + "tauri-store-macros", + "tauri-store-utils", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "tauri-store-macros" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cae6e366131190fd2f23a9275cefc90cdac0c4837e6ad3920d9b99901cfdb2f1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tauri-store-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2edd6d150fd2797bbce745d1c55c11887c4456c41d31f56e8eb867d62bd334e7" +dependencies = [ + "parking_lot", + "semver", + "tauri", + "thiserror 2.0.18", + "tokio", +] + [[package]] name = "tauri-utils" version = "2.9.2" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 992a7e5c1..6d80b6456 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -110,6 +110,7 @@ tauri-plugin-os = "2" tauri-plugin-process = "2" tauri-plugin-single-instance = { version = "2", features = ["deep-link"] } tauri-plugin-window-state = "2" +tauri-plugin-zustand = "1" thiserror.workspace = true time = { version = "0.3", features = ["formatting", "macros"] } tokio.workspace = true diff --git a/src-tauri/capabilities/zustand.json b/src-tauri/capabilities/zustand.json new file mode 100644 index 000000000..f0fc49a0b --- /dev/null +++ b/src-tauri/capabilities/zustand.json @@ -0,0 +1,5 @@ +{ + "identifier": "zustand", + "windows": ["*"], + "permissions": ["zustand:default", "core:event:default"] +} diff --git a/src-tauri/core/src/events.rs b/src-tauri/core/src/events.rs index 1a3504e80..6b9cb1962 100644 --- a/src-tauri/core/src/events.rs +++ b/src-tauri/core/src/events.rs @@ -14,6 +14,7 @@ pub enum EventKey { MfaTrigger, VersionMismatch, UuidMismatch, + WindowSwapped, } impl From for &'static str { @@ -31,6 +32,7 @@ impl From for &'static str { EventKey::MfaTrigger => "mfa-trigger", EventKey::VersionMismatch => "version-mismatch", EventKey::UuidMismatch => "uuid-mismatch", + EventKey::WindowSwapped => "window-swapped", } } } diff --git a/src-tauri/src/bin/defguard-client.rs b/src-tauri/src/bin/defguard-client.rs index fcdf4d895..c3914d708 100644 --- a/src-tauri/src/bin/defguard-client.rs +++ b/src-tauri/src/bin/defguard-client.rs @@ -37,6 +37,7 @@ use log::{Level, LevelFilter}; use tauri::{async_runtime, AppHandle, Builder, Manager, RunEvent, WindowEvent}; use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_log::{Target, TargetKind}; +use tauri_plugin_zustand; #[macro_use] extern crate log; @@ -241,6 +242,7 @@ fn main() { .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_process::init()) + .plugin(tauri_plugin_zustand::Builder::new().save_denylist(&["shared-session-store"]).build()) .setup(|app| { // Create Help menu on macOS. // https://github.com/tauri-apps/tauri/issues/9371 diff --git a/src-tauri/src/window_manager/mod.rs b/src-tauri/src/window_manager/mod.rs index 6e70b235c..d600f1c65 100644 --- a/src-tauri/src/window_manager/mod.rs +++ b/src-tauri/src/window_manager/mod.rs @@ -1,8 +1,11 @@ #[cfg(not(target_os = "windows"))] use tauri::Manager; -use tauri::{AppHandle, WebviewUrl, WebviewWindow, WebviewWindowBuilder}; +use tauri::{AppHandle, Emitter, WebviewUrl, WebviewWindow, WebviewWindowBuilder}; -use crate::database::{models::location::Location, DB_POOL}; +use crate::{ + database::{models::location::Location, DB_POOL}, + events::EventKey, +}; /// Returns `true` if there are any non-service locations in the database. pub async fn has_non_service_locations() -> bool { @@ -141,6 +144,8 @@ pub fn swap_to_full_view(app: AppHandle) { } if let Err(err) = WindowManager::open_full_view(&app) { error!("swap_to_full_view task: Failed to open full view: {err:?}"); + } else if let Err(err) = app.emit(EventKey::WindowSwapped.into(), ()) { + error!("swap_to_full_view task: Failed to emit window swapped event: {err:?}"); } } @@ -167,4 +172,7 @@ pub fn swap_to_tray(app: AppHandle) { error!("swap_to_tray task: Failed to hide full-view window: {err:?}"); } } + if let Err(err) = app.emit(EventKey::WindowSwapped.into(), ()) { + error!("swap_to_tray task: Failed to emit window swapped event: {err:?}"); + } } From e51d9afdef8e96d8c2d78d279d625fd913d07784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Wed, 17 Jun 2026 13:03:02 +0200 Subject: [PATCH 2/4] rewrite shared state between windows --- new-ui/package.json | 1 - new-ui/pnpm-lock.yaml | 30 --------- .../CompactLocationsPage.tsx | 10 ++- .../components/InstanceSwitcher.tsx | 18 +++--- .../pages/full/OverviewPage/OverviewPage.tsx | 3 +- .../ConnectModalMfaSettings.tsx | 8 ++- .../OverviewSelection/OverviewSelection.tsx | 24 ++++---- new-ui/src/routes/compact/index.tsx | 15 +++-- new-ui/src/routes/full/_default/overview.tsx | 11 +++- new-ui/src/routes/full/index.tsx | 18 +++++- new-ui/src/routes/index.tsx | 61 ++----------------- .../LocationCardMfaEdit.tsx | 14 ++--- .../LocationCard/context/context.tsx | 8 ++- .../LocationCardMfaSettings.tsx | 9 ++- .../src/shared/providers/AppDataContext.tsx | 59 ++++++++++++++++-- .../shared/providers/TauriEventProvider.tsx | 4 ++ new-ui/src/shared/providers/types.ts | 8 +++ new-ui/src/shared/rust-api/api.ts | 10 +++ new-ui/src/shared/rust-api/query.ts | 5 ++ new-ui/src/shared/rust-api/types.ts | 17 ++++++ new-ui/src/shared/store/useSharedStorage.tsx | 43 ------------- .../core/src/database/models/instance.rs | 4 +- src-tauri/core/src/events.rs | 2 + src-tauri/src/appstate.rs | 3 + src-tauri/src/bin/defguard-client.rs | 4 +- src-tauri/src/commands.rs | 2 +- src-tauri/src/lib.rs | 1 + src-tauri/src/session_state.rs | 48 +++++++++++++++ 28 files changed, 242 insertions(+), 198 deletions(-) create mode 100644 new-ui/src/shared/providers/types.ts delete mode 100644 new-ui/src/shared/store/useSharedStorage.tsx create mode 100644 src-tauri/src/session_state.rs diff --git a/new-ui/package.json b/new-ui/package.json index 9f914f2f6..d11f1ca6d 100644 --- a/new-ui/package.json +++ b/new-ui/package.json @@ -29,7 +29,6 @@ "@tauri-apps/plugin-log": "^2.8.0", "@tauri-apps/plugin-opener": "^2.5.4", "@tauri-apps/plugin-os": "^2.3.2", - "@tauri-store/zustand": "^1.2.0", "@uidotdev/usehooks": "^2.4.1", "byte-size": "^9.0.1", "chart.js": "^4.5.1", diff --git a/new-ui/pnpm-lock.yaml b/new-ui/pnpm-lock.yaml index fdd98e20a..e74ca43b1 100644 --- a/new-ui/pnpm-lock.yaml +++ b/new-ui/pnpm-lock.yaml @@ -56,9 +56,6 @@ importers: '@tauri-apps/plugin-os': specifier: ^2.3.2 version: 2.3.2 - '@tauri-store/zustand': - specifier: ^1.2.0 - version: 1.2.0(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7)) '@uidotdev/usehooks': specifier: ^2.4.1 version: 2.4.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -890,12 +887,6 @@ packages: '@tauri-apps/plugin-os@2.3.2': resolution: {integrity: sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A==} - '@tauri-store/shared@0.10.3': - resolution: {integrity: sha512-6S2SbYxZUBOLL/t+49cIF4gdZNxYnw9yitNlIUsgiPI5ApRtdXfj4JdQd7WmDwoQXXTOzlOYaqGpmICWhzmmRg==} - - '@tauri-store/zustand@1.2.0': - resolution: {integrity: sha512-P/QoSn7fHaYaKrSm8khuyRxm0rTQ6iqjG0fUenq+ul4LIqKi6yRPpDJNlzYKCLfH6KgVNjzboq+jQal+kPG9mg==} - '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} @@ -1150,9 +1141,6 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - es-toolkit@1.47.1: - resolution: {integrity: sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==} - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2704,22 +2692,6 @@ snapshots: dependencies: '@tauri-apps/api': 2.11.0 - '@tauri-store/shared@0.10.3': - dependencies: - '@tauri-apps/api': 2.11.0 - es-toolkit: 1.47.1 - - '@tauri-store/zustand@1.2.0(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7))': - dependencies: - '@tauri-apps/api': 2.11.0 - '@tauri-store/shared': 0.10.3 - zustand: 5.0.14(@types/react@19.2.17)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7)) - transitivePeerDependencies: - - '@types/react' - - immer - - react - - use-sync-external-store - '@tybys/wasm-util@0.10.2': dependencies: tslib: 2.8.1 @@ -2930,8 +2902,6 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-toolkit@1.47.1: {} - escalade@3.2.0: {} estree-util-is-identifier-name@3.0.0: {} diff --git a/new-ui/src/pages/compact/CompactLocationsPage/CompactLocationsPage.tsx b/new-ui/src/pages/compact/CompactLocationsPage/CompactLocationsPage.tsx index 7feffe4e9..8bba51700 100644 --- a/new-ui/src/pages/compact/CompactLocationsPage/CompactLocationsPage.tsx +++ b/new-ui/src/pages/compact/CompactLocationsPage/CompactLocationsPage.tsx @@ -9,20 +9,20 @@ import { Divider } from '../../../shared/components/Divider/Divider'; import { LocationCard } from '../../../shared/components/LocationCard/LocationCard'; import { ScrollContainer } from '../../../shared/components/ScrollContainer/ScrollContainer'; import { WindowHeader } from '../../../shared/components/WindowHeader/WindowHeader'; +import { useAppData } from '../../../shared/providers/AppDataContext'; import { api } from '../../../shared/rust-api/api'; import { getInstancesQueryOptions, getLocationsQueryOptions, } from '../../../shared/rust-api/query'; import { useAppStore } from '../../../shared/store/useAppStore'; -import { useSharedStorage } from '../../../shared/store/useSharedStorage'; import { ThemeSpacing } from '../../../shared/types'; import { isPresent } from '../../../shared/utils/isPresent'; import { CompactPage } from '../CompactPage/CompactPage'; import { InstanceSwitcher } from './components/InstanceSwitcher'; export const CompactLocationsPage = () => { - const selection = useSharedStorage((s) => s.viewSelection); + const { viewSelection: selection, setViewSelection } = useAppData(); const openLocation = useAppStore((s) => s.expandedLocation); const routeData = useLoaderData({ from: '/compact/' }); @@ -54,11 +54,9 @@ export const CompactLocationsPage = () => { useEffect(() => { if (selection === null || instanceInfo === undefined) { - useSharedStorage.setState({ - viewSelection: { kind: 'instance', data: routeData.instances[0] }, - }); + setViewSelection({ kind: 'instance', data: routeData.instances[0] }); } - }, [routeData.instances, instanceInfo, selection]); + }, [routeData.instances, instanceInfo, selection, setViewSelection]); return ( { - const selectedInstance = useSharedStorage((s) => s.viewSelection); + const { viewSelection: selectedInstance, setViewSelection } = useAppData(); const { data: tunnels } = useQuery(getTunnelsQueryOptions); const { data: instances } = useQuery(getInstancesQueryOptions); - const groups = useMemo((): readonly SelectOptionGroup[] => { + const groups = useMemo((): readonly SelectOptionGroup[] => { if (!isPresent(instances) || !isPresent(tunnels)) return []; - const instanceGroup: SelectOptionGroup = { + const instanceGroup: SelectOptionGroup = { key: 'instances', label: 'Instances', options: instances.map((instance) => ({ @@ -34,7 +32,7 @@ export const InstanceSwitcher = () => { })), }; - const tunnelGroup: SelectOptionGroup = { + const tunnelGroup: SelectOptionGroup = { key: 'tunnels', label: 'Tunnels', options: tunnels.map((tunnel) => ({ @@ -52,7 +50,7 @@ export const InstanceSwitcher = () => { [groups], ); - const selectedOption = useMemo((): SelectOption | undefined => { + const selectedOption = useMemo((): SelectOption | undefined => { if (!isPresent(selectedInstance)) return undefined; for (const group of groups) { const found = group.options.find((o) => { @@ -77,7 +75,7 @@ export const InstanceSwitcher = () => { groups={groups} value={selectedOption as never} onChange={(option) => { - useSharedStorage.setState({ viewSelection: option.value }); + setViewSelection(option.value); }} /> ); diff --git a/new-ui/src/pages/full/OverviewPage/OverviewPage.tsx b/new-ui/src/pages/full/OverviewPage/OverviewPage.tsx index 1347497c3..50f3ae7b5 100644 --- a/new-ui/src/pages/full/OverviewPage/OverviewPage.tsx +++ b/new-ui/src/pages/full/OverviewPage/OverviewPage.tsx @@ -11,7 +11,6 @@ import { FullPage } from '../../../shared/layouts/FullPage/FullPage'; import { useAppData } from '../../../shared/providers/AppDataContext'; import { getLocationsQueryOptions } from '../../../shared/rust-api/query'; import type { InstanceInfo } from '../../../shared/rust-api/types'; -import { useSharedStorage } from '../../../shared/store/useSharedStorage'; import { ThemeSpacing } from '../../../shared/types'; import { isPresent } from '../../../shared/utils/isPresent'; import { ConnectModal } from './components/ConnectModal/ConnectModal'; @@ -23,7 +22,7 @@ const isWindows = platform() === 'windows'; export const OverviewPage = () => { const [detailsOpen, setDetailsOpen] = useState(false); const { instances, tunnels } = useAppData(); - const selection = useSharedStorage((s) => s.viewSelection); + const { viewSelection: selection } = useAppData(); const queryInstanceId = useMemo(() => { if (!isPresent(selection)) return instances[0].id; diff --git a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaSettings/ConnectModalMfaSettings.tsx b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaSettings/ConnectModalMfaSettings.tsx index b4e559def..f7c4d6b26 100644 --- a/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaSettings/ConnectModalMfaSettings.tsx +++ b/new-ui/src/pages/full/OverviewPage/components/ConnectModal/views/ConnectModalMfaSettings/ConnectModalMfaSettings.tsx @@ -11,13 +11,13 @@ import { IconButton } from '../../../../../../../shared/components/IconButton/Ic import { IconButtonVariant } from '../../../../../../../shared/components/IconButton/types'; import { MfaSelector } from '../../../../../../../shared/components/LocationCard/components/MfaSelector/MfaSelector'; import { SizedBox } from '../../../../../../../shared/components/SizedBox/SizedBox'; +import { useAppData } from '../../../../../../../shared/providers/AppDataContext'; import { api } from '../../../../../../../shared/rust-api/api'; import { LocationMfaMode, MfaMethod, type MfaMethodValue, } from '../../../../../../../shared/rust-api/types'; -import { useSharedStorage } from '../../../../../../../shared/store/useSharedStorage'; import { ThemeSpacing } from '../../../../../../../shared/types'; import { ConnectModalView } from '../../hooks/types'; import { useConnectModal } from '../../hooks/useConnectModal'; @@ -28,6 +28,8 @@ export const ConnectModalMfaSettings = () => { meta: { invalidate: [['locations']] }, }); + const { locationMfaPreference, setLocationMfaPreference } = useAppData(); + const [perviousView, location] = useConnectModal( useShallow((s) => [s.perviousView, s.location]), ); @@ -36,7 +38,7 @@ export const ConnectModalMfaSettings = () => { const [selectedMethod, setSelectedMethod] = useState( location - ? useSharedStorage.getState().getLocationMethod(location.id) + ? (locationMfaPreference[String(location.id)] ?? MfaMethod.Totp) : MfaMethod.Totp, ); const [setAsDefault, setSetAsDefault] = useState(true); @@ -50,7 +52,7 @@ export const ConnectModalMfaSettings = () => { const handleSubmit = () => { if (!location) return; - useSharedStorage.getState().setLocationMethod(location.id, selectedMethod); + setLocationMfaPreference(location.id, selectedMethod); if (setAsDefault && selectedMethod !== locationDefaultMfaMethod && location) { setMfaMethod({ locationId: location.id, mfaMethod: selectedMethod }); } else { diff --git a/new-ui/src/pages/full/OverviewPage/components/OverviewSelection/OverviewSelection.tsx b/new-ui/src/pages/full/OverviewPage/components/OverviewSelection/OverviewSelection.tsx index 81fe1f61a..4366435f4 100644 --- a/new-ui/src/pages/full/OverviewPage/components/OverviewSelection/OverviewSelection.tsx +++ b/new-ui/src/pages/full/OverviewPage/components/OverviewSelection/OverviewSelection.tsx @@ -1,11 +1,11 @@ import './style.scss'; import clsx from 'clsx'; - -import type { InstanceInfo, LocationInfo } from '../../../../../shared/rust-api/types'; -import { - type CompactViewSelection, - useSharedStorage, -} from '../../../../../shared/store/useSharedStorage'; +import { useAppData } from '../../../../../shared/providers/AppDataContext'; +import type { + InstanceInfo, + LocationInfo, + OverviewViewSelection, +} from '../../../../../shared/rust-api/types'; type Props = { instances: InstanceInfo[]; @@ -25,13 +25,13 @@ const SelectionItem = ({ label, selected, onClick }: SelectionItemProps) => ( ); export const OverviewSelection = ({ instances, tunnels }: Props) => { - const selection = useSharedStorage((s) => s.viewSelection); + const { viewSelection: selection, setViewSelection } = useAppData(); - const setSelection = (value: CompactViewSelection) => { - useSharedStorage.setState({ viewSelection: value }); + const setSelection = (value: OverviewViewSelection) => { + setViewSelection(value); }; - const isSelected = (candidate: CompactViewSelection): boolean => { + const isSelected = (candidate: OverviewViewSelection): boolean => { if (!selection) return false; if (candidate.kind !== selection.kind) return false; return candidate.data.id === selection.data.id; @@ -44,7 +44,7 @@ export const OverviewSelection = ({ instances, tunnels }: Props) => {

Instances

{instances.map((instance) => { - const value: CompactViewSelection = { kind: 'instance', data: instance }; + const value: OverviewViewSelection = { kind: 'instance', data: instance }; return ( {

Tunnels

{tunnels.map((tunnel) => { - const value: CompactViewSelection = { kind: 'tunnel', data: tunnel }; + const value: OverviewViewSelection = { kind: 'tunnel', data: tunnel }; return ( { @@ -19,7 +20,10 @@ export const Route = createFileRoute('/compact/')({ throw redirect({ to: '/empty' }); } - const stored = useSharedStorage.getState().viewSelection; + const sessionState = await context.queryClient.fetchQuery( + getSessionStateQueryOptions, + ); + const stored = sessionState?.view_selection ?? null; let storedIsValid: boolean; if (stored === null) { @@ -30,7 +34,7 @@ export const Route = createFileRoute('/compact/')({ storedIsValid = tunnels.some((t) => t.id === stored.data.id); } - let selected: NonNullable; + let selected: OverviewViewSelection; if (storedIsValid && stored !== null) { selected = stored; } else if (instances.length > 0) { @@ -40,7 +44,8 @@ export const Route = createFileRoute('/compact/')({ } if (!storedIsValid) { - useSharedStorage.setState({ viewSelection: selected }); + await api.patchSessionState({ view_selection: selected }); + await context.queryClient.invalidateQueries({ queryKey: ['session-state'] }); } let locations: LocationInfo[]; diff --git a/new-ui/src/routes/full/_default/overview.tsx b/new-ui/src/routes/full/_default/overview.tsx index a299c02af..4c4341386 100644 --- a/new-ui/src/routes/full/_default/overview.tsx +++ b/new-ui/src/routes/full/_default/overview.tsx @@ -1,10 +1,11 @@ import { createFileRoute, redirect } from '@tanstack/react-router'; import { OverviewPage } from '../../../pages/full/OverviewPage/OverviewPage'; +import { api } from '../../../shared/rust-api/api'; import { getInstancesQueryOptions, + getSessionStateQueryOptions, getTunnelsQueryOptions, } from '../../../shared/rust-api/query'; -import { useSharedStorage } from '../../../shared/store/useSharedStorage'; export const Route = createFileRoute('/full/_default/overview')({ loader: async ({ context }) => { @@ -17,7 +18,10 @@ export const Route = createFileRoute('/full/_default/overview')({ throw redirect({ to: '/empty' }); } - const stored = useSharedStorage.getState().viewSelection; + const sessionState = await context.queryClient.fetchQuery( + getSessionStateQueryOptions, + ); + const stored = sessionState?.view_selection ?? null; let storedIsValid: boolean; if (stored === null) { @@ -33,7 +37,8 @@ export const Route = createFileRoute('/full/_default/overview')({ instances.length > 0 ? { kind: 'instance' as const, data: instances[0] } : { kind: 'tunnel' as const, data: tunnels[0] }; - useSharedStorage.setState({ viewSelection: selected }); + await api.patchSessionState({ view_selection: selected }); + await context.queryClient.invalidateQueries({ queryKey: ['session-state'] }); } }, component: OverviewPage, diff --git a/new-ui/src/routes/full/index.tsx b/new-ui/src/routes/full/index.tsx index f068f8f48..1ad5e6e87 100644 --- a/new-ui/src/routes/full/index.tsx +++ b/new-ui/src/routes/full/index.tsx @@ -1,6 +1,22 @@ -import { createFileRoute, Navigate } from '@tanstack/react-router'; +import { createFileRoute, Navigate, redirect } from '@tanstack/react-router'; +import { + getInstancesQueryOptions, + getTunnelsQueryOptions, +} from '../../shared/rust-api/query'; export const Route = createFileRoute('/full/')({ + beforeLoad: async ({ context }) => { + const [instances, tunnels] = await Promise.all([ + context.queryClient.fetchQuery(getInstancesQueryOptions), + context.queryClient.fetchQuery(getTunnelsQueryOptions), + ]); + + if (instances.length === 0 && tunnels.length === 0) { + throw redirect({ to: '/full/add' }); + } else { + throw redirect({ to: '/full/overview' }); + } + }, component: RouteComponent, }); diff --git a/new-ui/src/routes/index.tsx b/new-ui/src/routes/index.tsx index d95afed73..5c016c86a 100644 --- a/new-ui/src/routes/index.tsx +++ b/new-ui/src/routes/index.tsx @@ -1,58 +1,9 @@ -import { createFileRoute, redirect } from '@tanstack/react-router'; -import { CompactLocationsPage } from '../pages/compact/CompactLocationsPage/CompactLocationsPage'; -import { - getInstancesQueryOptions, - getLocationsQueryOptions, - getTunnelsQueryOptions, -} from '../shared/rust-api/query'; -import type { LocationInfo } from '../shared/rust-api/types'; -import { useSharedStorage } from '../shared/store/useSharedStorage'; +import { createFileRoute, Navigate } from '@tanstack/react-router'; export const Route = createFileRoute('/')({ - loader: async ({ context }) => { - const [instances, tunnels] = await Promise.all([ - context.queryClient.fetchQuery(getInstancesQueryOptions), - context.queryClient.fetchQuery(getTunnelsQueryOptions), - ]); - - if (instances.length === 0 && tunnels.length === 0) { - throw redirect({ to: '/empty' }); - } - - const stored = useSharedStorage.getState().viewSelection; - - let storedIsValid: boolean; - if (stored === null) { - storedIsValid = false; - } else if (stored.kind === 'instance') { - storedIsValid = instances.some((i) => i.id === stored.data.id); - } else { - storedIsValid = tunnels.some((t) => t.id === stored.data.id); - } - - let selected: NonNullable; - if (storedIsValid && stored !== null) { - selected = stored; - } else if (instances.length > 0) { - selected = { kind: 'instance', data: instances[0] }; - } else { - selected = { kind: 'tunnel', data: tunnels[0] }; - } - - if (!storedIsValid) { - useSharedStorage.setState({ viewSelection: selected }); - } - - let locations: LocationInfo[]; - if (selected.kind === 'instance') { - locations = await context.queryClient.fetchQuery( - getLocationsQueryOptions(selected.data.id), - ); - } else { - locations = []; - } - - return { instances, tunnels, locations }; - }, - component: CompactLocationsPage, + component: Component, }); + +function Component() { + return ; +} diff --git a/new-ui/src/shared/components/LocationCard/components/LocationCardMfaEdit/LocationCardMfaEdit.tsx b/new-ui/src/shared/components/LocationCard/components/LocationCardMfaEdit/LocationCardMfaEdit.tsx index 22756f806..6e47a71b2 100644 --- a/new-ui/src/shared/components/LocationCard/components/LocationCardMfaEdit/LocationCardMfaEdit.tsx +++ b/new-ui/src/shared/components/LocationCard/components/LocationCardMfaEdit/LocationCardMfaEdit.tsx @@ -1,8 +1,8 @@ import './style.scss'; import clsx from 'clsx'; -import { useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; +import { useAppData } from '../../../../providers/AppDataContext'; import { type LocationInfo, MfaMethod } from '../../../../rust-api/types'; -import { useSharedStorage } from '../../../../store/useSharedStorage'; import { mfaToText } from '../../../../utils/mfa'; import { IconButton } from '../../../IconButton/IconButton'; import { IconButtonVariant } from '../../../IconButton/types'; @@ -14,16 +14,12 @@ interface Props { } export const LocationCardMfaEdit = ({ location, onEdit, variant }: Props) => { - const methodSelection = useSharedStorage((s) => s.locationMethodSelection); + const { locationMfaPreference } = useAppData(); const mfaMethod = useMemo( - () => methodSelection[location.id] ?? MfaMethod.Totp, - [methodSelection, location.id], + () => locationMfaPreference[String(location.id)] ?? MfaMethod.Totp, + [locationMfaPreference, location.id], ); - useEffect(() => { - console.log(mfaMethod, methodSelection); - }, [mfaMethod, methodSelection]); - if (location.location_mfa_mode === 'disabled') return null; return ( diff --git a/new-ui/src/shared/components/LocationCard/context/context.tsx b/new-ui/src/shared/components/LocationCard/context/context.tsx index fbf413943..a097fb913 100644 --- a/new-ui/src/shared/components/LocationCard/context/context.tsx +++ b/new-ui/src/shared/components/LocationCard/context/context.tsx @@ -1,7 +1,7 @@ import { createContext, type ReactNode, useCallback, useContext, useState } from 'react'; +import { useAppData } from '../../../providers/AppDataContext'; import type { InstanceInfo, LocationInfo } from '../../../rust-api/types'; import { MfaMethod } from '../../../rust-api/types'; -import { useSharedStorage } from '../../../store/useSharedStorage'; import { LocationCardViews, type LocationCardViewsValue } from './types'; interface LocationCardContextValue { @@ -50,8 +50,10 @@ export const LocationCardProvider = ({ [currentView], ); + const { locationMfaPreference } = useAppData(); + const startMfa = useCallback(() => { - const mfaMethod = useSharedStorage.getState().getLocationMethod(location.id); + const mfaMethod = locationMfaPreference[String(location.id)] ?? MfaMethod.Totp; switch (mfaMethod) { case MfaMethod.Totp: @@ -67,7 +69,7 @@ export const LocationCardProvider = ({ setView(LocationCardViews.MfaMobile); break; } - }, [setView, location.id]); + }, [setView, location.id, locationMfaPreference]); return ( { }, }); + const { locationMfaPreference, setLocationMfaPreference } = useAppData(); const { previousView, setView, location } = useLocationCardContext(); const locationDefaultMfaMethod = location.mfa_method ?? MfaMethod.Totp; const [selectedMethod, setSelectedPref] = useState( - location - ? useSharedStorage.getState().getLocationMethod(location.id) - : MfaMethod.Totp, + locationMfaPreference[String(location.id)] ?? MfaMethod.Totp, ); const isFromDefault = previousView === LocationCardViews.Default; @@ -52,7 +51,7 @@ export const LocationCardMfaSettings = () => { }, [location.location_mfa_mode]); const handleSubmit = () => { - useSharedStorage.getState().setLocationMethod(location.id, selectedMethod); + setLocationMfaPreference(location.id, selectedMethod); if ((isFromDefault || setAsDefault) && selectedMethod !== locationDefaultMfaMethod) { setMfaMethod({ locationId: location.id, diff --git a/new-ui/src/shared/providers/AppDataContext.tsx b/new-ui/src/shared/providers/AppDataContext.tsx index fb4f1f2a0..6d7c8049b 100644 --- a/new-ui/src/shared/providers/AppDataContext.tsx +++ b/new-ui/src/shared/providers/AppDataContext.tsx @@ -1,12 +1,25 @@ -import { useQuery } from '@tanstack/react-query'; -import { createContext, type PropsWithChildren, useContext } from 'react'; -import { getInstancesQueryOptions, getTunnelsQueryOptions } from '../rust-api/query'; -import type { InstanceInfo, LocationInfo } from '../rust-api/types'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { createContext, type PropsWithChildren, useCallback, useContext } from 'react'; +import { api } from '../rust-api/api'; +import { + getInstancesQueryOptions, + getSessionStateQueryOptions, + getTunnelsQueryOptions, +} from '../rust-api/query'; +import type { + InstanceInfo, + LocationInfo, + MfaMethodValue, + OverviewViewSelection, +} from '../rust-api/types'; +import type { SharedSessionStorage } from './types'; -interface AppDataContextValue { +interface AppDataContextValue extends SharedSessionStorage { instances: InstanceInfo[]; tunnels: LocationInfo[]; isEmpty: boolean; + setViewSelection: (selection: OverviewViewSelection | null) => void; + setLocationMfaPreference: (locationId: number, method: MfaMethodValue) => void; } const AppDataContext = createContext(null); @@ -18,11 +31,45 @@ export const useAppData = (): AppDataContextValue => { }; export const AppDataProvider = ({ children }: PropsWithChildren) => { + const queryClient = useQueryClient(); const { data: instances = [] } = useQuery(getInstancesQueryOptions); const { data: tunnels = [] } = useQuery(getTunnelsQueryOptions); + const { data: sessionState } = useQuery(getSessionStateQueryOptions); const isEmpty = instances.length === 0 && tunnels.length === 0; + + const setViewSelection = useCallback( + (selection: OverviewViewSelection | null) => { + api + .patchSessionState({ view_selection: selection }) + .then(() => queryClient.invalidateQueries({ queryKey: ['session-state'] })); + }, + [queryClient], + ); + + const setLocationMfaPreference = useCallback( + (locationId: number, method: MfaMethodValue) => { + const current = sessionState?.location_mfa_preference ?? {}; + api + .patchSessionState({ + location_mfa_preference: { ...current, [String(locationId)]: method }, + }) + .then(() => queryClient.invalidateQueries({ queryKey: ['session-state'] })); + }, + [queryClient, sessionState?.location_mfa_preference], + ); + return ( - + {children} ); diff --git a/new-ui/src/shared/providers/TauriEventProvider.tsx b/new-ui/src/shared/providers/TauriEventProvider.tsx index 2f3661269..4d7faa5a7 100644 --- a/new-ui/src/shared/providers/TauriEventProvider.tsx +++ b/new-ui/src/shared/providers/TauriEventProvider.tsx @@ -110,6 +110,10 @@ export const TauriEventProvider = ({ children }: PropsWithChildren) => { void debug(`UI Received event UuidMismatch: ${JSON.stringify(event.payload)}`); void queryClient.invalidateQueries({ queryKey: ['instances'] }); }), + + listen(TauriEvent.SessionStateChanged, () => { + void queryClient.invalidateQueries({ queryKey: ['session-state'] }); + }), ]); return () => { diff --git a/new-ui/src/shared/providers/types.ts b/new-ui/src/shared/providers/types.ts new file mode 100644 index 000000000..afc490534 --- /dev/null +++ b/new-ui/src/shared/providers/types.ts @@ -0,0 +1,8 @@ +import type { MfaMethodValue, OverviewViewSelection } from '../rust-api/types'; + +export type { OverviewViewSelection }; + +export type SharedSessionStorage = { + viewSelection: OverviewViewSelection | null; + locationMfaPreference: Record; +}; diff --git a/new-ui/src/shared/rust-api/api.ts b/new-ui/src/shared/rust-api/api.ts index 7e66f95ea..ab6f75891 100644 --- a/new-ui/src/shared/rust-api/api.ts +++ b/new-ui/src/shared/rust-api/api.ts @@ -15,6 +15,8 @@ import type { RoutingArgs, SaveConfigArgs, SaveDeviceConfigResponse, + SessionState, + SessionStatePatch, SetLocationMfaMethodArgs, StatsArgs, TunnelInfo, @@ -124,6 +126,11 @@ const swapToTray = async () => invoke(TauriCommand.SwapToTray); const closeTrayWindow = async () => invoke(TauriCommand.CloseTrayWindow); +const getSessionState = (): Promise => invoke(TauriCommand.GetSessionState); + +const patchSessionState = (patch: SessionStatePatch): Promise => + invoke(TauriCommand.PatchSessionState, { patch }); + export const api = { // Instances getInstances, @@ -167,4 +174,7 @@ export const api = { swapToFullView, swapToTray, closeTrayWindow, + // Session state + getSessionState, + patchSessionState, }; diff --git a/new-ui/src/shared/rust-api/query.ts b/new-ui/src/shared/rust-api/query.ts index 0d44f3ce2..ca5ea023d 100644 --- a/new-ui/src/shared/rust-api/query.ts +++ b/new-ui/src/shared/rust-api/query.ts @@ -91,3 +91,8 @@ export const getPlatformHeaderQueryOptions = queryOptions({ queryKey: ['platform-header'] as const, queryFn: () => api.getPlatformHeader(), }); + +export const getSessionStateQueryOptions = queryOptions({ + queryKey: ['session-state'] as const, + queryFn: () => api.getSessionState(), +}); diff --git a/new-ui/src/shared/rust-api/types.ts b/new-ui/src/shared/rust-api/types.ts index 03a0a2e75..9d0571e6d 100644 --- a/new-ui/src/shared/rust-api/types.ts +++ b/new-ui/src/shared/rust-api/types.ts @@ -123,6 +123,9 @@ export const TauriCommand = { SwapToFullView: 'swap_to_full_view', SwapToTray: 'swap_to_tray', CloseTrayWindow: 'close_tray_window', + // Session state + GetSessionState: 'command_get_session_state', + PatchSessionState: 'command_patch_session_state', } as const; export type TauriCommand = (typeof TauriCommand)[keyof typeof TauriCommand]; @@ -143,6 +146,7 @@ export const TauriEvent = { UuidMismatch: 'uuid-mismatch', GlobalLogUpdate: 'log-update-global', WindowSwapped: 'window-swapped', + SessionStateChanged: 'session-state-changed', } as const; export type TauriEventValue = (typeof TauriEvent)[keyof typeof TauriEvent]; @@ -362,3 +366,16 @@ export type SetLocationMfaMethodArgs = { locationId: number; mfaMethod: MfaMethodValue; }; + +export type OverviewViewSelection = + | { kind: 'instance'; data: InstanceInfo } + | { kind: 'tunnel'; data: LocationInfo }; + +/** Mirrors `SessionState` in src/session_state.rs. Fields are snake_case (raw serde output). */ +export type SessionState = { + view_selection: OverviewViewSelection | null; + /** Keys are location IDs serialized as strings (JSON object keys are always strings). */ + location_mfa_preference: Record; +}; + +export type SessionStatePatch = Partial; diff --git a/new-ui/src/shared/store/useSharedStorage.tsx b/new-ui/src/shared/store/useSharedStorage.tsx deleted file mode 100644 index 5855473fb..000000000 --- a/new-ui/src/shared/store/useSharedStorage.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { createTauriStore } from '@tauri-store/zustand'; -import { clone } from 'radashi'; -import { create, type StoreApi } from 'zustand'; -import { - type InstanceInfo, - type LocationInfo, - MfaMethod, - type MfaMethodValue, -} from '../rust-api/types'; - -export type CompactViewSelection = - | { kind: 'instance'; data: InstanceInfo } - | { kind: 'tunnel'; data: LocationInfo }; - -interface StoreValues { - locationMethodSelection: Record; - viewSelection: CompactViewSelection | null; -} - -interface Store extends StoreValues { - setLocationMethod: (id: number, method: MfaMethodValue) => void; - getLocationMethod: (id: number) => MfaMethodValue; -} - -export const useSharedStorage = create()((set, get) => ({ - locationMethodSelection: {}, - viewSelection: null, - getLocationMethod: (locationId) => { - const selection = get().locationMethodSelection; - return selection[locationId] ?? MfaMethod.Totp; - }, - setLocationMethod: (locationId, method) => { - const selection = clone(get().locationMethodSelection); - selection[locationId] = method; - set({ locationMethodSelection: selection }); - }, -})); - -export const sharedStorageTauriHandler = createTauriStore( - 'shared-session-store', - useSharedStorage as unknown as StoreApi>, - { autoStart: true, syncStrategy: 'debounce', syncInterval: 500 }, -); diff --git a/src-tauri/core/src/database/models/instance.rs b/src-tauri/core/src/database/models/instance.rs index fcfc7d78c..1b3a91e2a 100644 --- a/src-tauri/core/src/database/models/instance.rs +++ b/src-tauri/core/src/database/models/instance.rs @@ -205,7 +205,7 @@ impl Instance { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct InstanceInfo { pub id: I, pub name: String, @@ -226,7 +226,7 @@ impl fmt::Display for InstanceInfo { } /// Describes allowed traffic options for clients connecting to an instance. -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Type)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Type)] #[repr(u32)] #[serde(rename_all = "snake_case")] pub enum ClientTrafficPolicy { diff --git a/src-tauri/core/src/events.rs b/src-tauri/core/src/events.rs index 6b9cb1962..cc8802a24 100644 --- a/src-tauri/core/src/events.rs +++ b/src-tauri/core/src/events.rs @@ -15,6 +15,7 @@ pub enum EventKey { VersionMismatch, UuidMismatch, WindowSwapped, + SessionStateChanged, } impl From for &'static str { @@ -33,6 +34,7 @@ impl From for &'static str { EventKey::VersionMismatch => "version-mismatch", EventKey::UuidMismatch => "uuid-mismatch", EventKey::WindowSwapped => "window-swapped", + EventKey::SessionStateChanged => "session-state-changed", } } } diff --git a/src-tauri/src/appstate.rs b/src-tauri/src/appstate.rs index e9a98113d..1072c15fd 100644 --- a/src-tauri/src/appstate.rs +++ b/src-tauri/src/appstate.rs @@ -10,6 +10,7 @@ use crate::{ app_config::AppConfig, database::models::{connection::ActiveConnection, Id}, enterprise::provisioning::ProvisioningConfig, + session_state::SessionState, utils::stats_handler, ConnectionType, }; @@ -21,6 +22,7 @@ pub struct AppState { pub tray_click_position: Mutex>>, stat_threads: Mutex>>, // location ID is the key pub provisioning_config: Mutex>, + pub session_state: Mutex, } impl AppState { @@ -32,6 +34,7 @@ impl AppState { tray_click_position: Mutex::new(None), stat_threads: Mutex::new(HashMap::new()), provisioning_config: Mutex::new(provisioning_config), + session_state: Mutex::new(SessionState::default()), } } diff --git a/src-tauri/src/bin/defguard-client.rs b/src-tauri/src/bin/defguard-client.rs index c3914d708..c8a899ae2 100644 --- a/src-tauri/src/bin/defguard-client.rs +++ b/src-tauri/src/bin/defguard-client.rs @@ -26,7 +26,7 @@ use defguard_client::{ enterprise::provisioning::handle_client_initialization, events::handle_deep_link, periodic::run_periodic_tasks, - service, + service, session_state, tray::{configure_tray_icon, setup_tray}, utils::load_log_targets, window_manager::*, @@ -209,6 +209,8 @@ fn main() { close_tray_window, all_active_connections, disconnect_locations, + session_state::command_get_session_state, + session_state::command_patch_session_state, ]) .on_window_event(|window, event| { if let WindowEvent::CloseRequested { api, .. } = event { diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 2a413c152..9b262267b 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -520,7 +520,7 @@ pub async fn all_instances() -> Result>, Error> { Ok(instance_info) } -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct LocationInfo { pub id: Id, pub instance_id: Id, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 335ad4b5c..6b54ce62d 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -10,6 +10,7 @@ pub mod events; pub mod log_watcher; pub mod periodic; pub mod service; +pub mod session_state; pub mod tray; pub mod utils; pub mod window_manager; diff --git a/src-tauri/src/session_state.rs b/src-tauri/src/session_state.rs new file mode 100644 index 000000000..9bf2e4cc5 --- /dev/null +++ b/src-tauri/src/session_state.rs @@ -0,0 +1,48 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use struct_patch::Patch; +use tauri::{AppHandle, Emitter, Manager, State}; + +use defguard_client_core::{ + database::models::{instance::InstanceInfo, location::LocationMfaMethod, Id}, + events::EventKey, +}; + +use crate::{appstate::AppState, commands::LocationInfo}; + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(tag = "kind", content = "data", rename_all = "lowercase")] +pub enum OverviewViewSelection { + Instance(InstanceInfo), + Tunnel(LocationInfo), +} + +#[derive(Clone, Debug, Default, Deserialize, Patch, Serialize)] +#[patch(attribute(derive(Debug, Deserialize, Serialize)))] +pub struct SessionState { + pub view_selection: Option, + pub location_mfa_preference: HashMap, +} + +#[tauri::command] +pub fn command_get_session_state(app_state: State<'_, AppState>) -> SessionState { + app_state.session_state.lock().unwrap().clone() +} + +#[tauri::command(async)] +pub async fn command_patch_session_state( + patch: SessionStatePatch, + app_handle: AppHandle, +) -> Result { + let app_state = app_handle.state::(); + let updated = { + let mut session_state = app_state.session_state.lock().unwrap(); + session_state.apply(patch); + session_state.clone() + }; + if let Err(err) = app_handle.emit(EventKey::SessionStateChanged.into(), ()) { + error!("Failed to emit session-state-changed event: {err}"); + } + Ok(updated) +} From 28a3a4b1d087e2ebb1f9c45c09d8bdda1a0fa9fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Wed, 17 Jun 2026 13:19:48 +0200 Subject: [PATCH 3/4] fix new commands permissions --- new-ui/src/routes/__root.tsx | 9 ++++++--- new-ui/src/routes/full.tsx | 7 +------ new-ui/src/shared/rust-api/types.ts | 4 ++-- src-tauri/permissions/default.toml | 2 ++ src-tauri/src/bin/defguard-client.rs | 4 ++-- src-tauri/src/session_state.rs | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/new-ui/src/routes/__root.tsx b/new-ui/src/routes/__root.tsx index 0ba077398..43684fab9 100644 --- a/new-ui/src/routes/__root.tsx +++ b/new-ui/src/routes/__root.tsx @@ -1,5 +1,6 @@ import type { QueryClient } from '@tanstack/react-query'; import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'; +import { AppDataProvider } from '../shared/providers/AppDataContext'; import { TauriEventProvider } from '../shared/providers/TauriEventProvider'; interface RouterContext { @@ -14,8 +15,10 @@ export const Route = createRootRouteWithContext()({ function RootComponent() { return ( - - - + + + + + ); } diff --git a/new-ui/src/routes/full.tsx b/new-ui/src/routes/full.tsx index f9e4c062f..6321128ae 100644 --- a/new-ui/src/routes/full.tsx +++ b/new-ui/src/routes/full.tsx @@ -1,14 +1,9 @@ import { createFileRoute, Outlet } from '@tanstack/react-router'; -import { AppDataProvider } from '../shared/providers/AppDataContext'; export const Route = createFileRoute('/full')({ component: RouteComponent, }); function RouteComponent() { - return ( - - - - ); + return ; } diff --git a/new-ui/src/shared/rust-api/types.ts b/new-ui/src/shared/rust-api/types.ts index 9d0571e6d..176afe790 100644 --- a/new-ui/src/shared/rust-api/types.ts +++ b/new-ui/src/shared/rust-api/types.ts @@ -124,8 +124,8 @@ export const TauriCommand = { SwapToTray: 'swap_to_tray', CloseTrayWindow: 'close_tray_window', // Session state - GetSessionState: 'command_get_session_state', - PatchSessionState: 'command_patch_session_state', + GetSessionState: 'get_session_state', + PatchSessionState: 'patch_session_state', } as const; export type TauriCommand = (typeof TauriCommand)[keyof typeof TauriCommand]; diff --git a/src-tauri/permissions/default.toml b/src-tauri/permissions/default.toml index b1b216f9e..18b028e8e 100644 --- a/src-tauri/permissions/default.toml +++ b/src-tauri/permissions/default.toml @@ -39,4 +39,6 @@ commands.allow = [ "all_active_connections", "disconnect_locations", "get_posture_data", + "get_session_state", + "patch_session_state", ] diff --git a/src-tauri/src/bin/defguard-client.rs b/src-tauri/src/bin/defguard-client.rs index c8a899ae2..bacb38627 100644 --- a/src-tauri/src/bin/defguard-client.rs +++ b/src-tauri/src/bin/defguard-client.rs @@ -209,8 +209,8 @@ fn main() { close_tray_window, all_active_connections, disconnect_locations, - session_state::command_get_session_state, - session_state::command_patch_session_state, + session_state::get_session_state, + session_state::patch_session_state, ]) .on_window_event(|window, event| { if let WindowEvent::CloseRequested { api, .. } = event { diff --git a/src-tauri/src/session_state.rs b/src-tauri/src/session_state.rs index 9bf2e4cc5..8928918d8 100644 --- a/src-tauri/src/session_state.rs +++ b/src-tauri/src/session_state.rs @@ -26,12 +26,12 @@ pub struct SessionState { } #[tauri::command] -pub fn command_get_session_state(app_state: State<'_, AppState>) -> SessionState { +pub fn get_session_state(app_state: State<'_, AppState>) -> SessionState { app_state.session_state.lock().unwrap().clone() } #[tauri::command(async)] -pub async fn command_patch_session_state( +pub async fn patch_session_state( patch: SessionStatePatch, app_handle: AppHandle, ) -> Result { From 7bacbfc5e31dcf0ebfabdebd8678b68c5f916a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Wed, 17 Jun 2026 13:22:34 +0200 Subject: [PATCH 4/4] revert zustand changes rust side --- src-tauri/Cargo.lock | 70 ---------------------------- src-tauri/Cargo.toml | 1 - src-tauri/capabilities/zustand.json | 5 -- src-tauri/src/bin/defguard-client.rs | 2 - 4 files changed, 78 deletions(-) delete mode 100644 src-tauri/capabilities/zustand.json diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1f26d9f73..e93ac5df7 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1381,20 +1381,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "dashmap" -version = "6.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "data-url" version = "0.3.2" @@ -1492,7 +1478,6 @@ dependencies = [ "tauri-plugin-process", "tauri-plugin-single-instance", "tauri-plugin-window-state", - "tauri-plugin-zustand", "thiserror 2.0.18", "time", "tokio", @@ -6969,18 +6954,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "tauri-plugin-zustand" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555b972b3adc737535a82c64b3fc425d2272162979a123ab6b7a7517d30405e8" -dependencies = [ - "serde", - "tauri", - "tauri-plugin", - "tauri-store", -] - [[package]] name = "tauri-runtime" version = "2.11.2" @@ -7032,49 +7005,6 @@ dependencies = [ "wry", ] -[[package]] -name = "tauri-store" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8e2b75d2702cfa42b6994a187de814c691edf9e56dd049406cf5df8e915cf86" -dependencies = [ - "dashmap", - "futures", - "itertools", - "semver", - "serde", - "serde_json", - "tauri", - "tauri-store-macros", - "tauri-store-utils", - "thiserror 2.0.18", - "tokio", -] - -[[package]] -name = "tauri-store-macros" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae6e366131190fd2f23a9275cefc90cdac0c4837e6ad3920d9b99901cfdb2f1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "tauri-store-utils" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2edd6d150fd2797bbce745d1c55c11887c4456c41d31f56e8eb867d62bd334e7" -dependencies = [ - "parking_lot", - "semver", - "tauri", - "thiserror 2.0.18", - "tokio", -] - [[package]] name = "tauri-utils" version = "2.9.2" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6d80b6456..992a7e5c1 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -110,7 +110,6 @@ tauri-plugin-os = "2" tauri-plugin-process = "2" tauri-plugin-single-instance = { version = "2", features = ["deep-link"] } tauri-plugin-window-state = "2" -tauri-plugin-zustand = "1" thiserror.workspace = true time = { version = "0.3", features = ["formatting", "macros"] } tokio.workspace = true diff --git a/src-tauri/capabilities/zustand.json b/src-tauri/capabilities/zustand.json deleted file mode 100644 index f0fc49a0b..000000000 --- a/src-tauri/capabilities/zustand.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "identifier": "zustand", - "windows": ["*"], - "permissions": ["zustand:default", "core:event:default"] -} diff --git a/src-tauri/src/bin/defguard-client.rs b/src-tauri/src/bin/defguard-client.rs index bacb38627..919c12330 100644 --- a/src-tauri/src/bin/defguard-client.rs +++ b/src-tauri/src/bin/defguard-client.rs @@ -37,7 +37,6 @@ use log::{Level, LevelFilter}; use tauri::{async_runtime, AppHandle, Builder, Manager, RunEvent, WindowEvent}; use tauri_plugin_deep_link::DeepLinkExt; use tauri_plugin_log::{Target, TargetKind}; -use tauri_plugin_zustand; #[macro_use] extern crate log; @@ -244,7 +243,6 @@ fn main() { .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_process::init()) - .plugin(tauri_plugin_zustand::Builder::new().save_denylist(&["shared-session-store"]).build()) .setup(|app| { // Create Help menu on macOS. // https://github.com/tauri-apps/tauri/issues/9371