AuditLogManagement.tsx 6.1 KB

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