Skip to content

Release 1.9.0 - #183

Merged
retransmit merged 77 commits into
mainfrom
development
Oct 8, 2026
Merged

retransmit merged 77 commits into
mainfrom
development

Conversation

@retransmit

Copy link
Copy Markdown
Owner

AdGuard Home as a new service in beta, with its Home, Query log and Clients tabs and a dashboard widget, the custom filters saved on the server in Sonarr and Radarr, and Ombi out of beta. Build 25, version codes 251, 252 and 253.

retransmit and others added 30 commits October 5, 2026 18:45
- Fetch custom filters from /api/v3/customfilter in Sonarr and Radarr API clients
- Add custom filter evaluators supporting Servarr operators (equal, notEqual, contains, notContains, greaterThan, lessThan, list comparisons, etc.)
- Expose custom filter providers and integrate client-side filtering in filtered series and movies providers
- Render custom filter chips in the Sort & Filter bottom sheet alongside standard filters with mutually exclusive selection
- Add comprehensive unit test suites covering operators, edge cases, and custom filter evaluations for both Sonarr and Radarr
…d judges them

The evaluator now follows the series list's own filter rule for rule, so
that a custom filter shows the same series here as it does in Sonarr:

- the filter builder saves a rule's value as a list of alternatives. Each
  is tested on its own and any one is enough, except under notEqual and
  notContains, where none may match. The ordering tests and the starts and
  ends tests looked at the first value alone
- the relative date rules ("in the last 30 days") are measured from now.
  They fell through to an equality check that never matched. A series
  with no such date passes none of the date rules, where "before" used to
  let it through
- the keys Sonarr works a value out for are worked out the same way:
  episode progress, rating as a percentage, rating votes, original
  language, release groups, season count, missing season and seasons
  monitored
- equal is exact, case included
- a rule that cannot be tested rejects the series: an unknown key, a row
  left empty, or a property the server left out

SonarrSeries gains the properties those rules read: quality profile, root
folder, tags, scene numbering, ratings, original language, and the release
groups in its statistics.

The provider hands back only the filters saved for the series list, by
label as Sonarr's own menu orders them, so the sheet no longer sorts them
out itself.

The tests use rules in the shape the server sends them. The one on
episodeFileCount goes: Sonarr has no filter key of that name, and its own
page shows nothing for a rule on it.
…d judges them

The evaluator now follows the movie list's own filter rule for rule, so
that a custom filter shows the same movies here as it does in Radarr:

- the filter builder saves a rule's value as a list of alternatives. Each
  is tested on its own and any one is enough, except under notEqual and
  notContains, where none may match. The ordering tests and the starts and
  ends tests looked at the first value alone
- the relative date rules ("in the next 6 months") are measured from now.
  They fell through to an equality check that never matched. A movie with
  no such date passes none of the date rules, where "before" used to let
  it through
- the keys Radarr works a value out for are worked out the same way: the
  collection by its title, original language, release groups, the
  qualities of its files, size on disk from the statistics, and the TMDb,
  IMDb, Rotten Tomatoes and Trakt ratings and votes
- equal is exact, case included
- a rule that cannot be tested rejects the movie: an unknown key, a row
  left empty, or a property the server left out

RadarrMovie gains the properties those rules read: original title,
availability, minimum availability, quality profile, keywords, popularity,
tags, its statistics and the Trakt rating.

The provider hands back only the filters saved for the movie list
(movieIndex, or the movies section it belongs to), by label as Radarr's
own menu orders them, so the sheet no longer sorts them out itself.

The tests use rules in the shape the server sends them.
feat: support Sonarr and Radarr custom filters
… no password

AdGuard Home counts every wrong Authorization: Basic header as a failed sign-in, on any path, and refuses the address for fifteen minutes after five, right password included. Measured on v0.107.79; requests with no credentials do not count.

So the health probe for this kind leaves with no Authorization header at all, neither the instance's own nor one the profile's custom headers set, and reads a 401 as a server that is up. The dot cannot tell a wrong password from a right one, which is the price of not locking the owner out of their own web UI twice a minute.

Everything else is signed with HTTP Basic when credentials were entered, and with nothing when they were not: a server set up without a user answers anyone.
A new package, with the models the Home tab and the dashboard widget need and the formatting they share. The fixtures are what v0.107.79 answered.

The models are read by hand rather than generated: a top list arrives as a list of one-entry objects, and the server sends null where it means an empty list.
…refused sign-in

AdGuard Home refuses an address for fifteen minutes after five wrong sign-ins, and answers the right password with the same bare 401 while it does. Every signed request counts.

So requests to an instance queue behind one another: when the first is refused, the ones waiting fail without being sent. After a refusal nothing is sent at all until the user asks to try again, which costs one more request.

The client covers what the Home tab and the dashboard widget read and write: status, statistics, the filter lists, the statistics period, protection with an optional pause, and the custom rules.
…y its web UI does

Providers for the status (every 10 seconds), the statistics (30), the filter lists (5 minutes) and the statistics period, all through the instance's one session, so a refused sign-in is still a single request however many of them are being watched, and however often Riverpod's own retries or the poll come round again.

Blocking or unblocking a domain follows the web UI's own steps: an opposite custom rule is removed if there is one, and only otherwise is ||domain^$important or its @@ form appended. The rules are read fresh before the write, which replaces the whole list.

The pauses are the five the web UI offers; its "until tomorrow" is a flat 24 hours.
Protection with a switch and the five pauses the web UI offers, a countdown while paused, the six figures with their series, and the top lists. A queried domain can be blocked and a blocked one unblocked from its row.

The countdown runs on the device between polls and asks the server again the moment it reaches zero, so a finished pause is not left on screen until the next poll.

