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>
53 lines
1.2 KiB
TypeScript
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,
|
|
)}
|
|
/>
|
|
);
|
|
}
|