wulf-pulse/app/admin/rmm-overshell/page.tsx

282 lines
10 KiB
TypeScript
Raw Normal View History

'use client';
import { useEffect, useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Loader2, RefreshCw, Terminal } from 'lucide-react';
import { toast } from 'sonner';
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';
interface Settings {
overshellComponentUid: string | null;
overshellComponentName: string | null;
overshellVariableName: string;
discoveredAt: string | null;
logliftComponentUid: string | null;
logliftComponentName: string | null;
logliftDiscoveredAt: string | null;
updatedAt: string;
}
interface ExecRow {
id: string;
scriptId: string;
jobName: string;
targetHostname: string | null;
status: 'queued' | 'running' | 'complete' | 'failed' | 'timeout';
exitCode: number | null;
errorMessage: string | null;
performedByUserId: string | null;
queuedAt: string;
completedAt: string | null;
}
export default function RmmOvershellAdminPage() {
const [settings, setSettings] = useState<Settings | null>(null);
const [counts, setCounts] = useState<{ total: string; running: string; failed_24h: string } | null>(null);
const [executions, setExecutions] = useState<ExecRow[] | null>(null);
const [error, setError] = useState<string | null>(null);
const [discovering, setDiscovering] = useState(false);
const [discoveringLoglift, setDiscoveringLoglift] = useState(false);
async function loadAll() {
try {
const [s, e] = await Promise.all([
fetch('/api/admin/rmm/settings').then((r) => r.json()),
fetch('/api/rmm/executions?limit=50').then((r) => r.json()),
]);
if (s.error) throw new Error(s.error);
setSettings(s.settings);
setCounts(s.counts);
setExecutions(e.executions ?? []);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
}
}
useEffect(() => {
void loadAll();
}, []);
async function discover() {
setDiscovering(true);
try {
const res = await fetch('/api/admin/rmm/settings/discover', { method: 'POST' });
const data = await res.json();
if (!res.ok) throw new Error(data.message ?? data.error ?? 'Discovery failed');
toast.success(`Found component: ${data.discovered?.name ?? 'unknown'}`);
void loadAll();
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Discovery failed');
} finally {
setDiscovering(false);
}
}
async function discoverLoglift() {
setDiscoveringLoglift(true);
try {
const res = await fetch('/api/admin/rmm/settings/discover-loglift', {
method: 'POST',
});
const data = await res.json();
if (!res.ok) throw new Error(data.message ?? data.error ?? 'Discovery failed');
toast.success(`Found LogLift component: ${data.discovered?.name ?? 'unknown'}`);
void loadAll();
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Discovery failed');
} finally {
setDiscoveringLoglift(false);
}
}
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="RMM Overshell"
description="Datto RMM PowerShell evidence pipeline — settings, executions, and worker activity."
breadcrumbs={[{ label: 'Admin', href: '/admin' }, { label: 'RMM Overshell' }]}
accent
/>
<div className="container mx-auto px-6 py-6 max-w-5xl space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Terminal className="w-5 h-5" />
RMM Overshell
</CardTitle>
<p className="text-sm text-muted-foreground">
Datto RMM PowerShell evidence pipeline. Pulse dispatches scripts via
the configured Overshell component; the worker polls for results
and the audit pipeline pulls them in as live evidence.
</p>
</CardHeader>
<CardContent className="space-y-4">
{error && (
<Alert variant="destructive">
<AlertTitle>Couldn&rsquo;t load settings</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{settings === null && !error ? (
<Skeleton className="h-32 w-full" />
) : settings ? (
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<p className="font-medium">Overshell component</p>
{settings.overshellComponentUid ? (
<>
<p className="text-xs text-muted-foreground mt-0.5">
{settings.overshellComponentName}
</p>
<p className="text-[10px] font-mono text-muted-foreground">
{settings.overshellComponentUid}
</p>
{settings.discoveredAt && (
<p className="text-[10px] text-muted-foreground mt-1">
discovered {new Date(settings.discoveredAt).toLocaleString()}
</p>
)}
</>
) : (
<p className="text-xs text-amber-600 mt-0.5">
No component cached. Click Discover to scan Datto RMM.
</p>
)}
</div>
<div>
<p className="font-medium">Variable name</p>
<p className="text-xs font-mono text-muted-foreground mt-0.5">
{settings.overshellVariableName}
</p>
<p className="text-[10px] text-muted-foreground mt-1">
Adjust if your component uses a different variable.
</p>
</div>
<div>
<p className="font-medium">Activity (24h)</p>
<p className="text-xs text-muted-foreground mt-0.5">
{counts?.total ?? '0'} total · {counts?.running ?? '0'} running ·
<span className="text-destructive">
{' '}
{counts?.failed_24h ?? '0'} failed
</span>
</p>
</div>
<div className="flex items-end gap-2">
<Button onClick={discover} disabled={discovering}>
{discovering ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Discovering
</>
) : (
<>
<RefreshCw className="w-4 h-4 mr-2" />
Re-discover Overshell
</>
)}
</Button>
</div>
<div className="col-span-2 border-t pt-4">
<p className="font-medium">LogLift component</p>
{settings.logliftComponentUid ? (
<>
<p className="text-xs text-muted-foreground mt-0.5">
{settings.logliftComponentName}
</p>
<p className="text-[10px] font-mono text-muted-foreground">
{settings.logliftComponentUid}
</p>
{settings.logliftDiscoveredAt && (
<p className="text-[10px] text-muted-foreground mt-1">
discovered{' '}
{new Date(settings.logliftDiscoveredAt).toLocaleString()}
</p>
)}
</>
) : (
<p className="text-xs text-amber-600 mt-0.5">
No LogLift component cached. Click below to scan Datto RMM
for one named &ldquo;loglift&rdquo; or &ldquo;eventlog&rdquo;.
</p>
)}
<div className="mt-3">
<Button
variant="outline"
onClick={discoverLoglift}
disabled={discoveringLoglift}
>
{discoveringLoglift ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Discovering
</>
) : (
<>
<RefreshCw className="w-4 h-4 mr-2" />
Re-discover LogLift
</>
)}
</Button>
</div>
</div>
</div>
) : null}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">Recent executions</CardTitle>
</CardHeader>
<CardContent>
{executions === null ? (
<Skeleton className="h-24 w-full" />
) : executions.length === 0 ? (
<p className="text-sm text-muted-foreground">No executions yet.</p>
) : (
<ul className="divide-y">
{executions.map((e) => (
<li key={e.id} className="py-2 grid grid-cols-12 gap-2 text-sm">
<span className="col-span-3 font-mono truncate">{e.scriptId}</span>
<span className="col-span-3 truncate">{e.targetHostname ?? '—'}</span>
<span className="col-span-2">
<Badge
variant={
e.status === 'complete'
? 'default'
: e.status === 'failed' || e.status === 'timeout'
? 'destructive'
: 'outline'
}
className="text-[10px]"
>
{e.status}
{e.exitCode !== null ? ` · exit ${e.exitCode}` : ''}
</Badge>
</span>
<span className="col-span-3 text-xs text-muted-foreground truncate">
{new Date(e.queuedAt).toLocaleString()}
</span>
<span className="col-span-1 text-right">
{e.errorMessage && (
<span className="text-xs text-destructive truncate">!</span>
)}
</span>
</li>
))}
</ul>
)}
</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>
</>
);
}