Skip to content

feat(domains): add Domains page with DNS, certificate and Traefik error status - #60

Merged
agentHits merged 1 commit into
AgentHits-Devfrom
feat/domains-overview
Oct 12, 2026
Merged

agentHits merged 1 commit into
AgentHits-Devfrom
feat/domains-overview

Conversation

@agentHits

@agentHits agentHits commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds a read-only Domains page at /dashboard/domains. It lists the domains of the active organization on each server the caller can read, with DNS status, certificate expiry, where the domain is used, and the last Traefik error.

Base: AgentHits-Dev. This PR does not depend on another open PR.

What the page shows

  • One row per domain and server.
  • Where the domain is used: the owning application or compose service, or the container name for a route that no panel service owns.
  • DNS status from node:dns (A and AAAA, 5 s timeout, 10 minute cache), compared with the server address.
  • Certificate expiry from Traefik's acme.json. Red under 14 days, yellow under 30 days.
  • The last Traefik error for the domain, with its time, the original line, and a short explanation in Russian.
  • A Traefik log block per server: the last 300 certificate and ACME lines from the last 7 days.

Access

The page uses the same gate as the Traefik File System page and settings.readTraefikFile.

  • domainOverview.list and domainOverview.traefikLogs are protectedProcedure. Both call checkPermission({ traefikFiles: ["read"] }).
  • The panel's own host needs owner or admin. A remote server needs access to that server. The two helpers moved from settings.ts to server/api/utils/traefik-files-access.ts, so both routers use one implementation.
  • A server the caller cannot read is not contacted, and its domains are not listed.
  • Certificate data needs hasProtectedResourceAccess: owner or admin, browser session, and an open super session. Without it, the page shows no certificate data and a notice. The other columns still load.
  • The sidebar item and getServerSideProps check traefikFiles.read. Users without it are redirected to /.
  • Both procedures are classified read in procedure-access.ts, like readTraefikFile.

Members with the Traefik files permission can open the page, not only owners and admins.

Private keys

acme.json contains keys. The server-side jq filter keeps only the domain names and the certificate field. Without jq, the whole file is read, and the panel drops the keys before anything else touches the data. Keys are never returned, logged, or put in an error message.

The feature never reads container environment variables. Route discovery reads container names and labels only.

Test plan

  • apps/dokploy, domain-overview tests: bunx vitest run --config __test__/vitest.config.ts __test__/domain-overview. 7 files, 73 tests, all passing.
    • The router test has 13 access cases. It runs the real checkPermission and host checks against a fake member row.
    • The new access test has 3 cases: no contact with unreadable servers, no acme.json read without certificate access, and no key material in the response.
  • Fault injection: I forced certificate access open, skipped the host check in the router, and removed the server filter in the service. Six tests failed. I restored the files.
  • Full apps/dokploy suite on my local machine: 299 files, 3038 tests. 3028 passed, 4 failed, 6 skipped. The 4 failures are in __test__/deploy/application.real.test.ts, which runs real git clones and Docker builds. Docker is not running in my shell.
  • CI, pr-check (test) (bun run test): 299 of 299 files passed. 3037 tests passed, 1 skipped (3038). That run includes application.real.test.ts, and it passed.
  • CI, all other checks: audit and typecheck, pr-check (build), main-image-smoke, and the three service-image-builds jobs all passed.
  • bunx tsc --noEmit passes in packages/server and in apps/dokploy.
  • bunx biome check passes on the 23 changed files.

Not verified

  • Not run against a live panel or live servers. The tests mock every command, DNS call, and database read.
  • The 4 real-execution tests fail on my local machine, where Docker is not running. CI runs them on this branch and they pass.
  • Container names from route labels are shown without the docker read permission that guards the Docker view.
  • Panel domains and route owners are not filtered by a member's service access list. The overview page applies that filter; this page does not.
  • ACME log lines are shown as they are, up to 2,000 characters each. A log line that printed a secret would show it to any user who can read the page.

🤖 Generated with Claude Code

…or status

Adds a Domains section (sidebar item and /dashboard/domains) that lists the
domains of the active organization on the servers the caller can read, with
each domain's DNS record, certificate and last Traefik error. It is an
information page with no state changes: no buttons edit DNS, Traefik
configuration, certificates or containers, and nothing restarts.

For each server the page shows one row per domain:
- the domain and the server it is on;
- where it is used: links to the application or compose service that owns it
  (panel domains, including preview domains), or the container name and a link
  to the Docker containers view for a route that no panel service owns;
- DNS status from node:dns (A and AAAA, 5 s timeout, cached for 10 minutes),
  with the resolved addresses, compared with the server's public address;
- the certificate from Traefik's acme.json: expiry date and days left, red
  under 14 days and yellow under 30 days; a certificate that no route or panel
  domain uses is marked "certificate only, nothing uses it";
- the last Traefik error for the domain, with its time, the original line, a
  plain-language explanation and advice. Causes: no DNS record, no address
  record, Let's Encrypt timeout, port 443 answered by something other than
  Traefik (ALPN), port 443 closed or not reaching Traefik, rate limit, unknown.
  Explanations are in Russian and reuse docs/certificates-and-ports.md wording.

Each server also has a Traefik log block with the last 300 certificate and ACME
lines and a refresh button.

Access: the same gate as the Traefik File System page and readTraefikFile.
- domainOverview.list and domainOverview.traefikLogs are protectedProcedure and
  call checkPermission(traefikFiles: read), as settings.readTraefikFile does.
- Each server gets the check readTraefikFile applies to it: the panel's own
  host needs owner or admin (assertLocalHostAccess); a remote server needs
  access to that server (assertSettingsServerAccess). Both helpers moved from
  settings.ts to server/api/utils/traefik-files-access.ts so the two routers
  share one implementation. A server the caller cannot read is not contacted,
  and its domains and routes are not listed.
- acme.json is a protected file for readTraefikFile, so list reads it only when
  hasProtectedResourceAccess passes: owner or admin, browser session, open
  super session. Without that the page shows no certificate data and says so;
  the other columns still load.
- The sidebar item and the page (getServerSideProps) check traefikFiles.read,
  as the Traefik page does. Both procedures are classified "read" in
  procedure-access.ts, the same as readTraefikFile.

Private keys: acme.json contains keys. On the server, jq keeps only the domain
names and the certificate field; without jq the whole file is read and the
panel drops the keys before any other step touches the data. The keys are never
returned, logged or put into an error message.

Limits:
- Every server read has a 15 s timeout and a server that does not answer shows
  an error entry without blocking the page. A slow server can still delay the
  first load by up to about 15 seconds.
- docker logs is called with --since 168h (seven days), because Docker does not
  accept a day unit.
- Wildcard names are not checked in DNS.
- Domains that appear only in error lines of the Traefik log are shown as rows
  too, so the operator sees them.
- The panel's own address comes from the Web Server IP setting when it is set,
  otherwise from public address detection (cached for 10 minutes).
- Panel domains and route owners are not filtered by a member's service access
  list, and container names from route labels are shown without the docker read
  permission that guards the Docker view. The page follows the Traefik file gate
  only, not the overview's per-service scope.
- Not verified against a running panel or against live servers; the tests mock
  every command, DNS call and database read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@agentHits
agentHits merged commit 6d3b7da into AgentHits-Dev Oct 12, 2026
7 checks passed
@agentHits
agentHits deleted the feat/domains-overview branch October 12, 2026 22:02
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