feat: RMM Overshell, IT Glue audit/write-back, LogLift, link-aware bundles, dashboard overhaul
- RMM Overshell (migration 077): admin page, dispatch UI, executor/worker, target
resolver, script registry (AD/DHCP/DNS/event-log/services/software/network/loglift)
- LogLift evidence pipeline (migration 078): upload webhook, B2 storage client,
receiver/matcher, EventLogCollector PowerShell script
- IT Glue audit + write-back (migrations 075, 076): asset-audit runner, ticket
xrefs, applications/configurations browse pages + apply/revert/audit endpoints
- Link-aware analyzer bundles (migration 073) + provider toggle (migration 074):
link-discovery service, OpenRouter LLM provider, related-tickets/itglue-suggestion
panels, analyze-bundle endpoint
- Endpoint data model + device-link reconciliation (migrations 079, 080): conflicts
admin page, reconciler service, resolve endpoints
- Dashboard overhaul: integration-health service + alerts, overview/health endpoints
- Permissions: add itglue + rmm scopes; middleware: public /api/rmm/loglift route
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 07:13:18 -04:00
'use client' ;
import { useEffect , useState } from 'react' ;
import { toast } from 'sonner' ;
import { Card , CardContent , CardHeader , CardTitle } from '@/components/ui/card' ;
import { Badge } from '@/components/ui/badge' ;
import { Button } from '@/components/ui/button' ;
import { Skeleton } from '@/components/ui/skeleton' ;
import { Alert , AlertDescription , AlertTitle } from '@/components/ui/alert' ;
import {
Select ,
SelectContent ,
SelectItem ,
SelectTrigger ,
SelectValue ,
} from '@/components/ui/select' ;
import { CheckCircle2 , AlertTriangle , Loader2 } from 'lucide-react' ;
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
import { PageHeader } from '@/components/navigation/page-header' ;
feat: RMM Overshell, IT Glue audit/write-back, LogLift, link-aware bundles, dashboard overhaul
- RMM Overshell (migration 077): admin page, dispatch UI, executor/worker, target
resolver, script registry (AD/DHCP/DNS/event-log/services/software/network/loglift)
- LogLift evidence pipeline (migration 078): upload webhook, B2 storage client,
receiver/matcher, EventLogCollector PowerShell script
- IT Glue audit + write-back (migrations 075, 076): asset-audit runner, ticket
xrefs, applications/configurations browse pages + apply/revert/audit endpoints
- Link-aware analyzer bundles (migration 073) + provider toggle (migration 074):
link-discovery service, OpenRouter LLM provider, related-tickets/itglue-suggestion
panels, analyze-bundle endpoint
- Endpoint data model + device-link reconciliation (migrations 079, 080): conflicts
admin page, reconciler service, resolve endpoints
- Dashboard overhaul: integration-health service + alerts, overview/health endpoints
- Permissions: add itglue + rmm scopes; middleware: public /api/rmm/loglift route
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 07:13:18 -04:00
interface Candidate {
ciId : string ;
confidence : string | null ;
hostname : string | null ;
serial : string | null ;
mac : string | null ;
companyId : string | null ;
companyName : string | null ;
isDeleted : boolean ;
}
interface Review {
id : string ;
detectedAt : string ;
xref : {
id : string ;
source : string ;
sourceId : string ;
hostname : string | null ;
serial : string | null ;
mac : string | null ;
companyId : string | null ;
companyName : string | null ;
lastSeenAt : string | null ;
} ;
candidates : Candidate [ ] ;
}
const SOURCES = [ 'all' , 'datto_rmm' , 'itglue' , 's1' , 'veeam' ] as const ;
type SourceFilter = ( typeof SOURCES ) [ number ] ;
function confidenceColor ( c : string | null ) : 'default' | 'secondary' | 'outline' {
if ( c === 'exact_serial' ) return 'default' ;
if ( c === 'mac' ) return 'default' ;
if ( c === 'hostname_in_company' ) return 'secondary' ;
return 'outline' ;
}
export default function DeviceLinkConflictsPage() {
const [ items , setItems ] = useState < Review [ ] | null > ( null ) ;
const [ total , setTotal ] = useState ( 0 ) ;
const [ error , setError ] = useState < string | null > ( null ) ;
const [ source , setSource ] = useState < SourceFilter > ( 'all' ) ;
const [ resolving , setResolving ] = useState < string | null > ( null ) ;
async function load ( ) : Promise < void > {
setError ( null ) ;
setItems ( null ) ;
try {
const params = new URLSearchParams ( { limit : '100' } ) ;
if ( source !== 'all' ) params . set ( 'source' , source ) ;
const res = await fetch ( ` /api/admin/device-link-conflicts? ${ params } ` ) ;
if ( ! res . ok ) {
const data = ( await res . json ( ) . catch ( ( ) = > ( { } ) ) ) as { error? : string } ;
throw new Error ( data . error ? ? ` Request failed: ${ res . status } ` ) ;
}
const data = ( await res . json ( ) ) as { items : Review [ ] ; total : number } ;
setItems ( data . items ) ;
setTotal ( data . total ) ;
} catch ( err ) {
setError ( err instanceof Error ? err . message : 'Unknown error' ) ;
}
}
useEffect ( ( ) = > {
void load ( ) ;
} , [ source ] ) ;
async function resolve ( reviewId : string , ciId : string ) : Promise < void > {
setResolving ( ` ${ reviewId } : ${ ciId } ` ) ;
try {
const res = await fetch ( ` /api/admin/device-link-conflicts/ ${ reviewId } /resolve ` , {
method : 'POST' ,
headers : { 'content-type' : 'application/json' } ,
body : JSON.stringify ( { ciId } ) ,
} ) ;
const data = ( await res . json ( ) . catch ( ( ) = > ( { } ) ) ) as { error? : string } ;
if ( ! res . ok ) throw new Error ( data . error ? ? ` Request failed: ${ res . status } ` ) ;
toast . success ( ` Linked to CI ${ ciId } ` ) ;
setItems ( ( prev ) = > prev ? . filter ( ( r ) = > r . id !== reviewId ) ? ? null ) ;
setTotal ( ( t ) = > Math . max ( 0 , t - 1 ) ) ;
} catch ( err ) {
toast . error ( err instanceof Error ? err . message : 'Resolve failed' ) ;
} finally {
setResolving ( null ) ;
}
}
return (
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
< >
< PageHeader
title = "Device-link conflicts"
description = "Cases where the reconciler found two or more configuration_items matching one external device record. Pick the right CI to break the tie."
breadcrumbs = { [ { label : 'Admin' , href : '/admin' } , { label : 'Device-Link Conflicts' } ] }
accent
/ >
< div className = "container mx-auto px-6 py-6 max-w-6xl space-y-6" >
feat: RMM Overshell, IT Glue audit/write-back, LogLift, link-aware bundles, dashboard overhaul
- RMM Overshell (migration 077): admin page, dispatch UI, executor/worker, target
resolver, script registry (AD/DHCP/DNS/event-log/services/software/network/loglift)
- LogLift evidence pipeline (migration 078): upload webhook, B2 storage client,
receiver/matcher, EventLogCollector PowerShell script
- IT Glue audit + write-back (migrations 075, 076): asset-audit runner, ticket
xrefs, applications/configurations browse pages + apply/revert/audit endpoints
- Link-aware analyzer bundles (migration 073) + provider toggle (migration 074):
link-discovery service, OpenRouter LLM provider, related-tickets/itglue-suggestion
panels, analyze-bundle endpoint
- Endpoint data model + device-link reconciliation (migrations 079, 080): conflicts
admin page, reconciler service, resolve endpoints
- Dashboard overhaul: integration-health service + alerts, overview/health endpoints
- Permissions: add itglue + rmm scopes; middleware: public /api/rmm/loglift route
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 07:13:18 -04:00
< Card >
< CardHeader >
< CardTitle className = "flex items-center gap-2" >
< AlertTriangle className = "size-5 text-amber-500" / >
Device - link conflicts
< / CardTitle >
< / CardHeader >
< CardContent className = "space-y-4" >
< p className = "text-sm text-muted-foreground" >
Cases where the reconciler found two or more configuration_items
matching one external device record . Pick the right CI to break the
tie . Skipped rows stay unlinked until resolved .
< / p >
< div className = "flex items-center gap-3" >
< span className = "text-sm font-medium" > Source : < / span >
< Select value = { source } onValueChange = { ( v ) = > setSource ( v as SourceFilter ) } >
< SelectTrigger className = "w-[200px]" >
< SelectValue / >
< / SelectTrigger >
< SelectContent >
{ SOURCES . map ( ( s ) = > (
< SelectItem key = { s } value = { s } >
{ s }
< / SelectItem >
) ) }
< / SelectContent >
< / Select >
< span className = "text-sm text-muted-foreground" >
{ total } unresolved { total === 1 ? 'conflict' : 'conflicts' }
< / span >
< / div >
{ error && (
< Alert variant = "destructive" >
< AlertTitle > Failed to load < / AlertTitle >
< AlertDescription > { error } < / AlertDescription >
< / Alert >
) }
{ items === null && ! error && (
< div className = "space-y-2" >
< Skeleton className = "h-24 w-full" / >
< Skeleton className = "h-24 w-full" / >
< Skeleton className = "h-24 w-full" / >
< / div >
) }
{ items !== null && items . length === 0 && ! error && (
< Alert >
< CheckCircle2 className = "size-4" / >
< AlertTitle > No conflicts < / AlertTitle >
< AlertDescription >
Nothing waiting for review on this filter .
< / AlertDescription >
< / Alert >
) }
{ items ? . map ( ( r ) = > (
< Card key = { r . id } className = "border-amber-200" >
< CardHeader className = "pb-3" >
< div className = "flex items-baseline justify-between gap-3" >
< div className = "space-y-0.5" >
< div className = "text-sm font-mono" >
{ r . xref . source } : { r . xref . sourceId }
< / div >
< div className = "text-base font-medium" >
{ r . xref . hostname ? ? '(no hostname)' }
{ r . xref . companyName && (
< span className = "text-sm text-muted-foreground ml-2" >
@ { r . xref . companyName }
< / span >
) }
< / div >
< / div >
< Badge variant = "outline" className = "text-xs" >
{ r . candidates . length } candidates
< / Badge >
< / div >
< div className = "text-xs text-muted-foreground space-x-3" >
{ r . xref . serial && < span > serial : { r . xref . serial } < / span > }
{ r . xref . mac && < span > mac : { r . xref . mac } < / span > }
{ r . xref . lastSeenAt && (
< span > last seen : { new Date ( r . xref . lastSeenAt ) . toLocaleString ( ) } < / span >
) }
< / div >
< / CardHeader >
< CardContent className = "space-y-2" >
{ r . candidates . map ( ( c ) = > {
const isResolving = resolving === ` ${ r . id } : ${ c . ciId } ` ;
return (
< div
key = { c . ciId }
className = "flex items-center justify-between gap-3 rounded-md border p-3"
>
< div className = "space-y-0.5 min-w-0 flex-1" >
< div className = "flex items-center gap-2 text-sm" >
< span className = "font-medium truncate" >
{ c . hostname ? ? '(no hostname)' }
< / span >
{ c . isDeleted && (
< Badge variant = "destructive" className = "text-xs" >
deleted
< / Badge >
) }
{ c . confidence && (
< Badge
variant = { confidenceColor ( c . confidence ) }
className = "text-xs"
>
{ c . confidence }
< / Badge >
) }
< / div >
< div className = "text-xs text-muted-foreground space-x-3" >
< span className = "font-mono" > CI { c . ciId } < / span >
{ c . serial && < span > serial : { c . serial } < / span > }
{ c . companyName && < span > @ { c . companyName } < / span > }
< / div >
< / div >
< Button
size = "sm"
disabled = { isResolving || c . isDeleted }
onClick = { ( ) = > void resolve ( r . id , c . ciId ) }
>
{ isResolving ? (
< >
< Loader2 className = "size-3 animate-spin mr-1" / >
Linking …
< / >
) : (
'Link to this CI'
) }
< / Button >
< / div >
) ;
} ) }
< / CardContent >
< / Card >
) ) }
< / CardContent >
< / Card >
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
< / div >
< / >
feat: RMM Overshell, IT Glue audit/write-back, LogLift, link-aware bundles, dashboard overhaul
- RMM Overshell (migration 077): admin page, dispatch UI, executor/worker, target
resolver, script registry (AD/DHCP/DNS/event-log/services/software/network/loglift)
- LogLift evidence pipeline (migration 078): upload webhook, B2 storage client,
receiver/matcher, EventLogCollector PowerShell script
- IT Glue audit + write-back (migrations 075, 076): asset-audit runner, ticket
xrefs, applications/configurations browse pages + apply/revert/audit endpoints
- Link-aware analyzer bundles (migration 073) + provider toggle (migration 074):
link-discovery service, OpenRouter LLM provider, related-tickets/itglue-suggestion
panels, analyze-bundle endpoint
- Endpoint data model + device-link reconciliation (migrations 079, 080): conflicts
admin page, reconciler service, resolve endpoints
- Dashboard overhaul: integration-health service + alerts, overview/health endpoints
- Permissions: add itglue + rmm scopes; middleware: public /api/rmm/loglift route
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 07:13:18 -04:00
) ;
}