Skip to content

Group ProxyHosts table by base domain#5707

Open
jiemCode wants to merge 1 commit into
NginxProxyManager:developfrom
jiemCode:develop
Open

Group ProxyHosts table by base domain#5707
jiemCode wants to merge 1 commit into
NginxProxyManager:developfrom
jiemCode:develop

Conversation

@jiemCode

@jiemCode jiemCode commented Jul 13, 2026

Copy link
Copy Markdown

Summary

Groups the Proxy Hosts listing under collapsible headers keyed by each host's base domain (registrable domain). Once you get more than a handful of proxy hosts, the flat alphabetical list becomes hard to scan — related domains like api.booze.pics and booze.pics sit far from each other, mixed with unrelated ones like booze.app. Clustering by base domain makes related hosts visually adjacent and hides bulk behind expandable groups.

Grouping is always on, all groups expanded by default, and toggling any column header still sorts rows within their group.

Screenshot

How base domain is computed

Base domain is extracted from domainNames[0] (the first entry in a host's domainNames array) using tldts, which is backed by the Mozilla Public Suffix List. This handles multi-level TLDs correctly:

First domain Base domain
api.booze.pics booze.pics
booze.pics booze.pics
web.booze.pics booze.pics
api.school.co.uk school.co.uk
school.co.uk school.co.uk
blog.github.io blog.github.io
service.local service.local

The helper (frontend/src/pages/Nginx/ProxyHosts/baseDomain.ts):

```ts
import { getDomain } from "tldts";

export function getBaseDomain(domain: string | undefined): string {
if (!domain) return "";
return getDomain(domain) ?? domain;
}
```

getDomain returns null for IPs / non-PSL suffixes; we fall back to the raw string so those hosts still cluster deterministically by their exact hostname.

Why domainNames[0]?

A single proxy host can serve multiple domains — the domainNames field is string[]. Almost always they share a base (example.com + www.example.com), but occasionally a host serves domains from different bases (e.g., ['booze.app', 'booze.site', 'm.booze.pics'] — visible in the screenshot). We use the first domain only to pick the group, matching the existing sort behavior on the Source column (which already sorts by domainNames?.[0]). The full list still renders in the row, so the extra domains remain visible; they're just not indexed for grouping. Duplicating the row under every base group was rejected as more confusing than helpful (same record showing multiple times).

Pasted image

Implementation notes

  • Uses @tanstack/react-table's built-in getGroupedRowModel + getExpandedRowModel — no new table library.
  • baseDomain is added as a hidden accessor column (columnVisibility: { baseDomain: false }) so it drives grouping without occupying screen space.
  • grouping and columnVisibility live in initialState (uncontrolled), while expanded is controlled so a future "collapse all" toggle can hook in.
  • tableData is memoized in TableWrapper.tsx to avoid re-computing baseDomains on every render.
  • Group header rows use the theme-aware table-active class so contrast works in both light and dark themes.
  • TableBody.tsx now branches on row.getIsGrouped(); leaf-row rendering is unchanged, so every other table (Redirection Hosts, Streams, Dead Hosts, Access Lists) is unaffected.

Files changed

  • frontend/package.json — adds tldts (~small, tree-shakeable PSL lib)
  • frontend/src/pages/Nginx/ProxyHosts/baseDomain.ts (new) — getBaseDomain helper
  • frontend/src/pages/Nginx/ProxyHosts/TableWrapper.tsx — injects baseDomain into each row via memoized map, hooks reordered to satisfy rules-of-hooks
  • frontend/src/pages/Nginx/ProxyHosts/Table.tsx — hidden baseDomain accessor column + TanStack grouping/expansion config
  • frontend/src/components/Table/TableBody.tsx — renders group header rows (chevron + base domain + subrow count, click to toggle)

Cluster proxy hosts under collapsible group headers keyed by the
registrable domain (PSL via tldts). Grouping is always on; row order
within groups still honors column sorting. Multi-domain hosts group
by the first domain.
@nginxproxymanagerci

Copy link
Copy Markdown

Docker Image for build 1 is available on DockerHub:

nginxproxymanager/nginx-proxy-manager-dev:pr-5707

Note

Ensure you backup your NPM instance before testing this image! Especially if there are database changes.
This is a different docker image namespace than the official image.

Warning

Changes and additions to DNS Providers require verification by at least 2 members of the community!

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.

1 participant