Skip to content
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
83 changes: 43 additions & 40 deletions apps/studio/components/interfaces/Account/AuditLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ import { LogDetailsPanel } from 'components/interfaces/AuditLogs'
import Table from 'components/to-be-cleaned/Table'
import AlertError from 'components/ui/AlertError'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { DatePicker } from 'components/ui/DatePicker'
import { FilterPopover } from 'components/ui/FilterPopover'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
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 { useProjectsQuery } from 'data/projects/projects-query'
import { Alert, Button } from 'ui'
import { Button } from 'ui'
import { TimestampInfo } from 'ui-patterns'
import { formatSelectedDateRange } from '../Organization/AuditLogs/AuditLogs.utils'
import { LogsDatePicker } from '../Settings/Logs/Logs.DatePickers'

const AuditLogs = () => {
const currentTime = dayjs().utc().set('millisecond', 0)
Expand All @@ -33,12 +32,16 @@ const AuditLogs = () => {
const { data: projects } = useProjectsQuery()
const { data: organizations } = useOrganizationsQuery()
const { data, error, isLoading, isSuccess, isError, isRefetching, refetch } =
useProfileAuditLogsQuery({
iso_timestamp_start: dateRange.from,
iso_timestamp_end: dateRange.to,
})
useProfileAuditLogsQuery(
{
iso_timestamp_start: dateRange.from,
iso_timestamp_end: dateRange.to,
},
{
retry: false,
}
)

const retentionPeriod = data?.retention_period ?? 0
const logs = data?.result ?? []
const sortedLogs = logs
?.sort((a, b) =>
Expand All @@ -54,9 +57,6 @@ const AuditLogs = () => {
}
})

const minDate = dayjs().subtract(retentionPeriod, 'days')
const maxDate = dayjs()

// 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
Expand Down Expand Up @@ -88,35 +88,38 @@ const AuditLogs = () => {
activeOptions={filters.projects}
onSaveFilters={(values) => setFilters({ ...filters, projects: values })}
/>
<DatePicker
hideTime
hideClear
triggerButtonType="dashed"
triggerButtonTitle=""
from={dateRange.from}
to={dateRange.to}
minDate={minDate.toDate()}
maxDate={maxDate.toDate()}
onChange={(value) => {
if (value.from !== null && value.to !== null) {
const { from, to } = formatSelectedDateRange(value)
setDateRange({ from, to })
}
}}
renderFooter={() => {
return (
<Alert title="" variant="info" className="mx-3 pl-2 pr-2 pt-1 pb-2">
You have a log retention period of{' '}
<span className="text-brand">
{retentionPeriod} day
{retentionPeriod > 1 ? 's' : ''}
</span>
. You may only view logs from{' '}
{dayjs().subtract(retentionPeriod, 'days').format('DD MMM YYYY')} as the
earliest date.
</Alert>
)
}}
<LogsDatePicker
hideWarnings
value={dateRange}
onSubmit={(value) => 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 && (
<>
Expand Down
165 changes: 82 additions & 83 deletions apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ import {
Button,
WarningIcon,
} from 'ui'
import { Admonition } from 'ui-patterns'
import { formatSelectedDateRange } from './AuditLogs.utils'
import { LogsDatePicker } from 'components/interfaces/Settings/Logs/Logs.DatePickers'

// [Joshen considerations]
// - Maybe fix the height of the table to the remaining height of the viewport, so that the search input is always visible
Expand Down Expand Up @@ -69,14 +68,7 @@ const AuditLogs = () => {
},
{
enabled: canReadAuditLogs,
retry(_failureCount, error) {
if (error.message.endsWith('upgrade to Team or Enterprise Plan to access audit logs.')) {
return false
}
return true
},
retryOnMount: false,
refetchOnWindowFocus: false,
retry: false,
}
)

Expand Down Expand Up @@ -139,6 +131,77 @@ const AuditLogs = () => {
<>
<ScaffoldContainerLegacy>
<div className="space-y-4 flex flex-col">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<p className="text-xs prose">Filter by</p>
<FilterPopover
name="Users"
options={activeMembers}
labelKey="username"
valueKey="gotrue_id"
activeOptions={filters.users}
onSaveFilters={(values) => setFilters({ ...filters, users: values })}
/>
<FilterPopover
name="Projects"
options={
projects?.filter((p) => p.organization_id === currentOrganization?.id) ?? []
}
labelKey="name"
valueKey="ref"
activeOptions={filters.projects}
onSaveFilters={(values) => setFilters({ ...filters, projects: values })}
/>
<LogsDatePicker
hideWarnings
value={dateRange}
onSubmit={(value) => 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 && (
<>
<div className="h-[20px] border-r border-strong !ml-4 !mr-2" />
<p className="prose text-xs">Viewing {sortedLogs.length} logs in total</p>
</>
)}
</div>
<Button
type="default"
disabled={isLoading || isRefetching}
icon={<RefreshCw className={isRefetching ? 'animate-spin' : ''} />}
onClick={() => refetch()}
>
{isRefetching ? 'Refreshing' : 'Refresh'}
</Button>
</div>

{isLoading && (
<div className="space-y-2">
<ShimmeringLoader />
Expand Down Expand Up @@ -176,86 +239,22 @@ const AuditLogs = () => {
</div>
</div>
</Alert_Shadcn_>
) : error.message.includes('range exceeded') ? (
<Alert_Shadcn_ variant="destructive" title="Date range too large">
<WarningIcon />
<AlertTitle_Shadcn_>Date range too large</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
The selected date range exceeds the maximum allowed period. Please select a
smaller time range.
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
) : (
<AlertError error={error} subject="Failed to retrieve audit logs" />
)
) : null}

{isSuccess && (
<>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<p className="text-xs prose">Filter by</p>
<FilterPopover
name="Users"
options={activeMembers}
labelKey="username"
valueKey="gotrue_id"
activeOptions={filters.users}
onSaveFilters={(values) => setFilters({ ...filters, users: values })}
/>
<FilterPopover
name="Projects"
options={
projects?.filter((p) => p.organization_id === currentOrganization?.id) ?? []
}
labelKey="name"
valueKey="ref"
activeOptions={filters.projects}
onSaveFilters={(values) => setFilters({ ...filters, projects: values })}
/>
<DatePicker
hideTime
hideClear
triggerButtonType="dashed"
triggerButtonTitle=""
from={dateRange.from}
to={dateRange.to}
minDate={minDate.toDate()}
maxDate={maxDate.toDate()}
onChange={(value) => {
if (value.from !== null && value.to !== null) {
const { from, to } = formatSelectedDateRange(value)
setDateRange({ from, to })
}
}}
renderFooter={() => {
return (
<Admonition
showIcon={false}
type="default"
className="w-auto mx-2 px-3 py-2"
>
<div className="text-xs text-foreground-light">
Your organization has a log retention period of{' '}
<span className="text-brand">
{retentionPeriod} day
{retentionPeriod > 1 ? 's' : ''}
</span>
. You may only view logs from{' '}
{dayjs().subtract(retentionPeriod, 'days').format('DD MMM YYYY')} as the
earliest date.
</div>
</Admonition>
)
}}
/>
{isSuccess && (
<>
<div className="h-[20px] border-r border-strong !ml-4 !mr-2" />
<p className="prose text-xs">Viewing {sortedLogs.length} logs in total</p>
</>
)}
</div>
<Button
type="default"
disabled={isLoading || isRefetching}
icon={<RefreshCw className={isRefetching ? 'animate-spin' : ''} />}
onClick={() => refetch()}
>
{isRefetching ? 'Refreshing' : 'Refresh'}
</Button>
</div>
{logs.length === 0 ? (
<div className="bg-surface-100 border rounded p-4 flex items-center justify-between">
<p className="prose text-sm">
Expand Down
Loading
Loading