feat(viewer): add severity filtering for event logs

This commit is contained in:
Mikhail Chusavitin
2026-04-01 16:19:25 +03:00
parent ac8120c8ab
commit 8675791805
6 changed files with 306 additions and 56 deletions

View File

@@ -13,13 +13,14 @@ type pageData struct {
}
type sectionView struct {
ID string
Title string
Kind string
Rows []fieldRow
Columns []string
Items []tableRow
Groups []tableGroupView
ID string
Title string
Kind string
Rows []fieldRow
Columns []string
Items []tableRow
Groups []tableGroupView
SeverityOptions []severityOption
}
type fieldRow struct {
@@ -29,12 +30,19 @@ type fieldRow struct {
type tableRow struct {
Status string
Severity string
Cells map[string]string
RawCells map[string]any
}
type tableGroupView struct {
Title string
Columns []string
Items []tableRow
Title string
Columns []string
Items []tableRow
SeverityOptions []severityOption
}
type severityOption struct {
Value string
Label string
}

View File

@@ -252,17 +252,19 @@ func buildTableSection(key string, items []any) sectionView {
status := strings.TrimSpace(cells["status"])
tableRows = append(tableRows, tableRow{
Status: status,
Severity: normalizeSeverity(cells["severity"]),
Cells: cells,
RawCells: row,
})
}
return sectionView{
ID: key,
Title: titleFor(key),
Kind: "table",
Columns: columns,
Items: tableRows,
ID: key,
Title: titleFor(key),
Kind: "table",
Columns: columns,
Items: tableRows,
SeverityOptions: collectSeverityOptions(columns, rows),
}
}
@@ -301,14 +303,16 @@ func buildPCIeSection(items []any) sectionView {
}
items = append(items, tableRow{
Status: strings.TrimSpace(cells["status"]),
Severity: normalizeSeverity(cells["severity"]),
Cells: cells,
RawCells: row,
})
}
groups = append(groups, tableGroupView{
Title: className,
Columns: columns,
Items: items,
Title: className,
Columns: columns,
Items: items,
SeverityOptions: collectSeverityOptions(columns, rows),
})
}
@@ -350,6 +354,58 @@ func collectColumns(section string, rows []map[string]any) []string {
return append(columns, extra...)
}
func collectSeverityOptions(columns []string, rows []map[string]any) []severityOption {
if !containsColumn(columns, "severity") {
return nil
}
seen := make(map[string]string)
for _, row := range rows {
label := strings.TrimSpace(formatRowValue("severity", row))
value := normalizeSeverity(label)
if value == "" {
continue
}
if _, ok := seen[value]; ok {
continue
}
seen[value] = canonicalSeverityLabel(label, value)
}
if len(seen) == 0 {
return nil
}
knownOrder := []string{"critical", "warning", "info"}
options := make([]severityOption, 0, len(seen))
for _, value := range knownOrder {
label, ok := seen[value]
if !ok {
continue
}
options = append(options, severityOption{Value: value, Label: label})
delete(seen, value)
}
extraValues := make([]string, 0, len(seen))
for value := range seen {
extraValues = append(extraValues, value)
}
sort.Strings(extraValues)
for _, value := range extraValues {
options = append(options, severityOption{Value: value, Label: seen[value]})
}
return options
}
func containsColumn(columns []string, target string) bool {
for _, column := range columns {
if column == target {
return true
}
}
return false
}
func buildFieldRows(object map[string]any) []fieldRow {
keys := make([]string, 0, len(object))
for key := range object {
@@ -444,6 +500,35 @@ func formatRowValue(column string, row map[string]any) string {
return formatValue(row[column])
}
func normalizeSeverity(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "critical":
return "critical"
case "warning", "warn":
return "warning"
case "info", "informational":
return "info"
default:
return strings.ToLower(strings.TrimSpace(value))
}
}
func canonicalSeverityLabel(raw, normalized string) string {
switch normalized {
case "critical":
return "Critical"
case "warning":
return "Warning"
case "info":
return "Info"
default:
if strings.TrimSpace(raw) == "" {
return ""
}
return strings.TrimSpace(raw)
}
}
func formatVendorDeviceID(value map[string]any) string {
vendorID := strings.TrimSpace(formatValue(value["vendor_id"]))
deviceID := strings.TrimSpace(formatValue(value["device_id"]))

View File

@@ -270,3 +270,47 @@ func TestRenderHTMLGroupsPCIeDevicesByClass(t *testing.T) {
t.Fatalf("expected PCIe class groups to be sorted by device_class")
}
}
func TestRenderHTMLAddsSeverityFilterForEventLogs(t *testing.T) {
snapshot := []byte(`{
"target_host": "event-host",
"hardware": {
"event_logs": [
{
"component_ref": "PSU0",
"event_time": "2026-03-15T12:00:00Z",
"message": "Power restored",
"severity": "Info",
"source": "bmc"
},
{
"component_ref": "PSU1",
"event_time": "2026-03-15T12:05:00Z",
"message": "Power failure",
"severity": "Critical",
"source": "bmc"
}
]
}
}`)
html, err := RenderHTML(snapshot, "Reanimator Chart")
if err != nil {
t.Fatalf("RenderHTML() error = %v", err)
}
text := string(html)
for _, needle := range []string{
"Event Logs",
"All severities",
`<option value="critical">Critical</option>`,
`<option value="info">Info</option>`,
`data-severity="critical"`,
`data-severity="info"`,
"/static/view.js",
} {
if !strings.Contains(text, needle) {
t.Fatalf("expected rendered html to contain %q", needle)
}
}
}