graphile_worker_admin_ui_client 0.1.3

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

use super::api::refresh_data;
use super::browser::apply_theme;
use super::components::{ActiveWorkersPanel, Overview, QueuesPanel, WorkersPanel};
use super::filters::{filter_values, job_search_text, job_state, matches_column};
use super::modals::ModalView;
use super::types::{AdminClientConfig, JobState};

mod auth;
mod jobs;
mod maintenance;
mod sidebar;
mod signals;
mod state;
mod topbar;
use auth::AuthTokenPanel;
use jobs::JobsPanel;
use maintenance::MaintenancePanel;
use sidebar::Sidebar;
use signals::AdminSignals;
use topbar::Topbar;

#[component]
pub(super) fn AdminApp(config: AdminClientConfig) -> impl IntoView {
    let signals = AdminSignals::new();
    let refresh = signals.refresh();
    let jobs = signals.jobs;
    let overview = signals.overview;
    let selected_jobs = signals.selected_jobs;
    let selected_workers = signals.selected_workers;
    let active_state = signals.active_state;
    let search = signals.search;
    let task_filter = signals.task_filter;
    let queue_filter = signals.queue_filter;
    let key_filter = signals.key_filter;
    let worker_filter = signals.worker_filter;
    let modal = signals.modal;
    let toast = signals.toast;
    let theme = signals.theme;
    let accent = signals.accent;
    let compact = signals.compact;
    let auto_refresh_enabled = signals.auto_refresh_enabled;
    let auto_refresh_timer = signals.auto_refresh_timer.clone();

    let filtered_jobs = Memo::new(move |_| {
        let search = search.get().trim().to_lowercase();
        let active_state = active_state.get();
        let filters = [
            ("task_identifier", filter_values(&task_filter.get())),
            ("queue_name", filter_values(&queue_filter.get())),
            ("key", filter_values(&key_filter.get())),
            ("locked_by", filter_values(&worker_filter.get())),
        ];

        jobs.get()
            .into_iter()
            .filter(|job| {
                if active_state != JobState::All && job_state(job) != active_state {
                    return false;
                }
                if !search.is_empty() && !job_search_text(job).contains(&search) {
                    return false;
                }
                filters
                    .iter()
                    .all(|(column, values)| matches_column(job, column, values))
            })
            .collect::<Vec<_>>()
    });

    let selected_count = Memo::new(move |_| selected_jobs.get().len());
    let all_visible_selected = Memo::new(move |_| {
        let visible = filtered_jobs.get();
        let selected = selected_jobs.get();
        !visible.is_empty() && visible.iter().all(|job| selected.contains(&job.id))
    });
    let show_token_login = config.auth_mode.requires_token();

    Effect::new(move |_| {
        apply_theme(&theme.get(), &accent.get(), compact.get());
    });

    refresh_data(config.clone(), refresh);

    view! {
        <Sidebar config=config.clone() />

        <main class="gw-main">
            <Topbar
                config=config.clone()
                refresh=refresh
                theme=theme
                accent=accent
                compact=compact
                auto_refresh_enabled=auto_refresh_enabled
                auto_refresh_timer=auto_refresh_timer.clone()
            />

            <div class="gw-scroll">
                <AuthTokenPanel
                    show_token_login=show_token_login
                    config=config.clone()
                    refresh=refresh
                />

                <Overview stats=move || overview.get().stats />

                <JobsPanel
                    config=config.clone()
                    refresh=refresh
                    filtered_jobs=filtered_jobs
                    selected_count=selected_count
                    all_visible_selected=all_visible_selected
                    active_state=active_state
                    search=search
                    task_filter=task_filter
                    queue_filter=queue_filter
                    key_filter=key_filter
                    worker_filter=worker_filter
                    modal=modal
                />

                <section id="queues-workers" class="mt-4 grid gap-4 lg:grid-cols-2">
                    <QueuesPanel overview=overview queue_filter=queue_filter />
                    <div class="grid gap-4">
                        <ActiveWorkersPanel
                            config=config.clone()
                            refresh=refresh
                        />
                        <WorkersPanel
                            config=config.clone()
                            selected_workers=selected_workers
                            refresh=refresh
                        />
                    </div>
                </section>

                <MaintenancePanel
                    config=config.clone()
                    refresh=refresh
                />
            </div>
        </main>

        <ModalView
            modal=modal
            config=config
            refresh=refresh
        />
        <div class="gw-toast" data-open=move || toast.get().is_some().to_string()>{move || toast.get().unwrap_or_default()}</div>
    }
}