mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-15 08:49:59 +02:00
improve usage analytics filters
This commit is contained in:
+6
-4
@@ -1977,6 +1977,8 @@
|
|||||||
"aiUsageFilterAllResources": "All Resources",
|
"aiUsageFilterAllResources": "All Resources",
|
||||||
"aiUsageFilterAllRoles": "All Roles",
|
"aiUsageFilterAllRoles": "All Roles",
|
||||||
"aiUsageFilterAllUsers": "All Users",
|
"aiUsageFilterAllUsers": "All Users",
|
||||||
|
"aiUsageFilterSearch": "Search...",
|
||||||
|
"aiUsageFilterNotFound": "No options found",
|
||||||
"aiUsageResetFilters": "Reset Filters",
|
"aiUsageResetFilters": "Reset Filters",
|
||||||
"aiUsageRefresh": "Refresh",
|
"aiUsageRefresh": "Refresh",
|
||||||
"aiUsageTokenTypePrompt": "Prompt",
|
"aiUsageTokenTypePrompt": "Prompt",
|
||||||
@@ -2003,8 +2005,8 @@
|
|||||||
"aiUsageTopResources": "Top Resources",
|
"aiUsageTopResources": "Top Resources",
|
||||||
"aiUsageResourceCost": "Resource Cost",
|
"aiUsageResourceCost": "Resource Cost",
|
||||||
"aiUsageResourceTokenUsage": "Resource Token Usage",
|
"aiUsageResourceTokenUsage": "Resource Token Usage",
|
||||||
"aiUsageResourceTypePublic": "Resource",
|
"aiUsageResourceTypePublic": "Public Resource",
|
||||||
"aiUsageResourceTypeSite": "Site resource",
|
"aiUsageResourceTypeSite": "Private Resource",
|
||||||
"aiUsageNoResource": "No resource",
|
"aiUsageNoResource": "No resource",
|
||||||
"aiUsageRolesTab": "Roles",
|
"aiUsageRolesTab": "Roles",
|
||||||
"aiUsageUsersTab": "Users",
|
"aiUsageUsersTab": "Users",
|
||||||
@@ -3543,8 +3545,8 @@
|
|||||||
"sourceAddress": "Source Address",
|
"sourceAddress": "Source Address",
|
||||||
"destinationAddress": "Destination Address",
|
"destinationAddress": "Destination Address",
|
||||||
"duration": "Duration",
|
"duration": "Duration",
|
||||||
"licenseRequiredToUse": "<enterpriseEditionLink>Enterprise Edition license</enterpriseEditionLink> is required. <bookADemoLink>Book a demo or POC</bookADemoLink>",
|
"licenseRequiredToUse": "<enterpriseEditionLink>Enterprise Edition license</enterpriseEditionLink> is required. <bookADemoLink>Book a demo or trial</bookADemoLink>",
|
||||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition license</enterpriseEditionLink> is required. <bookADemoLink>Book a demo or POC</bookADemoLink>",
|
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition license</enterpriseEditionLink> is required. <bookADemoLink>Book a demo or trial</bookADemoLink>",
|
||||||
"certResolver": "Certificate Resolver",
|
"certResolver": "Certificate Resolver",
|
||||||
"certResolverDescription": "Select the certificate resolver to use for this resource.",
|
"certResolverDescription": "Select the certificate resolver to use for this resource.",
|
||||||
"selectCertResolver": "Select Certificate Resolver",
|
"selectCertResolver": "Select Certificate Resolver",
|
||||||
|
|||||||
@@ -7,20 +7,23 @@ import {
|
|||||||
type AiUsageAnalyticsFilters
|
type AiUsageAnalyticsFilters
|
||||||
} from "@app/lib/queries";
|
} from "@app/lib/queries";
|
||||||
import { useIsFetching, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useIsFetching, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { RefreshCw, XIcon } from "lucide-react";
|
import { CheckIcon, ChevronsUpDown, RefreshCw, XIcon } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import { useState } from "react";
|
||||||
import { DateRangePicker, type DateTimeValue } from "./DateTimePicker";
|
import { DateRangePicker, type DateTimeValue } from "./DateTimePicker";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { Card, CardHeader } from "./ui/card";
|
import { Card, CardHeader } from "./ui/card";
|
||||||
import { Label } from "./ui/label";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Command,
|
||||||
SelectContent,
|
CommandEmpty,
|
||||||
SelectItem,
|
CommandGroup,
|
||||||
SelectTrigger,
|
CommandInput,
|
||||||
SelectValue
|
CommandItem,
|
||||||
} from "./ui/select";
|
CommandList
|
||||||
|
} from "./ui/command";
|
||||||
|
import { Label } from "./ui/label";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
import { Separator } from "./ui/separator";
|
import { Separator } from "./ui/separator";
|
||||||
import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
|
import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
|
||||||
import { HorizontalTabs, type TabItem } from "./HorizontalTabs";
|
import { HorizontalTabs, type TabItem } from "./HorizontalTabs";
|
||||||
@@ -161,7 +164,7 @@ export function AiUsageAnalyticsData(props: AiUsageAnalyticsDataProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row flex-wrap items-end gap-x-3 gap-y-3">
|
<CardHeader className="flex flex-row flex-wrap items-end gap-x-3 gap-y-3 space-y-0">
|
||||||
<DateRangePicker
|
<DateRangePicker
|
||||||
startValue={{
|
startValue={{
|
||||||
date: dateRange.startDate,
|
date: dateRange.startDate,
|
||||||
@@ -230,38 +233,40 @@ export function AiUsageAnalyticsData(props: AiUsageAnalyticsDataProps) {
|
|||||||
onValueChange={(v) => setFilter("virtualApiKeyId", v)}
|
onValueChange={(v) => setFilter("virtualApiKeyId", v)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isEmptySearchParams && (
|
<div className="flex items-center gap-2 ml-auto">
|
||||||
|
{!isEmptySearchParams && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => router.replace(path)}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
<XIcon className="size-4" />
|
||||||
|
{t("aiUsageResetFilters")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="outline"
|
||||||
onClick={() => router.replace(path)}
|
onClick={() =>
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [
|
||||||
|
...AI_USAGE_ANALYTICS_QUERY_PREFIX,
|
||||||
|
props.orgId
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={isFetching}
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
>
|
>
|
||||||
<XIcon className="size-4" />
|
<RefreshCw
|
||||||
{t("aiUsageResetFilters")}
|
className={cn(
|
||||||
|
"size-4",
|
||||||
|
isFetching && "animate-spin"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{t("aiUsageRefresh")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() =>
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: [
|
|
||||||
...AI_USAGE_ANALYTICS_QUERY_PREFIX,
|
|
||||||
props.orgId
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
disabled={isFetching}
|
|
||||||
className="gap-2 ml-auto"
|
|
||||||
>
|
|
||||||
<RefreshCw
|
|
||||||
className={cn(
|
|
||||||
"size-4",
|
|
||||||
isFetching && "animate-spin"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{t("aiUsageRefresh")}
|
|
||||||
</Button>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -287,29 +292,93 @@ type FilterSelectProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function FilterSelect(props: FilterSelectProps) {
|
function FilterSelect(props: FilterSelectProps) {
|
||||||
|
const t = useTranslations();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const selected = props.options.find(
|
||||||
|
(option) => option.value === props.value
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-start gap-2 w-36">
|
<div className="flex flex-col items-start gap-2 w-44">
|
||||||
<Label htmlFor={props.id}>{props.label}</Label>
|
<Label htmlFor={props.id}>{props.label}</Label>
|
||||||
<Select
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
onValueChange={(newValue) =>
|
<PopoverTrigger asChild>
|
||||||
props.onValueChange(
|
<Button
|
||||||
newValue === "all" ? undefined : newValue
|
id={props.id}
|
||||||
)
|
type="button"
|
||||||
}
|
variant="outline"
|
||||||
value={props.value ?? "all"}
|
role="combobox"
|
||||||
>
|
aria-expanded={open}
|
||||||
<SelectTrigger id={props.id} className="w-full">
|
className={cn(
|
||||||
<SelectValue placeholder={props.placeholder} />
|
"w-full justify-between font-normal",
|
||||||
</SelectTrigger>
|
!selected && "text-muted-foreground"
|
||||||
<SelectContent className="w-full">
|
)}
|
||||||
<SelectItem value="all">{props.placeholder}</SelectItem>
|
>
|
||||||
{props.options.map((option) => (
|
<span className="truncate text-left">
|
||||||
<SelectItem key={option.value} value={option.value}>
|
{selected?.label ?? props.placeholder}
|
||||||
{option.label}
|
</span>
|
||||||
</SelectItem>
|
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
))}
|
</Button>
|
||||||
</SelectContent>
|
</PopoverTrigger>
|
||||||
</Select>
|
<PopoverContent
|
||||||
|
className="w-[var(--radix-popover-trigger-width)] min-w-56 p-0"
|
||||||
|
align="start"
|
||||||
|
>
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder={t("aiUsageFilterSearch")} />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>
|
||||||
|
{t("aiUsageFilterNotFound")}
|
||||||
|
</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
<CommandItem
|
||||||
|
value={props.placeholder}
|
||||||
|
onSelect={() => {
|
||||||
|
props.onValueChange(undefined);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckIcon
|
||||||
|
className={cn(
|
||||||
|
"mr-2 h-4 w-4 shrink-0",
|
||||||
|
!props.value
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{props.placeholder}
|
||||||
|
</CommandItem>
|
||||||
|
{props.options.map((option) => (
|
||||||
|
<CommandItem
|
||||||
|
key={option.value}
|
||||||
|
value={`${option.value} ${option.label}`}
|
||||||
|
onSelect={() => {
|
||||||
|
props.onValueChange(
|
||||||
|
option.value === props.value
|
||||||
|
? undefined
|
||||||
|
: option.value
|
||||||
|
);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckIcon
|
||||||
|
className={cn(
|
||||||
|
"mr-2 h-4 w-4 shrink-0",
|
||||||
|
option.value === props.value
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<span className="truncate">
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user