| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- import type { FC } from 'react';
- import React, { useState, useCallback, useRef } from 'react';
- import { format } from 'date-fns';
- import { useTranslation } from 'react-i18next';
- import type { IClearable } from '~/client/interfaces/clearable';
- import { toastError } from '~/client/util/toastr';
- import type { SupportedActionType } from '~/interfaces/activity';
- import { useSWRxActivity } from '~/stores/activity';
- import { useAuditLogEnabled, useAuditLogAvailableActions } from '~/stores/context';
- import { LoadingSpinner } from '../LoadingSpinner';
- import PaginationWrapper from '../PaginationWrapper';
- import { ActivityTable } from './AuditLog/ActivityTable';
- import { AuditLogDisableMode } from './AuditLog/AuditLogDisableMode';
- import { AuditLogSettings } from './AuditLog/AuditLogSettings';
- import { DateRangePicker } from './AuditLog/DateRangePicker';
- import { SearchUsernameTypeahead } from './AuditLog/SearchUsernameTypeahead';
- import { SelectActionDropdown } from './AuditLog/SelectActionDropdown';
- const formatDate = (date: Date | null) => {
- if (date == null) {
- return '';
- }
- return format(new Date(date), 'yyyy-MM-dd');
- };
- const PAGING_LIMIT = 10;
- export const AuditLogManagement: FC = () => {
- const { t } = useTranslation('admin');
- const typeaheadRef = useRef<IClearable>(null);
- const { data: auditLogAvailableActionsData } = useAuditLogAvailableActions();
- /*
- * State
- */
- const [isSettingPage, setIsSettingPage] = useState<boolean>(false);
- const [activePageNumber, setActivePageNumber] = useState<number>(1);
- const [jumpPageNumber, setJumpPageNumber] = useState<number>(1);
- const offset = (activePageNumber - 1) * PAGING_LIMIT;
- const [startDate, setStartDate] = useState<Date | null>(null);
- const [endDate, setEndDate] = useState<Date | null>(null);
- const [selectedUsernames, setSelectedUsernames] = useState<string[]>([]);
- const [actionMap, setActionMap] = useState(
- new Map<SupportedActionType, boolean>(auditLogAvailableActionsData != null ? auditLogAvailableActionsData.map(action => [action, true]) : []),
- );
- /*
- * Fetch
- */
- const selectedDate = { startDate: formatDate(startDate), endDate: formatDate(endDate) };
- const selectedActionList = Array.from(actionMap.entries()).filter(v => v[1]).map(v => v[0]);
- const searchFilter = { actions: selectedActionList, dates: selectedDate, usernames: selectedUsernames };
- const { data: activityData, mutate: mutateActivity, error } = useSWRxActivity(PAGING_LIMIT, offset, searchFilter);
- const activityList = activityData?.docs != null ? activityData.docs : [];
- const totalActivityNum = activityData?.totalDocs != null ? activityData.totalDocs : 0;
- const totalPagingPages = activityData?.totalPages != null ? activityData.totalPages : 0;
- const isLoading = activityData === undefined && error == null;
- if (error != null) {
- toastError('Failed to get Audit Log');
- }
- const { data: auditLogEnabled } = useAuditLogEnabled();
- /*
- * Functions
- */
- const setActivePageHandler = useCallback((selectedPageNum: number) => {
- setActivePageNumber(selectedPageNum);
- }, []);
- const datePickerChangedHandler = useCallback((dateList: Date[] | null[]) => {
- setActivePageNumber(1);
- setStartDate(dateList[0]);
- setEndDate(dateList[1]);
- }, []);
- const actionCheckboxChangedHandler = useCallback((action: SupportedActionType) => {
- setActivePageNumber(1);
- actionMap.set(action, !actionMap.get(action));
- setActionMap(new Map(actionMap.entries()));
- }, [actionMap, setActionMap]);
- const multipleActionCheckboxChangedHandler = useCallback((actions: SupportedActionType[], isChecked) => {
- setActivePageNumber(1);
- actions.forEach(action => actionMap.set(action, isChecked));
- setActionMap(new Map(actionMap.entries()));
- }, [actionMap, setActionMap]);
- const setUsernamesHandler = useCallback((usernames: string[]) => {
- setActivePageNumber(1);
- setSelectedUsernames(usernames);
- }, []);
- const clearButtonPushedHandler = useCallback(() => {
- setActivePageNumber(1);
- setStartDate(null);
- setEndDate(null);
- setSelectedUsernames([]);
- typeaheadRef.current?.clear();
- if (auditLogAvailableActionsData != null) {
- setActionMap(new Map<SupportedActionType, boolean>(auditLogAvailableActionsData.map(action => [action, true])));
- }
- }, [setActivePageNumber, setStartDate, setEndDate, setSelectedUsernames, setActionMap, auditLogAvailableActionsData]);
- const reloadButtonPushedHandler = useCallback(() => {
- setActivePageNumber(1);
- mutateActivity();
- }, [mutateActivity]);
- const jumpPageInputChangeHandler = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
- const inputNumber = Number(e.target.value);
- const isNan = Number.isNaN(inputNumber);
- if (!isNan) {
- // eslint-disable-next-line no-nested-ternary
- const jumpPageNumber = inputNumber > totalPagingPages ? totalPagingPages : inputNumber <= 0 ? activePageNumber : inputNumber;
- setJumpPageNumber(jumpPageNumber);
- }
- else {
- setJumpPageNumber(activePageNumber);
- }
- }, [totalPagingPages, activePageNumber, setJumpPageNumber]);
- const jumpPageInputKeyDownHandler = useCallback((e) => {
- if (e.key === 'Enter') {
- setActivePageNumber(jumpPageNumber);
- }
- }, [setActivePageNumber, jumpPageNumber]);
- const jumpPageButtonPushedHandler = useCallback(() => {
- setActivePageNumber(jumpPageNumber);
- }, [jumpPageNumber]);
- // eslint-disable-next-line max-len
- const activityCounter = `<b>${activityList.length === 0 ? 0 : offset + 1}</b> - <b>${(PAGING_LIMIT * activePageNumber) - (PAGING_LIMIT - activityList.length)}</b> of <b>${totalActivityNum}<b/>`;
- if (!auditLogEnabled) {
- return <AuditLogDisableMode />;
- }
- return (
- <div data-testid="admin-auditlog">
- <button type="button" className="btn btn-outline-secondary mb-4" onClick={() => setIsSettingPage(!isSettingPage)}>
- {
- isSettingPage
- ? <><span className="material-symbols-outlined">arrow_left_alt</span>{t('admin:audit_log_management.return')}</>
- : <><span className="material-symbols-outlined">settings</span>{t('admin:audit_log_management.settings')}</>
- }
- </button>
- <h2 className="admin-setting-header mb-3">
- <span>
- {isSettingPage ? t('audit_log_management.audit_log_settings') : t('audit_log_management.audit_log')}
- </span>
- { !isSettingPage && (
- <button type="button" className="btn btn-sm ms-auto grw-btn-reload" onClick={reloadButtonPushedHandler}>
- <span className="material-symbols-outlined">refresh</span>
- </button>
- )}
- </h2>
- {isSettingPage ? (
- <AuditLogSettings />
- ) : (
- <>
- <div className="row row-cols-lg-auto mb-3 g-3">
- <div className="col-12">
- <SearchUsernameTypeahead
- ref={typeaheadRef}
- onChange={setUsernamesHandler}
- />
- </div>
- <div className="col-12">
- <DateRangePicker
- startDate={startDate}
- endDate={endDate}
- onChange={datePickerChangedHandler}
- />
- </div>
- <div className="col-12">
- <SelectActionDropdown
- actionMap={actionMap}
- availableActions={auditLogAvailableActionsData || []}
- onChangeAction={actionCheckboxChangedHandler}
- onChangeMultipleAction={multipleActionCheckboxChangedHandler}
- />
- </div>
- <div className="col-12">
- <button type="button" className="btn btn-link" onClick={clearButtonPushedHandler}>
- {t('admin:audit_log_management.clear')}
- </button>
- </div>
- </div>
- <p
- className="ms-2"
- // eslint-disable-next-line react/no-danger
- dangerouslySetInnerHTML={{ __html: activityCounter }}
- />
- { isLoading
- ? (
- <div className="text-muted text-center mb-5">
- <LoadingSpinner className="me-1 fs-3" />
- </div>
- )
- : (
- <ActivityTable activityList={activityList} />
- )
- }
- <div className="d-flex flex-row justify-content-center">
- <PaginationWrapper
- activePage={activePageNumber}
- changePage={setActivePageHandler}
- totalItemsCount={totalActivityNum}
- pagingLimit={PAGING_LIMIT}
- align="center"
- size="sm"
- />
- <div className="admin-audit-log ms-3">
- <label htmlFor="jumpPageInput" className="form-label me-1 text-secondary">Jump To Page</label>
- <input
- id="jumpPageInput"
- type="text"
- className="jump-page-input"
- onChange={jumpPageInputChangeHandler}
- onKeyDown={jumpPageInputKeyDownHandler}
- />
- <button className="btn btn-sm" type="button" onClick={jumpPageButtonPushedHandler}>
- <b>Go</b>
- </button>
- </div>
- </div>
- </>
- )}
- </div>
- );
- };
|