mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-11 03:06:54 +00:00
fix(audit): harden audit log with summary fixes, design compliance, and test coverage (#561)
* fix(audit): harden audit log with summary fixes, design compliance, and test coverage Replace stale /compose/* route summaries with current /stacks/* patterns, add ~30 missing route summaries for container ops, labels, fleet updates, SSO, templates, and auto-update. Fix X-Forwarded-For extracting full proxy chain instead of first client IP. Pre-sort pattern table at module load for O(1) lookup instead of sorting per request. Add Calendar and DatePicker UI components (react-day-picker v9 + date-fns) replacing browser-native date inputs. Align AuditLogView with design system: Combobox instead of Select, design token colors, strokeWidth 1.5, card bevel, tabular-nums on all numeric cells. Add diagnostic logging gated behind developer mode for audit middleware, query, and export endpoints. Extract getAuditSummary to testable utility. Add 32 tests covering summary resolution, DB round-trips, API permissions, export formats, and middleware integration. * fix(audit): use correct Chevron prop types for react-day-picker compatibility
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { DayPicker, getDefaultClassNames } from "react-day-picker";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import "react-day-picker/style.css";
|
||||
|
||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
||||
|
||||
function CalendarChevron({ orientation }: { orientation?: "left" | "right" | "up" | "down" }) {
|
||||
return orientation === "left" ? (
|
||||
<ChevronLeft className="h-4 w-4" strokeWidth={1.5} />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4" strokeWidth={1.5} />
|
||||
);
|
||||
}
|
||||
|
||||
export function Calendar({ className, classNames, ...props }: CalendarProps) {
|
||||
const defaults = getDefaultClassNames();
|
||||
|
||||
return (
|
||||
<DayPicker
|
||||
className={cn("p-3", className)}
|
||||
classNames={{
|
||||
months: "flex flex-col sm:flex-row gap-2",
|
||||
month: "flex flex-col gap-4",
|
||||
month_caption: "flex items-center justify-center h-7 relative",
|
||||
caption_label: "text-sm font-medium font-sans",
|
||||
nav: "flex items-center gap-1",
|
||||
button_previous: cn(
|
||||
"absolute left-1 inline-flex items-center justify-center",
|
||||
"h-7 w-7 rounded-md border-0 bg-transparent p-0",
|
||||
"text-muted-foreground hover:text-foreground hover:bg-accent",
|
||||
"cursor-pointer transition-colors"
|
||||
),
|
||||
button_next: cn(
|
||||
"absolute right-1 inline-flex items-center justify-center",
|
||||
"h-7 w-7 rounded-md border-0 bg-transparent p-0",
|
||||
"text-muted-foreground hover:text-foreground hover:bg-accent",
|
||||
"cursor-pointer transition-colors"
|
||||
),
|
||||
weekdays: "flex",
|
||||
weekday: "text-muted-foreground w-8 font-sans text-xs font-medium",
|
||||
week: "flex mt-1",
|
||||
day: "relative p-0 text-center font-mono text-sm",
|
||||
day_button: cn(
|
||||
"inline-flex items-center justify-center h-8 w-8 rounded-md",
|
||||
"font-mono text-sm tabular-nums p-0 border-0 bg-transparent",
|
||||
"cursor-pointer transition-colors",
|
||||
"hover:bg-accent hover:text-accent-foreground",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-brand"
|
||||
),
|
||||
today: "border border-brand/40 rounded-md",
|
||||
selected: "bg-brand text-brand-foreground rounded-md hover:bg-brand hover:text-brand-foreground",
|
||||
outside: "text-muted-foreground/50",
|
||||
disabled: "text-muted-foreground/30 cursor-not-allowed",
|
||||
chevron: `${defaults.chevron} fill-muted-foreground`,
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
Chevron: CalendarChevron,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import * as React from "react";
|
||||
import { format } from "date-fns";
|
||||
import { Calendar as CalendarIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
|
||||
interface DatePickerProps {
|
||||
value: Date | undefined;
|
||||
onChange: (date: Date | undefined) => void;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function DatePicker({
|
||||
value,
|
||||
onChange,
|
||||
placeholder = "Pick a date",
|
||||
disabled = false,
|
||||
className,
|
||||
}: DatePickerProps) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"justify-start text-left font-normal border-border",
|
||||
!value && "text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" strokeWidth={1.5} />
|
||||
{value ? (
|
||||
<span className="font-mono text-xs tabular-nums">
|
||||
{format(value, "MMM d, yyyy")}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-xs">{placeholder}</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={value}
|
||||
onSelect={(date) => {
|
||||
onChange(date);
|
||||
setOpen(false);
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user