yggdrasil/pages/admin/system/
mod.rs1mod backup;
8mod db_status;
9mod export;
10mod server_status;
11mod sql_console;
12
13use backup::BackupTab;
14use db_status::DbStatusTab;
15use dioxus::prelude::*;
16use export::ExportTab;
17use server_status::ServerStatusTab;
18use sql_console::SqlConsoleTab;
19
20use crate::components::ui::FilterTabs;
21
22#[derive(Clone, Copy, PartialEq, Debug)]
24enum SystemTab {
25 DbStatus,
27 ServerStatus,
29 SqlConsole,
31 Export,
33 Backup,
35}
36
37impl SystemTab {
38 fn as_str(&self) -> &'static str {
41 match self {
42 SystemTab::DbStatus => "db_status",
43 SystemTab::ServerStatus => "server_status",
44 SystemTab::SqlConsole => "sql_console",
45 SystemTab::Export => "export",
46 SystemTab::Backup => "backup",
47 }
48 }
49
50 fn from_str(s: &str) -> Result<SystemTab, &'static str> {
53 match s {
54 "db_status" => Ok(SystemTab::DbStatus),
55 "server_status" => Ok(SystemTab::ServerStatus),
56 "sql_console" => Ok(SystemTab::SqlConsole),
57 "export" => Ok(SystemTab::Export),
58 "backup" => Ok(SystemTab::Backup),
59 _ => Err("unknown tab key"),
60 }
61 }
62}
63
64#[component]
66pub fn System() -> Element {
67 let mut active_tab = use_signal(|| SystemTab::DbStatus);
70
71 rsx! {
72 div { class: "animate-page-enter w-full max-w-7xl mx-auto space-y-6",
73 div { class: "flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-6 border-b border-[var(--color-paper-border)]/70 mb-6",
75 div {
76 h1 { class: "text-3xl sm:text-4xl font-extrabold tracking-tight text-[var(--color-paper-primary)]",
77 "系统面板"
78 }
79 p { class: "text-sm text-[var(--color-paper-secondary)] mt-1.5",
80 "数据库健康状态、服务器性能指标与系统诊断中心"
81 }
82 }
83 div { class: "flex items-center gap-2.5",
84 div { class: "inline-flex items-center gap-1.5 px-3.5 py-1.5 rounded-full text-xs font-mono bg-[var(--color-paper-entry)] text-[var(--color-paper-secondary)] border border-[var(--color-paper-border)]/70 shadow-2xs",
85 span { class: "w-1.5 h-1.5 rounded-full bg-[var(--color-paper-accent)]" }
86 span { "诊断引擎在线" }
87 }
88 }
89 }
90 FilterTabs {
93 items: vec![
94 ("db_status", "数据库状态"),
95 ("server_status", "服务器状态"),
96 ("sql_console", "SQL 控制台"),
97 ("export", "数据导出"),
98 ("backup", "备份恢复"),
99 ],
100 active_value: active_tab().as_str().to_string(),
101 on_change: move |v: String| {
102 active_tab.set(SystemTab::from_str(&v).unwrap_or(SystemTab::DbStatus));
104 },
105 }
106
107 for tab_key in std::iter::once(active_tab().as_str()) {
114 div { key: "{tab_key}", class: "animate-section-enter",
115 match active_tab() {
116 SystemTab::DbStatus => rsx! {
117 DbStatusTab {}
118 },
119 SystemTab::ServerStatus => rsx! {
120 ServerStatusTab {}
121 },
122 SystemTab::SqlConsole => rsx! {
123 SqlConsoleTab {}
124 },
125 SystemTab::Export => rsx! {
126 ExportTab {}
127 },
128 SystemTab::Backup => rsx! {
129 BackupTab {}
130 },
131 }
132 }
133 }
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::SystemTab;
141
142 #[test]
143 fn as_str_roundtrips_all_variants() {
144 for tab in [
146 SystemTab::DbStatus,
147 SystemTab::ServerStatus,
148 SystemTab::SqlConsole,
149 SystemTab::Export,
150 SystemTab::Backup,
151 ] {
152 let s = tab.as_str();
153 assert_eq!(
154 SystemTab::from_str(s),
155 Ok(tab),
156 "roundtrip failed for {tab:?}"
157 );
158 }
159 }
160
161 #[test]
162 fn as_str_returns_stable_keys() {
163 assert_eq!(SystemTab::DbStatus.as_str(), "db_status");
165 assert_eq!(SystemTab::ServerStatus.as_str(), "server_status");
166 assert_eq!(SystemTab::SqlConsole.as_str(), "sql_console");
167 assert_eq!(SystemTab::Export.as_str(), "export");
168 assert_eq!(SystemTab::Backup.as_str(), "backup");
169 }
170
171 #[test]
172 fn from_str_rejects_unknown_and_empty() {
173 assert!(SystemTab::from_str("nonsense").is_err());
174 assert!(SystemTab::from_str("").is_err());
175 assert!(SystemTab::from_str("DbStatus").is_err());
177 }
178}