flams_router_dashboard/
settings.rs

1use flams_backend_types::ManagerCacheSize;
2use flams_router_base::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}
41
42#[server(
43  prefix="/api",
44  endpoint="memory",
45  output=server_fn::codec::Json
46)]
47#[allow(clippy::unused_async)]
48pub async fn get_memory() -> Result<Memory, ServerFnError<String>> {
49    use flams_math_archives::backend::GlobalBackend;
50    use flams_router_base::LoginState;
51    match LoginState::get_server() {
52        LoginState::Admin | LoginState::NoAccounts | LoginState::User { is_admin: true, .. } => {
53            tokio::task::spawn_blocking(|| {
54                let backend = GlobalBackend.memory();
55                let uris = ftml_uris::get_memory_state();
56                let terms = ftml_ontology::terms::get_cache_size();
57                Ok(Memory {
58                    backend,
59                    uris,
60                    terms,
61                })
62            })
63            .await
64            .map_err(|e| e.to_string())?
65        }
66        _ => Err("Not logged in".to_string().into()),
67    }
68}
69
70#[server(
71  prefix="/api",
72  endpoint="reload",
73  output=server_fn::codec::Json
74)]
75pub async fn reload() -> Result<(), ServerFnError<String>> {
76    use flams_math_archives::backend::GlobalBackend;
77    use flams_router_base::LoginState;
78    match LoginState::get_server() {
79        LoginState::Admin | LoginState::NoAccounts | LoginState::User { is_admin: true, .. } => {
80            let _ = tokio::task::spawn_blocking(move || {
81                GlobalBackend.reset::<flams_system::TokioEngine>();
82                ftml_uris::clear_memory();
83                ftml_ontology::terms::clear_term_cache();
84            })
85            .await;
86            Ok(())
87        }
88        _ => Err("Not logged in".to_string().into()),
89    }
90}
91
92#[component]
93pub(super) fn Settings() -> AnyView {
94    use thaw::Table;
95    inject_css("flams-settings", include_str!("settings.css"));
96    require_login(Box::new(|| {
97        wait_and_then_fn(
98            || async {
99                Ok::<_, ServerFnError<String>>((get_settings().await?, get_memory().await?))
100            },
101            |((settings, gl), mem)| {
102                let loading = RwSignal::new(false);
103                let reload_act = flams_web_utils::components::message_action(
104                    move |()| {
105                        loading.set(true);
106                        reload()
107                    },
108                    move |()| {
109                        loading.set(false);
110                        "success".to_string()
111                    },
112                );
113                view!(
114                  <Table class="flams-settings-table"><thead/><tbody>
115                    <tr><td><h2>"Status"</h2></td><td/></tr>
116                    {do_memory(mem)}
117                      <tr>
118                        <td></td>
119                        <td>{move || if loading.get() {
120                          leptos::either::Either::Left(view!(<flams_web_utils::components::Spinner/>))
121                        } else {
122                          leptos::either::Either::Right(view!(<button on:click=move |_| {reload_act.dispatch(());}>"Reload"</button>))
123                        }
124                      }</td>
125                      </tr>
126                    <tr><td><h2>"Settings"</h2></td><td/></tr>
127                      <tr><td><h3>"General"</h3></td><td/></tr>
128                        <tr>
129                          <td class="flams-settings-col"><b>"MathHub"</b></td>
130                          <td class="flams-settings-col">{settings.mathhubs.into_iter().map(|m| m.display().to_string() + " ").collect::<Vec<_>>()}</td>
131                        </tr>
132                        <tr>
133                          <td class="flams-settings-col"><b>"Debug Mode"</b></td>
134                          <td class="flams-settings-col">{settings.debug}</td>
135                        </tr>
136                        <tr>
137                          <td class="flams-settings-col"><b>"Log Directory"</b></td>
138                          <td class="flams-settings-col">{settings.log_dir.unwrap_or_else(|| unreachable!()).display().to_string()}</td>
139                        </tr>
140                        <tr>
141                          <td class="flams-settings-col"><b>"Database Path"</b></td>
142                          <td class="flams-settings-col">{settings.database.unwrap_or_else(|| unreachable!()).display().to_string()}</td>
143                        </tr>
144                        <tr>
145                          <td class="flams-settings-col"><b>"Temp Directory"</b></td>
146                          <td class="flams-settings-col">{settings.temp_dir.unwrap_or_else(|| unreachable!()).display().to_string()}</td>
147                        </tr>
148                        <tr>
149                          <td class="flams-settings-col"><b>"Stack Size"</b></td>
150                          <td class="flams-settings-col">{(settings.stack_size)}{if settings.stack_size.is_some() {"MB"} else {"(System default)"}}</td>
151                        </tr>
152                      <tr><td><h3>"Server"</h3></td><td/></tr>
153                        <tr>
154                          <td class="flams-settings-col"><b>"IP/Port"</b></td>
155                          <td class="flams-settings-col">{settings.server.ip.unwrap_or_else(|| unreachable!())}":"{settings.server.port}</td>
156                        </tr>
157                        <tr>
158                          <td class="flams-settings-col"><b>"Gitlab URL"</b></td>
159                          <td class="flams-settings-col">{settings.gitlab.url.map_or_else(|| leptos::either::Either::Left("(None)".to_string()),|s|
160                            leptos::either::Either::Right(view!({s.to_string()}{
161                              if gl {
162                                leptos::either::Either::Left(view!(" "<div style="color:green;display:inline;"><thaw::Icon icon=icondata_ai::AiCheckOutlined/></div>))
163                              } else {
164                                leptos::either::Either::Right(view!(" "<div style="color:red;display:inline;"><thaw::Icon icon=icondata_ai::AiCloseOutlined/></div>))
165                              }
166                            })
167                          ))}</td>
168                        </tr>
169                      <tr><td><h3>"Build Queue"</h3></td><td/></tr>
170                        <tr>
171                          <td class="flams-settings-col"><b>"Threads:"</b></td>
172                          <td class="flams-settings-col">{settings.buildqueue.num_threads}</td>
173                        </tr>
174                    </tbody></Table>
175                ).into_any()
176            },
177        )
178    }))
179}
180
181fn do_memory(mem: Memory) -> impl IntoView {
182    let total = mem.terms.total_bytes() + mem.uris.total_bytes() + mem.backend.total_bytes();
183    macro_rules! disp {
184        ($name:literal = $num:expr;$bytes:expr) => {
185            view!(<tr>
186                <td class="flams-settings-col">{$name}</td>
187                <td class="flams-settings-col">{$num}" ("{disp!($bytes)}")"</td>
188            </tr>)
189        };
190        ($val:expr) => {
191            bytesize::ByteSize::b($val as u64)
192                .display()
193                .iec_short()
194                .to_string()
195        };
196    }
197    view! {
198        <tr>
199            <td class="flams-settings-col"><b>"Relations"</b></td>
200            <td class="flams-settings-col">{mem.backend.relations}</td>
201        </tr>
202        <tr><td/><td/></tr>
203        <tr><td><b>"Backend"</b></td></tr>
204            {disp!("Modules" = mem.backend.num_modules;mem.backend.modules_bytes)}
205            {disp!("Documents" = mem.backend.num_documents;mem.backend.documents_bytes)}
206            <tr>
207                <td class="flams-settings-col">"Total"</td>
208                <td class="flams-settings-col">{disp!(mem.backend.total_bytes())}</td>
209            </tr>
210        <tr><td><b>"URIs"</b></td></tr>
211            {disp!("Base URIs" = mem.uris.num_base_uris;mem.uris.base_uris_bytes)}
212            {disp!("IDs" = mem.uris.num_ids;mem.uris.ids_bytes)}
213            {disp!("Archive IDs" = mem.uris.num_archives;mem.uris.archives_bytes)}
214            {disp!("URI names" = mem.uris.num_uri_names;mem.uris.uri_names_bytes)}
215            {disp!("URI paths" = mem.uris.num_uri_paths;mem.uris.uri_paths_bytes)}
216            <tr>
217                <td class="flams-settings-col">"Total"</td>
218                <td class="flams-settings-col">{disp!(mem.uris.total_bytes())}</td>
219            </tr>
220        <tr><td><b>"Terms"</b></td></tr>
221            {disp!("Applications" = mem.terms.num_applications;mem.terms.applications_bytes)}
222            {disp!("Bindings" = mem.terms.num_bindings;mem.terms.bindings_bytes)}
223            {disp!("Records" = mem.terms.num_records;mem.terms.records_bytes)}
224            {disp!("Opaques" = mem.terms.num_opaques;mem.terms.opaques_bytes)}
225            <tr>
226                <td class="flams-settings-col">"Total"</td>
227                <td class="flams-settings-col">{disp!(mem.terms.total_bytes())}</td>
228            </tr>
229
230
231        <tr><td/><td/></tr>
232        <tr>
233            <td class="flams-settings-col"><b>"Total"</b></td>
234            <td class="flams-settings-col">{disp!(total)}</td>
235        </tr>
236    }
237}