aboutsummaryrefslogtreecommitdiff
path: root/server/src/ui/admin/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/ui/admin/mod.rs')
-rw-r--r--server/src/ui/admin/mod.rs283
1 files changed, 127 insertions, 156 deletions
diff --git a/server/src/ui/admin/mod.rs b/server/src/ui/admin/mod.rs
index d380ae2..b155121 100644
--- a/server/src/ui/admin/mod.rs
+++ b/server/src/ui/admin/mod.rs
@@ -10,108 +10,74 @@ use super::{
assets::{resolve_asset, AVIF_QUALITY, AVIF_SPEED},
error::MyResult,
};
-use crate::{database::Database, logic::session::AdminSession};
+use crate::{database::Database, locale::AcceptLanguage};
use anyhow::{anyhow, Context};
use jellybase::{assetfed::AssetInner, federation::Federation, CONF};
-use jellyimport::{import_wrap, IMPORT_ERRORS};
+use jellycommon::routes::u_admin_dashboard;
+use jellyimport::{import_wrap, is_importing, IMPORT_ERRORS};
+use jellylogic::session::AdminSession;
+use jellyui::{
+ admin::AdminDashboardPage,
+ render_page,
+ scaffold::{RenderInfo, SessionInfo},
+};
use rand::Rng;
-use rocket::{form::Form, get, post, FromForm, State};
+use rocket::{
+ form::Form,
+ get, post,
+ response::{content::RawHtml, Redirect},
+ FromForm, State,
+};
use std::time::Instant;
use tokio::{sync::Semaphore, task::spawn_blocking};
#[get("/admin/dashboard")]
pub async fn r_admin_dashboard(
- _session: AdminSession,
+ session: AdminSession,
database: &State<Database>,
-) -> MyResult<DynLayoutPage<'static>> {
- admin_dashboard(database, None).await
-}
-
-pub async fn admin_dashboard<'a>(
- database: &Database,
- flash: Option<MyResult<String>>,
-) -> MyResult<DynLayoutPage<'a>> {
+ lang: AcceptLanguage,
+) -> MyResult<RawHtml<String>> {
+ let AcceptLanguage(lang) = lang;
let invites = database.list_invites()?;
- let flash = flash.map(|f| f.map_err(|e| format!("{e:?}")));
+ let flash = None;
let last_import_err = IMPORT_ERRORS.read().await.to_owned();
- let database = database.to_owned();
- Ok(LayoutPage {
- title: "Admin Dashboard".to_string(),
- content: markup::new! {
- h1 { "Admin Panel" }
- @FlashDisplay { flash: flash.clone() }
- @if !last_import_err.is_empty() {
- section.message.error {
- details {
- summary { p.error { @format!("The last import resulted in {} errors:", last_import_err.len()) } }
- ol { @for e in &last_import_err {
- li.error { pre.error { @e } }
- }}
- }
- }
- }
- ul {
- li{a[href=uri!(r_admin_log(true))] { "Server Log (Warnings only)" }}
- li{a[href=uri!(r_admin_log(false))] { "Server Log (Full) " }}
- }
- h2 { "Library" }
- @if is_importing() {
- section.message { p.warn { "An import is currently running." } }
- }
- @if is_transcoding() {
- section.message { p.warn { "Currently transcoding posters." } }
- }
- form[method="POST", action=uri!(r_admin_import(true))] {
- input[type="submit", disabled=is_importing(), value="Start incremental import"];
- }
- form[method="POST", action=uri!(r_admin_import(false))] {
- input[type="submit", disabled=is_importing(), value="Start full import"];
- }
- form[method="POST", action=uri!(r_admin_transcode_posters())] {
- input[type="submit", disabled=is_transcoding(), value="Transcode all posters with low resolution"];
- }
- form[method="POST", action=uri!(r_admin_update_search())] {
- input[type="submit", value="Regenerate full-text search index"];
- }
- form[method="POST", action=uri!(r_admin_delete_cache())] {
- input.danger[type="submit", value="Delete Cache"];
- }
- h2 { "Users" }
- p { a[href=uri!(r_admin_users())] "Manage Users" }
- h2 { "Invitations" }
- form[method="POST", action=uri!(r_admin_invite())] {
- input[type="submit", value="Generate new invite code"];
- }
- ul { @for t in &invites {
- li {
- form[method="POST", action=uri!(r_admin_remove_invite())] {
- span { @t }
- input[type="text", name="invite", value=&t, hidden];
- input[type="submit", value="Invalidate"];
- }
- }
- }}
+ let busy = if is_importing() {
+ Some("An import is currently running.")
+ } else if is_transcoding() {
+ Some("Currently transcoding posters.")
+ } else {
+ None
+ };
- h2 { "Database" }
- @match db_stats(&database) {
- Ok(s) => { @s }
- Err(e) => { pre.error { @format!("{e:?}") } }
- }
+ Ok(RawHtml(render_page(
+ &AdminDashboardPage {
+ busy,
+ last_import_err: &last_import_err,
+ invites: &invites,
+ flash,
+ lang: &lang,
},
- ..Default::default()
- })
+ RenderInfo {
+ importing: is_importing(),
+ session: Some(SessionInfo {
+ user: session.0.user,
+ }),
+ },
+ lang,
+ )))
}
#[post("/admin/generate_invite")]
pub async fn r_admin_invite(
_session: AdminSession,
database: &State<Database>,
-) -> MyResult<DynLayoutPage<'static>> {
+) -> MyResult<Redirect> {
let i = format!("{}", rand::rng().random::<u128>());
database.create_invite(&i)?;
- admin_dashboard(database, Some(Ok(format!("Invite: {}", i)))).await
+ // admin_dashboard(database, Some(Ok(format!("Invite: {}", i)))).await
+ Ok(Redirect::temporary(u_admin_dashboard()))
}
#[derive(FromForm)]
@@ -124,12 +90,13 @@ pub async fn r_admin_remove_invite(
session: AdminSession,
database: &State<Database>,
form: Form<DeleteInvite>,
-) -> MyResult<DynLayoutPage<'static>> {
+) -> MyResult<Redirect> {
drop(session);
if !database.delete_invite(&form.invite)? {
Err(anyhow!("invite does not exist"))?;
};
- admin_dashboard(database, Some(Ok("Invite invalidated".into()))).await
+ // admin_dashboard(database, Some(Ok("Invite invalidated".into()))).await
+ Ok(Redirect::temporary(u_admin_dashboard()))
}
#[post("/admin/import?<incremental>")]
@@ -138,55 +105,58 @@ pub async fn r_admin_import(
database: &State<Database>,
_federation: &State<Federation>,
incremental: bool,
-) -> MyResult<DynLayoutPage<'static>> {
+) -> MyResult<Redirect> {
drop(session);
let t = Instant::now();
if !incremental {
database.clear_nodes()?;
}
let r = import_wrap((*database).clone(), incremental).await;
- let flash = r
- .map_err(|e| e.into())
- .map(|_| format!("Import successful; took {:?}", t.elapsed()));
- admin_dashboard(database, Some(flash)).await
+ // let flash = r
+ // .map_err(|e| e.into())
+ // .map(|_| format!("Import successful; took {:?}", t.elapsed()));
+ // admin_dashboard(database, Some(flash)).await
+ Ok(Redirect::temporary(u_admin_dashboard()))
}
#[post("/admin/update_search")]
pub async fn r_admin_update_search(
_session: AdminSession,
database: &State<Database>,
-) -> MyResult<DynLayoutPage<'static>> {
+) -> MyResult<Redirect> {
let db2 = (*database).clone();
let r = spawn_blocking(move || db2.search_create_index())
.await
.unwrap();
- admin_dashboard(
- database,
- Some(
- r.map_err(|e| e.into())
- .map(|_| "Search index updated".to_string()),
- ),
- )
- .await
+ // admin_dashboard(
+ // database,
+ // Some(
+ // r.map_err(|e| e.into())
+ // .map(|_| "Search index updated".to_string()),
+ // ),
+ // )
+ // .await
+ Ok(Redirect::temporary(u_admin_dashboard()))
}
#[post("/admin/delete_cache")]
pub async fn r_admin_delete_cache(
session: AdminSession,
database: &State<Database>,
-) -> MyResult<DynLayoutPage<'static>> {
+) -> MyResult<Redirect> {
drop(session);
let t = Instant::now();
let r = tokio::fs::remove_dir_all(&CONF.cache_path).await;
tokio::fs::create_dir(&CONF.cache_path).await?;
- admin_dashboard(
- database,
- Some(
- r.map_err(|e| e.into())
- .map(|_| format!("Cache deleted; took {:?}", t.elapsed())),
- ),
- )
- .await
+ // admin_dashboard(
+ // database,
+ // Some(
+ // r.map_err(|e| e.into())
+ // .map(|_| format!("Cache deleted; took {:?}", t.elapsed())),
+ // ),
+ // )
+ // .await
+ Ok(Redirect::temporary(u_admin_dashboard()))
}
static SEM_TRANSCODING: Semaphore = Semaphore::const_new(1);
@@ -198,7 +168,7 @@ fn is_transcoding() -> bool {
pub async fn r_admin_transcode_posters(
session: AdminSession,
database: &State<Database>,
-) -> MyResult<DynLayoutPage<'static>> {
+) -> MyResult<Redirect> {
drop(session);
let _permit = SEM_TRANSCODING
.try_acquire()
@@ -223,58 +193,59 @@ pub async fn r_admin_transcode_posters(
}
drop(_permit);
- admin_dashboard(
- database,
- Some(Ok(format!(
- "All posters pre-transcoded; took {:?}",
- t.elapsed()
- ))),
- )
- .await
+ // admin_dashboard(
+ // database,
+ // Some(Ok(format!(
+ // "All posters pre-transcoded; took {:?}",
+ // t.elapsed()
+ // ))),
+ // )
+ // .await
+ Ok(Redirect::temporary(u_admin_dashboard()))
}
-fn db_stats(_db: &Database) -> anyhow::Result<DynRender> {
- // TODO
- // let txn = db.inner.begin_read()?;
- // let stats = [
- // ("node", txn.open_table(T_NODE)?.stats()?),
- // ("user", txn.open_table(T_USER_NODE)?.stats()?),
- // ("user-node", txn.open_table(T_USER_NODE)?.stats()?),
- // ("invite", txn.open_table(T_INVITE)?.stats()?),
- // ];
+// fn db_stats(_db: &Database) -> anyhow::Result<DynRender> {
+// // TODO
+// // let txn = db.inner.begin_read()?;
+// // let stats = [
+// // ("node", txn.open_table(T_NODE)?.stats()?),
+// // ("user", txn.open_table(T_USER_NODE)?.stats()?),
+// // ("user-node", txn.open_table(T_USER_NODE)?.stats()?),
+// // ("invite", txn.open_table(T_INVITE)?.stats()?),
+// // ];
- // let cache_stats = db.node_index.reader.searcher().doc_store_cache_stats();
- // let ft_total_docs = db.node_index.reader.searcher().total_num_docs()?;
+// // let cache_stats = db.node_index.reader.searcher().doc_store_cache_stats();
+// // let ft_total_docs = db.node_index.reader.searcher().total_num_docs()?;
- Ok(markup::new! {
- // h3 { "Key-Value-Store Statistics" }
- // table.border {
- // tbody {
- // tr {
- // th { "table name" }
- // th { "tree height" }
- // th { "stored bytes" }
- // th { "metadata bytes" }
- // th { "fragmented bytes" }
- // th { "branch pages" }
- // th { "leaf pages" }
- // }
- // @for (name, stats) in &stats { tr {
- // td { @name }
- // td { @stats.tree_height() }
- // td { @format_size(stats.stored_bytes(), DECIMAL) }
- // td { @format_size(stats.metadata_bytes(), DECIMAL) }
- // td { @format_size(stats.fragmented_bytes(), DECIMAL) }
- // td { @stats.branch_pages() }
- // td { @stats.leaf_pages() }
- // }}
- // }
- // }
- // h3 { "Search Engine Statistics" }
- // ul {
- // li { "Total documents: " @ft_total_docs }
- // li { "Cache misses: " @cache_stats.cache_misses }
- // li { "Cache hits: " @cache_stats.cache_hits }
- // }
- })
-}
+// Ok(markup::new! {
+// // h3 { "Key-Value-Store Statistics" }
+// // table.border {
+// // tbody {
+// // tr {
+// // th { "table name" }
+// // th { "tree height" }
+// // th { "stored bytes" }
+// // th { "metadata bytes" }
+// // th { "fragmented bytes" }
+// // th { "branch pages" }
+// // th { "leaf pages" }
+// // }
+// // @for (name, stats) in &stats { tr {
+// // td { @name }
+// // td { @stats.tree_height() }
+// // td { @format_size(stats.stored_bytes(), DECIMAL) }
+// // td { @format_size(stats.metadata_bytes(), DECIMAL) }
+// // td { @format_size(stats.fragmented_bytes(), DECIMAL) }
+// // td { @stats.branch_pages() }
+// // td { @stats.leaf_pages() }
+// // }}
+// // }
+// // }
+// // h3 { "Search Engine Statistics" }
+// // ul {
+// // li { "Total documents: " @ft_total_docs }
+// // li { "Cache misses: " @cache_stats.cache_misses }
+// // li { "Cache hits: " @cache_stats.cache_hits }
+// // }
+// })
+// }