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