Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
efce889
update proto submodule
wojcik91 Jul 24, 2026
2d10231
add migration
wojcik91 Jul 26, 2026
7f96108
update model structs
wojcik91 Jul 26, 2026
ee6871f
update query data
wojcik91 Jul 26, 2026
3e4eec4
add helper method to derive flag status
wojcik91 Jul 26, 2026
e684192
update query data
wojcik91 Jul 26, 2026
22c9584
gate tunnel-related commands
wojcik91 Jul 26, 2026
d241728
update InstanceInfo
wojcik91 Jul 26, 2026
ade0daa
handle in instance update function
wojcik91 Jul 26, 2026
3da4ea6
add override to persist flag immediately if a connection is active
wojcik91 Jul 26, 2026
5aff9d7
add helper to disconnect all active tunnels
wojcik91 Jul 26, 2026
7937476
wire the helper in polling loop and enrollment process
wojcik91 Jul 26, 2026
ed9cf4e
update frontend types
wojcik91 Jul 28, 2026
36ebbd6
add mirror event for re-enabling tunnels
wojcik91 Jul 28, 2026
840afa3
add event listeners
wojcik91 Jul 28, 2026
d63d488
add route guards
wojcik91 Jul 28, 2026
813c0cb
hide Add tunnel card
wojcik91 Jul 28, 2026
df7d3aa
hide UI elements
wojcik91 Jul 28, 2026
12274fb
don't trigger MFA for tunnels
wojcik91 Jul 28, 2026
91312d8
update notification wording
wojcik91 Jul 28, 2026
cb4164c
formatting
wojcik91 Jul 28, 2026
9684e51
gate CLI commands
wojcik91 Jul 28, 2026
eade68e
add helper for error guards
wojcik91 Jul 28, 2026
2598427
extract duplicate logic
wojcik91 Jul 28, 2026
9c2a572
emit events when adding/removing instance
wojcik91 Jul 28, 2026
87980aa
Merge branch 'release/2.1' into disable_tunnels
wojcik91 Jul 28, 2026
0948bd6
replace macros
wojcik91 Jul 28, 2026
c13c098
Merge branch 'release/2.1' into disable_tunnels
wojcik91 Jul 29, 2026
a5c02e1
update proto submodule
wojcik91 Jul 29, 2026
871d827
update flake inputs
wojcik91 Jul 29, 2026
4049a90
fix fetcher version
wojcik91 Jul 29, 2026
4a83546
Merge branch 'release/2.1' into disable_tunnels
wojcik91 Jul 29, 2026
3e85ffb
use shared source for node & pnpm version in nix setup
wojcik91 Jul 29, 2026
5cc87ea
align node version across the repo
wojcik91 Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: 26
node-version-file: new-ui/.nvmrc

- uses: pnpm/action-setup@v6
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:

- uses: actions/setup-node@v6
with:
node-version: 26
node-version-file: new-ui/.nvmrc

- name: Install pnpm
uses: pnpm/action-setup@v6
Expand Down Expand Up @@ -133,7 +133,7 @@ jobs:

- uses: actions/setup-node@v6
with:
node-version: 26
node-version-file: new-ui/.nvmrc

- name: Install pnpm
uses: pnpm/action-setup@v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:

- uses: actions/setup-node@v6
with:
node-version: 26
node-version-file: new-ui/.nvmrc

- uses: pnpm/action-setup@v6
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: 26
node-version-file: new-ui/.nvmrc

- uses: pnpm/action-setup@v6
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:

- uses: actions/setup-node@v6
with:
node-version: 26
node-version-file: new-ui/.nvmrc

- name: Install pnpm
uses: pnpm/action-setup@v6
Expand Down Expand Up @@ -181,7 +181,7 @@ jobs:
echo "DEFGUARD_CLIENT_BUILD_VERSION=${GITHUB_REF_NAME#v}" >> ${GITHUB_ENV}
- uses: actions/setup-node@v6
with:
node-version: 26
node-version-file: new-ui/.nvmrc

