import { keepPreviousData } from '@tanstack/react-query' import { useDebounce } from '@uidotdev/usehooks' import dayjs from 'dayjs' import { ArrowDown, ArrowUp, RefreshCw } from 'lucide-react' import { useEffect, useMemo, useState } from 'react' import { LogDetailsPanel } from 'components/interfaces/AuditLogs/LogDetailsPanel' import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { FilterPopover } from 'components/ui/FilterPopover' import type { AuditLog } from 'data/organizations/organization-audit-logs-query' import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { useProfileAuditLogsQuery } from 'data/profile/profile-audit-logs-query' import { useProjectsInfiniteQuery } from 'data/projects/projects-infinite-query' import { Button } from 'ui' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { TimestampInfo } from 'ui-patterns/TimestampInfo' import { LogsDatePicker } from '../Settings/Logs/Logs.DatePickers' export const AuditLogs = () => { const currentTime = dayjs().utc().set('millisecond', 0) const [search, setSearch] = useState('') const debouncedSearch = useDebounce(search, 500) const [dateSortDesc, setDateSortDesc] = useState(true) const [dateRange, setDateRange] = useState({ from: currentTime.subtract(1, 'day').toISOString(), to: currentTime.toISOString(), }) const [selectedLog, setSelectedLog] = useState() const [filters, setFilters] = useState<{ projects: string[] }>({ projects: [], // project_ref[] }) const { data: projectsData, isLoading: isLoadingProjects, isFetching, isFetchingNextPage, hasNextPage, fetchNextPage, } = useProjectsInfiniteQuery( { search: search.length === 0 ? search : debouncedSearch }, { placeholderData: keepPreviousData } ) const projects = useMemo(() => projectsData?.pages.flatMap((page) => page.projects), [projectsData?.pages]) || [] const { data: organizations } = useOrganizationsQuery() const { data, error, isPending: isLoading, isSuccess, isError, isRefetching, refetch, } = useProfileAuditLogsQuery( { iso_timestamp_start: dateRange.from, iso_timestamp_end: dateRange.to, }, { retry: false, } ) const logs = data?.result ?? [] const sortedLogs = logs ?.sort((a, b) => dateSortDesc ? Number(new Date(b.occurred_at)) - Number(new Date(a.occurred_at)) : Number(new Date(a.occurred_at)) - Number(new Date(b.occurred_at)) ) ?.filter((log) => { if (filters.projects.length > 0) { return filters.projects.includes( log.target.metadata.project_ref || log.target.metadata.ref || '' ) } else { return log } }) // This feature depends on the subscription tier of the user. Free user can view logs up to 1 day // in the past. The API limits the logs to maximum of 1 day and 5 minutes so when the page is // viewed for more than 5 minutes, the call parameters needs to be updated. This also works with // higher tiers (7 days of logs).The user will see a loading shimmer. useEffect(() => { const duration = dayjs(dateRange.from).diff(dayjs(dateRange.to)) const interval = setInterval(() => { const currentTime = dayjs().utc().set('millisecond', 0) setDateRange({ from: currentTime.add(duration).toISOString(), to: currentTime.toISOString(), }) }, 5 * 60000) return () => clearInterval(interval) }, [dateRange.from, dateRange.to]) return ( <>

Filter by

setFilters({ ...filters, projects: values })} search={search} setSearch={setSearch} hasNextPage={hasNextPage} isLoading={isLoadingProjects} isFetching={isFetching} isFetchingNextPage={isFetchingNextPage} fetchNextPage={fetchNextPage} /> setDateRange(value)} helpers={[ { text: 'Last 1 hour', calcFrom: () => dayjs().subtract(1, 'hour').toISOString(), calcTo: () => dayjs().toISOString(), }, { text: 'Last 3 hours', calcFrom: () => dayjs().subtract(3, 'hour').toISOString(), calcTo: () => dayjs().toISOString(), }, { text: 'Last 6 hours', calcFrom: () => dayjs().subtract(6, 'hour').toISOString(), calcTo: () => dayjs().toISOString(), }, { text: 'Last 12 hours', calcFrom: () => dayjs().subtract(12, 'hour').toISOString(), calcTo: () => dayjs().toISOString(), }, { text: 'Last 24 hours', calcFrom: () => dayjs().subtract(1, 'day').toISOString(), calcTo: () => dayjs().toISOString(), }, ]} /> {isSuccess && ( <>

Viewing {sortedLogs.length} logs in total

)}
{isLoading && (
)} {isError && } {isSuccess && ( <> {logs.length === 0 ? (

You do not have any audit logs available yet

) : logs.length > 0 && sortedLogs.length === 0 ? (

No audit logs found based on the filters applied

) : (
Action , Target ,

Date

) : ( ) } onClick={() => setDateSortDesc(!dateSortDesc)} tooltip={{ content: { side: 'bottom', text: dateSortDesc ? 'Sort latest first' : 'Sort earliest first', }, }} />
, , ]} body={ sortedLogs?.map((log) => { const logProjectRef = log.target.metadata.project_ref || log.target.metadata.ref const logOrgSlug = log.target.metadata.org_slug || log.target.metadata.slug const project = projects?.find((project) => project.ref === logProjectRef) const organization = organizations?.find((org) => org.slug === logOrgSlug) const hasStatusCode = log.action.metadata[0]?.status !== undefined return ( setSelectedLog(log)} className="cursor-pointer hover:!bg-alternative transition duration-100" >
{hasStatusCode && (

{log.action.metadata[0].status}

)}

{log.action.name}

{project?.name ? 'Project: ' : organization?.name ? 'Organization: ' : null} {project?.name ?? organization?.name ?? '-'}

{logProjectRef ? 'Ref: ' : logOrgSlug ? 'Slug: ' : null} {logProjectRef ?? logOrgSlug}

) }) ?? [] } /> )} )} setSelectedLog(undefined)} /> ) }