Different tables used different date formats: Servers & Tasks used a
fixed "YYYY-MM-DD HH:mm:ss" (24h), while Audit Log, DNS, Integrations
(Tailscale/Semaphore), and Users called plain .toLocaleString() with
no options, which renders using the browser's own locale — different
per browser/OS, and inconsistent with the other pages' fixed format.
- New Settings -> Date & Time page: pick date order (YYYY-MM-DD,
DD/MM/YYYY, MM/DD/YYYY) and 12h vs 24h clock, with a live preview.
- utils/date.ts's formatDateTime() now reads these settings instead of
being hardcoded to sv-SE/24h; the setting is fetched once at app
startup (alongside /api/me) via a new non-secret GET
/api/settings/display (any signed-in user, same rationale as the
badge-color endpoints) and applied immediately on save too, without
needing a page reload.
- Switched every remaining raw new Date(...).toLocaleString() call
(Audit Log, DNS zone sync time, Tailscale/Semaphore last-seen,
Users' last login) over to the shared formatter, so every table now
renders dates identically.
Verified the formatter's date-order x 12h logic against all six
combinations plus the midnight/noon 12h edge cases, and the settings
endpoints end-to-end (defaults, partial updates, validation
rejection, audit logging) against the real dev server.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The new integration badge-color setting only reached the Integrations
page's Manage table — the Servers & Tasks overview card grid had its
own hardcoded bg-purple-lt badge for Proxmox-linked servers that never
picked it up. Reuses the same typeBadgeStyle pattern already used
there and in the DNS module.
Note: until a Proxmox color is set in Settings -> Badges, this badge
now renders with Tabler's default neutral badge style rather than
purple, matching how every other badge customized this way already
behaves before a color is chosen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Settings → DNS Badges only let you customize DNS provider badge
colors. Expand it into a general Badges page with a second section
for the six integration types (Tailscale, Proxmox, Synology,
Semaphore, Gitea, Dockhand), applied to the type badge in the Manage
integrations table.
- New integrationColors key on AppSettings, stored/merged the same way
as providerColors via the existing settingsStore.
- New GET /api/settings/integration-colors — non-secret, any signed-in
user, mirroring /provider-colors — so the badge color can be read
without needing admin access to the full settings payload.
- Renamed DnsBadgeSettings.tsx -> BadgeSettings.tsx (route
/settings/dns-badges -> /settings/badges, sub-nav label "DNS
Badges" -> "Badges") with both color sections saved together.
Verified end-to-end against the real dev server: PUT persists
integration colors independently of provider colors, the public
integration-colors endpoint reflects updates immediately, and the
change is audit-logged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Was a separate top page-header bar above every page's content. Moved
into the vertical navbar itself using Tabler's own .navbar-footer
class (order:1 + margin-top:auto), which pins it to the bottom of the
sidebar regardless of how many nav items are above it. Removed the
now-empty page-header entirely.
Verified visually in the browser at both desktop width (sidebar is
position:fixed there — footer sits flush at the bottom) and mobile
width (collapsed dropdown menu — footer just follows the nav list).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Stop maps to Proxmox's hard power-off (/status/stop) — fine for a
crashed guest, but risky for anything with a filesystem that'd rather
flush cleanly first. Proxmox exposes a separate /status/shutdown
endpoint that asks the guest to power itself down (ACPI event for a
VM, SIGTERM-then-wait for a container), so add it as its own action
rather than overloading Stop.
- New shutdownGuest() on the Proxmox adapter, calling /status/shutdown.
- The existing generic action route/loop already dispatches by
${action}Guest, so adding "shutdown" to that list was enough on the
server side — no new route needed.
- New Shutdown button next to Restart/Stop on both the Integrations
page's guest table and a Proxmox-linked server's detail page.
Stop's confirm prompt now explicitly points at Shutdown as the
gentler alternative.
Verified end-to-end against a mock Proxmox server: the shutdown call
hits /status/shutdown (never /status/stop) and is audit-logged as
shutdown_guest.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Proxmox start/stop/restart routes already existed (used by the
Integrations page's guest table) but weren't reachable from a server's
own detail page, even when that server was linked to a Proxmox guest.
Reuses the existing POST /api/integrations/:id/proxmox/nodes/:node/
:type/:vmid/{start,stop,restart} routes and the operator+ role gate
already enforced there — no server-side changes needed. Buttons only
render for hardware.source === "proxmox", mirroring the Integrations
page's running/stopped button-set logic, with the same confirm-before-
stop prompt for the non-reversible action.
Verified end-to-end against the real dev server with a mock Proxmox
HTTPS server: linked a server to a mock guest, confirmed the detail
endpoint's live status, then triggered restart/stop/start and
confirmed the mock actually received each action and every call was
audit-logged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Installing the agent against a Homelab Manager instance with a
self-signed cert failed: curl verifies TLS by default on the install
download, the report-tasks.sh fetch inside install.sh, and every
periodic check-in — not just the outer one-liner, so passing -k to only
that first curl wasn't enough. Mirrors the existing Proxmox/Synology
"insecure" toggle pattern already in this app.
- install.sh and report-tasks.sh accept API_INSECURE=true, adding -k to
their own curl calls; install.sh persists it into the agent's env
file so the periodic systemd timer picks it up too.
- The Servers & Tasks page has a new checkbox next to the generated
install/uninstall commands that adds -k and API_INSECURE=true for
you, so the outer one-liner (which install.sh's own logic can't
touch) also skips verification.
Off by default — only for a trusted LAN.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Manage integrations only supported add/toggle/delete — fixing an
expired API token meant deleting and recreating the whole integration.
- New GET /api/integrations/:id/config returns only the non-secret
config fields (never the decrypted secret) so an edit form can
pre-fill URL/tailnet/etc. fields.
- New POST /api/integrations/:id/test merges the stored, decrypted
config with any freshly-typed overrides and pings the real adapter —
lets "Test connection" work during an edit without ever sending the
current secret back to the browser.
- New IntegrationEditForm component: secret fields render blank with a
"leave blank to keep the current value" placeholder; submitting only
sends the fields that were actually filled in, so a name/URL edit
can't accidentally wipe a secret and a secret rotation can't touch
anything else. Reuses the existing PATCH /:id route, which already
merged partial config updates correctly.
Verified end-to-end against the real dev server: confirmed via direct
DB decryption that a non-secret-only edit leaves the stored secret
byte-for-byte unchanged, and that a secret-only edit rotates it without
touching other config; the test route was confirmed to make a real
network call (got a genuine "API token invalid" from Tailscale's API
against a fake key).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Servers & Tasks only tracked scheduled tasks — there was no overview of
the servers themselves and no way to see CPU/RAM/disk/IP info. Add a
clickable server overview (visible to every role, not just admins) that
opens a per-server detail page.
- servers table gains an agent-reported hardware/network snapshot
(IPs, CPU model/cores/load, memory, disks) and an optional link to a
Proxmox VM/LXC (integration + node + guest type + vmid).
- Proxmox adapter gains getGuestDetail(): live cores/memory/disk from
/config, live cpu/mem/uptime from /status/current, and IPs (LXC net
config directly, QEMU via a best-effort guest-agent call that degrades
gracefully when the agent isn't installed).
- New GET /api/servers/:id/detail combines whichever hardware source
applies (live Proxmox vs. last agent report) with a DNS reverse-lookup
against the DNS module's own record cache, so matching hostnames show
up next to each IP. New PATCH /api/servers/:id manages the Proxmox
link.
- agent/linux/report-tasks.sh now also collects and reports IPs,
CPU/memory/disk info on every check-in (load-average-based CPU number,
not instantaneous, to keep the agent a cheap oneshot).
- Extracted the per-server task table into a shared ServerTaskTable
component so the all-servers view and the new detail page render
tasks identically.
Verified server-side end-to-end against the real dev server (agent
report -> detail endpoint -> DNS match) and the Proxmox adapter against
a mock HTTPS server covering LXC/QEMU config parsing and the
guest-agent-unavailable fallback.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Settings was a single long page. Break it into a sub-nav (Notifications,
DNS Badges, Cache) with its own route per section, each loading and
saving independently. Also add the DNS record-cache clearing action
that Sloth Manager had — a new admin-only POST /api/dns/cache/clear
truncates the zone/record cache tables so stale data can be wiped and
zones re-synced from scratch.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Settings page was a "coming soon" placeholder. Port Sloth Manager's
settings feature set: Gotify/ntfy/SMTP/webhook notification channels
(each with its own test-send button), per-event toggles (DNS record
added/updated/deleted, a daily secret-expiry digest with configurable
time/timezone), and per-provider DNS badge color customization.
Settings persist in the existing `settings` key/value table via a new
settingsStore service; a notify service fans a message out to every
enabled channel. DNS record add/update/delete now fire notifications,
and a node-schedule job re-arms itself whenever the notification
settings change. Removed the now-superseded GOTIFY_URL/GOTIFY_TOKEN
env vars in favor of in-app configuration.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Target column only ever printed "<type> #<id>", so newly created
integrations (and other records) appeared as "integration 1",
"integration 2", etc. instead of the display name set in the form.
Audit entries already log the name in their detail JSON on
create/update/delete; parse it and show it when present.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Found while verifying against the user's real tailnet (25 devices): the
Tailscale device object has no "online" or "isExitNode" field at all — this
was inherited from Sloth Manager's original adapter, which apparently never
had this checked against live data either. With the old mapping, every
device showed online:null and isExitNode was always false regardless of
reality.
Fixed by deriving both from fields that actually exist:
- online <- connectedToControl (whether the device currently has an active
session with Tailscale's control plane)
- isExitNode <- enabledRoutes containing both 0.0.0.0/0 and ::/0 (a device
can *advertise* exit-node routes without them being approved; checking
enabledRoutes instead of advertisedRoutes reflects whether it's actually
acting as one right now)
Both fields come back from the list endpoint via `?fields=all`, so this
adds no extra requests.
Verified against the real tailnet: 25 devices, 24 online / 1 offline (a
phone last seen 9 days ago — correct), and the one device actually
configured as an exit node identified correctly. This is the last of the
five previously-unverified integrations now confirmed against real
infrastructure; combined with the earlier Synology (HTTP vs HTTPS) and
Proxmox (async task timing) findings, every integration has now had at
least one real bug shaken out by testing against the user's actual homelab
rather than mocks alone.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sixth and final live integration: volume and disk health across the NAS,
read-only per the delivery plan (DSM write actions are riskier and stayed
out of scope). Adapter built against the same discover-then-call pattern
used by hacf-fr/synologydsm-api (the library behind Home Assistant's
Synology integration) since DSM's API paths/versions vary by release and
the user's NAS isn't reachable from here to check directly:
- GET query.cgi?api=SYNO.API.Info&method=query&query=all once per adapter
instance, to learn the real path/version for SYNO.API.Auth and
SYNO.Storage.CGI.Storage rather than hardcoding them
- SYNO.API.Auth login (account/passwd/format=sid) for a session id, re-used
across calls and refreshed on session-related error codes (105/106/119)
- SYNO.Storage.CGI.Storage's `load_info` method, which returns disks,
volumes, and pools in one call — verified against that library's
storage.py field mapping (size.total/used, status, smart_status, temp,
exceed_bad_sector_thr, below_remain_life_thr)
Uses node:https directly (like Proxmox and the cPanel DNS adapter) for an
"allow self-signed certificate" option, since DSM ships one by default.
No 2FA support — login surfaces a clear error if the account requires it
rather than failing silently.
Verified: full build passes. Unreachable from here, so ran an 11-check HTTP
test against the live server: confirms all six integration types are now
registered, role gating, credential (password) non-leakage, disabled-
integration blocking, and the wrong_type crash-safety check — server stays
up throughout. Real volume/disk data still needs verification once this app
can reach the user's NAS.
This completes the integrations phase from the original plan: Tailscale,
Gitea, Dockhand, Semaphore, Proxmox, Synology are all built, each following
the same config-in-UI + encrypted-credentials pattern. Combined with the
foundation, Secrets, IPAM, DNS, and Servers & Tasks modules from earlier,
Homelab Manager now covers every feature area from the user's original
request.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fifth live integration: VM/LXC status across every online node in the
cluster, with start/stop/restart actions — matching the "dashboard + basic
actions" depth from the plan. Adapter built against Proxmox VE's own
published API tree (pve.proxmox.com/pve-docs/api-viewer/apidoc.js — parsed
its ~4MB ExtJS tree structure directly since it's not plain JSON) plus
community-verified docs for the token auth header format, since the user's
instance isn't reachable from here.
server/src/integrations/proxmox/adapter.ts: GET /nodes for online nodes,
then GET /nodes/{node}/{qemu,lxc} per node in parallel (Promise.all) and
flattened, tagging each guest with its node and type; POST
/nodes/{node}/{qemu,lxc}/{vmid}/status/{start,stop,reboot} for actions (all
confirmed token-auth-eligible via the spec's "allowtoken" flag). Uses
node:https directly (like the cPanel DNS adapter) rather than fetch, since
Proxmox commonly runs a self-signed certificate in homelab setups — added
an "Allow self-signed certificate" checkbox field for that. Auth is
`Authorization: PVEAPIToken=<tokenId>=<tokenSecret>`, a different shape
from the other three integrations' single bearer token, so the field
schema splits it into two fields (a non-secret token ID like
"root@pam!homelab-manager" and a secret token value).
Verified: full build passes. Unreachable from here, so ran a 16-check HTTP
test against the live server: role gating, credential non-leakage,
disabled-integration blocking, invalid-guest-type and non-numeric-vmid
rejection, checkbox-only config correctly still failing required-field
validation, and the wrong_type crash-safety check for this fifth adapter
type — server stays up throughout. Real node/VM data and the actual
start/stop/restart actions still need verification once this app can reach
the user's Proxmox cluster.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fourth live integration: Ansible run history per template with a
trigger-a-run action — matching the "dashboard + basic actions" depth from
the plan. Adapter built against Semaphore's official published OpenAPI spec
(github.com/semaphoreui/semaphore/blob/develop/api-docs.yml) plus its Go
source directly for the task-status enum, which the spec itself leaves
untyped (db/Task.go, pkg/task_logger/task_logger.go) — instance wasn't
reachable from here (semaphore.int.toolabs.se doesn't resolve outside the
user's LAN), so verified against the real spec/source rather than guessing.
server/src/integrations/semaphore/adapter.ts: GET /api/projects, then GET
/api/project/{id}/templates?sort=name&order=asc per project — each template
in that response already embeds its last_task, so listing every template's
current status across every project needs only one call per project (no
N+1 per-template lookup, unlike Gitea where the run history isn't embedded
in the repo list). POST /api/project/{id}/tasks {template_id} triggers a
run. Follows the same config-in-UI + encrypted-credential pattern as the
other three integrations.
Verified: full build passes. Same as Dockhand — unreachable, so ran a
14-check HTTP test against the live server (real target URL, bogus token):
role gating, credential non-leakage, disabled-integration blocking, and the
wrong_type crash-safety check for this fourth adapter type, confirming the
server stays up throughout. Real template/run data and the trigger-a-run
action still need verification once this app can reach the user's LAN.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Third live integration: container status across every Docker host Dockhand
manages, with start/stop/restart actions — matching the "dashboard + basic
actions" depth from the plan. Talks to Dockhand's own aggregating REST API
(bearer tokens, dh_...) rather than the raw Docker Engine API on each host
directly, so one credential covers every host Dockhand is already connected
to.
Adapter built against Dockhand's real published OpenAPI spec (fetched from
https://github.com/strausmann/mcp-dockhand/blob/main/docs/dockhand-openapi.json,
257 documented paths) since the instance itself isn't reachable from here —
same "verify against the real shape, don't guess" approach as Gitea, just
via the spec instead of a live instance:
- GET /api/environments — the Docker hosts Dockhand knows about
- GET /api/containers?env=<id>&all=true — containers per host
({id, name, image, state, status})
- POST /api/containers/{id}/{start,stop,restart}?env=<id>
server/src/integrations/dockhand/adapter.ts fans the environments call out to
one containers call per host (Promise.all) and flattens the result, tagging
each container with its environment; one unreachable host returns an empty
list for that host rather than failing the whole dashboard view. Follows the
same config-in-UI + encrypted-credential pattern as Tailscale and Gitea.
Verified: full build passes. Since Dockhand isn't reachable from here, ran a
14-check HTTP test against the live server instead of the real API — role
gating, credential non-leakage, disabled-integration blocking, and
(critically) re-confirmed the wrong_type crash-safety pattern holds for this
third adapter type: hitting the Dockhand routes on a differently-typed
integration row returns a clean 400 rather than crashing the process, and
the server keeps responding to /health afterward. Real container data and
the start/stop/restart actions still need verification once this app runs
on the user's LAN where Dockhand is reachable.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Second live integration: repo list with last CI run status, and re-running
failed jobs on a workflow run — matching the "dashboard + basic actions"
depth from the plan. Adapter built directly against the real Gitea 1.27
swagger spec (fetched from the user's own instance) rather than guessing at
the API shape: GET /user/repos for the repo list, GET
/repos/{owner}/{repo}/actions/runs?limit=1 for the latest run per repo (only
for repos with Actions enabled), and POST .../rerun-failed-jobs for retrying
just the failed jobs in a run. Follows the same config-in-UI +
encrypted-credential pattern as Tailscale and DNS providers.
Since Gitea collects its own base URL as a config field (unlike Tailscale,
which always talks to a fixed api.tailscale.com), generalized the
"integrations.baseUrl" bookkeeping into resolveBaseUrl() instead of the
one-fixed-URL-per-type map used previously.
Also fixed a real gap found while setting this up: server/src/env.ts reads
process.env directly, but nothing in the app ever loaded .env into
process.env for plain `node dist/index.js` / `tsx src/index.ts` runs — only
Docker's `env_file` config populated it, by injecting vars before Node even
starts. Every local (non-Docker) run silently had every setting at its
insecure default. Added server/src/loadEnv.ts (dotenv, pointed at the
repo-root .env) as the first import in both server/src/index.ts and
server/src/db/migrate.ts's standalone entrypoint.
Verified against the user's real, reachable services — not mocks:
- Authentik (auth.labsconnect.se): full OIDC login completed by the user
through the real UI; confirmed their account landed as admin (first user).
- Gitea (gitea.labsconnect.se): the compiled adapter run directly against a
real API token correctly listed all 11 real repos; a full HTTP-layer test
against the live server (8 checks) additionally covered a real
test-connection ping, credential non-leakage in list responses, and role
gating (403) on the rerun-failed-jobs action even with a valid token
behind it. None of the real repos have any workflow run history yet, so
the success/failure status badge and the rerun action itself are
implemented per the swagger spec but not yet exercised against a real run
— worth checking once one of those repos has actual CI activity.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
First of the six planned live integrations (Proxmox, Synology, Semaphore,
Tailscale, Gitea, Dockhand), reusing Sloth Manager's existing Tailscale
adapter logic. Generalizes the "integrations" table already scaffolded in
the foundation pass into a working config-in-UI + encrypted-credentials
flow, following the same pattern as the DNS providers module — an
"Add integration" form only offers types with an implemented adapter
(currently just Tailscale), so the framework is ready for the next five
integrations without further schema/plumbing changes.
- server/src/integrations/tailscale/adapter.ts: ported from Sloth Manager's
backend/src/adapters/tailscale.js — listDevices/setAuthorized/deleteDevice
against the Tailscale API, now config-based (tailnet + apiKey) instead of
reading process.env, and returning a ping() result instead of throwing.
- server/src/routes/integrations.ts: generic integration CRUD (admin) + a
"test connection" endpoint, plus Tailscale-specific device routes
(dashboard-and-basic-actions depth per the plan: authorize/deauthorize/
remove, gated to operator+, audit-logged).
- web: an Integrations page (provider-style manage/browse split, matching
the DNS page's UX) with a device table, and a live Tailscale widget on
the Dashboard.
Bug found and fixed while testing: hitting the Tailscale device routes on
a non-Tailscale integration row crashed the ENTIRE server process, not just
that request — the generic adapter registry throws for unimplemented types,
and that throw happened inside an async handler with no surrounding
try/catch, which Express 4 doesn't catch, so it became an unhandled
rejection that (on modern Node) kills the process. Fixed at the source
(check the row's type before ever constructing an adapter) and, since the
same "a helper throws before any local try/catch runs" shape existed
wherever a route calls into loadDnsProviderConfig/loadIntegrationConfig
(both call decryptSecret, which throws if CREDENTIALS_ENCRYPTION_KEY is
ever wrong/missing after data was already encrypted with a different key),
added a small asyncHandler() wrapper and applied it to every route handler
across every router — a single bad request should never be able to take
the whole app down for every user.
Verified: full build passes. Fresh HTTP-layer tests against a running
server (17 checks) cover not-implemented-type rejection, missing-field
validation, a real network call to api.tailscale.com with a bogus key
(clean ok:false, not a crash), role gating at every tier, credential
non-leakage, disabled-integration blocking, and the wrong_type case that
originally crashed the server — confirmed it now returns 400 cleanly and
the server stays up. Re-ran the existing DNS (13 checks) and Servers/Tasks
suites afterward to confirm the asyncHandler sweep didn't regress anything
— all passing. Authorizing/removing a real device still needs a real
Tailscale API key to verify end-to-end.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ports cron/systemd task tracking across Debian/Raspbian servers, including
the Linux push agent (install/report/uninstall scripts, rebranded from
"schedule-task-manager-agent" to "homelab-manager-agent") and the
manual-task-entry flow for things an agent can't see (Docker jobs, backups).
The schema (servers/scheduled_tasks tables) was already in place from the
foundation pass, so this is mostly a straight port of the original's
services/routes.
Deliberate change from the original: server/token management (which mints
agent credentials) is now admin-only rather than open to any logged-in user,
and manual task CRUD is gated to operator+ — consistent with how Secrets,
IPAM, and DNS already split "configure credentials" from "everyday edits"
across roles. All mutations are audit-logged.
- server/src/services/tokens.ts, taskSync.ts: ported near-verbatim (agent
token hashing, the agent-sync-marks-missing-as-stale-not-deleted logic).
- server/src/routes/servers.ts, tasks.ts, agentReport.ts: same contract as
the original (agent auth is a per-server bearer token, independent of the
session-based requireAuth used everywhere else).
- web: a single Servers & Tasks page (filter bar, task table grouped by
server/schedule type, manual task form, and an admin-only server
management panel with token reveal + copyable install/uninstall commands),
replacing the original's two separate pages/apps.
Verified: full build passes; a scripted HTTP test against a running server
covers unauthenticated access, role gating at each tier (admin-only server
mgmt, operator+ task mgmt), agent bearer-token auth (valid/invalid/rotated),
manual-vs-agent task edit protection, stale-marking on re-sync, and cascade
delete — 22/22 checks passing. Real agent installation on an actual
Debian/Raspbian host still needs to be tried on the user's network.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ports zone/record management across Cloudflare, Loopia, Pi-hole, Azure DNS,
cPanel, and Technitium onto the new stack. Unlike the original (one instance
per provider configured via env vars), providers are now configured through
the UI and support multiple named instances per type, with API
credentials encrypted at rest via the integration_credentials table.
- server/src/dns/adapters/*: each provider ported to a config-based factory
(no more process.env reads), preserving each provider's original quirks
(Pi-hole session auth, Azure record-set merging, Loopia XML-RPC, cPanel
UAPI/API2 fallbacks, Technitium composite record IDs).
- server/src/routes/dns.ts: provider CRUD (admin), a "test connection"
endpoint, and zone/record browsing+sync+CRUD (operator+), all audit-logged.
- dns_zones_cache/dns_records_cache tables replace Sloth Manager's
dns-cache.json file, keeping the same "cache is the source of truth for
display, sync fetches fresh from the provider" behavior.
- web: a DNS page with provider management, zone browsing, and a record
editor, plus a reusable dynamic provider-config form.
Verified: full build (tsc + vite) passes; a scripted HTTP-layer test against
a running server exercises auth, role gating (403 for viewer), validation
(400 on missing config fields), provider CRUD, credential non-leakage in
list responses, and adapter error propagation (502 against an unreachable
host) — all passing. Real provider connectivity still needs to be checked
against the user's actual DNS accounts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Monorepo (Express+TS+Drizzle/libSQL server, React+Vite+Tabler web) matching
the stack used by ScheduleTaskManager and Sloth Manager. Includes Authentik
OIDC login with local admin/operator/viewer roles (first user becomes admin),
a generalized audit log, encrypted-at-rest storage for future integration API
tokens, the DB schema for all planned modules, and the Tabler-styled app
shell/nav. Also ports the Secrets (expiry tracker) and IP Addresses (IPAM)
modules from Sloth Manager onto the new stack.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>