- uses: pnpm/action-setup@v6
with:
Expand Down
1 change: 0 additions & 1 deletion .nvmrc

This file was deleted.

20 changes: 10 additions & 10 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion new-ui/.nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v25
26
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useAppData } from '../../../../shared/providers/AppDataContext';
import {
getInstancesQueryOptions,
getTunnelsQueryOptions,
tunnelsDisabled,
} from '../../../../shared/rust-api/query';
import type { OverviewViewSelection } from '../../../../shared/rust-api/types';
import { isPresent } from '../../../../shared/utils/isPresent';
Expand All @@ -35,17 +36,22 @@ export const InstanceSwitcher = () => {
})),
};

const tunnelGroup: SelectOptionGroup<OverviewViewSelection> = {
key: 'tunnels',
label: 'Tunnels',
options: tunnels.map((tunnel) => ({
key: `tunnel-${tunnel.id ?? tunnel.name}`,
label: tunnel.name,
value: { kind: 'tunnel', id: tunnel.id },
})),
};
const tunnelGroup: SelectOptionGroup<OverviewViewSelection> | undefined =
!tunnelsDisabled(instances ?? []) && tunnels.length > 0
? {
key: 'tunnels',
label: 'Tunnels',
options: tunnels.map((tunnel) => ({
key: `tunnel-${tunnel.id ?? tunnel.name}`,
label: tunnel.name,
value: { kind: 'tunnel', id: tunnel.id },
})),
}
: undefined;

return [instanceGroup, tunnelGroup];
const result: SelectOptionGroup<OverviewViewSelection>[] = [instanceGroup];
if (tunnelGroup) result.push(tunnelGroup);
return result;
}, [instances, tunnels]);

