Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ratelimit date input and time display logs #2717

Merged
merged 4 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 80 additions & 46 deletions apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@
import { ArrayInput } from "@/components/array-input";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { CalendarRange, CheckCheck, ChevronDown, RefreshCw, User, X } from "lucide-react";
import {
CalendarIcon,
CalendarRange,
CheckCheck,
ChevronDown,
RefreshCw,
User,
X,
} from "lucide-react";
import {
parseAsArrayOf,
parseAsBoolean,
parseAsIsoDateTime,
parseAsString,
parseAsTimestamp,
useQueryState,
} from "nuqs";
import type React from "react";
import { useEffect, useState, useTransition } from "react";
import { useState, useTransition } from "react";

import { DateTimePicker } from "@/components/date-time-picker";
import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -27,6 +36,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { format } from "date-fns";
import { useRouter } from "next/navigation";

export const Filters: React.FC = () => {
Expand All @@ -49,36 +59,34 @@ export const Filters: React.FC = () => {
clearOnDefault: true,
}),
);

const [after, setAfter] = useQueryState(
"after",
parseAsIsoDateTime.withOptions({
parseAsTimestamp.withOptions({
history: "push",
shallow: false, // otherwise server components won't notice the change
shallow: false,
clearOnDefault: true,
}),
);

const [before, setBefore] = useQueryState(
"before",
parseAsIsoDateTime.withOptions({
parseAsTimestamp.withOptions({
history: "push",
shallow: false, // otherwise server components won't notice the change
shallow: false,
clearOnDefault: true,
}),
);

const [localTime, setLocalTime] = useState("");
useEffect(() => {
setLocalTime(after?.toLocaleString() ?? "");
}, [after]);

const [identifierVisible, setIdentifierVisible] = useState(false);
const [successVisible, setSuccessVisible] = useState(false);
const [timeRangeVisible, setTimeRangeVisible] = useState(false);

return (
<div className="flex flex-col w-full gap-2">
<div className="flex items-center justify-end w-full gap-2">
<DropdownMenu>
<DropdownMenuTrigger>
<DropdownMenuTrigger asChild>
<Button variant="secondary" className="text-xs">
Add Filter <ChevronDown className="w-4 h-4" />
</Button>
Expand Down Expand Up @@ -151,8 +159,9 @@ export const Filters: React.FC = () => {
<Select
value={success ? "true" : "false"}
onValueChange={(v) => {
setSuccess(v === "true");
startTransition(() => {});
startTransition(() => {
setSuccess(v === "true");
});
}}
>
<SelectTrigger>
Expand All @@ -167,9 +176,10 @@ export const Filters: React.FC = () => {
size="icon"
variant="secondary"
onClick={() => {
setSuccessVisible(false);
setSuccess(null);
startTransition(() => {});
startTransition(() => {
setSuccessVisible(false);
setSuccess(null);
});
}}
>
<X className="w-4 h-4" />
Expand All @@ -178,36 +188,60 @@ export const Filters: React.FC = () => {
) : null}
{timeRangeVisible || after !== null || before !== null ? (
<div className="flex items-center w-full gap-2">
<div className="flex items-center w-full h-8 p-1 text-sm border rounded-md group focus-within:border-primary">
<div className="flex flex-wrap items-center w-full gap-1 px-2">
<DateTimePicker
date={after ?? new Date()}
onDateChange={(date) =>
startTransition(() => {
setAfter(date);
})
}
timeInputLabel="Select Time"
calendarProps={{
disabled: { before: new Date() },
showOutsideDays: true,
}}
timeInputProps={{
className: "w-[100px]",
}}
>
<Button variant="outline" className="text-xs font-medium w-full justify-start gap-0">
<span className="mr-1 text-xs font-medium">From:</span>
--{after?.toLocaleString()}-- --{localTime}--
<input
type="datetime-local"
value={after?.toLocaleString()}
onChange={(v) => {
setAfter(new Date(v.currentTarget.value));
startTransition(() => {});
}}
className="flex-1 w-full bg-transparent outline-none placeholder:text-content-subtle"
/>
</div>
</div>
<div className="flex items-center w-full h-8 p-1 text-sm border rounded-md group focus-within:border-primary">
<div className="flex flex-wrap items-center w-full gap-1 px-2">
<span className="mr-1 text-xs font-medium">Until:</span>
<input
id="before"
type="datetime-local"
value={before?.toLocaleString()}
onChange={(v) => {
setBefore(new Date(v.currentTarget.value));
startTransition(() => {});
}}
className="flex-1 w-full bg-transparent outline-none placeholder:text-content-subtle"
/>
</div>

{after ? format(after, "PPp") : format(new Date(), "PPp")}

<CalendarIcon className="mr-2 h-4 w-4 ml-auto" />
</Button>
</DateTimePicker>
<div className="flex items-center w-full gap-2">
<DateTimePicker
date={before ?? new Date()}
onDateChange={(date) =>
startTransition(() => {
setBefore(date);
})
}
timeInputLabel="Select Time"
calendarProps={{
disabled: { before: after ?? new Date() },
showOutsideDays: true,
}}
timeInputProps={{
className: "w-[130px]",
}}
>
<Button
variant="outline"
className="text-xs font-medium w-full justify-start gap-0"
>
<span className="mr-1 text-xs font-medium">Until:</span>

{before ? format(before, "PPp") : format(new Date(), "PPp")}

<CalendarIcon className="mr-2 h-4 w-4 ml-auto" />
</Button>
</DateTimePicker>
</div>

<Button
className="flex-shrink-0"
size="icon"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { notFound } from "next/navigation";

import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
import { Loading } from "@/components/dashboard/loading";
import { TimestampInfo } from "@/components/timestamp-info";
import { Badge } from "@/components/ui/badge";
import {
Table,
Expand Down Expand Up @@ -159,7 +160,9 @@ const AuditLogTable: React.FC<{
{logs.map((l) => (
<TableRow key={l.request_id}>
<TableCell>
<span className="text-sm text-content">{new Date(l.time).toISOString()}</span>
<div className="px-[2px] flex items-center hover:underline hover:decoration-dotted">
<TimestampInfo value={l.time} className="text-sm" />
</div>
</TableCell>

<TableCell>
Expand Down
81 changes: 81 additions & 0 deletions apps/dashboard/components/date-time-picker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Calendar } from "@/components/ui/calendar";
import { Input } from "@/components/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Separator } from "@/components/ui/separator";
import { format, setHours, setMinutes } from "date-fns";
import type React from "react";
import { type PropsWithChildren, useState } from "react";

type DateTimePickerProps = {
date: Date;
onDateChange: (date: Date) => void;
calendarProps?: React.ComponentProps<typeof Calendar>;
timeInputProps?: React.ComponentProps<typeof Input>;
popoverContentProps?: React.ComponentProps<typeof PopoverContent>;
timeInputLabel?: string;
disabled?: boolean;
};

export function DateTimePicker({
date,
onDateChange: setDate,
children,
calendarProps,
timeInputProps,
popoverContentProps,
timeInputLabel = "Time",
disabled,
}: PropsWithChildren<DateTimePickerProps>) {
const [selectedDateTime, setSelectedDateTime] = useState<Date>(date);

const handleSelect = (selected: Date) => {
if (!selected) {
return;
}
const hours = selectedDateTime.getHours();
const minutes = selectedDateTime.getMinutes();
const newDate = setMinutes(setHours(selected, hours), minutes);
setSelectedDateTime(newDate);
setDate(newDate);
};

const handleTimeChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
const { value } = e.target;
const [hours, minutes] = value.split(":").map(Number);
const newDate = setMinutes(setHours(selectedDateTime, hours || 0), minutes || 0);
setSelectedDateTime(newDate);
setDate(newDate);
};
ogzhanolguncu marked this conversation as resolved.
Show resolved Hide resolved

const timeValue = format(selectedDateTime, "HH:mm");

return (
<Popover>
<PopoverTrigger asChild disabled={disabled}>
{children}
</PopoverTrigger>
<PopoverContent className="w-auto p-0" {...popoverContentProps}>
<Calendar
mode="single"
selected={selectedDateTime}
//@ts-expect-error Calendar can't infer the mode correctly
onSelect={(date) => handleSelect(date ?? new Date())}
initialFocus
{...calendarProps}
/>
<Separator />
<div className="p-3 flex flex-col gap-2">
<label className="text-sm font-medium leading-none ml-0.5">{timeInputLabel}</label>
<Input
type="time"
value={timeValue}
className="w-[130px] mt-2"
onChange={handleTimeChange}
disabled={disabled}
{...timeInputProps}
/>
ogzhanolguncu marked this conversation as resolved.
Show resolved Hide resolved
</div>
</PopoverContent>
</Popover>
);
}
Loading