WP-10: OS and package inventory
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / ui (push) Has been cancelled

Debian inspector (dpkg-query, apt-get -s upgrade, snap list, os-release,
reboot-required) behind a CommandRunner port with tested parsers, fake
inspector for dev, persisted inventory snapshot, package_refresh job,
/api/system/inventory, Updates page with OS card, summary and filterable
package table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Dennis Nemec
2026-09-02 22:21:46 +02:00
parent 7b6d242988
commit b6ddb8889d
12 changed files with 444 additions and 39 deletions

View File

@ -1,6 +1,7 @@
use std::sync::Arc;
use async_trait::async_trait;
use chrono::Utc;
use domain::host::Inventory;
use domain::ports::{HostInspector, InventoryRepository};
use domain::DomainError;
@ -14,18 +15,26 @@ pub struct InventoryService {
impl InventoryService {
pub fn new(inspector: Arc<dyn HostInspector>, repo: Arc<dyn InventoryRepository>) -> Self {
let _ = (&inspector, &repo);
Self { inspector, repo }
}
/// Query the host and persist a new snapshot.
pub async fn refresh(&self) -> Result<Inventory, DomainError> {
todo!()
let os = self.inspector.os_info().await?;
let mut packages = self.inspector.packages().await?;
packages.sort_by(|a, b| a.name.cmp(&b.name));
let inventory = Inventory {
os,
packages,
refreshed_at: Utc::now(),
};
self.repo.save(&inventory).await?;
Ok(inventory)
}
/// Last persisted snapshot.
pub async fn current(&self) -> Result<Option<Inventory>, DomainError> {
todo!()
self.repo.load().await
}
}
@ -34,7 +43,27 @@ pub struct PackageRefreshJob(pub Arc<InventoryService>);
#[async_trait]
impl JobHandler for PackageRefreshJob {
async fn run(&self, _params: Option<String>, _log: &dyn JobLog) -> Result<(), String> {
todo!()
async fn run(&self, _params: Option<String>, log: &dyn JobLog) -> Result<(), String> {
log.line("querying host for OS and package information")
.await;
let inv = self.0.refresh().await.map_err(|e| e.to_string())?;
log.line(&format!(
"{} {} on kernel {}",
inv.os.name, inv.os.version, inv.os.kernel
))
.await;
log.line(&format!(
"{} packages, {} upgradable ({} security){}",
inv.packages.len(),
inv.upgradable(),
inv.security_upgrades(),
if inv.os.reboot_required {
", reboot required"
} else {
""
}
))
.await;
Ok(())
}
}