Compare commits

...

4 Commits

4 changed files with 90 additions and 7 deletions

View File

@ -96,6 +96,10 @@ async fn main() -> anyhow::Result<()> {
"/embed/dash/:dash_idx/graph/:graph_idx",
get(routes::graph_embed).with_state(State(config.clone())),
)
.route(
"/embed/dash/:dash_idx/log/:graph_idx",
get(routes::log_embed).with_state(State(config.clone())),
)
.route("/dash/:dash_idx", get(routes::dashboard_direct))
.route("/", get(routes::index).with_state(State(config.clone())))
.layer(TraceLayer::new_for_http())

View File

@ -163,6 +163,21 @@ pub async fn graph_ui(
graph_component(dash_idx, graph_idx, graph)
}
pub async fn log_ui(
State(config): State<Config>,
Path((dash_idx, log_idx)): Path<(usize, usize)>,
) -> Markup {
let log = config
.get(dash_idx)
.expect(&format!("No such dashboard {}", dash_idx))
.logs
.as_ref()
.expect("No graphs in this dashboard")
.get(log_idx)
.expect("No such graph");
log_component(dash_idx, log_idx, log)
}
pub async fn dash_ui(State(config): State<Config>, Path(dash_idx): Path<usize>) -> Markup {
// TODO(zaphar): Should do better http error reporting here.
dash_elements(config, dash_idx)
@ -241,6 +256,23 @@ pub async fn graph_embed(
}
}
pub async fn log_embed(
State(config): State<Config>,
Path((dash_idx, log_idx)): Path<(usize, usize)>,
) -> Markup {
html! {
html {
head {
title { ("Heracles - Prometheus Unshackled") }
}
body {
(graph_lib_prelude())
(log_ui(State(config.clone()), Path((dash_idx, log_idx))).await)
}
}
}
}
async fn index_html(config: Config, dash_idx: Option<usize>) -> Markup {
html! {
html {

View File

@ -36,6 +36,7 @@
* @property {{color: string}=} fill
* @property {{width: number, color: string}=} line
* @property {{family: string, size: number, color: string }=} font
* @property {Array<number>=} columnwidth
*/
/**
@ -68,6 +69,41 @@
* @type {(TableTrace|GraphTrace)}
*/
/**
* Map ansi terminal codes to html color codes.
* @param {string} line
*/
function ansiToHtml(line) {
const ansiToHtmlMap = {
// Map ANSI color codes to HTML color names or hex values
// We don't necessarily handle all the colors but this is enough to start.
"30": "black",
"31": "red",
"32": "green",
"33": "yellow",
"34": "blue",
"35": "magenta",
"36": "cyan",
"37": "white",
"39": "initial"
};
// NOTE(zaphar): Yes this is gross and I should really do a better parser but I'm lazy.
// Replace ANSI codes with HTML span elements styled with the corresponding color
return line.replace(/\x1b\[([0-9;]*)m/g, (match, p1) => {
const parts = p1.split(';'); // ANSI codes can be compounded, e.g., "1;31" for bold red
let styles = '';
for (let part of parts) {
if (ansiToHtmlMap[part]) {
// If the code is a color, map it to a CSS color
styles += `color: ${ansiToHtmlMap[part]};`;
}
// TODO(zaphar): Add more conditions here to handle other styles like bold or underline?
}
return styles ? `<span style="${styles}">` : '</span>';
}) + '</span>';
}
/**
* Get's a css variable's value from the document.
* @param {string} variableName - Name of the variable to get `--var-name`
@ -395,7 +431,7 @@ export class GraphPlot extends HTMLElement {
var layout = {
displayModeBar: false,
responsive: true,
plot_bgcolor: getCssVariableValue('--paper-background-color').trim(),
plot_bgcolor: getCssVariableValue('--plot-background-color').trim(),
paper_bgcolor: getCssVariableValue('--paper-background-color').trim(),
font: {
color: getCssVariableValue('--text-color').trim()
@ -480,41 +516,50 @@ export class GraphPlot extends HTMLElement {
traces.push(trace);
}
} else if (subplot.Stream) {
// TODO(zaphar): subplot.Stream // log lines!!!
// TODO(jwall): It's possible that this should actually be a separate custom
// element.
const trace = /** @type TableTrace */({
type: "table",
// TODO(zaphar): Column width?
columnwidth: [15, 20, 70],
headers: {
align: "left",
values: ["Timestamp", "Log"],
values: ["Timestamp","Label", "Log"],
fill: { color: layout.xaxis.gridColor }
},
cells: {
align: "left",
values: [],
fill: { color: layout.paper_bgcolor }
fill: { color: layout.plot_bgcolor }
},
});
const dateColumn = [];
const metaColumn = [];
const logColumn = [];
loopStream: for (const pair of subplot.Stream) {
const labels = pair[0];
var labelList = [];
for (var label in labels) {
var show = this.#filteredLabelSets[label];
if (show && !show.includes(labels[label])) {
continue loopStream;
}
labelList.push(`${label}:${labels[label]}`);
}
const labelsName = labelList.join("<br>");
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);
// TODO(zaphar): We should improve the timstamp formatting a bit
let timestamp = new Date(line.timestamp / 1000000);
dateColumn.push(timestamp.toISOString());
metaColumn.push(labelsName);
logColumn.push(ansiToHtml(line.line));
}
}
trace.cells.values.push(dateColumn);
trace.cells.values.push(metaColumn);
trace.cells.values.push(logColumn);
traces.push(trace);
}

View File

@ -3,6 +3,7 @@
--background-color: #FFFFFF; /* Light background */
--text-color: #333333; /* Dark text for contrast */
--paper-background-color: #F0F0F0;
--plot-background-color: #F0F0F0;
--accent-color: #6200EE; /* For buttons and interactive elements */
/* Graph colors */
@ -27,6 +28,7 @@
/* Solarized Dark Base Colors */
--background-color: #002b36; /* base03 */
--paper-background-color: #003c4a;
--plot-background-color: rgb(24, 34, 21);
--text-color: #839496; /* base0 */
--accent-color: #268bd2; /* blue */