admin/settings: maintenance page, show database size

This commit is contained in:
Philippe Loctaux 2023-05-06 16:11:16 +02:00
parent 3dfcd542bf
commit ff6c910b2f
13 changed files with 203 additions and 0 deletions

7
Cargo.lock generated
View file

@ -1259,6 +1259,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "human_bytes"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27e2b089f28ad15597b48d8c0a8fe94eeb1c1cb26ca99b6f66ac9582ae10c5e6"
[[package]] [[package]]
name = "humansize" name = "humansize"
version = "2.1.3" version = "2.1.3"
@ -2959,6 +2965,7 @@ version = "0.0.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"database", "database",
"human_bytes",
"id", "id",
"thiserror", "thiserror",
"url", "url",

View file

@ -0,0 +1,2 @@
select page_count * page_size
FROM pragma_page_count(), pragma_page_size();

View file

@ -1598,6 +1598,24 @@
}, },
"query": "select id,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n label,\n redirect_uri,\n secret,\n is_confidential as \"is_confidential: bool\",\n is_archived as \"is_archived: bool\"\nfrom apps\n\nwhere id is (?)\n" "query": "select id,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n label,\n redirect_uri,\n secret,\n is_confidential as \"is_confidential: bool\",\n is_archived as \"is_archived: bool\"\nfrom apps\n\nwhere id is (?)\n"
}, },
"e977ca16c7bd7ec4125725ff5e42d3c547634e2b608d6be91814bce657e07b65": {
"describe": {
"columns": [
{
"name": "page_count * page_size",
"ordinal": 0,
"type_info": "Int"
}
],
"nullable": [
null
],
"parameters": {
"Right": 0
}
},
"query": "select page_count * page_size\nFROM pragma_page_count(), pragma_page_size();"
},
"eaf0744f65a1de803fa8cc21b67bad4bdf22760d431265cf97b911e6456b2fd8": { "eaf0744f65a1de803fa8cc21b67bad4bdf22760d431265cf97b911e6456b2fd8": {
"describe": { "describe": {
"columns": [ "columns": [

View file

@ -31,6 +31,13 @@ impl Settings {
.map_err(handle_error) .map_err(handle_error)
} }
pub async fn database_size(conn: impl SqliteExecutor<'_>) -> Result<Option<i32>, Error> {
sqlx::query_file_scalar!("queries/settings/database_size.sql")
.fetch_one(conn)
.await
.map_err(handle_error)
}
pub async fn set_business_name( pub async fn set_business_name(
conn: impl SqliteExecutor<'_>, conn: impl SqliteExecutor<'_>,
value: Option<&str>, value: Option<&str>,

View file

@ -65,6 +65,10 @@ impl AdminMenu {
label: "Security", label: "Security",
link: uri!(routes::admin::settings::settings_security).to_string(), link: uri!(routes::admin::settings::settings_security).to_string(),
}, },
SubItem {
label: "Maintenance",
link: uri!(routes::admin::settings::settings_maintenance).to_string(),
},
]), ]),
}, },
] ]

View file

