diff --git a/packages/console/src/App.tsx b/packages/console/src/App.tsx index f95a886c3..acd81b88f 100644 --- a/packages/console/src/App.tsx +++ b/packages/console/src/App.tsx @@ -71,6 +71,7 @@ const Main = () => { } /> } /> + } /> } /> diff --git a/packages/console/src/pages/AuditLogs/components/ApplicationSelector/index.tsx b/packages/console/src/components/AuditLogTable/components/ApplicationSelector/index.tsx similarity index 100% rename from packages/console/src/pages/AuditLogs/components/ApplicationSelector/index.tsx rename to packages/console/src/components/AuditLogTable/components/ApplicationSelector/index.tsx diff --git a/packages/console/src/pages/AuditLogs/components/EventName/index.module.scss b/packages/console/src/components/AuditLogTable/components/EventName/index.module.scss similarity index 100% rename from packages/console/src/pages/AuditLogs/components/EventName/index.module.scss rename to packages/console/src/components/AuditLogTable/components/EventName/index.module.scss diff --git a/packages/console/src/pages/AuditLogs/components/EventName/index.tsx b/packages/console/src/components/AuditLogTable/components/EventName/index.tsx similarity index 100% rename from packages/console/src/pages/AuditLogs/components/EventName/index.tsx rename to packages/console/src/components/AuditLogTable/components/EventName/index.tsx diff --git a/packages/console/src/pages/AuditLogs/components/EventSelector/index.tsx b/packages/console/src/components/AuditLogTable/components/EventSelector/index.tsx similarity index 100% rename from packages/console/src/pages/AuditLogs/components/EventSelector/index.tsx rename to packages/console/src/components/AuditLogTable/components/EventSelector/index.tsx diff --git a/packages/console/src/components/AuditLogTable/index.module.scss b/packages/console/src/components/AuditLogTable/index.module.scss new file mode 100644 index 000000000..66a32c543 --- /dev/null +++ b/packages/console/src/components/AuditLogTable/index.module.scss @@ -0,0 +1,36 @@ +@use '@/scss/underscore' as _; + +.filter { + display: flex; + justify-content: right; + align-items: center; + + .title { + color: var(--color-caption); + font: var(--font-body-medium); + } + + .eventSelector { + width: 300px; + margin-left: _.unit(2); + } + + .applicationSelector { + width: 250px; + margin-left: _.unit(2); + } +} + +.table { + margin-top: _.unit(4); + flex: 1; +} + +.pagination { + margin-top: _.unit(4); + min-height: 32px; +} + +.eventName { + width: 360px; +} diff --git a/packages/console/src/components/AuditLogTable/index.tsx b/packages/console/src/components/AuditLogTable/index.tsx new file mode 100644 index 000000000..f241223c4 --- /dev/null +++ b/packages/console/src/components/AuditLogTable/index.tsx @@ -0,0 +1,152 @@ +import { LogDTO, LogResult } from '@logto/schemas'; +import { conditionalString } from '@silverhand/essentials'; +import classNames from 'classnames'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import useSWR from 'swr'; + +import ApplicationName from '@/components/ApplicationName'; +import Pagination from '@/components/Pagination'; +import TableEmpty from '@/components/Table/TableEmpty'; +import TableError from '@/components/Table/TableError'; +import TableLoading from '@/components/Table/TableLoading'; +import UserName from '@/components/UserName'; +import { RequestError } from '@/hooks/use-api'; +import * as tableStyles from '@/scss/table.module.scss'; + +import ApplicationSelector from './components/ApplicationSelector'; +import EventName from './components/EventName'; +import EventSelector from './components/EventSelector'; +import * as styles from './index.module.scss'; + +const pageSize = 20; + +type Props = { + userId?: string; +}; + +const AuditLogTable = ({ userId }: Props) => { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const [query, setQuery] = useSearchParams(); + const pageIndex = Number(query.get('page') ?? '1'); + const event = query.get('event'); + const applicationId = query.get('applicationId'); + const queryString = [ + `page=${pageIndex}`, + `page_size=${pageSize}`, + conditionalString(event && `logType=${event}`), + conditionalString(applicationId && `applicationId=${applicationId}`), + conditionalString(userId && `userId=${userId}`), + ] + .filter(Boolean) + .join('&'); + const { data, error, mutate } = useSWR<[LogDTO[], number], RequestError>( + `/api/logs?${queryString}` + ); + const isLoading = !data && !error; + const navigate = useNavigate(); + const [logs, totalCount] = data ?? []; + + const updateQuery = (key: string, value: string) => { + const queries: Record = {}; + + for (const [key, value] of query.entries()) { + // eslint-disable-next-line @silverhand/fp/no-mutation + queries[key] = value; + } + + setQuery({ + ...queries, + [key]: value, + }); + }; + + return ( + <> +
+
{t('logs.filter_by')}
+
+ { + updateQuery('event', value ?? ''); + }} + /> +
+
+ { + updateQuery('applicationId', value ?? ''); + }} + /> +
+
+
+ + + + + + + + + + + + + + + + + {!data && error && ( + mutate(undefined, true)} + /> + )} + {isLoading && } + {logs?.length === 0 && } + {logs?.map(({ type, payload, createdAt, id }) => ( + { + navigate(`/audit-logs/${id}`); + }} + > + + + + + + ))} + +
{t('logs.event')}{t('logs.user')}{t('logs.application')}{t('logs.time')}
+ + {payload.userId ? : '-'} + {payload.applicationId ? ( + + ) : ( + '-' + )} + {new Date(createdAt).toLocaleString()}
+
+
+ {!!totalCount && ( + { + updateQuery('page', String(page)); + }} + /> + )} +
+ + ); +}; + +export default AuditLogTable; diff --git a/packages/console/src/pages/AuditLogs/index.module.scss b/packages/console/src/pages/AuditLogs/index.module.scss index fba4f4d6c..f5686478c 100644 --- a/packages/console/src/pages/AuditLogs/index.module.scss +++ b/packages/console/src/pages/AuditLogs/index.module.scss @@ -8,38 +8,3 @@ display: flex; justify-content: space-between; } - -.filter { - display: flex; - justify-content: right; - align-items: center; - - .title { - color: var(--color-caption); - font: var(--font-body-medium); - } - - .eventSelector { - width: 300px; - margin-left: _.unit(2); - } - - .applicationSelector { - width: 250px; - margin-left: _.unit(2); - } -} - -.table { - margin-top: _.unit(4); - flex: 1; -} - -.pagination { - margin-top: _.unit(4); - min-height: 32px; -} - -.eventName { - width: 360px; -} diff --git a/packages/console/src/pages/AuditLogs/index.tsx b/packages/console/src/pages/AuditLogs/index.tsx index 6989fe7dc..c7bde2cd2 100644 --- a/packages/console/src/pages/AuditLogs/index.tsx +++ b/packages/console/src/pages/AuditLogs/index.tsx @@ -1,130 +1,18 @@ -import { LogDTO, LogResult } from '@logto/schemas'; -import { conditionalString } from '@silverhand/essentials'; -import classNames from 'classnames'; import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { useNavigate, useSearchParams } from 'react-router-dom'; -import useSWR from 'swr'; -import ApplicationName from '@/components/ApplicationName'; +import AuditLogTable from '@/components/AuditLogTable'; import Card from '@/components/Card'; import CardTitle from '@/components/CardTitle'; -import Pagination from '@/components/Pagination'; -import TableEmpty from '@/components/Table/TableEmpty'; -import TableError from '@/components/Table/TableError'; -import TableLoading from '@/components/Table/TableLoading'; -import UserName from '@/components/UserName'; -import { RequestError } from '@/hooks/use-api'; -import * as tableStyles from '@/scss/table.module.scss'; -import ApplicationSelector from './components/ApplicationSelector'; -import EventName from './components/EventName'; -import EventSelector from './components/EventSelector'; import * as styles from './index.module.scss'; -const pageSize = 20; - const AuditLogs = () => { - const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - const [query, setQuery] = useSearchParams(); - const pageIndex = Number(query.get('page') ?? '1'); - const event = query.get('event'); - const applicationId = query.get('applicationId'); - const { data, error, mutate } = useSWR<[LogDTO[], number], RequestError>( - `/api/logs?page=${pageIndex}&page_size=${pageSize}${conditionalString( - event && `&logType=${event}` - )}${conditionalString(applicationId && `&applicationId=${applicationId}`)}` - ); - const isLoading = !data && !error; - const navigate = useNavigate(); - const [logs, totalCount] = data ?? []; - return (
-
-
{t('logs.filter_by')}
-
- { - setQuery({ event: value ?? '' }); - }} - /> -
-
- { - setQuery({ applicationId: value ?? '' }); - }} - /> -
-
-
- - - - - - - - - - - - - - - - - {!data && error && ( - mutate(undefined, true)} - /> - )} - {isLoading && } - {logs?.length === 0 && } - {logs?.map(({ type, payload, createdAt, id }) => ( - { - navigate(`/audit-logs/${id}`); - }} - > - - - - - - ))} - -
{t('logs.event')}{t('logs.user')}{t('logs.application')}{t('logs.time')}
- - {payload.userId ? : '-'} - {payload.applicationId ? ( - - ) : ( - '-' - )} - {new Date(createdAt).toLocaleString()}
-
-
- {!!totalCount && ( - { - setQuery({ page: String(page) }); - }} - /> - )} -
+
); }; diff --git a/packages/console/src/pages/UserDetails/index.module.scss b/packages/console/src/pages/UserDetails/index.module.scss index 6e2a39bac..1e056f022 100644 --- a/packages/console/src/pages/UserDetails/index.module.scss +++ b/packages/console/src/pages/UserDetails/index.module.scss @@ -76,4 +76,8 @@ .textField { @include _.form-text-field; } + + .logs { + padding: _.unit(6) 0; + } } diff --git a/packages/console/src/pages/UserDetails/index.tsx b/packages/console/src/pages/UserDetails/index.tsx index a8c15d2a5..dbcd484db 100644 --- a/packages/console/src/pages/UserDetails/index.tsx +++ b/packages/console/src/pages/UserDetails/index.tsx @@ -6,10 +6,11 @@ import { Controller, useController, useForm } from 'react-hook-form'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; import ReactModal from 'react-modal'; -import { useParams } from 'react-router-dom'; +import { useLocation, useParams } from 'react-router-dom'; import useSWR from 'swr'; import ActionMenu, { ActionMenuItem } from '@/components/ActionMenu'; +import AuditLogTable from '@/components/AuditLogTable'; import Button from '@/components/Button'; import Card from '@/components/Card'; import CodeEditor from '@/components/CodeEditor'; @@ -48,6 +49,8 @@ type FormData = { }; const UserDetails = () => { + const location = useLocation(); + const isLogs = location.pathname.endsWith('/logs'); const { id } = useParams(); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const [isDeleteFormOpen, setIsDeleteFormOpen] = useState(false); @@ -186,95 +189,101 @@ const UserDetails = () => { {t('user_details.tab_settings')} {t('user_details.tab_logs')} -
-
- {getValues('primaryEmail') && ( - - - - )} - {getValues('primaryPhone') && ( - - - - )} - {getValues('username') && ( - - - - )} - - - - - - !value || uriValidator(value) || t('errors.invalid_uri_format'), - })} - hasError={Boolean(errors.avatar)} - errorMessage={errors.avatar?.message} - /> - - - ( - - )} - /> - - - { - void mutate(); - }} - /> - - - - + {isLogs ? ( +
+
-
-
-
- +
+
+
+
+ + )} )}