aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/src/api.rs3
-rw-r--r--common/src/routes.rs3
-rw-r--r--database/src/query_syntax.rs2
-rw-r--r--locale/en.ini6
-rw-r--r--server/src/routes.rs26
-rw-r--r--server/src/ui/admin/mod.rs34
-rw-r--r--server/src/ui/admin/user.rs93
-rw-r--r--server/src/ui/mod.rs1
-rw-r--r--server/src/ui/user.rs28
-rw-r--r--ui/src/components/admin.rs12
-rw-r--r--ui/src/components/mod.rs5
-rw-r--r--ui/src/components/user.rs5
-rw-r--r--ui/src/old/admin/user.rs81
13 files changed, 92 insertions, 207 deletions
diff --git a/common/src/api.rs b/common/src/api.rs
index bec33ba..ea1a0c6 100644
--- a/common/src/api.rs
+++ b/common/src/api.rs
@@ -27,6 +27,7 @@ fields! {
VIEW_ADMIN_IMPORT: Object = b"adim";
VIEW_ADMIN_INFO: Object = b"adin";
VIEW_ADMIN_LOG: Object = b"adlo";
+ VIEW_ADMIN_USER_LIST: Object = b"adul";
VIEW_USER_SETTINGS: Object = b"uset";
ADMIN_IMPORT_BUSY: () = b"busy";
@@ -35,6 +36,8 @@ fields! {
ADMIN_INFO_TEXT: &str = b"aite";
ADMIN_LOG_MESSAGE: &str = b"aite";
+ ADMIN_USER_LIST_ITEM: Object = b"item";
+
NKU_NODE: Object = b"node";
NKU_UDATA: Object = b"udat";
NKU_ROLE: &str = b"role";
diff --git a/common/src/routes.rs b/common/src/routes.rs
index 3ee1e4c..3266745 100644
--- a/common/src/routes.rs
+++ b/common/src/routes.rs
@@ -24,6 +24,9 @@ pub fn u_image(path: &str, size: usize) -> String {
pub fn u_image_fallback_person(name: &str, size: usize) -> String {
format!("/image_fallback/person/{name}?size={size}")
}
+pub fn u_user(login: &str) -> String {
+ format!("/u/{login}")
+}
pub fn u_node_slug_watched(node: &str, state: &str) -> String {
format!("/n/{node}/watched?state={state}")
}
diff --git a/database/src/query_syntax.rs b/database/src/query_syntax.rs
index b192361..f3d1166 100644
--- a/database/src/query_syntax.rs
+++ b/database/src/query_syntax.rs
@@ -131,7 +131,7 @@ impl FromStr for Filter<'static> {
Path::from_str(l.trim()).map_err(|e| anyhow!("{e}"))?,
Value::from_str(r.trim()).map_err(|e| anyhow!("{e}"))?,
),
- _ => bail!("invalid filter"),
+ x => Self::Has(Path::from_str(x.trim()).map_err(|e| anyhow!("{e}"))?),
})
}
}
diff --git a/locale/en.ini b/locale/en.ini
index 8ec8c32..db7b077 100644
--- a/locale/en.ini
+++ b/locale/en.ini
@@ -197,7 +197,7 @@ admin.dashboard.invites=Invitations
admin.dashboard.users=Users
admin.dashboard.library=Library
admin.import.running=Import Running...
-admin.import.title=Import
+admin.import=Import
admin.invite_create_success=Invite created: {invite}
admin.invite_delete_success=Invite deleted
admin.import_success=Import finished
@@ -205,9 +205,7 @@ admin.import_errors=The last import resulted in {n} errors:
admin.update_search_success=Search index updated
admin.users.remove_success=User removed
admin.users.permission_update_success=Permissions updated
-admin.users.title=User Management
-admin.users.user_list=All Users
-admin.users.return_to_list=Back to user list
+admin.users=Users
admin.log.warnonly=Server Log (Warnings only)
admin.log.full=Server Log (Full)
diff --git a/server/src/routes.rs b/server/src/routes.rs
index 2b7fed9..7068fe0 100644
--- a/server/src/routes.rs
+++ b/server/src/routes.rs
@@ -19,7 +19,7 @@ use crate::{
admin::{
import::{r_admin_import, r_admin_import_post, r_admin_import_stream},
log::{r_admin_log, r_admin_log_stream},
- r_admin_dashboard,
+ r_admin_dashboard, r_admin_users,
},
assets::{r_image, r_image_fallback_person},
error::{r_api_catch, r_catch},
@@ -28,6 +28,7 @@ use crate::{
player::r_player,
r_favicon, r_index,
style::{r_assets_css, r_assets_font, r_assets_js, r_assets_js_map},
+ user::{r_user, r_user_remove},
},
};
use rocket::{
@@ -74,29 +75,19 @@ pub(super) fn build_rocket(state: Arc<State>) -> Rocket<Build> {
.mount(
"/",
routes![
- // Frontend
r_account_login_post,
r_account_login,
r_account_logout_post,
r_account_logout,
- // r_account_register_post,
- // 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_invite,
r_admin_log_stream,
r_admin_log,
- // r_admin_remove_invite,
- // r_admin_remove_user,
- // r_admin_update_search,
- // r_admin_user_permission,
- // r_admin_user,
- // r_admin_users,
- // r_items,
+ r_admin_users,
r_image,
r_image_fallback_person,
r_assets_font,
@@ -106,19 +97,12 @@ pub(super) fn build_rocket(state: Arc<State>) -> Rocket<Build> {
r_favicon,
r_home,
r_index,
- // r_item_poster,
+ r_user,
+ r_user_remove,
r_node,
- // r_node_thumbnail,
- // r_node_userdata_progress,
- // r_node_userdata_rating,
- // r_node_userdata_watched,
- // r_node_userdata,
r_player,
r_playersync,
- // r_search,
- // r_stats,
r_stream,
- // API
r_api_root,
r_version,
// Compat
diff --git a/server/src/ui/admin/mod.rs b/server/src/ui/admin/mod.rs
index 8413ead..3fa0591 100644
--- a/server/src/ui/admin/mod.rs
+++ b/server/src/ui/admin/mod.rs
@@ -9,9 +9,14 @@ pub mod log;
use super::error::MyResult;
use crate::{request_info::RequestInfo, ui_responder::UiResponse};
-use jellycommon::{jellyobject::ObjectBuffer, *};
+use jellycommon::{
+ jellyobject::{ObjectBuffer, ObjectBufferBuilder},
+ *,
+};
+use jellydb::Query;
use jellyui::tr;
use rocket::get;
+use std::str::FromStr;
#[get("/admin/dashboard")]
pub async fn r_admin_dashboard(ri: RequestInfo<'_>) -> MyResult<UiResponse> {
@@ -35,3 +40,30 @@ pub async fn r_admin_dashboard(ri: RequestInfo<'_>) -> MyResult<UiResponse> {
),
])))
}
+
+#[get("/admin/users")]
+pub fn r_admin_users(ri: RequestInfo) -> MyResult<UiResponse> {
+ ri.require_admin()?;
+
+ let mut users = Vec::new();
+ ri.state.database.transaction(&mut |txn| {
+ users.clear();
+ let rows = txn
+ .query(Query::from_str("FILTER Ulgn")?)?
+ .collect::<Vec<_>>();
+ for row in rows {
+ let (row, _) = row?;
+ users.push(txn.get(row)?.unwrap());
+ }
+ Ok(())
+ })?;
+
+ let mut list = ObjectBufferBuilder::default();
+ for u in users {
+ list.push(ADMIN_USER_LIST_ITEM, u.as_object());
+ }
+ Ok(ri.respond_ui(ObjectBuffer::new(&mut [(
+ VIEW_ADMIN_USER_LIST.0,
+ &list.finish().as_object(),
+ )])))
+}
diff --git a/server/src/ui/admin/user.rs b/server/src/ui/admin/user.rs
deleted file mode 100644
index b4770c8..0000000
--- a/server/src/ui/admin/user.rs
+++ /dev/null
@@ -1,93 +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>
-*/
-use crate::{request_info::RequestInfo, ui::error::MyResult};
-use anyhow::Context;
-use jellycommon::routes::{u_admin_user, u_admin_users};
-use rocket::{
- FromForm, FromFormField,
- form::Form,
- get, post,
- response::{Flash, Redirect, content::RawHtml},
-};
-
-#[get("/admin/users")]
-pub fn r_admin_users(ri: RequestInfo) -> MyResult<RawHtml<String>> {
- ri.session.assert_admin()?;
- let r = admin_users(&ri.session)?;
- Ok(RawHtml(render_page(
- &AdminUsersPage {
- flash: &None,
- lang: &ri.lang,
- users: &r.users,
- },
- ri.render_info(),
- )))
-}
-
-#[get("/admin/user/<name>")]
-pub fn r_admin_user(ri: RequestInfo, name: &str) -> MyResult<RawHtml<String>> {
- ri.session.assert_admin()?;
- let user = get_user(&ri.session, name)?;
-
- Ok(RawHtml(render_page(
- &AdminUserPage {
- flash: &None,
- lang: &ri.lang,
- user: &user,
- },
- ri.render_info(),
- )))
-}
-
-#[derive(FromForm)]
-pub struct UserPermissionForm {
- permission: String,
- action: UrlGrantState,
-}
-
-#[derive(FromFormField)]
-pub enum UrlGrantState {
- Grant,
- Revoke,
- Unset,
-}
-
-#[post("/admin/user/<name>/update_permission", data = "<form>")]
-pub fn r_admin_user_permission(
- ri: RequestInfo,
- form: Form<UserPermissionForm>,
- name: &str,
-) -> MyResult<Flash<Redirect>> {
- ri.session.assert_admin()?;
- let perm = serde_json::from_str::<UserPermission>(&form.permission)
- .context("parsing provided permission")?;
-
- update_user_perms(
- &ri.session,
- name,
- perm,
- match form.action {
- UrlGrantState::Grant => GrantState::Grant,
- UrlGrantState::Revoke => GrantState::Revoke,
- UrlGrantState::Unset => GrantState::Unset,
- },
- )?;
-
- Ok(Flash::success(
- Redirect::to(u_admin_user(name)),
- tr(ri.lang, "admin.users.permission_update_success"),
- ))
-}
-
-#[post("/admin/<name>/remove")]
-pub fn r_admin_remove_user(ri: RequestInfo, name: &str) -> MyResult<Flash<Redirect>> {
- ri.session.assert_admin()?;
- delete_user(&ri.session, name)?;
- Ok(Flash::success(
- Redirect::to(u_admin_users()),
- tr(ri.lang, "admin.users.remove_success"),
- ))
-}
diff --git a/server/src/ui/mod.rs b/server/src/ui/mod.rs
index 116ed3c..28762f1 100644
--- a/server/src/ui/mod.rs
+++ b/server/src/ui/mod.rs
@@ -18,6 +18,7 @@ pub mod home;
pub mod node;
pub mod style;
pub mod player;
+pub mod user;
#[get("/")]
pub async fn r_index(ri: RequestInfo<'_>) -> MyResult<Redirect> {
diff --git a/server/src/ui/user.rs b/server/src/ui/user.rs
new file mode 100644
index 0000000..31e521e
--- /dev/null
+++ b/server/src/ui/user.rs
@@ -0,0 +1,28 @@
+/*
+ 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, ui::error::MyResult};
+use jellycommon::routes::u_admin_users;
+use jellyui::tr;
+use rocket::{
+ get, post,
+ response::{Flash, Redirect, content::RawHtml},
+};
+
+#[get("/u/<name>")]
+pub fn r_user(ri: RequestInfo<'_>, name: &str) -> MyResult<RawHtml<String>> {
+ ri.require_user()?;
+ todo!()
+}
+
+#[post("/u/<name>/remove")]
+pub fn r_user_remove(ri: RequestInfo<'_>, name: &str) -> MyResult<Flash<Redirect>> {
+ ri.require_admin()?;
+ todo!();
+ Ok(Flash::success(
+ Redirect::to(u_admin_users()),
+ tr(ri.lang, "admin.users.remove_success"),
+ ))
+}
diff --git a/ui/src/components/admin.rs b/ui/src/components/admin.rs
index cddb0e2..cd691a2 100644
--- a/ui/src/components/admin.rs
+++ b/ui/src/components/admin.rs
@@ -7,7 +7,7 @@
use crate::RenderInfo;
use jellycommon::{
jellyobject::Object,
- routes::{u_admin_import, u_admin_import_post, u_admin_log},
+ routes::{u_admin_import, u_admin_import_post, u_admin_log, u_admin_users, u_user},
*,
};
use jellyui_locale::tr;
@@ -20,7 +20,8 @@ markup::define!(
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") }}
+ a[href=u_admin_import()] { h2 { @tr(ri.lang, "admin.import") }}
+ a[href=u_admin_users()] { h2 { @tr(ri.lang, "admin.users") }}
}
AdminImport<'a>(ri: &'a RenderInfo<'a>, data: Object<'a>) {
@@ -55,4 +56,11 @@ markup::define!(
pre { @data.get(ADMIN_INFO_TEXT) }
}
+ AdminUserList<'a>(ri: &'a RenderInfo<'a>, data: Object<'a>) {
+ h1 { @tr(ri.lang, "admin.users") }
+ ul { @for u in data.iter(ADMIN_USER_LIST_ITEM) {
+ li { a[href=u_user(u.get(USER_LOGIN).unwrap_or_default())] { @u.get(USER_LOGIN) } }
+ }}
+ }
+
);
diff --git a/ui/src/components/mod.rs b/ui/src/components/mod.rs
index dde77b0..e7e5f9c 100644
--- a/ui/src/components/mod.rs
+++ b/ui/src/components/mod.rs
@@ -18,7 +18,7 @@ pub mod user;
use crate::{
RenderInfo,
components::{
- admin::{AdminDashboard, AdminImport, AdminInfo},
+ admin::{AdminDashboard, AdminImport, AdminInfo, AdminUserList},
login::{AccountLogin, AccountLogout, AccountSetPassword},
message::Message,
node_list::NodeList,
@@ -64,5 +64,8 @@ define! {
@if let Some(user) = view.get(VIEW_USER_SETTINGS) {
@UserSettings { ri, user }
}
+ @if let Some(data) = view.get(VIEW_ADMIN_USER_LIST) {
+ @AdminUserList { ri, data }
+ }
}
}
diff --git a/ui/src/components/user.rs b/ui/src/components/user.rs
index cf1c123..22b296e 100644
--- a/ui/src/components/user.rs
+++ b/ui/src/components/user.rs
@@ -17,8 +17,8 @@ markup::define! {
h1 { @tr(ri.lang, "settings") }
h2 { @tr(ri.lang, "settings.account") }
- a.switch_account[href=u_account_login()] { button { "Switch Account" } }
- a.switch_account[href=u_account_logout()] { button { "Log out" } }
+ a[href=u_account_login()] { button { "Switch Account" } }
+ a[href=u_account_logout()] { button { "Log out" } }
p { @tr(ri.lang, "tag.Ulgn") ": " @user.get(USER_LOGIN) }
form[method="POST", action=u_account_settings()] {
label[for="name"] { @tr(ri.lang, "tag.Unam") }
@@ -41,7 +41,6 @@ markup::define! {
input[type="range", id="accent", name="theme_accent", min=0, max=360, step=1, value=user.get(USER_THEME_ACCENT)];
input[type="submit", value=tr(ri.lang, "settings.update")];
}
-
// h2 { @tr(ri.lang, "settings.appearance") }
// form[method="POST", action=u_account_settings()] {
// fieldset {
diff --git a/ui/src/old/admin/user.rs b/ui/src/old/admin/user.rs
deleted file mode 100644
index e4a8975..0000000
--- a/ui/src/old/admin/user.rs
+++ /dev/null
@@ -1,81 +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>
-*/
-
-use crate::{FlashM, Page, scaffold::FlashDisplay};
-use jellycommon::routes::{
- u_admin_user, u_admin_user_permission, u_admin_user_remove, u_admin_users,
-};
-
-impl Page for AdminUserPage<'_> {
- fn title(&self) -> String {
- "User Management".to_string()
- }
- fn to_render(&self) -> markup::DynRender<'_> {
- markup::new!(@self)
- }
-}
-impl Page for AdminUsersPage<'_> {
- fn title(&self) -> String {
- "User Management".to_string()
- }
- fn to_render(&self) -> markup::DynRender<'_> {
- markup::new!(@self)
- }
-}
-
-markup::define! {
- AdminUsersPage<'a>(lang: &'a Language, users: &'a [User], flash: &'a FlashM) {
- h1 { @trs(lang, "admin.users.title") }
- @FlashDisplay { flash }
- h2 { @trs(lang, "admin.users.user_list") }
- ul { @for u in *users {
- li {
- a[href=u_admin_user(&u.name)] { @format!("{:?}", u.display_name) " (" @u.name ")" }
- }
- }}
- }
- AdminUserPage<'a>(lang: &'a Language, user: &'a User, flash: &'a FlashM) {
- h1 { @format!("{:?}", user.display_name) " (" @user.name ")" }
- a[href=u_admin_users()] { @trs(lang, "admin.users.return_to_list") }
- @FlashDisplay { flash }
- form[method="POST", action=u_admin_user_remove(&user.name)] {
- // input[type="text", name="name", value=&user.name, hidden];
- input.danger[type="submit", value="Remove user(!)"];
- }
-
- h2 { "Permissions" }
- @PermissionDisplay { perms: &user.permissions }
-
- form[method="POST", action=u_admin_user_permission(&user.name)] {
- // input[type="text", name="name", value=&user.name, hidden];
- fieldset.perms {
- legend { "Permission" }
- @for p in UserPermission::ALL_ENUMERABLE {
- label {
- input[type="radio", name="permission", value=serde_json::to_string(p).unwrap()];
- @format!("{p}")
- } br;
- }
- }
- fieldset.perms {
- legend { "State" }
- label { input[type="radio", name="action", value="unset"]; "Unset" } br;
- label { input[type="radio", name="action", value="grant"]; "Grant" } br;
- label { input[type="radio", name="action", value="revoke"]; "Revoke" } br;
- }
- input[type="submit", value="Update"];
- }
- }
- PermissionDisplay<'a>(perms: &'a PermissionSet) {
- ul { @for (perm,grant) in &perms.0 {
- @if *grant {
- li[class="perm-grant"] { @format!("Allow {}", perm) }
- } else {
- li[class="perm-revoke"] { @format!("Deny {}", perm) }
- }
- }}
- }
-}