The bell rendered hard-coded template data ("Roman Joined the Team!",
"New Payment received") — a demo artifact that had nothing to do with the
product and no backing feature.
In its place, an About dialog reports the running server's build: version,
build time, and commit, read from GET /api/v1/version, with a copy button so
the exact build can be quoted in a bug report without shelling onto the host.
That probe predates the {success,data} envelope and returns bare snake_case
JSON, so it is fetched directly rather than through the api client, accepting
either shape in case it is ever normalised. Dev builds ("dev"/"unknown")
degrade to a readable "—" rather than an invalid date.
Removing the bell also stranded three other unreferenced template files, so
Notification.tsx, AppLinks.tsx, QuickLinks.tsx and their shared data.ts (fake
users, chat/ecommerce app links) are deleted with it.
Also fixes the account button announcing itself to screen readers as
"show 11 new notifications".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The OpenAPI spec and Swagger UI were public. Put them behind the same
authentication as the rest of the API, and add a compact route list so a
client can ask "what can I call?" without opening dev tools.
Access:
- /api/openapi.json and /api/routes require a bearer token or API key.
- /api/docs additionally accepts a session cookie set at login, so a
signed-in operator can open the docs in a new tab; an anonymous browser
is redirected to /login?redirect=... and returned afterwards.
- The cookie is HttpOnly and path-scoped to /api/docs, so it is never sent
to /api/v1/* and cannot authenticate an API call (no CSRF surface).
Verified: cookie-only request to /api/v1/rules returns 401.
Discovery: both the spec and GET /api/routes accept ?method=get,post and
?path=<substring> (comma-separated, case-insensitive). The route list
returns method, path, summary, tag, public, and `allowed` — false when a
read-scoped API key cannot invoke that route. /api/docs passes the same
query through to the spec it loads.
UI: a </> icon in the header (both layouts) and an Administration → API
Docs menu entry, opened in a new tab via a new `external` nav-item flag.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add client-side pagination (TablePagination) to lists that could grow long
and cause endless scroll:
- Object Viewer search results
- Object attribute detail dialog
- Windows certificate store list (Security)
- Rule preview matched objects (previously hard-capped at 100 with no paging)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Toggling LDAPS in the connection form now auto-sets the port (636 on / 389
off when it's still the other default) and enables "Allow invalid certs",
because a non-domain-joined host usually can't validate the DC's LDAPS
certificate. This fixes the common "existing connection was forcibly
closed" seen when LDAPS was enabled while the port stayed 389 (dialing TLS
to the plaintext port). Helper text explains the traffic is still encrypted.
- LDAP client sets tls ServerName to the host so verification succeeds when a
DC cert IS trusted (ignored under InsecureSkipVerify).
- Backups retention reduced from 10 to 3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A new Object Viewer page: pick a connection, choose an object type and search
by name (or drop in a raw LDAP filter and a search base), list the matches,
and drill into any object to see all its attributes with copy buttons for the
attribute name and value — so operators can discover the exact attributes and
values to put in a rule filter.
Built on the existing query-preview endpoint (now typed as QueryPreviewResult);
the detail view requests all attributes (["*"]). Added to the Directory nav.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Roles:
- Add GET /api/v1/roles (built-in SuperAdmin/Admin/Operator/Viewer) and
wire role assignment into user create/update (UserRepository.SetRoles /
GetRoleNames / ListRoles). User responses now include roles.
- User dialog gains a roles multi-select; the users list shows role chips.
OIDC / SSO config UI:
- Default role is now a dropdown populated from /roles.
- Issuer URL shows real provider examples (Entra/Okta/Google).
- Replace the confusing manual "Redirect URL" field with a read-only,
auto-derived callback URL (from the browser origin / public URL) plus a
copy button — the exact value to register at the IdP. The backend still
auto-derives the callback and honors an env override.
Verified: /roles lists the four roles; creating/updating a user with roles
round-trips through GET.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Windows-store certificate list only loaded after clicking a separate
"List certificates" button, so selecting the Windows-store option appeared
to show nothing. Fetch the certificates automatically when the source is
chosen (and the store is supported); the button becomes a Refresh. The
backend endpoint and windowsStoreSupported flag were already correct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
API keys were never validated — the middleware only checked session tokens,
so an X-API-Key request always 401'd. Add auth.Service.ValidateAPIKey (looks
up the key hash, enforces enabled/revoked/expiry, loads the user + roles,
stamps last_used_utc) and route X-API-Key / bearer auth through it.
Add per-key scopes (migration 006): "read" (GET/HEAD only) or "readwrite"
(full access, default). The middleware rejects mutating requests from a
read-scoped key with 403. Create accepts a scope; list and create responses
include it; the UI create dialog has a scope selector and the list shows a
scope chip. Disable/re-enable and revoke (permanent) were already correct.
Verified live: RW key GET/POST ok; read key GET ok, POST 403; disable→401,
re-enable→200; revoke→401 and re-enable blocked.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Count tiles link to their resource pages; the run-stat cards open Activity,
Recent Rule Runs opens Rule Runs, and Connection Health opens Connections.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a History icon to each rule that opens a dialog listing the actual
objects this rule acted on — added, removed, moved — scoped to the rule via
the activity feed's ruleId filter, with category/result filters, paging, and
the per-action detail drill-in. Colour a "Completed" last-run result as
success (not amber) now that the rules list surfaces it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Schedule column to the rules table that resolves each rule's
scheduleId to the schedule name and its next run time (or "Manual"),
pairing with the newly seeded built-in schedules.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The filter builder's attribute field now searches the selected connection's
full directory schema (scoped to the rule's object type) instead of a fixed
curated list, and the value field suggests the distinct values actually
present in the directory for the chosen attribute — both search-as-you-type
with the curated common attributes still shown first.
New AttributePicker and ValuePicker components (async MUI autocompletes over
ConnectionsApi.attributes / attributeValues), wired into each condition row;
memberOf still uses the group picker and raw-LDAP keeps a plain field.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the old read-only rule form (which could only edit scalar metadata
and showed "author via config import" for conditions/actions) with a full
tabbed editor that authors the whole rule:
- Scope: connection, object type, and a searchable base-OU picker.
- Filter: a condition-group builder with attribute autocomplete (from the
backend metadata), operator dropdowns, per-condition/-group NOT, AND/OR
join within and across groups, and a raw-LDAP escape hatch. memberOf
operators render a group picker for the value.
- Target & action: choose the action (Sync membership to group / Move to OU
/ Ensure group exists / Add to group), pick or paste the target group/OU,
set the sync mode (Full / Managed / Add-only) and create-if-missing;
multiple actions supported.
- Schedule: run manually, on an existing schedule, or create an interval or
cron schedule inline.
- Preview: live dry-run against the directory (POST /rules/preview) showing
match count, generated filter, planned +add/-remove, and sample matches.
New DirectoryPicker (search-or-type Autocomplete backed by the connection
directory endpoint) is reused for base OU, target group, target OU, and
memberOf values. API layer gains RuleMetadata/DirectoryObject/RuleInput
types and RulesApi.metadata/previewSpec + ConnectionsApi.directory.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Turn the rule_run_actions ledger into operator intelligence. A new
ActivityService rolls the ledger up by time window (24h / 7d / all-time)
into syncs, operations and removals with success/failure counts and the
number of distinct objects affected, plus all-time totals per action type
and a most-active-rules ranking. A companion filtered, paginated feed
answers the operational questions directly: what action ran, what
happened, to which object, triggered by whom, and when.
Backend:
- ActivityService.Summary() and ListActions(filter) over the joined
rule_run_actions / rule_runs / rules tables, categorising each action
type as Sync (AddToGroup/AddGroupToGroup), Removal
(RemoveFromGroupIfNoLongerMatched) or Operation (everything else).
- GET /api/v1/activity/summary and GET /api/v1/activity (category,
actionType, status, ruleId, search, pagination).
- Table-driven test seeding a run with mixed action types/statuses and
asserting window roll-ups, distinct-object counts, top rules, and the
category/status feed filters.
Frontend:
- New Activity page: window summary cards, by-action-type and
most-active-rules tables, and a drill-in feed with category/result/
object filters and a detail dialog that pretty-prints the action's
details JSON and error.
- Wired into the vertical sidebar and horizontal navbar under Automation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the flag/language dropdown from both the vertical and horizontal
headers and delete the now-unused Language component. OrchestrAD ships a
single language, so the selector was dead UI chrome.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Security admin page wiring the OIDC and TLS backends: an SSO/OIDC form
(issuer, client id/secret, redirect, scopes, claim mappings, default role) and a
TLS card showing the live certificate and a source picker - self-managed,
bring-your-own (PEM upload), or a Windows-store certificate (enumerate + select).
Add OidcApi/TlsApi clients + types, a Security nav entry, and a Canonical Name
column to the rule preview.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Default the theme to dark (toggle still available). Rebrand the logo to the
OrchestrAD icon + wordmark. Redesign the login into a calm, centered card on a
soft themed backdrop (removed the 3D character illustration and decorative
circles). Add a 'Sign in with SSO' button shown when OIDC is enabled, and adopt
the OIDC callback token from the URL fragment (hydrating the user via /auth/me).
Replace the hardcoded 'Mike/Admin' sidebar card with the real signed-in user and
a working sign-out; de-jar the search label.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add password_reset_required column (migration 004) + repository support
- auth.Service.ChangePassword verifies current, hashes new, clears flag,
emits PasswordChange audit events for success and failure
- Bootstrap: when no ORCHESTRAD_BOOTSTRAP_PASSWORD[_FILE] is set, seed
admin/admin with password_reset_required=true and log a one-time warn
banner; env/file-supplied passwords keep the flag clear
- Expose passwordResetRequired in UserInfo / /auth/me / login response
- POST /api/v1/auth/change-password behind the authenticated group
- Frontend: /change-password page + ChangePasswordForm, AuthLogin and
RequireAuth bounce any other route to it while the flag is set
- Docs: DesignSpecification 8.6/8.8 and Template 7.6/7.7 rewritten,
Trusted Proxy renumbered to 7.8 in the template, acceptance items
updated to match the new default-credential behavior
- Add sanitizeRedirect/safeRedirectTarget helper; reject anything that isn't
a safe local path (must start with a single /, no scheme, no whitespace,
not /login itself). Apply in RequireAuth when encoding the current pathname
and in AuthLogin when consuming ?redirect=.
- Make configureApi's getToken read localStorage directly via readCurrentToken
so the first request after login cannot race the AuthContext re-render that
previously owned the token via a React closure.
- Redirect legacy /auth/* (including the pre-flatten /auth/auth1/login) with
HTTP 301 to /login in the webui handler so stale bookmarks can't seed the
SPA router with a malformed URL.
- Regression test TestHandler_LegacyAuthPathRedirectsToLogin covering four
legacy path shapes.
getApiBaseUrl returned `http://localhost:8080` whenever window was
undefined, which meant the Next.js static export baked that string into
every chunk that could be prerendered (notably 3056-*.js and the
/login page chunk). A production binary served from any other host or
port still contained the literal string, which would surface in
developer-tooling searches and could misroute fetches in edge cases.
Return an empty string as the SSR fallback and teach buildUrl to
produce a relative URL (path + query only) when the base is empty. The
browser's fetch then resolves against the current origin, which is the
right answer for the single-binary same-origin deployment regardless
of scheme, host, or port.
Verified with a clean frontend/out rebuild: all 22 JS chunks loaded
from /login are now free of localhost:8080 and /auth/auth1/login; the
admin login POST continues to return 200 with a valid session token.
Two coupled frontend cleanups:
- The Spike template routed login through /auth/auth1/login, a leftover
from the multi-variant demo layout. Move the page to /login and
update every reference (header menu, sidebar, AuthContext redirects,
RequireAuth redirects). Any deep links to the old path still resolve
via the SPA fallback in the embedded webui handler.
- The API client hardcoded http://localhost:8080 as the base URL, which
meant the built frontend could only talk to a dev-mode backend on a
specific port. Replace with window.location.origin so the same
compiled bundle works regardless of scheme, host, or port; the single
binary serves both UI and API from one listener, so same-origin is
always correct for production deployments.
Prune the Spike NextJS PRO demo content that was still shipping inside
the embedded UI and rename the route group to match the app, so every
page we compile is one we actually use. Separately the full Spike main
is preserved at Grace-Solutions/Spike-NextJS-PRO-Template on a parallel
branch for future template pulls.
Route group
- Rename src/app/(DashboardLayout) -> src/app/(app); update all
imports, lazy chunk references, and layout boundary names.
Remove demo routes
- src/app/(app)/{apps,charts,muicharts,forms,icons,mui-trees,
react-tables,tables,theme-pages,ui-components,widgets,sample-page,
dashboards} and the remaining (app)/apps/{blog,ecommerce,invoice}
detail/edit subtrees.
- src/app/{frontend-pages,landingpage} and the blog [slug] route.
- src/app/auth/{auth2,error,maintenance} and the auth1 forgot/register
variants; keep the primary login flow only.
- src/app/api/* demo fetchers (blog, chat, contacts, dashboard,
eCommerce, email, invoice, kanban, notes, ticket, userprofile,
globalFetcher); real calls go through @/lib/api.
Remove demo components and contexts
- src/app/components/{apps,dashboards,forms/form-*,pages,widgets,...}.
- src/app/context/{BlogContext,ChatContext,Ecommercecontext,...};
keep AuthContext, CustomizerContext, Config only.
- src/app/types/{apps,auth}; AuthLogin now uses a local props type.
Layout cleanup
- (app)/layout/vertical/header/Header.tsx and horizontal/header
drop ProductProvider / Ecommerce wrappers so the build succeeds.
- (app)/layout/horizontal/navbar/Menudata.ts mirrors the OrchestrAD
sidebar MenuItems so both layouts expose the same routes.
Build artifacts
- Re-stage frontend/out into backend/internal/webui/dist after the
flatten; index.html now references the (app) chunk graph.
Binary impact (windows/amd64, CGO off): ~37.5 MB -> 26.27 MB.
Embedded dist: 298 files / 14.46 MB.
Switch Next.js to static export (output: 'export', trailingSlash: true) and add a new backend/internal/webui package that embeds frontend/out/ via //go:embed. The backend now serves the UI from its own HTTP server with SPA fallback, long-lived cache headers on _next/static/*, and no dependency on a separate Node.js runtime or static host.
- frontend/next.config.mjs: output='export', trailingSlash=true, images.unoptimized.
- backend/internal/webui: Handler() with SPA fallback, resolve() mirroring Next trailing-slash behavior, IsBuilt() helper, and a placeholder dist/index.html so go build works without a prior frontend build. Top-level .gitignore tracks only the placeholder and its own .gitignore; the copied static export is ignored.
- backend/internal/server/server.go: mount webui.Handler() as the chi NotFound handler so /api/v1/* and /health continue to be served by their handlers while all other routes resolve through the embedded UI with SPA deep-link support.
- scripts/build.ps1: build the frontend (npm install if needed, then npm run build) before any Go build, stage frontend/out into backend/internal/webui/dist, and fail loudly if the UI output is missing or empty. Adds -SkipFrontend for developer iteration.
- Remove Spike demo content that prevented static export: api/* route handlers (dynamic POST/PUT/DELETE), apps/{blog,ecommerce,invoice} dynamic [slug]/[id] detail/edit pages, and frontend-pages/blog/[slug].
- Fix TypeScript errors surfaced by enabling the production build: ApiMeta.totalCount (not total) in audit and rule-runs pages; MUI v7 Grid uses size={{...}} instead of item+xs+md in config page.
Binary size grows from ~8 MB to ~37 MB, reflecting the embedded UI. The produced single binary is now sufficient to run the full product; no separate web server is required.
Add /config with an Export card that downloads the current configuration as a pretty-printed JSON file (orchestrad-config-<timestamp>.json) and an Import card that parses a chosen file, shows a per-entity chip summary, and offers Validate (dry run) and Apply Import. Results render in an ImportReportView with created/updated/skipped totals, per-entity counts, and warnings/errors surfaced from the backend. Expand ConfigApi.import to send { payload, dryRun } in the JSON body per the server contract and add ConfigExport / ImportReport / ImportEntityReport and related RuleExport/etc. types mirroring services.ConfigExport.
Add /settings with sorted key/value list, type chip, description, sensitive badge, and updated timestamp. SettingFormDialog PUTs /settings/{key} for create/edit; key is locked when editing. Sensitive values come back masked from the API, so the dialog warns the user to re-enter the value and switches to a password input when isSensitive is toggled on. JSON values get a multiline textarea.
Add /audit page with server-side pagination and the full filter set supported by the backend (event type, resource type, action, username, outcome, start/end UTC). Table shows time, event chip, action, user, resource, outcome chip. AuditEventDialog pulls /audit/{id} and renders metadata (time, component, user, IP, user agent), any error message, and a pretty-printed JSON view of details. Extend AuditApi.list with a full AuditFilter type and add AuditApi.get.
Add /api-keys with user-scoped listing (defaults to current user, admin can select any user). Columns: name, key prefix, status chip (Active/Revoked/Expired), enabled toggle, created, last used, expires. Per-row enable/disable, revoke, delete. ApiKeyCreateDialog posts to /api-keys and displays the one-time plaintext key with a copy-to-clipboard affordance; closing is only allowed via the Done button once the key is shown so it cannot be missed by an accidental backdrop click.
Add /users list (username, display name, email, local/OIDC source, last login, active toggle) with per-row edit/delete. UserFormDialog supports create (password required) and edit (password optional, username locked for OIDC users). Update via PUT allows toggling active status and changing password.
Add /rule-runs list with server-side pagination, per-rule filter (uses /rules/{id}/runs when set), and client-side status filter. Columns: rule name, status chip, execution mode, started, duration, matched/processed, actions (ok/fail), triggered by. RuleRunDetailDialog loads /rule-runs/{id} (which includes action records) and renders run stats, any run-level error message, and a per-action table with status, type, target DN, duration, and error/details. Add RuleRunAction and RuleRunDetail types and update RuleRunsApi.get to return the detail shape.
Add /rules page with list view (object type, condition/action counts, enabled toggle, last run + result chip) and per-row preview/run/edit/delete/enable actions. RuleFormDialog covers top-level fields the API supports (identity, AD connection, object type, base DN / scope overrides, execution mode, schedule, group join operator, max parallelism, stop-on-error). Condition groups and actions are rendered read-only (authored via config import today). RulePreviewDialog invokes POST /rules/{id}/preview and renders the generated LDAP filter, warnings, matched DNs, and planned actions with change indicators. Add RulePreviewResult types and type RulesApi.preview accordingly.
Add /schedules page with list view (name, kind, definition summary, timezone, enabled, next run) and create/edit/delete/enable-disable actions. ScheduleFormDialog supports both Easy (interval + Minutes/Hours/Days) and Cron (six-field expression) kinds with UTC/Local timezone selection, matching the backend ScheduleRequest/Response shape.
Add /connections page with list view (hosts:port, root DN, TLS mode, enabled toggle, last test result) and per-row test/edit/delete actions. ConnectionFormDialog covers the full LDAP surface (hosts, port, TLS/StartTLS/invalid certs, root DN, optional bind DN, credential binding, search scope, paging). ConnectionTestDialog invokes the server-side test and renders each step (TCP, bind, root DN access) with latency. Add JSON tags to services.TestResult/TestDetail so the camelCase wire format matches the frontend ConnectionTestResult type.
Align frontend types with the Go backend payload shape (createdAt/updatedAt/lastTestedAt naming, CredentialTestResult). Add a typed CredentialsApi with CRUD + enable/disable + test. Add /credentials page with a data table showing name, type, username, enabled toggle, last test time, and last result, plus per-row actions (test, edit, delete). Add CredentialFormDialog for create/edit (leaving password blank preserves the stored secret). Add CredentialTestDialog that performs a live LDAP bind against a user-specified host/port/TLS combination and surfaces connect/bind latency. Extract shared formatDateTime/formatDurationMs/formatLatencyNs helpers into lib/format.ts. Trim the Spike template's orphan menu entries from MenuItems.ts.
Replace the Spike demo dashboard with a live view backed by /api/v1/dashboard/summary: entity count tiles, 24h/7d run stats, recent rule runs, and connection health. Rebuild the vertical sidebar navigation around OrchestrAD's actual sections (Rules, Schedules, Rule Runs, Connections, Credentials, Users, API Keys, Audit Log, Settings, Config). Wire the header profile dropdown to the current session, showing the logged-in user's name/role/email and a working Log out button. Align DashboardSummary TypeScript shape with the Go backend payload (CountPair counts, ruleRunStats, recentRuns, connectionsHealth).
Add a typed fetch wrapper that unwraps the backend envelope, attaches Bearer tokens, and normalizes errors. Introduce resource-specific API helpers for every management endpoint. Ship AuthProvider that persists the session to localStorage and exposes login/logout. Wrap the (DashboardLayout) subtree in a RequireAuth guard and replace the demo AuthLogin form with one that calls /api/v1/auth/login and honors a redirect query param. Include docs/FrontendCleanup.md describing the Spike template layout, what to keep, and what to prune.
- Next.js 14 with App Router
- TypeScript strict mode
- Tailwind CSS with dark mode support
- React Query for server state
- NextAuth.js integration ready
- Dashboard layout with sidebar navigation
- API client with error handling
- Core type definitions for all entities
- Utility functions for dates and classnames