src/pages/admin_queue.rs
//! Admin → **Queue** and **Admission** panes (holger `admit-queue`).
//!
//! Each pane reads its own answer from this console's server, which refuses
//! every session below admin before holger is asked (`ADMIN_READS`). A server
//! built without the job queue answers 501 and the pane says so; nothing is
//! drawn from a guess. The decisions (words, times, the pass-through rule) are
//! in [`crate::admin_queue`], host-tested.
use leptos::prelude::*;
use crate::admin_queue::{
attempts_words, consumer_lines, door_words, job_state, next_attempt_words, priority_class, stage_words, utc_micros,
work_words, AdmissionView, DEFAULT_JOBS, JOB_STATES, PASS_THROUGH,
};
use crate::components::feedback::{Badge, ErrorText, Loading, Tone};
use crate::grpc::pb;
fn now_micros() -> i64 {
(js_sys::Date::now() * 1_000.0) as i64
}
fn status_tone(status: &str) -> Tone {
match status {
"done" => Tone::Good,
"failed" | "interrupted" => Tone::Bad,
"retry_wait" | "queued" => Tone::Warn,
_ => Tone::Neutral,
}
}
/// The Queue pane: a state filter over `JobService.ListJobs`.
#[component]
pub fn QueuePane(generation: RwSignal<u32>) -> impl IntoView {
let state = RwSignal::new("");
let jobs = LocalResource::new(move || {
let _ = generation.get();
let state = job_state(state.get());
async move { crate::grpc::answer_or_none(crate::grpc::jobs().list_jobs(pb::ListJobsRequest { kind: String::new(), limit: DEFAULT_JOBS, state }).await) }
});
view! {
<div>
<nav class="tabs" aria-label="Job state">
{JOB_STATES.iter().map(|(word, label)| {
let word: &'static str = word;
view! {
<button
class=move || if state.get() == word { "tab active" } else { "tab" }
aria-pressed=move || (state.get() == word).to_string()
on:click=move |_| state.set(word)
>{*label}</button>
}
}).collect_view()}
</nav>
{move || match jobs.get() {
None => view! { <Loading what="the job queue"/> }.into_any(),
Some(Err(e)) => view! { <ErrorText context="job queue" message=e/> }.into_any(),
Some(Ok(None)) => view! {
<p class="count" role="status">{crate::util::not_in_build("Job queue")}</p>
}.into_any(),
Some(Ok(Some(r))) => jobs_table(r.jobs, now_micros()),
}}
</div>
}
}
fn jobs_table(jobs: Vec<pb::JobRow>, now: i64) -> AnyView {
if jobs.is_empty() {
return view! { <p class="count">"No job in this state."</p> }.into_any();
}
let count = format!("{} jobs, newest first", jobs.len());
view! {
<div>
<p class="count">{count}</p>
<table>
<thead><tr>
<th>"job"</th><th>"kind"</th><th>"target"</th><th>"status · stage"</th><th>"attempts"</th>
<th>"reason"</th><th>"next attempt"</th><th>"priority"</th><th>"requested by"</th><th>"started"</th>
</tr></thead>
<tbody>
{jobs.into_iter().map(|j| view! {
<tr>
<td class="mono">{j.job_id}</td>
<td class="mono">{j.kind}</td>
<td class="mono">{j.target}</td>
<td><Badge tone=status_tone(&j.status) label=stage_words(&j.status, &j.stage)/></td>
<td>{attempts_words(j.attempts, j.max_attempts)}</td>
<td>{if j.reason.is_empty() { "—".to_string() } else { j.reason }}</td>
<td class="mono">{next_attempt_words(j.next_attempt_micros, now)}</td>
<td>{priority_class(j.priority)}</td>
<td class="mono">{if j.requested_by.is_empty() { "—".to_string() } else { j.requested_by }}</td>
<td class="mono">{utc_micros(j.ts_start_micros)}</td>
</tr>
}).collect_view()}
</tbody>
</table>
</div>
}
.into_any()
}
/// The Admission pane: per consumer, used / limit over the window, the class
/// and the plan's sentence.
#[component]
pub fn AdmissionPane(generation: RwSignal<u32>) -> impl IntoView {
let adm = LocalResource::new(move || {
let _ = generation.get();
async move { crate::admin_queue::admission_view().await }
});
view! {
<div>
{move || match adm.get() {
None => view! { <Loading what="admission"/> }.into_any(),
Some(Err(e)) => view! { <ErrorText context="admission" message=e/> }.into_any(),
Some(Ok(None)) => view! {
<p class="count" role="status">{crate::util::not_in_build("Admission")}</p>
}.into_any(),
Some(Ok(Some(v))) => admission_view(v),
}}
</div>
}
}
fn admission_view(v: AdmissionView) -> AnyView {
let today = now_micros().div_euclid(86_400_000_000);
let lines = consumer_lines(&v, today);
let pass = v.usage.policy == PASS_THROUGH;
let door = door_words(&v.usage);
let details: Vec<(String, Vec<String>)> = v
.usage
.consumers
.iter()
.map(|r| (r.consumer.clone(), r.by_work.iter().map(work_words).collect()))
.collect();
view! {
<div>
<p class="count mono">{door}</p>
{pass.then(|| view! {
<p class="banner">"This server runs pass-through: nothing is refused and no limit is enforced, so used / limit is N/A."</p>
})}
{v.plan_errors.into_iter().map(|e| view! { <p class="banner banner-warn">{e}</p> }).collect_view()}
{lines.is_empty().then(|| view! { <p class="count">"The usage book holds no consumer yet."</p> })}
<table>
<thead><tr>
<th>"consumer"</th><th>"kind"</th><th>"used / limit (30-day window)"</th><th>"class"</th>
<th>"admission plan"</th><th>"in flight"</th><th>"by work"</th>
</tr></thead>
<tbody>
{lines.into_iter().map(|l| {
let work = details.iter().find(|(k, _)| *k == l.key).map(|(_, w)| w.join(" · ")).unwrap_or_default();
view! {
<tr>
<td><span>{l.label}</span><br/><span class="count mono">{l.key}</span></td>
<td>{l.kind}</td>
<td>{l.used_limit}</td>
<td>{l.class}</td>
<td>{l.why}</td>
<td>{l.in_flight.to_string()}</td>
<td class="count">{work}</td>
</tr>
}
}).collect_view()}
</tbody>
</table>
</div>
}
.into_any()
}