perspt-dashboard 0.5.7

Real-time web dashboard for Perspt agent monitoring
Documentation
use askama::Template;
use axum::extract::{Path, State};
use axum::response::{Html, IntoResponse};

use crate::error::DashboardError;
use crate::state::AppState;
use crate::views::friendly_name;
use crate::views::overview::BudgetSummary;
use crate::views::session_detail::{NodeSummaryRow, SessionDetailViewModel, VerifSummaryRow};

#[derive(Template)]
#[template(path = "pages/session_detail.html")]
struct SessionDetailTemplate {
    session_id: String,
    display_name: String,
    active_tab: String,
    task: String,
    working_dir: String,
    status: String,
    toolchain: String,
    total_nodes: usize,
    completed_nodes: usize,
    failed_nodes: usize,
    running_nodes: usize,
    llm_request_count: usize,
    llm_tokens_in: i64,
    llm_tokens_out: i64,
    avg_energy: f32,
    budget: Option<BudgetSummary>,
    nodes: Vec<NodeSummaryRow>,
    verifications: Vec<VerifSummaryRow>,
}

pub async fn session_detail_handler(
    State(state): State<AppState>,
    Path(session_id): Path<String>,
) -> Result<impl IntoResponse, DashboardError> {
    let session = state.store.get_session(&session_id)?;
    let (task, working_dir, status, toolchain) = match session {
        Some(s) => (s.task, s.working_dir, s.status, s.detected_toolchain),
        None => (
            "Unknown".to_string(),
            String::new(),
            "unknown".to_string(),
            None,
        ),
    };

    let nodes = state
        .store
        .get_latest_node_states(&session_id)
        .unwrap_or_default();
    let llm_records = state
        .store
        .get_llm_requests(&session_id)
        .unwrap_or_default();
    let energy_records = state
        .store
        .get_session_energy_history(&session_id)
        .unwrap_or_default();
    let budget = state.store.get_budget_envelope(&session_id).ok().flatten();
    let verifications = state
        .store
        .get_all_verification_results(&session_id)
        .unwrap_or_default();

    let vm = SessionDetailViewModel::from_store(
        session_id,
        task,
        working_dir,
        status,
        toolchain,
        nodes,
        &llm_records,
        &energy_records,
        budget,
        verifications,
    );

    let tmpl = SessionDetailTemplate {
        display_name: friendly_name(&vm.session_id),
        session_id: vm.session_id,
        active_tab: "summary".to_string(),
        task: vm.task,
        working_dir: vm.working_dir,
        status: vm.status,
        toolchain: vm.toolchain,
        total_nodes: vm.total_nodes,
        completed_nodes: vm.completed_nodes,
        failed_nodes: vm.failed_nodes,
        running_nodes: vm.running_nodes,
        llm_request_count: vm.llm_request_count,
        llm_tokens_in: vm.llm_tokens_in,
        llm_tokens_out: vm.llm_tokens_out,
        avg_energy: vm.avg_energy,
        budget: vm.budget,
        nodes: vm.nodes,
        verifications: vm.verifications,
    };
    Ok(Html(tmpl.render()?))
}