Small refactor

This commit is contained in:
Joshen Lim committed 2023-09-08 14:04:06 +08:00
1 parent 3a6ed05990
commit eff4373b6b
7 files changed
+229 -224

No files matched your search

+209 -215
View File
@@ -1,8 +1,9 @@
import * as Tooltip from '@radix-ui/react-tooltip'
import dayjs from 'dayjs'
import { useEffect, useState } from 'react'
import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw } from 'ui'
import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold'
import { FilterPopover, LogDetailsPanel } from 'components/interfaces/AuditLogs'
import Table from 'components/to-be-cleaned/Table'
import AlertError from 'components/ui/AlertError'
import { DatePicker } from 'components/ui/DatePicker'
@@ -11,9 +12,6 @@ import { 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, IconArrowDown, IconArrowUp, IconRefreshCw } from 'ui'
import FilterPopover from '../Organization/AuditLogs/FilterPopover'
import LogDetailsPanel from '../Organization/AuditLogs/LogDetailsPanel'
const AuditLogs = () => {
const currentTime = dayjs().utc().set('millisecond', 0)
@@ -71,219 +69,215 @@ const AuditLogs = () => {
return (
<>
<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="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">
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>
)
}}
/>
{isSuccess && (
<>
<div className="h-[20px] border-r border-scale-700 !ml-4 !mr-2" />
<p className="prose text-xs">Viewing {sortedLogs.length} logs in total</p>
</>
)}
</div>
<Button
type="default"
disabled={isLoading || isRefetching}
icon={<IconRefreshCw className={isRefetching ? 'animate-spin' : ''} />}
onClick={() => refetch()}
>
{isRefetching ? 'Refreshing' : 'Refresh'}
</Button>
<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="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">
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>
)
}}
/>
{isSuccess && (
<>
<div className="h-[20px] border-r border-scale-700 !ml-4 !mr-2" />
<p className="prose text-xs">Viewing {sortedLogs.length} logs in total</p>
</>
)}
</div>
{isLoading && (
<div className="space-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
</div>
)}
{isError && <AlertError error={error} subject="Failed to retrieve audit logs" />}
{isSuccess && (
<>
{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">You do 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>
) : (
<Table
head={[
<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 project = projects?.find(
(project) => project.ref === log.target.metadata.project_ref
)
const organization = organizations?.find(
(org) => org.slug === log.target.metadata.org_slug
)
const hasStatusCode = log.action.metadata[0]?.status !== undefined
return (
<Table.tr
key={log.occurred_at}
onClick={() => setSelectedLog(log)}
className="cursor-pointer hover:!bg-scale-100 transition duration-100"
>
<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-[170px] truncate" title={log.action.name}>
{log.action.name}
</p>
</div>
</Table.td>
<Table.td>
<p
className="text-scale-1100 max-w-[230px] truncate"
title={project?.name ?? organization?.name ?? 'Unknown'}
>
{project?.name
? 'Project: '
: organization?.name
? 'Organization: '
: null}
{project?.name ?? organization?.name ?? 'Unknown'}
</p>
<p
className="text-scale-1000 text-xs mt-0.5 truncate"
title={
log.target.metadata.project_ref ?? log.target.metadata.org_slug
}
>
{log.target.metadata.project_ref
? 'Ref: '
: log.target.metadata.org_slug
? 'Slug: '
: null}
{log.target.metadata.project_ref ?? log.target.metadata.org_slug}
</p>
</Table.td>
<Table.td>
{dayjs(log.occurred_at).format('DD MMM YYYY, HH:mm:ss')}
</Table.td>
<Table.td align="right">
<Button type="default">View details</Button>
</Table.td>
</Table.tr>
)
}) ?? []
}
/>
)}
</>
)}
<Button
type="default"
disabled={isLoading || isRefetching}
icon={<IconRefreshCw className={isRefetching ? 'animate-spin' : ''} />}
onClick={() => refetch()}
>
{isRefetching ? 'Refreshing' : 'Refresh'}
</Button>
</div>
</ScaffoldContainerLegacy>
{isLoading && (
<div className="space-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
</div>
)}
{isError && <AlertError error={error} subject="Failed to retrieve audit logs" />}
{isSuccess && (
<>
{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">You do 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>
) : (
<Table
head={[
<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 project = projects?.find(
(project) => project.ref === log.target.metadata.project_ref
)
const organization = organizations?.find(
(org) => org.slug === log.target.metadata.org_slug
)
const hasStatusCode = log.action.metadata[0]?.status !== undefined
return (
<Table.tr
key={log.occurred_at}
onClick={() => setSelectedLog(log)}
className="cursor-pointer hover:!bg-scale-100 transition duration-100"
>
<Table.td className="max-w-[250px]">
<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="truncate" title={log.action.name}>
{log.action.name}
</p>
</div>
</Table.td>
<Table.td>
<p
className="text-scale-1100 max-w-[230px] truncate"
title={project?.name ?? organization?.name ?? 'Unknown'}
>
{project?.name
? 'Project: '
: organization?.name
? 'Organization: '
: null}
{project?.name ?? organization?.name ?? 'Unknown'}
</p>
<p
className="text-scale-1000 text-xs mt-0.5 truncate"
title={log.target.metadata.project_ref ?? log.target.metadata.org_slug}
>
{log.target.metadata.project_ref
? 'Ref: '
: log.target.metadata.org_slug
? 'Slug: '
: null}
{log.target.metadata.project_ref ?? log.target.metadata.org_slug}
</p>
</Table.td>
<Table.td>
{dayjs(log.occurred_at).format('DD MMM YYYY, HH:mm:ss')}
</Table.td>
<Table.td align="right">
<Button type="default">View details</Button>
</Table.td>
</Table.tr>
)
}) ?? []
}
/>
)}
</>
)}
</div>
<LogDetailsPanel selectedLog={selectedLog} onClose={() => setSelectedLog(undefined)} />
</>
@@ -0,0 +1,4 @@
import FilterPopover from './FilterPopover'
import LogDetailsPanel from './LogDetailsPanel'
export { FilterPopover, LogDetailsPanel }
@@ -2,8 +2,10 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import dayjs from 'dayjs'
import Image from 'next/image'
import { useState } from 'react'
import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw, IconUser } from 'ui'
import { useParams } from 'common'
import { FilterPopover, LogDetailsPanel } from 'components/interfaces/AuditLogs'
import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold'
import Table from 'components/to-be-cleaned/Table'
import AlertError from 'components/ui/AlertError'
@@ -17,9 +19,6 @@ import { useOrganizationDetailQuery } from 'data/organizations/organization-deta
import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useProjectsQuery } from 'data/projects/projects-query'
import { Alert, Button, IconArrowDown, IconArrowUp, IconRefreshCw, IconUser } from 'ui'
import FilterPopover from './FilterPopover'
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
@@ -283,14 +282,14 @@ const AuditLogs = () => {
</div>
</div>
</Table.td>
<Table.td>
<Table.td className="max-w-[250px]">
<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-[170px] truncate" title={log.action.name}>
<p className="truncate" title={log.action.name}>
{log.action.name}
</p>
</div>
@@ -274,7 +274,7 @@ function _DatePicker({
{children !== undefined ? (
children
) : (
<span>
<>
{/* Custom */}
{selectsRange &&
appliedStartDate &&
@@ -289,7 +289,7 @@ function _DatePicker({
) : (
'Custom'
)}
</span>
</>
)}
</Button>
</Popover>
+10 -2
View File
@@ -1,12 +1,20 @@
import { AuditLogs } from 'components/interfaces/Account'
import { AccountLayout } from 'components/layouts'
import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold'
import { FormHeader } from 'components/ui/Forms'
import { NextPageWithLayout } from 'types'
const User: NextPageWithLayout = () => {
return (
<div className="my-2">
<ScaffoldContainerLegacy className="gap-0">
<div className="flex items-center justify-between">
<FormHeader
title="Account audit logs"
description="View the audit log trail of actions made from your account"
/>
</div>
<AuditLogs />
</div>
</ScaffoldContainerLegacy>
)
}