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

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"
},
"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": {
"describe": {
"columns": [

View file

@ -31,6 +31,13 @@ impl Settings {
.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(
conn: impl SqliteExecutor<'_>,
value: Option<&str>,

View file

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

View file

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

View file

@ -185,3 +185,27 @@ pub async fn settings_security_form(
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>
<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">Maintenance</a>
</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>
<a href="./security"
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>

View file

@ -9,3 +9,4 @@ id = { path = "../id" }
thiserror = { workspace = true }
chrono = { 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 database::sqlx::SqliteExecutor;
use database::Settings as DatabaseSettings;
use human_bytes::human_bytes;
use id::UserID;
use url::Url;
@ -34,6 +35,12 @@ impl Settings {
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(
conn: impl SqliteExecutor<'_>,
business_name: &str,