Przeglądaj źródła

Merge pull request #10692 from growilabs/imprv/admin-home

imprv: Admin Home
mergify[bot] 2 miesięcy temu
rodzic
commit
b7327ab61c

+ 1 - 1
apps/app/public/static/locales/en_US/admin.json

@@ -313,7 +313,7 @@
       "done": "Copied to clipboard!"
     },
     "bug_report": "Submitting a bug report",
-    "submit_bug_report": "<a href='https://github.com/growilabs/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>then submit your issue to GitHub.</a>"
+    "submit_bug_report": "Submit your issue to GitHub."
   },
   "v5_page_migration": {
     "migration_desc": "There are some pages with old v4 compatibility. To take advantage of new features such as page trees and easy renaming, please convert all your pages to v5 compatibility.",

+ 1 - 1
apps/app/public/static/locales/fr_FR/admin.json

@@ -313,7 +313,7 @@
       "done": "Copié dans le presse-papier!"
     },
     "bug_report": "Informations de diagnostic",
-    "submit_bug_report": "<a href='https://github.com/growilabs/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>soummettre ensuite sur GitHub.</a>"
+    "submit_bug_report": "Soummettre ensuite sur GitHub."
   },
   "v5_page_migration": {
     "migration_desc": "Des pages sont encore en V4. Pour profiter des nouvelles fonctionnalitées, convertir toutes les pages vers la V5.",

+ 1 - 1
apps/app/public/static/locales/ja_JP/admin.json

@@ -322,7 +322,7 @@
       "done": "クリップボードにコピーしました!"
     },
     "bug_report": "バグを報告する",
-    "submit_bug_report": "<a href='https://github.com/growilabs/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>次に GitHub で Issue を投稿してください。</a>"
+    "submit_bug_report": "GitHub で Issue を投稿"
   },
   "v5_page_migration": {
     "migration_desc": "公開されているページに 古い v4 互換形式のものが存在します。ページツリーや簡単なリネームなどの新機能を利用するには、全てのページを v5 互換形式に変換してください。",

+ 2 - 2
apps/app/public/static/locales/ko_KR/admin.json

@@ -298,7 +298,7 @@
   },
   "mailer_setup_required": "<a href='/admin/app'>이메일 설정</a>이 전송에 필요합니다.",
   "admin_top": {
-    "management_wiki": "관리 위키",
+    "management_wiki": "위키 관리",
     "system_information": "시스템 정보",
     "wiki_administrator": "위키 관리자만 이 페이지에 접근할 수 있습니다",
     "assign_administrator": "사용자 관리 페이지에서 '관리자 권한 부여' 버튼을 사용하여 선택한 사용자에게 위키 관리자 권한을 부여할 수 있습니다.",
@@ -313,7 +313,7 @@
       "done": "클립보드에 복사되었습니다!"
     },
     "bug_report": "버그 보고서 제출",
-    "submit_bug_report": "<a href='https://github.com/growilabs/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>그런 다음 GitHub에 문제를 제출하십시오.</a>"
+    "submit_bug_report": "GitHub 에 이슈를 제출하세요."
   },
   "v5_page_migration": {
     "migration_desc": "일부 페이지는 이전 v4 호환성을 가지고 있습니다. 페이지 트리 및 쉬운 이름 변경과 같은 새로운 기능을 활용하려면 모든 페이지를 v5 호환성으로 변환하십시오.",

+ 1 - 1
apps/app/public/static/locales/zh_CN/admin.json

@@ -322,7 +322,7 @@
       "done": "复制到剪贴板!"
     },
     "bug_report": "提交一个错误报告",
