215 lines
6.7 KiB
Rust
215 lines
6.7 KiB
Rust
use leptos::prelude::*;
|
|
use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title};
|
|
use leptos_router::{
|
|
components::{A, ParentRoute, Route, Router, Routes},
|
|
hooks::use_query_map,
|
|
path
|
|
};
|
|
|
|
use crate::users::User;
|
|
|
|
#[server]
|
|
pub async fn test_retrieve() -> Result<u64, ServerFnError> {
|
|
use leptos::server_fn::error::NoCustomError;
|
|
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
|
|
|
|
let start = std::time::SystemTime::now();
|
|
let since_the_epoch = start
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.map_err(|e| ServerFnError::<NoCustomError>::ServerError(e.to_string()))?
|
|
.as_secs();
|
|
|
|
Ok(since_the_epoch)
|
|
}
|
|
|
|
#[server]
|
|
pub async fn me() -> Result<Option<User>, ServerFnError> {
|
|
let user = crate::db::user::get_auth_session().await?;
|
|
|
|
Ok(user.map(|user| User {
|
|
user_id: user.user_id,
|
|
user_name: user.user_name
|
|
}))
|
|
}
|
|
|
|
#[server]
|
|
async fn login(username: String, password: String, next: String) -> Result<(), ServerFnError> {
|
|
crate::db::user::create_auth_session(username, password).await?;
|
|
|
|
leptos_axum::redirect(&next);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[server]
|
|
async fn logout() -> Result<(), ServerFnError> {
|
|
crate::db::user::destroy_auth_session().await
|
|
}
|
|
|
|
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
|
view! {
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
<AutoReload options=options.clone() />
|
|
<HydrationScripts options/>
|
|
<MetaTags/>
|
|
</head>
|
|
<body>
|
|
<App/>
|
|
</body>
|
|
</html>
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
pub fn App() -> impl IntoView {
|
|
provide_meta_context();
|
|
|
|
let login = ServerAction::<Login>::new();
|
|
|
|
let (user_res, set_user_res) = signal(None);
|
|
|
|
let user = Resource::new(
|
|
move || {
|
|
login.version().get()
|
|
},
|
|
|_| async {
|
|
#[cfg(feature = "ssr")]
|
|
tracing::info!("Checking user account");
|
|
#[cfg(feature = "hydrate")]
|
|
leptos::logging::log!("Checking user account");
|
|
|
|
me().await
|
|
}
|
|
);
|
|
|
|
Effect::new(move || {
|
|
let u = user.get();
|
|
set_user_res(u.map(|u2| u2.ok()).flatten().flatten());
|
|
});
|
|
|
|
provide_context(user_res);
|
|
|
|
Effect::new(move || {
|
|
leptos::logging::log!("User resource: {:?}", user.get());
|
|
});
|
|
|
|
crate::beacons::provide_beacon_resources();
|
|
|
|
view! {
|
|
<Stylesheet id="leptos" href="/pkg/sparse-server.css"/>
|
|
|
|
<Title text="Sparse Control"/>
|
|
|
|
<Router>
|
|
<nav>
|
|
<h1>"Sparse control"</h1>
|
|
<A href="/">"Home"</A>
|
|
<Suspense fallback=|| ()>
|
|
{move || user
|
|
.get()
|
|
.map(|err| err.ok())
|
|
.flatten()
|
|
.flatten()
|
|
.map(|_| view! {
|
|
<A href="/beacons">"Beacon management"</A>
|
|
<A href="/users">"Users"</A>
|
|
<a
|
|
href="#"
|
|
on:click=move |_| {
|
|
leptos::task::spawn_local(async move {
|
|
let _ = logout().await;
|
|
user.refetch();
|
|
});
|
|
}
|
|
>
|
|
"Log out"
|
|
</a>
|
|
})}
|
|
{move || user
|
|
.get()
|
|
.map(|err| err.ok())
|
|
.flatten()
|
|
.flatten()
|
|
.is_none()
|
|
.then(|| view! {
|
|
<A href="/login">"Log in"</A>
|
|
})}
|
|
</Suspense>
|
|
</nav>
|
|
|
|
<crate::beacons::BeaconSidebar />
|
|
|
|
<Routes fallback=|| "Page not found.".into_view()>
|
|
<Route path=path!("users") view=crate::users::UserView />
|
|
<Route path=path!("login") view=move || view! { <LoginPage login/> }/>
|
|
<ParentRoute path=path!("beacons") view=crate::beacons::BeaconView>
|
|
<Route path=path!("categories") view=crate::beacons::CategoriesView/>
|
|
<Route path=path!("commands") view=crate::beacons::CommandsView/>
|
|
<Route path=path!("configs") view=crate::beacons::ConfigsView/>
|
|
<Route path=path!("templates") view=crate::beacons::TemplatesView/>
|
|
<Route path=path!("instances") view=crate::beacons::InstancesView/>
|
|
<Route path=path!("listeners") view=crate::beacons::ListenersView/>
|
|
<Route path=path!("") view=|| view! {
|
|
<p>"Select a menu item on the left to get started"</p>
|
|
}/>
|
|
</ParentRoute>
|
|
<Route path=path!("") view=HomePage/>
|
|
</Routes>
|
|
</Router>
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
fn LoginPage(login: ServerAction<Login>) -> impl IntoView {
|
|
let next = move || use_query_map().read().get("next").unwrap_or("/".to_string());
|
|
|
|
view! {
|
|
<main class="login">
|
|
{move || match login.value().get() {
|
|
Some(Ok(_)) => None,
|
|
None => None,
|
|
Some(Err(e)) => Some(view! {
|
|
<div>
|
|
"Error signing in: "
|
|
{format!("{e:?}")}
|
|
</div>
|
|
})
|
|
}}
|
|
<ActionForm action=login>
|
|
<label>"Username"</label>
|
|
<input type="text" name="username" />
|
|
<label>"Password"</label>
|
|
<input type="password" name="password" />
|
|
<div></div>
|
|
<input type="submit" value="Submit" />
|
|
<input type="hidden" value=next name="next" />
|
|
</ActionForm>
|
|
</main>
|
|
}
|
|
}
|
|
|
|
/// Renders the home page of your application.
|
|
#[component]
|
|
fn HomePage() -> impl IntoView {
|
|
view! {
|
|
<main class="main">
|
|
<h1>"Welcome to sparse!"</h1>
|
|
<p>"To get started:"</p>
|
|
<ol>
|
|
<li>"Sign in"</li>
|
|
<li>"Go to beacon management"</li>
|
|
<li>"Create a listener"</li>
|
|
<li>"(Optional) Create a category"</li>
|
|
<li>"Create a template"</li>
|
|
<li>"Download the installer"</li>
|
|
<li>"Run the installer on a target system"</li>
|
|
</ol>
|
|
</main>
|
|
}
|
|
}
|