wulf-pulse/components/ui/status-light.tsx
lorentz e1427b62d7 feat(admin): DB-backed integration toggles + sticky cols + dark contrast
Builds on the env-var INTEGRATIONS_DISABLED shipped with the nav-design
overhaul.  Adds a DB-backed admin UI so operators can flip integrations
without editing .env and restarting the container, plus the remaining
visual cleanup items from the design backlog.

Integration toggles
- Migration 081 — integration_settings table (key PK, disabled flag,
  reason, disabled_by audit, disabled_at).  Seeded with all 13 known
  integrations as enabled.
- GET / PATCH /api/admin/integrations — gated by requirePermission
  (admin, access).  PATCH clears the in-process integration-health
  cache so toggles take effect within seconds.
- /admin/integrations admin page with a Switch per integration, optional
  reason input, audit-info subtitle (disabled by, when, why), live
  status light from /api/dashboard/integration-health.
- integration-health service merges env-var disable list with DB rows;
  degrades gracefully if migration unapplied / DB unreachable.
- Wired into the Admin nav dropdown (eight items now).
- CLAUDE.md describes both env + DB sources.

Sticky first column on tables
- Table primitive accepts stickyFirstColumn?: boolean.  When true, TH
  and TD :first-child stay pinned during horizontal scroll, with
  background inheritance preserving hover and selected row tints.
- DataTable exposes the prop too — on by default for paginated tables.
- /addigy-devices opts in.

Dark-mode contrast
- --border lifted from 10% to 14% in .dark; --input from 15% to 18%;
  --sidebar-border to 14%.
- StatusLight outline ring lifted from /10 to /15 (light) and /20 (dark).
- DetailModal empty-cell em-dash lifted from /40 to /70 so missing
  values are legible on dark surfaces.

DESIGN.md
- Closed sticky-first-column, dark-mode contrast, and palette-audit
  items (palette deprioritized — most uses are semantic).
- Skeleton helpers documented as preferred for new code; existing
  ad-hoc patterns left in place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:55:22 -04:00

53 lines
1.2 KiB
TypeScript

/* StatusLight — 8px colored square indicator.
*
* The defining geometry of /status: square, not circle, faintly
* outlined so it reads on white. Five states map to the brand
* status palette. Optional pulse is for "in flight" / "running" rows. */
import { cn } from '@/lib/utils';
export type StatusLightState = 'ok' | 'warn' | 'error' | 'idle' | 'pending';
interface StatusLightProps {
state: StatusLightState;
size?: 'sm' | 'md' | 'lg';
pulse?: boolean;
label?: string;
className?: string;
}
const sizeMap = {
sm: 'h-1.5 w-1.5',
md: 'h-2 w-2',
lg: 'h-3 w-3',
} as const;
const stateMap: Record<StatusLightState, string> = {
ok: 'bg-emerald-500',
warn: 'bg-amber-500',
error: 'bg-destructive',
idle: 'bg-muted-foreground/40',
pending: 'bg-primary',
};
export function StatusLight({
state,
size = 'md',
pulse = false,
label,
className,
}: StatusLightProps) {
return (
<span
role="status"
aria-label={label ?? state}
className={cn(
'inline-block ring-1 ring-foreground/15 dark:ring-foreground/20 align-middle',
sizeMap[size],
stateMap[state],
pulse && state === 'pending' && 'animate-pulse',
className,
)}
/>
);
}