From ae7ef10b67737f08a86743f687bca30a12e9e17a Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 12 Jul 2026 19:38:03 +0200 Subject: first stable --- src/routes/players.rs | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/routes/players.rs (limited to 'src/routes/players.rs') diff --git a/src/routes/players.rs b/src/routes/players.rs new file mode 100644 index 0000000..b98a39d --- /dev/null +++ b/src/routes/players.rs @@ -0,0 +1,131 @@ +use askama::Template; +use axum::{ + Form, Router, + extract::State, + response::{Html, Redirect}, + routing::{get, post}, +}; +use serde::Deserialize; + +use crate::auth::CurrentUser; +use crate::routes::AppState; + +#[derive(Template)] +#[template(path = "players/manage.html")] +struct PlayerManagementTemplate { + username: String, + is_superuser: bool, + password_change_required: bool, + active_instance_name: Option, + whitelist: Vec, + ops: Vec, + bans: Vec, + message: Option, +} + +#[derive(Debug, Deserialize)] +pub struct PlayerAction { + pub username: String, +} + +pub fn router() -> Router { + Router::new() + .route("/players", get(manage_players)) + .route("/players/whitelist/add", post(add_whitelist)) + .route("/players/whitelist/remove", post(remove_whitelist)) + .route("/players/op/add", post(add_op)) + .route("/players/op/remove", post(remove_op)) + .route("/players/ban/add", post(add_ban)) + .route("/players/ban/remove", post(remove_ban)) +} + +async fn manage_players( + State(state): State, + CurrentUser(account): CurrentUser, +) -> Result, axum::response::Redirect> { + let active = get_active_instance(&state.db); + + let (instance_name, whitelist, ops, bans) = + if let Some((_id, inst_name, _rcon_pw, _container_state)) = active { + let data_dir = state + .config + .server_directory + .join("instances") + .join(&inst_name) + .join("data"); + + let w = read_json_list(&data_dir.join("whitelist.json")); + let o = read_json_list(&data_dir.join("ops.json")); + let b = read_json_list(&data_dir.join("banned-players.json")); + (Some(inst_name), w, o, b) + } else { + (None, vec![], vec![], vec![]) + }; + + let template = PlayerManagementTemplate { + username: account.username, + is_superuser: account.is_superuser, + password_change_required: account.password_change_required, + active_instance_name: instance_name, + whitelist, + ops, + bans, + message: None, + }; + + Ok(Html(template.render().unwrap_or_default())) +} + +fn read_json_list(path: &std::path::Path) -> Vec { + if let Ok(contents) = std::fs::read_to_string(path) { + if let Ok(entries) = serde_json::from_str::>(&contents) { + return entries + .iter() + .filter_map(|e| e["name"].as_str().map(String::from)) + .collect(); + } + } + vec![] +} + +macro_rules! player_action { + ($name:ident, $command:expr) => { + async fn $name( + State(state): State, + CurrentUser(_user): CurrentUser, + Form(form): Form, + ) -> Result { + if let Some((_, _, rcon_pw, _)) = get_active_instance(&state.db) { + let client = crate::rcon::RconClient::new(rcon_pw); + let cmd = format!($command, form.username); + match client.send_command_async(cmd.clone()).await { + Ok(resp) => tracing::info!("RCON '{}' -> {}", cmd, resp.trim()), + Err(e) => tracing::error!("RCON '{}' failed: {}", cmd, e), + } + } else { + tracing::warn!( + "Player action '{}' called but no active instance", + stringify!($name) + ); + } + Ok(Redirect::to("/players")) + } + }; +} + +player_action!(add_whitelist, "whitelist add {}"); +player_action!(remove_whitelist, "whitelist remove {}"); +player_action!(add_op, "op {}"); +player_action!(remove_op, "deop {}"); +player_action!(add_ban, "ban {}"); +player_action!(remove_ban, "pardon {}"); + +fn get_active_instance(db: &crate::db::Database) -> Option<(i64, String, String, String)> { + db.conn() + .query_row( + "SELECT id, name, rcon_password, container_state FROM instances WHERE is_active = 1", + [], + |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)), + ) + .ok() +} -- cgit v1.2.3