1use crate::agent::ui::colors::{ansi, icons};
11use colored::Colorize;
12use std::io::{self, Write};
13
14pub const PROMETHEUS_ICON: &str = "🔥";
16pub const K8S_ICON: &str = "☸";
18pub const NETWORK_ICON: &str = "🔗";
20pub const PORT_FORWARD_ICON: &str = "🚇";
22pub const SEARCH_ICON: &str = "🔍";
24
25pub struct PrometheusDiscoveryDisplay {
27 started: bool,
28}
29
30impl PrometheusDiscoveryDisplay {
31 pub fn new() -> Self {
32 Self { started: false }
33 }
34
35 pub fn start(&mut self, namespace: Option<&str>) {
37 self.started = true;
38 let scope = namespace.unwrap_or("all namespaces");
39
40 println!();
41 println!(
42 "{}{} Prometheus Discovery{}",
43 ansi::BOLD,
44 PROMETHEUS_ICON,
45 ansi::RESET
46 );
47 println!(
48 "{}├─{} {} Searching for Prometheus services in {}...{}",
49 ansi::DIM,
50 ansi::RESET,
51 SEARCH_ICON,
52 scope.cyan(),
53 ansi::RESET
54 );
55 let _ = io::stdout().flush();
56 }
57
58 pub fn found_services(&self, services: &[DiscoveredService]) {
60 if services.is_empty() {
61 println!(
62 "{}├─{} {} {}{}",
63 ansi::DIM,
64 ansi::RESET,
65 icons::WARNING.yellow(),
66 "No Prometheus services found".yellow(),
67 ansi::RESET
68 );
69 } else {
70 println!(
71 "{}├─{} {} Found {} service(s):{}",
72 ansi::DIM,
73 ansi::RESET,
74 icons::SUCCESS.green(),
75 services.len().to_string().green().bold(),
76 ansi::RESET
77 );
78
79 for (i, svc) in services.iter().enumerate() {
80 let is_last = i == services.len() - 1;
81 let prefix = if is_last { "└─" } else { "├─" };
82
83 println!(
84 "{}│ {}─{} {} {}/{} {}:{}{}",
85 ansi::DIM,
86 prefix,
87 ansi::RESET,
88 K8S_ICON,
89 svc.namespace.cyan(),
90 svc.name.cyan().bold(),
91 "port".dimmed(),
92 svc.port.to_string().yellow(),
93 ansi::RESET
94 );
95 }
96 }
97 let _ = io::stdout().flush();
98 }
99
100 pub fn show_suggestion(&self, service: &DiscoveredService) {
102 println!("{}│{}", ansi::DIM, ansi::RESET);
103 println!(
104 "{}└─{} {} Next: Use {} to connect{}",
105 ansi::DIM,
106 ansi::RESET,
107 icons::ARROW.cyan(),
108 "prometheus_connect".cyan().bold(),
109 ansi::RESET
110 );
111 println!(
112 " {} service: {}, namespace: {}, port: {}",
113 "→".dimmed(),
114 service.name.green(),
115 service.namespace.green(),
116 service.port.to_string().yellow()
117 );
118 let _ = io::stdout().flush();
119 }
120
121 pub fn searching_all_namespaces(&self) {
123 println!(
124 "{}├─{} {} {}{}",
125 ansi::DIM,
126 ansi::RESET,
127 SEARCH_ICON,
128 "Not found in specified namespace, searching all namespaces...".yellow(),
129 ansi::RESET
130 );
131 let _ = io::stdout().flush();
132 }
133
134 pub fn error(&self, message: &str) {
136 println!(
137 "{}└─{} {} {}{}",
138 ansi::DIM,
139 ansi::RESET,
140 icons::ERROR.red(),
141 message.red(),
142 ansi::RESET
143 );
144 let _ = io::stdout().flush();
145 }
146}
147
148impl Default for PrometheusDiscoveryDisplay {
149 fn default() -> Self {
150 Self::new()
151 }
152}
153
154#[derive(Debug, Clone)]
156pub struct DiscoveredService {
157 pub name: String,
158 pub namespace: String,
159 pub port: u16,
160 pub service_type: String,
161}
162
163pub struct PrometheusConnectionDisplay {
165 mode: ConnectionMode,
166}
167
168#[derive(Debug, Clone, Copy, PartialEq)]
169pub enum ConnectionMode {
170 PortForward,
171 DirectUrl,
172}
173
174impl PrometheusConnectionDisplay {
175 pub fn new(mode: ConnectionMode) -> Self {
176 Self { mode }
177 }
178
179 pub fn start(&self, target: &str) {
181 println!();
182 println!(
183 "{}{} Prometheus Connection{}",
184 ansi::BOLD,
185 NETWORK_ICON,
186 ansi::RESET
187 );
188
189 match self.mode {
190 ConnectionMode::PortForward => {
191 println!(
192 "{}├─{} {} Establishing port-forward to {}...{}",
193 ansi::DIM,
194 ansi::RESET,
195 PORT_FORWARD_ICON,
196 target.cyan(),
197 ansi::RESET
198 );
199 }
200 ConnectionMode::DirectUrl => {
201 println!(
202 "{}├─{} {} Connecting to {}...{}",
203 ansi::DIM,
204 ansi::RESET,
205 NETWORK_ICON,
206 target.cyan(),
207 ansi::RESET
208 );
209 }
210 }
211 let _ = io::stdout().flush();
212 }
213
214 pub fn port_forward_established(&self, local_port: u16, service: &str, namespace: &str) {
216 println!(
217 "{}├─{} {} Port-forward active on localhost:{}{}",
218 ansi::DIM,
219 ansi::RESET,
220 icons::SUCCESS.green(),
221 local_port.to_string().green().bold(),
222 ansi::RESET
223 );
224 println!(
225 "{}│ {} {} {}/{} {}",
226 ansi::DIM,
227 ansi::RESET,
228 "→".dimmed(),
229 namespace.dimmed(),
230 service.dimmed(),
231 "(no auth needed)".dimmed()
232 );
233 let _ = io::stdout().flush();
234 }
235
236 pub fn testing_connection(&self) {
238 print!(
239 "{}├─{} {} Testing Prometheus API...{}",
240 ansi::DIM,
241 ansi::RESET,
242 icons::EXECUTING.cyan(),
243 ansi::RESET
244 );
245 let _ = io::stdout().flush();
246 }
247
248 pub fn connected(&self, url: &str, authenticated: bool) {
250 print!("\r{}", ansi::CLEAR_LINE);
252
253 println!(
254 "{}├─{} {} Connection established{}",
255 ansi::DIM,
256 ansi::RESET,
257 icons::SUCCESS.green(),
258 ansi::RESET
259 );
260
261 let auth_status = if authenticated {
262 "(authenticated)".green()
263 } else {
264 "(no auth)".dimmed()
265 };
266
267 println!(
268 "{}│ {} URL: {} {}{}",
269 ansi::DIM,
270 ansi::RESET,
271 url.cyan(),
272 auth_status,
273 ansi::RESET
274 );
275 let _ = io::stdout().flush();
276 }
277
278 pub fn ready_for_use(&self, url: &str) {
280 println!("{}│{}", ansi::DIM, ansi::RESET);
281 println!(
282 "{}└─{} {} Ready! Use with {}{}",
283 ansi::DIM,
284 ansi::RESET,
285 PROMETHEUS_ICON,
286 "k8s_optimize".cyan().bold(),
287 ansi::RESET
288 );
289 println!(" {} prometheus: \"{}\"", "→".dimmed(), url.green());
290 let _ = io::stdout().flush();
291 }
292
293 pub fn connection_failed(&self, error: &str, suggestions: &[&str]) {
295 print!("\r{}", ansi::CLEAR_LINE);
297
298 println!(
299 "{}├─{} {} Connection failed: {}{}",
300 ansi::DIM,
301 ansi::RESET,
302 icons::ERROR.red(),
303 error.red(),
304 ansi::RESET
305 );
306
307 if !suggestions.is_empty() {
308 println!("{}│{}", ansi::DIM, ansi::RESET);
309 println!("{}├─{} Suggestions:{}", ansi::DIM, ansi::RESET, ansi::RESET);
310
311 for (i, suggestion) in suggestions.iter().enumerate() {
312 let is_last = i == suggestions.len() - 1;
313 let prefix = if is_last { "└─" } else { "├─" };
314
315 println!(
316 "{}│ {}─{} {}{}",
317 ansi::DIM,
318 prefix,
319 ansi::RESET,
320 suggestion.yellow(),
321 ansi::RESET
322 );
323 }
324 }
325 let _ = io::stdout().flush();
326 }
327
328 pub fn auth_required(&self) {
330 println!(
331 "{}├─{} {} {}{}",
332 ansi::DIM,
333 ansi::RESET,
334 icons::SECURITY.yellow(),
335 "Authentication may be required for external Prometheus".yellow(),
336 ansi::RESET
337 );
338 println!(
339 "{}│ {} Provide auth_type: \"basic\" or \"bearer\"{}",
340 ansi::DIM,
341 "→".dimmed(),
342 ansi::RESET
343 );
344 let _ = io::stdout().flush();
345 }
346
347 pub fn background_process_info(&self, process_id: &str) {
349 println!(
350 "{}│ {} Background process: {} {}",
351 ansi::DIM,
352 ansi::RESET,
353 process_id.dimmed(),
354 "(will auto-cleanup)".dimmed()
355 );
356 let _ = io::stdout().flush();
357 }
358}
359
360pub struct PrometheusInlineDisplay;
362
363impl PrometheusInlineDisplay {
364 pub fn discovery_start() {
366 print!(
367 "{} {} Discovering Prometheus services...",
368 icons::EXECUTING.cyan(),
369 PROMETHEUS_ICON
370 );
371 let _ = io::stdout().flush();
372 }
373
374 pub fn discovery_result(count: usize) {
376 print!("\r{}", ansi::CLEAR_LINE);
377 if count > 0 {
378 println!(
379 "{} {} Found {} Prometheus service(s)",
380 icons::SUCCESS.green(),
381 PROMETHEUS_ICON,
382 count.to_string().green().bold()
383 );
384 } else {
385 println!(
386 "{} {} No Prometheus services found",
387 icons::WARNING.yellow(),
388 PROMETHEUS_ICON
389 );
390 }
391 let _ = io::stdout().flush();
392 }
393
394 pub fn connect_start(target: &str) {
396 print!(
397 "{} {} Connecting to {}...",
398 icons::EXECUTING.cyan(),
399 NETWORK_ICON,
400 target.cyan()
401 );
402 let _ = io::stdout().flush();
403 }
404
405 pub fn connect_result(success: bool, url: &str) {
407 print!("\r{}", ansi::CLEAR_LINE);
408 if success {
409 println!(
410 "{} {} Connected: {}",
411 icons::SUCCESS.green(),
412 NETWORK_ICON,
413 url.green()
414 );
415 } else {
416 println!(
417 "{} {} Connection failed to {}",
418 icons::ERROR.red(),
419 NETWORK_ICON,
420 url
421 );
422 }
423 let _ = io::stdout().flush();
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[test]
432 fn test_discovered_service() {
433 let svc = DiscoveredService {
434 name: "prometheus-server".to_string(),
435 namespace: "monitoring".to_string(),
436 port: 9090,
437 service_type: "ClusterIP".to_string(),
438 };
439 assert_eq!(svc.name, "prometheus-server");
440 assert_eq!(svc.port, 9090);
441 }
442
443 #[test]
444 fn test_connection_mode() {
445 let display = PrometheusConnectionDisplay::new(ConnectionMode::PortForward);
446 assert_eq!(display.mode, ConnectionMode::PortForward);
447 }
448}