Compare commits

...

6 Commits

4 changed files with 67 additions and 10 deletions

View File

@ -66,7 +66,7 @@ async fn main() -> anyhow::Result<()> {
.nest("/api", routes::mk_api_routes(config.clone()))
// HTMX ui component endpoints
.nest("/ui", routes::mk_ui_routes(config.clone()))
.route("/", get(routes::index).with_state(config.clone()))
.route("/", get(routes::index).with_state(State(config.clone())))
.layer(TraceLayer::new_for_http())
.with_state(State(config.clone()));
let socket_addr = args.listen.unwrap_or("127.0.0.1:3000".to_string());

View File

@ -18,10 +18,10 @@ use axum::{
routing::get,
Json, Router,
};
use maud::{html, Markup};
use maud::{html, Markup, PreEscaped};
use tracing::debug;
use crate::dashboard::Dashboard;
use crate::dashboard::{Dashboard, Graph};
use crate::query::{to_samples, QueryResult};
type Config = State<Arc<Vec<Dashboard>>>;
@ -58,38 +58,86 @@ pub fn mk_api_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
)
}
pub fn mk_ui_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
Router::new()
// TODO(jwall): This should probably be encapsulated in a web component?
pub fn graph_component(dash_idx: usize, graph_idx: usize, graph: &Graph) -> Markup {
let graph_id = format!("graph-{}-{}", dash_idx, graph_idx);
let script = format!("var data = []; Plotly.newPlot('{}', data, {{ width: 500, height: 500 }});", graph_id);
html!(
div {
h2 { (graph.title) }
script {
(script)
}
div id=(graph_id) { }
}
)
}
pub async fn index(State(config): Config) -> Markup {
pub async fn graph_ui(
State(config): State<Config>,
Path((dash_idx, graph_idx)): Path<(usize, usize)>,
) -> Markup {
let graph = config
.get(dash_idx)
.expect("No such dashboard")
.graphs
.get(graph_idx)
.expect("No such graph");
graph_component(dash_idx, graph_idx, graph)
}
pub async fn dash_ui(State(config): State<Config>, Path(dash_idx): Path<usize>) -> Markup {
// TODO(zaphar): Should do better http error reporting here.
let dash = config.get(dash_idx).expect("No such dashboard");
let graph_iter = dash.graphs.iter().enumerate().collect::<Vec<(usize, &Graph)>>();
html!(
h1 { (dash.title) }
@for (idx, graph) in &graph_iter {
(graph_component(dash_idx, *idx, *graph))
}
)
}
pub fn mk_ui_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
Router::new()
.route("/dash/:dash_idx", get(dash_ui).with_state(State(config.clone())))
.route(
"/dash/:dash_idx/graph/:graph_idx",
get(graph_ui).with_state(State(config)),
)
}
pub async fn index(State(config): State<Config>) -> Markup {
html! {
html {
head {
title { ("Heracles - Prometheus Unshackled") }
}
body {
script { (PreEscaped(include_str!("../static/plotly-2.27.0.min.js"))) }
script { (PreEscaped(include_str!("../static/htmx.min.js"))) }
(app(State(config.clone())).await)
}
}
}
}
pub async fn app(State(config): Config) -> Markup {
pub async fn app(State(config): State<Config>) -> Markup {
let titles = config
.iter()
.map(|d| d.title.clone())
.collect::<Vec<String>>();
.enumerate()
.collect::<Vec<(usize, String)>>();
html! {
div {
// Header menu
ul {
@for title in &titles {
li { (title) }
li hx-get=(format!("/ui/dash/{}", title.0)) hx-target="#dashboard" { (title.1) }
}
}
// dashboard display
div { }
div id="dashboard" { }
}
}
}

1
static/htmx.min.js vendored Normal file

File diff suppressed because one or more lines are too long

8
static/plotly-2.27.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long