diff options
| author | srdusr <[email protected]> | 2026-02-17 19:18:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2026-02-17 19:18:00 +0200 |
| commit | b8e5d5ea37cc64ffa05352c3fb3130ca59471935 (patch) | |
| tree | 18357a1a92214dec735b7422a66d9293fa64b565 /cmd | |
| parent | b33c1e5cc7086da46b36122aa5645ccee26be69f (diff) | |
| download | mitmux-b8e5d5ea37cc64ffa05352c3fb3130ca59471935.tar.gz mitmux-b8e5d5ea37cc64ffa05352c3fb3130ca59471935.zip | |
Flagged marker for history entries
Last of the "should build soon" items from the Burp/ZAP/Caido gap
research - Burp's row highlighting and Caido's Findings both serve
the same real workflow: mark something interesting mid-engagement,
revisit later. Scoped to a boolean flag (★) rather than full free-text
notes/comments, which would need their own text-input overlay for
comparatively modest extra value over a simple marker - tracked as a
real follow-up in PLAN.md, not dropped silently.
internal/store: history gains a flagged column (migrated in for
existing databases the same way source was) plus Store.SetFlagged and
Summary/Entry.Flagged. Search's structured-filter layer (added last
commit for status:/source:) gains flagged:true/false alongside them -
extractStructured already existed for exactly this kind of "pull it out
before it reaches FTS5" filter. internal/ipc gains a "set_flagged"
request. cmd/mitmux: 'f' toggles the flag on the selected history row
(applied optimistically to local state, persisted async - a drift
between local and server state on failure is an acceptable trade-off
for a marker this low-stakes), shown as a ★ column in the list and in
the detail view's title.
store_test.go covers the flagged: parsing (true/false spellings, and
a "looks like it but isn't" case - flagged:maybe - falling through as
literal search text, matching the existing pattern for status:).
Verified live: toggling 'f' shows the star immediately, flagged:true
correctly filtered to just that entry, and a direct SQLite check
confirmed the flag actually persisted to the database (flagged=1),
not just reflected in local UI state.
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/mitmux/main.go | 52 |
1 files changed, 45 insertions, 7 deletions
diff --git a/cmd/mitmux/main.go b/cmd/mitmux/main.go index aa659cd..1f0177c 100644 --- a/cmd/mitmux/main.go +++ b/cmd/mitmux/main.go @@ -171,10 +171,11 @@ type model struct { func newModel(client *ipc.Client, subCh <-chan store.Summary, socketPath string) *model { columns := []table.Column{ + {Title: "", Width: 1}, {Title: "ID", Width: 5}, {Title: "Method", Width: 7}, - {Title: "Host", Width: 28}, - {Title: "Path", Width: 32}, + {Title: "Host", Width: 27}, + {Title: "Path", Width: 31}, {Title: "Status", Width: 6}, {Title: "Size", Width: 10}, {Title: "Time", Width: 8}, @@ -194,7 +195,7 @@ func newModel(client *ipc.Client, subCh <-chan store.Summary, socketPath string) si := textinput.New() si.Prompt = "/" - si.Placeholder = "search - plain text, host:x, status:404 / status:4xx / status:>=400, source:repeater" + si.Placeholder = "search - plain text, host:x, status:404/4xx/>=400, source:repeater, flagged:true" rulesCols := []table.Column{ {Title: "On", Width: 3}, @@ -286,6 +287,20 @@ func (m *model) loadStatus() tea.Msg { return statusLoadedMsg{status: s, err: err} } +type flagSetMsg struct { + err error +} + +// setFlagged persists a flag toggle the caller has already applied +// optimistically to local state. On failure, just surfaces the error - +// the local/server state can drift until the next list reload, which +// is an acceptable trade-off for a marker this low-stakes. +func (m *model) setFlagged(id int64, flagged bool) tea.Cmd { + return func() tea.Msg { + return flagSetMsg{err: m.client.SetFlagged(id, flagged)} + } +} + func (m *model) loadList() tea.Msg { var entries []store.Summary var err error @@ -563,6 +578,12 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return m, nil + case flagSetMsg: + if msg.err != nil { + m.statusMsg = "flag error: " + msg.err.Error() + } + return m, nil + case detailLoadedMsg: if msg.err != nil { m.statusMsg = "get error: " + msg.err.Error() @@ -681,6 +702,12 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.statusMsg = "" return m, m.loadDetail(m.entries[row].ID, "intruder") } + case "f": + if row := m.table.Cursor(); row >= 0 && row < len(m.entries) { + m.entries[row].Flagged = !m.entries[row].Flagged + m.table.SetRows(rowsFor(m.entries)) + return m, m.setFlagged(m.entries[row].ID, m.entries[row].Flagged) + } case "/": m.searching = true m.searchInput.SetValue(m.query) @@ -1022,8 +1049,10 @@ func (m *model) helpView() string { "enter view request/response detail", "r open in Repeater", "i open in Intruder", + "f toggle flag (★ mark this, revisit later)", "/ search: plain text, host:value, AND/OR/NOT,", - " status:404 / status:4xx / status:>=400, source:repeater", + " status:404 / status:4xx / status:>=400,", + " source:repeater, flagged:true", "esc clear active search filter", "m match-and-replace rules", "q quit", @@ -1086,9 +1115,9 @@ func (m *model) listView() string { b.WriteString(statusStyle.Render(m.statusMsg)) b.WriteString("\n") } - help := "↑/↓ navigate · enter view · r repeater · i intruder · / search · m rules · q quit" + help := "↑/↓ navigate · enter view · r repeater · i intruder · f flag · / search · m rules · q quit" if m.query != "" { - help = "↑/↓ navigate · enter view · r repeater · i intruder · / search · m rules · esc clear filter · q quit" + help = "↑/↓ navigate · enter view · r repeater · i intruder · f flag · / search · m rules · esc clear filter · q quit" } b.WriteString(helpStyle.Render(help)) return b.String() @@ -1100,7 +1129,11 @@ func (m *model) detailView() string { b.WriteString("loading...\n") return b.String() } - title := fmt.Sprintf(" #%d %s %s%s -> %d ", m.detail.ID, m.detail.Method, m.detail.Host, m.detail.Path, m.detail.StatusCode) + flagMark := "" + if m.detail.Flagged { + flagMark = "★ " + } + title := fmt.Sprintf(" %s#%d %s %s%s -> %d ", flagMark, m.detail.ID, m.detail.Method, m.detail.Host, m.detail.Path, m.detail.StatusCode) b.WriteString(titleStyle.Render(title)) b.WriteString("\n") @@ -1344,8 +1377,13 @@ func rowsFor(entries []store.Summary) []table.Row { if e.StatusCode == 0 { status = "ERR" } + flag := "" + if e.Flagged { + flag = "★" + } size := fmt.Sprintf("%s/%s", humanBytes(e.ReqSize), humanBytes(e.RespSize)) rows[i] = table.Row{ + flag, fmt.Sprintf("%d", e.ID), e.Method, e.Host, |