mirror of
https://github.com/zaphar/Heracles.git
synced 2025-07-29 07:19:58 -04:00
Compare commits
No commits in common. "584cb237fe5f25ce737878b36a6e4b4751147606" and "a010112f39f81cf2628a40e3b32d949db21eeece" have entirely different histories.
584cb237fe
...
a010112f39
@ -64,7 +64,6 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let router = Router::new()
|
let router = Router::new()
|
||||||
// JSON api endpoints
|
// JSON api endpoints
|
||||||
.nest("/js", routes::mk_js_routes(config.clone()))
|
.nest("/js", routes::mk_js_routes(config.clone()))
|
||||||
.nest("/static", routes::mk_static_routes(config.clone()))
|
|
||||||
.nest("/api", routes::mk_api_routes(config.clone()))
|
.nest("/api", routes::mk_api_routes(config.clone()))
|
||||||
// HTMX ui component endpoints
|
// HTMX ui component endpoints
|
||||||
.nest("/ui", routes::mk_ui_routes(config.clone()))
|
.nest("/ui", routes::mk_ui_routes(config.clone()))
|
||||||
|
@ -21,20 +21,18 @@ use prometheus_http_query::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone)]
|
||||||
pub enum QueryType {
|
pub enum QueryType {
|
||||||
Range,
|
Range,
|
||||||
Scalar,
|
Scalar,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TimeSpan {
|
pub struct TimeSpan {
|
||||||
pub end: DateTime<Utc>,
|
pub end: DateTime<Utc>,
|
||||||
pub duration: chrono::Duration,
|
pub duration: chrono::Duration,
|
||||||
pub step_seconds: i64,
|
pub step_seconds: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct QueryConn<'conn> {
|
pub struct QueryConn<'conn> {
|
||||||
source: &'conn str,
|
source: &'conn str,
|
||||||
query: &'conn str,
|
query: &'conn str,
|
||||||
|
@ -115,7 +115,7 @@ pub async fn dash_ui(State(config): State<Config>, Path(dash_idx): Path<usize>)
|
|||||||
.collect::<Vec<(usize, &Graph)>>();
|
.collect::<Vec<(usize, &Graph)>>();
|
||||||
html!(
|
html!(
|
||||||
h1 { (dash.title) }
|
h1 { (dash.title) }
|
||||||
span-selector class="row-flex" {}
|
span-selector {}
|
||||||
@for (idx, graph) in &graph_iter {
|
@for (idx, graph) in &graph_iter {
|
||||||
(graph_component(dash_idx, *idx, *graph))
|
(graph_component(dash_idx, *idx, *graph))
|
||||||
}
|
}
|
||||||
@ -144,7 +144,6 @@ pub async fn index(State(config): State<Config>) -> Markup {
|
|||||||
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" { }
|
script src="/js/lib.js" { }
|
||||||
link rel="stylesheet" href="/static/site.css" { }
|
|
||||||
(app(State(config.clone())).await)
|
(app(State(config.clone())).await)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,16 +157,15 @@ pub async fn app(State(config): State<Config>) -> Markup {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.collect::<Vec<(usize, String)>>();
|
.collect::<Vec<(usize, String)>>();
|
||||||
html! {
|
html! {
|
||||||
div class="row-flex" {
|
div {
|
||||||
div class="flex-item-shrink" {
|
// Header menu
|
||||||
// Header menu
|
ul {
|
||||||
ul {
|
@for title in &titles {
|
||||||
@for title in &titles {
|
li hx-get=(format!("/ui/dash/{}", title.0)) hx-target="#dashboard" { (title.1) }
|
||||||
li hx-get=(format!("/ui/dash/{}", title.0)) hx-target="#dashboard" { (title.1) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div class="flex-item-grow" id="dashboard" { }
|
// dashboard display
|
||||||
|
div id="dashboard" { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,10 +197,3 @@ pub fn mk_js_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
|
|||||||
.route("/htmx.js", get(htmx))
|
.route("/htmx.js", get(htmx))
|
||||||
.with_state(State(config))
|
.with_state(State(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mk_static_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
|
|
||||||
Router::new()
|
|
||||||
.route("/site.css", get(|| async { return include_str!("../static/site.css"); }))
|
|
||||||
.with_state(State(config))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -12,10 +12,6 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
function getCssVariableValue(variableName) {
|
|
||||||
return getComputedStyle(document.documentElement).getPropertyValue(variableName);
|
|
||||||
}
|
|
||||||
|
|
||||||
class TimeseriesGraph extends HTMLElement {
|
class TimeseriesGraph extends HTMLElement {
|
||||||
#uri;
|
#uri;
|
||||||
#width;
|
#width;
|
||||||
@ -37,13 +33,10 @@ class TimeseriesGraph extends HTMLElement {
|
|||||||
this.#height = 600;
|
this.#height = 600;
|
||||||
this.#pollSeconds = 30;
|
this.#pollSeconds = 30;
|
||||||
this.#menuContainer = this.appendChild(document.createElement('div'));
|
this.#menuContainer = this.appendChild(document.createElement('div'));
|
||||||
// TODO(jwall): These should probably be done as template clones so we have less places
|
|
||||||
// to look for class attributes.
|
|
||||||
this.#menuContainer.setAttribute("class", "row-flex");
|
|
||||||
this.#targetNode = this.appendChild(document.createElement("div"));
|
this.#targetNode = this.appendChild(document.createElement("div"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static observedAttributes = ['uri', 'width', 'height', 'poll-seconds', 'end', 'duration', 'step-duration', 'd3-tick-format'];
|
static observedAttributes = ['uri', 'width', 'height', 'poll-seconds', 'end', 'duration', 'step-duration'];
|
||||||
|
|
||||||
attributeChangedCallback(name, _oldValue, newValue) {
|
attributeChangedCallback(name, _oldValue, newValue) {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
@ -172,9 +165,12 @@ class TimeseriesGraph extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildSelectElement(key) {
|
buildSelectElement(key) {
|
||||||
// TODO(jwall): Should we have a select all?
|
|
||||||
var id = key + "-select" + Math.random();
|
var id = key + "-select" + Math.random();
|
||||||
const element = document.createElement("div");
|
const element = document.createElement("div");
|
||||||
|
const label = document.createElement("label");
|
||||||
|
label.innerText = key + ": ";
|
||||||
|
label.setAttribute("for", id);
|
||||||
|
element.appendChild(label);
|
||||||
const select = document.createElement("select");
|
const select = document.createElement("select");
|
||||||
select.setAttribute("name", id);
|
select.setAttribute("name", id);
|
||||||
select.setAttribute("multiple", true);
|
select.setAttribute("multiple", true);
|
||||||
@ -185,7 +181,6 @@ class TimeseriesGraph extends HTMLElement {
|
|||||||
for (var opt of this.#filterLabels[key]) {
|
for (var opt of this.#filterLabels[key]) {
|
||||||
const optElement = document.createElement("option");
|
const optElement = document.createElement("option");
|
||||||
optElement.setAttribute("value", opt);
|
optElement.setAttribute("value", opt);
|
||||||
optElement.setAttribute("selected", true);
|
|
||||||
optElement.innerText = opt;
|
optElement.innerText = opt;
|
||||||
select.appendChild(optElement);
|
select.appendChild(optElement);
|
||||||
}
|
}
|
||||||
@ -222,12 +217,6 @@ class TimeseriesGraph extends HTMLElement {
|
|||||||
this.populateFilterData(labels);
|
this.populateFilterData(labels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (subplot.Scalar) {
|
|
||||||
for (const triple of subplot.Scalar) {
|
|
||||||
const labels = triple[0];
|
|
||||||
this.populateFilterData(labels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,14 +233,6 @@ class TimeseriesGraph extends HTMLElement {
|
|||||||
var layout = {
|
var layout = {
|
||||||
displayModeBar: false,
|
displayModeBar: false,
|
||||||
responsive: true,
|
responsive: true,
|
||||||
plot_bgcolor: getCssVariableValue('--paper-background-color').trim(),
|
|
||||||
paper_bgcolor: getCssVariableValue('--paper-background-color').trim(),
|
|
||||||
font: {
|
|
||||||
color: getCssVariableValue('--text-color').trim()
|
|
||||||
},
|
|
||||||
xaxis: {
|
|
||||||
gridcolor: getCssVariableValue("--accent-color")
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
var traces = [];
|
var traces = [];
|
||||||
for (var subplot_idx in data) {
|
for (var subplot_idx in data) {
|
||||||
@ -273,7 +254,6 @@ class TimeseriesGraph extends HTMLElement {
|
|||||||
// https://plotly.com/javascript/reference/layout/yaxis/
|
// https://plotly.com/javascript/reference/layout/yaxis/
|
||||||
layout["yaxis" + subplotCount] = {
|
layout["yaxis" + subplotCount] = {
|
||||||
anchor: yaxis,
|
anchor: yaxis,
|
||||||
gridcolor: getCssVariableValue("--accent-color"),
|
|
||||||
tickformat: meta["d3_tick_format"] || this.#d3TickFormat
|
tickformat: meta["d3_tick_format"] || this.#d3TickFormat
|
||||||
};
|
};
|
||||||
const series = triple[2];
|
const series = triple[2];
|
||||||
@ -297,18 +277,8 @@ class TimeseriesGraph extends HTMLElement {
|
|||||||
}
|
}
|
||||||
} else if (subplot.Scalar) {
|
} else if (subplot.Scalar) {
|
||||||
// https://plotly.com/javascript/reference/bar/
|
// https://plotly.com/javascript/reference/bar/
|
||||||
layout["yaxis"] = {
|
for (const triple of subplot.Scalar) {
|
||||||
tickformat: this.#d3TickFormat,
|
|
||||||
gridcolor: getCssVariableValue("--accent-color")
|
|
||||||
};
|
|
||||||
loopScalar: for (const triple of subplot.Scalar) {
|
|
||||||
const labels = triple[0];
|
const labels = triple[0];
|
||||||
for (var label in labels) {
|
|
||||||
var show = this.#filteredLabelSets[label];
|
|
||||||
if (show && !show.includes(labels[label])) {
|
|
||||||
continue loopScalar;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const meta = triple[1];
|
const meta = triple[1];
|
||||||
const series = triple[2];
|
const series = triple[2];
|
||||||
var trace = {
|
var trace = {
|
||||||
@ -358,7 +328,6 @@ class SpanSelector extends HTMLElement {
|
|||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
const self = this;
|
const self = this;
|
||||||
// TODO(jwall): We should probably show a loading indicator of some kind.
|
|
||||||
self.#updateInput.onclick = function(_evt) {
|
self.#updateInput.onclick = function(_evt) {
|
||||||
self.updateGraphs()
|
self.updateGraphs()
|
||||||
};
|
};
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
:root {
|
|
||||||
/* Base colors */
|
|
||||||
--background-color: #FFFFFF; /* Light background */
|
|
||||||
--text-color: #333333; /* Dark text for contrast */
|
|
||||||
--paper-background-color: #F0F0F0;
|
|
||||||
--accent-color: #6200EE; /* For buttons and interactive elements */
|
|
||||||
|
|
||||||
/* Graph colors */
|
|
||||||
--graph-color-1: #007BFF; /* Blue */
|
|
||||||
--graph-color-2: #28A745; /* Green */
|
|
||||||
--graph-color-3: #DC3545; /* Red */
|
|
||||||
--graph-color-4: #FFC107; /* Yellow */
|
|
||||||
--graph-color-5: #17A2B8; /* Cyan */
|
|
||||||
--graph-color-6: #6C757D; /* Gray */
|
|
||||||
|
|
||||||
/* Axis and grid lines */
|
|
||||||
--axis-color: #CCCCCC;
|
|
||||||
--grid-line-color: #EEEEEE;
|
|
||||||
|
|
||||||
/* Tooltip background */
|
|
||||||
--tooltip-background-color: #FFFFFF;
|
|
||||||
--tooltip-text-color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
/* Solarized Dark Base Colors */
|
|
||||||
--background-color: #002b36; /* base03 */
|
|
||||||
--paper-background-color: #003c4a;
|
|
||||||
--text-color: #839496; /* base0 */
|
|
||||||
--accent-color: #268bd2; /* blue */
|
|
||||||
|
|
||||||
/* Graph colors - Solarized Accent Colors */
|
|
||||||
--graph-color-1: #b58900; /* yellow */
|
|
||||||
--graph-color-2: #cb4b16; /* orange */
|
|
||||||
--graph-color-3: #dc322f; /* red */
|
|
||||||
--graph-color-4: #d33682; /* magenta */
|
|
||||||
--graph-color-5: #6c71c4; /* violet */
|
|
||||||
--graph-color-6: #2aa198; /* cyan */
|
|
||||||
|
|
||||||
/* Axis and grid lines */
|
|
||||||
--axis-color: #586e75; /* base01 */
|
|
||||||
--grid-line-color: #073642; /* base02 */
|
|
||||||
|
|
||||||
/* Tooltip background */
|
|
||||||
--tooltip-background-color: #002b36; /* base03 */
|
|
||||||
--tooltip-text-color: #839496; /* base0 */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: var(--background-color);
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
input, textarea, select, option, button {
|
|
||||||
background-color: var(--paper-background-color);
|
|
||||||
border: 1px solid var(--accent-color);
|
|
||||||
color: var(--text-color);
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body * {
|
|
||||||
padding-left: .3em;
|
|
||||||
padding-right: .3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column-flex {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-flex {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-item-grow {
|
|
||||||
flex: 1 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-item-shrink {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
timeseries-graph {
|
|
||||||
background-color: var(--paper-background-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user