diff --git a/crates/authorization_codes/src/database.rs b/crates/authorization_codes/src/database.rs index 94c104d..064795b 100644 --- a/crates/authorization_codes/src/database.rs +++ b/crates/authorization_codes/src/database.rs @@ -22,6 +22,26 @@ impl From for AuthorizationCode { } impl AuthorizationCode { + pub async fn get_all(conn: impl SqliteExecutor<'_>) -> Result, Error> { + Ok(DatabaseAuthorizationCodes::get_all(conn) + .await? + .into_iter() + .map(Self::from) + .collect::>()) + } + + pub async fn used_in_last_24_hours(conn: impl SqliteExecutor<'_>) -> Result { + let all = Self::get_all(conn).await?; + + let last_24_hours = Utc::now() - Duration::hours(24); + + Ok(all + .into_iter() + .filter_map(|code| code.used_at) + .filter(|&date| date >= last_24_hours) + .count()) + } + pub async fn insert( conn: impl SqliteExecutor<'_>, code: &str, diff --git a/crates/database/queries/authorization_codes/get_all.sql b/crates/database/queries/authorization_codes/get_all.sql new file mode 100644 index 0000000..8090e1f --- /dev/null +++ b/crates/database/queries/authorization_codes/get_all.sql @@ -0,0 +1,7 @@ +select code, + app, + user, + created_at as "created_at: DateTime", + expires_at as "expires_at: DateTime", + used_at as "used_at: DateTime" +from authorization_codes diff --git a/crates/database/sqlx-data.json b/crates/database/sqlx-data.json index 4ba5362..33c88e9 100644 --- a/crates/database/sqlx-data.json +++ b/crates/database/sqlx-data.json @@ -1332,6 +1332,54 @@ }, "query": "update users\n\nset email = ?\n\nwhere id is ?" }, + "c3dcd38a2d4ff391aed4a2ac3f393646319950334494ecb5fa7effe9806d07ab": { + "describe": { + "columns": [ + { + "name": "code", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "app", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "user", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "created_at: DateTime", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "expires_at: DateTime", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "used_at: DateTime", + "ordinal": 5, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + true + ], + "parameters": { + "Right": 0 + } + }, + "query": "select code,\n app,\n user,\n created_at as \"created_at: DateTime\",\n expires_at as \"expires_at: DateTime\",\n used_at as \"used_at: DateTime\"\nfrom authorization_codes\n" + }, "c6157ec3928527ec0ac5f493a5a91faff7e3668204a179e827a87d6279a02c40": { "describe": { "columns": [], diff --git a/crates/database/src/tables/authorization_codes.rs b/crates/database/src/tables/authorization_codes.rs index fcf5817..78f8fdd 100644 --- a/crates/database/src/tables/authorization_codes.rs +++ b/crates/database/src/tables/authorization_codes.rs @@ -17,6 +17,13 @@ pub struct AuthorizationCodes { } impl AuthorizationCodes { + pub async fn get_all(conn: impl SqliteExecutor<'_>) -> Result, Error> { + sqlx::query_file_as!(Self, "queries/authorization_codes/get_all.sql") + .fetch_all(conn) + .await + .map_err(handle_error) + } + pub async fn insert( conn: impl SqliteExecutor<'_>, code: &str, diff --git a/crates/ezidam/src/icons.rs b/crates/ezidam/src/icons.rs index 1cc77b3..cf7901c 100644 --- a/crates/ezidam/src/icons.rs +++ b/crates/ezidam/src/icons.rs @@ -53,7 +53,8 @@ impl Icon { "users-group", UsersGroup, r#""#, "users-group-large", UsersGroupLarge, r#""#, "adjustments", Adjustments, r#""#, - "device-floppy", DeviceFloppy, r#""# + "device-floppy", DeviceFloppy, r#""#, + "login", Login, r#""# } } @@ -88,6 +89,7 @@ pub fn icons_to_templates(tera: &mut Tera) { Icon::UsersGroupLarge, Icon::Adjustments, Icon::DeviceFloppy, + Icon::Login, ]; // For each icon, it will output: ("icons/name", "...") diff --git a/crates/ezidam/src/routes/admin.rs b/crates/ezidam/src/routes/admin.rs index e4fac3d..d38eb52 100644 --- a/crates/ezidam/src/routes/admin.rs +++ b/crates/ezidam/src/routes/admin.rs @@ -69,6 +69,10 @@ pub mod content { #[derive(Clone)] pub struct AdminDashboard { pub user: JwtClaims, + pub users: Vec, + pub roles: Vec, + pub apps: Vec, + pub number_logins_last_24_hours: usize, } #[derive(Serialize)] diff --git a/crates/ezidam/src/routes/admin/dashboard.rs b/crates/ezidam/src/routes/admin/dashboard.rs index 9c44d5e..fdee596 100644 --- a/crates/ezidam/src/routes/admin/dashboard.rs +++ b/crates/ezidam/src/routes/admin/dashboard.rs @@ -1,9 +1,34 @@ use crate::routes::prelude::*; +use apps::App; +use authorization_codes::AuthorizationCode; use rocket::get; +use roles::Role; +use users::User; #[get("/admin")] pub async fn admin_dashboard(mut db: Connection, admin: JwtAdmin) -> Result { + let mut transaction = db.begin().await?; + + // Get users + let users = User::get_all(&mut transaction).await?; + + // Get roles + let roles = Role::get_all(&mut transaction).await?; + + // Get apps + let apps = App::get_all(&mut transaction, None).await?; + + // Get number of logins in the last 24 hours + let number_logins_last_24_hours = + AuthorizationCode::used_in_last_24_hours(&mut transaction).await?; + + transaction.commit().await?; + Ok(Page::AdminDashboard(super::content::AdminDashboard { user: admin.0, + users, + roles, + apps, + number_logins_last_24_hours, })) } diff --git a/crates/ezidam/templates/pages/admin/dashboard.html.tera b/crates/ezidam/templates/pages/admin/dashboard.html.tera index 825b2ad..765b3ed 100644 --- a/crates/ezidam/templates/pages/admin/dashboard.html.tera +++ b/crates/ezidam/templates/pages/admin/dashboard.html.tera @@ -1,4 +1,107 @@ {% extends "shell" %} {% block content %} + + +
+
+ +
+ + +
+
+
+
+
+ + {% include "icons/apps" %} + +
+ +
+
+ {{ apps | length }} Applications +
+
+
+
+
+
+ + +
+
+
+
+
+ + {% include "icons/user" %} + +
+
+
+ {{ users | length }} Users +
+
+
+
+
+
+ + +
+
+
+
+
+ + {% include "icons/users-group" %} + +
+
+
+ {{ roles | length }} Roles +
+
+
+
+
+
+ + +
+
+
+
+
+ + {% include "icons/login" %} + +
+
+
+ {{ number_logins_last_24_hours }} logins in the last 24 hours +
+
+
+
+
+
+
+
+
{% endblock content %}