aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-02-16 17:50:57 +0100
committermetamuffin <metamuffin@disroot.org>2026-02-16 17:50:57 +0100
commitbe4af57d75cc1e233b4714b18198fb7bde49464d (patch)
tree6e529e748fc1d5c212dcf8033c8158630ca4f4d6
parentbb1822e3e68fe6f699102bfc1659731bdbac1a40 (diff)
downloadjellything-be4af57d75cc1e233b4714b18198fb7bde49464d.tar
jellything-be4af57d75cc1e233b4714b18198fb7bde49464d.tar.bz2
jellything-be4af57d75cc1e233b4714b18198fb7bde49464d.tar.zst
refactor ui responder; add admin import pages
-rw-r--r--common/src/api.rs7
-rw-r--r--common/src/lib.rs2
-rw-r--r--server/src/main.rs5
-rw-r--r--server/src/request_info.rs14
-rw-r--r--server/src/routes.rs16
-rw-r--r--server/src/ui/account/mod.rs27
-rw-r--r--server/src/ui/admin/import.rs89
-rw-r--r--server/src/ui/admin/mod.rs78
-rw-r--r--server/src/ui/home.rs12
-rw-r--r--server/src/ui/mod.rs43
-rw-r--r--server/src/ui/node.rs11
-rw-r--r--server/src/ui_responder.rs43
-rw-r--r--ui/src/components/admin.rs (renamed from ui/src/old/admin/import.rs)38
-rw-r--r--ui/src/components/mod.rs8
-rw-r--r--ui/src/lib.rs2
-rw-r--r--ui/src/old/admin/mod.rs64
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:?}") } }
- // }
- }
-);