Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ A powerful, real-time log analysis terminal UI inspired by k9s. Analyze log stre

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

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

#### Severity Filter Modal

The severity filter modal (`Ctrl+f`) provides fine-grained control over which log levels to display:

| Key | Action |
| ------------------ | ------------------------------------------------- |
| `↑`/`↓` or `k`/`j` | Navigate severity options |
| `Space` | Toggle selected severity level on/off |
| `Enter` | Apply filter and close modal (or select All/None) |
| `ESC` | Cancel changes and close modal |

**Features:**
- **Select All** - Quick option to enable all severity levels (Enter to apply and close)
- **Select None** - Quick option to disable all severity levels (Enter to apply and close)
- **Individual toggles** - Enable/disable specific levels (FATAL, ERROR, WARN, INFO, DEBUG, TRACE, etc.)
- **Color-coded display** - Each severity level shows in its standard color
- **Real-time count** - Header shows how many levels are currently active
- **Persistent filtering** - Applied filters remain active until changed
- **Quick shortcuts** - Press Enter on Select All/None to apply immediately

### Log Counts Analysis Modal

Press `Enter` on the Counts section to open a comprehensive analysis modal featuring:
Expand Down
69 changes: 65 additions & 4 deletions USAGE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,72 @@ cat test.log | ./build/gonzo
```

### 3. Keyboard Shortcuts (Interactive Mode)

#### Navigation & Control
- `q` or `Ctrl+C` - Clean exit
- `Tab`/`Shift+Tab` - Navigate sections (if implemented)
- `↑/↓` or `k/j` - Select items (if implemented)
- `Enter` - Show details (if implemented)
- `/` - Filter mode (if implemented)
- `Tab`/`Shift+Tab` - Navigate between sections
- `↑/↓` or `k/j` - Select items within sections
- `Enter` - Show details for selected item
- `Space` - Pause/unpause entire dashboard

#### Filtering & Search
- `/` - Enter regex filter mode
- `s` - Search and highlight text in logs
- `Ctrl+F` - Open severity filter modal

#### Severity Filter Modal (`Ctrl+f`)
- `↑/↓` or `k/j` - Navigate severity options
- `Space` - Toggle selected severity level on/off
- `Enter` - Apply filter and close modal (or quick-select All/None)
- `ESC` - Cancel changes and close modal

**Modal Features:**
- Select All/None options for quick changes (Enter to apply and close instantly)
- Individual severity toggles (FATAL, ERROR, WARN, INFO, DEBUG, TRACE, etc.)
- Color-coded severity levels
- Real-time active count display

#### Other Actions
- `f` - Open fullscreen log viewer modal
- `c` - Toggle Host/Service columns in log view
- `r` - Reset all data (manual reset)
- `u`/`U` - Cycle update intervals
- `i` - AI analysis (when viewing log details)
- `m` - Switch AI model
- `?`/`h` - Show help

### 4. Filtering Examples

#### Using Severity Filter
```bash
# Start Gonzo with mixed severity logs
./build/gonzo -f application.log

# In the TUI:
# Quick shortcut:
# 1. Press Ctrl+f to open severity filter modal
# 2. Navigate to "Select None" and press Enter (applies and closes instantly)
# 3. Navigate to "ERROR" and press Space to enable only errors
# 4. Navigate to "FATAL" and press Space to also show fatal logs
# 5. Press Enter to apply filter
# Now only ERROR and FATAL logs will be displayed

```

#### Combining Filters
```bash
# Start with logs that have various severities and content
./build/gonzo -f /var/log/app.log

