M2: terminal UI (ratatui)

- layout: header, top menu (quicklook, cpu, mem, swap, load), left sidebar,
  process list and alert footer
- decorations/colors, bars, per-plugin render implementations
- hotkeys: quit, help, modes, sorting, cursor, nice, kill, filters, toggles
- TestBackend layout test
This commit is contained in:
glances-rs
2026-10-09 23:35:23 +00:00
parent 975385da5d
commit 8385900ac1
27 changed files with 2053 additions and 31 deletions
+6 -3
View File
@@ -92,7 +92,7 @@ fn run(mut args: Args) -> Result<()> {
return run_quiet(&mut engine, &args);
}
tui_placeholder::run(&mut engine, &args)
glances_tui::run(&mut engine)
}
fn display_modules_list(engine: &Engine) {
@@ -139,8 +139,11 @@ fn run_stdout(engine: &mut Engine, args: &Args) -> Result<()> {
let csv_plugins = args.stdout_csv.as_deref().map(|value| {
value
.split(',')
.map(|p| p.split('.').next().unwrap_or(p).trim().to_string())
.filter(|p| !p.is_empty())
.map(|p| match p.split_once('.') {
Some((plugin, attribute)) => (plugin.trim().to_string(), Some(attribute.to_string())),
None => (p.trim().to_string(), None),
})
.filter(|(p, _)| !p.is_empty())
.collect::<Vec<_>>()
});
+19 -7
View File
@@ -1,7 +1,7 @@
//! Terminal output modes: `--stdout`, `--stdout-csv`, `--stdout-json`.
use glances_plugins::engine::Engine;
use serde_json::{Map, Value, json};
use serde_json::{Map, Value};
/// One `<plugin>[.<key>].<attribute>` item of the stdout modes.
#[derive(Debug, Clone, PartialEq)]
@@ -119,9 +119,9 @@ impl CsvRenderer {
Self { header: true, ..Default::default() }
}
pub fn render(&mut self, engine: &Engine, plugins: &[String]) -> String {
pub fn render(&mut self, engine: &Engine, items: &[(String, Option<String>)]) -> String {
let mut line = String::new();
for name in plugins {
for (name, attribute) in items {
let Some(plugin) = engine.get_plugin(name) else {
continue;
};
@@ -130,9 +130,9 @@ impl CsvRenderer {
}
let stat = plugin.export();
if self.header {
line.push_str(&self.build_header(name, &stat));
line.push_str(&self.build_header(name, attribute.as_deref(), &stat));
} else {
line.push_str(&self.build_data(name, &stat));
line.push_str(&self.build_data(name, attribute.as_deref(), &stat));
}
}
self.header = false;
@@ -140,8 +140,12 @@ impl CsvRenderer {
line
}
fn build_header(&mut self, plugin: &str, stat: &Value) -> String {
fn build_header(&mut self, plugin: &str, attribute: Option<&str>, stat: &Value) -> String {
let mut line = String::new();
if let Some(attribute) = attribute {
line.push_str(&format!("{plugin}.{attribute},"));
return line;
}
match stat {
Value::Object(map) => {
for key in map.keys() {
@@ -178,8 +182,16 @@ impl CsvRenderer {
line
}
fn build_data(&self, plugin: &str, stat: &Value) -> String {
fn build_data(&self, plugin: &str, attribute: Option<&str>, stat: &Value) -> String {
let mut line = String::new();
if let Some(attribute) = attribute {
let text = match stat {
Value::Object(map) => map.get(attribute).map(glances_core::py_str).unwrap_or_else(|| "N/A".to_string()),
_ => "N/A".to_string(),
};
line.push_str(&format!("{text},"));
return line;
}
match stat {
Value::Object(map) => {
for value in map.values() {
@@ -5,10 +5,6 @@ use anyhow::Result;
use glances_core::Args;
use glances_plugins::engine::Engine;
pub fn run(_engine: &mut Engine, _args: &Args) -> Result<()> {
anyhow::bail!("terminal UI is not implemented yet (use --stdout, --stdout-csv or --stdout-json)")
}
pub fn run_fetch(_engine: &mut Engine, _args: &Args) -> Result<()> {
anyhow::bail!("fetch mode is not implemented yet")
}