@ -18,6 +18,7 @@ pub enum Page {
AdminDashboard(AdminDashboard), AdminDashboard(AdminDashboard),
AdminSettingsBranding(AdminSettingsBranding), AdminSettingsBranding(AdminSettingsBranding),
AdminSettingsSecurity(AdminSettingsSecurity), AdminSettingsSecurity(AdminSettingsSecurity),
AdminSettingsMaintenance(AdminSettingsMaintenance),
AdminAppsList(AdminAppsList), AdminAppsList(AdminAppsList),
AdminAppsNew(AdminAppsNew), AdminAppsNew(AdminAppsNew),
AdminAppsView(AdminAppsView), AdminAppsView(AdminAppsView),
@ -45,6 +46,7 @@ impl Page {
Page::AdminDashboard(_) => "pages/admin/dashboard", Page::AdminDashboard(_) => "pages/admin/dashboard",
Page::AdminSettingsBranding(_) => "pages/admin/settings/branding", Page::AdminSettingsBranding(_) => "pages/admin/settings/branding",
Page::AdminSettingsSecurity(_) => "pages/admin/settings/security", Page::AdminSettingsSecurity(_) => "pages/admin/settings/security",
Page::AdminSettingsMaintenance(_) => "pages/admin/settings/maintenance",
Page::AdminAppsList(_) => "pages/admin/apps/list", Page::AdminAppsList(_) => "pages/admin/apps/list",
Page::AdminAppsNew(_) => "pages/admin/apps/new", Page::AdminAppsNew(_) => "pages/admin/apps/new",
Page::AdminAppsView(_) => "pages/admin/apps/view", Page::AdminAppsView(_) => "pages/admin/apps/view",
@ -72,6 +74,7 @@ impl Page {
Page::AdminDashboard(_) => "Admin dashboard", Page::AdminDashboard(_) => "Admin dashboard",
Page::AdminSettingsBranding(_) => "Server branding", Page::AdminSettingsBranding(_) => "Server branding",
Page::AdminSettingsSecurity(_) => "Server security", Page::AdminSettingsSecurity(_) => "Server security",
Page::AdminSettingsMaintenance(_) => "Server maintenance",
Page::AdminAppsList(_) => "Applications", Page::AdminAppsList(_) => "Applications",
Page::AdminAppsNew(_) => "New application", Page::AdminAppsNew(_) => "New application",
Page::AdminAppsView(_) => "Application info", Page::AdminAppsView(_) => "Application info",
@ -101,6 +104,7 @@ impl Page {
Page::AdminDashboard(_) => Some(AdminMenu::Dashboard.into()), Page::AdminDashboard(_) => Some(AdminMenu::Dashboard.into()),
Page::AdminSettingsBranding(_) => Some(AdminMenu::Settings.into()), Page::AdminSettingsBranding(_) => Some(AdminMenu::Settings.into()),
Page::AdminSettingsSecurity(_) => Some(AdminMenu::Settings.into()), Page::AdminSettingsSecurity(_) => Some(AdminMenu::Settings.into()),
Page::AdminSettingsMaintenance(_) => Some(AdminMenu::Settings.into()),
Page::AdminAppsList(_) => Some(AdminMenu::Apps.into()), Page::AdminAppsList(_) => Some(AdminMenu::Apps.into()),
Page::AdminAppsNew(_) => Some(AdminMenu::Apps.into()), Page::AdminAppsNew(_) => Some(AdminMenu::Apps.into()),
Page::AdminAppsView(_) => Some(AdminMenu::Apps.into()), Page::AdminAppsView(_) => Some(AdminMenu::Apps.into()),
@ -128,6 +132,7 @@ impl Page {
Page::AdminDashboard(dashboard) => Box::new(dashboard), Page::AdminDashboard(dashboard) => Box::new(dashboard),
Page::AdminSettingsBranding(branding) => Box::new(branding), Page::AdminSettingsBranding(branding) => Box::new(branding),
Page::AdminSettingsSecurity(security) => Box::new(security), Page::AdminSettingsSecurity(security) => Box::new(security),
Page::AdminSettingsMaintenance(maintenance) => Box::new(maintenance),
Page::AdminAppsList(list) => Box::new(list), Page::AdminAppsList(list) => Box::new(list),
Page::AdminAppsNew(new) => Box::new(new), Page::AdminAppsNew(new) => Box::new(new),
Page::AdminAppsView(view) => Box::new(view), Page::AdminAppsView(view) => Box::new(view),

View file

@ -16,6 +16,7 @@ pub fn routes() -> Vec<Route> {
settings_update_branding, settings_update_branding,
settings_security, settings_security,
settings_security_form, settings_security_form,
settings_maintenance,
admin_apps_list, admin_apps_list,
admin_apps_new, admin_apps_new,
admin_apps_new_form, admin_apps_new_form,
@ -65,6 +66,14 @@ pub mod content {
pub user: JwtClaims, pub user: JwtClaims,
} }
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
#[derive(Clone)]
pub struct AdminSettingsMaintenance {
pub user: JwtClaims,
pub database_size: String,
}
#[derive(Serialize)] #[derive(Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
#[derive(Clone)] #[derive(Clone)]

View file

@ -185,3 +185,27 @@ pub async fn settings_security_form(
flash_message, flash_message,
)) ))
} }
#[get("/admin/settings/maintenance")]
pub async fn settings_maintenance(
mut db: Connection<Database>,
admin: JwtAdmin,
flash: Option<FlashMessage<'_>>,
) -> Result<Template> {
let mut transaction = db.begin().await?;
let database_size = Settings::database_size(&mut transaction)
.await?
.ok_or_else(|| Error::internal_server_error("Failed to get database size"))?;
transaction.commit().await?;
let page = Page::AdminSettingsMaintenance(super::content::AdminSettingsMaintenance {
user: admin.0,
database_size,
});
Ok(flash
.map(|flash| Page::with_flash(page.clone(), flash))
.unwrap_or_else(|| page.into()))
}

View file

