Просмотр исходного кода

Merge pull request #6318 from weseek/imprv/100055-show-full-text-search-management-and-audit-log

Imprv/100055 show full text search management and audit log pages
cao 3 лет назад
Родитель
Сommit
e074348187

+ 0 - 245
packages/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.jsx

@@ -1,245 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-import { useTranslation } from 'next-i18next';
-
-import AdminSocketIoContainer from '~/client/services/AdminSocketIoContainer';
-import AppContainer from '~/client/services/AppContainer';
-import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { apiv3Get, apiv3Post, apiv3Put } from '~/client/util/apiv3-client';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
-import NormalizeIndicesControls from './NormalizeIndicesControls';
-import RebuildIndexControls from './RebuildIndexControls';
-import ReconnectControls from './ReconnectControls';
-import StatusTable from './StatusTable';
-
-class ElasticsearchManagement extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      isInitialized: false,
-
-      isConnected: false,
-      isConfigured: false,
-      isReconnectingProcessing: false,
-      isRebuildingProcessing: false,
-      isRebuildingCompleted: false,
-
-      isNormalized: null,
-      indicesData: null,
-      aliasesData: null,
-    };
-
-    this.reconnect = this.reconnect.bind(this);
-    this.normalizeIndices = this.normalizeIndices.bind(this);
-    this.rebuildIndices = this.rebuildIndices.bind(this);
-  }
-
-  async UNSAFE_UNSAFE_componentWillMount() {
-    this.retrieveIndicesStatus();
-  }
-
-  componentDidMount() {
-    this.initWebSockets();
-  }
-
-  initWebSockets() {
-    const socket = this.props.adminSocketIoContainer.getSocket();
-
-    socket.on('addPageProgress', (data) => {
-      this.setState({
-        isRebuildingProcessing: true,
-      });
-    });
-
-    socket.on('finishAddPage', async(data) => {
-      await this.retrieveIndicesStatus();
-      this.setState({
-        isRebuildingProcessing: false,
-        isRebuildingCompleted: true,
-      });
-    });
-
-    socket.on('rebuildingFailed', (data) => {
-      toastError(new Error(data.error), 'Rebuilding Index has failed.');
-    });
-  }
-
-  async retrieveIndicesStatus() {
-    const { appContainer } = this.props;
-
-    try {
-      const { data } = await apiv3Get('/search/indices');
-      const { info } = data;
-
-      this.setState({
-        isConnected: true,
-        isConfigured: true,
-
-        indicesData: info.indices,
-        aliasesData: info.aliases,
-        isNormalized: info.isNormalized,
-      });
-    }
-    catch (errors) {
-      this.setState({ isConnected: false });
-
-      // evaluate whether configured or not
-      for (const error of errors) {
-        if (error.code === 'search-service-unconfigured') {
-          this.setState({ isConfigured: false });
-        }
-      }
-
-      toastError(errors);
-    }
-    finally {
-      this.setState({ isInitialized: true });
-    }
-  }
-
-  async reconnect() {
-    const { appContainer } = this.props;
-
-    this.setState({ isReconnectingProcessing: true });
-
-    try {
-      await apiv3Post('/search/connection');
-    }
-    catch (e) {
-      toastError(e);
-      return;
-    }
-
-    // reload
-    window.location.reload();
-  }
-
-  async normalizeIndices() {
-    const { appContainer } = this.props;
-
-    try {
-      await apiv3Put('/search/indices', { operation: 'normalize' });
-    }
-    catch (e) {
-      toastError(e);
-    }
-
-    await this.retrieveIndicesStatus();
-
-    toastSuccess('Normalizing has succeeded');
-  }
-
-  async rebuildIndices() {
-    const { appContainer } = this.props;
-
-    this.setState({ isRebuildingProcessing: true });
-
-    try {
-      await apiv3Put('/search/indices', { operation: 'rebuild' });
-      toastSuccess('Rebuilding is requested');
-    }
-    catch (e) {
-      toastError(e);
-    }
-
-    await this.retrieveIndicesStatus();
-  }
-
-  render() {
-    const { t, appContainer } = this.props;
-    const {
-      isInitialized,
-      isConnected, isConfigured, isReconnectingProcessing, isRebuildingProcessing, isRebuildingCompleted,
-      isNormalized, indicesData, aliasesData,
-    } = this.state;
-
-    const isErrorOccuredOnSearchService = !appContainer.config.isSearchServiceReachable;
-
-    const isReconnectBtnEnabled = !isReconnectingProcessing && (!isInitialized || !isConnected || isErrorOccuredOnSearchService);
-
-    return (
-      <>
-        <div className="row">
-          <div className="col-md-12">
-            <StatusTable
-              isInitialized={isInitialized}
-              isErrorOccuredOnSearchService={isErrorOccuredOnSearchService}
-              isConnected={isConnected}
-              isConfigured={isConfigured}
-              isNormalized={isNormalized}
-              indicesData={indicesData}
-              aliasesData={aliasesData}
-            />
-          </div>
-        </div>
-
-        <hr />
-
-        {/* Controls */}
-        <div className="row">
-          <label className="col-md-3 col-form-label text-left text-md-right">{ t('full_text_search_management.reconnect') }</label>
-          <div className="col-md-6">
-            <ReconnectControls
-              isEnabled={isReconnectBtnEnabled}
-              isProcessing={isReconnectingProcessing}
-              onReconnectingRequested={this.reconnect}
-            />
-          </div>
-        </div>
-
-        <hr />
-
-        <div className="row">
-          <label className="col-md-3 col-form-label text-left text-md-right">{ t('full_text_search_management.normalize') }</label>
-          <div className="col-md-6">
-            <NormalizeIndicesControls
-              isRebuildingProcessing={isRebuildingProcessing}
-              isRebuildingCompleted={isRebuildingCompleted}
-              isNormalized={isNormalized}
-              onNormalizingRequested={this.normalizeIndices}
-            />
-          </div>
-        </div>
-
-        <hr />
-
-        <div className="row">
-          <label className="col-md-3 col-form-label text-left text-md-right">{ t('full_text_search_management.rebuild') }</label>
-          <div className="col-md-6">
-            <RebuildIndexControls
-              isRebuildingProcessing={isRebuildingProcessing}
-              isRebuildingCompleted={isRebuildingCompleted}
-              isNormalized={isNormalized}
-              onRebuildingRequested={this.rebuildIndices}
-            />
-          </div>
-        </div>
-
-      </>
-    );
-  }
-
-}
-
-const ElasticsearchManagementWrapperFC = (props) => {
-  const { t } = useTranslation();
-  return <ElasticsearchManagement t={t} {...props} />;
-};
-
-/**
- * Wrapper component for using unstated
- */
-const ElasticsearchManagementWrapper = withUnstatedContainers(ElasticsearchManagementWrapperFC, [AppContainer, AdminSocketIoContainer]);
-
-ElasticsearchManagement.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  adminSocketIoContainer: PropTypes.instanceOf(AdminSocketIoContainer).isRequired,
-};
-
-export default ElasticsearchManagementWrapper;

