mirror of
https://github.com/supabase/supabase.git
synced 2026-10-16 22:59:11 +03:00
Update empty state for no logs based on filters
This commit is contained in:
1 parent
50c9b46bdf
commit
6ca180bb05
1 file changed
+199
-202
@@ -23,8 +23,6 @@ import LogDetailsPanel from './LogDetailsPanel'
|
||||
// [Joshen considerations]
|
||||
// - Maybe fix the height of the table to the remaining height of the viewport, so that the search input is always visible
|
||||
// - We'll need pagination as well if the audit logs get too large, but that needs to be implemented on the API side first if possible
|
||||
// - Need to update RQ's resource key, so that it can update with iso_timestamp_start/end changes, check Alaister OR LogsExplorer
|
||||
// - Need to update to show retention policy (KM will update endpoint)
|
||||
// - I've hidden time input in the date picker for now cause the time support in the component is a bit iffy, need to investigate
|
||||
// - Maybe a rule to follow from here is just everytime we call dayjs, use UTC(), one TZ to rule them all
|
||||
|
||||
@@ -89,221 +87,220 @@ const AuditLogs = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* [Joshen] We'll need to also handle then, if the selected date range is out, rather than blocking all actions */}
|
||||
{isError && <AlertError subject="Failed to retrieve audit logs" />}
|
||||
|
||||
{isSuccess && (
|
||||
<>
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-xs prose">Filter by</p>
|
||||
<FilterPopover
|
||||
name="Users"
|
||||
options={members ?? []}
|
||||
labelKey="username"
|
||||
valueKey="gotrue_id"
|
||||
activeOptions={filters.users}
|
||||
onSaveFilters={(values) => setFilters({ ...filters, users: values })}
|
||||
/>
|
||||
<FilterPopover
|
||||
name="Projects"
|
||||
options={projects ?? []}
|
||||
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={dayjs().subtract(retentionPeriod, 'days').toDate()}
|
||||
maxDate={dayjs().toDate()}
|
||||
onChange={(value) => {
|
||||
if (value.from !== null && value.to !== null) {
|
||||
const current = dayjs().utc()
|
||||
const from = dayjs(value.from)
|
||||
.utc()
|
||||
.hour(current.hour())
|
||||
.minute(current.minute())
|
||||
.second(current.second())
|
||||
.toISOString()
|
||||
const to = dayjs(value.to)
|
||||
.utc()
|
||||
.hour(current.hour())
|
||||
.minute(current.minute())
|
||||
.second(current.second())
|
||||
.toISOString()
|
||||
setDateRange({ from, to })
|
||||
}
|
||||
}}
|
||||
renderFooter={() => {
|
||||
return (
|
||||
<Alert title="" variant="info" className="mx-3 pl-2 pr-2 pt-1 pb-2">
|
||||
Your organization has a log retention period of{' '}
|
||||
<span className="text-brand-900">
|
||||
{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>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{logs.length === 0 ? (
|
||||
<div className="bg-scale-100 dark:bg-scale-300 border rounded p-4 flex items-center justify-between">
|
||||
<p className="prose text-sm">
|
||||
Your organization does not have any audit logs available yet
|
||||
</p>
|
||||
</div>
|
||||
) : logs.length > 0 && sortedLogs.length === 0 ? (
|
||||
<div className="bg-scale-100 dark:bg-scale-300 border rounded p-4 flex items-center justify-between">
|
||||
<p className="prose text-sm">No audit logs found based on the filters applied</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-xs prose">Filter by</p>
|
||||
<FilterPopover
|
||||
name="Users"
|
||||
options={members ?? []}
|
||||
labelKey="username"
|
||||
valueKey="gotrue_id"
|
||||
activeOptions={filters.users}
|
||||
onSaveFilters={(values) => setFilters({ ...filters, users: values })}
|
||||
/>
|
||||
<FilterPopover
|
||||
name="Projects"
|
||||
options={projects ?? []}
|
||||
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={dayjs().subtract(retentionPeriod, 'days').toDate()}
|
||||
maxDate={dayjs().toDate()}
|
||||
onChange={(value) => {
|
||||
if (value.from !== null && value.to !== null) {
|
||||
const current = dayjs().utc()
|
||||
const from = dayjs(value.from)
|
||||
.utc()
|
||||
.hour(current.hour())
|
||||
.minute(current.minute())
|
||||
.second(current.second())
|
||||
.toISOString()
|
||||
const to = dayjs(value.to)
|
||||
.utc()
|
||||
.hour(current.hour())
|
||||
.minute(current.minute())
|
||||
.second(current.second())
|
||||
.toISOString()
|
||||
setDateRange({ from, to })
|
||||
}
|
||||
}}
|
||||
renderFooter={() => {
|
||||
return (
|
||||
<Alert title="" variant="info" className="mx-3 pl-2 pr-2 pt-1 pb-2">
|
||||
Your organization has a log retention period of{' '}
|
||||
<span className="text-brand-900">
|
||||
{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>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Table
|
||||
head={[
|
||||
<Table.th key="user" className="py-2">
|
||||
User
|
||||
</Table.th>,
|
||||
<Table.th key="action" className="py-2">
|
||||
Action
|
||||
</Table.th>,
|
||||
<Table.th key="target" className="py-2">
|
||||
Target
|
||||
</Table.th>,
|
||||
<Table.th key="date" className="py-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<p>Date</p>
|
||||
|
||||
<Table
|
||||
head={[
|
||||
<Table.th key="user" className="py-2">
|
||||
User
|
||||
</Table.th>,
|
||||
<Table.th key="action" className="py-2">
|
||||
Action
|
||||
</Table.th>,
|
||||
<Table.th key="target" className="py-2">
|
||||
Target
|
||||
</Table.th>,
|
||||
<Table.th key="date" className="py-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<p>Date</p>
|
||||
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
type="text"
|
||||
className="px-1"
|
||||
icon={
|
||||
dateSortDesc ? (
|
||||
<IconArrowDown strokeWidth={1.5} size={14} />
|
||||
) : (
|
||||
<IconArrowUp strokeWidth={1.5} size={14} />
|
||||
)
|
||||
}
|
||||
onClick={() => setDateSortDesc(!dateSortDesc)}
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="right">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
{dateSortDesc ? 'Sort latest first' : 'Sort earliest first'}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</Table.th>,
|
||||
<Table.th key="actions" className="py-2"></Table.th>,
|
||||
]}
|
||||
body={
|
||||
sortedLogs?.map((log) => {
|
||||
const user = members.find((member) => member.gotrue_id === log.actor.id)
|
||||
const role = roles.find((role) => user?.role_ids?.[0] === role.id)
|
||||
const project = projects?.find(
|
||||
(project) => project.ref === log.permission_group.project_ref
|
||||
)
|
||||
const organization = organizations?.find(
|
||||
(org) => org.slug === log.permission_group.org_slug
|
||||
)
|
||||
|
||||
const hasStatusCode = log.action.metadata[0]?.status !== undefined
|
||||
const userIcon =
|
||||
user === undefined ? (
|
||||
<div className="flex h-[40px] w-[40px] flex items-center justify-center border-2 rounded-full border-scale-700">
|
||||
<p>?</p>
|
||||
</div>
|
||||
) : user?.invited_id || user?.username === user?.primary_email ? (
|
||||
<div className="flex h-[40px] w-[40px] flex items-center justify-center border-2 rounded-full border-scale-700">
|
||||
<IconUser size={18} strokeWidth={2} />
|
||||
</div>
|
||||
) : (
|
||||
<Image
|
||||
alt={user?.username}
|
||||
src={`https://github.com/${user?.username ?? ''}.png?size=80`}
|
||||
width="40"
|
||||
height="40"
|
||||
className="border rounded-full"
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Button
|
||||
type="text"
|
||||
className="px-1"
|
||||
icon={
|
||||
dateSortDesc ? (
|
||||
<IconArrowDown strokeWidth={1.5} size={14} />
|
||||
) : (
|
||||
<IconArrowUp strokeWidth={1.5} size={14} />
|
||||
)
|
||||
}
|
||||
onClick={() => setDateSortDesc(!dateSortDesc)}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<Table.tr
|
||||
key={log.timestamp}
|
||||
onClick={() => setSelectedLog(log)}
|
||||
className="cursor-pointer hover:!bg-scale-100 transition duration-100"
|
||||
>
|
||||
<Table.td>
|
||||
<div className="flex items-center space-x-4">
|
||||
{userIcon}
|
||||
<div>
|
||||
<p className="text-scale-1100">{user?.username ?? log.actor.id}</p>
|
||||
{role && (
|
||||
<p className="mt-0.5 text-xs text-scale-1000">{role?.name}</p>
|
||||
)}
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="right">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
||||
'border border-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-scale-1200">
|
||||
{dateSortDesc ? 'Sort latest first' : 'Sort earliest first'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<div className="flex items-center space-x-2">
|
||||
{hasStatusCode && (
|
||||
<p className="bg-scale-400 rounded px-1 flex items-center justify-center text-xs font-mono border">
|
||||
{log.action.metadata[0].status}
|
||||
</p>
|
||||
)}
|
||||
<p className="max-w-[200px] truncate">{log.action.name}</p>
|
||||
</div>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<p className="text-scale-1100 truncate">
|
||||
{project?.name
|
||||
? 'Project: '
|
||||
: organization?.name
|
||||
? 'Organization: '
|
||||
: null}
|
||||
{project?.name ?? organization?.name ?? 'Entity no longer exists'}
|
||||
</p>
|
||||
<p className="text-scale-1000 text-xs mt-0.5 truncate">
|
||||
{log.permission_group.project_ref
|
||||
? 'Ref: '
|
||||
: log.permission_group.org_slug
|
||||
? 'Slug: '
|
||||
: null}
|
||||
{log.permission_group.project_ref ?? log.permission_group.org_slug}
|
||||
</p>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
{dayjs(log.timestamp).format('DD MMM YYYY, HH:mm:ss')}
|
||||
</Table.td>
|
||||
<Table.td align="right">
|
||||
<Button type="default">View details</Button>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</Table.th>,
|
||||
<Table.th key="actions" className="py-2"></Table.th>,
|
||||
]}
|
||||
body={
|
||||
sortedLogs?.map((log) => {
|
||||
const user = members.find((member) => member.gotrue_id === log.actor.id)
|
||||
const role = roles.find((role) => user?.role_ids?.[0] === role.id)
|
||||
const project = projects?.find(
|
||||
(project) => project.ref === log.permission_group.project_ref
|
||||
)
|
||||
const organization = organizations?.find(
|
||||
(org) => org.slug === log.permission_group.org_slug
|
||||
)
|
||||
|
||||
const hasStatusCode = log.action.metadata[0]?.status !== undefined
|
||||
const userIcon =
|
||||
user === undefined ? (
|
||||
<div className="flex h-[40px] w-[40px] flex items-center justify-center border-2 rounded-full border-scale-700">
|
||||
<p>?</p>
|
||||
</div>
|
||||
) : user?.invited_id || user?.username === user?.primary_email ? (
|
||||
<div className="flex h-[40px] w-[40px] flex items-center justify-center border-2 rounded-full border-scale-700">
|
||||
<IconUser size={18} strokeWidth={2} />
|
||||
</div>
|
||||
) : (
|
||||
<Image
|
||||
alt={user?.username}
|
||||
src={`https://github.com/${user?.username ?? ''}.png?size=80`}
|
||||
width="40"
|
||||
height="40"
|
||||
className="border rounded-full"
|
||||
/>
|
||||
)
|
||||
}) ?? []
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
||||
return (
|
||||
<Table.tr
|
||||
key={log.timestamp}
|
||||
onClick={() => setSelectedLog(log)}
|
||||
className="cursor-pointer hover:!bg-scale-100 transition duration-100"
|
||||
>
|
||||
<Table.td>
|
||||
<div className="flex items-center space-x-4">
|
||||
{userIcon}
|
||||
<div>
|
||||
<p className="text-scale-1100">{user?.username ?? log.actor.id}</p>
|
||||
{role && (
|
||||
<p className="mt-0.5 text-xs text-scale-1000">{role?.name}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<div className="flex items-center space-x-2">
|
||||
{hasStatusCode && (
|
||||
<p className="bg-scale-400 rounded px-1 flex items-center justify-center text-xs font-mono border">
|
||||
{log.action.metadata[0].status}
|
||||
</p>
|
||||
)}
|
||||
<p className="max-w-[200px] truncate">{log.action.name}</p>
|
||||
</div>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<p className="text-scale-1100 truncate">
|
||||
{project?.name
|
||||
? 'Project: '
|
||||
: organization?.name
|
||||
? 'Organization: '
|
||||
: null}
|
||||
{project?.name ?? organization?.name ?? 'Entity no longer exists'}
|
||||
</p>
|
||||
<p className="text-scale-1000 text-xs mt-0.5 truncate">
|
||||
{log.permission_group.project_ref
|
||||
? 'Ref: '
|
||||
: log.permission_group.org_slug
|
||||
? 'Slug: '
|
||||
: null}
|
||||
{log.permission_group.project_ref ?? log.permission_group.org_slug}
|
||||
</p>
|
||||
</Table.td>
|
||||
<Table.td>{dayjs(log.timestamp).format('DD MMM YYYY, HH:mm:ss')}</Table.td>
|
||||
<Table.td align="right">
|
||||
<Button type="default">View details</Button>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
)
|
||||
}) ?? []
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user