Merge pull request 'Event-Monitor: Zeilen passen in den Monitor, Volltext im Tooltip' (#47) from fds2610-bot/monitor:main into main
Reviewed-on: https://codeberg.org/fds2610/monitor/pulls/47
This commit is contained in:
commit
75fa035712
4 changed files with 120 additions and 7 deletions
|
|
@ -240,13 +240,23 @@ header.office-header h1 {
|
||||||
.monitor-event-line {
|
.monitor-event-line {
|
||||||
font-family: "Consolas", "Courier New", monospace;
|
font-family: "Consolas", "Courier New", monospace;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
/* Grundfarbe fuer Schweregrade ohne eigene Farbe (z.B. info) - ohne sie stuende der Text
|
||||||
|
schwarz auf dem dunklen Monitor. */
|
||||||
|
fill: #cbd5e1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.severity-warning {
|
/* Kopfzeile eines Events (Zeit + System): tritt gegenueber der Meldung zurueck. */
|
||||||
|
.monitor-event-meta {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.severity-warning,
|
||||||
|
.severity-warning .monitor-event-line {
|
||||||
fill: #facc15;
|
fill: #facc15;
|
||||||
}
|
}
|
||||||
|
|
||||||
.severity-error {
|
.severity-error,
|
||||||
|
.severity-error .monitor-event-line {
|
||||||
fill: #f87171;
|
fill: #f87171;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import {
|
||||||
groupTicketsByColumn,
|
groupTicketsByColumn,
|
||||||
buildTicketModalContent,
|
buildTicketModalContent,
|
||||||
mapEventForDisplay,
|
mapEventForDisplay,
|
||||||
|
formatEventLineText,
|
||||||
|
truncateToWidth,
|
||||||
buildEventModalContent,
|
buildEventModalContent,
|
||||||
buildSystemModalContent,
|
buildSystemModalContent,
|
||||||
buildKpiTiles,
|
buildKpiTiles,
|
||||||
|
|
@ -85,6 +87,12 @@ const DESK_ROW_Y = [540, 840];
|
||||||
const DESK_COL_X = [232, 644, 1056, 1468];
|
const DESK_COL_X = [232, 644, 1056, 1468];
|
||||||
const COFFEE_CORNER = { x: 960, y: 780 };
|
const COFFEE_CORNER = { x: 960, y: 780 };
|
||||||
|
|
||||||
|
// Ein Event belegt im Event-Monitor zwei Zeilen (Kopfzeile Zeit/System, darunter die
|
||||||
|
// Meldung); EVENT_BLOCK_H ist der Abstand von Event zu Event, EVENT_MESSAGE_DY der der
|
||||||
|
// beiden Zeilen zueinander. MAX_EVENT_LINES (5) Bloecke passen damit in MONITOR_WALL.h.
|
||||||
|
const EVENT_BLOCK_H = 44;
|
||||||
|
const EVENT_MESSAGE_DY = 18;
|
||||||
|
|
||||||
// Fester Platz des Product Owner (Issue #19): bewusst ausserhalb des dynamischen 8er-Rasters
|
// Fester Platz des Product Owner (Issue #19): bewusst ausserhalb des dynamischen 8er-Rasters
|
||||||
// (DESK_COL_X/DESK_ROW_Y), in der freien Nische zwischen Board/Monitor-Wand und der ersten
|
// (DESK_COL_X/DESK_ROW_Y), in der freien Nische zwischen Board/Monitor-Wand und der ersten
|
||||||
// Schreibtischreihe - so kollidiert er nie mit einem echten, ueber die API registrierten Agenten.
|
// Schreibtischreihe - so kollidiert er nie mit einem echten, ueber die API registrierten Agenten.
|
||||||
|
|
@ -286,14 +294,38 @@ function renderEventMonitor(frame, events) {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Meldungen sind beliebig lang, der Monitor ist es nicht - einzeilig lief die Zeile ueber
|
||||||
|
// den Rahmen hinaus in die Szene. Jedes Event bekommt deshalb zwei Zeilen: Zeit + System
|
||||||
|
// oben, die Meldung eingerueckt darunter. Beide werden auf die Innenbreite gekuerzt, den
|
||||||
|
// Volltext zeigt der <title>-Tooltip beim Hover ueber das Event.
|
||||||
|
const textX = frame.x + 18;
|
||||||
|
const availableWidth = frame.w - 36;
|
||||||
|
const messageIndent = 12;
|
||||||
|
|
||||||
lines.forEach((eventLine, index) => {
|
lines.forEach((eventLine, index) => {
|
||||||
g.appendChild(
|
const blockY = frame.y + 38 + index * EVENT_BLOCK_H;
|
||||||
text(`${eventLine.time} ${eventLine.system}: ${eventLine.message}`, {
|
const entry = el("g", { class: `monitor-event severity-${eventLine.severity}` });
|
||||||
x: frame.x + 18,
|
|
||||||
y: frame.y + 38 + index * 26,
|
const title = el("title");
|
||||||
class: `monitor-event-line severity-${eventLine.severity}`,
|
title.textContent = formatEventLineText(eventLine);
|
||||||
|
entry.appendChild(title);
|
||||||
|
|
||||||
|
entry.appendChild(
|
||||||
|
text(truncateToWidth(`${eventLine.time} ${eventLine.system}`, availableWidth), {
|
||||||
|
x: textX,
|
||||||
|
y: blockY,
|
||||||
|
class: "monitor-event-line monitor-event-meta",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
entry.appendChild(
|
||||||
|
text(truncateToWidth(eventLine.message, availableWidth - messageIndent), {
|
||||||
|
x: textX + messageIndent,
|
||||||
|
y: blockY + EVENT_MESSAGE_DY,
|
||||||
|
class: "monitor-event-line monitor-event-message",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
g.appendChild(entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
return g;
|
return g;
|
||||||
|
|
|
||||||
|
|
@ -255,6 +255,48 @@ export function mapEventForDisplay(event) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Volltext einer Event-Zeile, wie er ungekuerzt im Tooltip steht.
|
||||||
|
*
|
||||||
|
* @param {{time: string, system: string, message: string}} eventLine
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function formatEventLineText(eventLine) {
|
||||||
|
return `${eventLine.time} ${eventLine.system}: ${eventLine.message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zeichenbreite der Monospace-Schrift des Event-Monitors (13px, siehe .monitor-event-line in
|
||||||
|
// office.css). Bei Monospace ist die Breite fuer alle Zeichen gleich; der Wert ist am
|
||||||
|
// breitesten der in Frage kommenden Schriften ausgerichtet (Courier New / DejaVu Sans Mono,
|
||||||
|
// ~0.6 em; Consolas ist schmaler), damit die Zeile auf keinem System ueberlaeuft. So bleibt
|
||||||
|
// die Abschaetzung ohne DOM-Messung auskommend und damit testbar.
|
||||||
|
export const EVENT_LINE_CHAR_WIDTH = 7.9;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kuerzt einen Text auf die Breite, die im Monitor tatsaechlich zur Verfuegung steht, und
|
||||||
|
* markiert die Kuerzung mit einer Ellipse. Zu lange Zeilen liefen sonst ueber den
|
||||||
|
* Monitorrahmen hinaus in die Szene hinein; die volle Meldung zeigt der Tooltip
|
||||||
|
* (siehe renderEventMonitor()).
|
||||||
|
*
|
||||||
|
* @param {string} value
|
||||||
|
* @param {number} availableWidth - verfuegbare Breite in SVG-Einheiten
|
||||||
|
* @param {number} [charWidth]
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function truncateToWidth(value, availableWidth, charWidth = EVENT_LINE_CHAR_WIDTH) {
|
||||||
|
const maxChars = Math.floor(availableWidth / charWidth);
|
||||||
|
if (maxChars <= 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (value.length <= maxChars) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (maxChars === 1) {
|
||||||
|
return "…";
|
||||||
|
}
|
||||||
|
return `${value.slice(0, maxChars - 1).trimEnd()}…`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inhaltsmodell fuer das Event-Monitor-Modal: volle Liste (kein MAX_EVENT_LINES-Limit).
|
* Inhaltsmodell fuer das Event-Monitor-Modal: volle Liste (kein MAX_EVENT_LINES-Limit).
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ import {
|
||||||
buildTicketModalContent,
|
buildTicketModalContent,
|
||||||
formatEventTime,
|
formatEventTime,
|
||||||
mapEventForDisplay,
|
mapEventForDisplay,
|
||||||
|
formatEventLineText,
|
||||||
|
truncateToWidth,
|
||||||
|
EVENT_LINE_CHAR_WIDTH,
|
||||||
buildEventModalContent,
|
buildEventModalContent,
|
||||||
buildSystemModalContent,
|
buildSystemModalContent,
|
||||||
buildKpiSparklinePoints,
|
buildKpiSparklinePoints,
|
||||||
|
|
@ -286,6 +289,32 @@ test("mapEventForDisplay uebersetzt ein rohes Event in die Wand-Zeilendarstellun
|
||||||
assert.match(line.time, /^\d{2}:\d{2}$/);
|
assert.match(line.time, /^\d{2}:\d{2}$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("formatEventLineText setzt Zeit, System und Meldung zur Monitorzeile zusammen", () => {
|
||||||
|
assert.equal(
|
||||||
|
formatEventLineText({ time: "14:32", system: "Datenbank", message: "Verbindung weg" }),
|
||||||
|
"14:32 Datenbank: Verbindung weg"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("truncateToWidth laesst passende Zeilen unveraendert", () => {
|
||||||
|
const value = "14:32 DB: weg";
|
||||||
|
assert.equal(truncateToWidth(value, value.length * EVENT_LINE_CHAR_WIDTH), value);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("truncateToWidth kuerzt zu lange Zeilen auf die verfuegbare Breite inkl. Ellipse", () => {
|
||||||
|
const value = "14:32 Datenbank: Verbindung fehlgeschlagen";
|
||||||
|
const truncated = truncateToWidth(value, 10 * EVENT_LINE_CHAR_WIDTH);
|
||||||
|
assert.equal(truncated.length, 10);
|
||||||
|
assert.ok(truncated.endsWith("…"));
|
||||||
|
assert.ok(value.startsWith(truncated.slice(0, -1)));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("truncateToWidth kommt mit entarteten Breiten klar", () => {
|
||||||
|
assert.equal(truncateToWidth("abc", 0), "");
|
||||||
|
assert.equal(truncateToWidth("abc", -5), "");
|
||||||
|
assert.equal(truncateToWidth("abc", EVENT_LINE_CHAR_WIDTH), "…");
|
||||||
|
});
|
||||||
|
|
||||||
test("buildEventModalContent zeigt die volle Liste (kein MAX_EVENT_LINES-Limit)", () => {
|
test("buildEventModalContent zeigt die volle Liste (kein MAX_EVENT_LINES-Limit)", () => {
|
||||||
const events = Array.from({ length: MAX_EVENT_LINES + 3 }, (_, i) => ({
|
const events = Array.from({ length: MAX_EVENT_LINES + 3 }, (_, i) => ({
|
||||||
occurred_at: "2026-07-27T10:00:00Z",
|
occurred_at: "2026-07-27T10:00:00Z",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue