add manage page

This commit is contained in:
2026-10-06 11:36:34 -05:00
parent cf101952a0
commit 5509a6d222
4 changed files with 288 additions and 17 deletions
+4 -15
View File
@@ -5,6 +5,8 @@ use leptos_router::{
StaticSegment,
};
use crate::manage::ManagePage;
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
<!DOCTYPE html>
@@ -34,28 +36,15 @@ pub fn App() -> impl IntoView {
<Stylesheet id="leptos" href="/pkg/code_submission.css"/>
// sets the document title
<Title text="Welcome to Leptos"/>
<Title text="Manage Server"/>
// content for this welcome page
<Router>
<main>
<Routes fallback=|| "Page not found.".into_view()>
<Route path=StaticSegment("") view=HomePage/>
<Route path=StaticSegment("manage") view=ManagePage/>
</Routes>
</main>
</Router>
}
}
/// Renders the home page of your application.
#[component]
fn HomePage() -> impl IntoView {
// Creates a reactive value to update the button
let count = RwSignal::new(0);
let on_click = move |_| *count.write() += 1;
view! {
<h1>"Welcome to Leptos!"</h1>
<button on:click=on_click>"Click Me: " {count}</button>
}
}
+1
View File
@@ -1,4 +1,5 @@
pub mod app;
pub mod manage;
#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
+123
View File
@@ -0,0 +1,123 @@
use leptos::prelude::*;
#[cfg(feature = "ssr")]
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(feature = "ssr")]
static SERVER_LIVE: AtomicBool = AtomicBool::new(false);
#[cfg(feature = "ssr")]
const STATUS_SECRET_KEY: &str = "password";
#[server]
async fn get_live_state() -> Result<bool, ServerFnError> {
Ok(SERVER_LIVE.load(Ordering::SeqCst))
}
#[server]
async fn set_live_state(live: bool, secret_key: String) -> Result<(), ServerFnError> {
if secret_key != STATUS_SECRET_KEY {
return Err(ServerFnError::ServerError("Invalid secret key".to_string()));
}
SERVER_LIVE.store(live, Ordering::SeqCst);
Ok(())
}
/// Renders the server management page.
#[component]
pub fn ManagePage() -> impl IntoView {
let live_state = Resource::new(|| (), |_| get_live_state());
view! {
<section class="status-card">
<p class="eyebrow">"CONTROL PANEL"</p>
<h1>"Server status"</h1>
<p class="intro">"Manage whether your server is accepting live traffic."</p>
<Suspense fallback=|| view! { <p class="loading">"Loading server status..."</p> }>
{move || Suspend::new(async move {
match live_state.await {
Ok(is_live) => view! { <LiveToggle initial=is_live/> }.into_any(),
Err(error) => view! {
<p class="error-message" role="alert">
"Unable to load server status: " {error.to_string()}
</p>
}.into_any(),
}
})}
</Suspense>
</section>
}
}
#[component]
fn LiveToggle(initial: bool) -> impl IntoView {
let (live, set_live) = signal(initial);
let (server_live, set_server_live) = signal(initial);
let (updating, set_updating) = signal(false);
let (secret_key, set_secret_key) = signal(String::new());
let (error, set_error) = signal(None::<String>);
let on_click = move |_| {
let previous = live.get_untracked();
let checked = !previous;
let key = secret_key.get_untracked();
set_live.set(checked);
set_updating.set(true);
set_error.set(None);
leptos::task::spawn_local(async move {
match set_live_state(checked, key).await {
Ok(()) => set_server_live.set(checked),
Err(error) => {
set_live.set(previous);
set_error.set(Some(error.to_string()));
}
}
set_updating.set(false);
});
};
view! {
<div class="status-controls">
<label class="key-label" for="status-secret-key">"Secret key"</label>
<input
id="status-secret-key"
class="key-input"
type="password"
placeholder="Enter your secret key"
autocomplete="current-password"
prop:disabled=updating
on:input=move |event| set_secret_key.set(event_target_value(&event))
/>
<button
class="status-button"
class:go-live=move || !live.get()
class:take-offline=move || live.get()
type="button"
prop:disabled=updating
on:click=on_click
>
{move || if updating.get() {
"Updating..."
} else if live.get() {
"Take Offline"
} else {
"Go Live"
}}
</button>
<p
class="status-indicator"
class:is-live=move || server_live.get()
aria-live="polite"
>
<span class="status-dot"></span>
"Server status: "
<strong>{move || if server_live.get() { "Live" } else { "Offline" }}</strong>
</p>
{move || error.get().map(|message| view! {
<p class="error-message" role="alert">{message}</p>
})}
</div>
}
}