Skip to content

Commit

Permalink
public status page
Browse files Browse the repository at this point in the history
  • Loading branch information
aspect committed Sep 14, 2024
1 parent 8615f66 commit 010a0fb
Show file tree
Hide file tree
Showing 11 changed files with 565 additions and 173 deletions.
9 changes: 9 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub struct Args {
pub auto_update: bool,
/// Custom config file
pub user_config: Option<PathBuf>,
/// public status page
pub public: bool,
// Show node data on each election
// pub election: bool,
// Enable resolver status access via `/status`
Expand All @@ -39,6 +41,10 @@ pub struct Args {
}

impl Args {
pub fn public(&self) -> bool {
self.public
}

pub fn parse() -> Args {
#[allow(unused)]
use clap::{arg, command, Arg, Command};
Expand All @@ -49,6 +55,7 @@ impl Args {
))
.arg(arg!(--version "Display software version"))
.arg(arg!(--verbose "Enable verbose logging"))
.arg(arg!(--public "Enable public status page"))
.arg(arg!(--trace "Enable trace log level"))
.arg(arg!(--debug "Enable additional debug output"))
// .arg(arg!(--auto-update "Poll configuration updates"))
Expand Down Expand Up @@ -94,6 +101,7 @@ impl Args {

let matches = cmd.get_matches();

let public = matches.get_one::<bool>("public").cloned().unwrap_or(false);
let trace = matches.get_one::<bool>("trace").cloned().unwrap_or(false);
let verbose = matches.get_one::<bool>("verbose").cloned().unwrap_or(false);
let debug = matches.get_one::<bool>("debug").cloned().unwrap_or(false);
Expand Down Expand Up @@ -173,6 +181,7 @@ impl Args {
debug,
auto_update,
user_config,
public,
// election,
// status,
listen,
Expand Down
39 changes: 39 additions & 0 deletions src/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use axum::{
body::Body,
http::{header, HeaderValue},
response::{IntoResponse, Response},
};

#[derive(Clone, Copy, Debug)]
#[must_use]
pub struct NoCacheHtml<T>(pub T);

impl<T> IntoResponse for NoCacheHtml<T>
where
T: Into<Body>,
{
fn into_response(self) -> Response {
(
[
(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()),
),
(
header::CACHE_CONTROL,
HeaderValue::from_static(
"no-cache, no-store, must-revalidate, proxy-revalidate, max-age=0",
),
),
],
self.0.into(),
)
.into_response()
}
}

impl<T> From<T> for NoCacheHtml<T> {
fn from(inner: T) -> Self {
Self(inner)
}
}
85 changes: 0 additions & 85 deletions src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,88 +461,3 @@ impl<'a> From<&'a Arc<Connection>> for Output<'a> {
}
}
}

#[derive(Serialize)]
pub struct Status<'a> {
pub version: String,
#[serde(with = "SerHex::<Strict>")]
pub sid: u64,
#[serde(with = "SerHex::<Strict>")]
pub uid: u64,
pub url: &'a str,
pub fqdn: &'a str,
pub service: String,
// pub service: &'a str,
pub protocol: ProtocolKind,
pub encoding: EncodingKind,
pub encryption: TlsKind,
pub network: &'a NetworkId,
pub cores: u64,
pub memory: u64,
pub status: &'static str,
pub peers: u64,
pub clients: u64,
pub capacity: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub delegates: Option<Vec<String>>,
}

impl<'a> From<&'a Arc<Connection>> for Status<'a> {
fn from(connection: &'a Arc<Connection>) -> Self {
let delegate = connection.delegate();

let node = connection.node();
let uid = node.uid();
let url = node.address.as_str();
let fqdn = node.fqdn.as_str();
let service = node.service().to_string();
let protocol = node.params().protocol();
let encoding = node.params().encoding();
let encryption = node.params().tls();
let network = &node.network;
let status = connection.status();
let clients = delegate.clients();
let peers = delegate.peers();
let (version, sid, capacity, cores, memory) = delegate
.caps()
.as_ref()
.as_ref()
.map(|caps| {
(
caps.version.clone(),
caps.system_id,
caps.clients_limit,
caps.cpu_physical_cores,
caps.total_memory,
)
})
.unwrap_or_else(|| ("n/a".to_string(), 0, 0, 0, 0));

let delegates = connection
.resolve_delegators()
.iter()
.map(|connection| format!("[{:016x}] {}", connection.system_id(), connection.address()))
.collect::<Vec<String>>();
let delegates = (!delegates.is_empty()).then_some(delegates);

Self {
sid,
uid,
version,
fqdn,
service,
url,
protocol,
encoding,
encryption,
network,
cores,
memory,
status,
clients,
peers,
capacity,
delegates,
}
}
}
4 changes: 3 additions & 1 deletion src/imports.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub use crate::args::Args;
pub use crate::cache::NoCacheHtml;
pub use crate::config::*;
pub use crate::connection::{Connection, Output, Status};
pub use crate::connection::{Connection, Output};
pub use crate::delegate::*;
pub use crate::error::Error;
pub use crate::events::Events;
Expand All @@ -10,6 +11,7 @@ pub use crate::monitor::Monitor;
pub use crate::node::*;
pub use crate::params::PathParams;
pub use crate::path::*;
pub(crate) use crate::public;
pub use crate::resolver::Resolver;
pub use crate::result::Result;
pub(crate) use crate::rpc;
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod args;
mod cache;
mod config;
mod connection;
mod delegate;
Expand All @@ -12,6 +13,7 @@ mod node;
mod panic;
mod params;
mod path;
mod public;
mod resolver;
mod result;
mod rpc;
Expand Down
104 changes: 104 additions & 0 deletions src/public.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use crate::imports::*;
use askama::Template;

