jam411 3 лет назад
Родитель
Сommit
d12adb569a

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

@@ -1025,6 +1025,10 @@
     "remove_user_success": "Succeeded to removing {{username}}",
     "remove_external_user_success": "Succeeded to remove {{accountId}}",
     "switch_disable_link_sharing_success": "Succeeded to update share link setting",
-    "failed_to_reset_password":"Failed to reset password"
+    "failed_to_reset_password":"Failed to reset password",
+    "install_plugin_success": "Succeeded to install {{pluginName}}",
+    "activate_plugin_success": "Succeeded to activating {{pluginName}}",
+    "deactivate_plugin_success": "Succeeded to deactivate {{pluginName}}",
+    "remove_plugin_success": "Succeeded to removing {{pluginName}}"
   }
 }

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

@@ -1033,6 +1033,10 @@
     "remove_user_success": "{{username}}を削除しました",
     "remove_external_user_success": "{{accountId}}を削除しました",
     "switch_disable_link_sharing_success": "共有リンクの設定を変更しました",
-    "failed_to_reset_password":"パスワードのリセットに失敗しました"
+    "failed_to_reset_password":"パスワードのリセットに失敗しました",
+    "install_plugin_success": "{{pluginName}}のインストールに成功しました",
+    "activate_plugin_success": "{{pluginName}}を有効化しました",
+    "deactivate_plugin_success": "{{pluginName}}を無効化しました",
+    "remove_plugin_success": "{{pluginName}}を削除しました"
   }
 }

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

@@ -1033,6 +1033,10 @@
     "remove_user_success": "Succeeded to removing {{username}}",
     "remove_external_user_success": "Succeeded to remove {{accountId}}",
     "switch_disable_link_sharing_success": "成功更新分享链接设置",
-    "failed_to_reset_password":"Failed to reset password"
+    "failed_to_reset_password":"Failed to reset password",
+    "install_plugin_success": "Succeeded to install {{pluginName}}",
+    "activate_plugin_success": "Succeeded to activating {{pluginName}}",
+    "deactivate_plugin_success": "Succeeded to deactivate {{pluginName}}",
+    "remove_plugin_success": "Succeeded to removing {{pluginName}}"
   }
 }

+ 14 - 30
packages/app/src/components/Admin/PluginsExtension/PluginCard.tsx

@@ -1,5 +1,6 @@
 import React, { useState } from 'react';
 
+import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 
 import { apiv3Delete, apiv3Put } from '~/client/util/apiv3-client';
@@ -22,6 +23,8 @@ export const PluginCard = (props: Props): JSX.Element => {
     id, name, url, isEnalbed, desc, mutate,
   } = props;
 
