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 {
|
||||
font-family: "Consolas", "Courier New", monospace;
|
||||
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;
|
||||
}
|
||||
|
||||
.severity-error {
|
||||
.severity-error,
|
||||
.severity-error .monitor-event-line {
|
||||
fill: #f87171;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import {
|
|||
groupTicketsByColumn,
|
||||
buildTicketModalContent,
|
||||
mapEventForDisplay,
|
||||
formatEventLineText,
|
||||
truncateToWidth,
|
||||
buildEventModalContent,
|
||||
buildSystemModalContent,
|
||||
buildKpiTiles,
|
||||
|
|
@ -85,6 +87,12 @@ const DESK_ROW_Y = [540, 840];
|
|||
const DESK_COL_X = [232, 644, 1056, 1468];
|
||||
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
|
||||
// (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.
|
||||
|
|
@ -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) => {
|
||||
g.appendChild(
|
||||
text(`${eventLine.time} ${eventLine.system}: ${eventLine.message}`, {
|
||||
x: frame.x + 18,
|
||||
y: frame.y + 38 + index * 26,
|
||||
class: `monitor-event-line severity-${eventLine.severity}`,
|
||||
const blockY = frame.y + 38 + index * EVENT_BLOCK_H;
|
||||
const entry = el("g", { class: `monitor-event severity-${eventLine.severity}` });
|
||||
|
||||
const title = el("title");
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
*
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ import {
|
|||
buildTicketModalContent,
|
||||
formatEventTime,
|
||||
mapEventForDisplay,
|
||||
formatEventLineText,
|
||||
truncateToWidth,
|
||||
EVENT_LINE_CHAR_WIDTH,
|
||||
buildEventModalContent,
|
||||
buildSystemModalContent,
|
||||
buildKpiSparklinePoints,
|
||||
|
|
@ -286,6 +289,32 @@ test("mapEventForDisplay uebersetzt ein rohes Event in die Wand-Zeilendarstellun
|
|||
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)", () => {
|
||||
const events = Array.from({ length: MAX_EVENT_LINES + 3 }, (_, i) => ({
|
||||
occurred_at: "2026-07-27T10:00:00Z",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue