1use crate::tui::app::UiKey;
5use crate::tui::driver::{driver_of, Driver};
6use crate::tui::projection::{ClientRow, ServerRow};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Tone {
11 Normal,
12 Dim,
13 Good,
14 Warn,
15 Bad,
16 Accent,
17 Server,
18 Client,
19 Title,
20 Reasoning,
21}
22
23impl Driver {
24 pub fn tone(&self) -> Tone {
25 match self {
26 Driver::Manual => Tone::Warn,
27 Driver::Llm => Tone::Reasoning,
28 Driver::Silent => Tone::Dim,
29 Driver::Rules => Tone::Good,
30 }
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct InstanceLine {
37 pub key: UiKey,
38 pub glyph: &'static str,
39 pub glyph_tone: Tone,
40 pub id: u32,
41 pub protocol: String,
42 pub target: String,
44 pub peers: String,
46 pub driver: Driver,
47 pub waiting: usize,
49 pub error: Option<String>,
51}
52
53pub fn server_line(row: &ServerRow) -> InstanceLine {
54 use crate::state::server::ServerStatus;
55 let (glyph, glyph_tone, error) = match &row.status {
56 ServerStatus::Running => ("●", Tone::Good, None),
57 ServerStatus::Starting => ("◐", Tone::Warn, None),
58 ServerStatus::Stopped => ("○", Tone::Dim, None),
59 ServerStatus::Error(e) => ("✗", Tone::Bad, Some(e.clone())),
60 };
61 let live = row.conns.iter().filter(|c| c.active).count();
62 InstanceLine {
63 key: UiKey::Server(row.id),
64 glyph,
65 glyph_tone,
66 id: row.id.as_u32(),
67 protocol: row.protocol.to_lowercase(),
68 target: row
69 .local_addr
70 .as_deref()
71 .and_then(|a| a.rsplit_once(':').map(|(_, p)| format!(":{p}")))
72 .unwrap_or_else(|| format!(":{}", row.port)),
73 peers: format!("{live}⇄"),
74 driver: driver_of(row.routing.as_ref()),
75 waiting: row.intercepts.len(),
76 error,
77 }
78}
79
80pub fn client_line(row: &ClientRow) -> InstanceLine {
81 use crate::state::client::ClientStatus;
82 let (glyph, glyph_tone, error) = match &row.status {
83 ClientStatus::Connected => ("●", Tone::Good, None),
84 ClientStatus::Connecting => ("◐", Tone::Warn, None),
85 ClientStatus::Disconnected => ("○", Tone::Dim, None),
86 ClientStatus::Error(e) => ("✗", Tone::Bad, Some(e.clone())),
87 };
88 InstanceLine {
89 key: UiKey::Client(row.id),
90 glyph,
91 glyph_tone,
92 id: row.id.as_u32(),
93 protocol: row.protocol.to_lowercase(),
94 target: format!("→{}", row.remote_addr),
95 peers: String::new(),
96 driver: driver_of(row.routing.as_ref()),
97 waiting: row.intercepts.len(),
98 error,
99 }
100}
101
102pub fn fit(text: &str, width: usize) -> String {
104 let count = text.chars().count();
105 if count <= width {
106 return text.to_string();
107 }
108 if width == 0 {
109 return String::new();
110 }
111 let mut out: String = text.chars().take(width - 1).collect();
112 out.push('…');
113 out
114}