feat(gui): search the connections table by column, block an address from a row - #103
Merged
Merged
Conversation
…tion row Two right-click actions on the Connections table. "Block this IP address" adds the row's address to the blocking field; "Leave this process alone" excludes that process from impairment by adding !name to the target. Both APPEND. They are used one row at a time, so replacing would discard the address blocked a moment ago and make the second click look broken. A repeat is dropped rather than doubled. With an empty target, !name is not a narrowing but a flip: "impair everything" becomes "impair everything except this", which is the case that entry exists for. The appending lives in matchers.add_term, not in the GUI, because splitting on commas is a question about the filter syntax (convention 10). It shipped the comma-escape bug for about ten minutes - re-joining without re-escaping emits a regex's literal comma as a separator - which is the same failure a property test once found in Matcher.describe. Pinned by a test now. Convention 15 holds and is finally guarded: the existing test was named "feeds the targeting FIELDS" and checked only the form, so a row action pushed straight into a running engine would have kept the suite green. The new guard asserts the engine is untouched before Apply and changed after it, and is mutation-checked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Plain text works exactly as before. A term may now name its column instead: port:443, ip:10.0.0.0/8, pid:>4000, scoped:yes, dropped:>0. Several terms narrow together, and a "?" next to the box opens the cheat sheet. The search used to match one substring against a blob of process, protocol, direction, addresses and ports - 6 of the table's 17 columns. A PID was on screen and could not be searched for, and there was no way to ask for "only the rows this session impaired" or "only the rows that dropped something", which are the questions a tester has when the table holds a hundred thousand flows. Values are parsed by matchers.py, so this is the same mini-language as the form fields rather than a second syntax to learn and maintain (convention 10). The query compiles ONCE into a list of predicates: parsing per row would put the expression parser on the path of every row on every keystroke. Half-typed queries match nothing instead of raising, and an unknown field falls back to plain text, because http://x is a URL someone pasted and not a field name. The first version silently returned zero rows for proto:tcp. A process matcher judges (pid, name), so passing a text column positionally handed it to pid, where a name cannot be evaluated - and an unevaluable term matches nothing without complaining. Now pinned by a test and a mutation entry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two new row actions and their shared helper were written as App methods and pushed gui/app.py from 1299 to 1319 logic lines, which turned the size ratchet red. The ratchet's answer is to put code where it belongs rather than to raise the number, and a Connections row action belongs to the Connections page. app.py is back to its previous size and conns.py, at less than half the ceiling, absorbs three short functions that already reach into app the way the rest of that page does. This also fixes a commit that should not have been made: the previous one landed with test_code_shape red, because the verification pipeline piped pytest into tail and therefore reported tail's exit code rather than pytest's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The docstring pinning the comma-escape regression contained a literal backslash comma in a non-raw string, which Python 3.14 reports as an invalid escape sequence. The suite ran green with two SyntaxWarnings, and a warning nobody fixes is a warning nobody reads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three improvements to the Connections tab, plus the guard that should have been there first.
The search box can name a column
Plain text works exactly as before. A term may now name its column instead:
Several terms narrow together (
proc:chrome port:443 dropped:>0), and a new "?" button next to the box opens a cheat sheet listing every column name with examples.Why: the search matched one substring against a blob of process, protocol, direction, addresses and ports - 6 of the table's 17 columns. A PID was on screen and could not be searched for, and there was no way to ask for "only the rows this session impaired" or "only the rows that dropped something", which are the questions a tester has when the table holds a hundred thousand flows.
Values are parsed by
matchers.py, so this is the same mini-language as the form fields rather than a second syntax to learn and to maintain (convention 10). The query compiles once into a list of predicates - parsing per row would put the expression parser on the path of every row on every keystroke. Half-typed queries match nothing instead of raising, and an unknown field falls back to plain text, becausehttp://xis a URL someone pasted and not a field name.🔴 The first version silently returned zero rows for
proto:tcp. A process matcher judges(pid, name), so passing a text column positionally handed it topid, where a name cannot be evaluated - and an unevaluable term matches nothing without complaining. The search looked like it worked. Now pinned by a test and a mutation entry.Two new right-click actions
"Block this IP address" adds the row's address to the blocking field. "Leave this process alone" excludes that process from impairment by adding
!nameto the target.Both append. They are used one row at a time, so replacing would discard the address blocked a moment ago and make the second click look broken. A repeat is dropped rather than doubled. With an empty target,
!nameis not a narrowing but a flip: "impair everything" becomes "impair everything except this", which is the case that entry exists for.The appending lives in
matchers.add_term, not in the GUI, because splitting on commas is a question about the filter syntax. 🔴 It shipped the comma-escape bug for about ten minutes - re-joining without re-escaping emits a regex's literal comma as a separator - which is the same failure a property test once found inMatcher.describe, reintroduced in a new function on the day the old one was cited as solved. Pinned by a test.The no-auto-apply rule finally has a guard
The question that started this was whether the row actions apply themselves. They do not - measured on a live engine:
target_activeanddst_activestayFalseafter both actions and only flip after "Apply changes".But the only test covering that area was named "feeds the targeting FIELDS" and checked only the form. Pushing a row action straight into a running engine would have kept the whole suite green. The new guard asserts both directions - untouched engine before Apply, changed after - because only the pair separates "did not apply" from "did not work at all". Mutation-checked.
One design decision made by a guard
The row actions were written as
Appmethods first and pushedgui/app.pyfrom 1299 to 1319 logic lines, turning the size ratchet red. Its answer is to put code where it belongs rather than to raise the number, so they moved to the Connections page, where they belong. That is the first time that guard has changed a design decision rather than just reported one.Testing
python -m pytest tests: 929 passed, exit 0.python smoke_gui.py: OK. Mutation registry: 11 caught, 0 survived, canary BROKEN.Every behaviour added here is mutation-checked: the search's text-column handling, the row actions reaching the engine, and the comma escape.
Not in this PR
The column chooser is still to come. It needs a decision first: a menu of checkbuttons would need
add_checkbuttonadded to the tkinter double (which today knows onlyadd_commandandadd_separator, so the feature would ship untested), or it becomes a small dialog instead.🤖 Generated with Claude Code