Once the server has refused the sign-in, the tab shows why and what AdGuard Home does about wrong tries, and sends nothing more until Try again is tapped.
The health probe carries no password for AdGuard Home, so Test connection signs in once itself. A refusal says that the server, by default, blocks an address for 15 minutes after five wrong tries. An address that answers with something other than AdGuard Home's API says that instead of calling the server unreachable.

The form tests the local address and then the external one, and each wrong password counts towards that lockout. So a sign-in refused in the last 30 seconds is not sent again unless something on the form changed, and the result says it was not tried.

The form no longer demands a username and password for it: a server set up without a user answers anyone.
One block per instance: whether it is protecting, a menu to pause it for one of the lengths AdGuard Home's own page offers or to turn it off, Resume while it is paused or off, and four figures: queries, blocked, the blocked share, and the rules on the enabled blocklists.

A refused sign-in is said in place, with what AdGuard Home does about wrong tries, and is tried again only when asked.
AdGuard Home returns rows of equal count in a different order on every read. Shown as they arrived, tied rows in the top lists swapped places at each poll, and a tap on Block could land on a different domain than the one aimed at. Ties are now settled by name.
When one tile's label took more lines than its neighbour's, the shorter tile was stretched to match but kept its chart at the top, so the two charts sat at different heights. A stretched tile now puts its chart at the bottom.
…in its tile

A nine-digit count at large text is shrunk to fit its tile. The row sized its tiles before that shrinking, as if the figure had wrapped onto more lines, and left a gap between the figure and the chart. The figure now sits in a box one line tall, whatever it shrinks to.
…text

Four figures across a 360 dp phone at large text touched each other and lost their labels. Where four do not fit, they now take two rows of two.

A figure too wide for its column shrinks inside a box that stays one line tall, so the labels of all four stay level.
…its message

A tap on Block or Unblock acts at once, on a list that re-reads itself every half minute and can reorder under a finger, and a block reaches everything that uses the server. The message that says what was done now offers Undo for eight seconds.

It is not offered when the rule was already there: nothing changed, and the opposite tap would remove a rule this one never added.
An instance with no username or password gets the same 401 from a server that has a user as a wrong password does. The refused view told that person their password was not accepted and warned of a fifteen-minute block, when nothing had been sent that the server counts. It now says the server asks for a sign-in and where to add one.
Test connection on an instance with no username or password, against a server that has a user, said the sign-in was refused and warned of a fifteen-minute block. Nothing had been sent that the server counts. It now says the server asks for a sign-in, and tries both addresses, since there is nothing to hold back.
…e was entered

With no username or password on the instance, the widget's refused state warned of a fifteen-minute block for wrong tries, though none had been made. It now says the server asks for a sign-in.
…Others, and a way to see all

Each top list on the Home tab shows its first five rows, adds the rest up as Others, and opens the whole list on a screen of its own, where a domain can be blocked or unblocked as well. Response times are averages, so they are not added up.

The message that says what a block or unblock did, with its Undo, is now shared by the tab and that screen.
retransmit and others added 28 commits October 8, 2026 02:57
The three actions were at the end of the entry, a scroll away once it
had a few rules or answers. Block or Unblock now sits under the
scrolling part, with the two things that can be done to the client in a
menu beside it.
…f the sheet

With the actions fixed under it, the scrolling part took the width of
its widest line and was centred, so a short entry sat in the middle.
…ead it again

Once the log was cleared, or a search found nothing, there was nothing
to pull: the first new query could only be seen by leaving the tab.
…ne, and clear its narrowing in one read

The end of the list asks for more a frame after it is drawn and the
search box a moment after the typing. A call that arrived after the
screen was left threw. It is now dropped.

Clearing the search and the filter sent two reads, the first for a
question that was already out of date. It is one.
…s none

"NaN" and "Infinity" parse as numbers and cannot be rounded, so one such
entry failed the whole page of the query log it came in.
…y so beside Try again

A try that was refused again looked exactly like no try at all, which
invites another tap, and the fifth wrong try in a row is the one that
gets the address blocked for fifteen minutes. The session now counts the
refusals that reached the server, and from the second on the refused
screen says how many there have been.
… log

Changing a client's access:
- The sheet closed "whatever is on top" once the server had answered. If
  it had been closed by then, on a slow connection, that was the screen
  itself. It now closes only itself, shows that the change is on its way
  and takes no other action meanwhile.
- A failure that arrived after the sheet was closed said nothing, leaving
  the client believed to be shut out. It is said at the foot of the
  screen.
- A client the allowed clients let in by a range or a client id has no
  entry of its own to take off. The lists went back unchanged and the
  client was reported disallowed. It is now refused, with the reason.

A query with no name, such as one for the root, offered Block, and the
rule written for it was "||^". It no longer does.

Try again on one tab now reads a query log that is showing on the other:
after it worked on Home, the log went on saying the sign-in was refused.
One tap is still one request.

The failed-read and search-in-progress screens scroll, so their button is
not under the keyboard on a small phone. Retry after a failure deep in a
search goes on from where the search had got to. The end of the list no
longer claims to be the end while the list is being read again. A failed
read of the log's settings is not repeated ten times by itself.
… refused in a row

Try again is on the card as well, and the fifth wrong try in a row gets
the address blocked wherever it is tapped.
feat(service_adguard_home): add the query log
…d open a client from the top clients and from a query
…view, and close the form itself after a write
feat(service_adguard_home): add the clients
@retransmit
retransmit merged commit 3f938c8 into main Oct 8, 2026
4 checks passed

This branch was successfully deployed

1 active deployment
github-pages — 0d490d5e Deployed Oct 8, 2026 by retransmit via deploy #30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants