bug fixes

This commit is contained in:
overcuriousity
2025-12-11 23:07:19 +01:00
parent 4f9522181e
commit e37597c315
2 changed files with 146 additions and 36 deletions

View File

@@ -39,9 +39,9 @@ def quick_add_note(content: str):
note.extract_iocs() # Extract IOCs from content note.extract_iocs() # Extract IOCs from content
# Try signing if enabled # Try signing if enabled
signature = None
if settings.get("pgp_enabled", True): if settings.get("pgp_enabled", True):
gpg_key_id = settings.get("gpg_key_id", None) gpg_key_id = settings.get("gpg_key_id", None)
if gpg_key_id:
signature = Crypto.sign_content(f"Hash: {note.content_hash}\nContent: {note.content}", key_id=gpg_key_id) signature = Crypto.sign_content(f"Hash: {note.content_hash}\nContent: {note.content}", key_id=gpg_key_id)
if signature: if signature:
note.signature = signature note.signature = signature

View File

@@ -490,14 +490,72 @@ class TUI:
self.stdscr.attroff(note_color) self.stdscr.attroff(note_color)
y_pos += 1 y_pos += 1
# Split screen between case notes and evidence
# Allocate space: half for case notes, half for evidence (if both exist)
available_space = self.content_h - 5
case_notes = self.active_case.notes
evidence_list = self._get_filtered_list(self.active_case.evidence, "name", "description")
# Determine context: are we selecting notes or evidence?
# If there are case notes, treat indices 0 to len(notes)-1 as notes
# If there is evidence, treat indices len(notes) to len(notes)+len(evidence)-1 as evidence
total_items = len(case_notes) + len(evidence_list)
# Determine what's selected
selecting_note = self.selected_index < len(case_notes)
# Case Notes section
if case_notes:
if y_pos < self.height - 3:
self.stdscr.attron(curses.color_pair(5) | curses.A_BOLD)
self.stdscr.addstr(y_pos, 2, "▪ Case Notes")
self.stdscr.attroff(curses.color_pair(5) | curses.A_BOLD)
self.stdscr.attron(curses.color_pair(6) | curses.A_DIM)
self.stdscr.addstr(y_pos, 16, f"({len(case_notes)} notes)")
self.stdscr.attroff(curses.color_pair(6) | curses.A_DIM)
y_pos += 1
# Calculate space for case notes
notes_space = min(len(case_notes), available_space // 2) if evidence_list else available_space
# Update scroll position if needed
self._update_scroll(total_items)
# Display notes
for i in range(notes_space):
note_idx = self.scroll_offset + i
if note_idx >= len(case_notes):
break
note = case_notes[note_idx]
y = y_pos + 1 + i
# Check if we're out of bounds
if y >= self.height - 3:
break
# Format note content
note_content = note.content.replace('\n', ' ').replace('\r', ' ')
display_str = f"- {note_content}"
display_str = self._safe_truncate(display_str, self.width - 6)
# Highlight if selected
if note_idx == self.selected_index:
self.stdscr.attron(curses.color_pair(1))
self.stdscr.addstr(y, 4, display_str)
self.stdscr.attroff(curses.color_pair(1))
else:
self.stdscr.addstr(y, 4, display_str)
y_pos += notes_space + 1
# Evidence section header # Evidence section header
y_pos += 1 y_pos += 1
if y_pos < self.height - 3:
self.stdscr.attron(curses.color_pair(5) | curses.A_BOLD) self.stdscr.attron(curses.color_pair(5) | curses.A_BOLD)
self.stdscr.addstr(y_pos, 2, "▪ Evidence") self.stdscr.addstr(y_pos, 2, "▪ Evidence")
self.stdscr.attroff(curses.color_pair(5) | curses.A_BOLD) self.stdscr.attroff(curses.color_pair(5) | curses.A_BOLD)
evidence_list = self._get_filtered_list(self.active_case.evidence, "name", "description")
# Show count # Show count
self.stdscr.attron(curses.color_pair(6) | curses.A_DIM) self.stdscr.attron(curses.color_pair(6) | curses.A_DIM)
self.stdscr.addstr(y_pos, 14, f"({len(evidence_list)} items)") self.stdscr.addstr(y_pos, 14, f"({len(evidence_list)} items)")
@@ -506,24 +564,30 @@ class TUI:
y_pos += 1 y_pos += 1
if not evidence_list: if not evidence_list:
# Check if we have space to display the message
if y_pos + 2 < self.height - 2:
self.stdscr.attron(curses.color_pair(3)) self.stdscr.attron(curses.color_pair(3))
self.stdscr.addstr(y_pos + 1, 4, "┌─ No evidence items") self.stdscr.addstr(y_pos + 1, 4, "┌─ No evidence items")
self.stdscr.addstr(y_pos + 2, 4, "└─ Press 'N' to add evidence") self.stdscr.addstr(y_pos + 2, 4, "└─ Press 'N' to add evidence")
self.stdscr.attroff(curses.color_pair(3)) self.stdscr.attroff(curses.color_pair(3))
else: else:
# Scrolling for evidence list # Scrolling for evidence list
# List starts at y=7 # Calculate remaining space
list_h = self.content_h - 5 # 7 is header offset remaining_space = self.content_h - (y_pos - 2)
if list_h < 1: list_h = 1 list_h = max(1, remaining_space)
self._update_scroll(len(evidence_list)) self._update_scroll(total_items)
for i in range(list_h): for i in range(list_h):
idx = self.scroll_offset + i # Evidence indices start after case notes
if idx >= len(evidence_list): break evidence_idx = self.scroll_offset + i - len(case_notes)
if evidence_idx < 0 or evidence_idx >= len(evidence_list):
continue
ev = evidence_list[idx] ev = evidence_list[evidence_idx]
y = y_pos + 2 + i y = y_pos + 2 + i
if y >= self.height - 3: # Don't overflow into status bar
break
note_count = len(ev.notes) note_count = len(ev.notes)
@@ -563,7 +627,9 @@ class TUI:
# Truncate safely # Truncate safely
base_display = self._safe_truncate(display_str, self.width - 6) base_display = self._safe_truncate(display_str, self.width - 6)
if idx == self.selected_index: # Check if this evidence item is selected
item_idx = len(case_notes) + evidence_idx
if item_idx == self.selected_index:
# Highlighted selection # Highlighted selection
self.stdscr.attron(curses.color_pair(1)) self.stdscr.attron(curses.color_pair(1))
self.stdscr.addstr(y, 4, base_display) self.stdscr.addstr(y, 4, base_display)
@@ -1082,8 +1148,10 @@ class TUI:
filtered = self._get_filtered_list(self.cases, "case_number", "name") filtered = self._get_filtered_list(self.cases, "case_number", "name")
max_idx = len(filtered) - 1 max_idx = len(filtered) - 1
elif self.current_view == "case_detail" and self.active_case: elif self.current_view == "case_detail" and self.active_case:
# Total items = case notes + evidence
case_notes = self.active_case.notes
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) - 1 max_idx = len(case_notes) + len(filtered) - 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
max_idx = len(self.active_evidence.notes) - 1 max_idx = len(self.active_evidence.notes) - 1
@@ -1120,12 +1188,23 @@ class TUI:
self.filter_query = "" # Reset filter on view change self.filter_query = "" # Reset filter on view change
elif self.current_view == "case_detail": elif self.current_view == "case_detail":
if self.active_case: if self.active_case:
case_notes = self.active_case.notes
filtered = self._get_filtered_list(self.active_case.evidence, "name", "description") filtered = self._get_filtered_list(self.active_case.evidence, "name", "description")
if filtered:
self.active_evidence = filtered[self.selected_index] # Check if selecting a note or evidence
if self.selected_index < len(case_notes):
# Selected a note - show note detail view
self.current_note = case_notes[self.selected_index]
self.previous_view = "case_detail"
self.current_view = "note_detail"
self.filter_query = ""
elif filtered and self.selected_index - len(case_notes) < len(filtered):
# Selected evidence - navigate to evidence detail
evidence_idx = self.selected_index - len(case_notes)
self.active_evidence = filtered[evidence_idx]
self.current_view = "evidence_detail" self.current_view = "evidence_detail"
self.selected_index = 0 self.selected_index = 0
self.filter_query = "" # Reset filter 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
if self.current_tags and self.selected_index < len(self.current_tags): if self.current_tags and self.selected_index < len(self.current_tags):
@@ -1143,6 +1222,7 @@ class TUI:
# Enter note -> show expanded view # Enter note -> show expanded view
if self.tag_notes and self.selected_index < len(self.tag_notes): if self.tag_notes and self.selected_index < len(self.tag_notes):
self.current_note = self.tag_notes[self.selected_index] self.current_note = self.tag_notes[self.selected_index]
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
@@ -1163,6 +1243,7 @@ class TUI:
# Enter note -> show expanded view # Enter note -> show expanded view
if self.ioc_notes and self.selected_index < len(self.ioc_notes): if self.ioc_notes and self.selected_index < len(self.ioc_notes):
self.current_note = self.ioc_notes[self.selected_index] self.current_note = self.ioc_notes[self.selected_index]
self.previous_view = "ioc_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
@@ -1175,7 +1256,8 @@ class TUI:
self.selected_index = 0 self.selected_index = 0
self.scroll_offset = 0 self.scroll_offset = 0
elif self.current_view == "note_detail": elif self.current_view == "note_detail":
self.current_view = "tag_notes_list" # Return to the view we came from
self.current_view = getattr(self, 'previous_view', 'case_detail')
self.current_note = None self.current_note = None
self.selected_index = 0 self.selected_index = 0
self.scroll_offset = 0 self.scroll_offset = 0
@@ -1291,14 +1373,26 @@ class TUI:
self.show_message(f"Active Case: {case.case_number}") self.show_message(f"Active Case: {case.case_number}")
elif self.current_view == "case_detail" and self.active_case: elif self.current_view == "case_detail" and self.active_case:
case_notes = self.active_case.notes
filtered = self._get_filtered_list(self.active_case.evidence, "name", "description") filtered = self._get_filtered_list(self.active_case.evidence, "name", "description")
if filtered:
ev = filtered[self.selected_index] # Only allow setting active for evidence, not notes
if self.selected_index < len(case_notes):
# Selected a note - set case as active (not evidence)
self.state_manager.set_active(case_id=self.active_case.case_id, evidence_id=None)
self.global_active_case_id = self.active_case.case_id
self.global_active_evidence_id = None
self.show_message(f"Active: Case {self.active_case.case_number}")
elif filtered and self.selected_index - len(case_notes) < len(filtered):
# Selected evidence - set it as active
evidence_idx = self.selected_index - len(case_notes)
ev = filtered[evidence_idx]
self.state_manager.set_active(case_id=self.active_case.case_id, evidence_id=ev.evidence_id) self.state_manager.set_active(case_id=self.active_case.case_id, evidence_id=ev.evidence_id)
self.global_active_case_id = self.active_case.case_id self.global_active_case_id = self.active_case.case_id
self.global_active_evidence_id = ev.evidence_id self.global_active_evidence_id = ev.evidence_id
self.show_message(f"Active: {ev.name}") self.show_message(f"Active: {ev.name}")
else: else:
# Nothing selected - set case as active
self.state_manager.set_active(case_id=self.active_case.case_id, evidence_id=None) self.state_manager.set_active(case_id=self.active_case.case_id, evidence_id=None)
self.global_active_case_id = self.active_case.case_id self.global_active_case_id = self.active_case.case_id
self.global_active_evidence_id = None self.global_active_evidence_id = None
@@ -1724,6 +1818,7 @@ class TUI:
x = (self.width - w) // 2 x = (self.width - w) // 2
win = curses.newwin(h, w, y, x) win = curses.newwin(h, w, y, x)
win.keypad(1) # Enable keypad mode for arrow keys
while True: while True:
win.clear() win.clear()
@@ -1820,6 +1915,7 @@ class TUI:
x = (self.width - w) // 2 x = (self.width - w) // 2
win = curses.newwin(h, w, y, x) win = curses.newwin(h, w, y, x)
win.keypad(1) # Enable keypad mode for arrow keys
scroll_offset = 0 scroll_offset = 0
while True: while True:
@@ -2064,9 +2160,25 @@ class TUI:
self.show_message(f"Case {case_to_del.case_number} deleted.") self.show_message(f"Case {case_to_del.case_number} deleted.")
elif self.current_view == "case_detail" and self.active_case: elif self.current_view == "case_detail" and self.active_case:
# Determine if we're deleting a note or evidence based on selected index
case_notes = self.active_case.notes
filtered = self._get_filtered_list(self.active_case.evidence, "name", "description") filtered = self._get_filtered_list(self.active_case.evidence, "name", "description")
if filtered:
ev_to_del = filtered[self.selected_index] # Check if selecting a note (indices 0 to len(notes)-1)
if self.selected_index < len(case_notes):
# Delete case note
note_to_del = case_notes[self.selected_index]
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}'?"):
self.active_case.notes.remove(note_to_del)
self.storage.save_data()
self.selected_index = 0
self.scroll_offset = 0
self.show_message("Note deleted.")
elif filtered and self.selected_index - len(case_notes) < len(filtered):
# Delete evidence (adjust index by subtracting case notes count)
evidence_idx = self.selected_index - len(case_notes)
ev_to_del = filtered[evidence_idx]
if self.dialog_confirm(f"Delete Evidence {ev_to_del.name}?"): if self.dialog_confirm(f"Delete Evidence {ev_to_del.name}?"):
self.storage.delete_evidence(self.active_case.case_id, ev_to_del.evidence_id) self.storage.delete_evidence(self.active_case.case_id, ev_to_del.evidence_id)
# Check active state # Check active state
@@ -2074,9 +2186,7 @@ class TUI:
# Fallback to case active # Fallback to case active
self.state_manager.set_active(self.active_case.case_id, None) self.state_manager.set_active(self.active_case.case_id, None)
self.global_active_evidence_id = None self.global_active_evidence_id = None
# Refresh (in-memory update was done by storage usually? No, storage reloads or we reload) # Refresh
# We need to reload active_case evidence list or trust storage.cases
# It's better to reload from storage to be safe
updated_case = self.storage.get_case(self.active_case.case_id) updated_case = self.storage.get_case(self.active_case.case_id)
if updated_case: if updated_case:
self.active_case = updated_case self.active_case = updated_case