mirror of
https://github.com/zaphar/Heracles.git
synced 2025-07-27 22:39:49 -04:00
Compare commits
4 Commits
a7a6c99099
...
e44d2087c8
Author | SHA1 | Date | |
---|---|---|---|
e44d2087c8 | |||
c830d1530d | |||
6a503947d8 | |||
01cd54afa4 |
@ -96,6 +96,10 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
"/embed/dash/:dash_idx/graph/:graph_idx",
|
"/embed/dash/:dash_idx/graph/:graph_idx",
|
||||||
get(routes::graph_embed).with_state(State(config.clone())),
|
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("/dash/:dash_idx", get(routes::dashboard_direct))
|
||||||
.route("/", get(routes::index).with_state(State(config.clone())))
|
.route("/", get(routes::index).with_state(State(config.clone())))
|
||||||
.layer(TraceLayer::new_for_http())
|
.layer(TraceLayer::new_for_http())
|
||||||
|
@ -163,6 +163,21 @@ pub async fn graph_ui(
|
|||||||
graph_component(dash_idx, graph_idx, graph)
|
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 {
|
pub async fn dash_ui(State(config): State<Config>, Path(dash_idx): Path<usize>) -> Markup {
|
||||||
// TODO(zaphar): Should do better http error reporting here.
|
// TODO(zaphar): Should do better http error reporting here.
|
||||||
dash_elements(config, dash_idx)
|
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 {
|
async fn index_html(config: Config, dash_idx: Option<usize>) -> Markup {
|
||||||
html! {
|
html! {
|
||||||
html {
|
html {
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
* @property {{color: string}=} fill
|
* @property {{color: string}=} fill
|
||||||
* @property {{width: number, color: string}=} line
|
* @property {{width: number, color: string}=} line
|
||||||
* @property {{family: string, size: number, color: string }=} font
|
* @property {{family: string, size: number, color: string }=} font
|
||||||
|
* @property {Array<number>=} columnwidth
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,6 +69,41 @@
|
|||||||
* @type {(TableTrace|GraphTrace)}
|
* @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.
|
* Get's a css variable's value from the document.
|
||||||
* @param {string} variableName - Name of the variable to get `--var-name`
|
* @param {string} variableName - Name of the variable to get `--var-name`
|
||||||
@ -395,7 +431,7 @@ export class GraphPlot extends HTMLElement {
|
|||||||
var layout = {
|
var layout = {
|
||||||
displayModeBar: false,
|
displayModeBar: false,
|
||||||
responsive: true,
|
responsive: true,
|
||||||
plot_bgcolor: getCssVariableValue('--paper-background-color').trim(),
|
plot_bgcolor: getCssVariableValue('--plot-background-color').trim(),
|
||||||
paper_bgcolor: getCssVariableValue('--paper-background-color').trim(),
|
paper_bgcolor: getCssVariableValue('--paper-background-color').trim(),
|
||||||
font: {
|
font: {
|
||||||
color: getCssVariableValue('--text-color').trim()
|
color: getCssVariableValue('--text-color').trim()
|
||||||
@ -480,41 +516,50 @@ export class GraphPlot extends HTMLElement {
|
|||||||
traces.push(trace);
|
traces.push(trace);
|
||||||
}
|
}
|
||||||
} else if (subplot.Stream) {
|
} 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 */({
|
const trace = /** @type TableTrace */({
|
||||||
type: "table",
|
type: "table",
|
||||||
// TODO(zaphar): Column width?
|
columnwidth: [15, 20, 70],
|
||||||
headers: {
|
headers: {
|
||||||
align: "left",
|
align: "left",
|
||||||
values: ["Timestamp", "Log"],
|
values: ["Timestamp","Label", "Log"],
|
||||||
fill: { color: layout.xaxis.gridColor }
|
fill: { color: layout.xaxis.gridColor }
|
||||||
},
|
},
|
||||||
cells: {
|
cells: {
|
||||||
align: "left",
|
align: "left",
|
||||||
values: [],
|
values: [],
|
||||||
fill: { color: layout.paper_bgcolor }
|
fill: { color: layout.plot_bgcolor }
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const dateColumn = [];
|
const dateColumn = [];
|
||||||
|
const metaColumn = [];
|
||||||
const logColumn = [];
|
const logColumn = [];
|
||||||
|
|
||||||
loopStream: for (const pair of subplot.Stream) {
|
loopStream: for (const pair of subplot.Stream) {
|
||||||
const labels = pair[0];
|
const labels = pair[0];
|
||||||
|
var labelList = [];
|
||||||
for (var label in labels) {
|
for (var label in labels) {
|
||||||
var show = this.#filteredLabelSets[label];
|
var show = this.#filteredLabelSets[label];
|
||||||
if (show && !show.includes(labels[label])) {
|
if (show && !show.includes(labels[label])) {
|
||||||
continue loopStream;
|
continue loopStream;
|
||||||
}
|
}
|
||||||
|
labelList.push(`${label}:${labels[label]}`);
|
||||||
}
|
}
|
||||||
|
const labelsName = labelList.join("<br>");
|
||||||
const lines = pair[1];
|
const lines = pair[1];
|
||||||
// TODO(jwall): Headers
|
// TODO(jwall): Headers
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
// For streams the timestamps are in nanoseconds
|
// For streams the timestamps are in nanoseconds
|
||||||
dateColumn.push(new Date(line.timestamp / 1000000));
|
// TODO(zaphar): We should improve the timstamp formatting a bit
|
||||||
logColumn.push(line.line);
|
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(dateColumn);
|
||||||
|
trace.cells.values.push(metaColumn);
|
||||||
trace.cells.values.push(logColumn);
|
trace.cells.values.push(logColumn);
|
||||||
traces.push(trace);
|
traces.push(trace);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
--background-color: #FFFFFF; /* Light background */
|
--background-color: #FFFFFF; /* Light background */
|
||||||
--text-color: #333333; /* Dark text for contrast */
|
--text-color: #333333; /* Dark text for contrast */
|
||||||
--paper-background-color: #F0F0F0;
|
--paper-background-color: #F0F0F0;
|
||||||
|
--plot-background-color: #F0F0F0;
|
||||||
--accent-color: #6200EE; /* For buttons and interactive elements */
|
--accent-color: #6200EE; /* For buttons and interactive elements */
|
||||||
|
|
||||||
/* Graph colors */
|
/* Graph colors */
|
||||||
@ -27,6 +28,7 @@
|
|||||||
/* Solarized Dark Base Colors */
|
/* Solarized Dark Base Colors */
|
||||||
--background-color: #002b36; /* base03 */
|
--background-color: #002b36; /* base03 */
|
||||||
--paper-background-color: #003c4a;
|
--paper-background-color: #003c4a;
|
||||||
|
--plot-background-color: rgb(24, 34, 21);
|
||||||
--text-color: #839496; /* base0 */
|
--text-color: #839496; /* base0 */
|
||||||
--accent-color: #268bd2; /* blue */
|
--accent-color: #268bd2; /* blue */
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user