diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-02-16 17:50:57 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-02-16 17:50:57 +0100 |
| commit | be4af57d75cc1e233b4714b18198fb7bde49464d (patch) | |
| tree | 6e529e748fc1d5c212dcf8033c8158630ca4f4d6 | |
| parent | bb1822e3e68fe6f699102bfc1659731bdbac1a40 (diff) | |
| download | jellything-be4af57d75cc1e233b4714b18198fb7bde49464d.tar jellything-be4af57d75cc1e233b4714b18198fb7bde49464d.tar.bz2 jellything-be4af57d75cc1e233b4714b18198fb7bde49464d.tar.zst | |
refactor ui responder; add admin import pages
| -rw-r--r-- | common/src/api.rs | 7 | ||||
| -rw-r--r-- | common/src/lib.rs | 2 | ||||
| -rw-r--r-- | server/src/main.rs | 5 | ||||
| -rw-r--r-- | server/src/request_info.rs | 14 | ||||
| -rw-r--r-- | server/src/routes.rs | 16 | ||||
| -rw-r--r-- | server/src/ui/account/mod.rs | 27 | ||||
| -rw-r--r-- | server/src/ui/admin/import.rs | 89 | ||||
| -rw-r--r-- | server/src/ui/admin/mod.rs | 78 | ||||
| -rw-r--r-- | server/src/ui/home.rs | 12 | ||||
| -rw-r--r-- | server/src/ui/mod.rs | 43 | ||||
| -rw-r--r-- | server/src/ui/node.rs | 11 | ||||
| -rw-r--r-- | server/src/ui_responder.rs | 43 | ||||
| -rw-r--r-- | ui/src/components/admin.rs (renamed from ui/src/old/admin/import.rs) | 38 | ||||
| -rw-r--r-- | ui/src/components/mod.rs | 8 | ||||
| -rw-r--r-- | ui/src/lib.rs | 2 | ||||
| -rw-r--r-- | ui/src/old/admin/mod.rs | 64 |
16 files changed, 200 insertions, 259 deletions
diff --git a/common/src/api.rs b/common/src/api.rs index 90efc76..c25405d 100644 --- a/common/src/api.rs +++ b/common/src/api.rs @@ -23,6 +23,11 @@ fields! { VIEW_ACCOUNT_LOGIN: () = 2043 "account_login"; VIEW_ACCOUNT_LOGOUT: () = 2044 "account_logout"; VIEW_ACCOUNT_SET_PASSWORD: &str = 2045 "account_set_password"; + VIEW_ADMIN_DASHBOARD: () = 2046 "admin_dashboard"; + VIEW_ADMIN_IMPORT: Object = 2047 "admin_import"; + + ADMIN_IMPORT_BUSY: () = 2048 "busy"; + ADMIN_IMPORT_ERROR: &str = 2049 "error"; // multi NKU_NODE: Object = 2025 "node"; NKU_UDATA: Object = 2026 "udata"; @@ -49,7 +54,7 @@ fields! { enums! { NLSTYLE_GRID = 1023 "grid"; NLSTYLE_INLINE = 1024 "inline"; - NLSTYLE_LIST = 1023 "list"; + NLSTYLE_LIST = 1025 "list"; } // use crate::user::{NodeUserData, User}; diff --git a/common/src/lib.rs b/common/src/lib.rs index af07e1c..4ae098d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -23,6 +23,8 @@ pub static TAGREG: LazyLock<Registry> = LazyLock::new(|| { node::register_fields(&mut reg); node::register_enums(&mut reg); user::register_fields(&mut reg); + api::register_fields(&mut reg); + api::register_enums(&mut reg); reg }); diff --git a/server/src/main.rs b/server/src/main.rs index 2bb0d61..fe55ef6 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -32,6 +32,7 @@ pub mod request_info; pub mod responders; pub mod routes; pub mod ui; +pub mod ui_responder; #[rocket::main] async fn main() { @@ -57,7 +58,7 @@ async fn main() { pub struct State { pub config: Config, - pub cache: Cache, + pub cache: Arc<Cache>, pub database: Arc<dyn Database>, pub session_key: SessionKey, } @@ -86,7 +87,7 @@ pub fn create_state() -> Result<Arc<State>> { // let db_storage = jellykv::memory::new(); let state = Arc::new(State { - cache: Cache::new(Box::new(cache_storage), config.max_memory_cache_size), + cache: Cache::new(Box::new(cache_storage), config.max_memory_cache_size).into(), database: Arc::new(db_storage), session_key: SessionKey::parse(&config.session_key)?, config, diff --git a/server/src/request_info.rs b/server/src/request_info.rs index 0f2fd3a..6ed311e 100644 --- a/server/src/request_info.rs +++ b/server/src/request_info.rs @@ -10,7 +10,10 @@ use crate::{ ui::error::{MyError, MyResult}, }; use anyhow::anyhow; -use jellycommon::jellyobject::{Object, ObjectBuffer}; +use jellycommon::{ + USER_ADMIN, + jellyobject::{Object, ObjectBuffer}, +}; use jellyui::RenderInfo; use rocket::{ Request, async_trait, @@ -22,6 +25,7 @@ use std::sync::Arc; pub struct RequestInfo<'a> { pub lang: &'a str, pub accept: Accept, + pub debug: bool, pub user: Option<ObjectBuffer>, pub state: Arc<State>, } @@ -45,6 +49,7 @@ impl<'a> RequestInfo<'a> { accept: Accept::from_request_ut(request), user: user_from_request(state, request)?, state: state.clone(), + debug: request.query_value::<&str>("debug").is_some(), }) } pub fn require_user(&'a self) -> MyResult<Object<'a>> { @@ -53,6 +58,13 @@ impl<'a> RequestInfo<'a> { .map(|u| u.as_object()) .ok_or(MyError(anyhow!("user required"))) } + pub fn require_admin(&'a self) -> MyResult<Object<'a>> { + let user = self.require_user()?; + if !user.has(USER_ADMIN.0) { + Err(anyhow!("admin required"))? + } + Ok(user) + } pub fn render_info(&'a self) -> RenderInfo<'a> { RenderInfo { lang: self.lang, diff --git a/server/src/routes.rs b/server/src/routes.rs index adbf6e0..b1fc52c 100644 --- a/server/src/routes.rs +++ b/server/src/routes.rs @@ -13,11 +13,15 @@ use crate::{ }, ui::{ account::{r_account_login, r_account_login_post, r_account_logout, r_account_logout_post}, + admin::{ + import::{r_admin_import, r_admin_import_post, r_admin_import_stream}, + r_admin_dashboard, + }, assets::r_image, error::{r_api_catch, r_catch}, home::r_home, node::r_node, - r_favicon, + r_favicon, r_index, style::{r_assets_css, r_assets_font, r_assets_js, r_assets_js_map}, }, }; @@ -74,10 +78,10 @@ pub(super) fn build_rocket(state: Arc<State>) -> Rocket<Build> { // r_account_register, // r_account_settings_post, // r_account_settings, - // r_admin_dashboard, - // r_admin_import, - // r_admin_import_post, - // r_admin_import_stream, + r_admin_dashboard, + r_admin_import, + r_admin_import_post, + r_admin_import_stream, // r_admin_invite, // r_admin_log_stream, // r_admin_log, @@ -95,7 +99,7 @@ pub(super) fn build_rocket(state: Arc<State>) -> Rocket<Build> { r_assets_css, r_favicon, r_home, - // r_index, + r_index, // r_item_poster, r_node, // r_node_thumbnail, diff --git a/server/src/ui/account/mod.rs b/server/src/ui/account/mod.rs index 765b6aa..8c60a2d 100644 --- a/server/src/ui/account/mod.rs +++ b/server/src/ui/account/mod.rs @@ -6,40 +6,29 @@ // pub mod settings; use super::error::MyError; -use crate::{auth::login, request_info::RequestInfo, ui::error::MyResult}; +use crate::{ + auth::login, request_info::RequestInfo, ui::error::MyResult, ui_responder::UiResponse, +}; use anyhow::anyhow; use jellycommon::{VIEW_ACCOUNT_LOGIN, VIEW_ACCOUNT_LOGOUT, jellyobject::Object, routes::u_home}; -use jellyui::render_view; use rocket::{ FromForm, form::{Contextual, Form}, get, http::{Cookie, CookieJar}, post, - response::{Flash, Redirect, content::RawHtml}, + response::{Flash, Redirect}, }; use serde::{Deserialize, Serialize}; -#[derive(FromForm)] -pub struct RegisterForm { - #[field(validate = len(8..128))] - pub invitation: String, - #[field(validate = len(4..32))] - pub username: String, - #[field(validate = len(4..64))] - pub password: String, -} - #[get("/account/login")] -pub async fn r_account_login(ri: RequestInfo<'_>) -> RawHtml<String> { - let ob = Object::EMPTY.insert(VIEW_ACCOUNT_LOGIN, ()); - RawHtml(render_view(ri.render_info(), ob.as_object())) +pub async fn r_account_login(ri: RequestInfo<'_>) -> UiResponse { + ri.respond_ui(Object::EMPTY.insert(VIEW_ACCOUNT_LOGIN, ())) } #[get("/account/logout")] -pub fn r_account_logout(ri: RequestInfo<'_>) -> RawHtml<String> { - let ob = Object::EMPTY.insert(VIEW_ACCOUNT_LOGOUT, ()); - RawHtml(render_view(ri.render_info(), ob.as_object())) +pub fn r_account_logout(ri: RequestInfo<'_>) -> UiResponse { + ri.respond_ui(Object::EMPTY.insert(VIEW_ACCOUNT_LOGOUT, ())) } #[derive(FromForm, Serialize, Deserialize)] diff --git a/server/src/ui/admin/import.rs b/server/src/ui/admin/import.rs index 79b8374..984966b 100644 --- a/server/src/ui/admin/import.rs +++ b/server/src/ui/admin/import.rs @@ -4,44 +4,62 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::{request_info::RequestInfo, ui::error::MyResult}; -use jellycommon::routes::u_admin_import; +use crate::{request_info::RequestInfo, ui::error::MyResult, ui_responder::UiResponse}; +use jellycommon::{ + ADMIN_IMPORT_BUSY, ADMIN_IMPORT_ERROR, VIEW_ADMIN_IMPORT, + jellyobject::{Object, ObjectBuffer}, + routes::u_admin_import, +}; use jellyimport::{ - is_importing, + ImportConfig, import_wrap, is_importing, reporting::{IMPORT_ERRORS, IMPORT_PROGRESS}, }; +use jellyui::tr; use rocket::{ get, post, - request::FlashMessage, - response::{Flash, Redirect, content::RawHtml}, + response::{Flash, Redirect}, }; use rocket_ws::{Message, Stream, WebSocket}; use std::time::Duration; -use tokio::time::sleep; +use tokio::{spawn, time::sleep}; #[get("/admin/import", rank = 2)] -pub async fn r_admin_import( - ri: RequestInfo, - flash: Option<FlashMessage<'_>>, -) -> MyResult<RawHtml<String>> { - ri.session.assert_admin()?; +pub async fn r_admin_import(ri: RequestInfo<'_>) -> MyResult<UiResponse> { + ri.require_admin()?; let last_import_err = IMPORT_ERRORS.read().await.clone(); - Ok(RawHtml(render_page( - &AdminImportPage { - busy: is_importing(), - flash: &flash.map(FlashMessage::into_inner), - lang: &ri.lang, - last_import_err: &last_import_err, - }, - ri.render_info(), - ))) + let last_import_err = last_import_err + .iter() + .map(|e| e.as_str()) + .collect::<Vec<_>>(); + let mut data = ObjectBuffer::empty(); + + if is_importing() { + data = data.as_object().insert(ADMIN_IMPORT_BUSY, ()); + } + data = data + .as_object() + .insert_multi(ADMIN_IMPORT_ERROR, &last_import_err); + + Ok(ri.respond_ui(Object::EMPTY.insert(VIEW_ADMIN_IMPORT, data.as_object()))) } #[post("/admin/import?<incremental>")] -pub async fn r_admin_import_post(ri: RequestInfo, incremental: bool) -> MyResult<Flash<Redirect>> { - ri.session.assert_admin()?; - do_import(&ri.session, incremental).await?; +pub async fn r_admin_import_post( + ri: RequestInfo<'_>, + incremental: bool, +) -> MyResult<Flash<Redirect>> { + ri.require_admin()?; + spawn(async move { + let _ = import_wrap( + ImportConfig { + cache: ri.state.cache.clone(), + db: ri.state.database.clone(), + }, + incremental, + ) + .await; + }); Ok(Flash::success( Redirect::to(u_admin_import()), tr(ri.lang, "admin.import_success"), @@ -49,16 +67,19 @@ pub async fn r_admin_import_post(ri: RequestInfo, incremental: bool) -> MyResult } #[get("/admin/import", rank = 1)] -pub fn r_admin_import_stream(_session: A<Session>, ws: WebSocket) -> Stream!['static] { - Stream! { ws => - loop { - let Some(p) = IMPORT_PROGRESS.read().await.clone() else { - break; - }; - yield Message::Text(serde_json::to_string(&p).unwrap()); - sleep(Duration::from_secs_f32(0.05)).await; +pub fn r_admin_import_stream(ri: RequestInfo<'_>, ws: WebSocket) -> MyResult<Stream!['static]> { + ri.require_admin()?; + Ok({ + Stream! { ws => + loop { + let Some(p) = IMPORT_PROGRESS.read().await.clone() else { + break; + }; + yield Message::Text(serde_json::to_string(&p).unwrap()); + sleep(Duration::from_secs_f32(0.05)).await; + } + yield Message::Text("done".to_string()); + let _ = ws; } - yield Message::Text("done".to_string()); - let _ = ws; - } + }) } diff --git a/server/src/ui/admin/mod.rs b/server/src/ui/admin/mod.rs index 5ee05b7..61e62a4 100644 --- a/server/src/ui/admin/mod.rs +++ b/server/src/ui/admin/mod.rs @@ -3,80 +3,16 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ + pub mod import; -pub mod log; -pub mod user; use super::error::MyResult; -use crate::request_info::RequestInfo; -use jellycommon::routes::u_admin_dashboard; -use jellyimport::is_importing; -use rocket::{ - FromForm, - form::Form, - get, post, - request::FlashMessage, - response::{Flash, Redirect, content::RawHtml}, -}; +use crate::{request_info::RequestInfo, ui_responder::UiResponse}; +use jellycommon::{VIEW_ADMIN_DASHBOARD, jellyobject::ObjectBuffer}; +use rocket::get; #[get("/admin/dashboard")] -pub async fn r_admin_dashboard( - ri: RequestInfo, - flash: Option<FlashMessage<'_>>, -) -> MyResult<RawHtml<String>> { - ri.session.assert_admin()?; - let invites = list_invites(&ri.session)?; - - let busy = if is_importing() { - Some("An import is currently running.") - } else { - None - }; - - Ok(RawHtml(render_page( - &AdminDashboardPage { - busy, - invites: &invites, - flash: &flash.map(FlashMessage::into_inner), - lang: &ri.lang, - }, - ri.render_info(), - ))) -} - -#[post("/admin/generate_invite")] -pub async fn r_admin_invite(ri: RequestInfo) -> MyResult<Flash<Redirect>> { - let i = create_invite(&ri.session)?; - Ok(Flash::success( - Redirect::to(u_admin_dashboard()), - tr(ri.lang, "admin.invite_create_success").replace("{invite}", &i), - )) -} - -#[derive(FromForm)] -pub struct DeleteInvite { - invite: String, -} - -#[post("/admin/remove_invite", data = "<form>")] -pub async fn r_admin_remove_invite( - ri: RequestInfo, - form: Form<DeleteInvite>, -) -> MyResult<Flash<Redirect>> { - ri.session.assert_admin()?; - delete_invite(&ri.session, &form.invite)?; - Ok(Flash::success( - Redirect::to(u_admin_dashboard()), - tr(ri.lang, "admin.invite_delete_success"), - )) -} - -#[post("/admin/update_search")] -pub async fn r_admin_update_search(ri: RequestInfo) -> MyResult<Flash<Redirect>> { - ri.session.assert_admin()?; - update_search_index(&ri.session).await?; - Ok(Flash::success( - Redirect::to(u_admin_dashboard()), - tr(ri.lang, "admin.update_search_success"), - )) +pub async fn r_admin_dashboard(ri: RequestInfo<'_>) -> MyResult<UiResponse> { + ri.require_admin()?; + Ok(ri.respond_ui(ObjectBuffer::new(&mut [(VIEW_ADMIN_DASHBOARD.0, &())]))) } diff --git a/server/src/ui/home.rs b/server/src/ui/home.rs index e9136ec..2fb4903 100644 --- a/server/src/ui/home.rs +++ b/server/src/ui/home.rs @@ -5,15 +5,11 @@ */ use super::error::MyResult; -use crate::request_info::RequestInfo; +use crate::{request_info::RequestInfo, ui_responder::UiResponse}; use jellycommon::jellyobject::ObjectBuffer; -use jellyui::render_view; -use rocket::{Either, get, response::content::RawHtml, serde::json::Json}; +use rocket::get; #[get("/home")] -pub fn r_home(ri: RequestInfo<'_>) -> MyResult<RawHtml<String>> { - Ok(RawHtml(render_view( - ri.render_info(), - ObjectBuffer::new(&mut []).as_object(), - ))) +pub fn r_home(ri: RequestInfo<'_>) -> MyResult<UiResponse> { + Ok(ri.respond_ui(ObjectBuffer::new(&mut []))) } diff --git a/server/src/ui/mod.rs b/server/src/ui/mod.rs index 112bb6b..855b25f 100644 --- a/server/src/ui/mod.rs +++ b/server/src/ui/mod.rs @@ -3,48 +3,29 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ +use crate::{State, request_info::RequestInfo}; use error::MyResult; -use rocket::{futures::FutureExt, get}; +use jellycommon::routes::{u_account_login, u_home}; +use rocket::{futures::FutureExt, get, response::Redirect}; use std::{future::Future, pin::Pin, sync::Arc}; use tokio::{fs::File, io::AsyncRead}; -use crate::State; - pub mod account; -// pub mod admin; +pub mod admin; pub mod assets; pub mod error; pub mod home; -// pub mod items; pub mod node; -// pub mod player; -// pub mod search; -// pub mod stats; pub mod style; -// #[get("/")] -// pub async fn r_index( -// lang: AcceptLanguage, -// sess: Option<A<Session>>, -// ) -> MyResult<Either<Redirect, RawHtml<String>>> { -// let AcceptLanguage(lang) = lang; -// if sess.is_some() { -// Ok(Either::Left(Redirect::temporary(rocket::uri!(r_home())))) -// } else { -// let front = read_to_string(CONF.asset_path.join("front.htm")).await?; -// Ok(Either::Right(RawHtml(render_page( -// &CustomPage { -// title: "Jellything".to_string(), -// body: front, -// }, -// RenderInfo { -// importing: false, -// session: None, -// lang, -// }, -// )))) -// } -// } +#[get("/")] +pub async fn r_index(ri: RequestInfo<'_>) -> MyResult<Redirect> { + if ri.user.is_some() { + Ok(Redirect::temporary(u_home())) + } else { + Ok(Redirect::temporary(u_account_login())) + } +} #[get("/favicon.ico")] pub async fn r_favicon(s: &rocket::State<Arc<State>>) -> MyResult<File> { diff --git a/server/src/ui/node.rs b/server/src/ui/node.rs index cf5c793..0473a94 100644 --- a/server/src/ui/node.rs +++ b/server/src/ui/node.rs @@ -4,13 +4,12 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ use super::error::MyResult; -use crate::request_info::RequestInfo; -use jellycommon::jellyobject::Object; -use jellyui::render_view; -use rocket::{get, response::content::RawHtml}; +use crate::{request_info::RequestInfo, ui_responder::UiResponse}; +use jellycommon::jellyobject::ObjectBuffer; +use rocket::get; #[get("/n/<slug>")] -pub fn r_node(ri: RequestInfo<'_>, slug: &str) -> MyResult<RawHtml<String>> { +pub fn r_node(ri: RequestInfo<'_>, slug: &str) -> MyResult<UiResponse> { ri.require_user()?; - Ok(RawHtml(render_view(ri.render_info(), Object::EMPTY))) + Ok(ri.respond_ui(ObjectBuffer::empty())) } diff --git a/server/src/ui_responder.rs b/server/src/ui_responder.rs new file mode 100644 index 0000000..2df6208 --- /dev/null +++ b/server/src/ui_responder.rs @@ -0,0 +1,43 @@ +/* + 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) 2026 metamuffin <metamuffin.org> +*/ + +use crate::request_info::RequestInfo; +use jellycommon::{ + TAGREG, + jellyobject::{ObjectBuffer, json::object_to_json}, +}; +use jellyui::render_view; +use rocket::response::{ + Responder, + content::{RawHtml, RawJson}, +}; + +pub enum UiResponse { + Html(String), + Json(String), + Object(ObjectBuffer), +} + +impl RequestInfo<'_> { + pub fn respond_ui(&self, view: ObjectBuffer) -> UiResponse { + if self.debug { + let value = object_to_json(&TAGREG, view.as_object()); + UiResponse::Json(serde_json::to_string(&value).unwrap()) + } else { + UiResponse::Html(render_view(self.render_info(), view.as_object())) + } + } +} + +impl<'r, 'o: 'r> Responder<'r, 'o> for UiResponse { + fn respond_to(self, request: &'r rocket::Request<'_>) -> rocket::response::Result<'o> { + match self { + UiResponse::Html(x) => RawHtml(x).respond_to(request), + UiResponse::Json(x) => RawJson(x).respond_to(request), + UiResponse::Object(_) => todo!(), + } + } +} diff --git a/ui/src/old/admin/import.rs b/ui/src/components/admin.rs index 805d787..831c746 100644 --- a/ui/src/old/admin/import.rs +++ b/ui/src/components/admin.rs @@ -4,32 +4,37 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::{FlashM, Page, locale::tr, scaffold::{FlashDisplay, RenderInfo}}; -use jellycommon::routes::u_admin_import_post; +use crate::RenderInfo; +use jellycommon::{ + ADMIN_IMPORT_BUSY, ADMIN_IMPORT_ERROR, + jellyobject::Object, + routes::{u_admin_import, u_admin_import_post, u_admin_log}, +}; +use jellyui_locale::tr; -impl Page for AdminImportPage<'_> { - fn title(&self) -> String { - "Import".to_string() - } - fn to_render(&self) -> markup::DynRender<'_> { - markup::new!(@self) +markup::define!( + AdminDashboard<'a>(ri: &'a RenderInfo<'a>) { + h1 { @tr(ri.lang, "admin.dashboard.title") } + ul { + li{a[href=u_admin_log(true)] { @tr(ri.lang, "admin.log.warnonly") }} + li{a[href=u_admin_log(false)] { @tr(ri.lang, "admin.log.full") }} + } + + a[href=u_admin_import()] { h2 { @tr(ri.lang, "admin.import.title") }} } -} -markup::define!( - AdminImportPage<'a>(ri: &'a RenderInfo<'a>, busy: bool, last_import_err: &'a [String], flash: &'a FlashM) { - @FlashDisplay { flash } - @if *busy { + AdminImport<'a>(ri: &'a RenderInfo<'a>, data: Object<'a>) { + @if data.has(ADMIN_IMPORT_BUSY.0) { h1 { @tr(ri.lang, "admin.import.running") } noscript { "Live import progress needs javascript." } div[id="admin_import"] {} } else { h1 { @tr(ri.lang, "admin.import.title") } - @if !last_import_err.is_empty() { + @if data.has(ADMIN_IMPORT_ERROR.0) { section.message.error { details { - summary { p.error { @tr(ri.lang, "admin.import_errors").replace("{n}", &last_import_err.len().to_string()) } } - ol { @for e in *last_import_err { + summary { p.error { @tr(ri.lang, "admin.import_errors").replace("{n}", "1") } } + ol { @for e in data.iter(ADMIN_IMPORT_ERROR) { li.error { pre.error { @e } } }} } @@ -43,4 +48,5 @@ markup::define!( } } } + ); diff --git a/ui/src/components/mod.rs b/ui/src/components/mod.rs index 792894e..cb9ec1b 100644 --- a/ui/src/components/mod.rs +++ b/ui/src/components/mod.rs @@ -4,6 +4,7 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ +pub mod admin; pub mod login; pub mod message; pub mod node_page; @@ -13,6 +14,7 @@ pub mod stats; use crate::{ RenderInfo, components::{ + admin::{AdminDashboard, AdminImport}, login::{AccountLogin, AccountLogout, AccountSetPassword}, message::Message, node_page::NodePage, @@ -38,5 +40,11 @@ define! { @if let Some(session) = view.get(VIEW_ACCOUNT_SET_PASSWORD) { @AccountSetPassword { ri, session } } + @if let Some(()) = view.get(VIEW_ADMIN_DASHBOARD) { + @AdminDashboard { ri } + } + @if let Some(data) = view.get(VIEW_ADMIN_IMPORT) { + @AdminImport { ri, data } + } } } diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 9509129..5b23ee2 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -10,6 +10,8 @@ mod scaffold; pub use jellyui_client_scripts::*; pub use jellyui_client_style::*; +pub use jellyui_locale::tr; + use crate::{components::View, scaffold::Scaffold}; use jellycommon::{jellyobject::Object, *}; use serde::{Deserialize, Serialize}; diff --git a/ui/src/old/admin/mod.rs b/ui/src/old/admin/mod.rs deleted file mode 100644 index f42ba76..0000000 --- a/ui/src/old/admin/mod.rs +++ /dev/null @@ -1,64 +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) 2026 metamuffin <metamuffin.org> -*/ - -pub mod import; -pub mod log; -pub mod user; - -use crate::{FlashM, Page, locale::tr, scaffold::{FlashDisplay, RenderInfo}}; -use jellycommon::routes::{ - u_admin_import, u_admin_invite_create, u_admin_invite_remove, u_admin_log, - u_admin_update_search, u_admin_users, -}; - -impl Page for AdminDashboardPage<'_> { - fn title(&self) -> String { - "Admin Dashboard".to_string() - } - fn to_render(&self) -> markup::DynRender<'_> { - markup::new!(@self) - } -} - -markup::define!( - AdminDashboardPage<'a>(ri: &'a RenderInfo<'a>, busy: Option<&'static str>, flash: &'a FlashM, invites: &'a [String]) { - h1 { @tr(ri.lang, "admin.dashboard.title") } - @FlashDisplay { flash } - ul { - li{a[href=u_admin_log(true)] { @tr(ri.lang, "admin.log.warnonly") }} - li{a[href=u_admin_log(false)] { @tr(ri.lang, "admin.log.full") }} - } - - a[href=u_admin_import()] { h2 { @tr(ri.lang, "admin.import.title") }} - @if let Some(text) = busy { - section.message { p.warn { @text } } - } - form[method="POST", action=u_admin_update_search()] { - input[type="submit", value=tr(ri.lang, "admin.dashboard.update_search").to_string()]; - } - h2 { @tr(ri.lang, "admin.dashboard.users") } - p { a[href=u_admin_users()] { @tr(ri.lang, "admin.dashboard.manage_users") } } - h2 { @tr(ri.lang, "admin.dashboard.invites") } - form[method="POST", action=u_admin_invite_create()] { - input[type="submit", value=tr(ri.lang, "admin.dashboard.create_invite").to_string()]; - } - ul { @for t in *invites { - li { - form[method="POST", action=u_admin_invite_remove()] { - span { @t } - input[type="text", name="invite", value=&t, hidden]; - input[type="submit", value=tr(ri.lang, "admin.dashboard.create_invite").to_string()]; - } - } - }} - - // h2 { "Database" } - // @match db_stats(&database) { - // Ok(s) => { @s } - // Err(e) => { pre.error { @format!("{e:?}") } } - // } - } -); |