+ 212 - 0
packages/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx

@@ -0,0 +1,212 @@
+import React, { useEffect, useState, useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+
+import { toastSuccess, toastError } from '~/client/util/apiNotification';
+import { apiv3Get, apiv3Post, apiv3Put } from '~/client/util/apiv3-client';
+import { SocketEventName } from '~/interfaces/websocket';
+import { useIsSearchServiceReachable } from '~/stores/context';
+import { useAdminSocket } from '~/stores/socket-io';
+
+import NormalizeIndicesControls from './NormalizeIndicesControls';
+import RebuildIndexControls from './RebuildIndexControls';
+import ReconnectControls from './ReconnectControls';
+import StatusTable from './StatusTable';
+
+const ElasticsearchManagement = () => {
+  const { t } = useTranslation();
+  const { data: isSearchServiceReachable } = useIsSearchServiceReachable();
+  const { data: socket } = useAdminSocket();
+
+  const [isInitialized, setIsInitialized] = useState(false);
+
+  const [isConnected, setIsConnected] = useState(false);
+  const [isConfigured, setIsConfigured] = useState(false);
+  const [isReconnectingProcessing, setIsReconnectingProcessing] = useState(false);
+  const [isRebuildingProcessing, setIsRebuildingProcessing] = useState(false);
+  const [isRebuildingCompleted, setIsRebuildingCompleted] = useState(false);
+
+  const [isNormalized, setIsNormalized] = useState(false);
+  const [indicesData, setIndicesData] = useState(null);
+  const [aliasesData, setAliasesData] = useState(null);
+
+
+  const retrieveIndicesStatus = useCallback(async() => {
+    try {
+      const { data } = await apiv3Get('/search/indices');
+      const { info } = data;
+
+      setIsConnected(true);
+      setIsConfigured(true);
+
+      setIndicesData(info.indices);
+      setAliasesData(info.aliases);
+      setIsNormalized(info.isNormalized);
+    }
+    catch (errors) {
+      setIsConnected(false);
+
+      // evaluate whether configured or not
+      for (const error of errors) {
+        if (error.code === 'search-service-unconfigured') {
+          setIsConfigured(false);
+        }
+      }
+
+      toastError(errors);
+    }
+    finally {
+      setIsInitialized(true);
+    }
+  }, []);
+
+  useEffect(() => {
+    const fetchIndicesStatusData = async() => {
+      await retrieveIndicesStatus();
+    };
+    fetchIndicesStatusData();
+  }, [retrieveIndicesStatus]);
+
+
+  useEffect(() => {
+    if (socket == null) {
+      return;
+    }
+    socket.on(SocketEventName.AddPageProgress, (data) => {
+      setIsRebuildingProcessing(true);
+    });
+
+    socket.on(SocketEventName.FinishAddPage, async(data) => {
+      await retrieveIndicesStatus();
+      setIsRebuildingProcessing(false);
+      setIsRebuildingCompleted(true);
+    });
+
+    socket.on(SocketEventName.RebuildingFailed, (data) => {
+      toastError(new Error(data.error), 'Rebuilding Index has failed.');
+    });
+
+    return () => {
+      socket.off(SocketEventName.AddPageProgress);
+      socket.off(SocketEventName.FinishAddPage);
+      socket.off(SocketEventName.RebuildingFailed);
+    };
+  }, [socket]);
+
+
+  const reconnect = async() => {
+    setIsReconnectingProcessing(true);
+
+    try {
+      await apiv3Post('/search/connection');
+    }
+    catch (e) {
+      toastError(e);
+      return;
+    }
+
+    // reload
+    window.location.reload();
+  };
+
+  const normalizeIndices = async() => {
+
+    try {
+      await apiv3Put('/search/indices', { operation: 'normalize' });
+    }
+    catch (e) {
+      toastError(e);
+    }
+
+    await retrieveIndicesStatus();
+
+    toastSuccess('Normalizing has succeeded');
+  };
+
+  const rebuildIndices = async() => {
+    setIsRebuildingProcessing(true);
+
+    try {
+      await apiv3Put('/search/indices', { operation: 'rebuild' });
+      toastSuccess('Rebuilding is requested');
+    }
+    catch (e) {
+      toastError(e);
+    }
+
+    await retrieveIndicesStatus();
+  };
+
+  const isErrorOccuredOnSearchService = !isSearchServiceReachable;
+
+  const isReconnectBtnEnabled = !isReconnectingProcessing && (!isInitialized || !isConnected || isErrorOccuredOnSearchService);
+
+  return (
+    <>
+      <div className="row">
+        <div className="col-md-12">
+          <StatusTable
+            isInitialized={isInitialized}
+            isErrorOccuredOnSearchService={isErrorOccuredOnSearchService}
+            isConnected={isConnected}
+            isConfigured={isConfigured}
+            isNormalized={isNormalized}
+            indicesData={indicesData}
+            aliasesData={aliasesData}
+          />
+        </div>
+      </div>
+
+      <hr />
+
+      {/* Controls */}
+      <div className="row">
+        <label className="col-md-3 col-form-label text-left text-md-right">{ t('full_text_search_management.reconnect') }</label>
+        <div className="col-md-6">
+          <ReconnectControls
+            isEnabled={isReconnectBtnEnabled}
+            isProcessing={isReconnectingProcessing}
+            onReconnectingRequested={reconnect}
+          />
+        </div>
+      </div>
+
+      <hr />
+
+      <div className="row">
+        <label className="col-md-3 col-form-label text-left text-md-right">{ t('full_text_search_management.normalize') }</label>
+        <div className="col-md-6">
+          <NormalizeIndicesControls
+            isRebuildingProcessing={isRebuildingProcessing}
+            isNormalized={isNormalized}
+            onNormalizingRequested={normalizeIndices}
+          />
+        </div>
+      </div>
+
+      <hr />
+
+      <div className="row">
+        <label className="col-md-3 col-form-label text-left text-md-right">{ t('full_text_search_management.rebuild') }</label>
+        <div className="col-md-6">
+          <RebuildIndexControls
+            isRebuildingProcessing={isRebuildingProcessing}
+            isRebuildingCompleted={isRebuildingCompleted}
+            isNormalized={isNormalized}
+            onRebuildingRequested={rebuildIndices}
+          />
+        </div>
+      </div>
+
+    </>
+  );
+
+};
+
+
+ElasticsearchManagement.propTypes = {
+
+};
+
+export default ElasticsearchManagement;

+ 22 - 26
packages/app/src/components/Admin/ElasticsearchManagement/RebuildIndexControls.jsx

@@ -1,11 +1,10 @@
 import React from 'react';
 
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
-import AdminSocketIoContainer from '~/client/services/AdminSocketIoContainer';
+import { useAdminSocket } from '~/stores/socket-io';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
 import LabeledProgressBar from '../Common/LabeledProgressBar';
 
 class RebuildIndexControls extends React.Component {
@@ -25,24 +24,25 @@ class RebuildIndexControls extends React.Component {
   }
 
   initWebSockets() {
-    const socket = this.props.adminSocketIoContainer.getSocket();
-
-    socket.on('addPageProgress', (data) => {
-      this.setState({
-        total: data.totalCount,
-        current: data.count,
-        skip: data.skipped,
+    const { socket } = this.props;
+
+    if (socket != null) {
+      socket.on('addPageProgress', (data) => {
+        this.setState({
+          total: data.totalCount,
+          current: data.count,
+          skip: data.skipped,
+        });
       });
-    });
 
-    socket.on('finishAddPage', (data) => {
-      this.setState({
-        total: data.totalCount,
-        current: data.count,
-        skip: data.skipped,
+      socket.on('finishAddPage', (data) => {
+        this.setState({
+          total: data.totalCount,
+          current: data.count,
+          skip: data.skipped,
+        });
       });
-    });
-
+    }
   }
 
   renderProgressBar() {
@@ -109,24 +109,20 @@ class RebuildIndexControls extends React.Component {
 
 const RebuildIndexControlsFC = (props) => {
   const { t } = useTranslation();
-  return <RebuildIndexControls t={t} {...props} />;
+  const { data: socket } = useAdminSocket();
+  return <RebuildIndexControls t={t} socket={socket} {...props} />;
 };
 
 
-/**
- * Wrapper component for using unstated
- */
-const RebuildIndexControlsWrapper = withUnstatedContainers(RebuildIndexControlsFC, [AdminSocketIoContainer]);
-
 RebuildIndexControls.propTypes = {
   t: PropTypes.func.isRequired, // i18next
-  adminSocketIoContainer: PropTypes.instanceOf(AdminSocketIoContainer).isRequired,
 
   isRebuildingProcessing: PropTypes.bool.isRequired,
   isRebuildingCompleted: PropTypes.bool.isRequired,
 
   isNormalized: PropTypes.bool,
   onRebuildingRequested: PropTypes.func.isRequired,
+  socket: PropTypes.object,
 };
 
-export default RebuildIndexControlsWrapper;
+export default RebuildIndexControlsFC;

+ 6 - 0
packages/app/src/interfaces/websocket.ts

@@ -11,6 +11,12 @@ export const SocketEventName = {
   // Page migration
   PageMigrationSuccess: 'PageMigrationSuccess',
   PageMigrationError: 'PageMigrationError',
+
+  // Elasticsearch
+  AddPageProgress: 'addPageProgress',
+  FinishAddPage: 'finishAddPage',
+  RebuildingFailed: 'rebuildingFailed',
+
 } as const;
 export type SocketEventName = typeof SocketEventName[keyof typeof SocketEventName];
 

+ 9 - 7
packages/app/src/pages/admin/[[...path]].page.tsx

@@ -9,6 +9,8 @@ import { useRouter } from 'next/router';
 
 import AdminHome from '~/components/Admin/AdminHome/AdminHome';
 import AppSettingsPageContents from '~/components/Admin/App/AppSettingsPageContents';
+import { AuditLogManagement } from '~/components/Admin/AuditLogManagement';
+import ElasticsearchManagement from '~/components/Admin/ElasticsearchManagement/ElasticsearchManagement';
 import ExportArchiveDataPage from '~/components/Admin/ExportArchiveDataPage';
 import DataImportPageContents from '~/components/Admin/ImportData/ImportDataPageContents';
 import LegacySlackIntegration from '~/components/Admin/LegacySlackIntegration/LegacySlackIntegration';
@@ -23,11 +25,8 @@ import { CrowiRequest } from '~/interfaces/crowi-request';
 import { CommonProps, getServerSideCommonProps, useCustomTitle } from '~/pages/commons';
 import PluginUtils from '~/server/plugins/plugin-utils';
 import ConfigLoader from '~/server/service/config-loader';
-
-// import ElasticsearchManagement from '~/components/Admin/ElasticsearchManagement/ElasticsearchManagement';
 import {
-  useCurrentUser,
-  /* useSearchServiceConfigured, useSearchServiceReachable, */ useSiteUrl,
+  useCurrentUser, /* useSearchServiceConfigured, */ useIsSearchServiceReachable, useSiteUrl,
 } from '~/stores/context';
 // import { useEnvVars } from '~/stores/admin-context';
 
@@ -119,8 +118,11 @@ const AdminMarkdownSettingsPage: NextPage<Props> = (props: Props) => {
     },
     search: {
       title: useCustomTitle(props, t('Full Text Search Management')),
-      // component: <ElasticsearchManagement />,
-      component: <>ElasticsearchManagement</>,
+      component: <ElasticsearchManagement />,
+    },
+    'audit-log': {
+      title: useCustomTitle(props, t('AuditLog')),
+      component: <AuditLogManagement />,
     },
   };
 
@@ -130,7 +132,7 @@ const AdminMarkdownSettingsPage: NextPage<Props> = (props: Props) => {
   useCurrentUser(props.currentUser != null ? JSON.parse(props.currentUser) : null);
 
   // useSearchServiceConfigured(props.isSearchServiceConfigured);
-  // useSearchServiceReachable(props.isSearchServiceReachable);
+  useIsSearchServiceReachable(props.isSearchServiceReachable);
 
   useSiteUrl(props.siteUrl);