Skip to content

Commit 1745b0c

Browse files
Display relevant warning message in the UI when proxy is in setup mode or core is disconnected (#259)
1 parent 2d29772 commit 1745b0c

17 files changed

Lines changed: 145 additions & 26 deletions

File tree

src/http.rs

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,39 @@ async fn handle_404() -> (StatusCode, &'static str) {
7070
}
7171

7272
#[derive(Serialize)]
73-
struct AppInfo<'a> {
74-
version: &'a str,
73+
#[serde(rename_all = "snake_case")]
74+
enum ServerState {
75+
Setup,
76+
Disconnected,
77+
Connected,
7578
}
7679

77-
async fn app_info<'a>() -> Result<Json<AppInfo<'a>>, ApiError> {
80+
impl From<&AppState> for ServerState {
81+
fn from(state: &AppState) -> Self {
82+
if !state.grpc_server.setup_completed() {
83+
Self::Setup
84+
} else if state.grpc_server.connected.load(Ordering::Relaxed) {
85+
Self::Connected
86+
} else {
87+
Self::Disconnected
88+
}
89+
}
90+
}
91+
92+
#[derive(Serialize)]
93+
struct AppInfo {
94+
version: &'static str,
95+
server_state: ServerState,
96+
}
97+
98+
async fn app_info(State(state): State<AppState>) -> Result<Json<AppInfo>, ApiError> {
7899
let version = crate_version!();
79-
Ok(Json(AppInfo { version }))
100+
let server_state = ServerState::from(&state);
101+
102+
Ok(Json(AppInfo {
103+
version,
104+
server_state,
105+
}))
80106
}
81107

82108
async fn healthcheck() -> &'static str {
@@ -249,9 +275,12 @@ async fn ensure_configured(
249275
request: Request<Body>,
250276
next: Next,
251277
) -> Response<Body> {
252-
// Allow healthchecks even before core connects and gives us the cookie key.
278+
// Allow healthchecks and info even before core connects and gives us the cookie key.
253279
let path = request.uri().path();
254-
if matches!(path, "/api/v1/health" | "/api/v1/health-grpc") {
280+
if matches!(
281+
path,
282+
"/api/v1/health" | "/api/v1/health-grpc" | "/api/v1/info"
283+
) {
255284
return next.run(request).await;
256285
}
257286

@@ -282,6 +311,12 @@ pub async fn run_server(
282311
reset_tx,
283312
);
284313

314+
// Preload existing TLS configuration so /api/v1/info can report "disconnected"
315+
// immediately on startup
316+
if let Some(existing_configuration) = config.clone() {
317+
grpc_server.configure(existing_configuration);
318+
}
319+
285320
let server_clone = grpc_server.clone();
286321
let env_config_clone = env_config.clone();
287322

web/messages/en.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,10 @@
108108
"openid_mfa_complete_subtitle": "You have been successfully authenticated. Please close this window and get back to the Defguard VPN Client",
109109
"open_desktop_title": "Open the desktop app to continue",
110110
"open_desktop_description": "We tried to open the desktop app automatically, but it didn't respond. This can happen if the browser blocks the request or the app didn't start in time.",
111-
"open_desktop_button": "Open desktop app"
111+
"open_desktop_button": "Open desktop app",
112+
"server_warning_setup_title": "Server is in setup mode",
113+
"server_warning_setup_subtitle": "Edge setup is not complete yet. Please contact your administrator.",
114+
"server_warning_disconnected_title": "Core is disconnected",
115+
"server_warning_disconnected_subtitle": "Edge is configured, but it is not connected to Defguard Core. Please contact your administrator.",
116+
"server_warning_retry": "Try again"
112117
}

web/src/pages/OpenDesktop/OpenDesktopPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import './style.scss';
22
import { useSearch } from '@tanstack/react-router';
33
import { m } from '../../paraglide/messages';
4-
import { PageProcessEnd } from '../../shared/components/PageProcessEnd/PageProcessEnd';
4+
import { PageInfo } from '../../shared/components/PageInfo/PageInfo';
55
import laptopImage from './assets/laptop.png';
66

77
export const OpenDesktopPage = () => {
@@ -16,7 +16,7 @@ export const OpenDesktopPage = () => {
1616
}
1717

1818
return (
19-
<PageProcessEnd
19+
<PageInfo
2020
imageSrc={laptopImage}
2121
title={m.open_desktop_title()}
2222
subtitle={m.open_desktop_description()}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useLoaderData } from '@tanstack/react-router';
2+
import { m } from '../../paraglide/messages';
3+
import { PageInfo } from '../../shared/components/PageInfo/PageInfo';
4+
5+
export const ServerWarningPage = () => {
6+
const serverState = useLoaderData({ from: '/server-warning' });
7+
8+
const title =
9+
serverState === 'setup'
10+
? m.server_warning_setup_title()
11+
: m.server_warning_disconnected_title();
12+
const subtitle =
13+
serverState === 'setup'
14+
? m.server_warning_setup_subtitle()
15+
: m.server_warning_disconnected_subtitle();
16+
17+
return (
18+
<PageInfo
19+
icon="warning"
20+
title={title}
21+
subtitle={subtitle}
22+
link="/"
23+
linkText={m.server_warning_retry()}
24+
/>
25+
);
26+
};

web/src/pages/SessionEnd/SessionEndPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { m } from '../../paraglide/messages';
2-
import { PageProcessEnd } from '../../shared/components/PageProcessEnd/PageProcessEnd';
2+
import { PageInfo } from '../../shared/components/PageInfo/PageInfo';
33

44
export const SessionEndPage = () => {
55
return (
6-
<PageProcessEnd
6+
<PageInfo
77
link="/"
88
icon="disabled"
99
title={m.session_end_title()}

web/src/routeTree.gen.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import { Route as rootRouteImport } from './routes/__root'
1212
import { Route as SessionEndRouteImport } from './routes/session-end'
13+
import { Route as ServerWarningRouteImport } from './routes/server-warning'
1314
import { Route as PasswordResetRouteImport } from './routes/password-reset'
1415
import { Route as OpenDesktopRouteImport } from './routes/open-desktop'
1516
import { Route as LinkInvalidRouteImport } from './routes/link-invalid'
@@ -30,6 +31,11 @@ const SessionEndRoute = SessionEndRouteImport.update({
3031
path: '/session-end',
3132
getParentRoute: () => rootRouteImport,
3233
} as any)
34+
const ServerWarningRoute = ServerWarningRouteImport.update({
35+
id: '/server-warning',
36+
path: '/server-warning',
37+
getParentRoute: () => rootRouteImport,
38+
} as any)
3339
const PasswordResetRoute = PasswordResetRouteImport.update({
3440
id: '/password-reset',
3541
path: '/password-reset',
@@ -109,6 +115,7 @@ export interface FileRoutesByFullPath {
109115
'/link-invalid': typeof LinkInvalidRoute
110116
'/open-desktop': typeof OpenDesktopRoute
111117
'/password-reset': typeof PasswordResetRoute
118+
'/server-warning': typeof ServerWarningRoute
112119
'/session-end': typeof SessionEndRoute
113120
'/openid/callback': typeof OpenidCallbackRoute
114121
'/openid/error': typeof OpenidErrorRoute
@@ -126,6 +133,7 @@ export interface FileRoutesByTo {
126133
'/link-invalid': typeof LinkInvalidRoute
127134
'/open-desktop': typeof OpenDesktopRoute
128135
'/password-reset': typeof PasswordResetRoute
136+
'/server-warning': typeof ServerWarningRoute
129137
'/session-end': typeof SessionEndRoute
130138
'/openid/callback': typeof OpenidCallbackRoute
131139
'/openid/error': typeof OpenidErrorRoute
@@ -144,6 +152,7 @@ export interface FileRoutesById {
144152
'/link-invalid': typeof LinkInvalidRoute
145153
'/open-desktop': typeof OpenDesktopRoute
146154
'/password-reset': typeof PasswordResetRoute
155+
'/server-warning': typeof ServerWarningRoute
147156
'/session-end': typeof SessionEndRoute
148157
'/openid/callback': typeof OpenidCallbackRoute
149158
'/openid/error': typeof OpenidErrorRoute
@@ -163,6 +172,7 @@ export interface FileRouteTypes {
163172
| '/link-invalid'
164173
| '/open-desktop'
165174
| '/password-reset'
175+
| '/server-warning'
166176
| '/session-end'
167177
| '/openid/callback'
168178
| '/openid/error'
@@ -180,6 +190,7 @@ export interface FileRouteTypes {
180190
| '/link-invalid'
181191
| '/open-desktop'
182192
| '/password-reset'
193+
| '/server-warning'
183194
| '/session-end'
184195
| '/openid/callback'
185196
| '/openid/error'
@@ -197,6 +208,7 @@ export interface FileRouteTypes {
197208
| '/link-invalid'
198209
| '/open-desktop'
199210
| '/password-reset'
211+
| '/server-warning'
200212
| '/session-end'
201213
| '/openid/callback'
202214
| '/openid/error'
@@ -215,6 +227,7 @@ export interface RootRouteChildren {
215227
LinkInvalidRoute: typeof LinkInvalidRoute
216228
OpenDesktopRoute: typeof OpenDesktopRoute
217229
PasswordResetRoute: typeof PasswordResetRoute
230+
ServerWarningRoute: typeof ServerWarningRoute
218231
SessionEndRoute: typeof SessionEndRoute
219232
OpenidCallbackRoute: typeof OpenidCallbackRoute
220233
OpenidErrorRoute: typeof OpenidErrorRoute
@@ -234,6 +247,13 @@ declare module '@tanstack/react-router' {
234247
preLoaderRoute: typeof SessionEndRouteImport
235248
parentRoute: typeof rootRouteImport
236249
}
250+
'/server-warning': {
251+
id: '/server-warning'
252+
path: '/server-warning'
253+
fullPath: '/server-warning'
254+
preLoaderRoute: typeof ServerWarningRouteImport
255+
parentRoute: typeof rootRouteImport
256+
}
237257
'/password-reset': {
238258
id: '/password-reset'
239259
path: '/password-reset'
@@ -343,6 +363,7 @@ const rootRouteChildren: RootRouteChildren = {
343363
LinkInvalidRoute: LinkInvalidRoute,
344364
OpenDesktopRoute: OpenDesktopRoute,
345365
PasswordResetRoute: PasswordResetRoute,
366+
ServerWarningRoute: ServerWarningRoute,
346367
SessionEndRoute: SessionEndRoute,
347368
OpenidCallbackRoute: OpenidCallbackRoute,
348369
OpenidErrorRoute: OpenidErrorRoute,

web/src/routes/__root.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1-
import { createRootRoute, Outlet } from '@tanstack/react-router';
1+
import { createRootRoute, Outlet, redirect } from '@tanstack/react-router';
22
import { SessionGuard } from '../app/SessionGuard';
3+
import { api } from '../shared/api/api';
34

45
export const Route = createRootRoute({
6+
beforeLoad: async ({ location }) => {
7+
if (location.pathname === '/server-warning') {
8+
return;
9+
}
10+
11+
const response = await api.appInfo.callbackFn({ params: undefined });
12+
if (response.data.server_state !== 'connected') {
13+
throw redirect({
14+
to: '/server-warning',
15+
replace: true,
16+
});
17+
}
18+
},
519
component: RootComponent,
620
});
721

web/src/routes/link-invalid.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { createFileRoute } from '@tanstack/react-router';
22
import { m } from '../paraglide/messages';
3-
import { PageProcessEnd } from '../shared/components/PageProcessEnd/PageProcessEnd';
3+
import { PageInfo } from '../shared/components/PageInfo/PageInfo';
44

55
export const Route = createFileRoute('/link-invalid')({
66
component: RouteComponent,
77
});
88

99
function RouteComponent() {
1010
return (
11-
<PageProcessEnd
11+
<PageInfo
1212
link="/"
1313
icon="disabled"
1414
title={m.link_invalid_title()}

web/src/routes/openid/error.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createFileRoute } from '@tanstack/react-router';
22
import { m } from '../../paraglide/messages';
3-
import { PageProcessEnd } from '../../shared/components/PageProcessEnd/PageProcessEnd';
3+
import { PageInfo } from '../../shared/components/PageInfo/PageInfo';
44
import { useOpenidStore } from '../../shared/hooks/useOpenIdStore';
55

66
export const Route = createFileRoute('/openid/error')({
@@ -13,7 +13,7 @@ function RouteComponent() {
1313
);
1414

1515
return (
16-
<PageProcessEnd
16+
<PageInfo
1717
title={m.openid_mfa_redirect_error_title()}
1818
subtitle={openIdError}
1919
icon="disabled"

web/src/routes/openid/mfa/callback.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createFileRoute, redirect } from '@tanstack/react-router';
22
import z from 'zod';
33
import { m } from '../../../paraglide/messages';
44
import { api } from '../../../shared/api/api';
5-
import { PageProcessEnd } from '../../../shared/components/PageProcessEnd/PageProcessEnd';
5+
import { PageInfo } from '../../../shared/components/PageInfo/PageInfo';
66
import { useOpenidStore } from '../../../shared/hooks/useOpenIdStore';
77

88
const searchSchema = z.object({
@@ -35,7 +35,7 @@ export const Route = createFileRoute('/openid/mfa/callback')({
3535

3636
function RouteComponent() {
3737
return (
38-
<PageProcessEnd
38+
<PageInfo
3939
title={m.openid_mfa_complete_title()}
4040
subtitle={m.openid_mfa_complete_subtitle()}
4141
icon="check-circle"

0 commit comments

Comments
 (0)