From cc4b0c19f975d382513ab53b255fdefa5e9e5dd3 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Sun, 5 Jan 2025 21:29:04 +0100 Subject: [PATCH 1/7] change client disconnect/reconnect mechanic and notifications --- src-tauri/src/events.rs | 26 ++++++ src-tauri/src/periodic/connection.rs | 89 ++++++++++++++++--- src/i18n/en/index.ts | 12 ++- src/i18n/i18n-types.ts | 53 +++++------ src/pages/client/ClientPage.tsx | 12 ++- src/pages/client/clientAPI/clientApi.ts | 4 +- .../DeadConDroppedModal.tsx | 24 ++--- .../LocationCardInfo/LocationCardInfo.tsx | 35 +------- .../components/LocationCardInfo/style.scss | 1 - .../LocationHistoryTable.tsx | 4 - .../GlobalSettingsTab/GlobalSettingsTab.tsx | 2 +- src/pages/client/types.ts | 10 ++- 12 files changed, 160 insertions(+), 112 deletions(-) diff --git a/src-tauri/src/events.rs b/src-tauri/src/events.rs index 772f92b5..857b53bd 100644 --- a/src-tauri/src/events.rs +++ b/src-tauri/src/events.rs @@ -11,6 +11,7 @@ pub static LOCATION_UPDATE: &str = "location-update"; pub static APP_VERSION_FETCH: &str = "app-version-fetch"; pub static CONFIG_CHANGED: &str = "config-changed"; pub static DEAD_CONNECTION_DROPPED: &str = "dead-connection-dropped"; +pub static DEAD_CONNECTION_RECONNECTED: &str = "dead-connection-reconnected"; pub static APPLICATION_CONFIG_CHANGED: &str = "application-config-changed"; /// Used as payload for [`DEAD_CONNECTION_DROPPED`] event @@ -18,6 +19,7 @@ pub static APPLICATION_CONFIG_CHANGED: &str = "application-config-changed"; pub struct DeadConnDroppedOut { pub(crate) name: String, pub(crate) con_type: ConnectionType, + pub(crate) peer_alive_period: i64, } impl DeadConnDroppedOut { @@ -35,3 +37,27 @@ impl DeadConnDroppedOut { } } } + +/// Used as payload for [`DEAD_CONNECTION_RECONNECTED`] event +#[derive(Serialize, Clone, Debug)] +pub struct DeadConnReconnected { + pub(crate) name: String, + pub(crate) con_type: ConnectionType, + pub(crate) peer_alive_period: i64, +} + +impl DeadConnReconnected { + /// Emits [`DEAD_CONNECTION_RECONNECTED`] event with corresponding side effects. + pub(crate) fn emit(self, app_handle: &AppHandle) { + if let Err(err) = Notification::new(&app_handle.config().tauri.bundle.identifier) + .title(format!("{} {} reconnected", self.con_type, self.name)) + .body("Connection activity timeout") + .show() + { + warn!("Dead connection reconnected notification not shown. Reason: {err}"); + } + if let Err(err) = app_handle.emit_all(DEAD_CONNECTION_RECONNECTED, self) { + error!("Event Dead Connection Reconnected was not emitted. Reason: {err}"); + } + } +} diff --git a/src-tauri/src/periodic/connection.rs b/src-tauri/src/periodic/connection.rs index a14e4d16..d929bf87 100644 --- a/src-tauri/src/periodic/connection.rs +++ b/src-tauri/src/periodic/connection.rs @@ -14,7 +14,7 @@ use crate::{ Id, }, error::Error, - events::DeadConnDroppedOut, + events::{DeadConnDroppedOut, DeadConnReconnected}, ConnectionType, }; @@ -34,6 +34,7 @@ async fn reconnect( con_interface_name: &str, app_handle: &AppHandle, con_type: ConnectionType, + peer_alive_period: &TimeDelta, ) { debug!("Starting attempt to reconnect {con_interface_name} {con_type}({con_id})..."); match disconnect(con_id, con_type, app_handle.clone()).await { @@ -45,9 +46,10 @@ async fn reconnect( } Err(err) => { error!("Reconnect attempt failed, disconnect succeeded but connect failed. Error: {err}"); - let payload = DeadConnDroppedOut { + let payload = DeadConnReconnected { name: con_interface_name.to_string(), con_type, + peer_alive_period: peer_alive_period.num_seconds(), }; payload.emit(app_handle); } @@ -66,6 +68,7 @@ async fn disconnect_dead_connection( con_interface_name: &str, app_handle: AppHandle, con_type: ConnectionType, + peer_alive_period: &TimeDelta, ) { debug!( "Attempting to disconnect dead connection for interface {con_interface_name}, {con_type}: {con_id}"); @@ -75,6 +78,7 @@ async fn disconnect_dead_connection( let event_payload = DeadConnDroppedOut { con_type, name: con_interface_name.to_string(), + peer_alive_period: peer_alive_period.num_seconds(), }; event_payload.emit(&app_handle); } @@ -91,7 +95,9 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro let pool = &app_state.db; debug!("Active connections verification started."); - // Both vectors contain IDs. + // Both vectors contain (ID, allow_reconnect) tuples. + // If allow_reconnect is false, the connection will always be dropped without a reconnect attempt. + // Otherwise, the connection will be reconnected if nothing else prevents it (e.g. MFA). let mut locations_to_disconnect = Vec::new(); let mut tunnels_to_disconnect = Vec::new(); @@ -117,15 +123,27 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro latest_stat.collected_at, peer_alive_period, ) { - debug!("There wasn't any activity for Location {}; considering it being dead.", con.location_id); - locations_to_disconnect.push(con.location_id); + // Check if there was any traffic since the connection was established. + // If not, consider the location dead and disconnect it later without reconnecting. + if latest_stat.collected_at - con.start < TimeDelta::zero() { + debug!("There wasn't any activity for Location {} since its connection at {}; considering it being dead and possibly broken. \ + It will be disconnected without a further automatic reconnect.", con.location_id, con.start); + locations_to_disconnect.push((con.location_id, false)); + } else { + debug!("There wasn't any activity for Location {}; considering it being dead.", con.location_id); + locations_to_disconnect.push((con.location_id, true)); + } } } Ok(None) => { - error!( + debug!( "LocationStats not found in database for active connection {} {}({})", con.connection_type, con.interface_name, con.location_id ); + if Utc::now() - con.start.and_utc() > peer_alive_period { + debug!("There wasn't any activity for Location {} since its connection at {}; considering it being dead.", con.location_id, con.start); + locations_to_disconnect.push((con.location_id, false)); + } } Err(err) => { warn!("Verification for location {}({}) skipped due to db error. Error: {err}", con.interface_name, con.location_id); @@ -140,8 +158,16 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro latest_stat.collected_at, peer_alive_period, ) { - debug!("There wasn't any activity for Tunnel {}; considering it being dead.", con.location_id); - tunnels_to_disconnect.push(con.location_id); + // Check if there was any traffic since the connection was established. + // If not, consider the location dead and disconnect it later without reconnecting. + if latest_stat.collected_at - con.start < TimeDelta::zero() { + debug!("There wasn't any activity for Tunnel {} since its connection at {}; considering it being dead and possibly broken. \ + It will be disconnected without a further automatic reconnect.", con.location_id, con.start); + tunnels_to_disconnect.push((con.location_id, false)); + } else { + debug!("There wasn't any activity for Tunnel {}; considering it being dead.", con.location_id); + tunnels_to_disconnect.push((con.location_id, true)); + } } } Ok(None) => { @@ -149,8 +175,11 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro "TunnelStats not found in database for active connection Tunnel {}({})", con.interface_name, con.location_id ); + if Utc::now() - con.start.and_utc() > peer_alive_period { + debug!("There wasn't any activity for Location {} since its connection at {}; considering it being dead.", con.location_id, con.start); + tunnels_to_disconnect.push((con.location_id, false)); + } } - Err(err) => { warn!( "Verification for tunnel {}({}) skipped due to db error. Error: {err}", @@ -166,17 +195,29 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro drop(connections); // Process locations - for location_id in locations_to_disconnect.drain(..) { + for (location_id, allow_reconnect) in locations_to_disconnect.drain(..) { match Location::find_by_id(pool, location_id).await { Ok(Some(location)) => { + if !allow_reconnect { + warn!("Automatic reconnect for location {}({}) is not possible due to lack of activity. Interface will be disconnected.", location.name, location.id); + disconnect_dead_connection( + location_id, + &location.name, + app_handle.clone(), + ConnectionType::Location, + &peer_alive_period, + ) + .await; + } else if // only try to reconnect when location is not protected behind MFA - if location.mfa_enabled { + location.mfa_enabled { warn!("Automatic reconnect for location {}({}) is not possible due to enabled MFA. Interface will be disconnected.", location.name, location.id); disconnect_dead_connection( location_id, &location.name, app_handle.clone(), ConnectionType::Location, + &peer_alive_period, ) .await; } else { @@ -185,6 +226,7 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro &location.name, &app_handle, ConnectionType::Location, + &peer_alive_period, ) .await; } @@ -200,6 +242,7 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro "DEAD LOCATION", app_handle.clone(), ConnectionType::Location, + &peer_alive_period, ) .await; } @@ -207,10 +250,29 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro } // Process tunnels - for tunnel_id in tunnels_to_disconnect.drain(..) { + for (tunnel_id, allow_reconnect) in tunnels_to_disconnect.drain(..) { match Tunnel::find_by_id(pool, tunnel_id).await { Ok(Some(tunnel)) => { - reconnect(tunnel.id, &tunnel.name, &app_handle, ConnectionType::Tunnel).await; + if allow_reconnect { + reconnect( + tunnel.id, + &tunnel.name, + &app_handle, + ConnectionType::Tunnel, + &peer_alive_period, + ) + .await; + } else { + warn!("Automatic reconnect for location {}({}) is not possible due to lack of activity. Interface will be disconnected.", tunnel.name, tunnel.id); + disconnect_dead_connection( + tunnel_id, + "DEAD TUNNEL", + app_handle.clone(), + ConnectionType::Tunnel, + &peer_alive_period, + ) + .await; + } } Ok(None) => { // Unlikely due to ON DELETE CASCADE. @@ -223,6 +285,7 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro "DEAD TUNNEL", app_handle.clone(), ConnectionType::Tunnel, + &peer_alive_period, ) .await; } diff --git a/src/i18n/en/index.ts b/src/i18n/en/index.ts index 2fb45f34..721f464d 100644 --- a/src/i18n/en/index.ts +++ b/src/i18n/en/index.ts @@ -52,7 +52,8 @@ const en = { networkError: "There was a network error. Can't reach proxy.", configChanged: 'Configuration for instance {instance: string} has changed. Disconnect from all locations to apply changes.', - deadConDropped: '{con_type: string} {interface_name: string} disconnected.', + deadConDropped: + 'Detected no traffic for {con_type: string} {interface_name: string} for {time: number}s. Performed an automatic reconnect to try to preserve connection.', }, }, components: { @@ -64,14 +65,11 @@ const en = { client: { modals: { deadConDropped: { - title: '{conType: string} disconnected', + title: '{conType: string} {name: string} disconnected', tunnel: 'Tunnel', location: 'Location', - body: { - periodic: - '{conType: string} {instanceName: string} was automatically disconnected because it exceeded the expected time for staying active without receiving confirmation from the server.', - connection: `{conType: string} {name: string} connection was automatically disconnected because it didn't complete the necessary setup in time. This can happen if the connection wasn't fully established`, - }, + message: + 'The {conType: string} {name: string} has been disconnected, since we have detected that the server is not responding with any traffic for {time: number}s. If this message keeps occurring, please contact your administrator and inform them about this fact.', controls: { close: 'Close', }, diff --git a/src/i18n/i18n-types.ts b/src/i18n/i18n-types.ts index 29210db3..07aaadfa 100644 --- a/src/i18n/i18n-types.ts +++ b/src/i18n/i18n-types.ts @@ -160,11 +160,12 @@ type RootTranslation = { */ configChanged: RequiredParams<'instance'> /** - * {​c​o​n​_​t​y​p​e​}​ ​{​i​n​t​e​r​f​a​c​e​_​n​a​m​e​}​ ​d​i​s​c​o​n​n​e​c​t​e​d​. + * D​e​t​e​c​t​e​d​ ​n​o​ ​t​r​a​f​f​i​c​ ​f​o​r​ ​{​c​o​n​_​t​y​p​e​}​ ​{​i​n​t​e​r​f​a​c​e​_​n​a​m​e​}​ ​f​o​r​ ​{​t​i​m​e​}​s​.​ ​P​e​r​f​o​r​m​e​d​ ​a​n​ ​a​u​t​o​m​a​t​i​c​ ​r​e​c​o​n​n​e​c​t​ ​t​o​ ​t​r​y​ ​t​o​ ​p​r​e​s​e​r​v​e​ ​c​o​n​n​e​c​t​i​o​n​. * @param {string} con_type * @param {string} interface_name + * @param {number} time */ - deadConDropped: RequiredParams<'con_type' | 'interface_name'> + deadConDropped: RequiredParams<'con_type' | 'interface_name' | 'time'> } } components: { @@ -180,10 +181,11 @@ type RootTranslation = { modals: { deadConDropped: { /** - * {​c​o​n​T​y​p​e​}​ ​d​i​s​c​o​n​n​e​c​t​e​d + * {​c​o​n​T​y​p​e​}​ ​{​n​a​m​e​}​ ​d​i​s​c​o​n​n​e​c​t​e​d * @param {string} conType + * @param {string} name */ - title: RequiredParams<'conType'> + title: RequiredParams<'conType' | 'name'> /** * T​u​n​n​e​l */ @@ -192,20 +194,13 @@ type RootTranslation = { * L​o​c​a​t​i​o​n */ location: string - body: { - /** - * {​c​o​n​T​y​p​e​}​ ​{​i​n​s​t​a​n​c​e​N​a​m​e​}​ ​w​a​s​ ​a​u​t​o​m​a​t​i​c​a​l​l​y​ ​d​i​s​c​o​n​n​e​c​t​e​d​ ​b​e​c​a​u​s​e​ ​i​t​ ​e​x​c​e​e​d​e​d​ ​t​h​e​ ​e​x​p​e​c​t​e​d​ ​t​i​m​e​ ​f​o​r​ ​s​t​a​y​i​n​g​ ​a​c​t​i​v​e​ ​w​i​t​h​o​u​t​ ​r​e​c​e​i​v​i​n​g​ ​c​o​n​f​i​r​m​a​t​i​o​n​ ​f​r​o​m​ ​t​h​e​ ​s​e​r​v​e​r​. - * @param {string} conType - * @param {string} instanceName - */ - periodic: RequiredParams<'conType' | 'instanceName'> - /** - * {​c​o​n​T​y​p​e​}​ ​{​n​a​m​e​}​ ​c​o​n​n​e​c​t​i​o​n​ ​w​a​s​ ​a​u​t​o​m​a​t​i​c​a​l​l​y​ ​d​i​s​c​o​n​n​e​c​t​e​d​ ​b​e​c​a​u​s​e​ ​i​t​ ​d​i​d​n​'​t​ ​c​o​m​p​l​e​t​e​ ​t​h​e​ ​n​e​c​e​s​s​a​r​y​ ​s​e​t​u​p​ ​i​n​ ​t​i​m​e​.​ ​T​h​i​s​ ​c​a​n​ ​h​a​p​p​e​n​ ​i​f​ ​t​h​e​ ​c​o​n​n​e​c​t​i​o​n​ ​w​a​s​n​'​t​ ​f​u​l​l​y​ ​e​s​t​a​b​l​i​s​h​e​d - * @param {string} conType - * @param {string} name - */ - connection: RequiredParams<'conType' | 'name'> - } + /** + * T​h​e​ ​{​c​o​n​T​y​p​e​}​ ​{​n​a​m​e​}​ ​h​a​s​ ​b​e​e​n​ ​d​i​s​c​o​n​n​e​c​t​e​d​,​ ​s​i​n​c​e​ ​w​e​ ​h​a​v​e​ ​d​e​t​e​c​t​e​d​ ​t​h​a​t​ ​t​h​e​ ​s​e​r​v​e​r​ ​i​s​ ​n​o​t​ ​r​e​s​p​o​n​d​i​n​g​ ​w​i​t​h​ ​a​n​y​ ​t​r​a​f​f​i​c​ ​f​o​r​ ​{​t​i​m​e​}​s​.​ ​I​f​ ​t​h​i​s​ ​m​e​s​s​a​g​e​ ​k​e​e​p​s​ ​o​c​c​u​r​r​i​n​g​,​ ​p​l​e​a​s​e​ ​c​o​n​t​a​c​t​ ​y​o​u​r​ ​a​d​m​i​n​i​s​t​r​a​t​o​r​ ​a​n​d​ ​i​n​f​o​r​m​ ​t​h​e​m​ ​a​b​o​u​t​ ​t​h​i​s​ ​f​a​c​t​. + * @param {string} conType + * @param {string} name + * @param {number} time + */ + message: RequiredParams<'conType' | 'name' | 'time'> controls: { /** * C​l​o​s​e @@ -1765,9 +1760,9 @@ export type TranslationFunctions = { */ configChanged: (arg: { instance: string }) => LocalizedString /** - * {con_type} {interface_name} disconnected. + * Detected no traffic for {con_type} {interface_name} for {time}s. Performed an automatic reconnect to try to preserve connection. */ - deadConDropped: (arg: { con_type: string, interface_name: string }) => LocalizedString + deadConDropped: (arg: { con_type: string, interface_name: string, time: number }) => LocalizedString } } components: { @@ -1783,9 +1778,9 @@ export type TranslationFunctions = { modals: { deadConDropped: { /** - * {conType} disconnected + * {conType} {name} disconnected */ - title: (arg: { conType: string }) => LocalizedString + title: (arg: { conType: string, name: string }) => LocalizedString /** * Tunnel */ @@ -1794,16 +1789,10 @@ export type TranslationFunctions = { * Location */ location: () => LocalizedString - body: { - /** - * {conType} {instanceName} was automatically disconnected because it exceeded the expected time for staying active without receiving confirmation from the server. - */ - periodic: (arg: { conType: string, instanceName: string }) => LocalizedString - /** - * {conType} {name} connection was automatically disconnected because it didn't complete the necessary setup in time. This can happen if the connection wasn't fully established - */ - connection: (arg: { conType: string, name: string }) => LocalizedString - } + /** + * The {conType} {name} has been disconnected, since we have detected that the server is not responding with any traffic for {time}s. If this message keeps occurring, please contact your administrator and inform them about this fact. + */ + message: (arg: { conType: string, name: string, time: number }) => LocalizedString controls: { /** * Close diff --git a/src/pages/client/ClientPage.tsx b/src/pages/client/ClientPage.tsx index f1fa8bd6..def21c5a 100644 --- a/src/pages/client/ClientPage.tsx +++ b/src/pages/client/ClientPage.tsx @@ -16,7 +16,7 @@ import { useDeadConDroppedModal } from './components/modals/DeadConDroppedModal/ import { useClientFlags } from './hooks/useClientFlags'; import { useClientStore } from './hooks/useClientStore'; import { clientQueryKeys } from './query'; -import { DeadConDroppedPayload, TauriEventKey } from './types'; +import { DeadConDroppedPayload, DeadConReconnectedPayload, TauriEventKey } from './types'; const { getInstances, getTunnels, getAppConfig } = clientApi; @@ -110,22 +110,30 @@ export const ClientPage = () => { const deadConnectionDropped = listen( TauriEventKey.DEAD_CONNECTION_DROPPED, + (data) => { + openDeadConDroppedModal(data.payload); + }, + ); + + const deadConnectionReconnected = listen( + TauriEventKey.DEAD_CONNECTION_RECONNECTED, (data) => { toaster.warning( LL.common.messages.deadConDropped({ interface_name: data.payload.name, con_type: data.payload.con_type, + time: data.payload.peer_alive_period, }), { lifetime: -1, }, ); - openDeadConDroppedModal(data.payload); }, ); return () => { deadConnectionDropped.then((cleanup) => cleanup()); + deadConnectionReconnected.then((cleanup) => cleanup()); configChanged.then((cleanup) => cleanup()); connectionChanged.then((cleanup) => cleanup()); instanceUpdate.then((cleanup) => cleanup()); diff --git a/src/pages/client/clientAPI/clientApi.ts b/src/pages/client/clientAPI/clientApi.ts index aa9ebcb9..b77afe2d 100644 --- a/src/pages/client/clientAPI/clientApi.ts +++ b/src/pages/client/clientAPI/clientApi.ts @@ -42,7 +42,9 @@ async function invokeWrapper( return res; // TODO: handle more error types ? } catch (e) { - let message: string = `Invoking command ${command} failed due to unknown error: ${JSON.stringify(e)}`; + let message: string = `Invoking command ${command} failed due to unknown error: ${JSON.stringify( + e, + )}`; trace(message); if (e instanceof TimeoutError) { message = `Invoking command ${command} timeout out after ${timeout / 1000} seconds`; diff --git a/src/pages/client/components/modals/DeadConDroppedModal/DeadConDroppedModal.tsx b/src/pages/client/components/modals/DeadConDroppedModal/DeadConDroppedModal.tsx index 2a9e698a..989f56f5 100644 --- a/src/pages/client/components/modals/DeadConDroppedModal/DeadConDroppedModal.tsx +++ b/src/pages/client/components/modals/DeadConDroppedModal/DeadConDroppedModal.tsx @@ -10,7 +10,7 @@ import { ButtonStyleVariant, } from '../../../../../shared/defguard-ui/components/Layout/Button/types'; import { ModalWithTitle } from '../../../../../shared/defguard-ui/components/Layout/modals/ModalWithTitle/ModalWithTitle'; -import { ClientConnectionType, DeadConDroppedOutReason } from '../../../types'; +import { ClientConnectionType } from '../../../types'; import { useDeadConDroppedModal } from './store'; export const DeadConDroppedModal = () => { @@ -25,6 +25,7 @@ export const DeadConDroppedModal = () => { isOpen={isOpen} title={localLL.title({ conType: payload?.con_type ?? '', + name: payload?.name ?? '', })} afterClose={reset} onClose={close} @@ -54,21 +55,14 @@ const ModalContent = () => { }, [localLL, payload?.con_type]); const message = useMemo(() => { - switch (payload?.reason) { - case DeadConDroppedOutReason.PERIODIC_VERIFICATION: - return localLL.body.periodic({ - conType: typeString, - instanceName: payload.name, - }); - case DeadConDroppedOutReason.CONNECTION_VERIFICATION: - return localLL.body.connection({ - conType: typeString, - name: payload.name, - }); - default: - return ''; + if (payload) { + return localLL.message({ + conType: typeString, + name: payload.name, + time: payload.peer_alive_period, + }); } - }, [payload?.reason, payload?.name, localLL.body, typeString]); + }, [payload?.name, localLL.message, typeString]); if (!payload) return null; return ( diff --git a/src/pages/client/pages/ClientInstancePage/components/LocationsList/components/LocationCardInfo/LocationCardInfo.tsx b/src/pages/client/pages/ClientInstancePage/components/LocationsList/components/LocationCardInfo/LocationCardInfo.tsx index 1dec96d6..c4395c72 100644 --- a/src/pages/client/pages/ClientInstancePage/components/LocationsList/components/LocationCardInfo/LocationCardInfo.tsx +++ b/src/pages/client/pages/ClientInstancePage/components/LocationsList/components/LocationCardInfo/LocationCardInfo.tsx @@ -1,54 +1,21 @@ import './style.scss'; -import { useQuery } from '@tanstack/react-query'; import dayjs from 'dayjs'; import { useI18nContext } from '../../../../../../../../i18n/i18n-react'; -import { clientApi } from '../../../../../../clientAPI/clientApi'; -import { clientQueryKeys } from '../../../../../../query'; -import { - CommonWireguardFields, - Connection, - WireguardInstanceType, -} from '../../../../../../types'; +import { CommonWireguardFields, Connection } from '../../../../../../types'; type Props = { location?: CommonWireguardFields; connection?: Connection; }; -const { getActiveConnection } = clientApi; - export const LocationCardInfo = ({ location, connection }: Props) => { const { LL } = useI18nContext(); const localLL = LL.pages.client.pages.instancePage.connectionLabels; - const { data: activeConnection } = useQuery({ - queryKey: [ - clientQueryKeys.getActiveConnection, - location?.id as number, - location?.connection_type, - ], - queryFn: () => - getActiveConnection({ - locationId: location?.id as number, - connectionType: location?.connection_type as WireguardInstanceType, - }), - enabled: location?.active, - }); - return ( <> -
- - {location?.active ? ( -

{activeConnection?.connected_from}

- ) : ( -

{connection ? connection.connected_from : localLL.neverConnected()}

- )} -

