Some checks failed
Run Check Script / check (pull_request) Failing after 1m51s
203 lines
6.1 KiB
Rust
203 lines
6.1 KiB
Rust
// TODO(frontend): the maud+htmx views currently inline mock data
|
|
// instead of going through this `FleetService` trait. Once a view
|
|
// reads from the trait, drop this allow. The structs and the mock
|
|
// implementation are intentionally complete so the wiring step is
|
|
// purely additive on the views side.
|
|
#![allow(dead_code)]
|
|
|
|
pub mod mock;
|
|
|
|
use async_trait::async_trait;
|
|
use chrono::{DateTime, Utc};
|
|
use serde::Serialize;
|
|
|
|
#[async_trait]
|
|
pub trait FleetService: Send + Sync + 'static {
|
|
async fn dashboard_detail(&self) -> anyhow::Result<DashboardDetail>;
|
|
async fn list_devices(&self) -> anyhow::Result<Vec<DeviceDetail>>;
|
|
async fn get_device(&self, id: &str) -> anyhow::Result<Option<DeviceDetail>>;
|
|
async fn list_deployments(&self) -> anyhow::Result<Vec<DeploymentDetail>>;
|
|
async fn get_deployment(&self, name: &str) -> anyhow::Result<Option<DeploymentDetail>>;
|
|
async fn get_deployment_devices(&self, name: &str) -> anyhow::Result<Vec<DeviceDetail>>;
|
|
async fn blacklist_device(&self, id: &str) -> anyhow::Result<DeviceDetail>;
|
|
async fn list_alerts(&self) -> anyhow::Result<Vec<Alert>>;
|
|
async fn ack_alert(&self, id: &str) -> anyhow::Result<bool>;
|
|
async fn get_task_graph(&self, deployment: &str) -> anyhow::Result<TaskGraph>;
|
|
async fn filtered_devices(
|
|
&self,
|
|
status: Option<DeviceStatus>,
|
|
deployment: Option<String>,
|
|
region: Option<String>,
|
|
search: Option<String>,
|
|
) -> anyhow::Result<Vec<DeviceDetail>>;
|
|
}
|
|
|
|
// ── Device ─────────────────────────────────────────────────────────────
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct DeviceDetail {
|
|
pub id: String,
|
|
pub status: DeviceStatus,
|
|
pub last_seen: DateTime<Utc>,
|
|
pub minutes_ago: i64,
|
|
pub deployment: Option<String>,
|
|
pub ip: Option<String>,
|
|
pub region: String,
|
|
pub model: String,
|
|
pub fw: String,
|
|
pub tags: Vec<String>,
|
|
pub uptime_h: u32,
|
|
pub cpu: u8,
|
|
pub mem: u8,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub enum DeviceStatus {
|
|
Healthy,
|
|
Pending,
|
|
Stale,
|
|
Failing,
|
|
Blacklisted,
|
|
Unknown,
|
|
}
|
|
|
|
impl DeviceStatus {
|
|
pub fn label(self) -> &'static str {
|
|
match self {
|
|
Self::Healthy => "healthy",
|
|
Self::Pending => "pending",
|
|
Self::Stale => "stale",
|
|
Self::Failing => "failing",
|
|
Self::Blacklisted => "blacklisted",
|
|
Self::Unknown => "unknown",
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Deployment ─────────────────────────────────────────────────────────
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct DeploymentDetail {
|
|
pub name: String,
|
|
pub version: String,
|
|
pub status: DeploymentStatus,
|
|
pub target: u32,
|
|
pub healthy: u32,
|
|
pub failing: u32,
|
|
pub pending: u32,
|
|
pub updated_at: String,
|
|
pub author: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub enum DeploymentStatus {
|
|
Active,
|
|
Rolling,
|
|
Failing,
|
|
Paused,
|
|
}
|
|
|
|
impl DeploymentStatus {
|
|
pub fn label(self) -> &'static str {
|
|
match self {
|
|
Self::Active => "active",
|
|
Self::Rolling => "rolling",
|
|
Self::Failing => "failing",
|
|
Self::Paused => "paused",
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Dashboard ──────────────────────────────────────────────────────────
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct DashboardDetail {
|
|
pub devices_total: u32,
|
|
pub devices_healthy: u32,
|
|
pub devices_pending: u32,
|
|
pub devices_failing: u32,
|
|
pub devices_stale: u32,
|
|
pub devices_blacklisted: u32,
|
|
pub devices_unknown: u32,
|
|
pub deployments_total: usize,
|
|
pub health_pct: u32,
|
|
pub health_trend: Vec<f64>,
|
|
pub ingest_rate: u32,
|
|
pub ingest_trend: Vec<u32>,
|
|
pub attention_devices: Vec<DeviceDetail>,
|
|
pub activity_feed: Vec<Activity>,
|
|
pub top_deployments: Vec<DeploymentDetail>,
|
|
pub active_alerts: Vec<Alert>,
|
|
pub rolling_count: usize,
|
|
pub failing_count: usize,
|
|
}
|
|
|
|
// ── Alert ──────────────────────────────────────────────────────────────
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct Alert {
|
|
pub id: String,
|
|
pub severity: AlertSeverity,
|
|
pub title: String,
|
|
pub deployment: Option<String>,
|
|
pub device: Option<String>,
|
|
pub at: String,
|
|
pub acked: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub enum AlertSeverity {
|
|
Critical,
|
|
Warning,
|
|
Info,
|
|
}
|
|
|
|
impl AlertSeverity {
|
|
pub fn label(self) -> &'static str {
|
|
match self {
|
|
Self::Critical => "critical",
|
|
Self::Warning => "warning",
|
|
Self::Info => "info",
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Activity ───────────────────────────────────────────────────────────
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct Activity {
|
|
pub who: String,
|
|
pub verb: String,
|
|
pub target: String,
|
|
pub at: String,
|
|
}
|
|
|
|
// ── Task Graph ─────────────────────────────────────────────────────────
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct TaskGraph {
|
|
pub nodes: Vec<TaskNode>,
|
|
pub edges: Vec<(String, String)>,
|
|
pub positions: std::collections::HashMap<String, (usize, usize)>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct TaskNode {
|
|
pub id: String,
|
|
pub label: String,
|
|
pub status: TaskStatus,
|
|
pub duration: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub enum TaskStatus {
|
|
Done,
|
|
Running,
|
|
Pending,
|
|
Failed,
|
|
}
|