AuditLogManagement.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import React, {
  2. FC, useState, useCallback, useRef,
  3. } from 'react';
  4. import { format } from 'date-fns';
  5. import { useTranslation } from 'react-i18next';
  6. import { IClearable } from '~/client/interfaces/clearable';
  7. import { toastError } from '~/client/util/apiNotification';
  8. import { SupportedActionType } from '~/interfaces/activity';
  9. import { useSWRxActivity } from '~/stores/activity';
  10. import { useAuditLogEnabled, useAuditLogAvailableActions } from '~/stores/context';
  11. import PaginationWrapper from '../PaginationWrapper';
  12. import { ActivityTable } from './AuditLog/ActivityTable';
  13. import { AuditLogDisableMode } from './AuditLog/AuditLogDisableMode';
  14. import { AuditLogSettings } from './AuditLog/AuditLogSettings';
  15. import { DateRangePicker } from './AuditLog/DateRangePicker';
  16. import { SearchUsernameTypeahead } from './AuditLog/SearchUsernameTypeahead';
  17. import { SelectActionDropdown } from './AuditLog/SelectActionDropdown';
  18. const formatDate = (date: Date | null) => {
  19. if (date == null) {
  20. return '';
  21. }
  22. return format(new Date(date), 'yyyy-MM-dd');
  23. };
  24. const PAGING_LIMIT = 10;
  25. export const AuditLogManagement: FC = () => {
  26. const { t } = useTranslation();
  27. const typeaheadRef = useRef<IClearable>(null);
  28. const { data: auditLogAvailableActionsData } = useAuditLogAvailableActions();
  29. /*
  30. * State
  31. */
  32. const [isSettingPage, setIsSettingPage] = useState<boolean>(false);
  33. const [activePage, setActivePage] = useState<number>(1);
  34. const offset = (activePage - 1) * PAGING_LIMIT;
  35. const [startDate, setStartDate] = useState<Date | null>(null);
  36. const [endDate, setEndDate] = useState<Date | null>(null);
  37. const [selectedUsernames, setSelectedUsernames] = useState<string[]>([]);
  38. const [actionMap, setActionMap] = useState(
  39. new Map<SupportedActionType, boolean>(auditLogAvailableActionsData != null ? auditLogAvailableActionsData.map(action => [action, true]) : []),
  40. );
  41. /*
  42. * Fetch
  43. */
  44. const selectedDate = { startDate: formatDate(startDate), endDate: formatDate(endDate) };
  45. const selectedActionList = Array.from(actionMap.entries()).filter(v => v[1]).map(v => v[0]);
  46. const searchFilter = { actions: selectedActionList, dates: selectedDate, usernames: selectedUsernames };
  47. const { data: activityData, mutate: mutateActivity, error } = useSWRxActivity(PAGING_LIMIT, offset, searchFilter);
  48. const activityList = activityData?.docs != null ? activityData.docs : [];
  49. const totalActivityNum = activityData?.totalDocs != null ? activityData.totalDocs : 0;
  50. const totalPagingPages = activityData?.totalPages != null ? activityData.totalPages : 0;
  51. const isLoading = activityData === undefined && error == null;
  52. if (error != null) {
  53. toastError('Failed to get Audit Log');
  54. }
  55. const { data: auditLogEnabled } = useAuditLogEnabled();
  56. /*
  57. * Functions
  58. */
  59. const setActivePageHandler = useCallback((selectedPageNum: number) => {
  60. setActivePage(selectedPageNum);
  61. }, []);
  62. const datePickerChangedHandler = useCallback((dateList: Date[] | null[]) => {
  63. setActivePage(1);
  64. setStartDate(dateList[0]);
  65. setEndDate(dateList[1]);
  66. }, []);
  67. const actionCheckboxChangedHandler = useCallback((action: SupportedActionType) => {
  68. setActivePage(1);
  69. actionMap.set(action, !actionMap.get(action));
  70. setActionMap(new Map(actionMap.entries()));
  71. }, [actionMap, setActionMap]);
  72. const multipleActionCheckboxChangedHandler = useCallback((actions: SupportedActionType[], isChecked) => {
  73. setActivePage(1);
  74. actions.forEach(action => actionMap.set(action, isChecked));
  75. setActionMap(new Map(actionMap.entries()));
  76. }, [actionMap, setActionMap]);
  77. const setUsernamesHandler = useCallback((usernames: string[]) => {
  78. setActivePage(1);
  79. setSelectedUsernames(usernames);
  80. }, []);
  81. const clearButtonPushedHandler = useCallback(() => {
  82. setActivePage(1);
  83. setStartDate(null);
  84. setEndDate(null);
  85. setSelectedUsernames([]);
  86. typeaheadRef.current?.clear();
  87. if (auditLogAvailableActionsData != null) {
  88. setActionMap(new Map<SupportedActionType, boolean>(auditLogAvailableActionsData.map(action => [action, true])));
  89. }
  90. }, [setActivePage, setStartDate, setEndDate, setSelectedUsernames, setActionMap, auditLogAvailableActionsData]);
  91. const reloadButtonPushedHandler = useCallback(() => {
  92. setActivePage(1);
  93. mutateActivity();
  94. }, [mutateActivity]);
  95. const jumpPageInputChangeHandler = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
  96. const inputNumber = Number(e.target.value);
  97. const isNan = Number.isNaN(inputNumber);
  98. if (!isNan) {
  99. if (inputNumber > totalPagingPages) {
  100. setActivePage(totalPagingPages);
  101. return;
  102. }
  103. if (inputNumber <= 0) {
  104. setActivePage(1);
  105. return;
  106. }
  107. setActivePage(inputNumber);
  108. }
  109. }, [totalPagingPages]);
  110. // eslint-disable-next-line max-len
  111. const activityCounter = `<b>${activityList.length === 0 ? 0 : offset + 1}</b> - <b>${(PAGING_LIMIT * activePage) - (PAGING_LIMIT - activityList.length)}</b> of <b>${totalActivityNum}<b/>`;
  112. if (!auditLogEnabled) {
  113. return <AuditLogDisableMode />;
  114. }
  115. return (
  116. <div data-testid="admin-auditlog">
  117. <button type="button" className="btn btn-outline-secondary mb-4" onClick={() => setIsSettingPage(!isSettingPage)}>
  118. {
  119. isSettingPage
  120. ? <><i className="fa fa-hand-o-left mr-1" />{t('admin:audit_log_management.return')}</>
  121. : <><i className="fa icon-settings mr-1" />{t('admin:audit_log_management.settings')}</>
  122. }
  123. </button>
  124. <h2 className="admin-setting-header mb-3">
  125. <span>
  126. {isSettingPage ? t('AuditLog Settings') : t('AuditLog')}
  127. </span>
  128. { !isSettingPage && (
  129. <button type="button" className="btn btn-sm ml-auto grw-btn-reload" onClick={reloadButtonPushedHandler}>
  130. <i className="icon icon-reload"></i>
  131. </button>
  132. )}
  133. </h2>
  134. {isSettingPage ? (
  135. <AuditLogSettings />
  136. ) : (
  137. <>
  138. <div className="form-inline mb-3">
  139. <SearchUsernameTypeahead
  140. ref={typeaheadRef}
  141. onChange={setUsernamesHandler}
  142. />
  143. <DateRangePicker
  144. startDate={startDate}
  145. endDate={endDate}
  146. onChange={datePickerChangedHandler}
  147. />
  148. <SelectActionDropdown
  149. actionMap={actionMap}
  150. availableActions={auditLogAvailableActionsData || []}
  151. onChangeAction={actionCheckboxChangedHandler}
  152. onChangeMultipleAction={multipleActionCheckboxChangedHandler}
  153. />
  154. <button type="button" className="btn btn-link" onClick={clearButtonPushedHandler}>
  155. {t('admin:audit_log_management.clear')}
  156. </button>
  157. </div>
  158. <p
  159. className="ml-2"
  160. // eslint-disable-next-line react/no-danger
  161. dangerouslySetInnerHTML={{ __html: activityCounter }}
  162. />
  163. { isLoading
  164. ? (
  165. <div className="text-muted text-center mb-5">
  166. <i className="fa fa-2x fa-spinner fa-pulse mr-1" />
  167. </div>
  168. )
  169. : (
  170. <ActivityTable activityList={activityList} />
  171. )
  172. }
  173. <PaginationWrapper
  174. activePage={activePage}
  175. changePage={setActivePageHandler}
  176. totalItemsCount={totalActivityNum}
  177. pagingLimit={PAGING_LIMIT}
  178. align="center"
  179. size="sm"
  180. />
  181. <div className="text-center admin-audit-log">
  182. <span>Go to: </span>
  183. <input
  184. type="text"
  185. className="jump-page-input"
  186. onChange={jumpPageInputChangeHandler}
  187. />
  188. </div>
  189. </>
  190. )}
  191. </div>
  192. );
  193. };