From 20c8eadbd1c078a7cc2f6086da2f54ec2591bf60 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Mon, 19 Feb 2024 22:51:03 -0500 Subject: [PATCH 01/11] ux: start out with all the labels selected --- static/lib.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/lib.js b/static/lib.js index 478f5b2..bd46ed9 100644 --- a/static/lib.js +++ b/static/lib.js @@ -181,6 +181,7 @@ class TimeseriesGraph extends HTMLElement { for (var opt of this.#filterLabels[key]) { const optElement = document.createElement("option"); optElement.setAttribute("value", opt); + optElement.setAttribute("selected", true); optElement.innerText = opt; select.appendChild(optElement); } From cf98532775af96eb9e65faf804eaf3f6f605bc27 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Tue, 20 Feb 2024 20:25:39 -0500 Subject: [PATCH 02/11] fix: Listen to changes to the d3-tick-format attr --- static/lib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/lib.js b/static/lib.js index bd46ed9..b97168f 100644 --- a/static/lib.js +++ b/static/lib.js @@ -36,7 +36,7 @@ class TimeseriesGraph extends HTMLElement { this.#targetNode = this.appendChild(document.createElement("div")); } - static observedAttributes = ['uri', 'width', 'height', 'poll-seconds', 'end', 'duration', 'step-duration']; + static observedAttributes = ['uri', 'width', 'height', 'poll-seconds', 'end', 'duration', 'step-duration', 'd3-tick-format']; attributeChangedCallback(name, _oldValue, newValue) { switch (name) { From f32a221022c475dd1a6757de56c91fba18f8e079 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Tue, 20 Feb 2024 20:49:14 -0500 Subject: [PATCH 03/11] ui: start out with flex column arrangments --- src/main.rs | 1 + src/routes.rs | 23 ++++++++++++++++------- static/site.css | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 static/site.css diff --git a/src/main.rs b/src/main.rs index eab586b..cdb66b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,6 +64,7 @@ async fn main() -> anyhow::Result<()> { let router = Router::new() // JSON api endpoints .nest("/js", routes::mk_js_routes(config.clone())) + .nest("/static", routes::mk_static_routes(config.clone())) .nest("/api", routes::mk_api_routes(config.clone())) // HTMX ui component endpoints .nest("/ui", routes::mk_ui_routes(config.clone())) diff --git a/src/routes.rs b/src/routes.rs index ae052bc..47dbb22 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -144,6 +144,7 @@ pub async fn index(State(config): State) -> Markup { script src="/js/plotly.js" { } script src="/js/htmx.js" { } script src="/js/lib.js" { } + link rel="stylesheet" href="/static/site.css" { } (app(State(config.clone())).await) } } @@ -157,15 +158,16 @@ pub async fn app(State(config): State) -> Markup { .enumerate() .collect::>(); html! { - div { - // Header menu - ul { - @for title in &titles { - li hx-get=(format!("/ui/dash/{}", title.0)) hx-target="#dashboard" { (title.1) } + div class="row-flex" { + div class="flex-item-shrink" { + // Header menu + ul { + @for title in &titles { + li hx-get=(format!("/ui/dash/{}", title.0)) hx-target="#dashboard" { (title.1) } + } } } - // dashboard display - div id="dashboard" { } + div class="flex-item-grow" id="dashboard" { } } } } @@ -197,3 +199,10 @@ pub fn mk_js_routes(config: Arc>) -> Router { .route("/htmx.js", get(htmx)) .with_state(State(config)) } + +pub fn mk_static_routes(config: Arc>) -> Router { + Router::new() + .route("/site.css", get(|| async { return include_str!("../static/site.css"); })) + .with_state(State(config)) +} + diff --git a/static/site.css b/static/site.css new file mode 100644 index 0000000..d8de113 --- /dev/null +++ b/static/site.css @@ -0,0 +1,18 @@ +.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; +} + From aa1f4f5795b53f5f28b503cbefe030fdf39abf36 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Tue, 20 Feb 2024 20:53:07 -0500 Subject: [PATCH 04/11] ui: reasonable default padding --- static/site.css | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/static/site.css b/static/site.css index d8de113..b0d8f82 100644 --- a/static/site.css +++ b/static/site.css @@ -1,18 +1,22 @@ +body * { + padding-left: .3em; + padding-right: .3em; +} + .column-flex { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .row-flex { - display: flex; - flex-direction: row; + display: flex; + flex-direction: row; } .flex-item-grow { - flex: 1 0 auto; + flex: 1 0 auto; } .flex-item-shrink { - flex: 0 1 auto; + flex: 0 1 auto; } - From 8eab0130f542c6382f04df8768ad3c9699ad1531 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Wed, 21 Feb 2024 15:48:15 -0500 Subject: [PATCH 05/11] ui: better layout for the filter selections --- src/routes.rs | 2 +- static/lib.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes.rs b/src/routes.rs index 47dbb22..0ca1186 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -115,7 +115,7 @@ pub async fn dash_ui(State(config): State, Path(dash_idx): Path) .collect::>(); html!( h1 { (dash.title) } - span-selector {} + span-selector class="row-flex" {} @for (idx, graph) in &graph_iter { (graph_component(dash_idx, *idx, *graph)) } diff --git a/static/lib.js b/static/lib.js index b97168f..5c9d4ec 100644 --- a/static/lib.js +++ b/static/lib.js @@ -33,6 +33,9 @@ class TimeseriesGraph extends HTMLElement { this.#height = 600; this.#pollSeconds = 30; 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")); } From 6bed94f63e29ad3993a68aa9418e4a1405e1f759 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Wed, 21 Feb 2024 16:22:02 -0500 Subject: [PATCH 06/11] ui: show the labels for scalar graphs as well --- static/lib.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/static/lib.js b/static/lib.js index 5c9d4ec..ce478ad 100644 --- a/static/lib.js +++ b/static/lib.js @@ -221,6 +221,12 @@ class TimeseriesGraph extends HTMLElement { this.populateFilterData(labels); } } + if (subplot.Scalar) { + for (const triple of subplot.Scalar) { + const labels = triple[0]; + this.populateFilterData(labels); + } + } } } @@ -281,6 +287,9 @@ class TimeseriesGraph extends HTMLElement { } } else if (subplot.Scalar) { // https://plotly.com/javascript/reference/bar/ + layout["yaxis"] = { + tickformat: this.#d3TickFormat + }; for (const triple of subplot.Scalar) { const labels = triple[0]; const meta = triple[1]; From 7b350a6612610fe7350999bffff4a58f2475ea20 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Wed, 21 Feb 2024 16:31:56 -0500 Subject: [PATCH 07/11] dev: Some debugging annotations. --- src/query.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/query.rs b/src/query.rs index ba246fc..7185a63 100644 --- a/src/query.rs +++ b/src/query.rs @@ -21,18 +21,20 @@ use prometheus_http_query::{ use serde::{Deserialize, Serialize}; use tracing::debug; -#[derive(Deserialize, Clone)] +#[derive(Deserialize, Clone, Debug)] pub enum QueryType { Range, Scalar, } +#[derive(Debug)] pub struct TimeSpan { pub end: DateTime, pub duration: chrono::Duration, pub step_seconds: i64, } +#[derive(Debug)] pub struct QueryConn<'conn> { source: &'conn str, query: &'conn str, From ef070f18d3cd2ec60eff5a9ad3921cdf65b5bb9f Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Wed, 21 Feb 2024 17:20:07 -0500 Subject: [PATCH 08/11] fix: actually do the filtering for scalar graphs --- static/lib.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/static/lib.js b/static/lib.js index ce478ad..969b16f 100644 --- a/static/lib.js +++ b/static/lib.js @@ -290,8 +290,14 @@ class TimeseriesGraph extends HTMLElement { layout["yaxis"] = { tickformat: this.#d3TickFormat }; - for (const triple of subplot.Scalar) { + loopScalar: for (const triple of subplot.Scalar) { 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 series = triple[2]; var trace = { From 3d192ed9a40fa8d8b52fbd8a29b30db0db1ed61e Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Thu, 22 Feb 2024 19:49:48 -0500 Subject: [PATCH 09/11] ui: first pass at a dark mode --- static/lib.js | 9 +++++++ static/site.css | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/static/lib.js b/static/lib.js index 969b16f..4880e94 100644 --- a/static/lib.js +++ b/static/lib.js @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +function getCssVariableValue(variableName) { + return getComputedStyle(document.documentElement).getPropertyValue(variableName); +} + class TimeseriesGraph extends HTMLElement { #uri; #width; @@ -243,6 +247,11 @@ class TimeseriesGraph extends HTMLElement { var layout = { displayModeBar: false, responsive: true, + plot_bgcolor: getCssVariableValue('--paper-background-color').trim(), + paper_bgcolor: getCssVariableValue('--paper-background-color').trim(), + font: { + color: getCssVariableValue('--text-color').trim() + } }; var traces = []; for (var subplot_idx in data) { diff --git a/static/site.css b/static/site.css index b0d8f82..3ce6933 100644 --- a/static/site.css +++ b/static/site.css @@ -1,3 +1,66 @@ +: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; From 7e93d499eb4a28868dda1dbccc530f4b49cf1422 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Thu, 22 Feb 2024 20:09:32 -0500 Subject: [PATCH 10/11] ui: Set off the plot container element a bit --- static/site.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/static/site.css b/static/site.css index 3ce6933..b7e6922 100644 --- a/static/site.css +++ b/static/site.css @@ -83,3 +83,10 @@ body * { .flex-item-shrink { flex: 0 1 auto; } + +timeseries-graph { + background-color: var(--paper-background-color); + border-radius: 4px; + display: flex; + flex-direction: column; +} From cf64c30d5826e9415d7fa6c47d3b033048d93490 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Thu, 22 Feb 2024 20:19:54 -0500 Subject: [PATCH 11/11] ui: Color the gridlines in the plots. --- static/lib.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/static/lib.js b/static/lib.js index 4880e94..16ea915 100644 --- a/static/lib.js +++ b/static/lib.js @@ -251,6 +251,9 @@ class TimeseriesGraph extends HTMLElement { paper_bgcolor: getCssVariableValue('--paper-background-color').trim(), font: { color: getCssVariableValue('--text-color').trim() + }, + xaxis: { + gridcolor: getCssVariableValue("--accent-color") } }; var traces = []; @@ -273,6 +276,7 @@ class TimeseriesGraph extends HTMLElement { // https://plotly.com/javascript/reference/layout/yaxis/ layout["yaxis" + subplotCount] = { anchor: yaxis, + gridcolor: getCssVariableValue("--accent-color"), tickformat: meta["d3_tick_format"] || this.#d3TickFormat }; const series = triple[2]; @@ -297,7 +301,8 @@ class TimeseriesGraph extends HTMLElement { } else if (subplot.Scalar) { // https://plotly.com/javascript/reference/bar/ layout["yaxis"] = { - tickformat: this.#d3TickFormat + tickformat: this.#d3TickFormat, + gridcolor: getCssVariableValue("--accent-color") }; loopScalar: for (const triple of subplot.Scalar) { const labels = triple[0]; @@ -356,6 +361,7 @@ class SpanSelector extends HTMLElement { connectedCallback() { const self = this; + // TODO(jwall): We should probably show a loading indicator of some kind. self.#updateInput.onclick = function(_evt) { self.updateGraphs() };