AuditLogManagement.tsx 8.2 KB

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