Files
harmony/fleet/harmony-fleet-operator/src/service/mod.rs

166 lines
5.1 KiB
Rust

pub mod mock;
pub mod real;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::Serialize;
pub use harmony_reconciler_contracts::InventorySnapshot;
#[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 run_command(&self, device_id: &str, command: &str) -> anyhow::Result<String>;
async fn list_alerts(&self) -> anyhow::Result<Vec<Alert>>;
async fn ack_alert(&self, id: &str) -> anyhow::Result<bool>;
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 region: String,
pub tags: Vec<String>,
/// Running version from the latest heartbeat.
pub current_version: Option<String>,
/// Hardware/OS facts from the agent. `None` until the first
/// post-enrollment publish (mirrors `DeviceInfo.inventory`).
pub inventory: Option<InventorySnapshot>,
}
#[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 rollout_revision: String,
pub version: String,
pub status: DeploymentStatus,
pub target: u32,
pub healthy: u32,
pub failing: u32,
pub pending: u32,
pub updated_at: String,
pub last_error: Option<DeploymentError>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct DeploymentError {
pub device: String,
pub message: String,
pub at: 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 attention_devices: Vec<DeviceDetail>,
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",
}
}
}