Skip to main content

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

  1. src/src/styles.scss — Replace all CSS variables with neumorphism tokens. Remove Google Fonts import. Set background-color: var(--neu-bg) on body.

  2. MenuComponent (menu.component.scss + menu.component.html) — Full sidebar redesign:

    • Background: var(--neu-bg) with right-side shadow 6px 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
  3. 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.

  4. 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 button
  • menu/menu.component.scss — full sidebar (see above)
  • templates/template-list.component.scss
  • templates/template-new-form.component.scss
  • templates/template-edit-form.component.scss
  • configs/config-list.component.scss
  • playground/playground.component.scss
  • profiles/users-at-profiles.component.scss
  • plans/plan-settings-list.component.scss
  • plans/plan-settings-form.component.scss
  • plans/plan-upgrade.component.scss
  • products/products-list.component.scss
  • products/product-form.component.scss
  • journeys/journey-list.component.scss
  • journeys/journey-form.component.scss
  • journeys/journey-profiles.component.scss
  • billing/invoices-list.component.scss
  • billing/invoice-detail.component.scss
  • coming-soon/coming-soon.component.scss
  • shared/ 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 LoginAuditLog entity + SecurityParameter entity in xtrakt-sso-be
  • New AdminController in xtrakt-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_case column names
  • DbSet<LoginAuditLog> and DbSet<SecurityParameter> in OpenIdDictDbContext
  • Repository implementations extending Repository<T>
  • Register in InfraStartup.cs
  • EF Core migration: AddLoginAuditAndSecurityConfig

Application Layer — Handlers

Each handler folder under Handlers/Admin/:

HandlerInputOutput
GetOverviewHandlerperiod (24h/7d/30d)usage, uniqueVisitors, chartData, heatmap, topOrigins/browsers/countries
GetDevicesHandlerperiod, risk filterlist of device rows (grouped by visitorId)
GetAuditHandlerpage, pageSize, period, success filter, userName, dateFrom, dateTopaginated AuditRow list + total
GetSecurityConfigHandler5 security parameters
UpdateSecurityConfigHandler5 int valuessuccess
GetWorldMapHandlerperiodcountry code + count list
GetHeatmapHandlerperiod7×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-Id header
  • Read X-Forwarded-For (or fall back to RemoteIpAddress) for IP
  • Read User-Agent header
  • Pass all three into a new ConnectTokenRequest wrapper DTO alongside the OpenIddictRequest

ConnectTokenHandler — extended to:

  1. Receive visitorId, ipAddress, userAgent from the enriched request DTO
  2. Call GeoIP service async (fire-and-forget; failure is silently swallowed)
  3. Write LoginAuditLog record 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 WorldMapComponent standalone 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's WorldMapChart.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 xlsx package)

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

ServiceNR InstrumentationNR App Name
xtrakt-sso-feBrowser 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 Agentxtrakt-platform-be
xtrakt-billing-be.NET APM Agentxtrakt-billing-be
xtrakt-vision-be.NET APM Agentxtrakt-vision-be
xtrakt-api-be.NET APM Agentxtrakt-api-be

Why one account: unified cross-service distributed tracing, correlated dashboards, single billing line, one secret to rotate.

APM Agent config per .NET servicenewrelic.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= on xtrakt-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: INewRelicService interface
  • Infra: NewRelicService — reads NewRelic:ApiKey from config, POSTs to https://api.newrelic.com/graphql with NerdGraph NRQL queries, parses timeseries response
  • Application: GetDashboardDataHandler — orchestrates queries for each metric, assembles response DTO
  • Web: MonitoringController with GET /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_ID with 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

  1. Create NR account, obtain license key and API key → store in GCP Secret Manager
  2. Add APM agent to each .NET service (NuGet + config)
  3. Add Browser Agent to xtrakt-sso-fe
  4. Create custom NR dashboard in NR UI
  5. Build NerdGraph proxy in xtrakt-platform-be
  6. Build NewRelicDashboardComponent in xtrakt-sso-fe
  7. Add MONITORING menu section + route

Implementation Sequence (across all tasks)

The three tasks are independent and can be planned/executed separately:

  1. Task 1 (Design) — Pure frontend CSS work. No backend changes. Can be executed first to establish the visual foundation before adding new screens.
  2. Task 2 (FingerprintJS + Admin) — Backend-first (entity → migration → handlers → controller), then frontend (components + menu). New API key: CFoxHcakGXnWQ4vp6BXX.
  3. 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.