diff options
| author | salmonik <salmonik@tuta.io> | 2026-07-29 01:03:24 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-07-29 01:03:24 +0200 |
| commit | 74d2ada067bd2fd9436710ab7ac260e0eddf1314 (patch) | |
| tree | 1f791bb895a611d8e729a735ba098a647808a157 /src/routes/instances.rs | |
| parent | create example config on startup (diff) | |
| download | minecraft-ui-74d2ada067bd2fd9436710ab7ac260e0eddf1314.tar.gz minecraft-ui-74d2ada067bd2fd9436710ab7ac260e0eddf1314.zip | |
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 <body> (was corrupting multipart uploads)
Diffstat (limited to '')
| -rw-r--r-- | src/routes/instances.rs | 61 |
1 files changed, 45 insertions, 16 deletions
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 { | |||
| 41 | instance: Instance, | 41 | instance: Instance, |
| 42 | current_seed: Option<String>, | 42 | current_seed: Option<String>, |
| 43 | max_memory_mb: u32, | 43 | max_memory_mb: u32, |
| 44 | mod_count: i64, | ||
| 45 | rejected_count: i64, | ||
| 44 | } | 46 | } |
| 45 | 47 | ||
| 46 | struct InstanceInfo { | 48 | struct InstanceInfo { |
| @@ -74,6 +76,7 @@ pub struct EditInstanceForm { | |||
| 74 | pub gamemode: i32, | 76 | pub gamemode: i32, |
| 75 | pub max_players: i32, | 77 | pub max_players: i32, |
| 76 | pub pvp: Option<String>, | 78 | pub pvp: Option<String>, |
| 79 | pub whitelist_enabled: Option<String>, | ||
| 77 | pub motd: Option<String>, | 80 | pub motd: Option<String>, |
| 78 | pub memory_mb: i32, | 81 | pub memory_mb: i32, |
| 79 | } | 82 | } |
| @@ -216,8 +219,8 @@ async fn edit_form( | |||
| 216 | let conn = state.db.conn(); | 219 | let conn = state.db.conn(); |
| 217 | conn.query_row( | 220 | conn.query_row( |
| 218 | "SELECT id, name, version, server_type, world_source, world_seed, level_type, | 221 | "SELECT id, name, version, server_type, world_source, world_seed, level_type, |
| 219 | has_modpack, memory_mb, difficulty, gamemode, max_players, pvp, motd, | 222 | has_modpack, memory_mb, difficulty, gamemode, max_players, pvp, whitelist_enabled, |
| 220 | rcon_password, is_active, created_at, updated_at | 223 | motd, rcon_password, is_active, created_at, updated_at |
| 221 | FROM instances WHERE id = ?1", | 224 | FROM instances WHERE id = ?1", |
| 222 | params![id], | 225 | params![id], |
| 223 | |row| { | 226 | |row| { |
| @@ -235,11 +238,12 @@ async fn edit_form( | |||
| 235 | gamemode: row.get(10)?, | 238 | gamemode: row.get(10)?, |
| 236 | max_players: row.get(11)?, | 239 | max_players: row.get(11)?, |
| 237 | pvp: row.get::<_, i32>(12)? != 0, | 240 | pvp: row.get::<_, i32>(12)? != 0, |
| 238 | motd: row.get(13)?, | 241 | whitelist_enabled: row.get::<_, i32>(13)? != 0, |
| 239 | rcon_password: row.get(14)?, | 242 | motd: row.get(14)?, |
| 240 | is_active: row.get::<_, i32>(15)? != 0, | 243 | rcon_password: row.get(15)?, |
| 241 | created_at: row.get(16)?, | 244 | is_active: row.get::<_, i32>(16)? != 0, |
| 242 | updated_at: row.get(17)?, | 245 | created_at: row.get(17)?, |
| 246 | updated_at: row.get(18)?, | ||
| 243 | }) | 247 | }) |
| 244 | }, | 248 | }, |
| 245 | ) | 249 | ) |
| @@ -272,6 +276,26 @@ async fn edit_form( | |||
| 272 | current_seed | 276 | current_seed |
| 273 | }; | 277 | }; |
| 274 | 278 | ||
| 279 | let (mod_count, rejected_count) = { | ||
| 280 | let conn = state.db.conn(); | ||
| 281 | let total: i64 = conn | ||
| 282 | .query_row( | ||
| 283 | "SELECT COUNT(*) FROM mods WHERE instance_id = ?1", | ||
| 284 | params![id], | ||
| 285 | |row| row.get(0), | ||
| 286 | ) | ||
| 287 | .unwrap_or(0); | ||
| 288 | let rejected: i64 = conn | ||
| 289 | .query_row( | ||
| 290 | "SELECT COUNT(*) FROM mods WHERE instance_id = ?1 AND accepted = 0", | ||
| 291 | params![id], | ||
| 292 | |row| row.get(0), | ||
| 293 | ) | ||
| 294 | .unwrap_or(0); | ||
| 295 | drop(conn); | ||
| 296 | (total, rejected) | ||
| 297 | }; | ||
| 298 | |||
| 275 | let template = EditInstanceTemplate { | 299 | let template = EditInstanceTemplate { |
| 276 | username: account.username.clone(), | 300 | username: account.username.clone(), |
| 277 | is_superuser: account.is_superuser, | 301 | is_superuser: account.is_superuser, |
| @@ -279,6 +303,8 @@ async fn edit_form( | |||
| 279 | instance, | 303 | instance, |
| 280 | current_seed, | 304 | current_seed, |
| 281 | max_memory_mb: state.config.max_memory_mb, | 305 | max_memory_mb: state.config.max_memory_mb, |
| 306 | mod_count, | ||
| 307 | rejected_count, | ||
| 282 | }; | 308 | }; |
| 283 | 309 | ||
| 284 | Ok(Html(template.render().unwrap_or_default())) | 310 | Ok(Html(template.render().unwrap_or_default())) |
| @@ -295,18 +321,20 @@ async fn edit_instance( | |||
| 295 | } | 321 | } |
| 296 | 322 | ||
| 297 | let pvp = form.pvp.as_deref() == Some("on"); | 323 | let pvp = form.pvp.as_deref() == Some("on"); |
| 324 | let whitelist_enabled = form.whitelist_enabled.as_deref() == Some("on"); | ||
| 298 | 325 | ||
| 299 | let is_active = { | 326 | let is_active = { |
| 300 | let conn = state.db.conn(); | 327 | let conn = state.db.conn(); |
| 301 | conn.execute( | 328 | conn.execute( |
| 302 | "UPDATE instances SET difficulty = ?1, gamemode = ?2, max_players = ?3, | 329 | "UPDATE instances SET difficulty = ?1, gamemode = ?2, max_players = ?3, |
| 303 | pvp = ?4, motd = ?5, memory_mb = ?6, updated_at = datetime('now') | 330 | pvp = ?4, whitelist_enabled = ?5, motd = ?6, memory_mb = ?7, |
| 304 | WHERE id = ?7", | 331 | updated_at = datetime('now') WHERE id = ?8", |
| 305 | params![ | 332 | params![ |
| 306 | form.difficulty, | 333 | form.difficulty, |
| 307 | form.gamemode, | 334 | form.gamemode, |
| 308 | form.max_players, | 335 | form.max_players, |
| 309 | pvp, | 336 | pvp, |
| 337 | whitelist_enabled, | ||
| 310 | form.motd, | 338 | form.motd, |
| 311 | form.memory_mb, | 339 | form.memory_mb, |
| 312 | id, | 340 | id, |
| @@ -390,8 +418,8 @@ async fn activate_instance( | |||
| 390 | 418 | ||
| 391 | conn.query_row( | 419 | conn.query_row( |
| 392 | "SELECT id, name, version, server_type, world_source, world_seed, level_type, | 420 | "SELECT id, name, version, server_type, world_source, world_seed, level_type, |
| 393 | has_modpack, memory_mb, difficulty, gamemode, max_players, pvp, motd, | 421 | has_modpack, memory_mb, difficulty, gamemode, max_players, pvp, whitelist_enabled, |
| 394 | rcon_password, is_active, created_at, updated_at | 422 | motd, rcon_password, is_active, created_at, updated_at |
| 395 | FROM instances WHERE id = ?1", | 423 | FROM instances WHERE id = ?1", |
| 396 | params![id], | 424 | params![id], |
| 397 | |row| { | 425 | |row| { |
| @@ -409,11 +437,12 @@ async fn activate_instance( | |||
| 409 | gamemode: row.get(10)?, | 437 | gamemode: row.get(10)?, |
| 410 | max_players: row.get(11)?, | 438 | max_players: row.get(11)?, |
| 411 | pvp: row.get::<_, i32>(12)? != 0, | 439 | pvp: row.get::<_, i32>(12)? != 0, |
| 412 | motd: row.get(13)?, | 440 | whitelist_enabled: row.get::<_, i32>(13)? != 0, |
| 413 | rcon_password: row.get(14)?, | 441 | motd: row.get(14)?, |
| 414 | is_active: row.get::<_, i32>(15)? != 0, | 442 | rcon_password: row.get(15)?, |
| 415 | created_at: row.get(16)?, | 443 | is_active: row.get::<_, i32>(16)? != 0, |
| 416 | updated_at: row.get(17)?, | 444 | created_at: row.get(17)?, |
| 445 | updated_at: row.get(18)?, | ||
| 417 | }) | 446 | }) |
| 418 | }, | 447 | }, |
| 419 | ) | 448 | ) |
