use crate::tui::app::UiKey;
use crate::tui::driver::{driver_of, Driver};
use crate::tui::projection::{ClientRow, ServerRow};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tone {
Normal,
Dim,
Good,
Warn,
Bad,
Accent,
Server,
Client,
Title,
Reasoning,
}
impl Driver {
pub fn tone(&self) -> Tone {
match self {
Driver::Manual => Tone::Warn,
Driver::Llm => Tone::Reasoning,
Driver::Silent => Tone::Dim,
Driver::Rules => Tone::Good,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstanceLine {
pub key: UiKey,
pub glyph: &'static str,
pub glyph_tone: Tone,
pub id: u32,
pub protocol: String,
pub target: String,
pub peers: String,
pub driver: Driver,
pub waiting: usize,
pub error: Option<String>,
}
pub fn server_line(row: &ServerRow) -> InstanceLine {
use crate::state::server::ServerStatus;
let (glyph, glyph_tone, error) = match &row.status {
ServerStatus::Running => ("●", Tone::Good, None),
ServerStatus::Starting => ("◐", Tone::Warn, None),
ServerStatus::Stopped => ("○", Tone::Dim, None),
ServerStatus::Error(e) => ("✗", Tone::Bad, Some(e.clone())),
};
let live = row.conns.iter().filter(|c| c.active).count();
InstanceLine {
key: UiKey::Server(row.id),
glyph,
glyph_tone,
id: row.id.as_u32(),
protocol: row.protocol.to_lowercase(),
target: row
.local_addr
.as_deref()
.and_then(|a| a.rsplit_once(':').map(|(_, p)| format!(":{p}")))
.unwrap_or_else(|| format!(":{}", row.port)),
peers: format!("{live}⇄"),
driver: driver_of(row.routing.as_ref()),
waiting: row.intercepts.len(),
error,
}
}
pub fn client_line(row: &ClientRow) -> InstanceLine {
use crate::state::client::ClientStatus;
let (glyph, glyph_tone, error) = match &row.status {
ClientStatus::Connected => ("●", Tone::Good, None),
ClientStatus::Connecting => ("◐", Tone::Warn, None),
ClientStatus::Disconnected => ("○", Tone::Dim, None),
ClientStatus::Error(e) => ("✗", Tone::Bad, Some(e.clone())),
};
InstanceLine {
key: UiKey::Client(row.id),
glyph,
glyph_tone,
id: row.id.as_u32(),
protocol: row.protocol.to_lowercase(),
target: format!("→{}", row.remote_addr),
peers: String::new(),
driver: driver_of(row.routing.as_ref()),
waiting: row.intercepts.len(),
error,
}
}
pub fn fit(text: &str, width: usize) -> String {
let count = text.chars().count();
if count <= width {
return text.to_string();
}
if width == 0 {
return String::new();
}
let mut out: String = text.chars().take(width - 1).collect();
out.push('…');
out
}