diff --git a/messages/en-US.json b/messages/en-US.json index 3dd1c94e..7d5deded 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2067,6 +2067,8 @@ "timestamp": "Timestamp", "accessLogs": "Access Logs", "exportCsv": "Export CSV", + "exportError": "Unknown error when exporting CSV", + "exportCsvTooltip": "Within Time Range", "actorId": "Actor ID", "allowedByRule": "Allowed by Rule", "allowedNoAuth": "Allowed No Auth", diff --git a/server/private/routers/auditLogs/exportAccessAuditLog.ts b/server/private/routers/auditLogs/exportAccessAuditLog.ts index fbeca932..7e912f8c 100644 --- a/server/private/routers/auditLogs/exportAccessAuditLog.ts +++ b/server/private/routers/auditLogs/exportAccessAuditLog.ts @@ -22,9 +22,11 @@ import logger from "@server/logger"; import { queryAccessAuditLogsParams, queryAccessAuditLogsQuery, - queryAccess + queryAccess, + countAccessQuery } from "./queryAccessAuditLog"; import { generateCSV } from "@server/routers/auditLogs/generateCSV"; +import { MAX_EXPORT_LIMIT } from "@server/routers/auditLogs"; registry.registerPath({ method: "get", @@ -65,6 +67,15 @@ export async function exportAccessAuditLogs( } const data = { ...parsedQuery.data, ...parsedParams.data }; + const [{ count }] = await countAccessQuery(data); + if (count > MAX_EXPORT_LIMIT) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + `Export limit exceeded. Your selection contains ${count} rows, but the maximum is ${MAX_EXPORT_LIMIT} rows. Please select a shorter time range to reduce the data.` + ) + ); + } const baseQuery = queryAccess(data); diff --git a/server/private/routers/auditLogs/exportActionAuditLog.ts b/server/private/routers/auditLogs/exportActionAuditLog.ts index 1fc4d743..d8987916 100644 --- a/server/private/routers/auditLogs/exportActionAuditLog.ts +++ b/server/private/routers/auditLogs/exportActionAuditLog.ts @@ -22,9 +22,11 @@ import logger from "@server/logger"; import { queryActionAuditLogsParams, queryActionAuditLogsQuery, - queryAction + queryAction, + countActionQuery } from "./queryActionAuditLog"; import { generateCSV } from "@server/routers/auditLogs/generateCSV"; +import { MAX_EXPORT_LIMIT } from "@server/routers/auditLogs"; registry.registerPath({ method: "get", @@ -65,6 +67,15 @@ export async function exportActionAuditLogs( } const data = { ...parsedQuery.data, ...parsedParams.data }; + const [{ count }] = await countActionQuery(data); + if (count > MAX_EXPORT_LIMIT) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + `Export limit exceeded. Your selection contains ${count} rows, but the maximum is ${MAX_EXPORT_LIMIT} rows. Please select a shorter time range to reduce the data.` + ) + ); + } const baseQuery = queryAction(data); diff --git a/server/private/routers/auditLogs/queryAccessAuditLog.ts b/server/private/routers/auditLogs/queryAccessAuditLog.ts index 5d3162aa..7bc9968c 100644 --- a/server/private/routers/auditLogs/queryAccessAuditLog.ts +++ b/server/private/routers/auditLogs/queryAccessAuditLog.ts @@ -24,6 +24,7 @@ import { fromError } from "zod-validation-error"; import { QueryAccessAuditLogResponse } from "@server/routers/auditLogs/types"; import response from "@server/lib/response"; import logger from "@server/logger"; +import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo"; export const queryAccessAuditLogsQuery = z.object({ // iso string just validate its a parseable date @@ -32,7 +33,8 @@ export const queryAccessAuditLogsQuery = z.object({ .refine((val) => !isNaN(Date.parse(val)), { error: "timeStart must be a valid ISO date string" }) - .transform((val) => Math.floor(new Date(val).getTime() / 1000)), + .transform((val) => Math.floor(new Date(val).getTime() / 1000)) + .prefault(() => getSevenDaysAgo().toISOString()), timeEnd: z .string() .refine((val) => !isNaN(Date.parse(val)), { diff --git a/server/private/routers/auditLogs/queryActionAuditLog.ts b/server/private/routers/auditLogs/queryActionAuditLog.ts index eca583b4..8c92aed2 100644 --- a/server/private/routers/auditLogs/queryActionAuditLog.ts +++ b/server/private/routers/auditLogs/queryActionAuditLog.ts @@ -24,6 +24,7 @@ import { fromError } from "zod-validation-error"; import { QueryActionAuditLogResponse } from "@server/routers/auditLogs/types"; import response from "@server/lib/response"; import logger from "@server/logger"; +import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo"; export const queryActionAuditLogsQuery = z.object({ // iso string just validate its a parseable date @@ -32,7 +33,8 @@ export const queryActionAuditLogsQuery = z.object({ .refine((val) => !isNaN(Date.parse(val)), { error: "timeStart must be a valid ISO date string" }) - .transform((val) => Math.floor(new Date(val).getTime() / 1000)), + .transform((val) => Math.floor(new Date(val).getTime() / 1000)) + .prefault(() => getSevenDaysAgo().toISOString()), timeEnd: z .string() .refine((val) => !isNaN(Date.parse(val)), { diff --git a/server/routers/auditLogs/exportRequestAuditLog.ts b/server/routers/auditLogs/exportRequestAuditLog.ts index 9e55cfc4..8b70ec5e 100644 --- a/server/routers/auditLogs/exportRequestAuditLog.ts +++ b/server/routers/auditLogs/exportRequestAuditLog.ts @@ -9,17 +9,23 @@ import logger from "@server/logger"; import { queryAccessAuditLogsQuery, queryRequestAuditLogsParams, - queryRequest + queryRequest, + countRequestQuery } from "./queryRequestAuditLog"; import { generateCSV } from "./generateCSV"; +export const MAX_EXPORT_LIMIT = 50_000; + registry.registerPath({ method: "get", path: "/org/{orgId}/logs/request", description: "Query the request audit log for an organization", tags: [OpenAPITags.Org], request: { - query: queryAccessAuditLogsQuery, + query: queryAccessAuditLogsQuery.omit({ + limit: true, + offset: true + }), params: queryRequestAuditLogsParams }, responses: {} @@ -53,9 +59,19 @@ export async function exportRequestAuditLogs( const data = { ...parsedQuery.data, ...parsedParams.data }; + const [{ count }] = await countRequestQuery(data); + if (count > MAX_EXPORT_LIMIT) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + `Export limit exceeded. Your selection contains ${count} rows, but the maximum is ${MAX_EXPORT_LIMIT} rows. Please select a shorter time range to reduce the data.` + ) + ); + } + const baseQuery = queryRequest(data); - const log = await baseQuery.limit(data.limit).offset(data.offset); + const log = await baseQuery.limit(MAX_EXPORT_LIMIT); const csvData = generateCSV(log); diff --git a/server/routers/auditLogs/queryRequestAnalytics.ts b/server/routers/auditLogs/queryRequestAnalytics.ts index 9e4ea17e..b439cbd6 100644 --- a/server/routers/auditLogs/queryRequestAnalytics.ts +++ b/server/routers/auditLogs/queryRequestAnalytics.ts @@ -2,7 +2,7 @@ import { db, requestAuditLog, driver } from "@server/db"; import { registry } from "@server/openApi"; import { NextFunction } from "express"; import { Request, Response } from "express"; -import { eq, gt, lt, and, count, sql, desc, not, isNull } from "drizzle-orm"; +import { eq, gte, lte, and, count, sql, desc, not, isNull } from "drizzle-orm"; import { OpenAPITags } from "@server/openApi"; import { z } from "zod"; import createHttpError from "http-errors"; @@ -10,6 +10,7 @@ import HttpCode from "@server/types/HttpCode"; import { fromError } from "zod-validation-error"; import response from "@server/lib/response"; import logger from "@server/logger"; +import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo"; const queryAccessAuditLogsQuery = z.object({ // iso string just validate its a parseable date @@ -19,7 +20,8 @@ const queryAccessAuditLogsQuery = z.object({ error: "timeStart must be a valid ISO date string" }) .transform((val) => Math.floor(new Date(val).getTime() / 1000)) - .optional(), + .optional() + .prefault(() => getSevenDaysAgo().toISOString()), timeEnd: z .string() .refine((val) => !isNaN(Date.parse(val)), { @@ -55,15 +57,10 @@ type Q = z.infer; async function query(query: Q) { let baseConditions = and( eq(requestAuditLog.orgId, query.orgId), - lt(requestAuditLog.timestamp, query.timeEnd) + gte(requestAuditLog.timestamp, query.timeStart), + lte(requestAuditLog.timestamp, query.timeEnd) ); - if (query.timeStart) { - baseConditions = and( - baseConditions, - gt(requestAuditLog.timestamp, query.timeStart) - ); - } if (query.resourceId) { baseConditions = and( baseConditions, diff --git a/server/routers/auditLogs/queryRequestAuditLog.ts b/server/routers/auditLogs/queryRequestAuditLog.ts index 663ad787..8776f21a 100644 --- a/server/routers/auditLogs/queryRequestAuditLog.ts +++ b/server/routers/auditLogs/queryRequestAuditLog.ts @@ -11,6 +11,7 @@ import { fromError } from "zod-validation-error"; import { QueryRequestAuditLogResponse } from "@server/routers/auditLogs/types"; import response from "@server/lib/response"; import logger from "@server/logger"; +import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo"; export const queryAccessAuditLogsQuery = z.object({ // iso string just validate its a parseable date @@ -19,7 +20,8 @@ export const queryAccessAuditLogsQuery = z.object({ .refine((val) => !isNaN(Date.parse(val)), { error: "timeStart must be a valid ISO date string" }) - .transform((val) => Math.floor(new Date(val).getTime() / 1000)), + .transform((val) => Math.floor(new Date(val).getTime() / 1000)) + .prefault(() => getSevenDaysAgo().toISOString()), timeEnd: z .string() .refine((val) => !isNaN(Date.parse(val)), { diff --git a/src/app/[orgId]/settings/logs/access/page.tsx b/src/app/[orgId]/settings/logs/access/page.tsx index 1e7f2476..d5b12ddb 100644 --- a/src/app/[orgId]/settings/logs/access/page.tsx +++ b/src/app/[orgId]/settings/logs/access/page.tsx @@ -1,16 +1,12 @@ "use client"; import { Button } from "@app/components/ui/button"; import { toast } from "@app/hooks/useToast"; -import { useState, useRef, useEffect } from "react"; +import { useState, useRef, useEffect, useTransition } from "react"; import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useTranslations } from "next-intl"; -import { - getStoredPageSize, - LogDataTable, - setStoredPageSize -} from "@app/components/LogDataTable"; +import { LogDataTable } from "@app/components/LogDataTable"; import { ColumnDef } from "@tanstack/react-table"; import { DateTimeValue } from "@app/components/DateTimePicker"; import { ArrowUpRight, Key, User } from "lucide-react"; @@ -21,21 +17,22 @@ import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusCo import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; import { build } from "@server/build"; import { Alert, AlertDescription } from "@app/components/ui/alert"; +import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo"; +import axios from "axios"; +import { useStoredPageSize } from "@app/hooks/useStoredPageSize"; export default function GeneralPage() { - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const router = useRouter(); const searchParams = useSearchParams(); const api = createApiClient(useEnvContext()); const t = useTranslations(); - const { env } = useEnvContext(); const { orgId } = useParams(); const subscription = useSubscriptionStatusContext(); const { isUnlocked } = useLicenseStatusContext(); const [rows, setRows] = useState([]); const [isRefreshing, setIsRefreshing] = useState(false); - const [isExporting, setIsExporting] = useState(false); + const [isExporting, startTransition] = useTransition(); const [filterAttributes, setFilterAttributes] = useState<{ actors: string[]; resources: { @@ -70,9 +67,7 @@ export default function GeneralPage() { const [isLoading, setIsLoading] = useState(false); // Initialize page size from storage or default - const [pageSize, setPageSize] = useState(() => { - return getStoredPageSize("access-audit-logs", 20); - }); + const [pageSize, setPageSize] = useStoredPageSize("access-audit-logs", 20); // Set default date range to last 24 hours const getDefaultDateRange = () => { @@ -91,11 +86,11 @@ export default function GeneralPage() { } const now = new Date(); - const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000); + const lastWeek = getSevenDaysAgo(); return { startDate: { - date: yesterday + date: lastWeek }, endDate: { date: now @@ -148,7 +143,6 @@ export default function GeneralPage() { // Handle page size changes const handlePageSizeChange = (newPageSize: number) => { setPageSize(newPageSize); - setStoredPageSize(newPageSize, "access-audit-logs"); setCurrentPage(0); // Reset to first page when changing page size queryDateTime(dateRange.startDate, dateRange.endDate, 0, newPageSize); }; @@ -309,8 +303,6 @@ export default function GeneralPage() { const exportData = async () => { try { - setIsExporting(true); - // Prepare query params for export const params: any = { timeStart: dateRange.startDate?.date @@ -339,11 +331,21 @@ export default function GeneralPage() { document.body.appendChild(link); link.click(); link.parentNode?.removeChild(link); - setIsExporting(false); } catch (error) { + let apiErrorMessage: string | null = null; + if (axios.isAxiosError(error) && error.response) { + const data = error.response.data; + + if (data instanceof Blob && data.type === "application/json") { + // Parse the Blob as JSON + const text = await data.text(); + const errorData = JSON.parse(text); + apiErrorMessage = errorData.message; + } + } toast({ title: t("error"), - description: t("exportError"), + description: apiErrorMessage ?? t("exportError"), variant: "destructive" }); } @@ -631,7 +633,7 @@ export default function GeneralPage() { title={t("accessLogs")} onRefresh={refreshData} isRefreshing={isRefreshing} - onExport={exportData} + onExport={() => startTransition(exportData)} isExporting={isExporting} onDateRangeChange={handleDateRangeChange} dateRange={{ diff --git a/src/app/[orgId]/settings/logs/action/page.tsx b/src/app/[orgId]/settings/logs/action/page.tsx index 68f67b07..344866bb 100644 --- a/src/app/[orgId]/settings/logs/action/page.tsx +++ b/src/app/[orgId]/settings/logs/action/page.tsx @@ -1,32 +1,28 @@ "use client"; -import { Button } from "@app/components/ui/button"; -import { toast } from "@app/hooks/useToast"; -import { useState, useRef, useEffect } from "react"; -import { createApiClient } from "@app/lib/api"; -import { useEnvContext } from "@app/hooks/useEnvContext"; -import { useParams, useRouter, useSearchParams } from "next/navigation"; -import { useTranslations } from "next-intl"; -import { - getStoredPageSize, - LogDataTable, - setStoredPageSize -} from "@app/components/LogDataTable"; -import { ColumnDef } from "@tanstack/react-table"; -import { DateTimeValue } from "@app/components/DateTimePicker"; -import { Key, User } from "lucide-react"; import { ColumnFilter } from "@app/components/ColumnFilter"; +import { DateTimeValue } from "@app/components/DateTimePicker"; +import { LogDataTable } from "@app/components/LogDataTable"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; -import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext"; -import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; -import { build } from "@server/build"; import { Alert, AlertDescription } from "@app/components/ui/alert"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; +import { useStoredPageSize } from "@app/hooks/useStoredPageSize"; +import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext"; +import { toast } from "@app/hooks/useToast"; +import { createApiClient } from "@app/lib/api"; +import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo"; +import { build } from "@server/build"; +import { ColumnDef } from "@tanstack/react-table"; +import axios from "axios"; +import { Key, User } from "lucide-react"; +import { useTranslations } from "next-intl"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; +import { useEffect, useState, useTransition } from "react"; export default function GeneralPage() { - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const router = useRouter(); const api = createApiClient(useEnvContext()); const t = useTranslations(); - const { env } = useEnvContext(); const { orgId } = useParams(); const searchParams = useSearchParams(); const subscription = useSubscriptionStatusContext(); @@ -34,7 +30,7 @@ export default function GeneralPage() { const [rows, setRows] = useState([]); const [isRefreshing, setIsRefreshing] = useState(false); - const [isExporting, setIsExporting] = useState(false); + const [isExporting, startTransition] = useTransition(); const [filterAttributes, setFilterAttributes] = useState<{ actors: string[]; actions: string[]; @@ -58,9 +54,7 @@ export default function GeneralPage() { const [isLoading, setIsLoading] = useState(false); // Initialize page size from storage or default - const [pageSize, setPageSize] = useState(() => { - return getStoredPageSize("action-audit-logs", 20); - }); + const [pageSize, setPageSize] = useStoredPageSize("action-audit-logs", 20); // Set default date range to last 24 hours const getDefaultDateRange = () => { @@ -79,11 +73,11 @@ export default function GeneralPage() { } const now = new Date(); - const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000); + const lastWeek = getSevenDaysAgo(); return { startDate: { - date: yesterday + date: lastWeek }, endDate: { date: now @@ -136,7 +130,6 @@ export default function GeneralPage() { // Handle page size changes const handlePageSizeChange = (newPageSize: number) => { setPageSize(newPageSize); - setStoredPageSize(newPageSize, "action-audit-logs"); setCurrentPage(0); // Reset to first page when changing page size queryDateTime(dateRange.startDate, dateRange.endDate, 0, newPageSize); }; @@ -293,8 +286,6 @@ export default function GeneralPage() { const exportData = async () => { try { - setIsExporting(true); - // Prepare query params for export const params: any = { timeStart: dateRange.startDate?.date @@ -323,11 +314,21 @@ export default function GeneralPage() { document.body.appendChild(link); link.click(); link.parentNode?.removeChild(link); - setIsExporting(false); } catch (error) { + let apiErrorMessage: string | null = null; + if (axios.isAxiosError(error) && error.response) { + const data = error.response.data; + + if (data instanceof Blob && data.type === "application/json") { + // Parse the Blob as JSON + const text = await data.text(); + const errorData = JSON.parse(text); + apiErrorMessage = errorData.message; + } + } toast({ title: t("error"), - description: t("exportError"), + description: apiErrorMessage ?? t("exportError"), variant: "destructive" }); } @@ -484,7 +485,7 @@ export default function GeneralPage() { searchColumn="action" onRefresh={refreshData} isRefreshing={isRefreshing} - onExport={exportData} + onExport={() => startTransition(exportData)} isExporting={isExporting} onDateRangeChange={handleDateRangeChange} dateRange={{ diff --git a/src/app/[orgId]/settings/logs/request/page.tsx b/src/app/[orgId]/settings/logs/request/page.tsx index 42cfec57..741dd994 100644 --- a/src/app/[orgId]/settings/logs/request/page.tsx +++ b/src/app/[orgId]/settings/logs/request/page.tsx @@ -1,34 +1,32 @@ "use client"; -import { Button } from "@app/components/ui/button"; -import { toast } from "@app/hooks/useToast"; -import { useState, useRef, useEffect } from "react"; -import { createApiClient } from "@app/lib/api"; -import { useEnvContext } from "@app/hooks/useEnvContext"; -import { useParams, useRouter, useSearchParams } from "next/navigation"; -import { useTranslations } from "next-intl"; -import { - getStoredPageSize, - LogDataTable, - setStoredPageSize -} from "@app/components/LogDataTable"; -import { ColumnDef } from "@tanstack/react-table"; -import { DateTimeValue } from "@app/components/DateTimePicker"; -import { Key, RouteOff, User, Lock, Unlock, ArrowUpRight } from "lucide-react"; -import Link from "next/link"; import { ColumnFilter } from "@app/components/ColumnFilter"; +import { DateTimeValue } from "@app/components/DateTimePicker"; +import { LogDataTable } from "@app/components/LogDataTable"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; +import { Button } from "@app/components/ui/button"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { toast } from "@app/hooks/useToast"; +import { createApiClient } from "@app/lib/api"; +import { useTranslations } from "next-intl"; +import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo"; +import { ColumnDef } from "@tanstack/react-table"; +import axios from "axios"; +import { ArrowUpRight, Key, Lock, Unlock, User } from "lucide-react"; +import Link from "next/link"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; +import { useEffect, useState, useTransition } from "react"; +import { useStoredPageSize } from "@app/hooks/useStoredPageSize"; export default function GeneralPage() { const router = useRouter(); const api = createApiClient(useEnvContext()); const t = useTranslations(); - const { env } = useEnvContext(); const { orgId } = useParams(); const searchParams = useSearchParams(); const [rows, setRows] = useState([]); const [isRefreshing, setIsRefreshing] = useState(false); - const [isExporting, setIsExporting] = useState(false); + const [isExporting, startTransition] = useTransition(); // Pagination state const [totalCount, setTotalCount] = useState(0); @@ -36,9 +34,7 @@ export default function GeneralPage() { const [isLoading, setIsLoading] = useState(false); // Initialize page size from storage or default - const [pageSize, setPageSize] = useState(() => { - return getStoredPageSize("request-audit-logs", 20); - }); + const [pageSize, setPageSize] = useStoredPageSize("request-audit-logs", 20); const [filterAttributes, setFilterAttributes] = useState<{ actors: string[]; @@ -95,11 +91,11 @@ export default function GeneralPage() { } const now = new Date(); - const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000); + const lastWeek = getSevenDaysAgo(); return { startDate: { - date: yesterday + date: lastWeek }, endDate: { date: now @@ -152,7 +148,6 @@ export default function GeneralPage() { // Handle page size changes const handlePageSizeChange = (newPageSize: number) => { setPageSize(newPageSize); - setStoredPageSize(newPageSize, "request-audit-logs"); setCurrentPage(0); // Reset to first page when changing page size queryDateTime(dateRange.startDate, dateRange.endDate, 0, newPageSize); }; @@ -302,8 +297,6 @@ export default function GeneralPage() { const exportData = async () => { try { - setIsExporting(true); - // Prepare query params for export const params: any = { timeStart: dateRange.startDate?.date @@ -335,11 +328,21 @@ export default function GeneralPage() { document.body.appendChild(link); link.click(); link.parentNode?.removeChild(link); - setIsExporting(false); } catch (error) { + let apiErrorMessage: string | null = null; + if (axios.isAxiosError(error) && error.response) { + const data = error.response.data; + + if (data instanceof Blob && data.type === "application/json") { + // Parse the Blob as JSON + const text = await data.text(); + const errorData = JSON.parse(text); + apiErrorMessage = errorData.message; + } + } toast({ title: t("error"), - description: t("exportError"), + description: apiErrorMessage ?? t("exportError"), variant: "destructive" }); } @@ -773,7 +776,7 @@ export default function GeneralPage() { searchColumn="host" onRefresh={refreshData} isRefreshing={isRefreshing} - onExport={exportData} + onExport={() => startTransition(exportData)} isExporting={isExporting} onDateRangeChange={handleDateRangeChange} dateRange={{ diff --git a/src/components/LogAnalyticsData.tsx b/src/components/LogAnalyticsData.tsx index 2fd30189..aaf1b344 100644 --- a/src/components/LogAnalyticsData.tsx +++ b/src/components/LogAnalyticsData.tsx @@ -1,22 +1,27 @@ "use client"; -import { useEnvContext } from "@app/hooks/useEnvContext"; -import { createApiClient } from "@app/lib/api"; +import { cn } from "@app/lib/cn"; import { logAnalyticsFiltersSchema, logQueries, resourceQueries } from "@app/lib/queries"; import { useQuery } from "@tanstack/react-query"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; -import { useState } from "react"; -import { Card, CardContent, CardHeader } from "./ui/card"; import { LoaderIcon, RefreshCw, XIcon } from "lucide-react"; +import { useTranslations } from "next-intl"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { DateRangePicker, type DateTimeValue } from "./DateTimePicker"; import { Button } from "./ui/button"; -import { cn } from "@app/lib/cn"; -import { useTranslations } from "next-intl"; +import { Card, CardContent, CardHeader } from "./ui/card"; +import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji"; +import { + InfoSection, + InfoSectionContent, + InfoSections, + InfoSectionTitle +} from "./InfoSection"; +import { Label } from "./ui/label"; import { Select, SelectContent, @@ -24,23 +29,10 @@ import { SelectTrigger, SelectValue } from "./ui/select"; -import { Label } from "./ui/label"; import { Separator } from "./ui/separator"; -import { - InfoSection, - InfoSectionContent, - InfoSections, - InfoSectionTitle -} from "./InfoSection"; import { WorldMap } from "./WorldMap"; -import { countryCodeToFlagEmoji } from "@app/lib/countryCodeToFlagEmoji"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger -} from "./ui/tooltip"; +import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts"; import { ChartContainer, ChartLegend, @@ -49,7 +41,13 @@ import { ChartTooltipContent, type ChartConfig } from "./ui/chart"; -import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger +} from "./ui/tooltip"; +import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo"; export type AnalyticsContentProps = { orgId: string; @@ -67,17 +65,18 @@ export function LogAnalyticsData(props: AnalyticsContentProps) { const isEmptySearchParams = !filters.resourceId && !filters.timeStart && !filters.timeEnd; - const env = useEnvContext(); - const [api] = useState(() => createApiClient(env)); const router = useRouter(); + console.log({ filters }); const dateRange = { - startDate: filters.timeStart ? new Date(filters.timeStart) : undefined, - endDate: filters.timeEnd ? new Date(filters.timeEnd) : undefined + startDate: filters.timeStart + ? new Date(filters.timeStart) + : getSevenDaysAgo(), + endDate: filters.timeEnd ? new Date(filters.timeEnd) : new Date() }; const { data: resources = [], isFetching: isFetchingResources } = useQuery( - resourceQueries.listNamesPerOrg(props.orgId, api) + resourceQueries.listNamesPerOrg(props.orgId) ); const { @@ -88,7 +87,6 @@ export function LogAnalyticsData(props: AnalyticsContentProps) { } = useQuery( logQueries.requestAnalytics({ orgId: props.orgId, - api, filters }) ); diff --git a/src/components/LogDataTable.tsx b/src/components/LogDataTable.tsx index 492a8c15..e21b5cc0 100644 --- a/src/components/LogDataTable.tsx +++ b/src/components/LogDataTable.tsx @@ -1,16 +1,5 @@ "use client"; -import { - ColumnDef, - flexRender, - getCoreRowModel, - useReactTable, - getPaginationRowModel, - SortingState, - getSortedRowModel, - ColumnFiltersState, - getFilteredRowModel -} from "@tanstack/react-table"; import { Table, TableBody, @@ -19,29 +8,36 @@ import { TableHeader, TableRow } from "@/components/ui/table"; -import { Button } from "@app/components/ui/button"; -import { useEffect, useMemo, useState } from "react"; -import { Input } from "@app/components/ui/input"; import { DataTablePagination } from "@app/components/DataTablePagination"; -import { - Plus, - Search, - RefreshCw, - Filter, - X, - Download, - ChevronRight, - ChevronDown -} from "lucide-react"; -import { - Card, - CardContent, - CardHeader, - CardTitle -} from "@app/components/ui/card"; -import { Tabs, TabsList, TabsTrigger } from "@app/components/ui/tabs"; -import { useTranslations } from "next-intl"; import { DateRangePicker, DateTimeValue } from "@app/components/DateTimePicker"; +import { Button } from "@app/components/ui/button"; +import { Card, CardContent, CardHeader } from "@app/components/ui/card"; +import { + ColumnDef, + ColumnFiltersState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + SortingState, + useReactTable +} from "@tanstack/react-table"; +import { + ChevronDown, + ChevronRight, + Download, + Loader, + RefreshCw +} from "lucide-react"; +import { useTranslations } from "next-intl"; +import { useState, useEffect, useMemo } from "react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger +} from "./ui/tooltip"; const STORAGE_KEYS = { PAGE_SIZE: "datatable-page-size", @@ -400,15 +396,28 @@ export function LogDataTable({ )} {onExport && ( - + + + + + + + {t("exportCsvTooltip")} + + + )} diff --git a/src/lib/getSevenDaysAgo.ts b/src/lib/getSevenDaysAgo.ts new file mode 100644 index 00000000..b58f3cb3 --- /dev/null +++ b/src/lib/getSevenDaysAgo.ts @@ -0,0 +1,7 @@ +export function getSevenDaysAgo() { + const today = new Date(); + today.setHours(0, 0, 0, 0); // Set to midnight + const sevenDaysAgo = new Date(today); + sevenDaysAgo.setDate(today.getDate() - 7); + return sevenDaysAgo; +} diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 2a19f3f9..5fe85078 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -168,17 +168,15 @@ export type LogAnalyticsFilters = z.TypeOf; export const logQueries = { requestAnalytics: ({ orgId, - filters, - api + filters }: { orgId: string; filters: LogAnalyticsFilters; - api: AxiosInstance; }) => queryOptions({ queryKey: ["REQUEST_LOG_ANALYTICS", orgId, filters] as const, - queryFn: async ({ signal }) => { - const res = await api.get< + queryFn: async ({ signal, meta }) => { + const res = await meta!.api.get< AxiosResponse >(`/org/${orgId}/logs/analytics`, { params: filters, @@ -228,11 +226,11 @@ export const resourceQueries = { return res.data.data.clients; } }), - listNamesPerOrg: (orgId: string, api: AxiosInstance) => + listNamesPerOrg: (orgId: string) => queryOptions({ queryKey: ["RESOURCES_NAMES", orgId] as const, - queryFn: async ({ signal }) => { - const res = await api.get< + queryFn: async ({ signal, meta }) => { + const res = await meta!.api.get< AxiosResponse >(`/org/${orgId}/resource-names`, { signal