From 74d2ada067bd2fd9436710ab7ac260e0eddf1314 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 29 Jul 2026 01:03:24 +0200 Subject: Add per-instance mod management with client-side detection - New mod validation engine (src/mods.rs) with constant-pool class-file scanning: detects server-side content (block, tileentity, command, worldgen) vs client-only API (gui, renderer, forge/client) - 7 client-only mods correctly detected (AppleSkin, CraftingTweaks, Durability101, EnchantmentDescriptions, InventoryHUD, MouseTweaks, TrashSlot); 51 server-capable; 1 unknown - Routes + templating for mod upload (jar/zip), listing, delete, rescan - Multipart upload with 500MB limit (bypasses axum's default 2MB) - Per-instance mod summary card on edit page - Whitelist toggle checkbox on edit page (persisted, drives ENABLE_WHITELIST) - Dynamic Java image tag (java8/java17/java21) based on MC version - Class scan is informational-only; rejection based on metadata side=CLIENT - has_modpack flag cleanup on mod delete - Remove hx-boost from (was corrupting multipart uploads) --- src/routes/instances.rs | 61 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 16 deletions(-) (limited to 'src/routes/instances.rs') diff --git a/src/routes/instances.rs b/src/routes/instances.rs index 8dd6a62..f1c3e37 100644 --- a/src/routes/instances.rs +++ b/src/routes/instances.rs @@ -41,6 +41,8 @@ struct EditInstanceTemplate { instance: Instance, current_seed: Option, max_memory_mb: u32, + mod_count: i64, + rejected_count: i64, } struct InstanceInfo { @@ -74,6 +76,7 @@ pub struct EditInstanceForm { pub gamemode: i32, pub max_players: i32, pub pvp: Option, + pub whitelist_enabled: Option, pub motd: Option, pub memory_mb: i32, } @@ -216,8 +219,8 @@ async fn edit_form( let conn = state.db.conn(); conn.query_row( "SELECT id, name, version, server_type, world_source, world_seed, level_type, - has_modpack, memory_mb, difficulty, gamemode, max_players, pvp, motd, - rcon_password, is_active, created_at, updated_at + has_modpack, memory_mb, difficulty, gamemode, max_players, pvp, whitelist_enabled, + motd, rcon_password, is_active, created_at, updated_at FROM instances WHERE id = ?1", params![id], |row| { @@ -235,11 +238,12 @@ async fn edit_form( gamemode: row.get(10)?, max_players: row.get(11)?, pvp: row.get::<_, i32>(12)? != 0, - motd: row.get(13)?, - rcon_password: row.get(14)?, - is_active: row.get::<_, i32>(15)? != 0, - created_at: row.get(16)?, - updated_at: row.get(17)?, + whitelist_enabled: row.get::<_, i32>(13)? != 0, + motd: row.get(14)?, + rcon_password: row.get(15)?, + is_active: row.get::<_, i32>(16)? != 0, + created_at: row.get(17)?, + updated_at: row.get(18)?, }) }, ) @@ -272,6 +276,26 @@ async fn edit_form( current_seed }; + let (mod_count, rejected_count) = { + let conn = state.db.conn(); + let total: i64 = conn + .query_row( + "SELECT COUNT(*) FROM mods WHERE instance_id = ?1", + params![id], + |row| row.get(0), + ) + .unwrap_or(0); + let rejected: i64 = conn + .query_row( + "SELECT COUNT(*) FROM mods WHERE instance_id = ?1 AND accepted = 0", + params![id], + |row| row.get(0), + ) + .unwrap_or(0); + drop(conn); + (total, rejected) + }; + let template = EditInstanceTemplate { username: account.username.clone(), is_superuser: account.is_superuser, @@ -279,6 +303,8 @@ async fn edit_form( instance, current_seed, max_memory_mb: state.config.max_memory_mb, + mod_count, + rejected_count, }; Ok(Html(template.render().unwrap_or_default())) @@ -295,18 +321,20 @@ async fn edit_instance( } let pvp = form.pvp.as_deref() == Some("on"); + let whitelist_enabled = form.whitelist_enabled.as_deref() == Some("on"); let is_active = { let conn = state.db.conn(); conn.execute( "UPDATE instances SET difficulty = ?1, gamemode = ?2, max_players = ?3, - pvp = ?4, motd = ?5, memory_mb = ?6, updated_at = datetime('now') - WHERE id = ?7", + pvp = ?4, whitelist_enabled = ?5, motd = ?6, memory_mb = ?7, + updated_at = datetime('now') WHERE id = ?8", params![ form.difficulty, form.gamemode, form.max_players, pvp, + whitelist_enabled, form.motd, form.memory_mb, id, @@ -390,8 +418,8 @@ async fn activate_instance( conn.query_row( "SELECT id, name, version, server_type, world_source, world_seed, level_type, - has_modpack, memory_mb, difficulty, gamemode, max_players, pvp, motd, - rcon_password, is_active, created_at, updated_at + has_modpack, memory_mb, difficulty, gamemode, max_players, pvp, whitelist_enabled, + motd, rcon_password, is_active, created_at, updated_at FROM instances WHERE id = ?1", params![id], |row| { @@ -409,11 +437,12 @@ async fn activate_instance( gamemode: row.get(10)?, max_players: row.get(11)?, pvp: row.get::<_, i32>(12)? != 0, - motd: row.get(13)?, - rcon_password: row.get(14)?, - is_active: row.get::<_, i32>(15)? != 0, - created_at: row.get(16)?, - updated_at: row.get(17)?, + whitelist_enabled: row.get::<_, i32>(13)? != 0, + motd: row.get(14)?, + rcon_password: row.get(15)?, + is_active: row.get::<_, i32>(16)? != 0, + created_at: row.get(17)?, + updated_at: row.get(18)?, }) }, ) -- cgit v1.2.3