AuditLogManagement.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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 isLoading = activityData === undefined && error == null;
  51. if (error != null) {
  52. toastError('Failed to get Audit Log');
  53. }
  54. const { data: auditLogEnabled } = useAuditLogEnabled();
  55. /*
  56. * Functions
  57. */
  58. const setActivePageHandler = useCallback((selectedPageNum: number) => {
  59. setActivePage(selectedPageNum);
  60. }, []);
  61. const datePickerChangedHandler = useCallback((dateList: Date[] | null[]) => {
  62. setActivePage(1);
  63. setStartDate(dateList[0]);
  64. setEndDate(dateList[1]);
  65. }, []);
  66. const actionCheckboxChangedHandler = useCallback((action: SupportedActionType) => {
  67. setActivePage(1);
  68. actionMap.set(action, !actionMap.get(action));
  69. setActionMap(new Map(actionMap.entries()));
  70. }, [actionMap, setActionMap]);
  71. const multipleActionCheckboxChangedHandler = useCallback((actions: SupportedActionType[], isChecked) => {
  72. setActivePage(1);
  73. actions.forEach(action => actionMap.set(action, isChecked));
  74. setActionMap(new Map(actionMap.entries()));
  75. }, [actionMap, setActionMap]);
  76. const setUsernamesHandler = useCallback((usernames: string[]) => {
  77. setActivePage(1);
  78. setSelectedUsernames(usernames);
  79. }, []);
  80. const clearButtonPushedHandler = useCallback(() => {
  81. setActivePage(1);
  82. setStartDate(null);
  83. setEndDate(null);
  84. setSelectedUsernames([]);
  85. typeaheadRef.current?.clear();
  86. if (auditLogAvailableActionsData != null) {
  87. setActionMap(new Map<SupportedActionType, boolean>(auditLogAvailableActionsData.map(action => [action, true])));
  88. }
  89. }, [setActivePage, setStartDate, setEndDate, setSelectedUsernames, setActionMap, auditLogAvailableActionsData]);
  90. const reloadButtonPushedHandler = useCallback(() => {
  91. setActivePage(1);
  92. mutateActivity();
  93. }, [mutateActivity]);
  94. // eslint-disable-next-line max-len
  95. const activityCounter = `<b>${activityList.length === 0 ? 0 : offset + 1}</b> - <b>${(PAGING_LIMIT * activePage) - (PAGING_LIMIT - activityList.length)}</b> of <b>${totalActivityNum}<b/>`;
  96. if (!auditLogEnabled) {
  97. return <AuditLogDisableMode />;
  98. }
  99. return (
  100. <div data-testid="admin-auditlog">
  101. <button type="button" className="btn btn-outline-secondary mb-4" onClick={() => setIsSettingPage(!isSettingPage)}>
  102. {
  103. isSettingPage
  104. ? <><i className="fa fa-hand-o-left mr-1" />{t('admin:audit_log_management.return')}</>
  105. : <><i className="fa icon-settings mr-1" />{t('admin:audit_log_management.settings')}</>
  106. }
  107. </button>
  108. <h2 className="admin-setting-header mb-3">
  109. <span>
  110. {isSettingPage ? t('AuditLog Settings') : t('AuditLog')}
  111. </span>
  112. { !isSettingPage && (
  113. <button type="button" className="btn btn-sm ml-auto grw-btn-reload" onClick={reloadButtonPushedHandler}>
  114. <i className="icon icon-reload"></i>
  115. </button>
  116. )}
  117. </h2>
  118. {isSettingPage ? (
  119. <AuditLogSettings />
  120. ) : (
  121. <>
  122. <div className="form-inline mb-3">
  123. <SearchUsernameTypeahead
  124. ref={typeaheadRef}
  125. onChange={setUsernamesHandler}
  126. />
  127. <DateRangePicker
  128. startDate={startDate}
  129. endDate={endDate}
  130. onChange={datePickerChangedHandler}
  131. />
  132. <SelectActionDropdown
  133. actionMap={actionMap}
  134. availableActions={auditLogAvailableActionsData || []}
  135. onChangeAction={actionCheckboxChangedHandler}
  136. onChangeMultipleAction={multipleActionCheckboxChangedHandler}
  137. />
  138. <button type="button" className="btn btn-link" onClick={clearButtonPushedHandler}>
  139. {t('admin:audit_log_management.clear')}
  140. </button>
  141. </div>
  142. <p
  143. className="ml-2"
  144. // eslint-disable-next-line react/no-danger
  145. dangerouslySetInnerHTML={{ __html: activityCounter }}
  146. />
  147. { isLoading
  148. ? (
  149. <div className="text-muted text-center mb-5">
  150. <i className="fa fa-2x fa-spinner fa-pulse mr-1" />
  151. </div>
  152. )
  153. : (
  154. <ActivityTable activityList={activityList} />
  155. )
  156. }
  157. <PaginationWrapper
  158. activePage={activePage}
  159. changePage={setActivePageHandler}
  160. totalItemsCount={totalActivityNum}
  161. pagingLimit={PAGING_LIMIT}
  162. align="center"
  163. size="sm"
  164. />
  165. </>
  166. )}
  167. </div>
  168. );
  169. };