Skip to main content

systemprompt_models/artifacts/dashboard/
section_data.rs

1//! Per-section data payloads for dashboard sections.
2//!
3//! Each struct here is the typed body of one dashboard section kind: metric
4//! cards ([`MetricsCardsData`]/[`MetricCard`]), charts ([`ChartSectionData`]),
5//! tables ([`TableSectionData`]), service/database status
6//! ([`StatusSectionData`]), and ranked lists ([`ListSectionData`]).
7
8use super::super::chart::ChartDataset;
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
13pub struct MetricsCardsData {
14    pub cards: Vec<MetricCard>,
15}
16
17impl MetricsCardsData {
18    pub const fn new(cards: Vec<MetricCard>) -> Self {
19        Self { cards }
20    }
21
22    pub fn add_card(mut self, card: MetricCard) -> Self {
23        self.cards.push(card);
24        self
25    }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
29pub struct MetricCard {
30    pub title: String,
31    pub value: String,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub subtitle: Option<String>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub icon: Option<String>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub status: Option<MetricStatus>,
38}
39
40#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, Default)]
41#[serde(rename_all = "lowercase")]
42pub enum MetricStatus {
43    Success,
44    Warning,
45    Error,
46    #[default]
47    Info,
48}
49
50impl std::str::FromStr for MetricStatus {
51    type Err = String;
52
53    fn from_str(s: &str) -> Result<Self, Self::Err> {
54        match s.to_lowercase().as_str() {
55            "success" | "healthy" | "ok" | "active" => Ok(Self::Success),
56            "warning" | "degraded" => Ok(Self::Warning),
57            "error" | "failed" | "critical" => Ok(Self::Error),
58            "info" | "unknown" => Ok(Self::Info),
59            _ => Err(format!("Invalid metric status: {s}")),
60        }
61    }
62}
63
64impl MetricCard {
65    pub fn new(title: impl Into<String>, value: impl Into<String>) -> Self {
66        Self {
67            title: title.into(),
68            value: value.into(),
69            subtitle: None,
70            icon: None,
71            status: None,
72        }
73    }
74
75    pub fn with_subtitle(mut self, subtitle: impl Into<String>) -> Self {
76        self.subtitle = Some(subtitle.into());
77        self
78    }
79
80    pub fn with_icon(mut self, icon: impl Into<String>) -> Self {
81        self.icon = Some(icon.into());
82        self
83    }
84
85    pub const fn with_status(mut self, status: MetricStatus) -> Self {
86        self.status = Some(status);
87        self
88    }
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
92pub struct ChartSectionData {
93    pub chart_type: String,
94    pub labels: Vec<String>,
95    pub datasets: Vec<ChartDataset>,
96}
97
98impl ChartSectionData {
99    pub fn new(
100        chart_type: impl Into<String>,
101        labels: Vec<String>,
102        datasets: Vec<ChartDataset>,
103    ) -> Self {
104        Self {
105            chart_type: chart_type.into(),
106            labels,
107            datasets,
108        }
109    }
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
113pub struct TableSectionData {
114    pub columns: Vec<String>,
115    pub rows: Vec<serde_json::Value>,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub sortable: Option<bool>,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub default_sort: Option<SortConfig>,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
123pub struct SortConfig {
124    pub column: String,
125    pub order: String,
126}
127
128impl TableSectionData {
129    pub const fn new(columns: Vec<String>, rows: Vec<serde_json::Value>) -> Self {
130        Self {
131            columns,
132            rows,
133            sortable: None,
134            default_sort: None,
135        }
136    }
137
138    pub const fn with_sortable(mut self, sortable: bool) -> Self {
139        self.sortable = Some(sortable);
140        self
141    }
142
143    pub fn with_default_sort(
144        mut self,
145        column: impl Into<String>,
146        order: impl Into<String>,
147    ) -> Self {
148        self.default_sort = Some(SortConfig {
149            column: column.into(),
150            order: order.into(),
151        });
152        self
153    }
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
157pub struct StatusSectionData {
158    pub services: Vec<ServiceStatus>,
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub database: Option<DatabaseStatus>,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub recent_errors: Option<ErrorCounts>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
166pub struct ServiceStatus {
167    pub name: String,
168    pub status: String,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub uptime: Option<String>,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
174pub struct DatabaseStatus {
175    pub size_mb: f64,
176    pub status: String,
177}
178
179#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
180pub struct ErrorCounts {
181    pub critical: i32,
182    pub error: i32,
183    pub warn: i32,
184}
185
186impl StatusSectionData {
187    pub const fn new(services: Vec<ServiceStatus>) -> Self {
188        Self {
189            services,
190            database: None,
191            recent_errors: None,
192        }
193    }
194
195    pub fn with_database(mut self, status: DatabaseStatus) -> Self {
196        self.database = Some(status);
197        self
198    }
199
200    pub const fn with_error_counts(mut self, counts: ErrorCounts) -> Self {
201        self.recent_errors = Some(counts);
202        self
203    }
204}
205
206impl ServiceStatus {
207    pub fn new(name: impl Into<String>, status: impl Into<String>) -> Self {
208        Self {
209            name: name.into(),
210            status: status.into(),
211            uptime: None,
212        }
213    }
214
215    pub fn with_uptime(mut self, uptime: impl Into<String>) -> Self {
216        self.uptime = Some(uptime.into());
217        self
218    }
219}
220
221impl DatabaseStatus {
222    pub fn new(size_mb: f64, status: impl Into<String>) -> Self {
223        Self {
224            size_mb,
225            status: status.into(),
226        }
227    }
228}
229
230impl ErrorCounts {
231    pub const fn new(critical: i32, error: i32, warn: i32) -> Self {
232        Self {
233            critical,
234            error,
235            warn,
236        }
237    }
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
241pub struct ListSectionData {
242    pub lists: Vec<ItemList>,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
246pub struct ItemList {
247    pub title: String,
248    pub items: Vec<ListItem>,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
252pub struct ListItem {
253    pub rank: i32,
254    pub label: String,
255    pub value: String,
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub badge: Option<String>,
258}
259
260impl ListSectionData {
261    pub const fn new(lists: Vec<ItemList>) -> Self {
262        Self { lists }
263    }
264}
265
266impl ItemList {
267    pub fn new(title: impl Into<String>, items: Vec<ListItem>) -> Self {
268        Self {
269            title: title.into(),
270            items,
271        }
272    }
273}
274
275impl ListItem {
276    pub fn new(rank: i32, label: impl Into<String>, value: impl Into<String>) -> Self {
277        Self {
278            rank,
279            label: label.into(),
280            value: value.into(),
281            badge: None,
282        }
283    }
284
285    pub fn with_badge(mut self, badge: impl Into<String>) -> Self {
286        self.badge = Some(badge.into());
287        self
288    }
289}