diff --git a/src/pages/client/pages/ClientInstancePage/components/LocationsList/components/LocationCardInfo/style.scss b/src/pages/client/pages/ClientInstancePage/components/LocationsList/components/LocationCardInfo/style.scss index 43135801..4dc78022 100644 --- a/src/pages/client/pages/ClientInstancePage/components/LocationsList/components/LocationCardInfo/style.scss +++ b/src/pages/client/pages/ClientInstancePage/components/LocationsList/components/LocationCardInfo/style.scss @@ -1,4 +1,3 @@ -.location-card-info-from, .location-card-info-connected, .location-card-info-ip, .location-card-allowed-traffic { diff --git a/src/pages/client/pages/ClientInstancePage/components/LocationsList/components/LocationsDetailView/components/LocationConnectionHistory/LocationHistoryTable/LocationHistoryTable.tsx b/src/pages/client/pages/ClientInstancePage/components/LocationsList/components/LocationsDetailView/components/LocationConnectionHistory/LocationHistoryTable/LocationHistoryTable.tsx index f4d0eb43..bb9aeea0 100644 --- a/src/pages/client/pages/ClientInstancePage/components/LocationsList/components/LocationsDetailView/components/LocationConnectionHistory/LocationHistoryTable/LocationHistoryTable.tsx +++ b/src/pages/client/pages/ClientInstancePage/components/LocationsList/components/LocationsDetailView/components/LocationConnectionHistory/LocationHistoryTable/LocationHistoryTable.tsx @@ -48,10 +48,6 @@ export const LocationHistoryTable = ({ connections }: Props) => { text: pageLL.duration(), key: 'duration', }, - { - text: pageLL.connectedFrom(), - key: 'connected_from', - }, { text: pageLL.upload(), key: 'upload', diff --git a/src/pages/client/pages/ClientSettingsPage/components/GlobalSettingsTab/GlobalSettingsTab.tsx b/src/pages/client/pages/ClientSettingsPage/components/GlobalSettingsTab/GlobalSettingsTab.tsx index 23b5dfe1..c4e6d936 100644 --- a/src/pages/client/pages/ClientSettingsPage/components/GlobalSettingsTab/GlobalSettingsTab.tsx +++ b/src/pages/client/pages/ClientSettingsPage/components/GlobalSettingsTab/GlobalSettingsTab.tsx @@ -80,7 +80,7 @@ export const GlobalSettingsTab = () => { invalid_type_error: LL.form.errors.required(), required_error: LL.form.errors.required(), }) - .gte(120, LL.form.errors.minValue({ min: 120 })), + .gte(4, LL.form.errors.minValue({ min: 4 })), }), [LL.form.errors], ); diff --git a/src/pages/client/types.ts b/src/pages/client/types.ts index 9e0849d8..9ab660f7 100644 --- a/src/pages/client/types.ts +++ b/src/pages/client/types.ts @@ -85,10 +85,15 @@ export enum DeadConDroppedOutReason { } export type DeadConDroppedPayload = { - id: number; name: string; con_type: ClientConnectionType; - reason: DeadConDroppedOutReason; + peer_alive_period: number; +}; + +export type DeadConReconnectedPayload = { + name: string; + con_type: ClientConnectionType; + peer_alive_period: number; }; export enum TauriEventKey { @@ -99,5 +104,6 @@ export enum TauriEventKey { APP_VERSION_FETCH = 'app-version-fetch', CONFIG_CHANGED = 'config-changed', DEAD_CONNECTION_DROPPED = 'dead-connection-dropped', + DEAD_CONNECTION_RECONNECTED = 'dead-connection-reconnected', APPLICATION_CONFIG_CHANGED = 'application-config-changed', } From 17578f2fa94d2d3e5c1a27ea5771b3720a4e4339 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Tue, 7 Jan 2025 12:50:10 +0100 Subject: [PATCH 2/7] Update src-tauri/src/periodic/connection.rs Co-authored-by: Adam --- src-tauri/src/periodic/connection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/src/periodic/connection.rs b/src-tauri/src/periodic/connection.rs index d929bf87..fd56289e 100644 --- a/src-tauri/src/periodic/connection.rs +++ b/src-tauri/src/periodic/connection.rs @@ -125,7 +125,7 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro ) { // Check if there was any traffic since the connection was established. // If not, consider the location dead and disconnect it later without reconnecting. - if latest_stat.collected_at - con.start < TimeDelta::zero() { + if latest_stat.collected_at < con.start { debug!("There wasn't any activity for Location {} since its connection at {}; considering it being dead and possibly broken. \ It will be disconnected without a further automatic reconnect.", con.location_id, con.start); locations_to_disconnect.push((con.location_id, false)); From 0ab60bcab456a56fa98e73fc1e0083dfe4dbc5fd Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Tue, 7 Jan 2025 12:53:26 +0100 Subject: [PATCH 3/7] fix frontend --- .../modals/DeadConDroppedModal/DeadConDroppedModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/client/components/modals/DeadConDroppedModal/DeadConDroppedModal.tsx b/src/pages/client/components/modals/DeadConDroppedModal/DeadConDroppedModal.tsx index 989f56f5..6a488f80 100644 --- a/src/pages/client/components/modals/DeadConDroppedModal/DeadConDroppedModal.tsx +++ b/src/pages/client/components/modals/DeadConDroppedModal/DeadConDroppedModal.tsx @@ -62,7 +62,7 @@ const ModalContent = () => { time: payload.peer_alive_period, }); } - }, [payload?.name, localLL.message, typeString]); + }, [localLL, payload, typeString]); if (!payload) return null; return ( From 871d61d7458b41fcfa7973537b4918e42fbd7424 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Tue, 7 Jan 2025 14:51:09 +0100 Subject: [PATCH 4/7] change message --- src-tauri/src/periodic/connection.rs | 12 ++++++------ src/i18n/en/index.ts | 2 +- src/i18n/i18n-types.ts | 9 ++++----- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src-tauri/src/periodic/connection.rs b/src-tauri/src/periodic/connection.rs index fd56289e..8fee56e7 100644 --- a/src-tauri/src/periodic/connection.rs +++ b/src-tauri/src/periodic/connection.rs @@ -40,18 +40,18 @@ async fn reconnect( match disconnect(con_id, con_type, app_handle.clone()).await { Ok(()) => { debug!("Connection for {con_type} {con_interface_name}({con_id}) disconnected successfully in path of reconnection."); + let payload = DeadConnReconnected { + name: con_interface_name.to_string(), + con_type, + peer_alive_period: peer_alive_period.num_seconds(), + }; + payload.emit(app_handle); match connect(con_id, con_type, None, app_handle.clone()).await { Ok(()) => { info!("Reconnect for {con_type} {con_interface_name} ({con_id}) succeeded.",); } Err(err) => { error!("Reconnect attempt failed, disconnect succeeded but connect failed. Error: {err}"); - let payload = DeadConnReconnected { - name: con_interface_name.to_string(), - con_type, - peer_alive_period: peer_alive_period.num_seconds(), - }; - payload.emit(app_handle); } } } diff --git a/src/i18n/en/index.ts b/src/i18n/en/index.ts index 721f464d..b55b0c08 100644 --- a/src/i18n/en/index.ts +++ b/src/i18n/en/index.ts @@ -53,7 +53,7 @@ const en = { configChanged: 'Configuration for instance {instance: string} has changed. Disconnect from all locations to apply changes.', deadConDropped: - 'Detected no traffic for {con_type: string} {interface_name: string} for {time: number}s. Performed an automatic reconnect to try to preserve connection.', + 'Detected that the {con_type: string} {interface_name: string} has disconnected, trying to reconnect...', }, }, components: { diff --git a/src/i18n/i18n-types.ts b/src/i18n/i18n-types.ts index 07aaadfa..63726cd1 100644 --- a/src/i18n/i18n-types.ts +++ b/src/i18n/i18n-types.ts @@ -160,12 +160,11 @@ type RootTranslation = { */ configChanged: RequiredParams<'instance'> /** - * D​e​t​e​c​t​e​d​ ​n​o​ ​t​r​a​f​f​i​c​ ​f​o​r​ ​{​c​o​n​_​t​y​p​e​}​ ​{​i​n​t​e​r​f​a​c​e​_​n​a​m​e​}​ ​f​o​r​ ​{​t​i​m​e​}​s​.​ ​P​e​r​f​o​r​m​e​d​ ​a​n​ ​a​u​t​o​m​a​t​i​c​ ​r​e​c​o​n​n​e​c​t​ ​t​o​ ​t​r​y​ ​t​o​ ​p​r​e​s​e​r​v​e​ ​c​o​n​n​e​c​t​i​o​n​. + * D​e​t​e​c​t​e​d​ ​t​h​a​t​ ​t​h​e​ ​{​c​o​n​_​t​y​p​e​}​ ​{​i​n​t​e​r​f​a​c​e​_​n​a​m​e​}​ ​h​a​s​ ​d​i​s​c​o​n​n​e​c​t​e​d​,​ ​t​r​y​i​n​g​ ​t​o​ ​r​e​c​o​n​n​e​c​t​.​.​. * @param {string} con_type * @param {string} interface_name - * @param {number} time */ - deadConDropped: RequiredParams<'con_type' | 'interface_name' | 'time'> + deadConDropped: RequiredParams<'con_type' | 'interface_name'> } } components: { @@ -1760,9 +1759,9 @@ export type TranslationFunctions = { */ configChanged: (arg: { instance: string }) => LocalizedString /** - * Detected no traffic for {con_type} {interface_name} for {time}s. Performed an automatic reconnect to try to preserve connection. + * Detected that the {con_type} {interface_name} has disconnected, trying to reconnect... */ - deadConDropped: (arg: { con_type: string, interface_name: string, time: number }) => LocalizedString + deadConDropped: (arg: { con_type: string, interface_name: string }) => LocalizedString } } components: { From 33ce793f01921f5c82f3cebc47c13fbc177997a9 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Tue, 7 Jan 2025 17:41:20 +0100 Subject: [PATCH 5/7] change some logs --- src-tauri/src/periodic/connection.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src-tauri/src/periodic/connection.rs b/src-tauri/src/periodic/connection.rs index 8fee56e7..e406cd6d 100644 --- a/src-tauri/src/periodic/connection.rs +++ b/src-tauri/src/periodic/connection.rs @@ -107,6 +107,8 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro let connection_count = connections.len(); if connection_count == 0 { debug!("Connections verification skipped, no active connections found, task will wait for next {CHECK_INTERVAL:?}"); + } else { + debug!("Verifying state of {connection_count} active connections. Inactive connections will be disconnected and reconnected if possible."); } let peer_alive_period = TimeDelta::seconds(i64::from( app_state.app_config.lock().unwrap().peer_alive_period, @@ -130,7 +132,7 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro It will be disconnected without a further automatic reconnect.", con.location_id, con.start); locations_to_disconnect.push((con.location_id, false)); } else { - debug!("There wasn't any activity for Location {}; considering it being dead.", con.location_id); + debug!("There wasn't any activity for Location {} for the last {}s; considering it being dead.", con.location_id, peer_alive_period.num_seconds()); locations_to_disconnect.push((con.location_id, true)); } } @@ -165,7 +167,7 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro It will be disconnected without a further automatic reconnect.", con.location_id, con.start); tunnels_to_disconnect.push((con.location_id, false)); } else { - debug!("There wasn't any activity for Tunnel {}; considering it being dead.", con.location_id); + debug!("There wasn't any activity for Tunnel {} for the last {}s; considering it being dead.", con.location_id, peer_alive_period.num_seconds()); tunnels_to_disconnect.push((con.location_id, true)); } } @@ -263,7 +265,7 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro ) .await; } else { - warn!("Automatic reconnect for location {}({}) is not possible due to lack of activity. Interface will be disconnected.", tunnel.name, tunnel.id); + debug!("Automatic reconnect for location {}({}) is not possible due to lack of activity since the connection start. Interface will be disconnected.", tunnel.name, tunnel.id); disconnect_dead_connection( tunnel_id, "DEAD TUNNEL", From 46d963b413360badf76087aabb275174ec6c66d4 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Tue, 7 Jan 2025 17:43:10 +0100 Subject: [PATCH 6/7] fix frontend --- src/pages/client/ClientPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/client/ClientPage.tsx b/src/pages/client/ClientPage.tsx index def21c5a..ff8b49a1 100644 --- a/src/pages/client/ClientPage.tsx +++ b/src/pages/client/ClientPage.tsx @@ -122,7 +122,6 @@ export const ClientPage = () => { LL.common.messages.deadConDropped({ interface_name: data.payload.name, con_type: data.payload.con_type, - time: data.payload.peer_alive_period, }), { lifetime: -1, From bb2a417a28319267fb52a43f76227f2e4adeb9c3 Mon Sep 17 00:00:00 2001 From: Aleksander <170264518+t-aleksander@users.noreply.github.com> Date: Tue, 7 Jan 2025 17:53:18 +0100 Subject: [PATCH 7/7] Update GlobalSettingsTab.tsx --- .../components/GlobalSettingsTab/GlobalSettingsTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/client/pages/ClientSettingsPage/components/GlobalSettingsTab/GlobalSettingsTab.tsx b/src/pages/client/pages/ClientSettingsPage/components/GlobalSettingsTab/GlobalSettingsTab.tsx index c4e6d936..23b5dfe1 100644 --- a/src/pages/client/pages/ClientSettingsPage/components/GlobalSettingsTab/GlobalSettingsTab.tsx +++ b/src/pages/client/pages/ClientSettingsPage/components/GlobalSettingsTab/GlobalSettingsTab.tsx @@ -80,7 +80,7 @@ export const GlobalSettingsTab = () => { invalid_type_error: LL.form.errors.required(), required_error: LL.form.errors.required(), }) - .gte(4, LL.form.errors.minValue({ min: 4 })), + .gte(120, LL.form.errors.minValue({ min: 120 })), }), [LL.form.errors], );