mirror of
				https://github.com/csehviktor/status-monitor.git
				synced 2025-08-08 18:06:14 +02:00 
			
		
		
		
	formatter + implement status history
This commit is contained in:
		@@ -1,29 +1,42 @@
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use chrono::Utc;
 | 
			
		||||
use tokio::sync::Mutex;
 | 
			
		||||
use async_trait::async_trait;
 | 
			
		||||
use chrono::Utc;
 | 
			
		||||
use common::StatusMessage;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use tokio::sync::Mutex;
 | 
			
		||||
 | 
			
		||||
use super::{StorageRepository, UptimeMessage, UptimeStorageModel};
 | 
			
		||||
 | 
			
		||||
pub struct InMemoryRepository {
 | 
			
		||||
    agents: Mutex<HashMap<String, UptimeStorageModel>>
 | 
			
		||||
    agents: Mutex<HashMap<String, UptimeStorageModel>>,
 | 
			
		||||
    messages: Mutex<HashMap<String, Vec<StatusMessage>>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl InMemoryRepository {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            agents: Default::default(),
 | 
			
		||||
            messages: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
impl StorageRepository for InMemoryRepository {
 | 
			
		||||
    async fn record_message(&self, message: &StatusMessage) -> anyhow::Result<()> {
 | 
			
		||||
        let mut messages = self.messages.lock().await;
 | 
			
		||||
 | 
			
		||||
        let agent_messages = messages.entry(message.agent.clone()).or_default();
 | 
			
		||||
        agent_messages.push(message.clone());
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn record_uptime(&self, agent: &str) -> anyhow::Result<()> {
 | 
			
		||||
        let mut agents = self.agents.lock().await;
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
 | 
			
		||||
        agents.entry(agent.to_string())
 | 
			
		||||
        agents
 | 
			
		||||
            .entry(agent.to_string())
 | 
			
		||||
            .and_modify(|a| {
 | 
			
		||||
                a.last_seen = now;
 | 
			
		||||
                a.message_count += 1;
 | 
			
		||||
@@ -38,18 +51,11 @@ impl StorageRepository for InMemoryRepository {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    async fn get_uptime(&self, agent: &str) -> anyhow::Result<Option<UptimeMessage>> {
 | 
			
		||||
        let agents = self.agents.lock().await;
 | 
			
		||||
    async fn get_history(&self, agent: &str) -> anyhow::Result<Vec<StatusMessage>> {
 | 
			
		||||
        let messages = self.messages.lock().await;
 | 
			
		||||
 | 
			
		||||
        match agents.get(agent) {
 | 
			
		||||
            Some(data) => {
 | 
			
		||||
                Ok(Some(data.clone().into()))
 | 
			
		||||
            }
 | 
			
		||||
            None => Ok(None),
 | 
			
		||||
        }
 | 
			
		||||
        Ok(messages.get(agent).cloned().unwrap_or_default())
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    async fn get_agents(&self) -> anyhow::Result<Vec<UptimeMessage>> {
 | 
			
		||||
        let agents = self.agents.lock().await;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use async_trait::async_trait;
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use common::{MQTT_SEND_INTERVAL, StatusMessage};
 | 
			
		||||
use memory::InMemoryRepository;
 | 
			
		||||
use rusqlite::Row;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use sqlite::SQLiteRepository;
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
use memory::InMemoryRepository;
 | 
			
		||||
use async_trait::async_trait;
 | 
			
		||||
use common::MQTT_SEND_INTERVAL;
 | 
			
		||||
 | 
			
		||||
pub mod memory;
 | 
			
		||||
pub mod sqlite;
 | 
			
		||||
@@ -39,9 +40,32 @@ impl Into<UptimeMessage> for UptimeStorageModel {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TryFrom<&Row<'_>> for UptimeStorageModel {
 | 
			
		||||
    type Error = rusqlite::Error;
 | 
			
		||||
 | 
			
		||||
    fn try_from(row: &Row) -> Result<Self, Self::Error> {
 | 
			
		||||
        let first_seen: DateTime<Utc> = row.get::<_, String>(1)?.parse().map_err(|e| {
 | 
			
		||||
            rusqlite::Error::FromSqlConversionFailure(1, rusqlite::types::Type::Text, Box::new(e))
 | 
			
		||||
        })?;
 | 
			
		||||
 | 
			
		||||
        let last_seen: DateTime<Utc> = row.get::<_, String>(2)?.parse().map_err(|e| {
 | 
			
		||||
            rusqlite::Error::FromSqlConversionFailure(2, rusqlite::types::Type::Text, Box::new(e))
 | 
			
		||||
        })?;
 | 
			
		||||
 | 
			
		||||
        Ok(UptimeStorageModel {
 | 
			
		||||
            id: row.get(0)?,
 | 
			
		||||
            first_seen,
 | 
			
		||||
            last_seen,
 | 
			
		||||
            message_count: row.get(1)?,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
pub trait StorageRepository: Send + Sync {
 | 
			
		||||
    async fn record_message(&self, message: &StatusMessage) -> anyhow::Result<()>;
 | 
			
		||||
    async fn record_uptime(&self, agent: &str) -> anyhow::Result<()>;
 | 
			
		||||
    async fn get_history(&self, agent: &str) -> anyhow::Result<Vec<StatusMessage>>;
 | 
			
		||||
    async fn get_agents(&self) -> anyhow::Result<Vec<UptimeMessage>>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -51,7 +75,7 @@ pub enum StorageStrategy {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct StorageRepositoryImpl {
 | 
			
		||||
    inner: Arc<dyn StorageRepository>
 | 
			
		||||
    inner: Arc<dyn StorageRepository>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl StorageRepositoryImpl {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,15 @@
 | 
			
		||||
use anyhow::Ok;
 | 
			
		||||
use async_trait::async_trait;
 | 
			
		||||
use chrono::Utc;
 | 
			
		||||
use common::StatusMessage;
 | 
			
		||||
use rusqlite::Connection;
 | 
			
		||||
use tokio::sync::Mutex;
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use tokio::sync::Mutex;
 | 
			
		||||
 | 
			
		||||
use super::{StorageRepository, UptimeMessage, UptimeStorageModel};
 | 
			
		||||
 | 
			
		||||
pub struct SQLiteRepository {
 | 
			
		||||
    conn: Mutex<Connection>
 | 
			
		||||
    conn: Mutex<Connection>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SQLiteRepository {
 | 
			
		||||
@@ -27,53 +29,101 @@ impl SQLiteRepository {
 | 
			
		||||
                message_count INTEGER NOT NULL DEFAULT 0
 | 
			
		||||
            ) STRICT;
 | 
			
		||||
 | 
			
		||||
            CREATE TABLE IF NOT EXISTS agents (
 | 
			
		||||
                id TEXT PRIMARY KEY AUTO_INCREMENT,
 | 
			
		||||
                agent_id TEXT NOT NULL,
 | 
			
		||||
                message TEXT NOT NULL,
 | 
			
		||||
                timestamp TEXT NOT NULL
 | 
			
		||||
 | 
			
		||||
                FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
 | 
			
		||||
            ) STRICT;
 | 
			
		||||
 | 
			
		||||
            CREATE INDEX IF NOT EXISTS idx_agents_id ON agents(id);
 | 
			
		||||
            CREATE INDEX IF NOT EXISTS idx_agents_times ON agents(first_seen, last_seen);
 | 
			
		||||
            "#
 | 
			
		||||
        ).unwrap();
 | 
			
		||||
            CREATE INDEX IF NOT EXISTS idx_messages_agent_id ON messages(agent_id);
 | 
			
		||||
            CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(timestamp);
 | 
			
		||||
            "#,
 | 
			
		||||
        )
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            conn: Mutex::new(conn)
 | 
			
		||||
            conn: Mutex::new(conn),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
impl StorageRepository for SQLiteRepository {
 | 
			
		||||
    async fn record_uptime(&self, agent: &str) -> anyhow::Result<()> {
 | 
			
		||||
    async fn record_message(&self, message: &StatusMessage) -> anyhow::Result<()> {
 | 
			
		||||
        let conn = self.conn.lock().await;
 | 
			
		||||
        let now = Utc::now().to_rfc3339();
 | 
			
		||||
 | 
			
		||||
        conn.execute(r#"
 | 
			
		||||
            INSERT INTO agents (id, first_seen, last_seen, message_count)
 | 
			
		||||
            VALUES (?1, ?2, ?2, 1)
 | 
			
		||||
            ON CONFLICT (id) DO UPDATE SET
 | 
			
		||||
                last_seen = excluded.last_seen,
 | 
			
		||||
                message_count = message_count + 1;
 | 
			
		||||
            "#, [agent, &now]
 | 
			
		||||
        let payload_str = message.to_string().unwrap();
 | 
			
		||||
 | 
			
		||||
        conn.execute(
 | 
			
		||||
            r#"
 | 
			
		||||
                INSERT INTO messages (agent_id, message, timestamp)
 | 
			
		||||
                VALUES (?1, ?2, ?3)
 | 
			
		||||
            "#,
 | 
			
		||||
            [
 | 
			
		||||
                &message.agent,
 | 
			
		||||
                &payload_str,
 | 
			
		||||
                &message.timestamp.to_rfc3339(),
 | 
			
		||||
            ],
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn get_agents(&self) -> anyhow::Result<Vec<UptimeMessage>> {
 | 
			
		||||
    async fn record_uptime(&self, agent: &str) -> anyhow::Result<()> {
 | 
			
		||||
        let conn = self.conn.lock().await;
 | 
			
		||||
        let mut stmt = conn.prepare("SELECT id, first_seen, last_seen, message_count FROM agents")?;
 | 
			
		||||
        let now = Utc::now().to_rfc3339();
 | 
			
		||||
 | 
			
		||||
        let result = stmt.query_map([], |row| {
 | 
			
		||||
            let first_seen: DateTime<Utc> = row.get::<_, String>(1)?.parse().unwrap();
 | 
			
		||||
            let last_seen: DateTime<Utc> = row.get::<_, String>(2)?.parse().unwrap();
 | 
			
		||||
        conn.execute(
 | 
			
		||||
            r#"
 | 
			
		||||
            INSERT INTO agents (id, first_seen, last_seen, message_count)
 | 
			
		||||
            VALUES (?1, ?2, ?2, 1)
 | 
			
		||||
            ON CONFLICT (id) DO UPDATE SET
 | 
			
		||||
                last_seen = excluded.last_seen,
 | 
			
		||||
                message_count = message_count + 1;
 | 
			
		||||
            "#,
 | 
			
		||||
            [agent, &now],
 | 
			
		||||
        )?;
 | 
			
		||||
 | 
			
		||||
            Ok(UptimeStorageModel {
 | 
			
		||||
                id: row.get(0)?,
 | 
			
		||||
                first_seen,
 | 
			
		||||
                last_seen,
 | 
			
		||||
                message_count: row.get(3)?,
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn get_history(&self, agent: &str) -> anyhow::Result<Vec<StatusMessage>> {
 | 
			
		||||
        let conn = self.conn.lock().await;
 | 
			
		||||
        let mut stmt =
 | 
			
		||||
            conn.prepare("SELECT agent_id, message, timestamp FROM messages WHERE agent_id = ?")?;
 | 
			
		||||
 | 
			
		||||
        let rows = stmt.query_map([agent], |row| {
 | 
			
		||||
            let row: String = row.get::<_, String>(1).unwrap();
 | 
			
		||||
 | 
			
		||||
            StatusMessage::try_from(row).map_err(|e| {
 | 
			
		||||
                rusqlite::Error::FromSqlConversionFailure(
 | 
			
		||||
                    1,
 | 
			
		||||
                    rusqlite::types::Type::Text,
 | 
			
		||||
                    Box::new(e),
 | 
			
		||||
                )
 | 
			
		||||
            })
 | 
			
		||||
        })?;
 | 
			
		||||
 | 
			
		||||
        let models: Result<Vec<UptimeStorageModel>, _> = result.collect();
 | 
			
		||||
        let models: Vec<StatusMessage> = rows.collect::<Result<Vec<StatusMessage>, _>>()?;
 | 
			
		||||
 | 
			
		||||
        Ok(models?.into_iter().map(Into::into).collect())
 | 
			
		||||
        Ok(models.into_iter().map(Into::into).collect())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn get_agents(&self) -> anyhow::Result<Vec<UptimeMessage>> {
 | 
			
		||||
        let conn = self.conn.lock().await;
 | 
			
		||||
        let mut stmt =
 | 
			
		||||
            conn.prepare("SELECT id, first_seen, last_seen, message_count FROM agents")?;
 | 
			
		||||
 | 
			
		||||
        let rows = stmt.query_map([], |row| UptimeStorageModel::try_from(row))?;
 | 
			
		||||
 | 
			
		||||
        let models: Vec<UptimeStorageModel> =
 | 
			
		||||
            rows.collect::<Result<Vec<UptimeStorageModel>, _>>()?;
 | 
			
		||||
 | 
			
		||||
        Ok(models.into_iter().map(Into::into).collect())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user