-
Notifications
You must be signed in to change notification settings - Fork 610
feat: ratelimit overrides v2 #2905
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
Merged
Merged
Changes from all commits
Commits
Show all changes
64 commits
Select commit
Hold shift + click to select a range
9e83c8d
feat: add initial overview
ogzhanolguncu e1fa1d8
fix: run formatter
ogzhanolguncu 5c01407
refactor: Add card back
ogzhanolguncu 8323bec
feat: add first draft of barchart
ogzhanolguncu 3ffdd3b
feat: add latency line chart
ogzhanolguncu c14754a
refactor: update timestamp colors
ogzhanolguncu e4372a8
feat: add initial draft of filters for overvioew
ogzhanolguncu 8b6ce2f
feat: add overview filters
ogzhanolguncu 2a33711
fix: round to nearest for latencies
ogzhanolguncu 81f482c
feat: add data fetching for overviews
ogzhanolguncu 0b152ba
fix: add chart states
ogzhanolguncu 4eb831f
fix: add mvs for timeseries latency
ogzhanolguncu a5b07e7
fix: chart colors
ogzhanolguncu 2eea8c7
refactor: organize files
ogzhanolguncu 8d5aaa0
feat: add new optinos to action menu
ogzhanolguncu fcab8df
feat: add override modal
ogzhanolguncu 5209c9e
feat: add new override fields
ogzhanolguncu d2c12ef
feat: extend logs data
ogzhanolguncu 2f6decd
fix: styles
ogzhanolguncu f479630
fix: small ui issues
ogzhanolguncu 580b859
feat: add status to filters for log
ogzhanolguncu c025068
fix: bugs
ogzhanolguncu 2e3e67f
feat: add new sebmenu
ogzhanolguncu b96cec1
feat: add delete and update for quick navbar
ogzhanolguncu 52a57fc
feat: add new quickbar to every ratelimit page
ogzhanolguncu 10efc04
feat: add charts for ratelimit list
ogzhanolguncu f0ae734
feat: add missing search to ratelimit
ogzhanolguncu 499d978
fix: ui issues
ogzhanolguncu 54ba243
fix: ordering issues
ogzhanolguncu b2f7427
feat: add more granular timeseries for logs
ogzhanolguncu 68c0d14
feat: add more granular options for charts
ogzhanolguncu c77360a
feat: add new search for identifiers and paths
ogzhanolguncu bc2ccaa
chore: cleanup
ogzhanolguncu 954324f
fix: typo
ogzhanolguncu 0d074c6
feat: add sorting
ogzhanolguncu 1a78349
chore: remove mvs
ogzhanolguncu 4272fb7
chore: formatter
ogzhanolguncu 65f07de
feat: replace icons and remove latency
ogzhanolguncu 8c408d6
fix: icon
ogzhanolguncu ff96dcb
Merge branch 'main' of github.com:unkeyed/unkey into ratelimit-overvi…
ogzhanolguncu 95c6c15
fix: conflict issue
ogzhanolguncu c38d27b
fix: coderabit issues
ogzhanolguncu 6e1bddc
fix: review comments
ogzhanolguncu 99ecae9
Merge branch 'main' of github.com:unkeyed/unkey into ratelimit-overvi…
ogzhanolguncu b893b39
chore: remove unused icon
ogzhanolguncu f20639c
fix: load issue
ogzhanolguncu 341c340
fix: missing error check
ogzhanolguncu 8d47a24
fix: build issue
ogzhanolguncu 825aecf
Merge branch 'main' of github.com:unkeyed/unkey into ratelimit-overvi…
ogzhanolguncu 4316e95
[autofix.ci] apply automated fixes
autofix-ci[bot] 5ca0947
fix: missing action for logs action menu item
ogzhanolguncu 6259a45
Merge branch 'ratelimit-overview-v2' of github.com:unkeyed/unkey into…
ogzhanolguncu ad19525
fix: ch data fetching
ogzhanolguncu 4ae4323
feat: add new table structure and refactor dialogs
ogzhanolguncu 04a9624
fix: use different keys
ogzhanolguncu 0138ed9
fix: chart selection issue
ogzhanolguncu 7707e6c
Merge branch 'main' of github.com:unkeyed/unkey into ratelimit-overri…
ogzhanolguncu 399a2e8
fix: typo
ogzhanolguncu 9db985b
chore: remove unuseds
ogzhanolguncu 6ba9a35
fix: modal openning issue
ogzhanolguncu a70746a
fix: update override bug
ogzhanolguncu 06970cc
fix: correctly handle timezones
ogzhanolguncu 1c921a1
fix: timestamp issue
ogzhanolguncu 34e52ea
fix: use same type of error for charts
ogzhanolguncu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
apps/dashboard/app/(app)/ratelimits/[namespaceId]/_components/delete-dialog.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| "use client"; | ||
|
|
||
| import { | ||
| Dialog, | ||
| DialogContent, | ||
| DialogFooter, | ||
| DialogHeader, | ||
| DialogTitle, | ||
| } from "@/components/ui/dialog"; | ||
| import { Input } from "@/components/ui/input"; | ||
| import { toast } from "@/components/ui/toaster"; | ||
| import { trpc } from "@/lib/trpc/client"; | ||
| import { zodResolver } from "@hookform/resolvers/zod"; | ||
| import { Button } from "@unkey/ui"; | ||
| import type { PropsWithChildren } from "react"; | ||
| import { useForm } from "react-hook-form"; | ||
| import { z } from "zod"; | ||
|
|
||
| const formSchema = z.object({ | ||
| identifier: z | ||
| .string() | ||
| // biome-ignore lint/suspicious/noSelfCompare: <explanation> | ||
| .refine((v) => v === v, "Please confirm the identifier"), | ||
| }); | ||
|
|
||
| type FormValues = z.infer<typeof formSchema>; | ||
|
|
||
| type Props = PropsWithChildren<{ | ||
| isModalOpen: boolean; | ||
| onOpenChange: (value: boolean) => void; | ||
| overrideId: string; | ||
| identifier: string; | ||
| }>; | ||
|
|
||
| export const DeleteDialog = ({ isModalOpen, onOpenChange, overrideId, identifier }: Props) => { | ||
| const { ratelimit } = trpc.useUtils(); | ||
|
|
||
| const { | ||
| register, | ||
| handleSubmit, | ||
| watch, | ||
| formState: { isSubmitting }, | ||
| } = useForm<FormValues>({ | ||
| mode: "onChange", | ||
| resolver: zodResolver(formSchema), | ||
| defaultValues: { | ||
| identifier: "", | ||
| }, | ||
| }); | ||
|
|
||
| const isValid = watch("identifier") === identifier; | ||
|
|
||
| const deleteOverride = trpc.ratelimit.override.delete.useMutation({ | ||
| onSuccess() { | ||
| toast.success("Override has been deleted", { | ||
| description: "Changes may take up to 60s to propagate globally", | ||
| }); | ||
| onOpenChange(false); | ||
| ratelimit.overview.logs.query.invalidate(); | ||
| ratelimit.logs.queryRatelimitTimeseries.invalidate(); | ||
| }, | ||
| onError(err) { | ||
| toast.error("Failed to delete override", { | ||
| description: err.message, | ||
| }); | ||
| }, | ||
| }); | ||
|
|
||
| const onSubmit = async () => { | ||
| try { | ||
| await deleteOverride.mutateAsync({ id: overrideId }); | ||
| } catch (error) { | ||
| console.error("Delete error:", error); | ||
| } | ||
| }; | ||
ogzhanolguncu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return ( | ||
| <Dialog open={isModalOpen} onOpenChange={onOpenChange}> | ||
| <DialogContent | ||
| className="bg-gray-1 dark:bg-black drop-shadow-2xl border-gray-4 rounded-lg p-0 gap-0" | ||
| onOpenAutoFocus={(e) => { | ||
| e.preventDefault(); | ||
| }} | ||
| > | ||
ogzhanolguncu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <DialogHeader className="border-b border-gray-4"> | ||
| <DialogTitle className="px-6 py-4 text-gray-12 font-medium text-base"> | ||
| Delete Override | ||
| </DialogTitle> | ||
| </DialogHeader> | ||
|
|
||
| <form onSubmit={handleSubmit(onSubmit)}> | ||
| <div className="flex flex-col gap-4 py-4 px-6 bg-accent-2"> | ||
| <p className="text-gray-11 text-[13px]"> | ||
| <span className="font-medium">Warning: </span> | ||
| Are you sure you want to delete this override? The identifier associated with this | ||
| override will now use the default limits. | ||
| </p> | ||
|
|
||
| <div className="space-y-1"> | ||
| <p className="text-gray-11 text-[13px]"> | ||
| Type <span className="text-gray-12 font-medium">{identifier}</span> to confirm | ||
| </p> | ||
|
|
||
| <Input | ||
| {...register("identifier")} | ||
| placeholder={`Enter "${identifier}" to confirm`} | ||
| className="border border-gray-5 focus:border focus:border-gray-4 px-3 py-1 hover:bg-gray-4 hover:border-gray-8 focus:bg-gray-4 rounded-md placeholder:text-gray-8 h-9" | ||
| /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <DialogFooter className="p-6 border-t border-gray-4"> | ||
| <div className="w-full flex flex-col gap-2 items-center justify-center"> | ||
| <Button | ||
| type="submit" | ||
| variant="destructive" | ||
| disabled={!isValid || deleteOverride.isLoading || isSubmitting} | ||
| loading={deleteOverride.isLoading || isSubmitting} | ||
| className="h-10 w-full rounded-lg" | ||
| > | ||
| Delete Override | ||
| </Button> | ||
| <div className="text-gray-9 text-xs"> | ||
| This action cannot be undone – proceed with caution | ||
| </div> | ||
| </div> | ||
| </DialogFooter> | ||
| </form> | ||
| </DialogContent> | ||
| </Dialog> | ||
| ); | ||
| }; | ||
2 changes: 1 addition & 1 deletion
2
apps/dashboard/app/(app)/ratelimits/[namespaceId]/_components/form-field.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
apps/dashboard/app/(app)/ratelimits/[namespaceId]/_components/table-action-button.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { Dots } from "@unkey/icons"; | ||
| import { cn } from "@unkey/ui/src/lib/utils"; | ||
|
|
||
| export const TableActionButton = () => { | ||
| return ( | ||
| <button | ||
| type="button" | ||
| className={cn( | ||
| "group-data-[state=open]:bg-gray-6 bg-gray-5 hover:bg-gray-6 group size-5 p-0 rounded m-0 items-center flex justify-center", | ||
| "border border-gray-6 hover:border-gray-8 ring-2 ring-transparent focus-visible:ring-gray-7 focus-visible:border-gray-7", | ||
| )} | ||
| > | ||
| <Dots className="group-hover:text-gray-12 text-gray-11" size="sm-regular" /> | ||
| </button> | ||
| ); | ||
| }; |
179 changes: 179 additions & 0 deletions
179
apps/dashboard/app/(app)/ratelimits/[namespaceId]/_components/table-action-popover.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; | ||
| import { cn } from "@unkey/ui/src/lib/utils"; | ||
| import { type PropsWithChildren, useEffect, useRef, useState } from "react"; | ||
| import { TableActionButton } from "./table-action-button"; | ||
|
|
||
| export type MenuItem = { | ||
| id: string; | ||
| label: string; | ||
| icon: React.ReactNode; | ||
| onClick: (e: React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<Element>) => void; | ||
| className?: string; | ||
| disabled?: boolean; | ||
| }; | ||
|
|
||
| type BaseTableActionPopoverProps = PropsWithChildren<{ | ||
| items: MenuItem[]; | ||
| align?: "start" | "end"; | ||
| headerContent?: React.ReactNode; | ||
| }>; | ||
|
|
||
| export const TableActionPopover = ({ | ||
| items, | ||
| align = "end", | ||
| headerContent, | ||
| children, | ||
| }: BaseTableActionPopoverProps) => { | ||
| const [open, setOpen] = useState(false); | ||
| const [focusIndex, setFocusIndex] = useState(0); | ||
| const menuItems = useRef<HTMLDivElement[]>([]); | ||
|
|
||
| useEffect(() => { | ||
| if (open) { | ||
| const firstEnabledIndex = items.findIndex((item) => !item.disabled); | ||
| setFocusIndex(firstEnabledIndex >= 0 ? firstEnabledIndex : 0); | ||
| if (firstEnabledIndex >= 0) { | ||
| menuItems.current[firstEnabledIndex]?.focus(); | ||
| } | ||
| } | ||
| }, [open, items]); | ||
|
|
||
| const handleKeyDown = (e: React.KeyboardEvent<Element>) => { | ||
| e.stopPropagation(); | ||
|
|
||
| const activeElement = document.activeElement; | ||
| const currentIndex = menuItems.current.findIndex((item) => item === activeElement); | ||
| const itemCount = items.length; | ||
|
|
||
| const findNextEnabledIndex = (startIndex: number, direction: 1 | -1) => { | ||
| let index = startIndex; | ||
| for (let i = 0; i < itemCount; i++) { | ||
| index = (index + direction + itemCount) % itemCount; | ||
| if (!items[index].disabled) { | ||
| return index; | ||
| } | ||
| } | ||
| return startIndex; | ||
| }; | ||
|
|
||
| switch (e.key) { | ||
| case "Tab": { | ||
| e.preventDefault(); | ||
| const nextIndex = findNextEnabledIndex(currentIndex, e.shiftKey ? -1 : 1); | ||
| setFocusIndex(nextIndex); | ||
| menuItems.current[nextIndex]?.focus(); | ||
| break; | ||
| } | ||
|
|
||
| case "j": | ||
| case "ArrowDown": { | ||
| e.preventDefault(); | ||
| const nextDownIndex = findNextEnabledIndex(currentIndex, 1); | ||
| setFocusIndex(nextDownIndex); | ||
| menuItems.current[nextDownIndex]?.focus(); | ||
| break; | ||
| } | ||
|
|
||
| case "k": | ||
| case "ArrowUp": { | ||
| e.preventDefault(); | ||
| const nextUpIndex = findNextEnabledIndex(currentIndex, -1); | ||
| setFocusIndex(nextUpIndex); | ||
| menuItems.current[nextUpIndex]?.focus(); | ||
| break; | ||
| } | ||
|
|
||
| case "Escape": | ||
| e.preventDefault(); | ||
| setOpen(false); | ||
| break; | ||
|
|
||
| case "Enter": | ||
| case "ArrowRight": | ||
| case "l": | ||
| case " ": | ||
| e.preventDefault(); | ||
| if (activeElement === menuItems.current[currentIndex] && !items[currentIndex].disabled) { | ||
| items[currentIndex].onClick(e); | ||
| } | ||
| break; | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <Popover open={open} onOpenChange={setOpen}> | ||
| <PopoverTrigger onClick={(e) => e.stopPropagation()}> | ||
| {children ? children : <TableActionButton />} | ||
| </PopoverTrigger> | ||
|
|
||
| <PopoverContent | ||
| className="w-60 bg-gray-1 dark:bg-black drop-shadow-2xl p-2 border-gray-6 rounded-lg" | ||
| align={align} | ||
| onOpenAutoFocus={(e) => { | ||
| e.preventDefault(); | ||
| const firstEnabledIndex = items.findIndex((item) => !item.disabled); | ||
| if (firstEnabledIndex >= 0) { | ||
| menuItems.current[firstEnabledIndex]?.focus(); | ||
| } | ||
| }} | ||
| onCloseAutoFocus={(e) => e.preventDefault()} | ||
| onEscapeKeyDown={(e) => { | ||
| e.preventDefault(); | ||
| setOpen(false); | ||
| }} | ||
| onInteractOutside={(e) => { | ||
| e.preventDefault(); | ||
| setOpen(false); | ||
| }} | ||
| > | ||
| <div | ||
| className="flex flex-col gap-2" | ||
| role="menu" | ||
| onClick={(e) => e.stopPropagation()} | ||
| onKeyDown={handleKeyDown} | ||
| > | ||
| {headerContent ?? <PopoverHeader />} | ||
|
|
||
| {items.map((item, index) => ( | ||
| // biome-ignore lint/a11y/useKeyWithClickEvents: <explanation> | ||
| <div | ||
| key={item.id} | ||
| ref={(el) => { | ||
| if (el) { | ||
| menuItems.current[index] = el; | ||
| } | ||
| }} | ||
| role="menuitem" | ||
| aria-disabled={item.disabled} | ||
| tabIndex={!item.disabled && focusIndex === index ? 0 : -1} | ||
| className={cn( | ||
| "flex w-full items-center px-2 py-1.5 gap-3 rounded-lg group", | ||
| !item.disabled && | ||
| "cursor-pointer hover:bg-gray-3 data-[state=open]:bg-gray-3 focus:outline-none focus:bg-gray-3", | ||
| item.disabled && "cursor-not-allowed opacity-50", | ||
| item.className, | ||
| )} | ||
| onClick={(e) => { | ||
| if (!item.disabled) { | ||
| item.onClick(e); | ||
| setOpen(false); | ||
| } | ||
| }} | ||
| > | ||
| {item.icon} | ||
| <span className="text-[13px] font-medium">{item.label}</span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </PopoverContent> | ||
| </Popover> | ||
| ); | ||
| }; | ||
|
|
||
| const PopoverHeader = () => { | ||
| return ( | ||
| <div className="flex w-full justify-between items-center px-2 py-1"> | ||
| <span className="text-gray-9 text-[13px]">Actions...</span> | ||
| </div> | ||
| ); | ||
| }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.