xtrakt Platform — Design Spec
Neumorphic Redesign + FingerprintJS Admin Screens + NewRelic Integration
Date: 2026-04-08 Status: Approved for implementation Covers: Task 1 (Visual Design), Task 2 (FingerprintJS + Admin Screens), Task 3 (NewRelic Plan)
Task 1: Neumorphic Visual Redesign
Decision
Apply the Portal project's full neumorphism design system to every screen in xtrakt-sso-fe, including the sidebar/navigation shell. Portal's exact color palette is used — xtrakt's purple/cyan brand identity is replaced on all screens with the neumorphic blue-accent system.
Design Tokens (from portal's style.css)
/* Base palette */
--neu-bg: #e8ecf1;
--neu-shadow-dark: #b8c0cc;
--neu-shadow-light: #ffffff;
--neu-text: #2d3561;
--neu-text-muted: #8f9bb3;
--neu-text-light: #b0b8cc;
--neu-accent: #3b82f6;
--neu-accent-dark: #2563eb;
--neu-danger: #e05c5c;
--neu-success: #3dba7e;
/* Raised shadows */
--neu-raised-xs: 3px 3px 6px var(--neu-shadow-dark), -3px -3px 6px var(--neu-shadow-light);
--neu-raised-sm: 5px 5px 10px var(--neu-shadow-dark), -5px -5px 10px var(--neu-shadow-light);
--neu-raised-md: 8px 8px 16px var(--neu-shadow-dark), -8px -8px 16px var(--neu-shadow-light);
--neu-raised-lg: 12px 12px 24px var(--neu-shadow-dark),-12px -12px 24px var(--neu-shadow-light);
/* Inset shadows */
--neu-inset-sm: inset 3px 3px 6px var(--neu-shadow-dark), inset -3px -3px 6px var(--neu-shadow-light);
--neu-inset-md: inset 5px 5px 10px var(--neu-shadow-dark), inset -5px -5px 10px var(--neu-shadow-light);
--neu-inset-lg: inset 8px 8px 16px var(--neu-shadow-dark), inset -8px -8px 16px var(--neu-shadow-light);
/* Border radii */
--neu-r-xs: 6px; --neu-r-sm: 10px; --neu-r-md: 14px;
--neu-r-lg: 20px; --neu-r-pill: 50px;
Typography
Replace Outfit/Inter with portal's system font stack:
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
Scope of Changes in xtrakt-sso-fe
-
src/src/styles.scss— Replace all CSS variables with neumorphism tokens. Remove Google Fonts import. Setbackground-color: var(--neu-bg)onbody. -
MenuComponent(menu.component.scss+menu.component.html) — Full sidebar redesign:- Background:
var(--neu-bg)with right-side shadow6px 0 18px var(--neu-shadow-dark) - Logo container: neumorphic raised box
- Nav section labels: small uppercase in
var(--neu-text-muted) - Active nav item:
box-shadow: var(--neu-inset-sm)+color: var(--neu-accent)(pressed-in effect) - Inactive nav items: flat, hover gets
var(--neu-raised-xs) - User avatar: neumorphic circle with initials
- Collapsed state: show mini logo in neumorphic box
- Background:
-
All page content components — Replace flat card pattern (
background: white; border: 1px solid) with neumorphic card pattern (background: var(--neu-bg); box-shadow: var(--neu-raised-md)). Update buttons, inputs, tables, pills, modals to use neumorphism utility classes. -
Global utility classes in
styles.scss— Port portal's shared classes:.btn,.btn-primary,.btn-secondary,.btn-danger,.btn-success,.neu-card,.neu-table,.modal-overlay,.modal,.modal-header,.modal-body,.modal-footer.
Component-by-Component SCSS Files to Rewrite
All components under src/src/app/:
login/login.component.scss— neumorphic login card, inset inputs, raised buttonmenu/menu.component.scss— full sidebar (see above)templates/template-list.component.scsstemplates/template-new-form.component.scsstemplates/template-edit-form.component.scssconfigs/config-list.component.scssplayground/playground.component.scssprofiles/users-at-profiles.component.scssplans/plan-settings-list.component.scssplans/plan-settings-form.component.scssplans/plan-upgrade.component.scssproducts/products-list.component.scssproducts/product-form.component.scssjourneys/journey-list.component.scssjourneys/journey-form.component.scssjourneys/journey-profiles.component.scssbilling/invoices-list.component.scssbilling/invoice-detail.component.scsscoming-soon/coming-soon.component.scssshared/components
Task 2: FingerprintJS Pro + Admin Security Screens
Overview
Replicate Portal's four Administration screens in xtrakt: Insights, Device Intelligence, Audit Log, and Security Config. These require:
- FingerprintJS Pro integration in
xtrakt-sso-fe(new API key) - New
LoginAuditLogentity +SecurityParameterentity inxtrakt-sso-be - New
AdminControllerinxtrakt-sso-be - Four new Angular components in
xtrakt-sso-fe - New SECURITY menu section in the sidebar
2.1 FingerprintJS Pro — Frontend
Package: @fingerprintjs/fingerprintjs-pro-angular
API Key: CFoxHcakGXnWQ4vp6BXX
app.config.ts — provide FingerprintjsProAgentModule:
import { FingerprintjsProAgentModule } from '@fingerprintjs/fingerprintjs-pro-angular';
providers: [
importProvidersFrom(
FingerprintjsProAgentModule.forRoot({ loadOptions: { apiKey: 'CFoxHcakGXnWQ4vp6BXX' } })
)
]
LoginComponent — inject FingerprintjsProAgentService, call getVisitorData() before submitting credentials. Pass visitorId as X-Visitor-Id HTTP header on POST /connect/token. If FingerprintJS fails or times out, proceed with login without a visitorId (non-blocking).
2.2 Backend — xtrakt-sso-be (Clean Architecture)
Domain Layer — New Entities
LoginAuditLog (extends AuditEntity):
Id (Guid), SubscriptionId (Guid, tenant),
VisitorId (string?), UserId (Guid?), UserName (string),
IpAddress (string?), UserAgent (string?),
Country (string?), CountryCode (string?), City (string?),
Success (bool), LoggedAt (DateTime UTC)
SecurityParameter (extends AuditEntity):
Id (Guid), SubscriptionId (Guid, tenant),
ParamKey (string), ParamValue (string), Description (string),
UpdatedAt (DateTime UTC)
Repository interfaces: ILoginAuditLogRepository, ISecurityParameterRepository.
Infra Layer
- EF Core mappings with
snake_casecolumn names DbSet<LoginAuditLog>andDbSet<SecurityParameter>inOpenIdDictDbContext- Repository implementations extending
Repository<T> - Register in
InfraStartup.cs - EF Core migration:
AddLoginAuditAndSecurityConfig
Application Layer — Handlers
Each handler folder under Handlers/Admin/:
| Handler | Input | Output |
|---|---|---|
GetOverviewHandler | period (24h/7d/30d) | usage, uniqueVisitors, chartData, heatmap, topOrigins/browsers/countries |
GetDevicesHandler | period, risk filter | list of device rows (grouped by visitorId) |
GetAuditHandler | page, pageSize, period, success filter, userName, dateFrom, dateTo | paginated AuditRow list + total |
GetSecurityConfigHandler | — | 5 security parameters |
UpdateSecurityConfigHandler | 5 int values | success |
GetWorldMapHandler | period | country code + count list |
GetHeatmapHandler | period | 7×24 activity matrix |
Business logic is a direct port of Portal's AdminController.cs. GeoIP lookup via http://ip-api.com/json/{ip} (free tier, called async on login, non-blocking).
AuthController — modified to read HTTP context fields before passing to ConnectTokenHandler:
- Read
X-Visitor-Idheader - Read
X-Forwarded-For(or fall back toRemoteIpAddress) for IP - Read
User-Agentheader - Pass all three into a new
ConnectTokenRequestwrapper DTO alongside theOpenIddictRequest
ConnectTokenHandler — extended to:
- Receive
visitorId,ipAddress,userAgentfrom the enriched request DTO - Call GeoIP service async (fire-and-forget; failure is silently swallowed)
- Write
LoginAuditLogrecord after auth decision (success or failure recorded)
This keeps HTTP context out of the handler layer, consistent with Clean Architecture.
Web Layer — AdminController
[Route("api/admin")]
[Authorize(Roles = "admin")]
GET /api/admin/overview?period=
GET /api/admin/devices?period=&risk=
GET /api/admin/audit?page=&pageSize=&period=&success=&userName=&dateFrom=&dateTo=
GET /api/admin/world-map?period=
GET /api/admin/heatmap?period=
GET /api/admin/security-config
PUT /api/admin/security-config
All handlers injected via [FromServices] per xtrakt convention.
2.3 Frontend — Four Angular Components
All components: standalone, neumorphic SCSS, inject HttpClient, use Transloco for labels, admin-role guarded.
InsightsComponent (/insights)
Mirrors AdminOverviewView.vue:
- 3 KPI cards: Login Events, Unique Visitors, Events/Visitor
- Line chart (current vs previous period) using
chart.js+ng2-charts - Activity heatmap (7×24 grid, color intensity)
- World map: new
WorldMapComponentstandalone Angular component (world-map.component.ts/.html/.scss) — SVG-based, receives{ code: string; count: number }[]as@Input(), fills country paths by ISO alpha-2 code with a blue intensity scale matching the neumorphic palette. This is a new component to create, ported from portal'sWorldMapChart.vue. - Top browsers / top origins / top countries lists
- Period selector: 24h / 7d / 30d + granularity toggle (Hourly/Daily/Monthly)
Dependencies to install: chart.js, ng2-charts
DeviceIntelligenceComponent (/device-intelligence)
Mirrors DeviceIntelligenceView.vue:
- 3 KPI cards: Unique Devices, High Risk, Medium Risk
- Risk scoring logic (client-side, same as portal):
distinctUsers > 1→ HIGH; failure rate >60% → HIGH; >30% → MEDIUM; else LOW - Paginated device table (20/page): Visitor ID, First/Last Seen, Login count, Failure count, Last IP, Browser, Risk pill
- Risk filter toggle: All / Low / Medium / High
- Period toggle: Last 7 days / Last 30 days
- Click row → detail modal with risk analysis breakdown
AuditLogComponent (/audit-log)
Mirrors LoginAuditView.vue:
- 4 KPI cards: Total, Successful, Failed, Success Rate %
- Paginated audit table (20/page): DateTime, User, Visitor ID, IP, Country, City, Browser, Status badge
- Filters: period selector, success/fail/all toggle, username text search, date range (from/to)
- Export: CSV download (client-side) + XLSX download (client-side via
xlsxpackage)
SecurityConfigComponent (/security-config)
Mirrors SecurityConfigView.vue:
- Config form with 5 neumorphic number inputs:
- Max failed attempts per Visitor (default 5)
- Max failed attempts per IP (default 10)
- Block window in minutes (default 15)
- Multi-visitor alert threshold (default 3)
- Multi-visitor window in minutes (default 60)
- GET on init, PUT on save
- Toast feedback on save success/error
2.4 Menu Integration
menu.component.ts — add to allMenuItems:
{
label: 'menu.security.title',
id: 'security',
expanded: false,
children: [
{ label: 'menu.security.insights', id: 'security.insights' },
{ label: 'menu.security.device_intelligence', id: 'security.device_intelligence' },
{ label: 'menu.security.audit_log', id: 'security.audit_log' },
{ label: 'menu.security.security_config', id: 'security.security_config' },
]
}
i18n — add to en.json and pt-BR.json:
"security": {
"title": "Security",
"insights": "Insights",
"device_intelligence": "Device Intelligence",
"audit_log": "Audit Log",
"security_config": "Security Config"
}
app.routes.ts — add 4 routes as children under the MenuComponent parent, all with canActivate: [adminGuard].
admin.guard.ts (new file alongside auth.guard.ts) — functional CanActivateFn that should rely on the current cookie-backed platform session model instead of reading JWTs from browser storage. Any claim checks should come from the authenticated app state or a server-validated user profile, with redirects to /menu when the user lacks the required admin role.
Task 3: NewRelic Integration Plan
Decision
One NewRelic account with one license key, shared across all xtrakt services. Each service registers its own named application in NR. License key stored in GCP Secret Manager as xtrakt-json-newrelic.
3.1 Instrumented Services
| Service | NR Instrumentation | NR App Name |
|---|---|---|
xtrakt-sso-fe | Browser Agent (JS snippet in index.html) | xtrakt-sso-fe |
xtrakt-sso-be | .NET APM Agent (NewRelic.Agent NuGet) | xtrakt-sso-be |
xtrakt-platform-be | .NET APM Agent | xtrakt-platform-be |
xtrakt-billing-be | .NET APM Agent | xtrakt-billing-be |
xtrakt-vision-be | .NET APM Agent | xtrakt-vision-be |
xtrakt-api-be | .NET APM Agent | xtrakt-api-be |
Why one account: unified cross-service distributed tracing, correlated dashboards, single billing line, one secret to rotate.
APM Agent config per .NET service — newrelic.config (or env vars):
<application>
<name>xtrakt-{service-name}</name>
</application>
License key via env var NEW_RELIC_LICENSE_KEY injected from GCP Secret Manager at Cloud Run startup.
Browser Agent — inject the NR browser snippet into xtrakt-sso-fe/src/src/index.html <head>. Tracks: page views, AJAX calls, JS errors, Core Web Vitals (LCP, FID, CLS).
3.2 Custom NR Dashboard
Create one NR dashboard named "xtrakt Platform Health" containing:
- Throughput (req/min) per service — line chart
- Error rate % per service — line chart
- Apdex score per service — gauge
- Response time P50/P90/P99 — line chart
- Browser: LCP, FID, CLS — KPI tiles
- Top slowest transactions — table
- DB query latency (PostgreSQL) — line chart
3.3 In-App NR Dashboard (xtrakt-sso-fe)
New menu item: "New Relic" under MONITORING section in the sidebar.
NewRelicDashboardComponent (/monitoring/newrelic):
- Standalone Angular component, admin-only guard
- Period selector: 1h / 6h / 24h / 7d
- Calls
GET /monitoring/dashboard?period=onxtrakt-platform-be - Renders charts with Chart.js (same lib installed for Insights)
- Charts displayed: Throughput, Error Rate, Apdex, Response Time, Web Vitals
NerdGraph Proxy — xtrakt-platform-be:
Clean Architecture implementation:
- Domain:
INewRelicServiceinterface - Infra:
NewRelicService— readsNewRelic:ApiKeyfrom config, POSTs tohttps://api.newrelic.com/graphqlwith NerdGraph NRQL queries, parses timeseries response - Application:
GetDashboardDataHandler— orchestrates queries for each metric, assembles response DTO - Web:
MonitoringControllerwithGET /monitoring/dashboard
NR API key stored in GCP Secret Manager as xtrakt-json-newrelic (separate from the license key). Never exposed to the Angular client.
NerdGraph queries (examples):
{ actor { account(id: ACCOUNT_ID) {
nrql(query: "SELECT rate(count(*), 1 minute) FROM Transaction WHERE appName = 'xtrakt-platform-be' SINCE 24 hours ago TIMESERIES") { results }
}}}
Note: Replace
ACCOUNT_IDwith the actual NewRelic account ID obtained after account creation. Store it in GCP Secret Manager alongside the NR API key (xtrakt-json-newrelic), not hardcoded.
3.4 Distributed Tracing
With all APM agents on the same account, NR automatically correlates traces across services via W3C Trace Context headers. No additional config needed.
3.5 Implementation Order
- Create NR account, obtain license key and API key → store in GCP Secret Manager
- Add APM agent to each .NET service (NuGet + config)
- Add Browser Agent to
xtrakt-sso-fe - Create custom NR dashboard in NR UI
- Build NerdGraph proxy in
xtrakt-platform-be - Build
NewRelicDashboardComponentinxtrakt-sso-fe - Add MONITORING menu section + route
Implementation Sequence (across all tasks)
The three tasks are independent and can be planned/executed separately:
- Task 1 (Design) — Pure frontend CSS work. No backend changes. Can be executed first to establish the visual foundation before adding new screens.
- Task 2 (FingerprintJS + Admin) — Backend-first (entity → migration → handlers → controller), then frontend (components + menu). New API key:
CFoxHcakGXnWQ4vp6BXX. - Task 3 (NewRelic) — Infrastructure first (NR account setup), then backend APM agents, then browser agent, then NerdGraph proxy + dashboard component.
Each task gets its own implementation plan.