flams_router_dashboard/
settings.rs1use flams_backend_types::ManagerCacheSize;
2use flams_router_base::{maybe_lazy, require_login};
3use flams_utils::settings::SettingsSpec;
4use flams_web_utils::components::wait_and_then_fn;
5use ftml_dom::utils::css::inject_css;
6use leptos::prelude::*;
7
8#[server(
9 prefix="/api",
10 endpoint="settings",
11 output=server_fn::codec::Json
12)]
13#[allow(clippy::unused_async)]
14pub async fn get_settings() -> Result<(SettingsSpec, bool), ServerFnError<String>> {
15 use flams_router_base::LoginState;
16 use flams_system::settings::Settings;
17 match LoginState::get_server() {
18 LoginState::Admin | LoginState::NoAccounts | LoginState::User { is_admin: true, .. } => {
19 let mut spec = Settings::get().as_spec();
20 if let Some(pw) = spec.server.admin_pwd.as_mut() {
21 *pw = "********".to_string();
22 }
23 if let Some(tk) = spec.gitlab.token.as_mut() {
24 *tk = "********".to_string().into_boxed_str();
25 }
26 if let Some(secret) = spec.gitlab.app_secret.as_mut() {
27 *secret = "********".to_string().into_boxed_str();
28 }
29 Ok((spec, flams_git::gl::GLInstance::global().has_loaded()))
30 }
31 _ => Err("Not logged in".to_string().into()),
32 }
33}
34
35#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
36pub struct Memory {
37 uris: ftml_uris::MemoryState,
38 terms: ftml_ontology::terms::TermCacheSize,
39 backend: ManagerCacheSize,
40 search: (usize, usize),
41}
42
43#[server(
44 prefix="/api",
45 endpoint="memory",
46 output=server_fn::codec::Json
47)]
48#[allow(clippy::unused_async)]
49pub async fn get_memory() -> Result<Memory, ServerFnError<String>> {
50 use flams_math_archives::backend::GlobalBackend;
51 use flams_router_base::LoginState;
52 match LoginState::get_server() {
53 LoginState::Admin | LoginState::NoAccounts | LoginState::User { is_admin: true, .. } => {
54 tokio::task::spawn_blocking(|| {
55 let backend = GlobalBackend.memory();
56 let uris = ftml_uris::get_memory_state();
57 let terms = ftml_ontology::terms::get_cache_size();
58 Ok(Memory {
59 backend,
60 uris,
61 terms,
62 search: flams_search::Searcher::get().size(),
63 })
64 })
65 .await
66 .map_err(|e| e.to_string())?
67 }
68 _ => Err("Not logged in".to_string().into()),
69 }
70}
71
72#[server(
73 prefix="/api",
74 endpoint="reload",
75 output=server_fn::codec::Json
76)]
77pub async fn reload() -> Result<(), ServerFnError<String>> {
78 use flams_math_archives::backend::GlobalBackend;
79 use flams_router_base::LoginState;
80 match LoginState::get_server() {
81 LoginState::Admin | LoginState::NoAccounts | LoginState::User { is_admin: true, .. } => {
82 let _ = tokio::task::spawn_blocking(move || {
83 GlobalBackend.reset::<flams_system::TokioEngine>();
84 ftml_uris::clear_memory();
85 ftml_ontology::terms::clear_term_cache();
86 })
87 .await;
88 let _ = tokio::task::spawn_blocking(|| {
89 for e in flams_system::iter::<flams_system::FlamsExtension>() {
90 (e.on_reload)();
91 }
92 });
93 Ok(())
94 }
95 _ => Err("Not logged in".to_string().into()),
96 }
97}
98
99maybe_lazy!(Settings = settings());
100
101fn settings() -> AnyView {
103 use ftml_component_utils::{Table, TableCell, TableRow};
104 require_login(Box::new(|| {
105 wait_and_then_fn(
106 || async {
107 Ok::<_, ServerFnError<String>>((get_settings().await?, get_memory().await?))
108 },
109 |((settings, gl), mem)| {
110 let loading = RwSignal::new(false);
111 let reload_act = flams_web_utils::components::message_action(
112 move |()| {
113 loading.set(true);
114 reload()
115 },
116 move |()| {
117 loading.set(false);
118 "success".to_string()
119 },
120 );
121 let r = view!(
122 <Table class="flams-settings-table">
123 <TableRow><TableCell><h2>"Status"</h2></TableCell><td/></TableRow>
124 {do_memory(mem)}
125 <TableRow>
126 <TableCell>""</TableCell>
127 <TableCell>{move || if loading.get() {
128 leptos::either::Either::Left(view!(<ftml_component_utils::Spinner/>))
129 } else {
130 leptos::either::Either::Right(view!(<button on:click=move |_| {reload_act.dispatch(());}>"Reload"</button>))
131 }
132 }</TableCell>
133 </TableRow>
134 <TableRow><TableCell><h2>"Settings"</h2></TableCell><td/></TableRow>
135 <TableRow><TableCell><h3>"General"</h3></TableCell><td/></TableRow>
136 <TableRow>
137 <TableCell class="flams-settings-col"><b>"MathHub"</b></TableCell>
138 <TableCell class="flams-settings-col">{settings.mathhubs.into_iter().map(|m| m.display().to_string() + " ").collect::<Vec<_>>()}</TableCell>
139 </TableRow>
140 <TableRow>
141 <TableCell class="flams-settings-col"><b>"Debug Mode"</b></TableCell>
142 <TableCell class="flams-settings-col">{settings.debug}</TableCell>
143 </TableRow>
144 <TableRow>
145 <TableCell class="flams-settings-col"><b>"Log Directory"</b></TableCell>
146 <TableCell class="flams-settings-col">{settings.log_dir.unwrap_or_else(|| unreachable!()).display().to_string()}</TableCell>
147 </TableRow>
148 <TableRow>
149 <TableCell class="flams-settings-col"><b>"Database Path"</b></TableCell>
150 <TableCell class="flams-settings-col">{settings.database.unwrap_or_else(|| unreachable!()).display().to_string()}</TableCell>
151 </TableRow>
152 <TableRow>
153 <TableCell class="flams-settings-col"><b>"Temp Directory"</b></TableCell>
154 <TableCell class="flams-settings-col">{settings.temp_dir.unwrap_or_else(|| unreachable!()).display().to_string()}</TableCell>
155 </TableRow>
156 <TableRow>
157 <TableCell class="flams-settings-col"><b>"Stack Size"</b></TableCell>
158 <TableCell class="flams-settings-col">{settings.stack_size}{if settings.stack_size.is_some() {"MB"} else {"(System default)"}}</TableCell>
159 </TableRow>
160 <TableRow><TableCell><h3>"Server"</h3></TableCell><td/></TableRow>
161 <TableRow>
162 <TableCell class="flams-settings-col"><b>"IP/Port"</b></TableCell>
163 <TableCell class="flams-settings-col">{settings.server.ip.unwrap_or_else(|| unreachable!())}":"{settings.server.port}</TableCell>
164 </TableRow>
165 <TableRow>
166 <TableCell class="flams-settings-col"><b>"Gitlab URL"</b></TableCell>
167 <TableCell class="flams-settings-col">{settings.gitlab.url.map_or_else(|| leptos::either::Either::Left("(None)".to_string()),|s|
168 leptos::either::Either::Right(view!({s.to_string()}{
169 if gl {
170 leptos::either::Either::Left(view!(" "<div style="color:green;display:inline;"><ftml_component_utils::icons::CheckmarkIcon/></div>))
171 } else {
172 leptos::either::Either::Right(view!(" "<div style="color:red;display:inline;"><ftml_component_utils::icons::XMarkIcon/></div>))
173 }
174 })
175 ))}</TableCell>
176 </TableRow>
177 <TableRow><TableCell><h3>"Build Queue"</h3></TableCell><td/></TableRow>
178 <TableRow>
179 <TableCell class="flams-settings-col"><b>"Threads:"</b></TableCell>
180 <TableCell class="flams-settings-col">{settings.buildqueue.num_threads}</TableCell>
181 </TableRow>
182 </Table>
183 ).into_any();
184 inject_css("flams-settings", include_str!("settings.css"));
185 r
186 },
187 )
188 }))
189}
190
191fn do_memory(mem: Memory) -> impl IntoView {
192 let total = mem.terms.total_bytes() + mem.uris.total_bytes() + mem.backend.total_bytes();
193
194 let total = total + mem.search.1;
195 macro_rules! disp {
196 ($name:literal = $num:expr;$bytes:expr) => {
197 view!(<tr>
198 <td class="flams-settings-col">{$name}</td>
199 <td class="flams-settings-col">{$num}" ("{disp!($bytes)}")"</td>
200 </tr>)
201 };
202 ($val:expr) => {
203 bytesize::ByteSize::b($val as u64)
204 .display()
205 .iec_short()
206 .to_string()
207 };
208 }
209
210 let search = disp!("Search Index" = mem.search.0;mem.search.1);
211 view! {
212 <tr>
213 <td class="flams-settings-col"><b>"Relations"</b></td>
214 <td class="flams-settings-col">{mem.backend.relations}</td>
215 </tr>
216 {search}
217 <tr><td/><td/></tr>
218 <tr><td><b>"Backend"</b></td></tr>
219 {disp!("Modules" = mem.backend.num_modules;mem.backend.modules_bytes)}
220 {disp!("Documents" = mem.backend.num_documents;mem.backend.documents_bytes)}
221 <tr>
222 <td class="flams-settings-col">"Total"</td>
223 <td class="flams-settings-col">{disp!(mem.backend.total_bytes())}</td>
224 </tr>
225 <tr><td><b>"URIs"</b></td></tr>
226 {disp!("Base URIs" = mem.uris.num_base_uris;mem.uris.base_uris_bytes)}
227 {disp!("IDs" = mem.uris.num_ids;mem.uris.ids_bytes)}
228 {disp!("Archive IDs" = mem.uris.num_archives;mem.uris.archives_bytes)}
229 {disp!("URI names" = mem.uris.num_uri_names;mem.uris.uri_names_bytes)}
230 {disp!("URI paths" = mem.uris.num_uri_paths;mem.uris.uri_paths_bytes)}
231 <tr>
232 <td class="flams-settings-col">"Total"</td>
233 <td class="flams-settings-col">{disp!(mem.uris.total_bytes())}</td>
234 </tr>
235 <tr><td><b>"Terms"</b></td></tr>
236 {disp!("Applications" = mem.terms.num_applications;mem.terms.applications_bytes)}
237 {disp!("Bindings" = mem.terms.num_bindings;mem.terms.bindings_bytes)}
238 {disp!("Records" = mem.terms.num_records;mem.terms.records_bytes)}
239 {disp!("Opaques" = mem.terms.num_opaques;mem.terms.opaques_bytes)}
240 <tr>
241 <td class="flams-settings-col">"Total"</td>
242 <td class="flams-settings-col">{disp!(mem.terms.total_bytes())}</td>
243 </tr>
244
245
246 <tr><td/><td/></tr>
247 <tr>
248 <td class="flams-settings-col"><b>"Total"</b></td>
249 <td class="flams-settings-col">{disp!(total)}</td>
250 </tr>
251 }
252}