mirror of
https://github.com/overcuriousity/trace.git
synced 2025-12-20 13:02:21 +00:00
Fix filter-related index bugs across all filterable views
When adding filter support, the display logic correctly used filtered lists but the interaction handlers (Enter, 'v', 'd' keys) and navigation (max_idx calculations) still used unfiltered lists. This caused: - Wrong item selected when filter active - Potential out-of-bounds errors - Inconsistent behavior between display and action Fixed in all affected views: 1. evidence_detail: - Enter key navigation now uses filtered notes - 'v' key (notes modal) now uses filtered notes - Delete handler now uses filtered notes - max_idx navigation now uses filtered notes 2. tag_notes_list: - Enter key navigation now uses filtered notes - Delete handler now uses filtered notes - max_idx navigation now uses filtered notes 3. ioc_notes_list: - Enter key navigation now uses filtered notes - Delete handler now uses filtered notes - max_idx navigation now uses filtered notes 4. tags_list: - Enter key navigation now uses filtered tags - max_idx navigation now uses filtered tags 5. ioc_list: - Enter key navigation now uses filtered IOCs - max_idx navigation now uses filtered IOCs All views now consistently respect active filters across display, navigation, and interaction handlers.
This commit is contained in:
104
trace/tui.py
104
trace/tui.py
@@ -1313,16 +1313,32 @@ class TUI:
|
|||||||
filtered = self._get_filtered_list(self.active_case.evidence, "name", "description")
|
filtered = self._get_filtered_list(self.active_case.evidence, "name", "description")
|
||||||
max_idx = len(filtered) + len(case_notes) - 1
|
max_idx = len(filtered) + len(case_notes) - 1
|
||||||
elif self.current_view == "evidence_detail" and self.active_evidence:
|
elif self.current_view == "evidence_detail" and self.active_evidence:
|
||||||
# Navigate through notes in evidence detail view
|
# Navigate through notes in evidence detail view (respect filter)
|
||||||
max_idx = len(self.active_evidence.notes) - 1
|
notes = self._get_filtered_list(self.active_evidence.notes, "content") if self.filter_query else self.active_evidence.notes
|
||||||
|
max_idx = len(notes) - 1
|
||||||
elif self.current_view == "tags_list":
|
elif self.current_view == "tags_list":
|
||||||
max_idx = len(self.current_tags) - 1
|
# Respect filter for tags
|
||||||
|
tags_to_show = self.current_tags
|
||||||
|
if self.filter_query:
|
||||||
|
q = self.filter_query.lower()
|
||||||
|
tags_to_show = [(tag, count) for tag, count in self.current_tags if q in tag.lower()]
|
||||||
|
max_idx = len(tags_to_show) - 1
|
||||||
elif self.current_view == "tag_notes_list":
|
elif self.current_view == "tag_notes_list":
|
||||||
max_idx = len(self.tag_notes) - 1
|
# Respect filter for notes
|
||||||
|
notes_to_show = self._get_filtered_list(self.tag_notes, "content") if self.filter_query else self.tag_notes
|
||||||
|
max_idx = len(notes_to_show) - 1
|
||||||
elif self.current_view == "ioc_list":
|
elif self.current_view == "ioc_list":
|
||||||
max_idx = len(self.current_iocs) - 1
|
# Respect filter for IOCs
|
||||||
|
iocs_to_show = self.current_iocs
|
||||||
|
if self.filter_query:
|
||||||
|
q = self.filter_query.lower()
|
||||||
|
iocs_to_show = [(ioc, count, ioc_type) for ioc, count, ioc_type in self.current_iocs
|
||||||
|
if q in ioc.lower() or q in ioc_type.lower()]
|
||||||
|
max_idx = len(iocs_to_show) - 1
|
||||||
elif self.current_view == "ioc_notes_list":
|
elif self.current_view == "ioc_notes_list":
|
||||||
max_idx = len(self.ioc_notes) - 1
|
# Respect filter for notes
|
||||||
|
notes_to_show = self._get_filtered_list(self.ioc_notes, "content") if self.filter_query else self.ioc_notes
|
||||||
|
max_idx = len(notes_to_show) - 1
|
||||||
elif self.current_view == "help":
|
elif self.current_view == "help":
|
||||||
# Scrolling help content - just increment scroll_offset directly
|
# Scrolling help content - just increment scroll_offset directly
|
||||||
# Help view uses scroll_offset for scrolling, not selected_index
|
# Help view uses scroll_offset for scrolling, not selected_index
|
||||||
@@ -1347,8 +1363,8 @@ class TUI:
|
|||||||
self.scroll_offset = 0
|
self.scroll_offset = 0
|
||||||
self.filter_query = ""
|
self.filter_query = ""
|
||||||
elif self.current_view == "evidence_detail" and self.active_evidence:
|
elif self.current_view == "evidence_detail" and self.active_evidence:
|
||||||
# Check if a note is selected
|
# Check if a note is selected (respect filter if active)
|
||||||
notes = self.active_evidence.notes
|
notes = self._get_filtered_list(self.active_evidence.notes, "content") if self.filter_query else self.active_evidence.notes
|
||||||
list_h = self.content_h - 5
|
list_h = self.content_h - 5
|
||||||
display_notes = notes[-list_h:] if len(notes) > list_h else notes
|
display_notes = notes[-list_h:] if len(notes) > list_h else notes
|
||||||
|
|
||||||
@@ -1381,9 +1397,13 @@ class TUI:
|
|||||||
self.current_view = "note_detail"
|
self.current_view = "note_detail"
|
||||||
self.filter_query = ""
|
self.filter_query = ""
|
||||||
elif self.current_view == "tags_list":
|
elif self.current_view == "tags_list":
|
||||||
# Enter tag -> show notes with that tag
|
# Enter tag -> show notes with that tag (respect filter if active)
|
||||||
if self.current_tags and self.selected_index < len(self.current_tags):
|
tags_to_show = self.current_tags
|
||||||
tag, _ = self.current_tags[self.selected_index]
|
if self.filter_query:
|
||||||
|
q = self.filter_query.lower()
|
||||||
|
tags_to_show = [(tag, count) for tag, count in self.current_tags if q in tag.lower()]
|
||||||
|
if tags_to_show and self.selected_index < len(tags_to_show):
|
||||||
|
tag, _ = tags_to_show[self.selected_index]
|
||||||
self.current_tag = tag
|
self.current_tag = tag
|
||||||
# Get all notes (case + evidence if in case view, or just evidence if in evidence view)
|
# Get all notes (case + evidence if in case view, or just evidence if in evidence view)
|
||||||
all_notes = self._get_context_notes()
|
all_notes = self._get_context_notes()
|
||||||
@@ -1394,17 +1414,23 @@ class TUI:
|
|||||||
self.selected_index = 0
|
self.selected_index = 0
|
||||||
self.scroll_offset = 0
|
self.scroll_offset = 0
|
||||||
elif self.current_view == "tag_notes_list":
|
elif self.current_view == "tag_notes_list":
|
||||||
# Enter note -> show expanded view
|
# Enter note -> show expanded view (respect filter if active)
|
||||||
if self.tag_notes and self.selected_index < len(self.tag_notes):
|
notes_to_show = self._get_filtered_list(self.tag_notes, "content") if self.filter_query else self.tag_notes
|
||||||
self.current_note = self.tag_notes[self.selected_index]
|
if notes_to_show and self.selected_index < len(notes_to_show):
|
||||||
|
self.current_note = notes_to_show[self.selected_index]
|
||||||
self.previous_view = "tag_notes_list"
|
self.previous_view = "tag_notes_list"
|
||||||
self.current_view = "note_detail"
|
self.current_view = "note_detail"
|
||||||
self.selected_index = 0
|
self.selected_index = 0
|
||||||
self.scroll_offset = 0
|
self.scroll_offset = 0
|
||||||
elif self.current_view == "ioc_list":
|
elif self.current_view == "ioc_list":
|
||||||
# Enter IOC -> show notes with that IOC
|
# Enter IOC -> show notes with that IOC (respect filter if active)
|
||||||
if self.current_iocs and self.selected_index < len(self.current_iocs):
|
iocs_to_show = self.current_iocs
|
||||||
ioc, _, _ = self.current_iocs[self.selected_index]
|
if self.filter_query:
|
||||||
|
q = self.filter_query.lower()
|
||||||
|
iocs_to_show = [(ioc, count, ioc_type) for ioc, count, ioc_type in self.current_iocs
|
||||||
|
if q in ioc.lower() or q in ioc_type.lower()]
|
||||||
|
if iocs_to_show and self.selected_index < len(iocs_to_show):
|
||||||
|
ioc, _, _ = iocs_to_show[self.selected_index]
|
||||||
self.current_ioc = ioc
|
self.current_ioc = ioc
|
||||||
# Get all notes from current context
|
# Get all notes from current context
|
||||||
all_notes = self._get_context_notes()
|
all_notes = self._get_context_notes()
|
||||||
@@ -1415,9 +1441,10 @@ class TUI:
|
|||||||
self.selected_index = 0
|
self.selected_index = 0
|
||||||
self.scroll_offset = 0
|
self.scroll_offset = 0
|
||||||
elif self.current_view == "ioc_notes_list":
|
elif self.current_view == "ioc_notes_list":
|
||||||
# Enter note -> show expanded view
|
# Enter note -> show expanded view (respect filter if active)
|
||||||
if self.ioc_notes and self.selected_index < len(self.ioc_notes):
|
notes_to_show = self._get_filtered_list(self.ioc_notes, "content") if self.filter_query else self.ioc_notes
|
||||||
self.current_note = self.ioc_notes[self.selected_index]
|
if notes_to_show and self.selected_index < len(notes_to_show):
|
||||||
|
self.current_note = notes_to_show[self.selected_index]
|
||||||
self.previous_view = "ioc_notes_list"
|
self.previous_view = "ioc_notes_list"
|
||||||
self.current_view = "note_detail"
|
self.current_view = "note_detail"
|
||||||
self.selected_index = 0
|
self.selected_index = 0
|
||||||
@@ -1524,17 +1551,22 @@ class TUI:
|
|||||||
else:
|
else:
|
||||||
self.view_case_notes()
|
self.view_case_notes()
|
||||||
elif self.current_view == "evidence_detail":
|
elif self.current_view == "evidence_detail":
|
||||||
# Open notes modal with selected note highlighted
|
# Open notes modal with selected note highlighted (respect filter if active)
|
||||||
if self.active_evidence:
|
if self.active_evidence:
|
||||||
notes = self.active_evidence.notes
|
notes = self._get_filtered_list(self.active_evidence.notes, "content") if self.filter_query else self.active_evidence.notes
|
||||||
list_h = self.content_h - 5
|
list_h = self.content_h - 5
|
||||||
display_notes = notes[-list_h:] if len(notes) > list_h else notes
|
display_notes = notes[-list_h:] if len(notes) > list_h else notes
|
||||||
|
|
||||||
if display_notes and self.selected_index < len(display_notes):
|
if display_notes and self.selected_index < len(display_notes):
|
||||||
# Calculate the actual note index in the full list
|
# Get the selected note from filtered/displayed list
|
||||||
note_offset = len(notes) - len(display_notes)
|
selected_note = display_notes[self.selected_index]
|
||||||
actual_note_index = note_offset + self.selected_index
|
# Find its index in the full unfiltered list for highlighting
|
||||||
self.view_evidence_notes(highlight_note_index=actual_note_index)
|
try:
|
||||||
|
actual_note_index = self.active_evidence.notes.index(selected_note)
|
||||||
|
self.view_evidence_notes(highlight_note_index=actual_note_index)
|
||||||
|
except ValueError:
|
||||||
|
# Note not found in full list (shouldn't happen)
|
||||||
|
self.view_evidence_notes()
|
||||||
else:
|
else:
|
||||||
self.view_evidence_notes()
|
self.view_evidence_notes()
|
||||||
|
|
||||||
@@ -2440,13 +2472,13 @@ class TUI:
|
|||||||
self.show_message("Note deleted.")
|
self.show_message("Note deleted.")
|
||||||
|
|
||||||
elif self.current_view == "evidence_detail" and self.active_evidence:
|
elif self.current_view == "evidence_detail" and self.active_evidence:
|
||||||
# Delete individual notes from evidence
|
# Delete individual notes from evidence (respect filter if active)
|
||||||
if not self.active_evidence.notes:
|
if not self.active_evidence.notes:
|
||||||
self.show_message("No notes to delete.")
|
self.show_message("No notes to delete.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Calculate which note to delete based on display (showing last N notes)
|
# Calculate which note to delete based on display (showing last N filtered notes)
|
||||||
notes = self.active_evidence.notes
|
notes = self._get_filtered_list(self.active_evidence.notes, "content") if self.filter_query else self.active_evidence.notes
|
||||||
list_h = self.content_h - 5 # Adjust for header
|
list_h = self.content_h - 5 # Adjust for header
|
||||||
display_notes = notes[-list_h:] if len(notes) > list_h else notes
|
display_notes = notes[-list_h:] if len(notes) > list_h else notes
|
||||||
|
|
||||||
@@ -2499,11 +2531,12 @@ class TUI:
|
|||||||
self.show_message("Error: Note not found.")
|
self.show_message("Error: Note not found.")
|
||||||
|
|
||||||
elif self.current_view == "tag_notes_list":
|
elif self.current_view == "tag_notes_list":
|
||||||
# Delete selected note from tag notes list
|
# Delete selected note from tag notes list (respect filter if active)
|
||||||
if not self.tag_notes or self.selected_index >= len(self.tag_notes):
|
notes_to_show = self._get_filtered_list(self.tag_notes, "content") if self.filter_query else self.tag_notes
|
||||||
|
if not notes_to_show or self.selected_index >= len(notes_to_show):
|
||||||
return
|
return
|
||||||
|
|
||||||
note_to_del = self.tag_notes[self.selected_index]
|
note_to_del = notes_to_show[self.selected_index]
|
||||||
preview = note_to_del.content[:50] + "..." if len(note_to_del.content) > 50 else note_to_del.content
|
preview = note_to_del.content[:50] + "..." if len(note_to_del.content) > 50 else note_to_del.content
|
||||||
if self.dialog_confirm(f"Delete note: '{preview}'?"):
|
if self.dialog_confirm(f"Delete note: '{preview}'?"):
|
||||||
# Find and delete the note from its parent
|
# Find and delete the note from its parent
|
||||||
@@ -2532,11 +2565,12 @@ class TUI:
|
|||||||
self.show_message("Error: Note not found.")
|
self.show_message("Error: Note not found.")
|
||||||
|
|
||||||
elif self.current_view == "ioc_notes_list":
|
elif self.current_view == "ioc_notes_list":
|
||||||
# Delete selected note from IOC notes list
|
# Delete selected note from IOC notes list (respect filter if active)
|
||||||
if not self.ioc_notes or self.selected_index >= len(self.ioc_notes):
|
notes_to_show = self._get_filtered_list(self.ioc_notes, "content") if self.filter_query else self.ioc_notes
|
||||||
|
if not notes_to_show or self.selected_index >= len(notes_to_show):
|
||||||
return
|
return
|
||||||
|
|
||||||
note_to_del = self.ioc_notes[self.selected_index]
|
note_to_del = notes_to_show[self.selected_index]
|
||||||
preview = note_to_del.content[:50] + "..." if len(note_to_del.content) > 50 else note_to_del.content
|
preview = note_to_del.content[:50] + "..." if len(note_to_del.content) > 50 else note_to_del.content
|
||||||
if self.dialog_confirm(f"Delete note: '{preview}'?"):
|
if self.dialog_confirm(f"Delete note: '{preview}'?"):
|
||||||
# Find and delete the note from its parent
|
# Find and delete the note from its parent
|
||||||
|
|||||||
Reference in New Issue
Block a user