Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
- server-wide stats page has no PMC/Scav raid breakdown (per-user profile already tracks both)
- restarting a headless client from the main headless page causes you to end up at that headless' info page
- headless client start/stop/restart buttons on `/quma/headless` go past the card length
- currency items (USD, EUR) displayed as roubles instead of as currency balances (Stash)
- typo in update changelog: versions url should use `/#versions` hash anchor, not `/versions` path segment (current URL 404s on Forge)
- no auto-refresh when scaling/converging clients
- typo in update changelog: versions url should be like this `https://forge.sp-tarkov.com/mod/2310/wtt-commonlib/#versions` (`#version` is the important part)
- mod queue allows duplicate pending operations (operations on mods should be exlcusive in the queue)
- spt server profile creation is broken on user signup

## Convoy
- user config file sync
Expand All @@ -27,6 +28,7 @@
## Core Architecture
- `WebError` always returns HTML even for API endpoints (`error.rs`)
- blocking filesystem reads on async runtime (partially fixed — `svm::save_section` uses `web::block`, many others don't)
- standardize all overlays ontop of host overlayfs instead of podman managed

## Headless Client
- too-many-arguments on convergence functions (clippy lint suppressed)
Expand All @@ -40,6 +42,7 @@
- headless client actions
- container cpu stats don't work (`cpu_percent` is never populated — always `None`; memory stats work)
- `ensure_fika_headless` writes to base headless dir — should move to `mod_overlay()` for proper layering
- headless client numa scheduling section has shitty formatting

## Robustness
- no mutual exclusion on server start/stop/restart (`server.rs`)
Expand Down
2 changes: 1 addition & 1 deletion src/assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ button.htmx-request .icon { animation: spin 0.8s linear infinite; }
.tab-bar .tab:hover { color: var(--text); }

.profile-card { line-height: 1.5; }
.action-buttons { display: flex; gap: 0.25rem; flex-wrap: wrap; }
.action-buttons { display: flex; gap: 0.25rem; flex-wrap: wrap; min-width: 10rem; }
.reset-link-display { display: flex; gap: 0.25rem; align-items: center; }
.reset-link-display input { font-family: monospace; font-size: 0.85rem; }

Expand Down
2 changes: 1 addition & 1 deletion src/web/handlers/clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ pub async fn client_restart(
return Ok(HttpResponse::NoContent().finish());
}
Ok(HttpResponse::SeeOther()
.insert_header(("Location", format!("/quma/headless/{index}")))
.insert_header(("Location", "/quma/headless"))
.finish())
}

Expand Down
40 changes: 39 additions & 1 deletion src/web/handlers/profiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,18 @@ struct StashItemDisplay {
short_name: String,
count: i64,
total_value: i64,
currency_type: Option<String>, // "RUB", "USD", or "EUR" for currency items
}

struct StashCategoryDisplay {
name: String,
items: Vec<StashItemDisplay>,
total_value: i64,
item_count: usize,
// Per-currency subtotals (only populated for Money category)
rub_total: i64,
usd_total: i64,
eur_total: i64,
}

#[derive(Template)]
Expand Down Expand Up @@ -478,6 +483,14 @@ pub async fn stash_partial(
let unit_price = state.game_data.item_price(tpl).unwrap_or(0);
let total_value = unit_price.saturating_mul(*count);

// ponytail: currency type detection by template ID
let currency_type = match tpl.as_str() {
"5449016a4bdc2d6f08b456f6" => Some("RUB".to_string()),
"5696686a4bdc2da3298b456a" => Some("USD".to_string()),
"569668774bdc2da2298b4568" => Some("EUR".to_string()),
_ => None,
};

all_categories_set.insert(category.clone());

if !search_lower.is_empty()
Expand All @@ -499,6 +512,7 @@ pub async fn stash_partial(
short_name,
count: *count,
total_value,
currency_type,
});
}

Expand Down Expand Up @@ -535,17 +549,41 @@ pub async fn stash_partial(
}
}

