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
|
|
|
/**
|
|
|
|
|
* Integration auth health + token expiry checks.
|
|
|
|
|
*
|
|
|
|
|
* Lives outside any specific integration's client because the goal is to
|
|
|
|
|
* surface "did anything just break silently" without forcing the dashboard
|
|
|
|
|
* to depend on every per-tool client. Each check is a minimal authenticated
|
|
|
|
|
* call against a cheap endpoint of the target API; results are cached
|
|
|
|
|
* in-process for a few minutes so concurrent dashboard hits don't fan out
|
|
|
|
|
* into a wave of API calls.
|
|
|
|
|
*
|
|
|
|
|
* Usage:
|
|
|
|
|
* const results = await checkIntegrationHealth();
|
|
|
|
|
*
|
|
|
|
|
* Tools covered live: S1, Datto RMM, IT Glue, Autotask. Others report
|
|
|
|
|
* configured / not_configured only — extending to live checks is mechanical.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
export type HealthStatus =
|
|
|
|
|
| 'ok' // configured, auth succeeded
|
|
|
|
|
| 'auth_failed' // configured, server returned 401/403
|
|
|
|
|
| 'unreachable' // configured, network/DNS/TLS error
|
|
|
|
|
| 'not_configured' // env vars missing
|
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
|
|
|
| 'unknown' // configured, no live check implemented
|
|
|
|
|
| 'disabled'; // operator-suppressed (see INTEGRATIONS_DISABLED env)
|
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
|
|
|
|
|
|
|
|
export interface TokenExpiry {
|
|
|
|
|
envVar: string;
|
|
|
|
|
expiresAt: string; // ISO
|
|
|
|
|
daysRemaining: number; // negative when already expired
|
|
|
|
|
subject?: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface IntegrationHealth {
|
|
|
|
|
key: string;
|
|
|
|
|
name: string;
|
|
|
|
|
category: 'psa' | 'rmm' | 'docs' | 'security' | 'backup' | 'network' | 'identity' | 'mdm' | 'mail' | 'finance' | 'productivity' | 'llm';
|
|
|
|
|
status: HealthStatus;
|
|
|
|
|
configured: boolean;
|
|
|
|
|
latencyMs?: number;
|
|
|
|
|
error?: string | null;
|
|
|
|
|
tokenExpiry?: TokenExpiry | null;
|
|
|
|
|
checkedAt: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface CacheEntry {
|
|
|
|
|
expiresAt: number;
|
|
|
|
|
data: IntegrationHealth[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
|
|
|
let cache: CacheEntry | null = null;
|
|
|
|
|
|
|
|
|
|
function decodeJwt(token: string, envVar: string): TokenExpiry | null {
|
|
|
|
|
if (!token || !token.startsWith('eyJ')) return null;
|
|
|
|
|
const parts = token.split('.');
|
|
|
|
|
if (parts.length < 2) return null;
|
|
|
|
|
try {
|
|
|
|
|
const b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
|
|
|
const pad = b64.length % 4 ? '='.repeat(4 - (b64.length % 4)) : '';
|
|
|
|
|
const json = Buffer.from(b64 + pad, 'base64').toString('utf8');
|
|
|
|
|
const claims = JSON.parse(json) as { exp?: number; sub?: string };
|
|
|
|
|
if (!claims.exp) return null;
|
|
|
|
|
const expiresMs = claims.exp * 1000;
|
|
|
|
|
return {
|
|
|
|
|
envVar,
|
|
|
|
|
expiresAt: new Date(expiresMs).toISOString(),
|
|
|
|
|
daysRemaining: (expiresMs - Date.now()) / 86400_000,
|
|
|
|
|
subject: claims.sub ?? null,
|
|
|
|
|
};
|
|
|
|
|
} catch {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function timed<T>(fn: () => Promise<T>): Promise<{ result: T; latencyMs: number }> {
|
|
|
|
|
const start = Date.now();
|
|
|
|
|
const result = await fn();
|
|
|
|
|
return { result, latencyMs: Date.now() - start };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function liveCheck(opts: {
|
|
|
|
|
url: string;
|
|
|
|
|
headers: Record<string, string>;
|
|
|
|
|
timeoutMs?: number;
|
|
|
|
|
}): Promise<{ status: HealthStatus; error: string | null; latencyMs: number; httpStatus: number | null }> {
|
|
|
|
|
const ctrl = new AbortController();
|
|
|
|
|
const timeout = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 8000);
|
|
|
|
|
try {
|
|
|
|
|
const { result, latencyMs } = await timed(() =>
|
|
|
|
|
fetch(opts.url, { headers: { accept: 'application/json', ...opts.headers }, signal: ctrl.signal })
|
|
|
|
|
);
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
if (result.ok) return { status: 'ok', error: null, latencyMs, httpStatus: result.status };
|
|
|
|
|
if (result.status === 401 || result.status === 403) {
|
|
|
|
|
const body = await result.text().catch(() => '');
|
|
|
|
|
return {
|
|
|
|
|
status: 'auth_failed',
|
|
|
|
|
error: `${result.status}: ${body.slice(0, 200)}`,
|
|
|
|
|
latencyMs,
|
|
|
|
|
httpStatus: result.status,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
status: 'unknown',
|
|
|
|
|
error: `${result.status} ${result.statusText}`,
|
|
|
|
|
latencyMs,
|
|
|
|
|
httpStatus: result.status,
|
|
|
|
|
};
|
|
|
|
|
} catch (err) {
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
return {
|
|
|
|
|
status: 'unreachable',
|
|
|
|
|
error: err instanceof Error ? err.message : String(err),
|
|
|
|
|
latencyMs: opts.timeoutMs ?? 8000,
|
|
|
|
|
httpStatus: null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function checkS1(): Promise<IntegrationHealth> {
|
|
|
|
|
const url = process.env.S1_API_URL?.replace(/\/$/, '');
|
|
|
|
|
const token = process.env.S1_API_TOKEN;
|
|
|
|
|
const checkedAt = new Date().toISOString();
|
|
|
|
|
if (!url || !token) {
|
|
|
|
|
return { key: 's1', name: 'SentinelOne', category: 'security', status: 'not_configured', configured: false, checkedAt };
|
|
|
|
|
}
|
|
|
|
|
const tokenExpiry = decodeJwt(token, 'S1_API_TOKEN');
|
|
|
|
|
const live = await liveCheck({
|
|
|
|
|
url: `${url}/web/api/v2.1/system/info`,
|
|
|
|
|
headers: { Authorization: `ApiToken ${token}` },
|
|
|
|
|
});
|
|
|
|
|
return {
|
|
|
|
|
key: 's1',
|
|
|
|
|
name: 'SentinelOne',
|
|
|
|
|
category: 'security',
|
|
|
|
|
status: live.status,
|
|
|
|
|
configured: true,
|
|
|
|
|
latencyMs: live.latencyMs,
|
|
|
|
|
error: live.error,
|
|
|
|
|
tokenExpiry,
|
|
|
|
|
checkedAt,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function checkDattoRmm(): Promise<IntegrationHealth> {
|
|
|
|
|
const url = process.env.DATTO_RMM_API_URL?.replace(/\/$/, '');
|
|
|
|
|
const key = process.env.DATTO_RMM_API_KEY;
|
|
|
|
|
const secret = process.env.DATTO_RMM_API_SECRET;
|
|
|
|
|
const checkedAt = new Date().toISOString();
|
|
|
|
|
if (!url || !key || !secret) {
|
|
|
|
|
return { key: 'datto_rmm', name: 'Datto RMM', category: 'rmm', status: 'not_configured', configured: false, checkedAt };
|
|
|
|
|
}
|
|
|
|
|
// OAuth password grant — same flow the client uses internally.
|
|
|
|
|
const start = Date.now();
|
|
|
|
|
try {
|
|
|
|
|
const ctrl = new AbortController();
|
|
|
|
|
const t = setTimeout(() => ctrl.abort(), 8000);
|
|
|
|
|
const tokRes = await fetch(`${url}/auth/oauth/token`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'content-type': 'application/x-www-form-urlencoded',
|
|
|
|
|
authorization: 'Basic ' + Buffer.from('public-client:public').toString('base64'),
|
|
|
|
|
},
|
|
|
|
|
body: `grant_type=password&username=${encodeURIComponent(key)}&password=${encodeURIComponent(secret)}`,
|
|
|
|
|
signal: ctrl.signal,
|
|
|
|
|
});
|
|
|
|
|
clearTimeout(t);
|
|
|
|
|
if (tokRes.ok) {
|
|
|
|
|
return { key: 'datto_rmm', name: 'Datto RMM', category: 'rmm', status: 'ok', configured: true, latencyMs: Date.now() - start, checkedAt };
|
|
|
|
|
}
|
|
|
|
|
if (tokRes.status === 401 || tokRes.status === 403) {
|
|
|
|
|
const body = await tokRes.text().catch(() => '');
|
|
|
|
|
return {
|
|
|
|
|
key: 'datto_rmm', name: 'Datto RMM', category: 'rmm', status: 'auth_failed',
|
|
|
|
|
configured: true, latencyMs: Date.now() - start,
|
|
|
|
|
error: `${tokRes.status}: ${body.slice(0, 200)}`, checkedAt,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
key: 'datto_rmm', name: 'Datto RMM', category: 'rmm', status: 'unknown',
|
|
|
|
|
configured: true, latencyMs: Date.now() - start,
|
|
|
|
|
error: `${tokRes.status} ${tokRes.statusText}`, checkedAt,
|
|
|
|
|
};
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return {
|
|
|
|
|
key: 'datto_rmm', name: 'Datto RMM', category: 'rmm', status: 'unreachable',
|
|
|
|
|
configured: true, latencyMs: Date.now() - start,
|
|
|
|
|
error: err instanceof Error ? err.message : String(err), checkedAt,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function checkItglue(): Promise<IntegrationHealth> {
|
|
|
|
|
const apiKey = process.env.ITGLUE_API_KEY;
|
|
|
|
|
const checkedAt = new Date().toISOString();
|
|
|
|
|
if (!apiKey) {
|
|
|
|
|
return { key: 'itglue', name: 'IT Glue', category: 'docs', status: 'not_configured', configured: false, checkedAt };
|
|
|
|
|
}
|
|
|
|
|
const live = await liveCheck({
|
|
|
|
|
url: 'https://api.itglue.com/organizations?page[size]=1',
|
|
|
|
|
headers: { 'x-api-key': apiKey },
|
|
|
|
|
});
|
|
|
|
|
return {
|
|
|
|
|
key: 'itglue', name: 'IT Glue', category: 'docs',
|
|
|
|
|
status: live.status, configured: true,
|
|
|
|
|
latencyMs: live.latencyMs, error: live.error,
|
|
|
|
|
checkedAt,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function checkAutotask(): Promise<IntegrationHealth> {
|
|
|
|
|
const url = process.env.AUTOTASK_API_URL?.replace(/\/$/, '');
|
|
|
|
|
const user = process.env.AUTOTASK_USERNAME;
|
|
|
|
|
const secret = process.env.AUTOTASK_SECRET;
|
|
|
|
|
const code = process.env.AUTOTASK_API_INTEGRATION_CODE;
|
|
|
|
|
const checkedAt = new Date().toISOString();
|
|
|
|
|
if (!url || !user || !secret || !code) {
|
|
|
|
|
return { key: 'autotask', name: 'Autotask', category: 'psa', status: 'not_configured', configured: false, checkedAt };
|
|
|
|
|
}
|
|
|
|
|
// Cheapest authenticated call — version endpoint (not behind auth at all
|
|
|
|
|
// tenants, but failing here usually means URL/credential mismatch).
|
|
|
|
|
const live = await liveCheck({
|
|
|
|
|
url: `${url}/v1.0/Version`,
|
|
|
|
|
headers: {
|
|
|
|
|
ApiIntegrationCode: code,
|
|
|
|
|
UserName: user,
|
|
|
|
|
Secret: secret,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return {
|
|
|
|
|
key: 'autotask', name: 'Autotask', category: 'psa',
|
|
|
|
|
status: live.status, configured: true,
|
|
|
|
|
latencyMs: live.latencyMs, error: live.error,
|
|
|
|
|
checkedAt,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function checkConfigOnly(
|
|
|
|
|
key: string,
|
|
|
|
|
name: string,
|
|
|
|
|
category: IntegrationHealth['category'],
|
|
|
|
|
envVars: string[]
|
|
|
|
|
): IntegrationHealth {
|
|
|
|
|
const checkedAt = new Date().toISOString();
|
|
|
|
|
const allSet = envVars.every((v) => !!process.env[v]);
|
|
|
|
|
return {
|
|
|
|
|
key, name, category,
|
|
|
|
|
status: allSet ? 'unknown' : 'not_configured',
|
|
|
|
|
configured: allSet,
|
|
|
|
|
checkedAt,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
/**
|
|
|
|
|
* Operator-side disable list. Set INTEGRATIONS_DISABLED to a comma- or
|
|
|
|
|
* space-separated list of integration keys (or aliases) to suppress them
|
|
|
|
|
* from the /status page and the top-bar indicator. Disabled entries
|
|
|
|
|
* render muted and don't count toward failure summaries.
|
|
|
|
|
*
|
|
|
|
|
* Aliases:
|
|
|
|
|
* sentinelone, s1 → s1
|
|
|
|
|
* datto, datto-rmm → datto_rmm
|
|
|
|
|
* itglue, it-glue → itglue
|
|
|
|
|
* msgraph, ms-graph → msgraph
|
|
|
|
|
*/
|
|
|
|
|
const KEY_ALIASES: Record<string, string> = {
|
|
|
|
|
sentinelone: 's1',
|
|
|
|
|
's1': 's1',
|
|
|
|
|
datto: 'datto_rmm',
|
|
|
|
|
'datto-rmm': 'datto_rmm',
|
|
|
|
|
'datto_rmm': 'datto_rmm',
|
|
|
|
|
itglue: 'itglue',
|
|
|
|
|
'it-glue': 'itglue',
|
|
|
|
|
'it_glue': 'itglue',
|
|
|
|
|
msgraph: 'msgraph',
|
|
|
|
|
'ms-graph': 'msgraph',
|
|
|
|
|
'ms_graph': 'msgraph',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function getDisabledKeys(): Set<string> {
|
|
|
|
|
const raw = process.env.INTEGRATIONS_DISABLED;
|
|
|
|
|
if (!raw) return new Set();
|
|
|
|
|
return new Set(
|
|
|
|
|
raw
|
|
|
|
|
.split(/[\s,]+/)
|
|
|
|
|
.map((s) => s.trim().toLowerCase())
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.map((s) => KEY_ALIASES[s] ?? s),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyDisableOverlay(items: IntegrationHealth[]): IntegrationHealth[] {
|
|
|
|
|
const disabled = getDisabledKeys();
|
|
|
|
|
if (disabled.size === 0) return items;
|
|
|
|
|
return items.map((item) =>
|
|
|
|
|
disabled.has(item.key)
|
|
|
|
|
? { ...item, status: 'disabled', error: null, configured: false }
|
|
|
|
|
: item,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
export async function checkIntegrationHealth(opts?: { skipCache?: boolean }): Promise<IntegrationHealth[]> {
|
|
|
|
|
if (!opts?.skipCache && cache && cache.expiresAt > Date.now()) {
|
|
|
|
|
return cache.data;
|
|
|
|
|
}
|
|
|
|
|
const results = await Promise.all([
|
|
|
|
|
checkAutotask(),
|
|
|
|
|
checkDattoRmm(),
|
|
|
|
|
checkItglue(),
|
|
|
|
|
checkS1(),
|
|
|
|
|
Promise.resolve(checkConfigOnly('veeam', 'Veeam VSPC', 'backup',
|
|
|
|
|
['VEEAM_VSPC_URL', 'VEEAM_VSPC_API_KEY'])),
|
|
|
|
|
Promise.resolve(checkConfigOnly('msgraph', 'Microsoft Graph', 'productivity',
|
|
|
|
|
['MSGRAPH_CLIENT_ID', 'MSGRAPH_CLIENT_SECRET', 'MSGRAPH_TENANT_ID'])),
|
|
|
|
|
Promise.resolve(checkConfigOnly('auvik', 'Auvik', 'network',
|
|
|
|
|
['AUVIK_API_URL', 'AUVIK_API_USER', 'AUVIK_API_KEY'])),
|
|
|
|
|
Promise.resolve(checkConfigOnly('addigy', 'Addigy', 'mdm',
|
|
|
|
|
['ADDIGY_API_URL', 'ADDIGY_API_TOKEN', 'ADDIGY_ORG_ID'])),
|
|
|
|
|
Promise.resolve(checkConfigOnly('mimecast', 'Mimecast', 'mail',
|
|
|
|
|
['MIMECAST_CLIENT_ID', 'MIMECAST_CLIENT_SECRET'])),
|
|
|
|
|
Promise.resolve(checkConfigOnly('duo', 'Duo', 'identity',
|
|
|
|
|
['DUO_API_HOST', 'DUO_INTEGRATION_KEY', 'DUO_SECRET_KEY'])),
|
|
|
|
|
Promise.resolve(checkConfigOnly('zabbix', 'Zabbix', 'network',
|
|
|
|
|
['ZABBIX_API_URL', 'ZABBIX_API_TOKEN'])),
|
|
|
|
|
Promise.resolve(checkConfigOnly('qbo', 'QuickBooks Online', 'finance',
|
|
|
|
|
['QBO_CLIENT_ID', 'QBO_CLIENT_SECRET'])),
|
|
|
|
|
Promise.resolve(checkConfigOnly('anthropic', 'Anthropic', 'llm',
|
|
|
|
|
['ANTHROPIC_API_KEY'])),
|
|
|
|
|
]);
|
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
|
|
|
const overlaid = applyDisableOverlay(results);
|
|
|
|
|
cache = { expiresAt: Date.now() + CACHE_TTL_MS, data: overlaid };
|
|
|
|
|
return overlaid;
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function clearIntegrationHealthCache(): void {
|
|
|
|
|
cache = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface HealthSummary {
|
|
|
|
|
total: number;
|
|
|
|
|
ok: number;
|
|
|
|
|
failed: number;
|
|
|
|
|
notConfigured: number;
|
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
|
|
|
disabled: number;
|
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
|
|
|
expiringWithin14Days: number;
|
|
|
|
|
expired: number;
|
|
|
|
|
hasIssues: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function summarize(items: IntegrationHealth[]): HealthSummary {
|
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
|
|
|
let ok = 0, failed = 0, notConfigured = 0, disabled = 0;
|
|
|
|
|
let expiringWithin14Days = 0, expired = 0;
|
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
|
|
|
for (const i of items) {
|
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
|
|
|
if (i.status === 'disabled') {
|
|
|
|
|
disabled += 1;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
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
|
|
|
if (i.status === 'ok' || i.status === 'unknown') ok += 1;
|
|
|
|
|
else if (i.status === 'auth_failed' || i.status === 'unreachable') failed += 1;
|
|
|
|
|
else if (i.status === 'not_configured') notConfigured += 1;
|
|
|
|
|
if (i.tokenExpiry) {
|
|
|
|
|
if (i.tokenExpiry.daysRemaining <= 0) expired += 1;
|
|
|
|
|
else if (i.tokenExpiry.daysRemaining <= 14) expiringWithin14Days += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
total: items.length,
|
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
|
|
|
ok, failed, notConfigured, disabled,
|
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
|
|
|
expiringWithin14Days, expired,
|
|
|
|
|
hasIssues: failed > 0 || expired > 0 || expiringWithin14Days > 0,
|
|
|
|
|
};
|
|
|
|
|
}
|