This commit is contained in:
Mateusz Gruszczyński committed 2026-10-13 21:29:57 +02:00
1 parent bb025dcf44
commit b0f211555a
74 files changed
+6089 -556

No files matched your search

+57 -22
View File
@@ -18,6 +18,9 @@ fn default_admin_chart_style() -> String {
fn default_admin_button_style() -> String {
"normal".to_owned()
}
fn default_admin_corner_style() -> String {
"default".to_owned()
}
fn default_admin_table_density() -> String {
"normal".to_owned()
}
@@ -27,29 +30,34 @@ fn default_admin_dashboard_density() -> String {
fn default_admin_dashboard_widgets() -> Vec<String> {
DASHBOARD_WIDGETS
.iter()
.map(|value| (*value).to_owned())
.map(|widget| widget.id.clone())
.collect()
}
const STREAM_DASHBOARD_WIDGETS: &[&str] = &[
"stream_overview",
"stream_traffic",
"stream_sessions",
"stream_backend_health",
];
fn default_hidden_dashboard_widgets() -> Vec<String> {
STREAM_DASHBOARD_WIDGETS
DASHBOARD_WIDGETS
.iter()
.map(|id| (*id).to_owned())
.filter(|widget| widget.default_hidden)
.map(|widget| widget.id.clone())
.collect()
}
// Old accounts stored a complete 12-card order and often an explicit empty
// hidden list. Upgrade it in memory: append opt-in Stream cards as hidden.
fn migrate_dashboard_widgets(prefs: &mut AdminPreferences) {
for id in STREAM_DASHBOARD_WIDGETS {
if !prefs.dashboard_widgets.iter().any(|item| item == id) {
prefs.dashboard_widgets.push((*id).to_owned());
if !prefs.dashboard_hidden_widgets.iter().any(|item| item == id) {
prefs.dashboard_hidden_widgets.push((*id).to_owned());
// Preserve the user's saved order/visibility, drop identifiers unknown to this
// binary, and append new registry widgets according to their opt-in policy.
// Stored preferences from older/newer releases are normalized on read.
fn reconcile_dashboard_widgets(prefs: &mut AdminPreferences) {
let mut seen = std::collections::HashSet::new();
prefs.dashboard_widgets.retain(|id| {
DASHBOARD_WIDGETS.iter().any(|widget| widget.id == *id) && seen.insert(id.clone())
});
let mut seen_hidden = std::collections::HashSet::new();
prefs.dashboard_hidden_widgets.retain(|id| {
DASHBOARD_WIDGETS.iter().any(|widget| widget.id == *id) && seen_hidden.insert(id.clone())
});
for widget in DASHBOARD_WIDGETS.iter() {
if !seen.contains(&widget.id) {
prefs.dashboard_widgets.push(widget.id.clone());
if widget.default_hidden && seen_hidden.insert(widget.id.clone()) {
prefs.dashboard_hidden_widgets.push(widget.id.clone());
}
}
}
@@ -120,6 +128,8 @@ struct AdminPreferences {
chart_style: String,
#[serde(default = "default_admin_button_style")]
button_style: String,
#[serde(default = "default_admin_corner_style")]
corner_style: String,
#[serde(default = "default_admin_table_density")]
table_density: String,
#[serde(default = "default_admin_dashboard_density")]
@@ -176,6 +186,7 @@ impl Default for AdminPreferences {
time_format: default_admin_time_format(),
chart_style: default_admin_chart_style(),
button_style: default_admin_button_style(),
corner_style: default_admin_corner_style(),
table_density: default_admin_table_density(),
dashboard_density: default_admin_dashboard_density(),
dashboard_widgets: default_admin_dashboard_widgets(),
@@ -246,7 +257,10 @@ fn validate_admin_preferences(preferences: &AdminPreferences) -> Result<(), ApiE
"invalid stored-log sort column preference".into(),
));
}
if !matches!(preferences.theme.as_str(), "dark" | "light") {
if !matches!(
preferences.theme.as_str(),
"dark" | "ultra_dark" | "light" | "cream"
) {
return Err(ApiError(
StatusCode::UNPROCESSABLE_ENTITY,
"invalid administration theme preference".into(),
@@ -293,6 +307,15 @@ fn validate_admin_preferences(preferences: &AdminPreferences) -> Result<(), ApiE
"invalid administration chart style preference".into(),
));
}
if !matches!(
preferences.corner_style.as_str(),
"default" | "square" | "subtle" | "rounded" | "extra"
) {
return Err(ApiError(
StatusCode::UNPROCESSABLE_ENTITY,
"invalid administration corner style preference".into(),
));
}
if !matches!(preferences.button_style.as_str(), "normal" | "compact") {
return Err(ApiError(
StatusCode::UNPROCESSABLE_ENTITY,
@@ -327,7 +350,9 @@ fn validate_admin_preferences(preferences: &AdminPreferences) -> Result<(), ApiE
}
let mut seen = std::collections::HashSet::new();
for widget in &preferences.dashboard_widgets {
if !DASHBOARD_WIDGETS.contains(&widget.as_str()) || !seen.insert(widget.as_str()) {
if !DASHBOARD_WIDGETS.iter().any(|entry| entry.id == *widget)
|| !seen.insert(widget.as_str())
{
return Err(ApiError(
StatusCode::UNPROCESSABLE_ENTITY,
"invalid administration dashboard widget order".into(),
@@ -342,7 +367,9 @@ fn validate_admin_preferences(preferences: &AdminPreferences) -> Result<(), ApiE
}
let mut seen = std::collections::HashSet::new();
for widget in &preferences.dashboard_hidden_widgets {
if !DASHBOARD_WIDGETS.contains(&widget.as_str()) || !seen.insert(widget.as_str()) {
if !DASHBOARD_WIDGETS.iter().any(|entry| entry.id == *widget)
|| !seen.insert(widget.as_str())
{
return Err(ApiError(
StatusCode::UNPROCESSABLE_ENTITY,
"invalid administration hidden dashboard widgets".into(),
@@ -418,7 +445,7 @@ async fn preferences(
let preferences = match stored {
Some(document) => match serde_json::from_str::<AdminPreferences>(&document) {
Ok(mut preferences) => {
migrate_dashboard_widgets(&mut preferences);
reconcile_dashboard_widgets(&mut preferences);
if validate_admin_preferences(&preferences).is_ok() {
preferences
} else {
@@ -441,7 +468,15 @@ async fn update_preferences(
Extension(session): Extension<Session>,
input: Result<Json<AdminPreferences>, axum::extract::rejection::JsonRejection>,
) -> Result<Json<AdminPreferences>, ApiError> {
let Json(preferences) = input.map_err(public::invalid_json)?;
let Json(mut preferences) = input.map_err(public::invalid_json)?;
// Do not rewrite the caller's saved order on PUT; drop only stale IDs.
// Legacy partial lists are expanded on the next GET, as before.
preferences
.dashboard_widgets
.retain(|id| DASHBOARD_WIDGETS.iter().any(|widget| widget.id == *id));
preferences
.dashboard_hidden_widgets
.retain(|id| DASHBOARD_WIDGETS.iter().any(|widget| widget.id == *id));
validate_admin_preferences(&preferences)?;
let document = serde_json::to_string(&preferences).map_err(auth::internal)?;
if !state