mirror of
https://github.com/zaphar/Heracles.git
synced 2025-07-23 04:29:48 -04:00
feat: Huzzah! the graph renders!
This commit is contained in:
parent
e0bbf15c6f
commit
08267f7727
@ -11,6 +11,7 @@ anyhow = "1.0.79"
|
|||||||
async-io = "2.3.1"
|
async-io = "2.3.1"
|
||||||
axum = { version = "0.7.4", features = [ "ws" ] }
|
axum = { version = "0.7.4", features = [ "ws" ] }
|
||||||
axum-macros = "0.4.1"
|
axum-macros = "0.4.1"
|
||||||
|
chrono = { version = "0.4.33", features = ["alloc", "std", "now"] }
|
||||||
clap = { version = "4.4.18", features = ["derive"] }
|
clap = { version = "4.4.18", features = ["derive"] }
|
||||||
maud = { version = "0.26.0", features = ["axum"] }
|
maud = { version = "0.26.0", features = ["axum"] }
|
||||||
prometheus-http-query = "0.8.2"
|
prometheus-http-query = "0.8.2"
|
||||||
|
14
src/query.rs
14
src/query.rs
@ -16,6 +16,7 @@ use std::collections::HashMap;
|
|||||||
use prometheus_http_query::{Client, response::{PromqlResult, Data}};
|
use prometheus_http_query::{Client, response::{PromqlResult, Data}};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
use chrono::prelude::*;
|
||||||
|
|
||||||
pub struct QueryConn<'conn> {
|
pub struct QueryConn<'conn> {
|
||||||
source: &'conn str,
|
source: &'conn str,
|
||||||
@ -33,13 +34,16 @@ impl<'conn> QueryConn<'conn> {
|
|||||||
pub async fn get_results(&self) -> anyhow::Result<PromqlResult> {
|
pub async fn get_results(&self) -> anyhow::Result<PromqlResult> {
|
||||||
debug!("Getting results for query");
|
debug!("Getting results for query");
|
||||||
let client = Client::try_from(self.source)?;
|
let client = Client::try_from(self.source)?;
|
||||||
Ok(client.query(self.query).get().await?)
|
let end = Utc::now().timestamp();
|
||||||
|
let start = end - (60 * 10);
|
||||||
|
let step_resolution = 10 as f64;
|
||||||
|
Ok(client.query_range(self.query, start, end, step_resolution).get().await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct DataPoint {
|
pub struct DataPoint {
|
||||||
timesstamp: f64,
|
timestamp: f64,
|
||||||
value: f64,
|
value: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,18 +60,18 @@ pub fn to_samples(data: Data) -> QueryResult {
|
|||||||
QueryResult::Series(range.drain(0..).map(|rv| {
|
QueryResult::Series(range.drain(0..).map(|rv| {
|
||||||
let (metric, mut samples) = rv.into_inner();
|
let (metric, mut samples) = rv.into_inner();
|
||||||
(metric, samples.drain(0..).map(|s| {
|
(metric, samples.drain(0..).map(|s| {
|
||||||
DataPoint { timesstamp: s.timestamp(), value: s.value() }
|
DataPoint { timestamp: s.timestamp(), value: s.value() }
|
||||||
}).collect())
|
}).collect())
|
||||||
}).collect())
|
}).collect())
|
||||||
}
|
}
|
||||||
Data::Vector(mut vector) => {
|
Data::Vector(mut vector) => {
|
||||||
QueryResult::Series(vector.drain(0..).map(|iv| {
|
QueryResult::Series(vector.drain(0..).map(|iv| {
|
||||||
let (metric, sample) = iv.into_inner();
|
let (metric, sample) = iv.into_inner();
|
||||||
(metric, vec![DataPoint { timesstamp: sample.timestamp(), value: sample.value() }])
|
(metric, vec![DataPoint { timestamp: sample.timestamp(), value: sample.value() }])
|
||||||
}).collect())
|
}).collect())
|
||||||
}
|
}
|
||||||
Data::Scalar(sample) => {
|
Data::Scalar(sample) => {
|
||||||
QueryResult::Scalar(DataPoint { timesstamp: sample.timestamp(), value: sample.value() })
|
QueryResult::Scalar(DataPoint { timestamp: sample.timestamp(), value: sample.value() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,11 +63,14 @@ pub fn mk_api_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
|
|||||||
// TODO(jwall): This should probably be encapsulated in a web component?
|
// TODO(jwall): This should probably be encapsulated in a web component?
|
||||||
pub fn graph_component(dash_idx: usize, graph_idx: usize, graph: &Graph) -> Markup {
|
pub fn graph_component(dash_idx: usize, graph_idx: usize, graph: &Graph) -> Markup {
|
||||||
let graph_id = format!("graph-{}-{}", dash_idx, graph_idx);
|
let graph_id = format!("graph-{}-{}", dash_idx, graph_idx);
|
||||||
|
let graph_data_uri = format!("/api/dash/{}/graph/{}", dash_idx, graph_idx);
|
||||||
// initialize the plot with Plotly.react
|
// initialize the plot with Plotly.react
|
||||||
// Update plot with Plotly.react which is more efficient
|
// Update plot with Plotly.react which is more efficient
|
||||||
let script = format!(
|
let script = format!(
|
||||||
"var data = []; Plotly.react('{}', data, {{ width: 500, height: 500 }});",
|
"var graph{graph_idx} = new Timeseries('{uri}', '{graph_id}'); graph{graph_idx}.updateGraph();",
|
||||||
graph_id
|
uri = graph_data_uri,
|
||||||
|
graph_id = graph_id,
|
||||||
|
graph_idx = graph_idx,
|
||||||
);
|
);
|
||||||
html!(
|
html!(
|
||||||
div {
|
div {
|
||||||
@ -130,6 +133,7 @@ pub async fn index(State(config): State<Config>) -> Markup {
|
|||||||
body {
|
body {
|
||||||
script src="/js/plotly.js" { }
|
script src="/js/plotly.js" { }
|
||||||
script src="/js/htmx.js" { }
|
script src="/js/htmx.js" { }
|
||||||
|
script src="/js/lib.js" { }
|
||||||
(app(State(config.clone())).await)
|
(app(State(config.clone())).await)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,6 +167,7 @@ pub fn javascript_response(content: &str) -> Response<String> {
|
|||||||
.expect("Invalid javascript response")
|
.expect("Invalid javascript response")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(jwall): Should probably hook in one of the axum directory serving crates here.
|
||||||
pub async fn htmx() -> Response<String> {
|
pub async fn htmx() -> Response<String> {
|
||||||
javascript_response(include_str!("../static/htmx.min.js"))
|
javascript_response(include_str!("../static/htmx.min.js"))
|
||||||
}
|
}
|
||||||
@ -171,9 +176,14 @@ pub async fn plotly() -> Response<String> {
|
|||||||
javascript_response(include_str!("../static/plotly-2.27.0.min.js"))
|
javascript_response(include_str!("../static/plotly-2.27.0.min.js"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn lib() -> Response<String> {
|
||||||
|
javascript_response(include_str!("../static/lib.js"))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn mk_js_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
|
pub fn mk_js_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/plotly.js", get(plotly))
|
.route("/plotly.js", get(plotly))
|
||||||
|
.route("/lib.js", get(lib))
|
||||||
.route("/htmx.js", get(htmx))
|
.route("/htmx.js", get(htmx))
|
||||||
.with_state(State(config))
|
.with_state(State(config))
|
||||||
}
|
}
|
||||||
|
47
static/lib.js
Normal file
47
static/lib.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
class Timeseries {
|
||||||
|
#uri;
|
||||||
|
#title;
|
||||||
|
#targetEl;
|
||||||
|
//#width;
|
||||||
|
//#height;
|
||||||
|
|
||||||
|
constructor(uri, targetEl, /** width, height **/) {
|
||||||
|
this.#uri = uri;
|
||||||
|
this.#targetEl = targetEl;
|
||||||
|
//this.#width = width;
|
||||||
|
//this.#height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchData() {
|
||||||
|
const response = await fetch(this.#uri);
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateGraph() {
|
||||||
|
const data = await this.fetchData();
|
||||||
|
if (data.Series) {
|
||||||
|
var traces = [];
|
||||||
|
for (const pair of data.Series) {
|
||||||
|
var trace = {
|
||||||
|
type: "scatter",
|
||||||
|
mode: "lines",
|
||||||
|
x: [],
|
||||||
|
y: []
|
||||||
|
};
|
||||||
|
//const labels = pair[0];
|
||||||
|
const series = pair[1];
|
||||||
|
for (const point of series) {
|
||||||
|
trace.x.push(point.timestamp);
|
||||||
|
trace.y.push(point.value);
|
||||||
|
}
|
||||||
|
traces.push(trace);
|
||||||
|
}
|
||||||
|
console.log("Traces: ", traces);
|
||||||
|
Plotly.react(this.#targetEl, traces, { width: 500, height: 500 });
|
||||||
|
} else if (data.Scalar) {
|
||||||
|
// The graph should be a single value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user