diff --git a/.sqlx/query-3daa0bbce8f2f5d0d30b104db8fd043e7a4938d94962844c66a2be3d242c00ce.json b/.sqlx/query-3daa0bbce8f2f5d0d30b104db8fd043e7a4938d94962844c66a2be3d242c00ce.json deleted file mode 100644 index 9acf7d2ebe..0000000000 --- a/.sqlx/query-3daa0bbce8f2f5d0d30b104db8fd043e7a4938d94962844c66a2be3d242c00ce.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE \"wireguard_peer_stats\" SET \"device_id\" = $2,\"collected_at\" = $3,\"network\" = $4,\"endpoint\" = $5,\"upload\" = $6,\"download\" = $7,\"latest_handshake\" = $8,\"allowed_ips\" = $9 WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Timestamp", - "Int8", - "Text", - "Int8", - "Int8", - "Timestamp", - "Text" - ] - }, - "nullable": [] - }, - "hash": "3daa0bbce8f2f5d0d30b104db8fd043e7a4938d94962844c66a2be3d242c00ce" -} diff --git a/.sqlx/query-45ab60569d24c9f9859a68c022cfc2fb13ea09e95ba0cb86f5419a85147dbf97.json b/.sqlx/query-45ab60569d24c9f9859a68c022cfc2fb13ea09e95ba0cb86f5419a85147dbf97.json deleted file mode 100644 index eefbf825a7..0000000000 --- a/.sqlx/query-45ab60569d24c9f9859a68c022cfc2fb13ea09e95ba0cb86f5419a85147dbf97.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id, \"device_id\",\"collected_at\",\"network\",\"endpoint\",\"upload\",\"download\",\"latest_handshake\",\"allowed_ips\" FROM \"wireguard_peer_stats\"", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "device_id", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "collected_at", - "type_info": "Timestamp" - }, - { - "ordinal": 3, - "name": "network", - "type_info": "Int8" - }, - { - "ordinal": 4, - "name": "endpoint", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "upload", - "type_info": "Int8" - }, - { - "ordinal": 6, - "name": "download", - "type_info": "Int8" - }, - { - "ordinal": 7, - "name": "latest_handshake", - "type_info": "Timestamp" - }, - { - "ordinal": 8, - "name": "allowed_ips", - "type_info": "Text" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - false, - false, - false, - true, - false, - false, - false, - true - ] - }, - "hash": "45ab60569d24c9f9859a68c022cfc2fb13ea09e95ba0cb86f5419a85147dbf97" -} diff --git a/.sqlx/query-9ea6f3e288d0a23c2b020034c80e60c0c73bcc37705cf408ea14c65ec3d1dab8.json b/.sqlx/query-9ea6f3e288d0a23c2b020034c80e60c0c73bcc37705cf408ea14c65ec3d1dab8.json deleted file mode 100644 index 583b36b236..0000000000 --- a/.sqlx/query-9ea6f3e288d0a23c2b020034c80e60c0c73bcc37705cf408ea14c65ec3d1dab8.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM \"wireguard_peer_stats\" WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "9ea6f3e288d0a23c2b020034c80e60c0c73bcc37705cf408ea14c65ec3d1dab8" -} diff --git a/.sqlx/query-a1ffe5a3d79b9fb9261b59067286a6bd455ce7059baf539d7b678996c2c92c8c.json b/.sqlx/query-a1ffe5a3d79b9fb9261b59067286a6bd455ce7059baf539d7b678996c2c92c8c.json deleted file mode 100644 index b6b7ea5bec..0000000000 --- a/.sqlx/query-a1ffe5a3d79b9fb9261b59067286a6bd455ce7059baf539d7b678996c2c92c8c.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "WITH stats AS ( SELECT DISTINCT ON (network) network, endpoint, latest_handshake FROM wireguard_peer_stats WHERE device_id = $2 ORDER BY network, collected_at DESC ) SELECT n.id network_id, n.name network_name, n.endpoint gateway_endpoint, wnd.wireguard_ips \"device_wireguard_ips: Vec\", stats.endpoint device_endpoint, stats.latest_handshake \"latest_handshake?\", COALESCE((NOW() - stats.latest_handshake) < $1, FALSE) \"is_active!\" FROM wireguard_network_device wnd JOIN wireguard_network n ON n.id = wnd.wireguard_network_id LEFT JOIN stats ON n.id = stats.network WHERE wnd.device_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "network_id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "network_name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "gateway_endpoint", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "device_wireguard_ips: Vec", - "type_info": "InetArray" - }, - { - "ordinal": 4, - "name": "device_endpoint", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "latest_handshake?", - "type_info": "Timestamp" - }, - { - "ordinal": 6, - "name": "is_active!", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Interval", - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - true, - false, - null - ] - }, - "hash": "a1ffe5a3d79b9fb9261b59067286a6bd455ce7059baf539d7b678996c2c92c8c" -} diff --git a/.sqlx/query-a4941d4bcf5483036924c13b2293034b9ebc57993c63a2dedb347648c0dfc6a9.json b/.sqlx/query-a4941d4bcf5483036924c13b2293034b9ebc57993c63a2dedb347648c0dfc6a9.json deleted file mode 100644 index 9cf3342fda..0000000000 --- a/.sqlx/query-a4941d4bcf5483036924c13b2293034b9ebc57993c63a2dedb347648c0dfc6a9.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO \"wireguard_peer_stats\" (\"device_id\",\"collected_at\",\"network\",\"endpoint\",\"upload\",\"download\",\"latest_handshake\",\"allowed_ips\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Int8", - "Timestamp", - "Int8", - "Text", - "Int8", - "Int8", - "Timestamp", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "a4941d4bcf5483036924c13b2293034b9ebc57993c63a2dedb347648c0dfc6a9" -} diff --git a/.sqlx/query-b83158ecab3024ae6e63c2f59e36c94b531af794d9540d1585eb5512db787a38.json b/.sqlx/query-b83158ecab3024ae6e63c2f59e36c94b531af794d9540d1585eb5512db787a38.json deleted file mode 100644 index b7f40eaee9..0000000000 --- a/.sqlx/query-b83158ecab3024ae6e63c2f59e36c94b531af794d9540d1585eb5512db787a38.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id, device_id \"device_id!\", collected_at \"collected_at!\", network \"network!\", endpoint, upload \"upload!\", download \"download!\", latest_handshake \"latest_handshake!\", allowed_ips FROM wireguard_peer_stats WHERE device_id = $1 AND network = $2 ORDER BY collected_at DESC LIMIT 1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "device_id!", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "collected_at!", - "type_info": "Timestamp" - }, - { - "ordinal": 3, - "name": "network!", - "type_info": "Int8" - }, - { - "ordinal": 4, - "name": "endpoint", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "upload!", - "type_info": "Int8" - }, - { - "ordinal": 6, - "name": "download!", - "type_info": "Int8" - }, - { - "ordinal": 7, - "name": "latest_handshake!", - "type_info": "Timestamp" - }, - { - "ordinal": 8, - "name": "allowed_ips", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Int8", - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - true, - false, - false, - false, - true - ] - }, - "hash": "b83158ecab3024ae6e63c2f59e36c94b531af794d9540d1585eb5512db787a38" -} diff --git a/.sqlx/query-edb099ee36c93c25aa9f91afdbf0c673f870e9d8c619826f855c7090a7d3cf30.json b/.sqlx/query-edb099ee36c93c25aa9f91afdbf0c673f870e9d8c619826f855c7090a7d3cf30.json deleted file mode 100644 index 94f69bdb1d..0000000000 --- a/.sqlx/query-edb099ee36c93c25aa9f91afdbf0c673f870e9d8c619826f855c7090a7d3cf30.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id, \"device_id\",\"collected_at\",\"network\",\"endpoint\",\"upload\",\"download\",\"latest_handshake\",\"allowed_ips\" FROM \"wireguard_peer_stats\" WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "device_id", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "collected_at", - "type_info": "Timestamp" - }, - { - "ordinal": 3, - "name": "network", - "type_info": "Int8" - }, - { - "ordinal": 4, - "name": "endpoint", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "upload", - "type_info": "Int8" - }, - { - "ordinal": 6, - "name": "download", - "type_info": "Int8" - }, - { - "ordinal": 7, - "name": "latest_handshake", - "type_info": "Timestamp" - }, - { - "ordinal": 8, - "name": "allowed_ips", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - true, - false, - false, - false, - true - ] - }, - "hash": "edb099ee36c93c25aa9f91afdbf0c673f870e9d8c619826f855c7090a7d3cf30" -} diff --git a/.sqlx/query-f64a22a8141030a41aa9b85275158a3a75e5ebfa0f14c84b1aefc8582156eb03.json b/.sqlx/query-f64a22a8141030a41aa9b85275158a3a75e5ebfa0f14c84b1aefc8582156eb03.json new file mode 100644 index 0000000000..72e7fb3e2e --- /dev/null +++ b/.sqlx/query-f64a22a8141030a41aa9b85275158a3a75e5ebfa0f14c84b1aefc8582156eb03.json @@ -0,0 +1,69 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT n.id network_id, n.name network_name, n.endpoint gateway_endpoint, wnd.wireguard_ips \"device_wireguard_ips: Vec\", vs.endpoint \"device_endpoint?\", vs.latest_handshake \"latest_handshake?\", vs.state \"state?: VpnClientSessionState\" FROM wireguard_network_device wnd JOIN wireguard_network n ON n.id = wnd.wireguard_network_id LEFT JOIN LATERAL ( SELECT id, state, location_id, endpoint, latest_handshake FROM vpn_client_session LEFT JOIN LATERAL ( SELECT session_id, endpoint, latest_handshake FROM vpn_session_stats WHERE session_id = vpn_client_session.id ORDER BY collected_at DESC LIMIT 1 ) vss ON vss.session_id = vpn_client_session.id WHERE location_id = n.id and device_id = $1 ORDER BY created_at DESC LIMIT 1 ) vs ON vs.location_id = n.id WHERE wnd.device_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "network_id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "network_name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "gateway_endpoint", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "device_wireguard_ips: Vec", + "type_info": "InetArray" + }, + { + "ordinal": 4, + "name": "device_endpoint?", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "latest_handshake?", + "type_info": "Timestamp" + }, + { + "ordinal": 6, + "name": "state?: VpnClientSessionState", + "type_info": { + "Custom": { + "name": "vpn_client_session_state", + "kind": { + "Enum": [ + "new", + "connected", + "disconnected" + ] + } + } + } + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "f64a22a8141030a41aa9b85275158a3a75e5ebfa0f14c84b1aefc8582156eb03" +} diff --git a/crates/defguard_common/src/db/models/device.rs b/crates/defguard_common/src/db/models/device.rs index 53aa3abbff..51e5ef7654 100644 --- a/crates/defguard_common/src/db/models/device.rs +++ b/crates/defguard_common/src/db/models/device.rs @@ -11,8 +11,8 @@ use rand::{ }; use serde::{Deserialize, Serialize}; use sqlx::{ - Error as SqlxError, FromRow, PgConnection, PgExecutor, PgPool, Type, - postgres::types::PgInterval, query, query_as, query_scalar, + Error as SqlxError, FromRow, PgConnection, PgExecutor, PgPool, Type, query, query_as, + query_scalar, }; use thiserror::Error; use tracing::{debug, error, info}; @@ -26,9 +26,8 @@ use crate::{ models::{ ModelError, WireguardNetwork, user::User, - wireguard::{ - LocationMfaMode, NetworkAddressError, ServiceLocationMode, WIREGUARD_MAX_HANDSHAKE, - }, + vpn_client_session::VpnClientSessionState, + wireguard::{LocationMfaMode, NetworkAddressError, ServiceLocationMode}, }, }, }; @@ -195,30 +194,35 @@ pub struct UserDeviceNetworkInfo { pub network_gateway_ip: String, pub device_wireguard_ips: Vec, pub last_connected_ip: Option, - pub last_connected_location: Option, pub last_connected_at: Option, pub is_active: bool, } impl UserDevice { pub async fn from_device(pool: &PgPool, device: Device) -> Result, SqlxError> { - // fetch device config and connection info for all networks + // fetch device config and connection info for all allowed networks let result = query!( - "WITH stats AS ( \ - SELECT DISTINCT ON (network) network, endpoint, latest_handshake \ - FROM wireguard_peer_stats \ - WHERE device_id = $2 \ - ORDER BY network, collected_at DESC \ - ) \ - SELECT n.id network_id, n.name network_name, n.endpoint gateway_endpoint, \ - wnd.wireguard_ips \"device_wireguard_ips: Vec\", stats.endpoint device_endpoint, \ - stats.latest_handshake \"latest_handshake?\", \ - COALESCE((NOW() - stats.latest_handshake) < $1, FALSE) \"is_active!\" \ + "SELECT n.id network_id, n.name network_name, n.endpoint gateway_endpoint, \ + wnd.wireguard_ips \"device_wireguard_ips: Vec\", vs.endpoint \"device_endpoint?\", \ + vs.latest_handshake \"latest_handshake?\", \ + vs.state \"state?: VpnClientSessionState\" \ FROM wireguard_network_device wnd \ JOIN wireguard_network n ON n.id = wnd.wireguard_network_id \ - LEFT JOIN stats ON n.id = stats.network \ - WHERE wnd.device_id = $2", - PgInterval::try_from(WIREGUARD_MAX_HANDSHAKE).unwrap(), + LEFT JOIN LATERAL ( \ + SELECT id, state, location_id, endpoint, latest_handshake \ + FROM vpn_client_session \ + LEFT JOIN LATERAL ( \ + SELECT session_id, endpoint, latest_handshake \ + FROM vpn_session_stats \ + WHERE session_id = vpn_client_session.id \ + ORDER BY collected_at DESC \ + LIMIT 1 \ + ) vss ON vss.session_id = vpn_client_session.id \ + WHERE location_id = n.id and device_id = $1 \ + ORDER BY created_at DESC \ + LIMIT 1 \ + ) vs ON vs.location_id = n.id \ + WHERE wnd.device_id = $1", device.id, ) .fetch_all(pool) @@ -227,7 +231,7 @@ impl UserDevice { let networks_info: Vec = result .into_iter() .map(|r| { - // TODO: merge below enclosure with WireguardPeerStats::endpoint_without_port(). + // extract latest public IP from stats endpoint let device_ip = r.device_endpoint.and_then(|endpoint| { let mut addr = endpoint.rsplit_once(':')?.0; // Strip square brackets. @@ -237,6 +241,12 @@ impl UserDevice { } Some(addr.to_owned()) }); + + let is_active = match r.state { + Some(session_state) => session_state == VpnClientSessionState::Connected, + None => false, + }; + UserDeviceNetworkInfo { network_id: r.network_id, network_name: r.network_name, @@ -247,9 +257,8 @@ impl UserDevice { .map(IpAddr::to_string) .collect(), last_connected_ip: device_ip, - last_connected_location: None, last_connected_at: r.latest_handshake, - is_active: r.is_active, + is_active, } }) .collect(); diff --git a/crates/defguard_common/src/db/models/mod.rs b/crates/defguard_common/src/db/models/mod.rs index 9f0150aa36..e93d57d940 100644 --- a/crates/defguard_common/src/db/models/mod.rs +++ b/crates/defguard_common/src/db/models/mod.rs @@ -19,7 +19,6 @@ pub mod vpn_client_session; pub mod vpn_session_stats; pub mod webauthn; pub mod wireguard; -pub mod wireguard_peer_stats; pub mod yubikey; pub use auth_code::AuthCode; diff --git a/migrations/20260202100252_[2.0.0]_drop_legacy_stats_table.down.sql b/migrations/20260202100252_[2.0.0]_drop_legacy_stats_table.down.sql new file mode 100644 index 0000000000..bab1a6b5f0 --- /dev/null +++ b/migrations/20260202100252_[2.0.0]_drop_legacy_stats_table.down.sql @@ -0,0 +1,29 @@ +-- Restore legacy stats table +CREATE TABLE wireguard_peer_stats ( + id bigserial PRIMARY KEY, + device_id bigint NOT NULL, + collected_at timestamp without time zone NOT NULL DEFAULT current_timestamp, + network bigint NOT NULL, + endpoint text, + upload bigint NOT NULL, + download bigint NOT NULL, + latest_handshake timestamp without time zone NOT NULL, + allowed_ips text, + FOREIGN KEY (device_id) REFERENCES device(id) ON DELETE CASCADE +); +CREATE INDEX peer_stats_device_id_collected_at on wireguard_peer_stats (device_id, network, collected_at DESC, latest_handshake DESC NULLS LAST); + + +-- Restore stats view +CREATE OR REPLACE VIEW wireguard_peer_stats_view AS + SELECT + device_id, + greatest(upload - lag(upload, 1, upload) OVER (PARTITION BY device_id, network ORDER BY collected_at), 0) upload, + greatest(download - lag(download, 1, download) OVER (PARTITION BY device_id, network ORDER BY collected_at), 0) download, + latest_handshake - (lag(latest_handshake, 1, latest_handshake) OVER (PARTITION BY device_id, network ORDER BY collected_at)) latest_handshake_diff, + latest_handshake, + collected_at, + network, + endpoint, + allowed_ips + FROM wireguard_peer_stats; diff --git a/migrations/20260202100252_[2.0.0]_drop_legacy_stats_table.up.sql b/migrations/20260202100252_[2.0.0]_drop_legacy_stats_table.up.sql new file mode 100644 index 0000000000..ebdfaaf7ab --- /dev/null +++ b/migrations/20260202100252_[2.0.0]_drop_legacy_stats_table.up.sql @@ -0,0 +1,6 @@ +-- Remove stats view +DROP VIEW wireguard_peer_stats_view; + +-- Drop stats table +DROP TABLE wireguard_peer_stats; + diff --git a/tools/defguard_generator/src/vpn_session_stats.rs b/tools/defguard_generator/src/vpn_session_stats.rs index fefd12e8f6..b306abb647 100644 --- a/tools/defguard_generator/src/vpn_session_stats.rs +++ b/tools/defguard_generator/src/vpn_session_stats.rs @@ -9,7 +9,6 @@ use defguard_common::db::{ gateway::Gateway, vpn_client_session::{VpnClientSession, VpnClientSessionState}, vpn_session_stats::VpnSessionStats, - wireguard::LocationMfaMode, }, }; use rand::{Rng, rngs::ThreadRng}; diff --git a/web/src/routeTree.gen.ts b/web/src/routeTree.gen.ts index c0cb54e137..421695fee1 100644 --- a/web/src/routeTree.gen.ts +++ b/web/src/routeTree.gen.ts @@ -328,6 +328,7 @@ const AuthorizedDefaultEdgeEdgeIdEditRoute = export interface FileRoutesByFullPath { '/404': typeof R404Route + '/': typeof AuthorizedDefaultRouteWithChildren '/auth': typeof AuthRouteWithChildren '/consent': typeof ConsentRoute '/playground': typeof PlaygroundRoute @@ -367,15 +368,16 @@ export interface FileRoutesByFullPath { '/settings/smtp': typeof AuthorizedDefaultSettingsSmtpRoute '/user/$username': typeof AuthorizedDefaultUserUsernameRoute '/vpn-overview/$locationId': typeof AuthorizedDefaultVpnOverviewLocationIdRoute - '/edge': typeof AuthorizedDefaultEdgeIndexRoute - '/locations': typeof AuthorizedDefaultLocationsIndexRoute - '/settings': typeof AuthorizedDefaultSettingsIndexRoute - '/vpn-overview': typeof AuthorizedDefaultVpnOverviewIndexRoute + '/edge/': typeof AuthorizedDefaultEdgeIndexRoute + '/locations/': typeof AuthorizedDefaultLocationsIndexRoute + '/settings/': typeof AuthorizedDefaultSettingsIndexRoute + '/vpn-overview/': typeof AuthorizedDefaultVpnOverviewIndexRoute '/edge/$edgeId/edit': typeof AuthorizedDefaultEdgeEdgeIdEditRoute '/locations/$locationId/edit': typeof AuthorizedDefaultLocationsLocationIdEditRoute } export interface FileRoutesByTo { '/404': typeof R404Route + '/': typeof AuthorizedDefaultRouteWithChildren '/consent': typeof ConsentRoute '/playground': typeof PlaygroundRoute '/snackbar': typeof SnackbarRoute @@ -476,6 +478,7 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/404' + | '/' | '/auth' | '/consent' | '/playground' @@ -515,15 +518,16 @@ export interface FileRouteTypes { | '/settings/smtp' | '/user/$username' | '/vpn-overview/$locationId' - | '/edge' - | '/locations' - | '/settings' - | '/vpn-overview' + | '/edge/' + | '/locations/' + | '/settings/' + | '/vpn-overview/' | '/edge/$edgeId/edit' | '/locations/$locationId/edit' fileRoutesByTo: FileRoutesByTo to: | '/404' + | '/' | '/consent' | '/playground' | '/snackbar' @@ -662,7 +666,7 @@ declare module '@tanstack/react-router' { '/_authorized': { id: '/_authorized' path: '' - fullPath: '' + fullPath: '/' preLoaderRoute: typeof AuthorizedRouteImport parentRoute: typeof rootRouteImport } @@ -711,7 +715,7 @@ declare module '@tanstack/react-router' { '/_authorized/_default': { id: '/_authorized/_default' path: '' - fullPath: '' + fullPath: '/' preLoaderRoute: typeof AuthorizedDefaultRouteImport parentRoute: typeof AuthorizedRoute } @@ -823,28 +827,28 @@ declare module '@tanstack/react-router' { '/_authorized/_default/vpn-overview/': { id: '/_authorized/_default/vpn-overview/' path: '/vpn-overview' - fullPath: '/vpn-overview' + fullPath: '/vpn-overview/' preLoaderRoute: typeof AuthorizedDefaultVpnOverviewIndexRouteImport parentRoute: typeof AuthorizedDefaultRoute } '/_authorized/_default/settings/': { id: '/_authorized/_default/settings/' path: '/settings' - fullPath: '/settings' + fullPath: '/settings/' preLoaderRoute: typeof AuthorizedDefaultSettingsIndexRouteImport parentRoute: typeof AuthorizedDefaultRoute } '/_authorized/_default/locations/': { id: '/_authorized/_default/locations/' path: '/locations' - fullPath: '/locations' + fullPath: '/locations/' preLoaderRoute: typeof AuthorizedDefaultLocationsIndexRouteImport parentRoute: typeof AuthorizedDefaultRoute } '/_authorized/_default/edge/': { id: '/_authorized/_default/edge/' path: '/edge' - fullPath: '/edge' + fullPath: '/edge/' preLoaderRoute: typeof AuthorizedDefaultEdgeIndexRouteImport parentRoute: typeof AuthorizedDefaultRoute }