mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-13 04:06:59 +00:00
083442d5ea
Security page UX fixes: - Stop the CVSS x EPSS scatter chart from painting a full-plot "white rectangle" cursor on click (cursor disabled), and prevent click-drag selection on charts. - Differentiate the overview action links: "fixable" links (masthead primary action, review-queue blocker, and the Fixable signal tile) now open the Images tab pre-filtered to fixable findings; the Stale and Failed signal tiles link to the History tab where those scans are listed; Secrets and Misconfigs tiles link to their tabs. The Images tab accepts an initialFilter and exposes a Fixable option in the severity dropdown. - Fix the "Secrets / misconfigs" option wrapping and misaligning in the severity dropdown (single-line option labels, wider trigger). - Add Edit for CVE suppressions and misconfig acknowledgements (reason, scope pattern, expiry), reusing the existing dialog and the existing PUT endpoints; the CVE/rule identity stays fixed.
84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
import { cn } from '@/lib/utils';
|
|
import { Sparkline } from '@/components/ui/sparkline';
|
|
|
|
export type SignalTone = 'value' | 'warn' | 'error' | 'subtitle';
|
|
|
|
export interface SignalTile {
|
|
kicker: string;
|
|
value: string;
|
|
tone?: SignalTone;
|
|
/** Optional series for a 64x20 sparkline stroked in brand cyan. */
|
|
spark?: number[];
|
|
/** When set, the tile renders as a button that navigates on click. */
|
|
onClick?: () => void;
|
|
}
|
|
|
|
interface SignalRailProps {
|
|
tiles: SignalTile[];
|
|
className?: string;
|
|
}
|
|
|
|
const toneClass: Record<SignalTone, string> = {
|
|
value: 'text-stat-value',
|
|
warn: 'text-warning',
|
|
error: 'text-destructive',
|
|
subtitle: 'text-stat-subtitle',
|
|
};
|
|
|
|
export function SignalRail({ tiles, className }: SignalRailProps) {
|
|
if (tiles.length === 0) return null;
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'shrink-0 grid border-b border-card-border bg-card',
|
|
className,
|
|
)}
|
|
style={{ gridTemplateColumns: `repeat(${tiles.length}, minmax(0, 1fr))` }}
|
|
>
|
|
{tiles.map((tile, idx) => {
|
|
const inner = (
|
|
<>
|
|
<div className="flex min-w-0 flex-col gap-1">
|
|
<span className="font-mono text-[10px] uppercase tracking-[0.22em] text-stat-subtitle">
|
|
{tile.kicker}
|
|
</span>
|
|
<span
|
|
className={cn(
|
|
'font-mono tabular-nums tracking-tight text-2xl leading-none',
|
|
toneClass[tile.tone ?? 'value'],
|
|
)}
|
|
>
|
|
{tile.value}
|
|
</span>
|
|
</div>
|
|
{tile.spark && tile.spark.length > 1 ? (
|
|
<div className="h-5 w-16 shrink-0 opacity-90">
|
|
<Sparkline
|
|
points={tile.spark}
|
|
stroke="var(--brand)"
|
|
fill="var(--brand)"
|
|
strokeWidth={1.25}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
</>
|
|
);
|
|
const cellClass = cn(
|
|
'flex items-center justify-between gap-4 px-5 py-[var(--density-tile-y)] text-left',
|
|
idx > 0 && 'border-l border-card-border',
|
|
tile.onClick && 'cursor-pointer transition-colors hover:bg-accent/5',
|
|
);
|
|
return tile.onClick ? (
|
|
<button key={tile.kicker} type="button" onClick={tile.onClick} className={cellClass}>
|
|
{inner}
|
|
</button>
|
|
) : (
|
|
<div key={tile.kicker} className={cellClass}>
|
|
{inner}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|