Name update
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
//! In-memory client state registry — the "who has contacted us" table the
|
||||
//! web UI displays. Not persisted: PXE sessions are ephemeral by nature.
|
||||
|
||||
use crate::arch::ClientArch;
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ClientEvent {
|
||||
DhcpDiscover,
|
||||
DhcpRequest,
|
||||
PxeBootServerRequest,
|
||||
TftpRead { file: String },
|
||||
HttpScriptFetch { target: String },
|
||||
HttpIsoAsset { file: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ClientSnapshot {
|
||||
pub mac: String,
|
||||
pub last_ip: Option<IpAddr>,
|
||||
pub arch: Option<ClientArch>,
|
||||
pub hostname: Option<String>,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub first_seen: OffsetDateTime,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub last_seen: OffsetDateTime,
|
||||
// Events are left with default serialization (9-tuple) — they're
|
||||
// diagnostic only and not consumed by the UI today.
|
||||
pub events: Vec<(OffsetDateTime, ClientEvent)>,
|
||||
/// The boot target (ISO id) last selected via the iPXE menu, if any.
|
||||
pub selected_target: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ClientRegistry {
|
||||
inner: RwLock<HashMap<String, ClientSnapshot>>,
|
||||
}
|
||||
|
||||
impl ClientRegistry {
|
||||
#[must_use]
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self::default())
|
||||
}
|
||||
|
||||
pub fn record(
|
||||
&self,
|
||||
mac: &str,
|
||||
ip: Option<IpAddr>,
|
||||
arch: Option<ClientArch>,
|
||||
event: ClientEvent,
|
||||
) {
|
||||
let mut guard = self.inner.write();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let entry = guard.entry(mac.to_string()).or_insert_with(|| ClientSnapshot {
|
||||
mac: mac.to_string(),
|
||||
last_ip: ip,
|
||||
arch,
|
||||
hostname: None,
|
||||
first_seen: now,
|
||||
last_seen: now,
|
||||
events: Vec::new(),
|
||||
selected_target: None,
|
||||
});
|
||||
entry.last_seen = now;
|
||||
if ip.is_some() { entry.last_ip = ip; }
|
||||
if arch.is_some() { entry.arch = arch; }
|
||||
entry.events.push((now, event));
|
||||
// Cap event history per client to keep memory bounded.
|
||||
const MAX_EVENTS: usize = 64;
|
||||
if entry.events.len() > MAX_EVENTS {
|
||||
let drop_n = entry.events.len() - MAX_EVENTS;
|
||||
entry.events.drain(..drop_n);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_selected_target(&self, mac: &str, target: Option<String>) {
|
||||
let mut guard = self.inner.write();
|
||||
if let Some(c) = guard.get_mut(mac) {
|
||||
c.selected_target = target;
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn list(&self) -> Vec<ClientSnapshot> {
|
||||
let guard = self.inner.read();
|
||||
let mut v: Vec<_> = guard.values().cloned().collect();
|
||||
v.sort_by(|a, b| b.last_seen.cmp(&a.last_seen));
|
||||
v
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get(&self, mac: &str) -> Option<ClientSnapshot> {
|
||||
self.inner.read().get(mac).cloned()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user