Skip to content

Commit

Permalink
Merge pull request #895 from DefGuard/updates-ui
Browse files Browse the repository at this point in the history
* core update notification ui (modal)

* feat: version update notification as custom toast

* cleanup

* update check backend

* core updates frontend

* cleanup

* update lockfile

---------

Co-authored-by: Filip Ślęzak <[email protected]>
  • Loading branch information
t-aleksander and filipslezaklab authored Dec 11, 2024
2 parents 96d27b7 + b97bd1f commit d5d602e
Show file tree
Hide file tree
Showing 29 changed files with 1,178 additions and 220 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ secp256k1 = { version = "0.29", features = [
"global-context",
] }
secrecy = { version = "0.8", features = ["serde"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive"] }
# match version from webauthn-rs-core
serde_cbor = { version = "0.12.0-dev", package = "serde_cbor_2" }
Expand Down
1 change: 1 addition & 0 deletions src/handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub mod openid_flow;
pub(crate) mod settings;
pub(crate) mod ssh_authorized_keys;
pub(crate) mod support;
pub(crate) mod updates;
pub(crate) mod user;
pub(crate) mod webhooks;
#[cfg(feature = "wireguard")]
Expand Down
29 changes: 29 additions & 0 deletions src/handlers/updates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use axum::http::StatusCode;
use serde_json::json;

use super::{ApiResponse, ApiResult};
use crate::{
auth::{AdminRole, SessionInfo},
updates::get_update,
};

pub async fn check_new_version(_admin: AdminRole, session: SessionInfo) -> ApiResult {
debug!(
"User {} is checking if there is a new version available",
session.user.username
);
let update = get_update();
if let Some(update) = update.as_ref() {
debug!("A new version is available, returning the update information");
Ok(ApiResponse {
json: json!(update),
status: StatusCode::OK,
})
} else {
debug!("No new version available");
Ok(ApiResponse {
json: serde_json::json!({ "message": "No updates available" }),
status: StatusCode::NO_CONTENT,
})
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use handlers::{
add_authentication_key, delete_authentication_key, fetch_authentication_keys,
rename_authentication_key,
},
updates::check_new_version,
yubikey::{delete_yubikey, rename_yubikey},
};
use ipnetwork::IpNetwork;
Expand Down Expand Up @@ -130,6 +131,7 @@ pub(crate) mod random;
pub mod secret;
pub mod support;
pub mod templates;
pub mod updates;
pub mod utility_thread;
pub mod wg_config;
pub mod wireguard_peer_disconnect;
Expand Down Expand Up @@ -299,6 +301,7 @@ pub fn build_webapp(
.route("/info", get(get_app_info))
.route("/ssh_authorized_keys", get(get_authorized_keys))
.route("/api-docs", get(openapi))
.route("/updates", get(check_new_version))
// /auth
.route("/auth", post(authenticate))
.route("/auth/logout", post(logout))
Expand Down
71 changes: 71 additions & 0 deletions src/updates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use std::{
env,
sync::{RwLock, RwLockReadGuard},
};

use chrono::NaiveDate;
use semver::Version;

const PRODUCT_NAME: &str = "Defguard";
const UPDATES_URL: &str = "https://update-service-dev.defguard.net/api/update/check";
const VERSION: &str = env!("CARGO_PKG_VERSION");

#[derive(Deserialize, Debug, Serialize)]
pub struct Update {
version: String,
release_date: NaiveDate,
release_notes_url: String,
update_url: String,
critical: bool,
notes: String,
}

static NEW_UPDATE: RwLock<Option<Update>> = RwLock::new(None);

fn set_update(update: Update) {
*NEW_UPDATE
.write()
.expect("Failed to acquire lock on the update.") = Some(update);
}

pub fn get_update() -> RwLockReadGuard<'static, Option<Update>> {
NEW_UPDATE
.read()
.expect("Failed to acquire lock on the update.")
}

async fn fetch_update() -> Result<Update, anyhow::Error> {
let body = serde_json::json!({
"product": PRODUCT_NAME,
"client_version": VERSION,
"operating_system": env::consts::OS,
});
let response = reqwest::Client::new()
.post(UPDATES_URL)
.json(&body)
.send()
.await?;
Ok(response.json::<Update>().await?)
}

pub(crate) async fn do_new_version_check() -> Result<(), anyhow::Error> {
debug!("Checking for new version of Defguard ...");
let update = fetch_update().await?;
let current_version = Version::parse(VERSION)?;
let new_version = Version::parse(&update.version)?;
if new_version > current_version {
if update.critical {
warn!("There is a new critical Defguard update available: {} (Released on {}). It's recommended to update as soon as possible.",
update.version, update.release_date);
} else {
info!(
"There is a new Defguard version available: {} (Released on {})",
update.version, update.release_date
);
}
set_update(update);
} else {
debug!("New version check done. You are using the latest version of Defguard.");
}
Ok(())
}
24 changes: 21 additions & 3 deletions src/utility_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@ use std::time::Duration;
use sqlx::PgPool;
use tokio::time::{sleep, Instant};

use crate::enterprise::{
directory_sync::{do_directory_sync, get_directory_sync_interval},
limits::do_count_update,
use crate::{
enterprise::{
directory_sync::{do_directory_sync, get_directory_sync_interval},
limits::do_count_update,
},
updates::do_new_version_check,
};

const UTILITY_THREAD_MAIN_SLEEP_TIME: u64 = 5;
const COUNT_UPDATE_INTERVAL: u64 = 60 * 60;
const UPDATES_CHECK_INTERVAL: u64 = 60 * 60 * 6;

pub async fn run_utility_thread(pool: &PgPool) -> Result<(), anyhow::Error> {
let mut last_count_update = Instant::now();
let mut last_directory_sync = Instant::now();
let mut last_updates_check = Instant::now();

let directory_sync_task = || async {
if let Err(e) = do_directory_sync(pool).await {
Expand All @@ -27,8 +32,15 @@ pub async fn run_utility_thread(pool: &PgPool) -> Result<(), anyhow::Error> {
}
};

let updates_check_task = || async {
if let Err(e) = do_new_version_check().await {
error!("There was an error while checking for new Defguard version: {e:?}");
}
};

directory_sync_task().await;
count_update_task().await;
updates_check_task().await;

loop {
sleep(Duration::from_secs(UTILITY_THREAD_MAIN_SLEEP_TIME)).await;
Expand All @@ -44,5 +56,11 @@ pub async fn run_utility_thread(pool: &PgPool) -> Result<(), anyhow::Error> {
directory_sync_task().await;
last_directory_sync = Instant::now();
}

// Check for new Defguard version
if last_updates_check.elapsed().as_secs() >= UPDATES_CHECK_INTERVAL {
updates_check_task().await;
last_updates_check = Instant::now();
}
}
}
1 change: 1 addition & 0 deletions web/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PROXY_TARGET=https://defguard-dev.teonite.net
12 changes: 7 additions & 5 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"serve": "vite preview",
"generate-translation-types": "typesafe-i18n --no-watch",
"lint": "eslint -c ./.eslintrc.cjs --ignore-path .eslintignore --ext .ts --ext .tsx src/ && prettier --check 'src/**/*.{ts,tsx,scss}' && tsc",
"fix": "prettier -w 'src/**/*.{ts,tsx,scss}' && eslint -c ./.eslintrc.cjs --ignore-path .eslintignore --fix 'src/**/*.{ts,tsx}'",
"fix": "prettier -w 'src/**/*.{ts,tsx,scss}' && eslint -c ./.eslintrc.cjs --ignore-path .eslintignore --fix --ext .ts --ext .tsx src/",
"parse-core-svgs": "svgr --no-index --jsx-runtime 'automatic' --svgo-config ./svgo.config.json --prettier-config ./.prettierrc --out-dir ./src/shared/components/svg/ --typescript ./src/shared/images/svg/",
"parse-ui-svgs": "svgr --no-index --jsx-runtime 'automatic' --svgo-config ./svgo.config.json --prettier-config ./.prettierrc --out-dir ./src/shared/defguard-ui/components/svg/ --typescript ./src/shared/defguard-ui/images/svg/",
"parse-svgs": "pnpm parse-ui-svgs && pnpm parse-core-svgs",
Expand Down Expand Up @@ -49,8 +49,8 @@
"@react-rxjs/core": "^0.10.7",
"@stablelib/base64": "^1.0.1",
"@stablelib/x25519": "^1.0.3",
"@tanstack/query-core": "^4.32.6",
"@tanstack/react-query": "^4.32.6",
"@tanstack/query-core": "^4.36.1",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-virtual": "3.0.0-beta.9",
"@tanstack/virtual-core": "3.0.0-beta.9",
"@tauri-apps/api": "^1.5.3",
Expand Down Expand Up @@ -91,7 +91,6 @@
"react-virtualized-auto-sizer": "^1.0.21",
"react-window": "^1.8.10",
"recharts": "^2.10.4",
"rollup": "^4.9.6",
"rxjs": "^7.8.1",
"terser": "^5.27.0",
"typesafe-i18n": "^5.26.2",
Expand All @@ -104,8 +103,9 @@
"@csstools/css-parser-algorithms": "^2.5.0",
"@csstools/css-tokenizer": "^2.2.3",
"@hookform/devtools": "^4.3.1",
"@rollup/wasm-node": "^4.28.1",
"@svgr/cli": "^8.1.0",
"@tanstack/react-query-devtools": "^4.32.6",
"@tanstack/react-query-devtools": "^4.36.1",
"@types/byte-size": "^8.1.2",
"@types/file-saver": "^2.0.7",
"@types/lodash-es": "^4.17.12",
Expand All @@ -118,6 +118,7 @@
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.17",
"concurrently": "^8.2.2",
"dotenv": "^16.4.7",
"esbuild": "^0.19.12",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
Expand All @@ -131,6 +132,7 @@
"postcss": "^8.4.33",
"prettier": "^3.2.4",
"prop-types": "^15.8.1",
"rollup": "npm:@rollup/wasm-node@^4.28.1",
"rollup-plugin-preserve-directives": "^0.3.1",
"sass": "^1.70.0",
"standard-version": "^9.5.0",
Expand Down
Loading

0 comments on commit d5d602e

Please sign in to comment.