use axum::{
body::Body,
http::{header, HeaderValue, Request, StatusCode},
response::{IntoResponse, Response},
};

#[derive(Template)]
#[template(path = "public.html", escape = "none")]
struct PublicTemplate {}

pub async fn json_handler(resolver: &Arc<Resolver>, _req: Request<Body>) -> impl IntoResponse {
let connections = resolver.connections();
let connections = connections
.iter()
// .filter(|c| c.is_delegate())
.map(Public::from)
.collect::<Vec<_>>();
let nodes = serde_json::to_string(&connections).unwrap();
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "application/json")
.header(
header::CACHE_CONTROL,
HeaderValue::from_static(
"no-cache, no-store, must-revalidate, proxy-revalidate, max-age=0",
),
)
.body(Body::from(nodes))
.unwrap()
}

pub async fn status_handler(_resolver: &Arc<Resolver>, _req: Request<Body>) -> impl IntoResponse {
let index = PublicTemplate {};

Response::builder()
.status(StatusCode::OK)
.header(
header::CACHE_CONTROL,
HeaderValue::from_static(
"no-cache, no-store, must-revalidate, proxy-revalidate, max-age=0",
),
)
.body(Body::from(index.render().unwrap()))
.unwrap()
}

#[derive(Serialize)]
pub struct Public<'a> {
pub version: String,
#[serde(with = "SerHex::<Strict>")]
pub sid: u64,
#[serde(with = "SerHex::<Strict>")]
pub uid: u64,
pub service: String,
pub protocol: ProtocolKind,
pub encoding: EncodingKind,
pub encryption: TlsKind,
pub network: &'a NetworkId,
pub status: &'static str,
pub peers: u64,
pub clients: u64,
pub capacity: u64,
}

impl<'a> From<&'a Arc<Connection>> for Public<'a> {
fn from(connection: &'a Arc<Connection>) -> Self {
let delegate = connection.delegate();

let node = connection.node();
let uid = node.uid();
let service = node.service().to_string();
let protocol = node.params().protocol();
let encoding = node.params().encoding();
let encryption = node.params().tls();
let network = &node.network;
let status = connection.status();
let clients = delegate.clients();
let peers = delegate.peers();
let (version, sid, capacity) = delegate
.caps()
.as_ref()
.as_ref()
.map(|caps| (caps.version.clone(), caps.system_id, caps.clients_limit))
.unwrap_or_else(|| ("n/a".to_string(), 0, 0));

Self {
sid,
uid,
version,
service,
protocol,
encoding,
encryption,
network,
status,
clients,
peers,
capacity,
}
}
}
67 changes: 14 additions & 53 deletions src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ impl Resolver {
get(|req: Request<Body>| async move { status::json_handler(&this, req).await }),
);

if self.args().public() {
let this = self.clone();
router = router.route(
"/",
get(|req: Request<Body>| async move { public::status_handler(&this, req).await }),
);

let this = self.clone();
router = router.route(
"/json",
get(|req: Request<Body>| async move { public::json_handler(&this, req).await }),
);
}

if let Some(rate_limit) = self.args().rate_limit.as_ref() {
log_success!(
"Limits",
Expand Down Expand Up @@ -278,34 +292,6 @@ impl Resolver {
}
}

// respond with a JSON object containing the status of all nodes
#[allow(dead_code)]
fn get_status<F>(&self, monitor: Option<&Monitor>, filter: F) -> impl IntoResponse
where
F: Fn(&&Arc<Connection>) -> bool,
{
if let Some(monitor) = monitor {
let connections = monitor.to_vec();
let status = connections
.iter()
.filter(filter)
.map(Status::from)
.collect::<Vec<_>>();

with_json(status)
} else {
let kaspa = self.inner.kaspa.to_vec();
let sparkle = self.inner.sparkle.to_vec();
let status = kaspa
.iter()
.chain(sparkle.iter())
.filter(filter)
.map(Status::from)
.collect::<Vec<_>>();
with_json(status)
};
}

// // respond with a JSON object containing the status of all nodes
pub fn connections(&self) -> Vec<Arc<Connection>> {
let kaspa = self.inner.kaspa.to_vec();
Expand Down Expand Up @@ -374,31 +360,6 @@ fn with_json_string(json: String) -> Response<Body> {
.into_response()
}

#[inline]
fn with_json<T>(data: T) -> Response<Body>
where
T: Serialize,
{
(
StatusCode::OK,
[
(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
),
(
header::CACHE_CONTROL,
HeaderValue::from_static(
"no-cache, no-store, must-revalidate, proxy-revalidate, max-age=0",
),
),
(header::CONNECTION, HeaderValue::from_static("close")),
],
serde_json::to_string(&data).unwrap(),
)
.into_response()
}

#[inline]
#[allow(dead_code)]
fn with_mime(body: impl Into<String>, mime: &'static str) -> Response<Body> {
Expand Down
Loading

0 comments on commit 010a0fb

Please sign in to comment.