// Build sorted category list
// Build sorted category list with per-currency subtotals
let mut categories: Vec<StashCategoryDisplay> = items_by_category
.into_iter()
.map(|(name, items)| {
let total_value = items.iter().map(|i| i.total_value).sum();
let item_count = items.len();
// ponytail: only calculate currency subtotals for Money category
let (rub_total, usd_total, eur_total) = if name == "Money" {
let rub = items
.iter()
.filter(|i| i.currency_type.as_deref() == Some("RUB"))
.map(|i| i.count)
.sum();
let usd = items
.iter()
.filter(|i| i.currency_type.as_deref() == Some("USD"))
.map(|i| i.count)
.sum();
let eur = items
.iter()
.filter(|i| i.currency_type.as_deref() == Some("EUR"))
.map(|i| i.count)
.sum();
(rub, usd, eur)
} else {
(0, 0, 0)
};
StashCategoryDisplay {
name,
items,
total_value,
item_count,
rub_total,
usd_total,
eur_total,
}
})
.collect();
Expand Down
5 changes: 5 additions & 0 deletions src/web/template_filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ pub fn format_roubles_i64(value: &i64, _env: &dyn askama::Values) -> askama::Res
Ok(format!("₽{}", format_roubles_value(*value)))
}

#[askama::filter_fn]
pub fn format_number(value: &i64, _env: &dyn askama::Values) -> askama::Result<String> {
Ok(format_roubles_value(*value))
}

/// Truncate a datetime string to just the date+time portion (first 19 chars: `YYYY-MM-DD HH:MM:SS`).
#[askama::filter_fn]
pub fn format_datetime(s: &str, _env: &dyn askama::Values) -> askama::Result<String> {
Expand Down
2 changes: 1 addition & 1 deletion templates/mods/partials/updates_carousel.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ <h2 style="margin: 0">Updates Available ({{ total }})</h2>
<span>{{ e.current_version }}</span>
<span style="color: var(--success)"> → {{ e.new_version }}</span>
{% if let Some(slug) = &e.slug %}
<a href="https://forge.sp-tarkov.com/mod/{{ e.forge_mod_id }}/{{ slug }}/versions" target="_blank" rel="noopener" class="text-sm text-muted" style="margin-left:0.5rem">changelog ↗</a>
<a href="https://forge.sp-tarkov.com/mod/{{ e.forge_mod_id }}/{{ slug }}/#versions" target="_blank" rel="noopener" class="text-sm text-muted" style="margin-left:0.5rem">changelog ↗</a>
{% endif %}
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions templates/profiles/partials/stash.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
{% for cat in categories %}
<details open style="margin-bottom:0.75rem">
<summary style="cursor:pointer;font-weight:600;padding:0.5rem 0">
{{ cat.name }} <span class="text-muted text-sm">({{ cat.item_count }} items · {{ cat.total_value|format_roubles_i64 }})</span>
{{ cat.name }} <span class="text-muted text-sm">({{ cat.item_count }} items{% if cat.name == "Money" %}{% if cat.rub_total > 0 %} · ₽ {{ cat.rub_total|format_number }}{% endif %}{% if cat.usd_total > 0 %} · $ {{ cat.usd_total|format_number }}{% endif %}{% if cat.eur_total > 0 %} · € {{ cat.eur_total|format_number }}{% endif %}{% else %} · {{ cat.total_value|format_roubles_i64 }}{% endif %})</span>
</summary>
<table>
<thead>
Expand All @@ -64,7 +64,7 @@
<tr>
<td title="{{ item.name }}">{{ item.short_name }}</td>
<td style="text-align:right">{% if item.count > 1 %}{{ item.count }}{% else %}1{% endif %}</td>
<td style="text-align:right">{{ item.total_value|format_roubles_i64 }}</td>
<td style="text-align:right">{% match item.currency_type %}{% when Some with (c) %}{% if c == "USD" %}${% else if c == "EUR" %}€{% else %}₽{% endif %} {{ item.count|format_number }}{% when None %}{{ item.total_value|format_roubles_i64 }}{% endmatch %}</td>
</tr>
{% endfor %}
</tbody>
Expand Down
Loading