const totalOptions = useMemo(
Expand Down
30 changes: 19 additions & 11 deletions new-ui/src/pages/full/AddPage/AddPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import './style.scss';
import { useQuery } from '@tanstack/react-query';
import { useNavigate } from '@tanstack/react-router';
import { FullPageTitle } from '../../../shared/components/FullPageTitle/FullPageTitle';
import { FullPage } from '../../../shared/layouts/FullPage/FullPage';
import {
getInstancesQueryOptions,
tunnelsDisabled,
} from '../../../shared/rust-api/query';
import { ThemeSpacing } from '../../../shared/types';
import { AddCard } from './components/AddCard/AddCard';

export const AddPage = () => {
const navigate = useNavigate();
const { data: instances } = useQuery(getInstancesQueryOptions);
return (
<FullPage id="add-page-view">
<FullPageTitle title="Add Defguard items" spacing={ThemeSpacing.Xl} />
Expand All @@ -22,17 +28,19 @@ export const AddPage = () => {
actionText="Add instance"
description={`Establish a secure connection to your Defguard instance effortlessly by configuring it with a single token—no manual setup.`}
/>
<AddCard
image="wireguard"
onClick={() => {
navigate({
to: '/full/add/tunnel',
});
}}
title="Add WireGuard Tunnel"
actionText="Add tunnel"
description={`Add and configure a WireGuard tunnel to securely route traffic through an encrypted connection using predefined configuration.`}
/>
{!tunnelsDisabled(instances ?? []) && (
<AddCard
image="wireguard"
onClick={() => {
navigate({
to: '/full/add/tunnel',
});
}}
title="Add WireGuard Tunnel"
actionText="Add tunnel"
description={`Add and configure a WireGuard tunnel to securely route traffic through an encrypted connection using predefined configuration.`}
/>
)}
</div>
</FullPage>
);
Expand Down
12 changes: 9 additions & 3 deletions new-ui/src/pages/full/OverviewPage/OverviewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { ScrollContainer } from '../../../shared/components/ScrollContainer/Scro
import { SizedBox } from '../../../shared/components/SizedBox/SizedBox';
import { FullPage } from '../../../shared/layouts/FullPage/FullPage';
import { useAppData } from '../../../shared/providers/AppDataContext';
import { getLocationsQueryOptions } from '../../../shared/rust-api/query';
import {
getLocationsQueryOptions,
tunnelsDisabled,
} from '../../../shared/rust-api/query';
import type { InstanceInfo } from '../../../shared/rust-api/types';
import { ThemeSpacing } from '../../../shared/types';
import { isPresent } from '../../../shared/utils/isPresent';
Expand Down Expand Up @@ -59,7 +62,10 @@ export const OverviewPage = () => {
<Fragment>
<FullPage id="overview-page" hideScrollContainer>
<div className="page-grid">
<OverviewSelection instances={instances} tunnels={tunnels} />
<OverviewSelection
instances={instances}
tunnels={tunnelsDisabled(instances) ? [] : tunnels}
/>
<div
className={clsx('overview-content', {
windows: isWindows,
Expand Down Expand Up @@ -96,7 +102,7 @@ export const OverviewPage = () => {
</div>
</FullPage>
<ConnectModal />
<UpdateTunnelModal />
{!tunnelsDisabled(instances) && <UpdateTunnelModal />}
<UpdateInstanceModal />
</Fragment>
);
Expand Down
12 changes: 11 additions & 1 deletion new-ui/src/routes/full/_default/add/tunnel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { createFileRoute } from '@tanstack/react-router';
import { createFileRoute, redirect } from '@tanstack/react-router';
import { AddTunnelPage } from '../../../../pages/full/AddTunnelPage/AddTunnelPage';
import {
getInstancesQueryOptions,
tunnelsDisabled,
} from '../../../../shared/rust-api/query';

export const Route = createFileRoute('/full/_default/add/tunnel')({
beforeLoad: async ({ context }) => {
const instances = await context.queryClient.ensureQueryData(getInstancesQueryOptions);
if (tunnelsDisabled(instances)) {
throw redirect({ to: '/full/overview' });
}
},
component: AddTunnelPage,
});
9 changes: 8 additions & 1 deletion new-ui/src/routes/full/tunnel-wizard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { createFileRoute } from '@tanstack/react-router';
import { createFileRoute, redirect } from '@tanstack/react-router';
import { TunnelWizardPage } from '../../pages/full/TunnelWizardPage/TunnelWizardPage';
import { getInstancesQueryOptions, tunnelsDisabled } from '../../shared/rust-api/query';

export const Route = createFileRoute('/full/tunnel-wizard')({
beforeLoad: async ({ context }) => {
const instances = await context.queryClient.ensureQueryData(getInstancesQueryOptions);
if (tunnelsDisabled(instances)) {
throw redirect({ to: '/full/overview' });
}
},
component: TunnelWizardPage,
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMutation } from '@tanstack/react-query';
import { api } from '../../../rust-api/api';
import { LocationMfaMode } from '../../../rust-api/types';
import { shouldStartMfa } from '../../../utils/mfa';
import { parseConnectError } from '../api/connectError';
import { useLocationCardContext } from '../context/context';
import { LocationCardViews } from '../context/types';
Expand Down Expand Up @@ -46,7 +46,7 @@ export const LocationCardConnectButton = () => {
connectionType: location.connection_type,
locationId: location.id,
});
} else if (location.location_mfa_mode !== LocationMfaMode.Disabled) {
} else if (shouldStartMfa(location)) {
startMfa();
} else {
connect({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import { useConnectModal } from '../../../pages/full/OverviewPage/components/Con
import { api } from '../../rust-api/api';
import { getAppConfigQueryOptions } from '../../rust-api/query';
import type { InstanceInfo, LocationInfo } from '../../rust-api/types';
import { LocationMfaMode, MfaMethod } from '../../rust-api/types';
import { MfaMethod } from '../../rust-api/types';
import { ThemeSpacing } from '../../types';
import { isPresent } from '../../utils/isPresent';
import { shouldStartMfa } from '../../utils/mfa';
import { Divider } from '../Divider/Divider';
import { parseConnectError } from '../LocationCard/api/connectError';
import { ConnectButton } from '../LocationCard/components/ConnectButton/ConnectButton';
Expand Down Expand Up @@ -78,7 +79,7 @@ export const OverviewLocationCard = ({ location, instance }: Props) => {
return;
}

if (location.location_mfa_mode !== LocationMfaMode.Disabled) {
if (shouldStartMfa(location)) {
useConnectModal.getState().open({
view: mfaMethodToConnectModalView(location.mfa_method ?? MfaMethod.Totp),
location,
Expand Down
13 changes: 13 additions & 0 deletions new-ui/src/shared/providers/TauriEventProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
type LocationInfo,
MfaMethod,
TauriEvent,
type TunnelsDisabledPayload,
} from '../rust-api/types';
import { useAppStore } from '../store/useAppStore';
import { decideLocationMfaMethod } from '../utils/decideLocationMfaMethod';
Expand Down Expand Up @@ -162,6 +163,18 @@ export const TauriEventProvider = ({ children }: PropsWithChildren) => {
listen(TauriEvent.SessionStateChanged, () => {
void queryClient.invalidateQueries({ queryKey: ['session-state'] });
}),

listen<TunnelsDisabledPayload>(TauriEvent.TunnelsDisabled, (event) => {
void debug(`UI Received event TunnelsDisabled: ${JSON.stringify(event.payload)}`);
void queryClient.invalidateQueries({ queryKey: ['instances'] });
void queryClient.invalidateQueries({ queryKey: ['tunnels'] });
}),

listen(TauriEvent.TunnelsEnabled, () => {
void debug('UI Received event TunnelsEnabled');
void queryClient.invalidateQueries({ queryKey: ['instances'] });
void queryClient.invalidateQueries({ queryKey: ['tunnels'] });
}),
]);

return () => {
Expand Down
14 changes: 13 additions & 1 deletion new-ui/src/shared/rust-api/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@ import { queryOptions, skipToken } from '@tanstack/react-query';

import { isPresent } from '../utils/isPresent';
import { api } from './api';
import type { ConnectionArgs, LocationDetailsArgs, StatsArgs } from './types';
import type {
ConnectionArgs,
InstanceInfo,
LocationDetailsArgs,
StatsArgs,
} from './types';

/**
* Single source of truth for the OR-across-instances "tunnels disabled" rule:
* tunnels are disabled when any enrolled instance has the flag set.
*/
export const tunnelsDisabled = (instances: InstanceInfo[]): boolean =>
instances.some((i) => i.disable_tunnels);

export const getAllActiveConnectionQueryOptions = queryOptions({
queryKey: ['alive-connections'] as const,
Expand Down
8 changes: 8 additions & 0 deletions new-ui/src/shared/rust-api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ export const TauriEvent = {
MfaOpenIdError: 'mfa-openid-error',
MfaMobileComplete: 'mfa-mobile-complete',
MfaMobileError: 'mfa-mobile-error',
TunnelsDisabled: 'tunnel-disabled-by-policy',
TunnelsEnabled: 'tunnel-enabled-by-policy',
} as const;

export type TauriEventValue = (typeof TauriEvent)[keyof typeof TauriEvent];
Expand All @@ -190,6 +192,11 @@ export type AddInstanceEventPayload = {
url: string;
};

/** Payload for the `tunnel-disabled-by-policy` event. Mirrors `TunnelsDisabled` in events.rs. */
export type TunnelsDisabledPayload = {
names: string[];
};

export type ActiveConnectionSummary = {
id: number;
name: string;
Expand Down Expand Up @@ -222,6 +229,7 @@ export type InstanceInfo = {
pubkey: string;
client_traffic_policy: ClientTrafficPolicy;
enterprise_enabled: boolean;
disable_tunnels: boolean;
openid_display_name: string | null;
};

Expand Down
Loading
Loading