Skip to main content

netget/tui/
command_exec.rs

1//! Sink-based execution of the remaining `UserCommand` variants.
2//!
3//! The read-only reports are derived from `AppState` and the registries as
4//! plain lines; the state-changing ones route through the same APIs the rest
5//! of the dashboard uses.
6
7use tokio::sync::mpsc;
8
9use crate::events::UserCommand;
10use crate::state::app_state::AppState;
11
12/// Execute a command, returning lines for the chat pane.
13pub async fn execute(
14    command: UserCommand,
15    state: &AppState,
16    status_tx: &mpsc::UnboundedSender<String>,
17) -> Vec<String> {
18    match command {
19        UserCommand::Status => status_report(state).await,
20        UserCommand::Manage => {
21            let mut lines = vec![
22                "The list on the left is the management surface (Tab reaches it):".to_string(),
23                "  a  start a server      A  start a client     e  edit config".to_string(),
24                "  r  rules               m  cycle the driver   x  stop / remove".to_string(),
25                "  c  connect a client to a server              n  send through a client"
26                    .to_string(),
27            ];
28            lines.extend(status_report(state).await);
29            lines
30        }
31        UserCommand::ShowStability => crate::protocol::stability_report(None),
32        UserCommand::ShowUsage => {
33            let (input, output, calls) = state.get_llm_stats().await;
34            vec![
35                format!("LLM calls:     {calls}"),
36                format!("Input tokens:  {input}"),
37                format!("Output tokens: {output}"),
38            ]
39        }
40        UserCommand::ShowEnvironment => environment_report(state).await,
41        UserCommand::ShowWebSearch => {
42            vec![format!(
43                "Web search: {:?}",
44                state.get_web_search_mode().await
45            )]
46        }
47        UserCommand::SetWebSearch { mode } => {
48            state.set_web_search_mode(mode).await;
49            vec![format!("Web search set to {mode:?}")]
50        }
51        UserCommand::ShowEventHandler => {
52            vec![format!(
53                "Handler mode: {:?}",
54                state.get_event_handler_mode().await
55            )]
56        }
57        UserCommand::SetEventHandler { mode } => {
58            state.set_event_handler_mode(mode).await;
59            vec![format!("Handler mode set to {mode:?}")]
60        }
61        UserCommand::ShowDocs { protocol } => match protocol {
62            Some(name) => match crate::protocol::server_registry::registry().resolve(&name) {
63                Ok(proto) => vec![
64                    format!("{} — {}", proto.protocol_name(), proto.description()),
65                    format!("maturity: {}", proto.metadata().summary()),
66                ],
67                Err(e) => vec![format!("{e}")],
68            },
69            None => vec!["Usage: /docs <protocol> — or press d on an instance".to_string()],
70        },
71        UserCommand::ShowBackend => {
72            vec![format!("Backend URL: {}", state.get_ollama_url().await)]
73        }
74        UserCommand::TestOutput { count } => {
75            for i in 1..=count {
76                let _ = status_tx.send(format!("[INFO] test output line {i}/{count}"));
77            }
78            Vec::new()
79        }
80        UserCommand::SetFooterStatus { message } => match message {
81            Some(m) => vec![format!("Status note: {m}")],
82            None => vec!["Status note cleared".to_string()],
83        },
84        other => vec![format!("'{other:?}' is not available in the dashboard yet")],
85    }
86}
87
88async fn status_report(state: &AppState) -> Vec<String> {
89    let servers = state.get_all_servers().await;
90    let clients = state.get_all_clients().await;
91    let mut lines = vec![format!(
92        "{} server(s), {} client(s)",
93        servers.len(),
94        clients.len()
95    )];
96    for s in servers {
97        lines.push(format!(
98            "  server #{} {} :{} {} — {} conn",
99            s.id.as_u32(),
100            s.protocol_name,
101            s.port,
102            s.status,
103            s.connections.len()
104        ));
105    }
106    for c in clients {
107        lines.push(format!(
108            "  client #{} {} → {} {}",
109            c.id.as_u32(),
110            c.protocol_name,
111            c.remote_addr,
112            c.status
113        ));
114    }
115    lines
116}
117
118async fn environment_report(state: &AppState) -> Vec<String> {
119    let env = state.get_scripting_env().await;
120    let caps = state.get_system_capabilities().await;
121    let mut lines = vec!["Scripting interpreters:".to_string()];
122    for language in [
123        crate::scripting::ScriptLanguage::Python,
124        crate::scripting::ScriptLanguage::JavaScript,
125        crate::scripting::ScriptLanguage::Go,
126        crate::scripting::ScriptLanguage::Perl,
127    ] {
128        lines.push(format!(
129            "  {:<11} {}",
130            language.as_str(),
131            if env.is_available(language) {
132                "available"
133            } else {
134                "not installed"
135            }
136        ));
137    }
138    lines.push(String::new());
139    lines.push("Privileges:".to_string());
140    lines.push(format!("  root:            {}", caps.is_root));
141    lines.push(format!(
142        "  privileged port: {}",
143        caps.can_bind_privileged_ports
144    ));
145    lines.push(format!("  raw sockets:     {}", caps.has_raw_socket_access));
146    lines.push(format!(
147        "  packet capture:  {}",
148        caps.has_packet_capture_access
149    ));
150    lines
151}