2024-02-01 15:25:51 -05:00
|
|
|
// Copyright 2021 Jeremy Wall
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
2024-02-03 16:31:41 -06:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
2024-02-04 16:39:25 -06:00
|
|
|
use axum::{
|
|
|
|
extract::{Path, State},
|
2024-02-07 14:37:24 -06:00
|
|
|
response::Response,
|
2024-02-04 16:39:25 -06:00
|
|
|
routing::get,
|
|
|
|
Json, Router,
|
|
|
|
};
|
2024-02-07 14:37:24 -06:00
|
|
|
use axum_macros::debug_handler;
|
2024-02-06 16:03:04 -06:00
|
|
|
use maud::{html, Markup, PreEscaped};
|
2024-02-04 16:39:25 -06:00
|
|
|
use tracing::debug;
|
2024-02-03 16:31:41 -06:00
|
|
|
|
2024-02-07 13:37:43 -06:00
|
|
|
use crate::dashboard::{Dashboard, Graph};
|
2024-02-04 16:39:25 -06:00
|
|
|
use crate::query::{to_samples, QueryResult};
|
2024-02-03 16:31:41 -06:00
|
|
|
|
|
|
|
type Config = State<Arc<Vec<Dashboard>>>;
|
2024-02-01 15:25:51 -05:00
|
|
|
|
2024-02-04 16:39:25 -06:00
|
|
|
//#[axum_macros::debug_handler]
|
|
|
|
pub async fn graph_query(
|
|
|
|
State(config): Config,
|
|
|
|
Path((dash_idx, graph_idx)): Path<(usize, usize)>,
|
|
|
|
) -> Json<QueryResult> {
|
|
|
|
debug!("Getting data for query");
|
|
|
|
let graph = config
|
|
|
|
.get(dash_idx)
|
|
|
|
.expect("No such dashboard index")
|
|
|
|
.graphs
|
|
|
|
.get(graph_idx)
|
|
|
|
.expect(&format!("No such graph in dasboard {}", dash_idx));
|
|
|
|
let data = to_samples(
|
|
|
|
graph
|
|
|
|
.get_query_connection()
|
|
|
|
.get_results()
|
|
|
|
.await
|
|
|
|
.expect("Unable to get query results")
|
|
|
|
.data()
|
|
|
|
.clone(),
|
|
|
|
);
|
|
|
|
Json(data)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn mk_api_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
|
2024-02-03 16:31:41 -06:00
|
|
|
// Query routes
|
2024-02-04 16:39:25 -06:00
|
|
|
Router::new().route(
|
|
|
|
"/dash/:dash_idx/graph/:graph_idx",
|
|
|
|
get(graph_query).with_state(config),
|
|
|
|
)
|
2024-02-01 15:25:51 -05:00
|
|
|
}
|
|
|
|
|
2024-02-07 13:59:44 -06:00
|
|
|
// 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);
|
2024-02-07 14:37:24 -06:00
|
|
|
// initialize the plot with Plotly.react
|
|
|
|
// Update plot with Plotly.react which is more efficient
|
|
|
|
let script = format!(
|
|
|
|
"var data = []; Plotly.react('{}', data, {{ width: 500, height: 500 }});",
|
|
|
|
graph_id
|
|
|
|
);
|
2024-02-07 13:37:43 -06:00
|
|
|
html!(
|
|
|
|
div {
|
|
|
|
h2 { (graph.title) }
|
2024-02-07 13:59:44 -06:00
|
|
|
script {
|
|
|
|
(script)
|
|
|
|
}
|
|
|
|
div id=(graph_id) { }
|
2024-02-07 13:37:43 -06:00
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
2024-02-07 13:59:44 -06:00
|
|
|
graph_component(dash_idx, graph_idx, graph)
|
2024-02-07 13:37:43 -06:00
|
|
|
}
|
|
|
|
|
2024-02-07 13:59:44 -06:00
|
|
|
pub async fn dash_ui(State(config): State<Config>, Path(dash_idx): Path<usize>) -> Markup {
|
2024-02-07 13:37:43 -06:00
|
|
|
// TODO(zaphar): Should do better http error reporting here.
|
2024-02-07 13:59:44 -06:00
|
|
|
let dash = config.get(dash_idx).expect("No such dashboard");
|
2024-02-07 14:37:24 -06:00
|
|
|
let graph_iter = dash
|
|
|
|
.graphs
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.collect::<Vec<(usize, &Graph)>>();
|
2024-02-07 07:10:12 -06:00
|
|
|
html!(
|
2024-02-07 13:37:43 -06:00
|
|
|
h1 { (dash.title) }
|
2024-02-07 13:59:44 -06:00
|
|
|
@for (idx, graph) in &graph_iter {
|
|
|
|
(graph_component(dash_idx, *idx, *graph))
|
2024-02-07 13:37:43 -06:00
|
|
|
}
|
2024-02-07 07:10:12 -06:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-02-04 16:39:25 -06:00
|
|
|
pub fn mk_ui_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
|
2024-02-07 13:37:43 -06:00
|
|
|
Router::new()
|
2024-02-07 14:37:24 -06:00
|
|
|
.route(
|
|
|
|
"/dash/:dash_idx",
|
|
|
|
get(dash_ui).with_state(State(config.clone())),
|
|
|
|
)
|
2024-02-07 13:37:43 -06:00
|
|
|
.route(
|
|
|
|
"/dash/:dash_idx/graph/:graph_idx",
|
|
|
|
get(graph_ui).with_state(State(config)),
|
|
|
|
)
|
2024-02-01 15:25:51 -05:00
|
|
|
}
|
|
|
|
|
2024-02-07 07:10:12 -06:00
|
|
|
pub async fn index(State(config): State<Config>) -> Markup {
|
2024-02-01 15:25:51 -05:00
|
|
|
html! {
|
|
|
|
html {
|
|
|
|
head {
|
|
|
|
title { ("Heracles - Prometheus Unshackled") }
|
|
|
|
}
|
|
|
|
body {
|
2024-02-07 14:37:24 -06:00
|
|
|
script src="/js/plotly.js" { }
|
|
|
|
script src="/js/htmx.js" { }
|
2024-02-03 16:31:41 -06:00
|
|
|
(app(State(config.clone())).await)
|
2024-02-01 15:25:51 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-02-03 16:31:41 -06:00
|
|
|
|
2024-02-07 07:10:12 -06:00
|
|
|
pub async fn app(State(config): State<Config>) -> Markup {
|
2024-02-04 16:39:25 -06:00
|
|
|
let titles = config
|
|
|
|
.iter()
|
|
|
|
.map(|d| d.title.clone())
|
2024-02-06 16:27:52 -06:00
|
|
|
.enumerate()
|
|
|
|
.collect::<Vec<(usize, String)>>();
|
2024-02-03 16:31:41 -06:00
|
|
|
html! {
|
|
|
|
div {
|
|
|
|
// Header menu
|
2024-02-03 19:06:38 -06:00
|
|
|
ul {
|
|
|
|
@for title in &titles {
|
2024-02-07 07:10:12 -06:00
|
|
|
li hx-get=(format!("/ui/dash/{}", title.0)) hx-target="#dashboard" { (title.1) }
|
2024-02-03 19:06:38 -06:00
|
|
|
}
|
|
|
|
}
|
2024-02-03 16:31:41 -06:00
|
|
|
// dashboard display
|
2024-02-06 16:27:52 -06:00
|
|
|
div id="dashboard" { }
|
2024-02-03 16:31:41 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-02-07 14:37:24 -06:00
|
|
|
|
|
|
|
pub fn javascript_response(content: &str) -> Response<String> {
|
|
|
|
Response::builder()
|
|
|
|
.header("Content-Type", "text/javascript")
|
|
|
|
.body(content.to_string())
|
|
|
|
.expect("Invalid javascript response")
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn htmx() -> Response<String> {
|
|
|
|
javascript_response(include_str!("../static/htmx.min.js"))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn plotly() -> Response<String> {
|
|
|
|
javascript_response(include_str!("../static/plotly-2.27.0.min.js"))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn mk_js_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
|
|
|
|
Router::new()
|
|
|
|
.route("/plotly.js", get(plotly))
|
|
|
|
.route("/htmx.js", get(htmx))
|
|
|
|
.with_state(State(config))
|
|
|
|
}
|