Skip to content
Open
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
2 changes: 1 addition & 1 deletion cmd/gonzo/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func runApp(cmd *cobra.Command, args []string) error {
freqMemory := memory.NewFrequencyMemory(cfg.MemorySize)

// Initialize TUI model with components
dashboard := tui.NewDashboardModel(cfg.LogBuffer, cfg.UpdateInterval, cfg.AIModel, textAnalyzer.GetStopWords(), cfg.ReverseScrollWheel)
dashboard := tui.NewDashboardModel(cfg.LogBuffer, cfg.UpdateInterval, cfg.AIModel, textAnalyzer.GetStopWords(), cfg.ReverseScrollWheel, cfg.UseLogTime)
if versionChecker != nil {
dashboard.SetVersionChecker(versionChecker)
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/gonzo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type Config struct {
Format string `mapstructure:"format"`
DisableVersionCheck bool `mapstructure:"disable-version-check"`
ReverseScrollWheel bool `mapstructure:"reverse-scroll-wheel"`
UseLogTime bool `mapstructure:"use-log-time"`
}

var (
Expand Down Expand Up @@ -181,6 +182,7 @@ func init() {
rootCmd.Flags().String("format", "", "Log format to use (auto-detect if not specified). Can be: otlp, json, text, or a custom format name from ~/.config/gonzo/formats/")
rootCmd.Flags().Bool("disable-version-check", false, "Disable automatic version checking on startup")
rootCmd.Flags().Bool("reverse-scroll-wheel", false, "Reverse scroll wheel direction (natural scrolling)")
rootCmd.Flags().Bool("use-log-time", false, "Use original log timestamps instead of receive time for heatmap and display (falls back to receive time if log has no timestamp)")

// Bind flags to viper
viper.BindPFlag("memory-size", rootCmd.Flags().Lookup("memory-size"))
Expand Down Expand Up @@ -209,6 +211,7 @@ func init() {
viper.BindPFlag("format", rootCmd.Flags().Lookup("format"))
viper.BindPFlag("disable-version-check", rootCmd.Flags().Lookup("disable-version-check"))
viper.BindPFlag("reverse-scroll-wheel", rootCmd.Flags().Lookup("reverse-scroll-wheel"))
viper.BindPFlag("use-log-time", rootCmd.Flags().Lookup("use-log-time"))

// Add version command
rootCmd.AddCommand(versionCmd)
Expand Down
15 changes: 14 additions & 1 deletion internal/tui/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,17 +133,30 @@ func (m *DashboardModel) renderStatusLine() string {
}
}

// Add timestamp mode indicator
var timestampMode string
if m.useLogTime {
if narrow {
timestampMode = "⏱Log"
} else {
timestampMode = "⏱ Log Time"
}
}

// Add branding (show unless terminal is very narrow)
branding := ""
if m.width >= 30 { // Show branding unless terminal is very narrow
branding = m.renderGonzoBranding()
}

// Combine status info, version update, and branding
// Combine status info, timestamp mode, version update, and branding
var rightParts []string
if statusInfo != "" {
rightParts = append(rightParts, statusInfo)
}
if timestampMode != "" {
rightParts = append(rightParts, timestampMode)
}
if versionUpdateInfo != "" {
rightParts = append(rightParts, versionUpdateInfo)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/tui/formatting.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (

// formatLogEntry formats a log entry with colors
func (m *DashboardModel) formatLogEntry(entry LogEntry, availableWidth int, isSelected bool) string {
// Use receive time for display
timestamp := entry.Timestamp.Format("15:04:05")
// Use getDisplayTimestamp to respect the useLogTime setting
timestamp := m.getDisplayTimestamp(entry).Format("15:04:05")

// If selected, apply selection style to entire row
if isSelected {
Expand Down
1 change: 1 addition & 0 deletions internal/tui/modal_help.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ ACTIONS:
f - Open fullscreen log viewer modal
Space - Pause/unpause UI updates
c - Toggle Host/Service columns in log view
T - Toggle timestamp mode (Log Time / Receive Time)
r - Reset all data (manual reset)
u/U - Cycle update intervals (forward/backward)
i - Show comprehensive statistics modal
Expand Down
4 changes: 3 additions & 1 deletion internal/tui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ type DashboardModel struct {
maxLogBuffer int
updateInterval time.Duration
reverseScrollWheel bool
useLogTime bool // Use OrigTimestamp instead of Timestamp for heatmap/display

// Filter
filterInput textinput.Model
Expand Down Expand Up @@ -244,7 +245,7 @@ func initializeDrain3BySeverity() map[string]*Drain3Manager {
}

// NewDashboardModel creates a new dashboard model with stop words
func NewDashboardModel(maxLogBuffer int, updateInterval time.Duration, aiModel string, stopWords map[string]bool, reverseScrollWheel bool) *DashboardModel {
func NewDashboardModel(maxLogBuffer int, updateInterval time.Duration, aiModel string, stopWords map[string]bool, reverseScrollWheel bool, useLogTime bool) *DashboardModel {
filterInput := textinput.New()
filterInput.Placeholder = "Filter logs by message or attributes (regex supported)..."
filterInput.CharLimit = 200
Expand Down Expand Up @@ -285,6 +286,7 @@ func NewDashboardModel(maxLogBuffer int, updateInterval time.Duration, aiModel s
maxLogBuffer: maxLogBuffer,
updateInterval: updateInterval,
reverseScrollWheel: reverseScrollWheel,
useLogTime: useLogTime,
filterInput: filterInput,
searchInput: searchInput,
chatInput: chatInput,
Expand Down
10 changes: 9 additions & 1 deletion internal/tui/navigation.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,15 @@ func (m *DashboardModel) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.showColumns = !m.showColumns
return m, nil
}


case "T":
// Toggle timestamp mode (Log Time vs Receive Time)
if !m.showModal && !m.filterActive && !m.searchActive && !m.showSeverityFilterModal {
m.useLogTime = !m.useLogTime
m.rebuildHeatmap()
return m, nil
}

case "i":
// Toggle statistics modal
if !m.showModal && !m.filterActive && !m.searchActive && !m.showHelp && !m.showPatternsModal && !m.showModelSelectionModal && !m.showSeverityFilterModal {
Expand Down
24 changes: 21 additions & 3 deletions internal/tui/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -847,11 +847,29 @@ func (m *DashboardModel) matchesFilter(entry LogEntry) bool {
return false
}

// getDisplayTimestamp returns the appropriate timestamp based on useLogTime setting
// Falls back to receive time (Timestamp) if OrigTimestamp is not available
func (m *DashboardModel) getDisplayTimestamp(entry LogEntry) time.Time {
if m.useLogTime && !entry.OrigTimestamp.IsZero() {
return entry.OrigTimestamp
}
return entry.Timestamp
}

// rebuildHeatmap clears and rebuilds the heatmap from all log entries
// Used when toggling timestamp mode to recalculate with the new timestamp source
func (m *DashboardModel) rebuildHeatmap() {
m.heatmapData = make([]HeatmapMinute, 0)
for _, entry := range m.allLogEntries {
m.updateHeatmapData(entry)
}
}

// updateHeatmapData updates the minute-by-minute heatmap data for the counts modal
func (m *DashboardModel) updateHeatmapData(entry LogEntry) {
// Now entry.Timestamp is always the receive time, so we can use it directly
// This ensures the heatmap shows when logs were received, not their original timestamps
entryTime := entry.Timestamp.Truncate(time.Minute)
// Use getDisplayTimestamp to respect the useLogTime setting
// This allows users to choose between log time and receive time
entryTime := m.getDisplayTimestamp(entry).Truncate(time.Minute)

// Find or create the heatmap minute entry
var targetMinute *HeatmapMinute
Expand Down