admin dashboard: apps, users, roles, logins in the last 24 hours
This commit is contained in:
parent
264eee9044
commit
a05646a19b
8 changed files with 217 additions and 1 deletions
|
|
@ -22,6 +22,26 @@ impl From<DatabaseAuthorizationCodes> for AuthorizationCode {
|
|||
}
|
||||
|
||||
impl AuthorizationCode {
|
||||
pub async fn get_all(conn: impl SqliteExecutor<'_>) -> Result<Vec<Self>, Error> {
|
||||
Ok(DatabaseAuthorizationCodes::get_all(conn)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Self::from)
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
pub async fn used_in_last_24_hours(conn: impl SqliteExecutor<'_>) -> Result<usize, Error> {
|
||||
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,
|
||||
|
|
|
|||
7
crates/database/queries/authorization_codes/get_all.sql
Normal file
7
crates/database/queries/authorization_codes/get_all.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
select code,
|
||||
app,
|
||||
user,
|
||||
created_at as "created_at: DateTime<Utc>",
|
||||
expires_at as "expires_at: DateTime<Utc>",
|
||||
used_at as "used_at: DateTime<Utc>"
|
||||
from authorization_codes
|
||||
|
|
@ -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<Utc>",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "expires_at: DateTime<Utc>",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "used_at: DateTime<Utc>",
|
||||
"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<Utc>\",\n expires_at as \"expires_at: DateTime<Utc>\",\n used_at as \"used_at: DateTime<Utc>\"\nfrom authorization_codes\n"
|
||||
},
|
||||
"c6157ec3928527ec0ac5f493a5a91faff7e3668204a179e827a87d6279a02c40": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@ pub struct AuthorizationCodes {
|
|||
}
|
||||
|
||||
impl AuthorizationCodes {
|
||||
pub async fn get_all(conn: impl SqliteExecutor<'_>) -> Result<Vec<Self>, 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,
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ impl Icon {
|
|||
"users-group", UsersGroup, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-users-group" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1"></path><path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M17 10h2a2 2 0 0 1 2 2v1"></path><path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M3 13v-1a2 2 0 0 1 2 -2h2"></path></svg>"#,
|
||||
"users-group-large", UsersGroupLarge, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-lg icon-tabler icon-tabler-users-group" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1"></path><path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M17 10h2a2 2 0 0 1 2 2v1"></path><path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M3 13v-1a2 2 0 0 1 2 -2h2"></path></svg>"#,
|
||||
"adjustments", Adjustments, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-adjustments" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 10a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M6 4v4"></path><path d="M6 12v8"></path><path d="M10 16a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M12 4v10"></path><path d="M12 18v2"></path><path d="M16 7a2 2 0 1 0 4 0a2 2 0 0 0 -4 0"></path><path d="M18 4v1"></path><path d="M18 9v11"></path></svg>"#,
|
||||
"device-floppy", DeviceFloppy, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-device-floppy" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M6 4h10l4 4v10a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2"></path><path d="M12 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path><path d="M14 4l0 4l-6 0l0 -4"></path></svg>"#
|
||||
"device-floppy", DeviceFloppy, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-device-floppy" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M6 4h10l4 4v10a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2"></path><path d="M12 14m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path><path d="M14 4l0 4l-6 0l0 -4"></path></svg>"#,
|
||||
"login", Login, r#"<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-login" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M14 8v-2a2 2 0 0 0 -2 -2h-7a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2 -2v-2"></path><path d="M20 12h-13l3 -3m0 6l-3 -3"></path></svg>"#
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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", "<svg>...</svg>")
|
||||
|
|
|
|||
|
|
@ -69,6 +69,10 @@ pub mod content {
|
|||
#[derive(Clone)]
|
||||
pub struct AdminDashboard {
|
||||
pub user: JwtClaims,
|
||||
pub users: Vec<User>,
|
||||
pub roles: Vec<Role>,
|
||||
pub apps: Vec<App>,
|
||||
pub number_logins_last_24_hours: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
|
|
|||
|
|
@ -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<Database>, admin: JwtAdmin) -> Result<Page> {
|
||||
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,
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,107 @@
|
|||
{% extends "shell" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<div class="page-pretitle">
|
||||
Admin panel
|
||||
</div>
|
||||
<h2 class="page-title">
|
||||
Dashboard
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
|
||||
<div class="row row-cards">
|
||||
|
||||
<!-- Apps -->
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-primary text-white avatar">
|
||||
{% include "icons/apps" %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="font-weight-medium">
|
||||
{{ apps | length }} Applications
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Users -->
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-green text-white avatar">
|
||||
{% include "icons/user" %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="font-weight-medium">
|
||||
{{ users | length }} Users
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Roles -->
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-red text-white avatar">
|
||||
{% include "icons/users-group" %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="font-weight-medium">
|
||||
{{ roles | length }} Roles
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Number of logins today -->
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<span class="bg-yellow text-white avatar">
|
||||
{% include "icons/login" %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="font-weight-medium">
|
||||
{{ number_logins_last_24_hours }} logins in the last 24 hours
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue