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