+  const { t } = useTranslation('admin');
+
   const PluginCardButton = (): JSX.Element => {
     const [isEnabled, setState] = useState<boolean>(isEnalbed);
 
@@ -29,19 +32,21 @@ export const PluginCard = (props: Props): JSX.Element => {
       try {
         if (isEnabled) {
           const reqUrl = `/plugins/${id}/deactivate`;
-          await apiv3Put(reqUrl);
+          const res = await apiv3Put(reqUrl);
           setState(!isEnabled);
-          toastSuccess('Plugin Deactivated');
+          const pluginName = res.data.pluginName;
+          toastSuccess(t('toaster.deactivate_plugin_success', { pluginName }));
         }
         else {
           const reqUrl = `/plugins/${id}/activate`;
-          await apiv3Put(reqUrl);
+          const res = await apiv3Put(reqUrl);
           setState(!isEnabled);
-          toastSuccess('Plugin Activated');
+          const pluginName = res.data.pluginName;
+          toastSuccess(t('toaster.activate_plugin_success', { pluginName }));
         }
       }
       catch (err) {
-        toastError('pluginIsEnabled', err);
+        toastError(err);
       }
     };
 
@@ -69,11 +74,12 @@ export const PluginCard = (props: Props): JSX.Element => {
       const reqUrl = `/plugins/${id}/remove`;
 
       try {
-        await apiv3Delete(reqUrl);
-        toastSuccess(`${name} Deleted`);
+        const res = await apiv3Delete(reqUrl);
+        const pluginName = res.data.pluginName;
+        toastSuccess(t('toaster.remove_plugin_success', { pluginName }));
       }
       catch (err) {
-        toastError('pluginDelete', err);
+        toastError(err);
       }
       finally {
         mutate();
@@ -93,7 +99,6 @@ export const PluginCard = (props: Props): JSX.Element => {
     );
   };
 
-  // TODO: Refactor commented out areas.
   return (
     <div className="card shadow border-0" key={name}>
       <div className="card-body px-5 py-4 mt-3">
@@ -113,30 +118,9 @@ export const PluginCard = (props: Props): JSX.Element => {
             </div>
           </div>
         </div>
-        <div className="row">
-          <div className="col-12 d-flex flex-wrap gap-2">
-            {/* {topics?.map((topic: string) => {
-              return (
-                <span key={`${name}-${topic}`} className="badge rounded-1 mp-bg-light-blue text-dark fw-normal">
-                  {topic}
-                </span>
-              );
-            })} */}
-          </div>
-        </div>
       </div>
       <div className="card-footer px-5 border-top-0 mp-bg-light-blue">
         <p className="d-flex justify-content-between align-self-center mb-0">
-          <span>
-            {/* {owner.login === 'weseek' ? <FontAwesomeIcon icon={faCircleCheck} className="me-1 text-primary" /> : <></>}
-
-            <a href={owner.html_url} target="_blank" rel="noreferrer">
-              {owner.login}
-            </a> */}
-          </span>
-          {/* <span>
-            <FontAwesomeIcon icon={faCircleArrowDown} className="me-1" /> {stargazersCount}
-          </span> */}
         </p>
       </div>
     </div>

+ 7 - 4
packages/app/src/components/Admin/PluginsExtension/PluginInstallerForm.tsx

@@ -1,12 +1,14 @@
 import React, { useCallback } from 'react';
 
+import { useTranslation } from 'next-i18next';
+
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
 import { useSWRxPlugins } from '~/stores/plugin';
 
-
 export const PluginInstallerForm = (): JSX.Element => {
   const { mutate } = useSWRxPlugins();
+  const { t } = useTranslation('admin');
 
   const submitHandler = useCallback(async(e) => {
     e.preventDefault();
@@ -26,8 +28,9 @@ export const PluginInstallerForm = (): JSX.Element => {
     };
 
     try {
-      await apiv3Post('/plugins', { pluginInstallerForm });
-      toastSuccess('Plugin Install Successed!');
+      const res = await apiv3Post('/plugins', { pluginInstallerForm });
+      const pluginName = res.data.pluginName;
+      toastSuccess(t('toaster.install_plugin_success', { pluginName }));
     }
     catch (e) {
       toastError(e);
@@ -35,7 +38,7 @@ export const PluginInstallerForm = (): JSX.Element => {
     finally {
       mutate();
     }
-  }, [mutate]);
+  }, [mutate, t]);
 
   return (
     <form role="form" onSubmit={submitHandler}>

+ 18 - 6
packages/app/src/server/models/growi-plugin.ts

@@ -15,8 +15,8 @@ export interface GrowiPluginModel extends Model<GrowiPluginDocument> {
   findEnabledPlugins(): Promise<GrowiPlugin[]>
   findEnabledPluginsIncludingAnyTypes(includingTypes: GrowiPluginResourceType[]): Promise<GrowiPlugin[]>
   findPlugins(): Promise<GrowiPlugin[]>
-  activatePlugin(id: Types.ObjectId): Promise<void>
-  deactivatePlugin(id: Types.ObjectId): Promise<void>
+  activatePlugin(id: Types.ObjectId): Promise<string>
+  deactivatePlugin(id: Types.ObjectId): Promise<string>
 }
 
 const growiThemeMetadataSchema = new Schema<GrowiThemeMetadata>({
@@ -73,12 +73,24 @@ growiPluginSchema.statics.findPlugins = async function(): Promise<GrowiPlugin[]>
   return this.find({});
 };
 
-growiPluginSchema.statics.activatePlugin = async function(id: Types.ObjectId): Promise<void> {
-  await this.findOneAndUpdate({ _id: id }, { isEnabled: true });
+growiPluginSchema.statics.activatePlugin = async function(id: Types.ObjectId): Promise<string> {
+  const growiPlugin = await this.findOneAndUpdate({ _id: id }, { isEnabled: true });
+  if (growiPlugin == null) {
+    const message = 'No plugin found for this ID.';
+    throw new Error(message);
+  }
+  const pluginName = growiPlugin.meta.name;
+  return pluginName;
 };
 
-growiPluginSchema.statics.deactivatePlugin = async function(id: Types.ObjectId): Promise<void> {
-  await this.findOneAndUpdate({ _id: id }, { isEnabled: false });
+growiPluginSchema.statics.deactivatePlugin = async function(id: Types.ObjectId): Promise<string> {
+  const growiPlugin = await this.findOneAndUpdate({ _id: id }, { isEnabled: false });
+  if (growiPlugin == null) {
+    const message = 'No plugin found for this ID.';
+    throw new Error(message);
+  }
+  const pluginName = growiPlugin.meta.name;
+  return pluginName;
 };
 
 export default getOrCreateModel<GrowiPluginDocument, GrowiPluginModel>('GrowiPlugin', growiPluginSchema);

+ 8 - 8
packages/app/src/server/routes/apiv3/plugins.ts

@@ -51,8 +51,8 @@ module.exports = (crowi: Crowi): Router => {
     const { pluginInstallerForm: formValue } = req.body;
 
     try {
-      await pluginService.install(formValue);
-      return res.apiv3();
+      const pluginName = await pluginService.install(formValue);
+      return res.apiv3({ pluginName });
     }
     catch (err) {
       return res.apiv3Err(err);
@@ -68,8 +68,8 @@ module.exports = (crowi: Crowi): Router => {
 
     try {
       const GrowiPluginModel = mongoose.model('GrowiPlugin') as GrowiPluginModel;
-      await GrowiPluginModel.activatePlugin(pluginId);
-      return res.apiv3();
+      const pluginName = await GrowiPluginModel.activatePlugin(pluginId);
+      return res.apiv3({ pluginName });
     }
     catch (err) {
       return res.apiv3Err(err);
@@ -86,8 +86,8 @@ module.exports = (crowi: Crowi): Router => {
 
     try {
       const GrowiPluginModel = mongoose.model('GrowiPlugin') as GrowiPluginModel;
-      await GrowiPluginModel.deactivatePlugin(pluginId);
-      return res.apiv3();
+      const pluginName = await GrowiPluginModel.deactivatePlugin(pluginId);
+      return res.apiv3({ pluginName });
     }
     catch (err) {
       return res.apiv3Err(err);
@@ -103,8 +103,8 @@ module.exports = (crowi: Crowi): Router => {
     const pluginId = new ObjectID(id);
 
     try {
-      await pluginService.deletePlugin(pluginId);
-      return res.apiv3();
+      const pluginName = await pluginService.deletePlugin(pluginId);
+      return res.apiv3({ pluginName });
     }
     catch (err) {
       return res.apiv3Err(err);

+ 20 - 11
packages/app/src/server/service/plugin.ts

@@ -4,6 +4,7 @@ import path from 'path';
 import { GrowiThemeMetadata, ViteManifest } from '@growi/core';
 // eslint-disable-next-line no-restricted-imports
 import axios from 'axios';
+import { plugins } from 'handsontable';
 import mongoose from 'mongoose';
 import streamToPromise from 'stream-to-promise';
 import unzipper from 'unzipper';
@@ -34,7 +35,7 @@ function retrievePluginManifest(growiPlugin: GrowiPlugin): ViteManifest {
 }
 
 export interface IPluginService {
-  install(origin: GrowiPluginOrigin): Promise<void>
+  install(origin: GrowiPluginOrigin): Promise<string>
   retrieveThemeHref(theme: string): Promise<string | undefined>
   retrieveAllPluginResourceEntries(): Promise<GrowiPluginResourceEntries>
   downloadNotExistPluginRepositories(): Promise<void>
@@ -62,7 +63,7 @@ export class PluginService implements IPluginService {
           const ghBranch = 'main';
           const match = ghPathname.match(githubReposIdPattern);
           if (ghUrl.hostname !== 'github.com' || match == null) {
-            throw new Error('The GitHub Repository URL is invalid.');
+            throw new Error('GitHub repository URL is invalid.');
           }
 
           const ghOrganizationName = match[1];
@@ -79,7 +80,7 @@ export class PluginService implements IPluginService {
     }
   }
 
-  async install(origin: GrowiPluginOrigin): Promise<void> {
+  async install(origin: GrowiPluginOrigin): Promise<string> {
     try {
     // download
       const ghUrl = new URL(origin.url);
@@ -89,7 +90,7 @@ export class PluginService implements IPluginService {
 
       const match = ghPathname.match(githubReposIdPattern);
       if (ghUrl.hostname !== 'github.com' || match == null) {
-        throw new Error('The GitHub Repository URL is invalid.');
+        throw new Error('GitHub repository URL is invalid.');
       }
 
       const ghOrganizationName = match[1];
@@ -105,13 +106,13 @@ export class PluginService implements IPluginService {
       // save plugin metadata
       const plugins = await PluginService.detectPlugins(origin, installedPath);
       await this.savePluginMetaData(plugins);
+
+      return plugins[0].meta.name;
     }
     catch (err) {
       logger.error(err);
       throw err;
     }
-
-    return;
   }
 
   private async deleteOldPluginDocument(path: string): Promise<void> {
@@ -144,8 +145,8 @@ export class PluginService implements IPluginService {
             else {
               rejects(res.status);
             }
-          }).catch((e) => {
-            logger.error(e);
+          }).catch((err) => {
+            logger.error(err);
             // eslint-disable-next-line prefer-promise-reject-errors
             rejects('Filed to download file.');
           });
@@ -253,7 +254,7 @@ export class PluginService implements IPluginService {
   /**
    * Delete plugin
    */
-  async deletePlugin(pluginId: mongoose.Types.ObjectId): Promise<void> {
+  async deletePlugin(pluginId: mongoose.Types.ObjectId): Promise<string> {
     const deleteFolder = (path: fs.PathLike): Promise<void> => {
       return fs.promises.rm(path, { recursive: true });
     };
@@ -268,13 +269,21 @@ export class PluginService implements IPluginService {
     try {
       const growiPluginsPath = path.join(pluginStoringPath, growiPlugins.installedPath);
       await deleteFolder(growiPluginsPath);
+    }
+    catch (err) {
+      logger.error(err);
+      throw new Error('Filed to delete plugin repository.');
+    }
+
+    try {
       await GrowiPlugin.deleteOne({ _id: pluginId });
     }
     catch (err) {
-      throw new Error('Plugin local repository deleting failed.');
+      logger.error(err);
+      throw new Error('Filed to delete plugin from GrowiPlugin documents.');
     }
 
-    return;
+    return growiPlugins.meta.name;
   }
 
   async retrieveThemeHref(theme: string): Promise<string | undefined> {