inbound-antispam-panel: Filter lists, kind column, matches, note on a sheet
Follows the design commit before it (owner's words quoted there). The lists' tables are Value, Kind, Matched and Remove. The kind tag opens the entry's sheet: note, day added, how often it decided a message in the days the log keeps, and the last match. Matches are read off the journal on each view, nothing is stored (inbound.Lists.Usage, store.ListSpamListHits). A row counts for an entry when the list decided the message - a rejected row whose reply is the deny rule's message, an accepted row whose verdict carries the allow rule's symbol for that kind - and the entry fits the row's sender or client address the way rspamd matches it: an address whole, a domain as the sender's domain, a network containing the client (an IPv4-mapped address as IPv4). A failure to count shows the lists with "never" and logs why. For that the journal milter keeps the lists' own symbols ahead of its six-symbol cap: rspamd writes symbols heaviest first, so an allow entry's -100 came last and was cut behind any six others. The plan gains "Filter list usage": what counting from the journal implies, and - the owner asked for it in writing - the conditions under which a counter kept with each entry becomes worth a migration (the journal cut by its row cap and not by age; a short retention; counts that must outlive the journal; a slow page). multimap.conf says which of its texts SelfPost reads back. Tests: Usage over every kind on both lists and the rows that are nobody's; the milter's symbol order; the store query; the page and its sheets; a handler test from journal rows to the numbers on the page. Seen: the fixture page with a sheet open, headless Edge at 1360 px - the same as the accepted drawing. Checked on the test server, uncommitted tree: go vet ./... ok, go test ./... ok (22 packages), make e2e ok. Not seen in a real run: a count above zero. It needs an entry added in the panel, which I cannot sign in to, and then mail that the list decides; in particular the deny rule's message arriving in mail.log as the reply of a milter-reject line is what the trial with rspamc showed, not yet what Postfix logged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,8 @@ type FilterLists struct {
|
||||
|
||||
Where Box
|
||||
WhereFacts []Fact
|
||||
|
||||
retentionDays int
|
||||
}
|
||||
|
||||
// FilterList is one of the two boxes of entries.
|
||||
@@ -55,24 +57,29 @@ type FilterList struct {
|
||||
Empty EmptyState
|
||||
}
|
||||
|
||||
// FilterRow is one entry of a list: the value with the kind of entry as a tag,
|
||||
// the operator's note, the day it was added and the way to remove it.
|
||||
// FilterRow is one entry of a list: the value, the kind of entry as a tag that
|
||||
// opens the entry's sheet (the note, the day it was added, how often it decided
|
||||
// a message and when last), how often it decided, and the way to remove it.
|
||||
// Matched is empty for an entry that never did; the table then says "never".
|
||||
type FilterRow struct {
|
||||
Value string
|
||||
Kind Tag
|
||||
Note string
|
||||
Added string
|
||||
Remove RowAction
|
||||
Value string
|
||||
Entry Sheet
|
||||
Matched string
|
||||
Remove RowAction
|
||||
}
|
||||
|
||||
// FilterEntryInput is what the page knows of one entry of a list.
|
||||
// FilterEntryInput is what the page knows of one entry of a list. Hits and
|
||||
// LastHit are counted from the Inbound log, so they reach back as far as it
|
||||
// does.
|
||||
type FilterEntryInput struct {
|
||||
ID int64
|
||||
List string
|
||||
Kind string
|
||||
Value string
|
||||
Note string
|
||||
Added time.Time
|
||||
ID int64
|
||||
List string
|
||||
Kind string
|
||||
Value string
|
||||
Note string
|
||||
Added time.Time
|
||||
Hits int
|
||||
LastHit time.Time
|
||||
}
|
||||
|
||||
// FilterListsInput is the state of the filter the lists are written for: whether
|
||||
@@ -80,6 +87,9 @@ type FilterEntryInput struct {
|
||||
type FilterListsInput struct {
|
||||
FilterOn bool
|
||||
LastWritten time.Time
|
||||
// RetentionDays is how long the Inbound log keeps a row: how far back an
|
||||
// entry's matches are counted.
|
||||
RetentionDays int
|
||||
}
|
||||
|
||||
// NewFilterLists builds the page. The form starts on the Deny list for a sender
|
||||
@@ -113,6 +123,7 @@ func NewFilterLists(m Meta, in FilterListsInput) *FilterLists {
|
||||
}
|
||||
p.Deny.Box.End = Plain(plural(0, "entry", "entries") + " · wins over Allow")
|
||||
p.Allow.Box.End = Plain(plural(0, "entry", "entries"))
|
||||
p.retentionDays = in.RetentionDays
|
||||
p.setWhere(in)
|
||||
return p
|
||||
}
|
||||
@@ -144,10 +155,25 @@ func (p *FilterLists) setWhere(in FilterListsInput) {
|
||||
func (p *FilterLists) WithEntries(entries []FilterEntryInput) *FilterLists {
|
||||
p.Deny.Rows, p.Allow.Rows = nil, nil
|
||||
for _, e := range entries {
|
||||
row := FilterRow{
|
||||
Value: e.Value, Kind: Tag{Label: e.Kind}, Note: e.Note, Added: e.Added.UTC().Format("2006-01-02"),
|
||||
Remove: RowAction{Label: "Remove", Post: inboundFilterLists + "/" + strconv.FormatInt(e.ID, 10) + "/delete", Danger: true},
|
||||
id := strconv.FormatInt(e.ID, 10)
|
||||
window := "in the last " + plural(int64(p.retentionDays), "day", "days")
|
||||
sheet := []SheetRow{
|
||||
{Label: "Note", Lines: []string{orDash(e.Note)}},
|
||||
{Label: "Added", Lines: []string{e.Added.UTC().Format("2006-01-02")}, Mono: true},
|
||||
}
|
||||
row := FilterRow{
|
||||
Value: e.Value,
|
||||
Remove: RowAction{Label: "Remove", Post: inboundFilterLists + "/" + id + "/delete", Danger: true},
|
||||
}
|
||||
if e.Hits > 0 {
|
||||
row.Matched = strconv.Itoa(e.Hits)
|
||||
sheet = append(sheet,
|
||||
SheetRow{Label: "Matched", Lines: []string{row.Matched + " " + window}},
|
||||
SheetRow{Label: "Last match", Lines: []string{e.LastHit.UTC().Format("2006-01-02 15:04")}, Mono: true})
|
||||
} else {
|
||||
sheet = append(sheet, SheetRow{Label: "Matched", Lines: []string{"never " + window}})
|
||||
}
|
||||
row.Entry = Sheet{ID: "entry-" + id, Tag: Tag{Label: e.Kind}, Title: e.Value, Rows: sheet}
|
||||
switch e.List {
|
||||
case "deny":
|
||||
p.Deny.Rows = append(p.Deny.Rows, row)
|
||||
|
||||
Reference in New Issue
Block a user