@ -25,6 +25,8 @@
class="list-group-item list-group-item-action d-flex align-items-center active">Branding</a> class="list-group-item list-group-item-action d-flex align-items-center active">Branding</a>
<a href="./security" <a href="./security"
class="list-group-item list-group-item-action d-flex align-items-center">Security</a> class="list-group-item list-group-item-action d-flex align-items-center">Security</a>
<a href="./maintenance"
class="list-group-item list-group-item-action d-flex align-items-center">Maintenance</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,115 @@
{% extends "shell" %}
{% block content %}
<!-- Page header -->
<div class="page-header d-print-none">
<div class="container-xl">
<div class="row g-2 align-items-center">
<div class="col">
<h2 class="page-title">
Server settings
</h2>
</div>
</div>
</div>
</div>
<!-- Page body -->
<div class="page-body">
<div class="container-xl">
{% if flash %}
<div class="alert alert-{{flash.0}}" role="alert">
<h4 class="alert-title">{{ flash.1 | safe }}</h4>
</div>
{% endif %}
<div class="card">
<div class="row g-0">
<div class="col-3 d-none d-md-block border-end">
<div class="card-body">
<div class="list-group list-group-transparent">
<a href="./branding"
class="list-group-item list-group-item-action d-flex align-items-center">Branding</a>
<a href="./security"
class="list-group-item list-group-item-action d-flex align-items-center">Security</a>
<a href="./maintenance"
class="list-group-item list-group-item-action d-flex align-items-center active">Maintenance</a>
</div>
</div>
</div>
<div class="col d-flex flex-column">
<div class="card-body">
<h2 class="mb-4">Maintenance</h2>
<h3 class="card-title">Database size</h3>
<p>{{ database_size }}</p>
<div class="row align-items-center mb-4">
<!-- Clean database -->
<div class="col-auto">
<a class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#modal-clean-database">
Clean database
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Clean database modal -->
<div class="modal modal-blur" tabindex="-1" id="modal-clean-database">
<div class="modal-dialog modal-sm modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-status bg-danger"></div>
<div class="modal-body text-center py-4">
<div class="text-danger mb-2">
{% include "icons/alert-triangle-large" %}
</div>
<h3>Do you want to clean the database?</h3>
<div class="mt-2">
This action will <strong>delete</strong> the following:
</div>
<ul class="list-inline mt-1">
<li>Authorization codes</li>
<li>Refresh tokens</li>
<li>TOTP requests</li>
</ul>
<div class="mt-2">
This action will be permanent.
</div>
</div>
<div class="modal-footer">
<div class="w-100">
<div class="row">
<div class="col">
<a href="#" class="btn w-100" data-bs-dismiss="modal">Cancel</a>
</div>
<div class="col">
<form action="" method="post">
<button type="submit" name="clean" value="true" class="btn btn-danger w-100">
Clean database
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View file

@ -32,6 +32,8 @@
class="list-group-item list-group-item-action d-flex align-items-center">Branding</a> class="list-group-item list-group-item-action d-flex align-items-center">Branding</a>
<a href="./security" <a href="./security"
class="list-group-item list-group-item-action d-flex align-items-center active">Security</a> class="list-group-item list-group-item-action d-flex align-items-center active">Security</a>
<a href="./maintenance"
class="list-group-item list-group-item-action d-flex align-items-center">Maintenance</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -9,3 +9,4 @@ id = { path = "../id" }
thiserror = { workspace = true } thiserror = { workspace = true }
chrono = { workspace = true } chrono = { workspace = true }
url = { workspace = true } url = { workspace = true }
human_bytes = { version = "0.4.1", default-features = false }

View file

@ -2,6 +2,7 @@ use crate::error::Error;
use crate::Settings; use crate::Settings;
use database::sqlx::SqliteExecutor; use database::sqlx::SqliteExecutor;
use database::Settings as DatabaseSettings; use database::Settings as DatabaseSettings;
use human_bytes::human_bytes;
use id::UserID; use id::UserID;
use url::Url; use url::Url;
@ -34,6 +35,12 @@ impl Settings {
Ok(DatabaseSettings::get(conn).await.map(Self::from)?) Ok(DatabaseSettings::get(conn).await.map(Self::from)?)
} }
pub async fn database_size(conn: impl SqliteExecutor<'_>) -> Result<Option<String>, Error> {
Ok(DatabaseSettings::database_size(conn)
.await?
.map(human_bytes))
}
pub async fn set_business_name( pub async fn set_business_name(
conn: impl SqliteExecutor<'_>, conn: impl SqliteExecutor<'_>,
business_name: &str, business_name: &str,