# In the TUI:
# 1. Press / to enter regex filter mode, type "database" and press Enter
# 2. Press Ctrl+f to open severity filter
# 3. Navigate to "Select None" and press Enter (quick clear)
# 4. Press Ctrl+f again to reopen modal
# 5. Enable only "ERROR" and "WARN" levels with Space
# 6. Press Enter to apply
# Now you see only database-related errors and warnings
```

## Command Line Options

Expand Down
154 changes: 153 additions & 1 deletion internal/tui/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,20 +329,172 @@ func (m *DashboardModel) renderLogScrollContent(height int, logWidth int) []stri
" • Stream logs: kubectl logs -f pod | gonzo",
" • From file: gonzo < application.log",
"",
}

// Add current filters section if any are applied
filterStatus := m.buildFilterStatus()
if len(filterStatus) > 0 {
instructions = append(instructions, "🔍 Current filters:")
instructions = append(instructions, filterStatus...)
instructions = append(instructions, "")
}

instructions = append(instructions, []string{
"📋 Key commands:",
" • ?/h: Show help",
" • /: Filter logs (regex)",
" • Ctrl+f: Filter logs by severity",
" • s: Search and highlight",
" • Tab: Navigate sections",
" • q: Quit",
}...)

// Handle scrolling for instructions if they exceed available height
availableLines := height - 1 // Reserve space for status line that's already added
if availableLines < 1 {
availableLines = 1
}

logLines = append(logLines, instructions...)
if len(instructions) > availableLines {
// Add scroll indicators and implement scrolling
maxScroll := len(instructions) - availableLines + 1 // +1 for scroll indicator space
if m.instructionsScrollOffset > maxScroll {
m.instructionsScrollOffset = maxScroll
}
if m.instructionsScrollOffset < 0 {
m.instructionsScrollOffset = 0
}

// Add scroll up indicator if not at top
if m.instructionsScrollOffset > 0 {
scrollUpIndicator := lipgloss.NewStyle().
Foreground(ColorGray).
Render(fmt.Sprintf(" ↑ %d more lines above", m.instructionsScrollOffset))
logLines = append(logLines, scrollUpIndicator)
availableLines-- // Use one line for indicator
}

// Show visible portion of instructions
endIdx := m.instructionsScrollOffset + availableLines
if endIdx > len(instructions) {
endIdx = len(instructions)
}

// Reserve space for bottom scroll indicator if needed
if endIdx < len(instructions) {
availableLines-- // Reserve space for bottom indicator
endIdx = m.instructionsScrollOffset + availableLines
}

// Add visible instructions
visibleInstructions := instructions[m.instructionsScrollOffset:endIdx]
logLines = append(logLines, visibleInstructions...)

// Add scroll down indicator if not at bottom
if endIdx < len(instructions) {
remaining := len(instructions) - endIdx
scrollDownIndicator := lipgloss.NewStyle().
Foreground(ColorGray).
Render(fmt.Sprintf(" ↓ %d more lines below (use ↑↓ or k/j to scroll)", remaining))
logLines = append(logLines, scrollDownIndicator)
}
} else {
// All instructions fit, no scrolling needed
logLines = append(logLines, instructions...)
}
}

return logLines
}

// buildFilterStatus returns a list of currently applied filters for display when no logs are shown
func (m *DashboardModel) buildFilterStatus() []string {
var filters []string

// Check severity filter
if m.severityFilterActive {
disabledSeverities := []string{}
enabledSeverities := []string{}

severityLevels := []string{"FATAL", "CRITICAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE", "UNKNOWN"}
for _, severity := range severityLevels {
if enabled, exists := m.severityFilter[severity]; exists {
if enabled {
enabledSeverities = append(enabledSeverities, severity)
} else {
disabledSeverities = append(disabledSeverities, severity)
}
}
}

if len(enabledSeverities) > 0 && len(enabledSeverities) < len(severityLevels) {
if len(enabledSeverities) <= 3 {
filters = append(filters, " • Severity: Only showing "+joinWithCommas(enabledSeverities))
} else {
filters = append(filters, " • Severity: Hiding "+joinWithCommas(disabledSeverities))
}
} else if len(enabledSeverities) == 0 {
filters = append(filters, " • Severity: All severities disabled (no logs will show)")
}
}

// Check regex filter
if m.filterRegex != nil {
pattern := m.filterInput.Value()
if pattern == "" && m.filterRegex != nil {
pattern = m.filterRegex.String()
}
if pattern != "" {
filters = append(filters, " • Regex filter: "+pattern)
}
}

// Check search term
if m.searchTerm != "" {
filters = append(filters, " • Search highlight: "+m.searchTerm)
}

// Add instructions for clearing filters if any are active
if len(filters) > 0 {
filters = append(filters, "")
filters = append(filters, " 💡 To clear filters:")
if m.severityFilterActive {
filters = append(filters, " • Ctrl+F → Select All → Enter (enable all severities)")
}
if m.filterRegex != nil {
filters = append(filters, " • / → Backspace/Delete → Enter (clear regex)")
}
if m.searchTerm != "" {
filters = append(filters, " • s → Backspace/Delete → Enter (clear search)")
}
}

return filters
}

// joinWithCommas joins a slice of strings with commas and "and" before the last item
func joinWithCommas(items []string) string {
if len(items) == 0 {
return ""
}
if len(items) == 1 {
return items[0]
}
if len(items) == 2 {
return items[0] + " and " + items[1]
}

result := ""
for i, item := range items {
if i == len(items)-1 {
result += "and " + item
} else {
result += item + ", "
}
}
return result
}

// renderLogScroll renders the scrolling log section
func (m *DashboardModel) renderLogScroll(height int) string {
// Use most of terminal width for logs
Expand Down
Loading
Loading