104 lines
3.5 KiB
Rust
104 lines
3.5 KiB
Rust
const DASHBOARD_PRESET_MAX: usize = 20;
|
|
const DASHBOARD_PRESET_NAME_MAX: usize = 64;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct DashboardPreset {
|
|
name: String,
|
|
dashboard_density: String,
|
|
dashboard_widgets: Vec<String>,
|
|
dashboard_hidden_widgets: Vec<String>,
|
|
#[serde(default)]
|
|
dashboard_widget_sizes: Option<std::collections::BTreeMap<String, u8>>,
|
|
}
|
|
|
|
fn reconcile_dashboard_layout(
|
|
order: &mut Vec<String>,
|
|
hidden: &mut Vec<String>,
|
|
sizes: &mut Option<std::collections::BTreeMap<String, u8>>,
|
|
) {
|
|
let mut seen = std::collections::HashSet::new();
|
|
order.retain(|id| {
|
|
DASHBOARD_WIDGETS.iter().any(|widget| widget.id == *id) && seen.insert(id.clone())
|
|
});
|
|
let mut seen_hidden = std::collections::HashSet::new();
|
|
hidden.retain(|id| {
|
|
DASHBOARD_WIDGETS.iter().any(|widget| widget.id == *id) && seen_hidden.insert(id.clone())
|
|
});
|
|
if let Some(sizes) = sizes.as_mut() {
|
|
sizes.retain(|id, span| {
|
|
DASHBOARD_WIDGETS
|
|
.iter()
|
|
.any(|widget| widget.id == *id && valid_dashboard_span(&widget.display_type, *span))
|
|
});
|
|
}
|
|
for widget in DASHBOARD_WIDGETS.iter() {
|
|
if !seen.contains(&widget.id) {
|
|
order.push(widget.id.clone());
|
|
if widget.default_hidden && seen_hidden.insert(widget.id.clone()) {
|
|
hidden.push(widget.id.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn reconcile_dashboard_presets(presets: &mut [DashboardPreset]) {
|
|
for preset in presets {
|
|
reconcile_dashboard_layout(
|
|
&mut preset.dashboard_widgets,
|
|
&mut preset.dashboard_hidden_widgets,
|
|
&mut preset.dashboard_widget_sizes,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn valid_dashboard_preset_name(name: &str) -> bool {
|
|
!name.is_empty()
|
|
&& name.chars().count() <= DASHBOARD_PRESET_NAME_MAX
|
|
&& name.trim() == name
|
|
&& !name.chars().any(char::is_control)
|
|
}
|
|
|
|
fn valid_dashboard_preset(preset: &DashboardPreset) -> bool {
|
|
if !valid_dashboard_preset_name(&preset.name)
|
|
|| !matches!(preset.dashboard_density.as_str(), "normal" | "compact" | "ultra")
|
|
|| preset.dashboard_widgets.is_empty()
|
|
|| preset.dashboard_widgets.len() > DASHBOARD_WIDGETS.len()
|
|
|| preset.dashboard_hidden_widgets.len() > DASHBOARD_WIDGETS.len()
|
|
|| !valid_dashboard_sizes(&preset.dashboard_widget_sizes)
|
|
{
|
|
return false;
|
|
}
|
|
let mut order = std::collections::HashSet::new();
|
|
if preset.dashboard_widgets.iter().any(|id| {
|
|
!DASHBOARD_WIDGETS.iter().any(|widget| widget.id == *id) || !order.insert(id.as_str())
|
|
}) {
|
|
return false;
|
|
}
|
|
let mut hidden = std::collections::HashSet::new();
|
|
!preset.dashboard_hidden_widgets.iter().any(|id| {
|
|
!order.contains(id.as_str()) || !hidden.insert(id.as_str())
|
|
})
|
|
}
|
|
|
|
fn validate_dashboard_presets(presets: &[DashboardPreset]) -> Result<(), ApiError> {
|
|
if presets.len() > DASHBOARD_PRESET_MAX {
|
|
return Err(ApiError(
|
|
StatusCode::UNPROCESSABLE_ENTITY,
|
|
"too many dashboard presets".into(),
|
|
));
|
|
}
|
|
let mut names = std::collections::HashSet::new();
|
|
for preset in presets {
|
|
if !valid_dashboard_preset(preset)
|
|
|| !names.insert(preset.name.to_lowercase())
|
|
{
|
|
return Err(ApiError(
|
|
StatusCode::UNPROCESSABLE_ENTITY,
|
|
"invalid dashboard preset".into(),
|
|
));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|