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}