Skip to content

Commit fb82825

Browse files
destarirbg
andauthored
filter by severity level (#65)
* filter by severity level * Switch to ctrl-f, fix modal selections, better help guides * Apply suggestions from code review Co-authored-by: Robert Gordon <rbg@users.noreply.github.com> --------- Co-authored-by: Robert Gordon <rbg@users.noreply.github.com>
1 parent 5ac1466 commit fb82825

File tree

8 files changed

+620
-43
lines changed

8 files changed

+620
-43
lines changed

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ A powerful, real-time log analysis terminal UI inspired by k9s. Analyze log stre
5151

5252
- **Regex support** - Filter logs with regular expressions
5353
- **Attribute search** - Find logs by specific attribute values
54-
- **Severity filtering** - Focus on errors, warnings, or specific log levels
54+
- **Severity filtering** - Interactive modal to select specific log levels (Ctrl+f)
55+
- **Multi-level selection** - Enable/disable multiple severity levels at once
5556
- **Interactive selection** - Click or keyboard navigate to explore logs
5657

5758
### 🎨 Customizable Themes
@@ -293,6 +294,7 @@ cat logs.json | gonzo --ai-model="gpt-4"
293294
| `Space` | Pause/unpause entire dashboard |
294295
| `/` | Enter filter mode (regex supported) |
295296
| `s` | Search and highlight text in logs |
297+
| `Ctrl+f` | Open severity filter modal |
296298
| `f` | Open fullscreen log viewer modal |
297299
| `c` | Toggle Host/Service columns in log view |
298300
| `r` | Reset all data (manual reset) |
@@ -319,6 +321,26 @@ cat logs.json | gonzo --ai-model="gpt-4"
319321
| `Tab` | Switch between log details and chat pane |
320322
| `m` | Switch AI model (works in modal too) |
321323

324+
#### Severity Filter Modal
325+
326+
The severity filter modal (`Ctrl+f`) provides fine-grained control over which log levels to display:
327+
328+
| Key | Action |
329+
| ------------------ | ------------------------------------------------- |
330+
| ``/`` or `k`/`j` | Navigate severity options |
331+
| `Space` | Toggle selected severity level on/off |
332+
| `Enter` | Apply filter and close modal (or select All/None) |
333+
| `ESC` | Cancel changes and close modal |
334+
335+
**Features:**
336+
- **Select All** - Quick option to enable all severity levels (Enter to apply and close)
337+
- **Select None** - Quick option to disable all severity levels (Enter to apply and close)
338+
- **Individual toggles** - Enable/disable specific levels (FATAL, ERROR, WARN, INFO, DEBUG, TRACE, etc.)
339+
- **Color-coded display** - Each severity level shows in its standard color
340+
- **Real-time count** - Header shows how many levels are currently active
341+
- **Persistent filtering** - Applied filters remain active until changed
342+
- **Quick shortcuts** - Press Enter on Select All/None to apply immediately
343+
322344
### Log Counts Analysis Modal
323345

324346
Press `Enter` on the Counts section to open a comprehensive analysis modal featuring:

USAGE_GUIDE.md

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,72 @@ cat test.log | ./build/gonzo
6565
```
6666

6767
### 3. Keyboard Shortcuts (Interactive Mode)
68+
69+
#### Navigation & Control
6870
- `q` or `Ctrl+C` - Clean exit
69-
- `Tab`/`Shift+Tab` - Navigate sections (if implemented)
70-
- `↑/↓` or `k/j` - Select items (if implemented)
71-
- `Enter` - Show details (if implemented)
72-
- `/` - Filter mode (if implemented)
71+
- `Tab`/`Shift+Tab` - Navigate between sections
72+
- `↑/↓` or `k/j` - Select items within sections
73+
- `Enter` - Show details for selected item
74+
- `Space` - Pause/unpause entire dashboard
75+
76+
#### Filtering & Search
77+
- `/` - Enter regex filter mode
78+
- `s` - Search and highlight text in logs
79+
- `Ctrl+F` - Open severity filter modal
80+
81+
#### Severity Filter Modal (`Ctrl+f`)
82+
- `↑/↓` or `k/j` - Navigate severity options
83+
- `Space` - Toggle selected severity level on/off
84+
- `Enter` - Apply filter and close modal (or quick-select All/None)
85+
- `ESC` - Cancel changes and close modal
86+
87+
**Modal Features:**
88+
- Select All/None options for quick changes (Enter to apply and close instantly)
89+
- Individual severity toggles (FATAL, ERROR, WARN, INFO, DEBUG, TRACE, etc.)
90+
- Color-coded severity levels
91+
- Real-time active count display
92+
93+
#### Other Actions
94+
- `f` - Open fullscreen log viewer modal
95+
- `c` - Toggle Host/Service columns in log view
96+
- `r` - Reset all data (manual reset)
97+
- `u`/`U` - Cycle update intervals
98+
- `i` - AI analysis (when viewing log details)
99+
- `m` - Switch AI model
100+
- `?`/`h` - Show help
101+
102+
### 4. Filtering Examples
103+
104+
#### Using Severity Filter
105+
```bash
106+
# Start Gonzo with mixed severity logs
107+
./build/gonzo -f application.log
108+
109+
# In the TUI:
110+
# Quick shortcut:
111+
# 1. Press Ctrl+f to open severity filter modal
112+
# 2. Navigate to "Select None" and press Enter (applies and closes instantly)
113+
# 3. Navigate to "ERROR" and press Space to enable only errors
114+
# 4. Navigate to "FATAL" and press Space to also show fatal logs
115+
# 5. Press Enter to apply filter
116+
# Now only ERROR and FATAL logs will be displayed
117+
118+
```
119+
120+
#### Combining Filters
121+
```bash
122+
# Start with logs that have various severities and content
123+
./build/gonzo -f /var/log/app.log
124+
125+
# In the TUI:
126+
# 1. Press / to enter regex filter mode, type "database" and press Enter
127+
# 2. Press Ctrl+f to open severity filter
128+
# 3. Navigate to "Select None" and press Enter (quick clear)
129+
# 4. Press Ctrl+f again to reopen modal
130+
# 5. Enable only "ERROR" and "WARN" levels with Space
131+
# 6. Press Enter to apply
132+
# Now you see only database-related errors and warnings
133+
```
73134

74135
## Command Line Options
75136

internal/tui/components.go

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,20 +329,172 @@ func (m *DashboardModel) renderLogScrollContent(height int, logWidth int) []stri
329329
" • Stream logs: kubectl logs -f pod | gonzo",
330330
" • From file: gonzo < application.log",
331331
"",
332+
}
333+
334+
// Add current filters section if any are applied
335+
filterStatus := m.buildFilterStatus()
336+
if len(filterStatus) > 0 {
337+
instructions = append(instructions, "🔍 Current filters:")
338+
instructions = append(instructions, filterStatus...)
339+
instructions = append(instructions, "")
340+
}
341+
342+
instructions = append(instructions, []string{
332343
"📋 Key commands:",
333344
" • ?/h: Show help",
334345
" • /: Filter logs (regex)",
346+
" • Ctrl+f: Filter logs by severity",
335347
" • s: Search and highlight",
336348
" • Tab: Navigate sections",
337349
" • q: Quit",
350+
}...)
351+
352+
// Handle scrolling for instructions if they exceed available height
353+
availableLines := height - 1 // Reserve space for status line that's already added
354+
if availableLines < 1 {
355+
availableLines = 1
338356
}
339357

340-
logLines = append(logLines, instructions...)
358+
if len(instructions) > availableLines {
359+
// Add scroll indicators and implement scrolling
360+
maxScroll := len(instructions) - availableLines + 1 // +1 for scroll indicator space
361+
if m.instructionsScrollOffset > maxScroll {
362+
m.instructionsScrollOffset = maxScroll
363+
}
364+
if m.instructionsScrollOffset < 0 {
365+
m.instructionsScrollOffset = 0
366+
}
367+
368+
// Add scroll up indicator if not at top
369+
if m.instructionsScrollOffset > 0 {
370+
scrollUpIndicator := lipgloss.NewStyle().
371+
Foreground(ColorGray).
372+
Render(fmt.Sprintf(" ↑ %d more lines above", m.instructionsScrollOffset))
373+
logLines = append(logLines, scrollUpIndicator)
374+
availableLines-- // Use one line for indicator
375+
}
376+
377+
// Show visible portion of instructions
378+
endIdx := m.instructionsScrollOffset + availableLines
379+
if endIdx > len(instructions) {
380+
endIdx = len(instructions)
381+
}
382+
383+
// Reserve space for bottom scroll indicator if needed
384+
if endIdx < len(instructions) {
385+
availableLines-- // Reserve space for bottom indicator
386+
endIdx = m.instructionsScrollOffset + availableLines
387+
}
388+
389+
// Add visible instructions
390+
visibleInstructions := instructions[m.instructionsScrollOffset:endIdx]
391+
logLines = append(logLines, visibleInstructions...)
392+
393+
// Add scroll down indicator if not at bottom
394+
if endIdx < len(instructions) {
395+
remaining := len(instructions) - endIdx
396+
scrollDownIndicator := lipgloss.NewStyle().
397+
Foreground(ColorGray).
398+
Render(fmt.Sprintf(" ↓ %d more lines below (use ↑↓ or k/j to scroll)", remaining))
399+
logLines = append(logLines, scrollDownIndicator)
400+
}
401+
} else {
402+
// All instructions fit, no scrolling needed
403+
logLines = append(logLines, instructions...)
404+
}
341405
}
342406

343407
return logLines
344408
}
345409

410+
// buildFilterStatus returns a list of currently applied filters for display when no logs are shown
411+
func (m *DashboardModel) buildFilterStatus() []string {
412+
var filters []string
413+
414+
// Check severity filter
415+
if m.severityFilterActive {
416+
disabledSeverities := []string{}
417+
enabledSeverities := []string{}
418+
419+
severityLevels := []string{"FATAL", "CRITICAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE", "UNKNOWN"}
420+
for _, severity := range severityLevels {
421+
if enabled, exists := m.severityFilter[severity]; exists {
422+
if enabled {
423+
enabledSeverities = append(enabledSeverities, severity)
424+
} else {
425+
disabledSeverities = append(disabledSeverities, severity)
426+
}
427+
}
428+
}
429+
430+
if len(enabledSeverities) > 0 && len(enabledSeverities) < len(severityLevels) {
431+
if len(enabledSeverities) <= 3 {
432+
filters = append(filters, " • Severity: Only showing "+joinWithCommas(enabledSeverities))
433+
} else {
434+
filters = append(filters, " • Severity: Hiding "+joinWithCommas(disabledSeverities))
435+
}
436+
} else if len(enabledSeverities) == 0 {
437+
filters = append(filters, " • Severity: All severities disabled (no logs will show)")
438+
}
439+
}
440+
441+
// Check regex filter
442+
if m.filterRegex != nil {
443+
pattern := m.filterInput.Value()
444+
if pattern == "" && m.filterRegex != nil {
445+
pattern = m.filterRegex.String()
446+
}
447+
if pattern != "" {
448+
filters = append(filters, " • Regex filter: "+pattern)
449+
}
450+
}
451+
452+
// Check search term
453+
if m.searchTerm != "" {
454+
filters = append(filters, " • Search highlight: "+m.searchTerm)
455+
}
456+
457+
// Add instructions for clearing filters if any are active
458+
if len(filters) > 0 {
459+
filters = append(filters, "")
460+
filters = append(filters, " 💡 To clear filters:")
461+
if m.severityFilterActive {
462+
filters = append(filters, " • Ctrl+F → Select All → Enter (enable all severities)")
463+
}
464+
if m.filterRegex != nil {
465+
filters = append(filters, " • / → Backspace/Delete → Enter (clear regex)")
466+
}
467+
if m.searchTerm != "" {
468+
filters = append(filters, " • s → Backspace/Delete → Enter (clear search)")
469+
}
470+
}
471+
472+
return filters
473+
}
474+
475+
// joinWithCommas joins a slice of strings with commas and "and" before the last item
476+
func joinWithCommas(items []string) string {
477+
if len(items) == 0 {
478+
return ""
479+
}
480+
if len(items) == 1 {
481+
return items[0]
482+
}
483+
if len(items) == 2 {
484+
return items[0] + " and " + items[1]
485+
}
486+
487+
result := ""
488+
for i, item := range items {
489+
if i == len(items)-1 {
490+
result += "and " + item
491+
} else {
492+
result += item + ", "
493+
}
494+
}
495+
return result
496+
}
497+
346498
// renderLogScroll renders the scrolling log section
347499
func (m *DashboardModel) renderLogScroll(height int) string {
348500
// Use most of terminal width for logs

0 commit comments

Comments
 (0)