-    "submit_bug_report": "<a href='https://github.com/growilabs/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>然后提交你的问题到GitHub。</a>"
+    "submit_bug_report": "将您的问题提交到 GitHub。"
   },
   "v5_page_migration": {
     "migration_desc": "有一些页面具有旧的v4兼容性。为了利用新的功能,如页面树和容易重命名,请将您的所有页面转换为v5兼容性。",

+ 69 - 63
apps/app/src/client/components/Admin/AdminHome/AdminHome.jsx → apps/app/src/client/components/Admin/AdminHome/AdminHome.tsx

@@ -1,43 +1,43 @@
-import React, { useCallback, useEffect } from 'react';
+import type { FC } from 'react';
+import { useId, useState } from 'react';
 import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { Tooltip } from 'reactstrap';
 
-import AdminHomeContainer from '~/client/services/AdminHomeContainer';
-import { toastError } from '~/client/util/toastr';
+import { useSWRxAdminHome } from '~/stores/admin/admin-home';
 import { useSWRxV5MigrationStatus } from '~/stores/page-listing';
-import loggerFactory from '~/utils/logger';
+import { generatePrefilledHostInformationMarkdown } from '~/utils/admin-home';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
 import { EnvVarsTable } from './EnvVarsTable';
 import SystemInfomationTable from './SystemInfomationTable';
 
-const logger = loggerFactory('growi:admin');
+const COPY_STATE = {
+  DEFAULT: 'default',
+  DONE: 'done',
+} as const;
 
-const AdminHome = (props) => {
-  const { adminHomeContainer } = props;
+export const AdminHome: FC = () => {
   const { t } = useTranslation();
+  const { data: adminHomeData } = useSWRxAdminHome();
   const { data: migrationStatus } = useSWRxV5MigrationStatus();
+  const [copyState, setCopyState] = useState<string>(COPY_STATE.DEFAULT);
 
-  const fetchAdminHomeData = useCallback(async () => {
-    try {
-      await adminHomeContainer.retrieveAdminHomeData();
-    } catch (err) {
-      toastError(err);
-      logger.error(err);
-    }
-  }, [adminHomeContainer]);
+  const handleCopyPrefilledHostInformation = () => {
+    setCopyState(COPY_STATE.DONE);
+    setTimeout(() => {
+      setCopyState(COPY_STATE.DEFAULT);
+    }, 500);
+  };
 
-  useEffect(() => {
-    fetchAdminHomeData();
-  }, [fetchAdminHomeData]);
+  // Generate CSS-safe ID by removing colons from useId() result
+  const copyButtonIdRaw = useId();
+  const copyButtonId = `copy-button-${copyButtonIdRaw.replace(/:/g, '')}`;
 
   return (
     <div data-testid="admin-home">
       {
         // Alert message will be displayed in case that the GROWI is under maintenance
-        adminHomeContainer.state.isMaintenanceMode && (
+        adminHomeData?.isMaintenanceMode && (
           <div className="alert alert-danger alert-link" role="alert">
             <h3 className="alert-heading">
               {t('admin:maintenance_mode.maintenance_mode')}
@@ -104,7 +104,7 @@ const AdminHome = (props) => {
               __html: t('admin:admin_top.about_security'),
             }}
           />
-          <EnvVarsTable envVars={adminHomeContainer.state.envVars} />
+          <EnvVarsTable envVars={adminHomeData?.envVars} />
         </div>
       </div>
 
@@ -113,50 +113,56 @@ const AdminHome = (props) => {
           <h2 className="admin-setting-header">
             {t('admin:admin_top.bug_report')}
           </h2>
-          <div className="d-flex align-items-center">
-            <CopyToClipboard
-              text={adminHomeContainer.generatePrefilledHostInformationMarkdown()}
-              onCopy={() => adminHomeContainer.onCopyPrefilledHostInformation()}
-            >
-              <button
-                id="prefilledHostInformationButton"
-                type="button"
-                className="btn btn-primary"
+          <ol className="mb-0">
+            <li className="mb-3">
+              <CopyToClipboard
+                text={generatePrefilledHostInformationMarkdown({
+                  growiVersion: adminHomeData?.growiVersion,
+                  nodeVersion: adminHomeData?.nodeVersion,
+                  npmVersion: adminHomeData?.npmVersion,
+                  pnpmVersion: adminHomeData?.pnpmVersion,
+                })}
+                onCopy={handleCopyPrefilledHostInformation}
               >
-                {t('admin:admin_top:copy_prefilled_host_information:default')}
-              </button>
-            </CopyToClipboard>
-            <Tooltip
-              placement="bottom"
-              isOpen={
-                adminHomeContainer.state.copyState ===
-                adminHomeContainer.copyStateValues.DONE
-              }
-              target="prefilledHostInformationButton"
-              fade={false}
-            >
-              {t('admin:admin_top:copy_prefilled_host_information:done')}
-            </Tooltip>
-            <span
-              className="ms-2"
-              // biome-ignore lint/security/noDangerouslySetInnerHtml: ignore
-              dangerouslySetInnerHTML={{
-                __html: t('admin:admin_top:submit_bug_report'),
-              }}
-            />
-          </div>
+                <button
+                  type="button"
+                  className="btn btn-outline-secondary btn-sm"
+                  style={{ verticalAlign: 'baseline' }}
+                  onClick={(e) => e.preventDefault()}
+                >
+                  <span
+                    id={copyButtonId}
+                    className="material-symbols-outlined"
+                    aria-hidden="true"
+                  >
+                    content_copy
+                  </span>
+                  {t('admin:admin_top:copy_prefilled_host_information:default')}
+                </button>
+              </CopyToClipboard>
+              <Tooltip
+                placement="bottom"
+                isOpen={copyState === COPY_STATE.DONE}
+                target={copyButtonId}
+                fade={false}
+              >
+                {t('admin:admin_top:copy_prefilled_host_information:done')}
+              </Tooltip>
+            </li>
+            <li>
+              <a
+                className="link-secondary link-offset-1"
+                style={{ textDecoration: 'underline' }}
+                href="https://github.com/growilabs/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A"
+                target="_blank"
+                rel="noreferrer"
+              >
+                {t('admin:admin_top:submit_bug_report')}
+              </a>
+            </li>
+          </ol>
         </div>
       </div>
     </div>
   );
 };
-
-const AdminHomeWrapper = withUnstatedContainers(AdminHome, [
-  AdminHomeContainer,
-]);
-
-AdminHome.propTypes = {
-  adminHomeContainer: PropTypes.instanceOf(AdminHomeContainer).isRequired,
-};
-
-export default AdminHomeWrapper;

+ 5 - 20
apps/app/src/client/components/Admin/AdminHome/SystemInfomationTable.tsx

@@ -1,19 +1,12 @@
-import React from 'react';
 import { LoadingSpinner } from '@growi/ui/dist/components';
 
-import AdminHomeContainer from '~/client/services/AdminHomeContainer';
+import { useSWRxAdminHome } from '~/stores/admin/admin-home';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
-type Props = {
-  adminHomeContainer: AdminHomeContainer;
-};
-
-const SystemInformationTable = (props: Props) => {
-  const { adminHomeContainer } = props;
+const SystemInformationTable = () => {
+  const { data: adminHomeData } = useSWRxAdminHome();
 
   const { growiVersion, nodeVersion, npmVersion, pnpmVersion } =
-    adminHomeContainer.state;
+    adminHomeData ?? {};
 
   if (
     growiVersion == null ||
@@ -51,12 +44,4 @@ const SystemInformationTable = (props: Props) => {
   );
 };
 
-/**
- * Wrapper component for using unstated
- */
-const SystemInformationTableWrapper = withUnstatedContainers(
-  SystemInformationTable,
-  [AdminHomeContainer],
-);
-
-export default SystemInformationTableWrapper;
+export default SystemInformationTable;

+ 1 - 0
apps/app/src/client/components/Admin/AdminHome/index.ts

@@ -0,0 +1 @@
+export * from './AdminHome';

+ 0 - 110
apps/app/src/client/services/AdminHomeContainer.js

@@ -1,110 +0,0 @@
-import { isServer } from '@growi/core/dist/utils';
-import { Container } from 'unstated';
-
-import loggerFactory from '~/utils/logger';
-
-import { apiv3Get } from '../util/apiv3-client';
-
-const logger = loggerFactory('growi:services:AdminHomeContainer');
-
-/**
- * Service container for admin homepage (AdminHome.jsx)
- * @extends {Container} unstated Container
- */
-export default class AdminHomeContainer extends Container {
-  constructor() {
-    super();
-
-    if (isServer()) {
-      return;
-    }
-
-    this.copyStateValues = {
-      DEFAULT: 'default',
-      DONE: 'done',
-    };
-    this.timer = null;
-
-    this.state = {
-      growiVersion: null,
-      nodeVersion: null,
-      npmVersion: null,
-      pnpmVersion: null,
-      copyState: this.copyStateValues.DEFAULT,
-      installedPlugins: null,
-      isV5Compatible: null,
-      isMaintenanceMode: null,
-    };
-  }
-
-  /**
-   * Workaround for the mangling in production build to break constructor.name
-   */
-  static getClassName() {
-    return 'AdminHomeContainer';
-  }
-
-  componentWillUnmount() {
-    clearTimeout(this.timer);
-  }
-
-  /**
-   * retrieve admin home data
-   */
-  async retrieveAdminHomeData() {
-    try {
-      const response = await apiv3Get('/admin-home/');
-      const { adminHomeParams } = response.data;
-
-      this.setState((prevState) => ({
-        ...prevState,
-        growiVersion: adminHomeParams.growiVersion,
-        nodeVersion: adminHomeParams.nodeVersion,
-        npmVersion: adminHomeParams.npmVersion,
-        pnpmVersion: adminHomeParams.pnpmVersion,
-        envVars: adminHomeParams.envVars,
-        isV5Compatible: adminHomeParams.isV5Compatible,
-        isMaintenanceMode: adminHomeParams.isMaintenanceMode,
-      }));
-    } catch (err) {
-      logger.error(err);
-      throw new Error('Failed to retrive AdminHome data');
-    }
-  }
-
-  /**
-   * sets button text when copying system information
-   */
-  onCopyPrefilledHostInformation() {
-    this.setState((prevState) => ({
-      ...prevState,
-      copyState: this.copyStateValues.DONE,
-    }));
-
-    this.timer = setTimeout(() => {
-      this.setState((prevState) => ({
-        ...prevState,
-        copyState: this.copyStateValues.DEFAULT,
-      }));
-    }, 500);
-  }
-
-  /**
-   * generates prefilled host information as markdown
-   */
-  generatePrefilledHostInformationMarkdown() {
-    return `| item     | version |
-| ---      | --- |
-|OS        ||
-|GROWI     |${this.state.growiVersion}|
-|node.js   |${this.state.nodeVersion}|
-|npm       |${this.state.npmVersion}|
-|pnpm      |${this.state.pnpmVersion}|
-|Using Docker|yes/no|
-|Using [growi-docker-compose][growi-docker-compose]|yes/no|
-
-[growi-docker-compose]: https://github.com/growilabs/growi-docker-compose
-
-*(Accessing https://{GROWI_HOST}/admin helps you to fill in above versions)*`;
-  }
-}

+ 9 - 0
apps/app/src/interfaces/res/admin/admin-home.ts

@@ -0,0 +1,9 @@
+export type IResAdminHome = {
+  growiVersion: string;
+  nodeVersion: string;
+  npmVersion: string;
+  pnpmVersion: string;
+  envVars: Record<string, string>;
+  isV5Compatible: boolean;
+  isMaintenanceMode: boolean;
+};

+ 3 - 10
apps/app/src/pages/admin/index.page.tsx

@@ -14,8 +14,9 @@ import {
 } from './_shared';
 
 const AdminHome = dynamic(
-  // biome-ignore lint/style/noRestrictedImports: no-problem dynamic import
-  () => import('~/client/components/Admin/AdminHome/AdminHome'),
+  () =>
+    // biome-ignore lint/style/noRestrictedImports: no-problem dynamic import
+    import('~/client/components/Admin/AdminHome').then((mod) => mod.AdminHome),
   { ssr: false },
 );
 
@@ -43,14 +44,6 @@ const AdminHomepage: NextPageWithLayout<Props> = ({
 
 AdminHomepage.getLayout = createAdminPageLayout<Props>({
   title: (_p, t) => t('wiki_management_homepage'),
-  containerFactories: [
-    async () => {
-      const AdminHomeContainer =
-        // biome-ignore lint/style/noRestrictedImports: no-problem dynamic import
-        (await import('~/client/services/AdminHomeContainer')).default;
-      return new AdminHomeContainer();
-    },
-  ],
 });
 
 export const getServerSideProps: GetServerSideProps<Props> = async (

+ 3 - 2
apps/app/src/server/routes/apiv3/admin-home.ts

@@ -1,5 +1,6 @@
 import { SCOPE } from '@growi/core/dist/interfaces';
 
+import type { IResAdminHome } from '~/interfaces/res/admin/admin-home';
 import { accessTokenParser } from '~/server/middlewares/access-token-parser';
 import { configManager } from '~/server/service/config-manager';
 import { getGrowiVersion } from '~/utils/growi-version';
@@ -91,13 +92,13 @@ module.exports = (crowi) => {
     accessTokenParser([SCOPE.READ.ADMIN.TOP]),
     loginRequiredStrictly,
     adminRequired,
-    async (req, res) => {
+    async (_req, res) => {
       const { getRuntimeVersions } = await import(
         '~/server/util/runtime-versions'
       );
       const runtimeVersions = await getRuntimeVersions();
 
-      const adminHomeParams = {
+      const adminHomeParams: IResAdminHome = {
         growiVersion: getGrowiVersion(),
         nodeVersion: runtimeVersions.node ?? '-',
         npmVersion: runtimeVersions.npm ?? '-',

+ 13 - 0
apps/app/src/stores/admin/admin-home.tsx

@@ -0,0 +1,13 @@
+import type { SWRResponse } from 'swr';
+import useSWR from 'swr';
+
+import { apiv3Get } from '~/client/util/apiv3-client';
+import type { IResAdminHome } from '~/interfaces/res/admin/admin-home';
+
+export const useSWRxAdminHome = (): SWRResponse<IResAdminHome, Error> => {
+  return useSWR('/admin-home/', (endpoint) =>
+    apiv3Get(endpoint).then((response) => {
+      return response.data.adminHomeParams;
+    }),
+  );
+};

+ 31 - 0
apps/app/src/utils/admin-home.ts

@@ -0,0 +1,31 @@
+type SystemInfo = {
+  growiVersion?: string;
+  nodeVersion?: string;
+  npmVersion?: string;
+  pnpmVersion?: string;
+};
+
+/**
+ * Generates prefilled host information as markdown for bug reports
+ * @param systemInfo System version information
+ * @returns Markdown formatted string with system information
+ */
+export const generatePrefilledHostInformationMarkdown = (
+  systemInfo: SystemInfo,
+): string => {
+  const { growiVersion, nodeVersion, npmVersion, pnpmVersion } = systemInfo;
+
+  return `| item     | version |
+| ---      | --- |
+|OS        ||
+|GROWI     |${growiVersion ?? ''}|
+|node.js   |${nodeVersion ?? ''}|
+|npm       |${npmVersion ?? ''}|
+|pnpm      |${pnpmVersion ?? ''}|
+|Using Docker|yes/no|
+|Using [growi-docker-compose][growi-docker-compose]|yes/no|
+
+[growi-docker-compose]: https://github.com/growilabs/growi-docker-compose
+
+*(Accessing https://{GROWI_HOST}/admin helps you to fill in above versions)*`;
+};