graphile_worker_admin_ui_client 0.1.3

Leptos WASM client for the embedded graphile_worker admin UI
Documentation
use leptos::prelude::*;

use super::super::api::{post_maintenance, RefreshSignals};
use super::super::browser::show_toast;
use super::super::filters::format_date;
use super::super::types::{AdminClientConfig, MaintenanceAction, MaintenanceRequest};

#[component]
pub(in crate::wasm) fn ActiveWorkersPanel(
    config: AdminClientConfig,
    refresh: RefreshSignals,
) -> impl IntoView {
    view! {
        <div id="active-workers" class="gw-panel">
            <div class="flex items-center justify-between gap-2 border-b p-3" style="border-color: rgb(var(--border));">
                <div class="flex items-center gap-2">
                    <span class="i-lucide-heart-pulse h-4 w-4"></span>
                    <h3 class="font-semibold">"Active workers"</h3>
                </div>
                <button class="gw-btn" type="button" disabled=config.read_only on:click={
                    let config = config.clone();
                    move |_| post_maintenance(
                        config.clone(),
                        MaintenanceRequest {
                            action: MaintenanceAction::SweepStaleWorkers,
                            cleanup_tasks: Vec::new(),
                            worker_ids: Vec::new(),
                            dry_run: false,
                            sweep_threshold_secs: None,
                            recovery_delay_secs: None,
                        },
                        refresh,
                    )
                }>
                    <span class="i-lucide-radar h-4 w-4"></span>"Sweep stale"
                </button>
            </div>
            <div class="divide-y" style="border-color: rgb(var(--border));">
                {move || if refresh.overview.get().active_workers.is_empty() {
                    view! { <div class="p-4 gw-muted">"No registered workers yet."</div> }.into_any()
                } else {
                    ().into_any()
                }}
                <For
                    each=move || refresh.overview.get().active_workers
                    key=|worker| worker.worker_id.clone()
                    children=move |worker| {
                        let worker_id = worker.worker_id.clone();
                        view! {
                            <div class="flex items-center justify-between gap-3 p-3">
                                <div class="min-w-0">
                                    <span class="block truncate font-mono text-sm">{worker_id.clone()}</span>
                                    <p class="gw-muted text-xs">
                                        {format!("started {}", format_date(worker.started_at))}
                                        " · heartbeat "
                                        {format_date(worker.last_heartbeat_at)}
                                    </p>
                                </div>
                                <span class=format!(
                                    "gw-pill {}",
                                    if worker.is_stale { "text-amber-600 dark:text-amber-300" } else { "text-emerald-600 dark:text-emerald-300" }
                                )>
                                    {if worker.is_stale { "stale" } else { "healthy" }}
                                </span>
                            </div>
                        }
                    }
                />
            </div>
        </div>
    }
}

#[component]
pub(in crate::wasm) fn WorkersPanel(
    config: AdminClientConfig,
    selected_workers: RwSignal<Vec<String>>,
    refresh: RefreshSignals,
) -> impl IntoView {
    view! {
        <div id="workers" class="gw-panel">
            <div class="flex items-center justify-between gap-2 border-b p-3" style="border-color: rgb(var(--border));">
                <div class="flex items-center gap-2">
                    <span class="i-lucide-hard-drive h-4 w-4"></span>
                    <h3 class="font-semibold">"Workers"</h3>
                </div>
                <button class="gw-btn" type="button" disabled=config.read_only on:click={
                    let config = config.clone();
                    move |_| {
                        let worker_ids = selected_workers.get_untracked();
                        if worker_ids.is_empty() {
                            show_toast(refresh.toast, "Select at least one worker");
                            return;
                        }
                        post_maintenance(
                            config.clone(),
                            MaintenanceRequest {
                                action: MaintenanceAction::ForceUnlock,
                                cleanup_tasks: Vec::new(),
                                worker_ids,
                                dry_run: false,
                                sweep_threshold_secs: None,
                                recovery_delay_secs: None,
                            },
                            refresh,
                        );
                    }
                }>
                    <span class="i-lucide-unlock h-4 w-4"></span>"Force unlock"
                </button>
            </div>
            <div class="divide-y" style="border-color: rgb(var(--border));">
                {move || if refresh.overview.get().workers.is_empty() {
                    view! { <div class="p-4 gw-muted">"No active locks."</div> }.into_any()
                } else {
                    ().into_any()
                }}
                <For
                    each=move || refresh.overview.get().workers
                    key=|worker| worker.worker_id.clone()
                    children=move |worker| {
                        let worker_id = worker.worker_id.clone();
                        let worker_id_for_checked = worker_id.clone();
                        let worker_id_for_change = worker_id.clone();
                        view! {
                            <label class="flex cursor-pointer items-center justify-between gap-3 p-3">
                                <span class="min-w-0">
                                    <span class="block truncate font-mono text-sm">{worker.worker_id.clone()}</span>
                                    <span class="gw-muted text-xs">{format!("{} jobs · {} queues", worker.locked_jobs, worker.locked_queues)}</span>
                                </span>
                                <input
                                    class="h-4 w-4"
                                    type="checkbox"
                                    name="selected_workers"
                                    aria-label=format!("Select worker {}", worker.worker_id)
                                    prop:checked=move || selected_workers.get().contains(&worker_id_for_checked)
                                    on:change=move |event| {
                                        selected_workers.update(|selected| {
                                            if event_target_checked(&event) {
                                                if !selected.contains(&worker_id_for_change) {
                                                    selected.push(worker_id_for_change.clone());
                                                }
                                            } else {
                                                selected.retain(|candidate| candidate != &worker_id_for_change);
                                            }
                                        });
                                    }
                                />
                            </label>
                        }
                    }
                />
            </div>
        </div>
    }
}