aboutsummaryrefslogtreecommitdiff
path: root/server/src/routes/ui/admin/mod.rs
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-04-27 19:25:11 +0200
committermetamuffin <metamuffin@disroot.org>2025-04-27 19:25:11 +0200
commit11a585b3dbe620dcc8772e713b22f1d9ba80d598 (patch)
tree44f8d97137412aefc79a2425a489c34fa3e5f6c5 /server/src/routes/ui/admin/mod.rs
parentd871aa7c5bba49ff55170b5d2dac9cd440ae7170 (diff)
downloadjellything-11a585b3dbe620dcc8772e713b22f1d9ba80d598.tar
jellything-11a585b3dbe620dcc8772e713b22f1d9ba80d598.tar.bz2
jellything-11a585b3dbe620dcc8772e713b22f1d9ba80d598.tar.zst
move files around
Diffstat (limited to 'server/src/routes/ui/admin/mod.rs')
-rw-r--r--server/src/routes/ui/admin/mod.rs290
1 files changed, 0 insertions, 290 deletions
diff --git a/server/src/routes/ui/admin/mod.rs b/server/src/routes/ui/admin/mod.rs
deleted file mode 100644
index f44b36c..0000000
--- a/server/src/routes/ui/admin/mod.rs
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- This file is part of jellything (https://codeberg.org/metamuffin/jellything)
- which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
- Copyright (C) 2025 metamuffin <metamuffin.org>
-*/
-pub mod log;
-pub mod user;
-
-use super::{
- account::session::AdminSession,
- assets::{resolve_asset, AVIF_QUALITY, AVIF_SPEED},
-};
-use crate::{
- database::Database,
- routes::ui::{
- admin::log::rocket_uri_macro_r_admin_log,
- error::MyResult,
- layout::{DynLayoutPage, FlashDisplay, LayoutPage},
- },
- uri,
-};
-use anyhow::{anyhow, Context};
-use jellybase::{assetfed::AssetInner, federation::Federation, CONF};
-use jellyimport::{import_wrap, is_importing, IMPORT_ERRORS};
-use markup::DynRender;
-use rand::Rng;
-use rocket::{form::Form, get, post, FromForm, State};
-use std::time::Instant;
-use tokio::{sync::Semaphore, task::spawn_blocking};
-use user::rocket_uri_macro_r_admin_users;
-
-#[get("/admin/dashboard")]
-pub async fn r_admin_dashboard(
- _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>> {
- let invites = database.list_invites()?;
- let flash = flash.map(|f| f.map_err(|e| format!("{e:?}")));
-
- 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"];
- }
- }
- }}
-
- h2 { "Database" }
- @match db_stats(&database) {
- Ok(s) => { @s }
- Err(e) => { pre.error { @format!("{e:?}") } }
- }
- },
- ..Default::default()
- })
-}
-
-#[post("/admin/generate_invite")]
-pub async fn r_admin_invite(
- _session: AdminSession,
- database: &State<Database>,
-) -> MyResult<DynLayoutPage<'static>> {
- let i = format!("{}", rand::rng().random::<u128>());
- database.create_invite(&i)?;
- admin_dashboard(database, Some(Ok(format!("Invite: {}", i)))).await
-}
-
-#[derive(FromForm)]
-pub struct DeleteInvite {
- invite: String,
-}
-
-#[post("/admin/remove_invite", data = "<form>")]
-pub async fn r_admin_remove_invite(
- session: AdminSession,
- database: &State<Database>,
- form: Form<DeleteInvite>,
-) -> MyResult<DynLayoutPage<'static>> {
- drop(session);
- if !database.delete_invite(&form.invite)? {
- Err(anyhow!("invite does not exist"))?;
- };
- admin_dashboard(database, Some(Ok("Invite invalidated".into()))).await
-}
-
-#[post("/admin/import?<incremental>")]
-pub async fn r_admin_import(
- session: AdminSession,
- database: &State<Database>,
- _federation: &State<Federation>,
- incremental: bool,
-) -> MyResult<DynLayoutPage<'static>> {
- 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
-}
-
-#[post("/admin/update_search")]
-pub async fn r_admin_update_search(
- _session: AdminSession,
- database: &State<Database>,
-) -> MyResult<DynLayoutPage<'static>> {
- 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
-}
-
-#[post("/admin/delete_cache")]
-pub async fn r_admin_delete_cache(
- session: AdminSession,
- database: &State<Database>,
-) -> MyResult<DynLayoutPage<'static>> {
- 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
-}
-
-static SEM_TRANSCODING: Semaphore = Semaphore::const_new(1);
-fn is_transcoding() -> bool {
- SEM_TRANSCODING.available_permits() == 0
-}
-
-#[post("/admin/transcode_posters")]
-pub async fn r_admin_transcode_posters(
- session: AdminSession,
- database: &State<Database>,
-) -> MyResult<DynLayoutPage<'static>> {
- drop(session);
- let _permit = SEM_TRANSCODING
- .try_acquire()
- .context("transcoding in progress")?;
-
- let t = Instant::now();
-
- {
- let nodes = database.list_nodes_with_udata("")?;
- for (node, _) in nodes {
- if let Some(poster) = &node.poster {
- let asset = AssetInner::deser(&poster.0)?;
- if asset.is_federated() {
- continue;
- }
- let source = resolve_asset(asset).await.context("resolving asset")?;
- jellytranscoder::image::transcode(&source, AVIF_QUALITY, AVIF_SPEED, 1024)
- .await
- .context("transcoding asset")?;
- }
- }
- }
- drop(_permit);
-
- admin_dashboard(
- database,
- Some(Ok(format!(
- "All posters pre-transcoded; took {:?}",
- t.elapsed()
- ))),
- )
- .await
-}
-
-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()?;
-
- 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 }
- // }
- })
-}