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

+ 1 - 1
packages/app/src/client/services/AdminHomeContainer.js

@@ -66,7 +66,7 @@ export default class AdminHomeContainer extends Container {
         nodeVersion: adminHomeParams.nodeVersion,
         npmVersion: adminHomeParams.npmVersion,
         yarnVersion: adminHomeParams.yarnVersion,
-        installedPlugins: adminHomeParams.installedPlugins,
+        // installedPlugins: adminHomeParams.installedPlugins,
         envVars: adminHomeParams.envVars,
         isV5Compatible: adminHomeParams.isV5Compatible,
         isMaintenanceMode: adminHomeParams.isMaintenanceMode,

+ 2 - 2
packages/app/src/components/Admin/AdminHome/InstalledPluginTable.jsx

@@ -27,7 +27,7 @@ const InstalledPluginTable = (props) => {
         </tr>
       </thead>
       <tbody>
-        {adminHomeContainer.state.installedPlugins.map((plugin) => {
+        {/* {adminHomeContainer.state.installedPlugins.map((plugin) => {
           return (
             <tr key={plugin.name}>
               <td>{plugin.name}</td>
@@ -35,7 +35,7 @@ const InstalledPluginTable = (props) => {
               <td data-hide-in-vrt className="text-center">{plugin.installedVersion}</td>
             </tr>
           );
-        })}
+        })} */}
       </tbody>
     </table>
   );

+ 1 - 1
packages/app/src/components/Admin/Common/AdminNavigation.jsx

@@ -38,7 +38,7 @@ const AdminNavigation = (props) => {
         { t('full_text_search_management.full_text_search_management') }</>;
       // TODO: Consider where to place the "AuditLog"
       case 'audit-log':                return <><i className="icon-fw icon-feed"></i>            { t('audit_log_management.audit_log')}</>;
-      case 'plugins-extention':        return <><i className="icon-fw icon-plugin"></i>          { t('plugins-extention.title')}</>;
+      case 'plugins-extention':        return <><i className="icon-fw icon-cloud-download"></i>  { t('plugins-extention.title')}</>;
       case 'cloud':                    return <><i className="icon-fw icon-share-alt"></i>       { t('to_cloud_settings')} </>;
       default:                         return <><i className="icon-fw icon-home"></i>            { t('wiki_management_home_page') }</>;
       /* eslint-enable no-multi-spaces */

+ 13 - 0
packages/app/src/components/Admin/PluginsExtension/Loading.js

@@ -0,0 +1,13 @@
+import {
+  Spinner,
+} from 'reactstrap';
+
+const Loading = () => {
+  return (
+    <Spinner className='d-flex justify-content-center aligh-items-center'>
+      Loading...
+    </Spinner>
+  );
+};
+
+export default Loading;

+ 48 - 11
packages/app/src/components/Admin/PluginsExtension/PluginCard.tsx

@@ -1,17 +1,54 @@
-import React from 'react';
+import { faCircleArrowDown, faCircleCheck } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Image from 'next/image';
+import Link from 'next/link';
+
+import { SearchResultItem } from '~/models/SearchResultItem';
+
+export const PluginCard = (props: SearchResultItem) => {
+  const {
+    name, full_name, description, html_url, topics, stargazers_count, owner,
+  } = props;
 
-export const PluginCard = (): JSX.Element => {
   return (
-    <div>
-      <div>
-        <strong>Plugin Name</strong>
-        <small>Discription</small>
-        <span>version</span>
-        <span>tags</span>
+    <div className="card shadow border-0" key={name}>
+      <div className="card-body px-5 py-4">
+        <div className="row mb-3">
+          <div className="col-9">
+            <h2 className="card-title h3 border-bottom pb-2 mb-3">
+              <Link href={`/${full_name}`}>{name}</Link>
+            </h2>
+            <p className="card-text text-muted">{description}</p>
+          </div>
+          <div className="col-3">
+            <Image className="mx-auto" alt="GitHub avator image" src={owner.avatar_url} width={250} height={250} />
+          </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>
-        <span>URL</span>
-        <span>DeleteButton</span>
+      <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>
   );

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

@@ -42,21 +42,21 @@ export const PluginInstallerForm = (): JSX.Element => {
   return (
     <form role="form" onSubmit={submitHandler}>
       <div className='form-group row'>
-        <label className="text-left text-md-right col-md-3 col-form-label">GitHub repository URL</label>
+        <label className="text-left text-md-right col-md-3 col-form-label">GitHub Repository URL</label>
         <div className="col-md-6">
           <input
             className="form-control"
             type="text"
             // defaultValue={adminAppContainer.state.title || ''}
             name="pluginInstallerForm[url]"
-            placeholder="https://github.com/weseek/growi-plugins/vibrant-dark-ui"
+            placeholder="https://github.com/weseek/growi-plugin-lsx"
             required
           />
-          <p className="form-text text-muted">Install the plugin in GROWI: Enter the URL of the plugin repository and press the Update.</p>
+          <p className="form-text text-muted">You can install plugins by inputting the GitHub URL.</p>
           {/* <p className="form-text text-muted">{t('admin:app_setting.sitename_change')}</p> */}
         </div>
       </div>
-      <div className='form-group row'>
+      {/* <div className='form-group row'>
         <label className="text-left text-md-right col-md-3 col-form-label">branch</label>
         <div className="col-md-6">
           <input
@@ -79,11 +79,11 @@ export const PluginInstallerForm = (): JSX.Element => {
           />
           <p className="form-text text-muted">tag name</p>
         </div>
-      </div>
+      </div> */}
 
       <div className="row my-3">
         <div className="mx-auto">
-          <button type="submit" className="btn btn-primary" >Install</button>
+          <button type="submit" className="btn btn-primary">Install</button>
         </div>
       </div>
     </form>

+ 24 - 3
packages/app/src/components/Admin/PluginsExtension/PluginsExtensionPageContents.tsx

@@ -1,22 +1,43 @@
 import React from 'react';
 
+import { SearchResultItem } from '~/models/SearchResultItem';
+import { useInstalledPlugins } from '~/stores/useInstalledPlugins';
+
+import Loading from './Loading';
 import { PluginCard } from './PluginCard';
 import { PluginInstallerForm } from './PluginInstallerForm';
 
+
 // TODO: i18n
 
 export const PluginsExtensionPageContents = (): JSX.Element => {
+  const { data, error } = useInstalledPlugins();
+
+  if (data == null) {
+    return <Loading />;
+  }
+
   return (
     <div>
-      <div className="row">
+
+      <div className="row mb-5">
         <div className="col-lg-12">
           <h2 className="admin-setting-header">Plugins Installer</h2>
           <PluginInstallerForm />
         </div>
       </div>
-      <div>
-        <PluginCard />
+
+      <div className="row mb-5">
+        <div className="col-lg-12">
+          <h2 className="admin-setting-header">Plugins</h2>
+          <div className="d-grid gap-5">
+            {/* {data?.items.map((item: SearchResultItem) => {
+              return <PluginCard key={item.name} {...item} />;
+            })} */}
+          </div>
+        </div>
       </div>
+
     </div>
   );
 };

+ 7 - 0
packages/app/src/models/SearchResult.ts

@@ -0,0 +1,7 @@
+import { SearchResultItem } from './SearchResultItem';
+
+export type SearchResult = {
+  total_count: number,
+  imcomplete_results: boolean,
+  items: SearchResultItem[];
+}

+ 10 - 0
packages/app/src/models/SearchResultItem.ts

@@ -0,0 +1,10 @@
+export type SearchResultItem = {
+  id: number,
+  name: string,
+  full_name: string,
+  html_url: string,
+  description: string,
+  topics: string[],
+  homepage: string,
+  stargazers_count: number,
+}

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

@@ -109,7 +109,7 @@ const AdminMarkdownSettingsPage: NextPage<Props> = (props: Props) => {
         nodeVersion={props.nodeVersion}
         npmVersion={props.npmVersion}
         yarnVersion={props.yarnVersion}
-        installedPlugins={props.installedPlugins}
+        // installedPlugins={props.installedPlugins}
       />,
     },
     app: {
@@ -290,7 +290,7 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
   props.nodeVersion = crowi.runtimeVersions.versions.node ? crowi.runtimeVersions.versions.node.version.version : null;
   props.npmVersion = crowi.runtimeVersions.versions.npm ? crowi.runtimeVersions.versions.npm.version.version : null;
   props.yarnVersion = crowi.runtimeVersions.versions.yarn ? crowi.runtimeVersions.versions.yarn.version.version : null;
-  props.installedPlugins = crowi.pluginService.listPlugins(crowi.rootDir);
+  // props.installedPlugins = crowi.pluginService.listPlugins(crowi.rootDir);
   props.envVars = await ConfigLoader.getEnvVarsForDisplay(true);
   props.isAclEnabled = aclService.isAclEnabled();
 

+ 0 - 1
packages/app/src/server/routes/apiv3/plugins-extention.ts

@@ -4,7 +4,6 @@ import Crowi from '../../crowi';
 
 import { ApiV3Response } from './interfaces/apiv3-response';
 
-
 type PluginInstallerFormRequest = Request & { form: any };
 
 module.exports = (crowi: Crowi) => {

+ 5 - 6
packages/app/src/server/service/plugin.ts

@@ -33,10 +33,9 @@ export class PluginService {
   async install(crowi: Crowi, origin: GrowiPluginOrigin): Promise<void> {
     // download
     const ghUrl = origin.url;
-    const ghBranch = origin.url;
     const downloadDir = path.join(process.cwd(), 'tmp/plugins/');
     try {
-      await this.downloadZipFile(`${ghUrl}/archive/refs/heads/${ghBranch}.zip`, downloadDir, ghBranch);
+      await this.downloadZipFile(`${ghUrl}/archive/refs/heads/master.zip`, downloadDir);
     }
     catch (err) {
       console.log('downloadZipFile error', err);
@@ -98,11 +97,11 @@ export class PluginService {
     return [];
   }
 
-  async downloadZipFile(ghUrl: string, filePath:string, ghBranch: string): Promise<void> {
+  async downloadZipFile(ghUrl: string, filePath:string): Promise<void> {
 
-    const stdout1 = execSync(`wget ${ghUrl} -O ${filePath}${ghBranch}.zip`);
-    const stdout2 = execSync(`unzip ${filePath}${ghBranch}.zip -d ${filePath}`);
-    const stdout3 = execSync(`rm ${filePath}${ghBranch}.zip`);
+    const stdout1 = execSync(`wget ${ghUrl} -O ${filePath}master.zip`);
+    const stdout2 = execSync(`unzip ${filePath}master.zip -d ${filePath}`);
+    const stdout3 = execSync(`rm ${filePath}master.zip`);
 
     return;
   }

+ 28 - 0
packages/app/src/stores/useInstalledPlugins.ts

@@ -0,0 +1,28 @@
+import useSWR, { SWRResponse } from 'swr';
+
+import { SearchResult } from '../models/SearchResult';
+import { SearchResultItem } from '../models/SearchResultItem';
+
+const pluginFetcher = (owner: string, repo: string) => {
+  return async() => {
+    const reqUrl = `/api/fetch_repository?owner=${owner}&repo=${repo}`;
+    const data = await fetch(reqUrl).then(res => res.json());
+    return data.searchResultItem;
+  };
+};
+
+export const useInstalledPlugin = (owner: string, repo: string): SWRResponse<SearchResultItem | null, Error> => {
+  return useSWR(`${owner}/{repo}`, pluginFetcher(owner, repo));
+};
+
+const pluginsFetcher = () => {
+  return async() => {
+    const reqUrl = '/api/fetch_repositories';
+    const data = await fetch(reqUrl).then(res => res.json());
+    return data.searchResult;
+  };
+};
+
+export const useInstalledPlugins = (): SWRResponse<SearchResult | null, Error> => {
+  return useSWR('/api/fetch_repositories', pluginsFetcher());
+};