1use std::{
2    collections::{BTreeMap, HashMap, HashSet},
3    fmt::{self, Display, Formatter},
4    net::SocketAddr,
5};
6
7use prettytable::{Row, Table, cell, row};
8use time::format_description;
9use x509_parser::time::ASN1Time;
10
11use super::command::FilteredHistogram;
12use crate::{
13    AsString,
14    proto::{
15        DisplayError,
16        command::{
17            AggregatedMetrics, AvailableMetrics, CertificateAndKey, CertificateSummary,
18            CertificatesWithFingerprints, ClusterMetrics, CustomHttpAnswers, Event, EventKind,
19            FilteredMetrics, HttpEndpoint, HttpListenerConfig, HttpsListenerConfig,
20            ListOfCertificatesByAddress, ListedFrontends, ListenersList, ProtobufEndpoint,
21            QueryCertificatesFilters, RequestCounts, Response, ResponseContent, ResponseStatus,
22            RunState, SocketAddress, TlsVersion, WorkerInfos, WorkerMetrics, WorkerResponses,
23            filtered_metrics, protobuf_endpoint, request::RequestType,
24            response_content::ContentType,
25        },
26    },
27};
28
29impl Display for CertificateAndKey {
30    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
31        let versions = self.versions.iter().fold(String::new(), |acc, tls_v| {
32            acc + " "
33                + match TlsVersion::try_from(*tls_v) {
34                    Ok(v) => v.as_str_name(),
35                    Err(_) => "",
36                }
37        });
38        write!(
39            f,
40            "\tcertificate: {}\n\tcertificate_chain: {:?}\n\tkey: {}\n\tTLS versions: {}\n\tnames: {:?}",
41            self.certificate,
42            self.certificate_chain,
43            self.key,
44            versions,
45            concatenate_vector(&self.names)
46        )
47    }
48}
49
50impl Display for CertificateSummary {
51    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
52        write!(f, "{}:\t{}", self.fingerprint, self.domain)
53    }
54}
55
56impl Display for QueryCertificatesFilters {
57    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
58        if let Some(d) = self.domain.clone() {
59            write!(f, "domain:{}", d)
60        } else if let Some(fp) = self.fingerprint.clone() {
61            write!(f, "domain:{}", fp)
62        } else {
63            write!(f, "all certificates")
64        }
65    }
66}
67
68pub fn concatenate_vector(vec: &[String]) -> String {
69    vec.join(", ")
70}
71
72pub fn format_request_type(request_type: &RequestType) -> &str {
73    match request_type {
74        RequestType::SaveState(_) => "SaveState",
75        RequestType::LoadState(_) => "LoadState",
76        RequestType::CountRequests(_) => "CountRequests",
77        RequestType::ListWorkers(_) => "ListWorkers",
78        RequestType::ListFrontends(_) => "ListFrontends",
79        RequestType::ListListeners(_) => "ListListeners",
80        RequestType::LaunchWorker(_) => "LaunchWorker",
81        RequestType::UpgradeMain(_) => "UpgradeMain",
82        RequestType::UpgradeWorker(_) => "UpgradeWorker",
83        RequestType::SubscribeEvents(_) => "SubscribeEvents",
84        RequestType::ReloadConfiguration(_) => "ReloadConfiguration",
85        RequestType::Status(_) => "Status",
86        RequestType::AddCluster(_) => "AddCluster",
87        RequestType::RemoveCluster(_) => "RemoveCluster",
88        RequestType::AddHttpFrontend(_) => "AddHttpFrontend",
89        RequestType::RemoveHttpFrontend(_) => "RemoveHttpFrontend",
90        RequestType::AddHttpsFrontend(_) => "AddHttpsFrontend",
91        RequestType::RemoveHttpsFrontend(_) => "RemoveHttpsFrontend",
92        RequestType::AddCertificate(_) => "AddCertificate",
93        RequestType::ReplaceCertificate(_) => "ReplaceCertificate",
94        RequestType::RemoveCertificate(_) => "RemoveCertificate",
95        RequestType::AddTcpFrontend(_) => "AddTcpFrontend",
96        RequestType::RemoveTcpFrontend(_) => "RemoveTcpFrontend",
97        RequestType::AddBackend(_) => "AddBackend",
98        RequestType::RemoveBackend(_) => "RemoveBackend",
99        RequestType::AddHttpListener(_) => "AddHttpListener",
100        RequestType::AddHttpsListener(_) => "AddHttpsListener",
101        RequestType::AddTcpListener(_) => "AddTcpListener",
102        RequestType::RemoveListener(_) => "RemoveListener",
103        RequestType::ActivateListener(_) => "ActivateListener",
104        RequestType::DeactivateListener(_) => "DeactivateListener",
105        RequestType::QueryClusterById(_) => "QueryClusterById",
106        RequestType::QueryClustersByDomain(_) => "QueryClustersByDomain",
107        RequestType::QueryClustersHashes(_) => "QueryClustersHashes",
108        RequestType::QueryMetrics(_) => "QueryMetrics",
109        RequestType::SoftStop(_) => "SoftStop",
110        RequestType::HardStop(_) => "HardStop",
111        RequestType::ConfigureMetrics(_) => "ConfigureMetrics",
112        RequestType::Logging(_) => "Logging",
113        RequestType::ReturnListenSockets(_) => "ReturnListenSockets",
114        RequestType::QueryCertificatesFromTheState(_) => "QueryCertificatesFromTheState",
115        RequestType::QueryCertificatesFromWorkers(_) => "QueryCertificatesFromWorkers",
116    }
117}
118
119pub fn print_json_response<T: ::serde::Serialize>(input: &T) -> Result<(), DisplayError> {
120    let pretty_json = serde_json::to_string_pretty(&input).map_err(DisplayError::Json)?;
121    println!("{pretty_json}");
122    Ok(())
123}
124
125impl Response {
126    pub fn display(&self, json: bool) -> Result<(), DisplayError> {
127        match self.status() {
128            ResponseStatus::Ok => {
129                if !json {
131                    println!("Success: {}", self.message)
132                }
133            }
134            ResponseStatus::Failure => println!("Failure: {}", self.message),
135            ResponseStatus::Processing => {
136                return Err(DisplayError::WrongResponseType(
137                    "ResponseStatus::Processing".to_string(),
138                ));
139            }
140        }
141
142        match &self.content {
143            Some(content) => content.display(json),
144            None => {
145                if json {
146                    println!("{{}}");
147                } else {
148                    println!("No content");
149                }
150                Ok(())
151            }
152        }
153    }
154}
155
156impl ResponseContent {
157    fn display(&self, json: bool) -> Result<(), DisplayError> {
158        let content_type = match &self.content_type {
159            Some(content_type) => content_type,
160            None => {
161                println!("No content");
162                return Ok(());
163            }
164        };
165
166        if json {
167            return print_json_response(&content_type);
168        }
169
170        match content_type {
171            ContentType::Workers(worker_infos) => print_status(worker_infos),
172            ContentType::Metrics(aggr_metrics) => print_metrics(aggr_metrics),
173            ContentType::FrontendList(frontends) => print_frontends(frontends),
174            ContentType::ListenersList(listeners) => print_listeners(listeners),
175            ContentType::WorkerMetrics(worker_metrics) => print_worker_metrics(worker_metrics),
176            ContentType::AvailableMetrics(list) => print_available_metrics(list),
177            ContentType::RequestCounts(request_counts) => print_request_counts(request_counts),
178            ContentType::CertificatesWithFingerprints(certs) => {
179                print_certificates_with_validity(certs)
180            }
181            ContentType::WorkerResponses(worker_responses) => {
182                if worker_responses.contain_cluster_infos() {
184                    print_cluster_infos(worker_responses)
185                } else if worker_responses.contain_cluster_hashes() {
186                    print_cluster_hashes(worker_responses)
187                } else {
188                    print_responses_by_worker(worker_responses, json)
189                }
190            }
191            ContentType::Clusters(_) | ContentType::ClusterHashes(_) => Ok(()), ContentType::CertificatesByAddress(certs) => print_certificates_by_address(certs),
193            ContentType::Event(_event) => Ok(()), }
195    }
196}
197
198impl WorkerResponses {
199    fn contain_cluster_infos(&self) -> bool {
200        for (_worker_id, response) in self.map.iter() {
201            if let Some(content_type) = &response.content_type {
202                if matches!(content_type, ContentType::Clusters(_)) {
203                    return true;
204                }
205            }
206        }
207        false
208    }
209
210    fn contain_cluster_hashes(&self) -> bool {
211        for (_worker_id, response) in self.map.iter() {
212            if let Some(content_type) = &response.content_type {
213                if matches!(content_type, ContentType::ClusterHashes(_)) {
214                    return true;
215                }
216            }
217        }
218        false
219    }
220}
221
222pub fn print_status(worker_infos: &WorkerInfos) -> Result<(), DisplayError> {
223    let mut table = Table::new();
224    table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
225    table.add_row(row!["worker id", "pid", "run state"]);
226
227    let mut sorted_infos = worker_infos.vec.clone();
228    sorted_infos.sort_by_key(|worker| worker.id);
229
230    for worker_info in &sorted_infos {
231        let row = row!(
232            worker_info.id,
233            worker_info.pid,
234            RunState::try_from(worker_info.run_state)
235                .map_err(DisplayError::DecodeError)?
236                .as_str_name()
237        );
238        table.add_row(row);
239    }
240
241    table.printstd();
242    Ok(())
243}
244
245pub fn print_metrics(aggregated_metrics: &AggregatedMetrics) -> Result<(), DisplayError> {
246    println!("\nMAIN PROCESS\n============");
248    print_proxy_metrics(&aggregated_metrics.main);
249
250    if !aggregated_metrics.proxying.is_empty() {
251        println!("\nPROXYING\n============");
252        print_proxy_metrics(&aggregated_metrics.proxying);
253    }
254
255    for (worker_id, worker) in aggregated_metrics.workers.iter() {
257        if !worker.clusters.is_empty() && !worker.proxy.is_empty() {
258            println!("\nWorker {worker_id}\n=========");
259            print_worker_metrics(worker)?;
260        }
261    }
262
263    if !aggregated_metrics.clusters.is_empty() {
265        println!("\nClusters\n=======");
266        print_cluster_metrics(&aggregated_metrics.clusters);
267    }
268
269    Ok(())
270}
271
272fn print_proxy_metrics(proxy_metrics: &BTreeMap<String, FilteredMetrics>) {
273    let filtered = filter_metrics(proxy_metrics);
274    print_gauges_and_counts(&filtered);
275    print_percentiles(&filtered);
276    print_histograms(&filtered);
277}
278
279fn print_worker_metrics(worker_metrics: &WorkerMetrics) -> Result<(), DisplayError> {
280    print_proxy_metrics(&worker_metrics.proxy);
281    print_cluster_metrics(&worker_metrics.clusters);
282
283    Ok(())
284}
285
286fn print_cluster_metrics(cluster_metrics: &BTreeMap<String, ClusterMetrics>) {
287    for (cluster_id, cluster_metrics_data) in cluster_metrics.iter() {
288        println!("\nCluster {cluster_id}\n--------");
289
290        let filtered = filter_metrics(&cluster_metrics_data.cluster);
291        print_gauges_and_counts(&filtered);
292        print_percentiles(&filtered);
293        print_histograms(&filtered);
294
295        let mut backend_metric_acc: HashMap<String, BTreeMap<String, FilteredMetrics>> =
297            HashMap::new();
298
299        for backend in &cluster_metrics_data.backends {
300            for (metric_name, value) in &backend.metrics {
301                backend_metric_acc
302                    .entry(backend.backend_id.clone())
303                    .and_modify(|map| {
304                        map.insert(metric_name.clone(), value.clone());
305                    })
306                    .or_insert(BTreeMap::from([(metric_name.clone(), value.clone())]));
307            }
308        }
309
310        for (backend_id, metrics) in backend_metric_acc {
311            println!("\n{cluster_id}/{backend_id}\n--------");
312            let filtered = filter_metrics(&metrics);
313            print_gauges_and_counts(&filtered);
314            print_percentiles(&filtered);
315            print_histograms(&filtered);
316        }
317    }
318}
319
320fn filter_metrics(
321    metrics: &BTreeMap<String, FilteredMetrics>,
322) -> BTreeMap<String, FilteredMetrics> {
323    let mut filtered_metrics = BTreeMap::new();
324
325    for (metric_key, filtered_value) in metrics.iter() {
326        filtered_metrics.insert(
327            metric_key.replace('\t', ".").to_string(),
328            filtered_value.clone(),
329        );
330    }
331    filtered_metrics
332}
333
334fn print_gauges_and_counts(filtered_metrics: &BTreeMap<String, FilteredMetrics>) {
335    let mut titles: Vec<String> = filtered_metrics
336        .iter()
337        .filter_map(|(title, filtered_data)| match filtered_data.inner {
338            Some(filtered_metrics::Inner::Count(_)) | Some(filtered_metrics::Inner::Gauge(_)) => {
339                Some(title.to_owned())
340            }
341            _ => None,
342        })
343        .collect();
344
345    titles.sort();
347
348    if titles.is_empty() {
349        return;
350    }
351
352    let mut table = Table::new();
353    table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
354
355    table.set_titles(Row::new(vec![cell!(""), cell!("gauge"), cell!("count")]));
356
357    for title in titles {
358        let mut row = vec![cell!(title)];
359        match filtered_metrics.get(&title) {
360            Some(filtered_metrics) => match filtered_metrics.inner {
361                Some(filtered_metrics::Inner::Count(c)) => {
362                    row.push(cell!(""));
363                    row.push(cell!(c))
364                }
365                Some(filtered_metrics::Inner::Gauge(c)) => {
366                    row.push(cell!(c));
367                    row.push(cell!(""))
368                }
369                _ => {}
370            },
371            _ => row.push(cell!("")),
372        }
373        table.add_row(Row::new(row));
374    }
375
376    table.printstd();
377}
378
379fn print_percentiles(filtered_metrics: &BTreeMap<String, FilteredMetrics>) {
380    let mut percentile_titles: Vec<String> = filtered_metrics
381        .iter()
382        .filter_map(|(title, filtered_data)| match filtered_data.inner.clone() {
383            Some(filtered_metrics::Inner::Percentiles(_)) => Some(title.to_owned()),
384            _ => None,
385        })
386        .collect();
387
388    percentile_titles.sort();
390
391    if percentile_titles.is_empty() {
392        return;
393    }
394
395    let mut percentile_table = Table::new();
396    percentile_table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
397
398    percentile_table.set_titles(Row::new(vec![
399        cell!("Percentiles"),
400        cell!("samples"),
401        cell!("p50"),
402        cell!("p90"),
403        cell!("p99"),
404        cell!("p99.9"),
405        cell!("p99.99"),
406        cell!("p99.999"),
407        cell!("p100"),
408    ]));
409
410    for title in percentile_titles {
411        if let Some(FilteredMetrics {
412            inner: Some(filtered_metrics::Inner::Percentiles(percentiles)),
413        }) = filtered_metrics.get(&title)
414        {
415            percentile_table.add_row(Row::new(vec![
416                cell!(title),
417                cell!(percentiles.samples),
418                cell!(percentiles.p_50),
419                cell!(percentiles.p_90),
420                cell!(percentiles.p_99),
421                cell!(percentiles.p_99_9),
422                cell!(percentiles.p_99_99),
423                cell!(percentiles.p_99_999),
424                cell!(percentiles.p_100),
425            ]));
426        } else {
427            println!("Something went VERY wrong here");
428        }
429    }
430
431    percentile_table.printstd();
432}
433
434fn print_histograms(filtered_metrics: &BTreeMap<String, FilteredMetrics>) {
435    let histograms: BTreeMap<String, FilteredHistogram> = filtered_metrics
436        .iter()
437        .filter_map(|(name, metric)| match metric.inner.clone() {
438            Some(filtered_metrics::Inner::Histogram(hist)) => Some((name.to_owned(), hist)),
439            _ => None,
440        })
441        .collect();
442
443    let mut histogram_titles: Vec<String> = histograms.keys().map(ToOwned::to_owned).collect();
444    histogram_titles.sort();
445    if histogram_titles.is_empty() {
446        return;
447    }
448
449    let mut histogram_table = Table::new();
450    histogram_table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
451
452    let mut first_row = Row::new(vec![cell!("Histograms (ms)"), cell!("sum"), cell!("count")]);
453
454    let biggest_hist_length = histograms
455        .values()
456        .map(|hist| hist.buckets.len())
457        .max()
458        .unwrap_or(0);
459
460    for exponent in 0..biggest_hist_length {
462        first_row.add_cell(cell!(format!("{}", (1 << exponent) - 1)));
463    }
464    histogram_table.set_titles(first_row);
465
466    for title in histogram_titles {
467        if let Some(hist) = histograms.get(&title) {
468            let trimmed_name = title.strip_suffix("_histogram").unwrap_or_default();
469            let mut row = Row::new(vec![
470                cell!(trimmed_name),
471                cell!(hist.sum),
472                cell!(hist.count),
473            ]);
474            let mut last_bucket_count = 0;
476            for bucket in &hist.buckets {
477                row.add_cell(cell!(bucket.count - last_bucket_count));
478                last_bucket_count = bucket.count;
479            }
480            histogram_table.add_row(row);
481        }
482    }
483
484    histogram_table.printstd();
485}
486
487fn print_available_metrics(available_metrics: &AvailableMetrics) -> Result<(), DisplayError> {
488    println!("Available metrics on the proxy level:");
489    for metric_name in &available_metrics.proxy_metrics {
490        println!("\t{metric_name}");
491    }
492    println!("Available metrics on the cluster level:");
493    for metric_name in &available_metrics.cluster_metrics {
494        println!("\t{metric_name}");
495    }
496    Ok(())
497}
498
499fn print_frontends(frontends: &ListedFrontends) -> Result<(), DisplayError> {
500    trace!(" We received this frontends to display {:#?}", frontends);
501    if !frontends.http_frontends.is_empty() {
503        let mut table = Table::new();
504        table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
505        table.add_row(row!["HTTP frontends "]);
506        table.add_row(row![
507            "cluster_id",
508            "address",
509            "hostname",
510            "path",
511            "method",
512            "position",
513            "tags"
514        ]);
515        for http_frontend in frontends.http_frontends.iter() {
516            table.add_row(row!(
517                http_frontend
518                    .cluster_id
519                    .clone()
520                    .unwrap_or("Deny".to_owned()),
521                http_frontend.address.to_string(),
522                http_frontend.hostname.to_string(),
523                format!("{:?}", http_frontend.path),
524                format!("{:?}", http_frontend.method),
525                format!("{:?}", http_frontend.position),
526                format_tags_to_string(&http_frontend.tags)
527            ));
528        }
529        table.printstd();
530    }
531
532    if !frontends.https_frontends.is_empty() {
534        let mut table = Table::new();
535        table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
536        table.add_row(row!["HTTPS frontends"]);
537        table.add_row(row![
538            "cluster_id",
539            "address",
540            "hostname",
541            "path",
542            "method",
543            "position",
544            "tags"
545        ]);
546        for https_frontend in frontends.https_frontends.iter() {
547            table.add_row(row!(
548                https_frontend
549                    .cluster_id
550                    .clone()
551                    .unwrap_or("Deny".to_owned()),
552                https_frontend.address.to_string(),
553                https_frontend.hostname.to_string(),
554                format!("{:?}", https_frontend.path),
555                format!("{:?}", https_frontend.method),
556                format!("{:?}", https_frontend.position),
557                format_tags_to_string(&https_frontend.tags)
558            ));
559        }
560        table.printstd();
561    }
562
563    if !frontends.tcp_frontends.is_empty() {
565        let mut table = Table::new();
566        table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
567        table.add_row(row!["TCP frontends  "]);
568        table.add_row(row!["Cluster ID", "address", "tags"]);
569        for tcp_frontend in frontends.tcp_frontends.iter() {
570            table.add_row(row!(
571                tcp_frontend.cluster_id,
572                tcp_frontend.address,
573                format_tags_to_string(&tcp_frontend.tags)
574            ));
575        }
576        table.printstd();
577    }
578    Ok(())
579}
580
581pub fn print_listeners(listeners_list: &ListenersList) -> Result<(), DisplayError> {
582    println!("\nHTTP LISTENERS\n================");
583
584    for (_, http_listener) in listeners_list.http_listeners.iter() {
585        println!("{}", http_listener);
586    }
587
588    println!("\nHTTPS LISTENERS\n================");
589
590    for (_, https_listener) in listeners_list.https_listeners.iter() {
591        println!("{}", https_listener);
592    }
593
594    println!("\nTCP LISTENERS\n================");
595
596    if !listeners_list.tcp_listeners.is_empty() {
597        let mut table = Table::new();
598        table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
599        table.add_row(row!["TCP frontends"]);
600        table.add_row(row![
601            "socket address",
602            "public address",
603            "expect proxy",
604            "front timeout",
605            "back timeout",
606            "connect timeout",
607            "activated"
608        ]);
609        for (_, tcp_listener) in listeners_list.tcp_listeners.iter() {
610            table.add_row(row![
611                format!("{:?}", tcp_listener.address),
612                format!("{:?}", tcp_listener.public_address),
613                tcp_listener.expect_proxy,
614                tcp_listener.front_timeout,
615                tcp_listener.back_timeout,
616                tcp_listener.connect_timeout,
617                tcp_listener.active,
618            ]);
619        }
620        table.printstd();
621    }
622    Ok(())
623}
624
625fn print_cluster_infos(worker_responses: &WorkerResponses) -> Result<(), DisplayError> {
626    let mut cluster_table = create_cluster_table(
627        vec!["id", "sticky_session", "https_redirect"],
628        &worker_responses.map,
629    );
630
631    let mut frontend_table =
632        create_cluster_table(vec!["id", "hostname", "path"], &worker_responses.map);
633
634    let mut https_frontend_table =
635        create_cluster_table(vec!["id", "hostname", "path"], &worker_responses.map);
636
637    let mut tcp_frontend_table = create_cluster_table(vec!["id", "address"], &worker_responses.map);
638
639    let mut backend_table = create_cluster_table(
640        vec!["backend id", "IP address", "Backup"],
641        &worker_responses.map,
642    );
643
644    let worker_ids: HashSet<&String> = worker_responses.map.keys().collect();
645
646    let mut cluster_infos = BTreeMap::new();
647    let mut http_frontends = BTreeMap::new();
648    let mut https_frontends = BTreeMap::new();
649    let mut tcp_frontends = BTreeMap::new();
650    let mut backends = BTreeMap::new();
651
652    for (worker_id, response_content) in worker_responses.map.iter() {
653        if let Some(ContentType::Clusters(clusters)) = &response_content.content_type {
654            for cluster in clusters.vec.iter() {
655                if cluster.configuration.is_some() {
656                    let entry = cluster_infos.entry(cluster).or_insert(Vec::new());
657                    entry.push(worker_id.to_owned());
658                }
659
660                for frontend in cluster.http_frontends.iter() {
661                    let entry = http_frontends.entry(frontend).or_insert(Vec::new());
662                    entry.push(worker_id.to_owned());
663                }
664
665                for frontend in cluster.https_frontends.iter() {
666                    let entry = https_frontends.entry(frontend).or_insert(Vec::new());
667                    entry.push(worker_id.to_owned());
668                }
669
670                for frontend in cluster.tcp_frontends.iter() {
671                    let entry = tcp_frontends.entry(frontend).or_insert(Vec::new());
672                    entry.push(worker_id.to_owned());
673                }
674
675                for backend in cluster.backends.iter() {
676                    let entry = backends.entry(backend).or_insert(Vec::new());
677                    entry.push(worker_id.to_owned());
678                }
679            }
680        }
681    }
682
683    if cluster_infos.is_empty() {
684        println!("no cluster found");
685        return Ok(());
686    }
687
688    println!("Cluster level configuration:\n");
689
690    for (cluster_info, workers_the_cluster_is_present_on) in cluster_infos.iter() {
691        let mut row = Vec::new();
692        row.push(cell!(
693            cluster_info
694                .configuration
695                .as_ref()
696                .map(|conf| conf.cluster_id.to_owned())
697                .unwrap_or_else(|| String::from("None"))
698        ));
699        row.push(cell!(
700            cluster_info
701                .configuration
702                .as_ref()
703                .map(|conf| conf.sticky_session)
704                .unwrap_or_else(|| false)
705        ));
706        row.push(cell!(
707            cluster_info
708                .configuration
709                .as_ref()
710                .map(|conf| conf.https_redirect)
711                .unwrap_or_else(|| false)
712        ));
713
714        for worker in workers_the_cluster_is_present_on {
715            if worker_ids.contains(worker) {
716                row.push(cell!("X"));
717            } else {
718                row.push(cell!(""));
719            }
720        }
721
722        cluster_table.add_row(Row::new(row));
723    }
724
725    cluster_table.printstd();
726
727    println!("\nHTTP frontends configuration for:\n");
728
729    for (key, values) in http_frontends.iter() {
730        let mut row = Vec::new();
731        match &key.cluster_id {
732            Some(cluster_id) => row.push(cell!(cluster_id)),
733            None => row.push(cell!("-")),
734        }
735        row.push(cell!(key.hostname));
736        row.push(cell!(key.path));
737
738        for val in values.iter() {
739            if worker_ids.contains(val) {
740                row.push(cell!("X"));
741            } else {
742                row.push(cell!(""));
743            }
744        }
745
746        frontend_table.add_row(Row::new(row));
747    }
748
749    frontend_table.printstd();
750
751    println!("\nHTTPS frontends configuration for:\n");
752
753    for (key, values) in https_frontends.iter() {
754        let mut row = Vec::new();
755        match &key.cluster_id {
756            Some(cluster_id) => row.push(cell!(cluster_id)),
757            None => row.push(cell!("-")),
758        }
759        row.push(cell!(key.hostname));
760        row.push(cell!(key.path));
761
762        for val in values.iter() {
763            if worker_ids.contains(val) {
764                row.push(cell!("X"));
765            } else {
766                row.push(cell!(""));
767            }
768        }
769
770        https_frontend_table.add_row(Row::new(row));
771    }
772
773    https_frontend_table.printstd();
774
775    println!("\nTCP frontends configuration:\n");
776
777    for (key, values) in tcp_frontends.iter() {
778        let mut row = vec![cell!(key.cluster_id), cell!(format!("{}", key.address))];
779
780        for val in values.iter() {
781            if worker_ids.contains(val) {
782                row.push(cell!(String::from("X")));
783            } else {
784                row.push(cell!(String::from("")));
785            }
786        }
787
788        tcp_frontend_table.add_row(Row::new(row));
789    }
790
791    tcp_frontend_table.printstd();
792
793    println!("\nbackends configuration:\n");
794
795    for (key, values) in backends.iter() {
796        let mut row = vec![
797            cell!(key.backend_id),
798            cell!(format!("{}", key.address)),
799            cell!(
800                key.backup
801                    .map(|b| if b { "X" } else { "" })
802                    .unwrap_or_else(|| "")
803            ),
804        ];
805
806        for val in values {
807            if worker_ids.contains(&val) {
808                row.push(cell!("X"));
809            } else {
810                row.push(cell!(""));
811            }
812        }
813
814        backend_table.add_row(Row::new(row));
815    }
816
817    backend_table.printstd();
818
819    Ok(())
820}
821
822fn print_cluster_hashes(worker_responses: &WorkerResponses) -> Result<(), DisplayError> {
824    let mut clusters_table = Table::new();
825    clusters_table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
826    let mut header = vec![cell!("cluster id")];
827    for worker_id in worker_responses.map.keys() {
828        header.push(cell!(format!("worker {}", worker_id)));
829    }
830    header.push(cell!("desynchronized"));
831    clusters_table.add_row(Row::new(header));
832
833    let mut cluster_hashes = HashMap::new();
834
835    for response_content in worker_responses.map.values() {
836        if let Some(ContentType::ClusterHashes(hashes)) = &response_content.content_type {
837            for (cluster_id, hash) in hashes.map.iter() {
838                cluster_hashes
839                    .entry(cluster_id)
840                    .or_insert(Vec::new())
841                    .push(hash);
842            }
843        }
844    }
845
846    for (cluster_id, hashes) in cluster_hashes.iter() {
847        let mut row = vec![cell!(cluster_id)];
848        for val in hashes.iter() {
849            row.push(cell!(format!("{val}")));
850        }
851
852        let hs: HashSet<&u64> = hashes.iter().cloned().collect();
853        if hs.len() > 1 {
854            row.push(cell!("X"));
855        } else {
856            row.push(cell!(""));
857        }
858
859        clusters_table.add_row(Row::new(row));
860    }
861
862    clusters_table.printstd();
863    Ok(())
864}
865
866fn print_responses_by_worker(
867    worker_responses: &WorkerResponses,
868    json: bool,
869) -> Result<(), DisplayError> {
870    for (worker_id, content) in worker_responses.map.iter() {
871        println!("Worker {}", worker_id);
872        content.display(json)?;
873    }
874
875    Ok(())
876}
877
878pub fn print_certificates_with_validity(
879    certs: &CertificatesWithFingerprints,
880) -> Result<(), DisplayError> {
881    if certs.certs.is_empty() {
882        println!("No certificates match your request.");
883        return Ok(());
884    }
885
886    let mut table = Table::new();
887    table.set_format(*prettytable::format::consts::FORMAT_CLEAN);
888    table.add_row(row![
889        "fingeprint",
890        "valid not before",
891        "valide not after",
892        "domain names",
893    ]);
894
895    for (fingerprint, cert) in &certs.certs {
896        let (_unparsed, pem_certificate) =
897            x509_parser::pem::parse_x509_pem(cert.certificate.as_bytes())
898                .expect("Could not parse pem certificate");
899
900        let x509_certificate = pem_certificate
901            .parse_x509()
902            .expect("Could not parse x509 certificate");
903
904        let validity = x509_certificate.validity();
905
906        table.add_row(row!(
907            fingerprint,
908            format_datetime(validity.not_before)?,
909            format_datetime(validity.not_after)?,
910            concatenate_vector(&cert.names),
911        ));
912    }
913    table.printstd();
914
915    Ok(())
916}
917
918fn print_certificates_by_address(list: &ListOfCertificatesByAddress) -> Result<(), DisplayError> {
919    for certs in list.certificates.iter() {
920        println!("\t{}:", certs.address);
921
922        for summary in certs.certificate_summaries.iter() {
923            println!("\t\t{}", summary);
924        }
925    }
926    Ok(())
927}
928
929fn print_request_counts(request_counts: &RequestCounts) -> Result<(), DisplayError> {
930    let mut table = Table::new();
931    table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
932    table.add_row(row!["request type", "count"]);
933
934    for (request_type, count) in &request_counts.map {
935        table.add_row(row!(request_type, count));
936    }
937    table.printstd();
938    Ok(())
939}
940
941fn format_tags_to_string(tags: &BTreeMap<String, String>) -> String {
942    tags.iter()
943        .map(|(k, v)| format!("{k}={v}"))
944        .collect::<Vec<_>>()
945        .join(", ")
946}
947
948fn list_string_vec(vec: &[String]) -> String {
949    let mut output = String::new();
950    for item in vec.iter() {
951        output.push_str(item);
952        output.push('\n');
953    }
954    output
955}
956
957fn format_datetime(asn1_time: ASN1Time) -> Result<String, DisplayError> {
959    let datetime = asn1_time.to_datetime();
960
961    let formatted = datetime
962        .format(&format_description::well_known::Iso8601::DEFAULT)
963        .map_err(|_| DisplayError::DateTime)?;
964    Ok(formatted)
965}
966
967fn create_cluster_table(headers: Vec<&str>, data: &BTreeMap<String, ResponseContent>) -> Table {
980    let mut table = Table::new();
981    table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
982    let mut row_header: Vec<_> = headers.iter().map(|h| cell!(h)).collect();
983    for ref key in data.keys() {
984        row_header.push(cell!(&key));
985    }
986    table.add_row(Row::new(row_header));
987    table
988}
989
990impl Display for SocketAddress {
991    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
992        write!(f, "{}", SocketAddr::from(*self))
993    }
994}
995
996impl Display for ProtobufEndpoint {
997    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
998        match &self.inner {
999            Some(protobuf_endpoint::Inner::Http(HttpEndpoint {
1000                method,
1001                authority,
1002                path,
1003                status,
1004                ..
1005            })) => write!(
1006                f,
1007                "{} {} {} -> {}",
1008                authority.as_string_or("-"),
1009                method.as_string_or("-"),
1010                path.as_string_or("-"),
1011                status.as_string_or("-"),
1012            ),
1013            Some(protobuf_endpoint::Inner::Tcp(_)) => {
1014                write!(f, "-")
1015            }
1016            None => Ok(()),
1017        }
1018    }
1019}
1020
1021impl Display for HttpListenerConfig {
1022    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1023        let mut table = Table::new();
1024        table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
1025        table.add_row(row!["socket address", format!("{:?}", self.address)]);
1026        table.add_row(row!["public address", format!("{:?}", self.public_address),]);
1027        for http_answer_row in CustomHttpAnswers::to_rows(&self.http_answers) {
1028            table.add_row(http_answer_row);
1029        }
1030        table.add_row(row!["expect proxy", self.expect_proxy]);
1031        table.add_row(row!["sticky name", self.sticky_name]);
1032        table.add_row(row!["front timeout", self.front_timeout]);
1033        table.add_row(row!["back timeout", self.back_timeout]);
1034        table.add_row(row!["connect timeout", self.connect_timeout]);
1035        table.add_row(row!["request timeout", self.request_timeout]);
1036        table.add_row(row!["activated", self.active]);
1037        write!(f, "{}", table)
1038    }
1039}
1040
1041impl Display for HttpsListenerConfig {
1042    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1043        let mut table = Table::new();
1044        table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
1045        let mut tls_versions = String::new();
1046        for tls_version in self.versions.iter() {
1047            tls_versions.push_str(&format!("{tls_version:?}\n"));
1048        }
1049
1050        table.add_row(row!["socket address", format!("{:?}", self.address)]);
1051        table.add_row(row!["public address", format!("{:?}", self.public_address)]);
1052        for http_answer_row in CustomHttpAnswers::to_rows(&self.http_answers) {
1053            table.add_row(http_answer_row);
1054        }
1055        table.add_row(row!["versions", tls_versions]);
1056        table.add_row(row!["cipher list", list_string_vec(&self.cipher_list),]);
1057        table.add_row(row!["cipher suites", list_string_vec(&self.cipher_suites),]);
1058        table.add_row(row![
1059            "signature algorithms",
1060            list_string_vec(&self.signature_algorithms),
1061        ]);
1062        table.add_row(row!["groups list", list_string_vec(&self.groups_list),]);
1063        table.add_row(row!["key", format!("{:?}", self.key),]);
1064        table.add_row(row!["expect proxy", self.expect_proxy]);
1065        table.add_row(row!["sticky name", self.sticky_name]);
1066        table.add_row(row!["front timeout", self.front_timeout]);
1067        table.add_row(row!["back timeout", self.back_timeout]);
1068        table.add_row(row!["connect timeout", self.connect_timeout]);
1069        table.add_row(row!["request timeout", self.request_timeout]);
1070        table.add_row(row!["activated", self.active]);
1071        write!(f, "{}", table)
1072    }
1073}
1074
1075impl CustomHttpAnswers {
1076    fn to_rows(option: &Option<Self>) -> Vec<Row> {
1077        let mut rows = Vec::new();
1078        if let Some(answers) = option {
1079            if let Some(a) = &answers.answer_301 {
1080                rows.push(row!("301", a));
1081            }
1082            if let Some(a) = &answers.answer_400 {
1083                rows.push(row!("400", a));
1084            }
1085            if let Some(a) = &answers.answer_404 {
1086                rows.push(row!("404", a));
1087            }
1088            if let Some(a) = &answers.answer_408 {
1089                rows.push(row!("408", a));
1090            }
1091            if let Some(a) = &answers.answer_413 {
1092                rows.push(row!("413", a));
1093            }
1094            if let Some(a) = &answers.answer_502 {
1095                rows.push(row!("502", a));
1096            }
1097            if let Some(a) = &answers.answer_503 {
1098                rows.push(row!("503", a));
1099            }
1100            if let Some(a) = &answers.answer_504 {
1101                rows.push(row!("504", a));
1102            }
1103            if let Some(a) = &answers.answer_507 {
1104                rows.push(row!("507", a));
1105            }
1106        }
1107        rows
1108    }
1109}
1110
1111impl Display for Event {
1112    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1113        let kind = match self.kind() {
1114            EventKind::BackendDown => "backend down",
1115            EventKind::BackendUp => "backend up",
1116            EventKind::NoAvailableBackends => "no available backends",
1117            EventKind::RemovedBackendHasNoConnections => "removed backend has no connections",
1118        };
1119        let address = match &self.address {
1120            Some(a) => a.to_string(),
1121            None => String::new(),
1122        };
1123        write!(
1124            f,
1125            "{}, backend={}, cluster={}, address={}",
1126            kind,
1127            self.backend_id(),
1128            self.cluster_id(),
1129            address,
1130        )
1131    }
1132}