mirror of
https://github.com/zaphar/Heracles.git
synced 2025-07-23 04:29:48 -04:00
ui: Show log results in our dashboards
This commit is contained in:
parent
17e6ae81a0
commit
f69ea6d6fa
@ -22,7 +22,7 @@ use tracing::{debug, error};
|
||||
use super::{LogLine, QueryResult, QueryType, TimeSpan};
|
||||
|
||||
// TODO(jwall): Should I allow non stream returns?
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub enum ResultType {
|
||||
/// Returned by query endpoints
|
||||
#[serde(rename = "vector")]
|
||||
@ -85,8 +85,10 @@ pub fn loki_to_sample(data: LokiData) -> QueryResult {
|
||||
}
|
||||
QueryResult::StreamInstant(values)
|
||||
}
|
||||
// Stream types are nanoseconds. // Matrix types are seconds
|
||||
ResultType::Matrix | ResultType::Streams => {
|
||||
let mut values = Vec::with_capacity(data.result.len());
|
||||
let multiple = (if data.result_type == ResultType::Matrix { 1000000 } else { 1 }) as f64;
|
||||
for result in data.result {
|
||||
if let Some(value) = result.values {
|
||||
values.push((
|
||||
@ -94,7 +96,7 @@ pub fn loki_to_sample(data: LokiData) -> QueryResult {
|
||||
value
|
||||
.into_iter()
|
||||
.map(|(timestamp, line)| LogLine {
|
||||
timestamp: timestamp.parse::<f64>().expect("Invalid f64 type"),
|
||||
timestamp: multiple * timestamp.parse::<f64>().expect("Invalid f64 type"),
|
||||
line,
|
||||
})
|
||||
.collect(),
|
||||
|
@ -26,7 +26,7 @@ use serde::{Deserialize, Serialize};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::dashboard::{
|
||||
loki_query_data, prom_query_data, AxisDefinition, Dashboard, Graph, GraphSpan, Orientation,
|
||||
loki_query_data, prom_query_data, AxisDefinition, Dashboard, Graph, GraphSpan, Orientation, LogStream,
|
||||
};
|
||||
use crate::query::QueryResult;
|
||||
|
||||
@ -120,6 +120,18 @@ pub fn mk_api_routes(config: Arc<Vec<Dashboard>>) -> Router<Config> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn log_component(dash_idx: usize, log_idx: usize, log: &LogStream) -> Markup {
|
||||
let log_id = format!("log-{}-{}", dash_idx, log_idx);
|
||||
let log_data_uri = format!("/api/dash/{}/log/{}", dash_idx, log_idx);
|
||||
let log_embed_uri = format!("/embed/dash/{}/log/{}", dash_idx, log_idx);
|
||||
html! {
|
||||
div {
|
||||
h2 { (log.title) " - " a href=(log_embed_uri) { "embed url" } }
|
||||
graph-plot uri=(log_data_uri) id=(log_id) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn graph_component(dash_idx: usize, graph_idx: usize, graph: &Graph) -> Markup {
|
||||
let graph_id = format!("graph-{}-{}", dash_idx, graph_idx);
|
||||
let graph_data_uri = format!("/api/dash/{}/graph/{}", dash_idx, graph_idx);
|
||||
@ -160,19 +172,35 @@ fn dash_elements(config: State<Arc<Vec<Dashboard>>>, dash_idx: usize) -> maud::P
|
||||
let dash = config
|
||||
.get(dash_idx)
|
||||
.expect(&format!("No such dashboard {}", dash_idx));
|
||||
let graph_iter = dash
|
||||
let graph_components = if let Some(graphs) = dash
|
||||
.graphs
|
||||
.as_ref()
|
||||
.expect("No graphs in this dashboard")
|
||||
.iter()
|
||||
.as_ref() {
|
||||
let graph_iter = graphs.iter()
|
||||
.enumerate()
|
||||
.collect::<Vec<(usize, &Graph)>>();
|
||||
Some(html! {
|
||||
@for (idx, graph) in &graph_iter {
|
||||
(graph_component(dash_idx, *idx, *graph))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let log_components = if let Some(logs) = dash.logs.as_ref() {
|
||||
let log_iter = logs.iter().enumerate().collect::<Vec<(usize, &LogStream)>>();
|
||||
Some(html! {
|
||||
@for (idx, log) in &log_iter {
|
||||
(log_component(dash_idx, *idx, *log))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
html!(
|
||||
h1 { (dash.title) }
|
||||
span-selector class="row-flex" {}
|
||||
@for (idx, graph) in &graph_iter {
|
||||
(graph_component(dash_idx, *idx, *graph))
|
||||
}
|
||||
@if graph_components.is_some() { (graph_components.unwrap()) }
|
||||
@if log_components.is_some() { (log_components.unwrap()) }
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -15,10 +15,10 @@
|
||||
/**
|
||||
* @typedef PlotList
|
||||
* @type {object}
|
||||
* @property {?Array} Series
|
||||
* @property {?Array} Scalar
|
||||
* @property {?Array} StreamInstant
|
||||
* @property {?Array} Stream
|
||||
* @property {Array=} Series
|
||||
* @property {Array=} Scalar
|
||||
* @property {Array<{timestamp: string, line: string}>=} StreamInstant - Timestamps are in seconds
|
||||
* @property {Array<{timestamp: string, line: string}>=} Stream - Timestamps are in nanoseconds
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -29,16 +29,43 @@
|
||||
* @property {Array<PlotList>} plots
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef HeaderOrCell
|
||||
* @type {object}
|
||||
* @property {array} values
|
||||
* @property {string=} fill
|
||||
* @property {{width: number, color: string}=} line
|
||||
* @property {{family: string, size: number, color: string }=} font
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef PlotTrace
|
||||
* @typedef TableTrace
|
||||
* @type {object}
|
||||
* @property {string=} name
|
||||
* @property type {string}
|
||||
* @property {string=} mode
|
||||
* @property {HeaderOrCell} headers
|
||||
* @property {HeaderOrCell} cells - An Array of columns for the table.
|
||||
* @property {string=} xaxis
|
||||
* @property {string=} yaxis
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef GraphTrace
|
||||
* @type {object}
|
||||
* @property {string=} name
|
||||
* @property {string=} fill
|
||||
* @property type {string}
|
||||
* @property {string=} mode
|
||||
* @property {Array} x
|
||||
* @property {Array} y
|
||||
* @peroperty {string=} xaxis
|
||||
* @peroperty {string=} yaxis
|
||||
* @property {string=} xaxis
|
||||
* @property {string=} yaxis
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef PlotTrace
|
||||
* @type {(TableTrace|GraphTrace)}
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -401,7 +428,7 @@ export class GraphPlot extends HTMLElement {
|
||||
var yaxis = meta.yaxis || "y";
|
||||
// https://plotly.com/javascript/reference/layout/yaxis/
|
||||
const series = triple[2];
|
||||
const trace = {
|
||||
const trace = /** @type GraphTrace */({
|
||||
type: "scatter",
|
||||
mode: "lines+text",
|
||||
x: [],
|
||||
@ -410,7 +437,7 @@ export class GraphPlot extends HTMLElement {
|
||||
xaxis: "x",
|
||||
yaxis: yaxis,
|
||||
//yhoverformat: yaxis.tickformat,
|
||||
};
|
||||
});
|
||||
if (meta.fill) {
|
||||
trace.fill = meta.fill;
|
||||
}
|
||||
@ -434,7 +461,7 @@ export class GraphPlot extends HTMLElement {
|
||||
}
|
||||
const meta = triple[1];
|
||||
const series = triple[2];
|
||||
const trace = /** @type PlotTrace */({
|
||||
const trace = /** @type GraphTrace */({
|
||||
type: "bar",
|
||||
x: [],
|
||||
y: [],
|
||||
@ -446,7 +473,42 @@ export class GraphPlot extends HTMLElement {
|
||||
trace.x.push(trace.name);
|
||||
traces.push(trace);
|
||||
}
|
||||
} // TODO(zaphar): subplot.Stream // log lines!!!
|
||||
} else if (subplot.Stream) {
|
||||
// TODO(zaphar): subplot.Stream // log lines!!!
|
||||
const trace = /** @type TableTrace */({
|
||||
type: "table",
|
||||
headers: {
|
||||
align: "left",
|
||||
values: ["Timestamp", "Log"]
|
||||
},
|
||||
cells: {
|
||||
align: "left",
|
||||
values: []
|
||||
},
|
||||
});
|
||||
const dateColumn = [];
|
||||
const logColumn = [];
|
||||
|
||||
loopStream: for (const pair of subplot.Stream) {
|
||||
const labels = pair[0];
|
||||
for (var label in labels) {
|
||||
var show = this.#filteredLabelSets[label];
|
||||
if (show && !show.includes(labels[label])) {
|
||||
continue loopStream;
|
||||
}
|
||||
}
|
||||
const lines = pair[1];
|
||||
// TODO(jwall): Headers
|
||||
for (const line of lines) {
|
||||
// For streams the timestamps are in nanoseconds
|
||||
dateColumn.push(new Date(line.timestamp / 1000000));
|
||||
logColumn.push(line.line);
|
||||
}
|
||||
}
|
||||
trace.cells.values.push(dateColumn);
|
||||
trace.cells.values.push(logColumn);
|
||||
traces.push(trace);
|
||||
}
|
||||
}
|
||||
// https://plotly.com/javascript/plotlyjs-function-reference/#plotlyreact
|
||||
// @ts-ignore
|
||||
|
Loading…
x
Reference in New Issue
Block a user