This is an automated email from the ASF dual-hosted git repository.

dockerzhang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/inlong.git


The following commit(s) were added to refs/heads/master by this push:
     new e3543ccea8 [INLONG-9938][Dashboard] Support Agent module and package 
page (#9939)
e3543ccea8 is described below

commit e3543ccea82207e929d888c68f804491cec30371
Author: haifxu <[email protected]>
AuthorDate: Mon Apr 8 17:29:31 2024 +0800

    [INLONG-9938][Dashboard] Support Agent module and package page (#9939)
---
 inlong-dashboard/src/configs/menus/conf.tsx        |   8 +
 inlong-dashboard/src/configs/routes/conf.ts        |   8 +
 inlong-dashboard/src/i18n.ts                       |   4 +
 inlong-dashboard/src/ui/locales/cn.json            |  22 ++-
 inlong-dashboard/src/ui/locales/en.json            |  22 ++-
 .../src/ui/pages/AgentModule/AgentModuleTag.tsx    | 143 ++++++++++++++++
 .../src/ui/pages/AgentModule/CreateModal.tsx       | 189 +++++++++++++++++++++
 .../src/ui/pages/AgentModule/config.tsx            |  76 +++++++++
 .../src/ui/pages/AgentModule/index.tsx             |  82 +++++++++
 .../src/ui/pages/AgentPackage/AgentPackageTag.tsx  | 143 ++++++++++++++++
 .../src/ui/pages/AgentPackage/CreateModal.tsx      | 123 ++++++++++++++
 .../src/ui/pages/AgentPackage/config.tsx           |  80 +++++++++
 .../src/ui/pages/AgentPackage/index.tsx            |  82 +++++++++
 13 files changed, 980 insertions(+), 2 deletions(-)

diff --git a/inlong-dashboard/src/configs/menus/conf.tsx 
b/inlong-dashboard/src/configs/menus/conf.tsx
index 29cde12f70..dc660dc62f 100644
--- a/inlong-dashboard/src/configs/menus/conf.tsx
+++ b/inlong-dashboard/src/configs/menus/conf.tsx
@@ -101,6 +101,14 @@ const conf: MenuItemType[] = [
         path: '/system',
         name: i18n.t('configs.menus.ModuleAudit'),
       },
+      {
+        path: '/agentModule',
+        name: i18n.t('configs.menus.agentModule'),
+      },
+      {
+        path: '/agentPackage',
+        name: i18n.t('configs.menus.agentPackage'),
+      },
     ],
   },
 ];
diff --git a/inlong-dashboard/src/configs/routes/conf.ts 
b/inlong-dashboard/src/configs/routes/conf.ts
index a5329e8319..007538d997 100644
--- a/inlong-dashboard/src/configs/routes/conf.ts
+++ b/inlong-dashboard/src/configs/routes/conf.ts
@@ -124,6 +124,14 @@ const conf: RouteProps[] = [
       },
     ],
   },
+  {
+    path: '/agentModule',
+    component: () => import('@/ui/pages/AgentModule'),
+  },
+  {
+    path: '/agentPackage',
+    component: () => import('@/ui/pages/AgentPackage'),
+  },
   {
     component: () => import('@/ui/pages/Error/404'),
   },
diff --git a/inlong-dashboard/src/i18n.ts b/inlong-dashboard/src/i18n.ts
index 6a0d7f6de4..0146bf9320 100644
--- a/inlong-dashboard/src/i18n.ts
+++ b/inlong-dashboard/src/i18n.ts
@@ -38,6 +38,8 @@ const resources = {
       'configs.menus.TenantManagement': 'Tenant Management',
       'configs.menus.SystemOperation': 'Operation',
       'configs.menus.ModuleAudit': 'Module audit',
+      'configs.menus.agentModule': 'Version Management',
+      'configs.menus.agentPackage': 'Package',
     },
   },
   cn: {
@@ -55,6 +57,8 @@ const resources = {
       'configs.menus.TenantManagement': '租户管理',
       'configs.menus.SystemOperation': '系统运维',
       'configs.menus.ModuleAudit': '模块审计',
+      'configs.menus.agentModule': '版本管理',
+      'configs.menus.agentPackage': '安装包',
     },
   },
 };
diff --git a/inlong-dashboard/src/ui/locales/cn.json 
b/inlong-dashboard/src/ui/locales/cn.json
index f126a8cedf..69eebbcb5a 100644
--- a/inlong-dashboard/src/ui/locales/cn.json
+++ b/inlong-dashboard/src/ui/locales/cn.json
@@ -14,6 +14,7 @@
   "basic.Creator": "创建人",
   "basic.Modifier": "修改人",
   "basic.CreateTime": "创建时间",
+  "basic.ModifyTime": "修改时间",
   "basic.Yes": "是",
   "basic.No": "否",
   "basic.Stop": "停止",
@@ -865,5 +866,24 @@
   "pages.ModuleAudit.config.BenchmarkIndicator": "基准指标",
   "pages.ModuleAudit.config.ComparativeIndicators": "对比指标",
   "pages.ModuleAudit.config.InlongGroupId": "数据流组ID",
-  "pages.ModuleAudit.config.InlongStreamId": "数据流ID"
+  "pages.ModuleAudit.config.InlongStreamId": "数据流ID",
+  "pages.ModuleAgent.Agent": "Agent",
+  "pages.ModuleAgent.Installer": "Installer",
+  "pages.ModuleAgent.Create": "新建 Module",
+  "pages.ModuleAgent.Config.Name": "名称",
+  "pages.ModuleAgent.Config.Version": "版本",
+  "pages.ModuleAgent.Config.Package": "安装包",
+  "pages.ModuleAgent.Config.CheckCommand": "检查命令",
+  "pages.ModuleAgent.Config.InstallCommand": "安装命令",
+  "pages.ModuleAgent.Config.StartCommand": "启动命令",
+  "pages.ModuleAgent.Config.StopCommand": "停止命令",
+  "pages.ModuleAgent.Config.UninstallCommand": "卸载命令",
+  "pages.ModuleAgent.Config.Creator": "创建人",
+  "pages.ModuleAgent.Config.Modifier": "修改人",
+  "pages.ModuleAgent.Config.ModifyTime": "修改时间",
+  "pages.PackageAgent.Create": "新建 Package",
+  "pages.PackageAgent.Config.FileName": "文件名称",
+  "pages.PackageAgent.Config.DownloadUrl": "下载地址",
+  "pages.PackageAgent.Config.StoragePath": "存储路径",
+  "pages.PackageAgent.Config.Md5": "MD5"
 }
diff --git a/inlong-dashboard/src/ui/locales/en.json 
b/inlong-dashboard/src/ui/locales/en.json
index f206b4f47b..06a27fb7e8 100644
--- a/inlong-dashboard/src/ui/locales/en.json
+++ b/inlong-dashboard/src/ui/locales/en.json
@@ -14,6 +14,7 @@
   "basic.Creator": "Creator",
   "basic.Modifier": "Modifier",
   "basic.CreateTime": "Create time",
+  "basic.ModifyTime": "Modify time",
   "basic.Yes": "Yes",
   "basic.No": "No",
   "basic.Stop": "Stop",
@@ -865,5 +866,24 @@
   "pages.ModuleAudit.config.BenchmarkIndicator": "Benchmark indicator",
   "pages.ModuleAudit.config.ComparativeIndicators": "Comparative indicator",
   "pages.ModuleAudit.config.InlongGroupId": "Inlong group id",
-  "pages.ModuleAudit.config.InlongStreamId": "Inlong stream id"
+  "pages.ModuleAudit.config.InlongStreamId": "Inlong stream id",
+  "pages.ModuleAgent.Agent": "Agent",
+  "pages.ModuleAgent.Installer": "Installer",
+  "pages.ModuleAgent.Create": "Create Module",
+  "pages.ModuleAgent.Config.Name": "Name",
+  "pages.ModuleAgent.Config.Version": "Version",
+  "pages.ModuleAgent.Config.Package": "Package",
+  "pages.ModuleAgent.Config.CheckCommand": "Check Command",
+  "pages.ModuleAgent.Config.InstallCommand": "Install Command",
+  "pages.ModuleAgent.Config.StartCommand": "Start Command",
+  "pages.ModuleAgent.Config.StopCommand": "Stop Command",
+  "pages.ModuleAgent.Config.UninstallCommand": "Uninstall Command",
+  "pages.ModuleAgent.Config.Creator": "Creator",
+  "pages.ModuleAgent.Config.Modifier": "Modifier",
+  "pages.ModuleAgent.Config.ModifyTime": "ModifyTime",
+  "pages.PackageAgent.Create": "Create Package",
+  "pages.PackageAgent.Config.FileName": "File Name",
+  "pages.PackageAgent.Config.DownloadUrl": "Download Url",
+  "pages.PackageAgent.Config.Md5": "MD5",
+  "pages.PackageAgent.Config.StoragePath": "Storage Path"
 }
diff --git a/inlong-dashboard/src/ui/pages/AgentModule/AgentModuleTag.tsx 
b/inlong-dashboard/src/ui/pages/AgentModule/AgentModuleTag.tsx
new file mode 100644
index 0000000000..9dd7789a91
--- /dev/null
+++ b/inlong-dashboard/src/ui/pages/AgentModule/AgentModuleTag.tsx
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { Button, message, Modal } from 'antd';
+import HighTable from '@/ui/components/HighTable';
+import { useRequest } from '@/ui/hooks';
+import { defaultSize } from '@/configs/pagination';
+import { useColumns, getFormContent } from './config';
+import { useLocation } from 'react-router-dom';
+import i18n from '@/i18n';
+import CreateModal from './CreateModal';
+import request from '@/core/utils/request';
+
+interface AgentModalProps {
+  AgentModalType: String;
+}
+
+const Comp: React.FC<AgentModalProps> = ({ AgentModalType }) => {
+  const location = useLocation();
+  const type = location.state || AgentModalType;
+
+  const [query, setQuery] = useState({
+    type: type,
+    keyword: '',
+    pageNum: 1,
+    pageSize: defaultSize,
+  });
+
+  useEffect(() => {
+    setQuery(prev => ({
+      ...prev,
+      type: type,
+    }));
+  }, [type]);
+
+  const [createModal, setCreateModal] = useState<Record<string, unknown>>({
+    open: false,
+  });
+
+  const { data: sourceData = [], run: getList } = useRequest(
+    {
+      url: '/module/list',
+      method: 'POST',
+      data: query,
+    },
+    {
+      refreshDeps: [query],
+    },
+  );
+
+  const pagination = {
+    pageSize: query.pageSize,
+    current: query.pageNum,
+    total: sourceData?.total,
+  };
+
+  const onChange = ({ current: pageNum, pageSize }) => {
+    setQuery(prev => ({
+      ...prev,
+      pageNum,
+      pageSize,
+    }));
+  };
+
+  const onFilter = keyword => {
+    setQuery({
+      ...query,
+      ...keyword,
+    });
+  };
+
+  const openModal = ({ id, type }) => {
+    setCreateModal({ open: true, id, type });
+  };
+
+  const onDelete = async ({ id }) => {
+    Modal.confirm({
+      title: i18n.t('basic.DeleteConfirm'),
+      onOk: async () => {
+        await request({
+          url: `/module/delete/${id}`,
+          method: 'DELETE',
+        });
+        await getList();
+        message.success(i18n.t('basic.DeleteSuccess'));
+      },
+    });
+  };
+
+  const columns = useColumns({ onDelete, openModal });
+
+  return (
+    <>
+      <HighTable
+        filterForm={{
+          content: getFormContent(query),
+          onFilter,
+        }}
+        suffix={
+          <Button type="primary" onClick={() => setCreateModal({ open: true, 
type })}>
+            {i18n.t('pages.ModuleAgent.Create')}
+          </Button>
+        }
+        table={{
+          columns: columns,
+          dataSource: sourceData?.list,
+          rowKey: 'id',
+          pagination,
+          onChange,
+        }}
+      />
+
+      <CreateModal
+        {...createModal}
+        open={createModal.open as boolean}
+        onOk={async () => {
+          await getList();
+          setCreateModal({ open: false });
+        }}
+        onCancel={() => setCreateModal({ open: false, type })}
+      />
+    </>
+  );
+};
+
+export default Comp;
diff --git a/inlong-dashboard/src/ui/pages/AgentModule/CreateModal.tsx 
b/inlong-dashboard/src/ui/pages/AgentModule/CreateModal.tsx
new file mode 100644
index 0000000000..b9532c1b29
--- /dev/null
+++ b/inlong-dashboard/src/ui/pages/AgentModule/CreateModal.tsx
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useMemo } from 'react';
+import { Button, message, Modal } from 'antd';
+import i18n from '@/i18n';
+import FormGenerator, { useForm } from '@/ui/components/FormGenerator';
+import { useRequest, useUpdateEffect } from '@/ui/hooks';
+import request from '@/core/utils/request';
+import { ModalProps } from 'antd/es/modal';
+
+export interface Props extends ModalProps {
+  id?: string;
+  type?: string;
+}
+
+const Comp: React.FC<Props> = ({ id, type, ...modalProps }) => {
+  const [form] = useForm();
+
+  const content = useMemo(() => {
+    return [
+      {
+        type: 'input',
+        label: i18n.t('pages.ModuleAgent.Config.Name'),
+        name: 'name',
+        rules: [{ required: true }],
+      },
+      {
+        type: 'input',
+        label: i18n.t('pages.ModuleAgent.Config.Version'),
+        name: 'version',
+        rules: [{ required: true }],
+      },
+      {
+        type: 'select',
+        label: i18n.t('pages.ModuleAgent.Config.Package'),
+        name: 'packageId',
+        rules: [{ required: true }],
+        props: {
+          showSearch: true,
+          allowClear: true,
+          filterOption: false,
+          options: {
+            requestAuto: true,
+            requestTrigger: ['onOpen', 'onSearch'],
+            requestService: keyword => ({
+              url: '/package/list',
+              method: 'POST',
+              data: {
+                keyword,
+                pageNum: 1,
+                pageSize: 9999,
+                type: type,
+              },
+            }),
+            requestParams: {
+              formatResult: result =>
+                result?.list?.map(item => ({
+                  ...item,
+                  label: item.fileName,
+                  value: item.id,
+                })),
+            },
+          },
+        },
+      },
+      {
+        type: 'textarea',
+        label: i18n.t('pages.ModuleAgent.Config.CheckCommand'),
+        name: 'checkCommand',
+        props: {
+          showCount: true,
+          maxLength: 1000,
+        },
+      },
+      {
+        type: 'textarea',
+        label: i18n.t('pages.ModuleAgent.Config.InstallCommand'),
+        name: 'installCommand',
+        props: {
+          showCount: true,
+          maxLength: 1000,
+        },
+      },
+      {
+        type: 'textarea',
+        label: i18n.t('pages.ModuleAgent.Config.StartCommand'),
+        name: 'startCommand',
+        props: {
+          showCount: true,
+          maxLength: 1000,
+        },
+      },
+      {
+        type: 'textarea',
+        label: i18n.t('pages.ModuleAgent.Config.StopCommand'),
+        name: 'stopCommand',
+        props: {
+          showCount: true,
+          maxLength: 1000,
+        },
+      },
+      {
+        type: 'textarea',
+        label: i18n.t('pages.ModuleAgent.Config.UninstallCommand'),
+        name: 'uninstallCommand',
+        props: {
+          showCount: true,
+          maxLength: 1000,
+        },
+      },
+    ];
+  }, [type]);
+
+  const { data, run: getData } = useRequest(
+    id => ({
+      url: `/module/get/${id}`,
+    }),
+    {
+      manual: true,
+      onSuccess: result => {
+        form.setFieldsValue(result);
+      },
+    },
+  );
+
+  const onOk = async () => {
+    const values = await form.validateFields();
+    const isUpdate = Boolean(id);
+    if (isUpdate) {
+      values.id = id;
+    } else {
+      values.type = type;
+    }
+    await request({
+      url: isUpdate ? '/module/update' : '/module/save',
+      method: 'POST',
+      data: { ...values },
+    });
+    await modalProps?.onOk(values);
+    message.success(i18n.t('basic.OperatingSuccess'));
+  };
+
+  useUpdateEffect(() => {
+    if (modalProps.open) {
+      if (id) {
+        getData(id);
+      }
+    } else {
+      form.resetFields();
+    }
+  }, [modalProps.open]);
+
+  return (
+    <Modal
+      {...modalProps}
+      width={800}
+      title={id ? i18n.t('basic.Edit') : i18n.t('basic.Create')}
+      footer={[
+        <Button key="cancel" onClick={e => modalProps.onCancel(e)}>
+          {i18n.t('basic.Cancel')}
+        </Button>,
+        <Button key="save" type="primary" onClick={onOk}>
+          {i18n.t('basic.Save')}
+        </Button>,
+      ]}
+    >
+      <FormGenerator content={content} form={form} initialValues={id ? data : 
{}} useMaxWidth />
+    </Modal>
+  );
+};
+
+export default Comp;
diff --git a/inlong-dashboard/src/ui/pages/AgentModule/config.tsx 
b/inlong-dashboard/src/ui/pages/AgentModule/config.tsx
new file mode 100644
index 0000000000..8f6d90a15e
--- /dev/null
+++ b/inlong-dashboard/src/ui/pages/AgentModule/config.tsx
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import i18n from '@/i18n';
+import { Button } from 'antd';
+import React from 'react';
+import { timestampFormat } from '@/core/utils';
+
+export const useColumns = ({ onDelete, openModal }) => {
+  const defaultColumns = [
+    {
+      title: i18n.t('pages.ModuleAgent.Config.Name'),
+      dataIndex: 'name',
+    },
+    {
+      title: i18n.t('pages.ModuleAgent.Config.Version'),
+      dataIndex: 'version',
+    },
+    {
+      title: i18n.t('basic.Creator'),
+      dataIndex: 'creator',
+    },
+    {
+      title: i18n.t('basic.Modifier'),
+      dataIndex: 'modifier',
+    },
+    {
+      title: i18n.t('basic.ModifyTime'),
+      dataIndex: 'modifyTime',
+      render: text => text && timestampFormat(text),
+    },
+  ];
+  return defaultColumns.concat([
+    {
+      title: i18n.t('basic.Operating'),
+      dataIndex: 'action',
+      render: (text, record) => (
+        <>
+          <Button type="link" onClick={() => openModal(record)}>
+            {i18n.t('basic.Detail')}
+          </Button>
+          <Button type="link" onClick={() => onDelete(record)}>
+            {i18n.t('basic.Delete')}
+          </Button>
+        </>
+      ),
+    } as any,
+  ]);
+};
+
+export const getFormContent = initialValues => [
+  {
+    type: 'inputsearch',
+    name: 'keyword',
+    initialValue: initialValues.keyword,
+    props: {
+      allowClear: true,
+    },
+  },
+];
diff --git a/inlong-dashboard/src/ui/pages/AgentModule/index.tsx 
b/inlong-dashboard/src/ui/pages/AgentModule/index.tsx
new file mode 100644
index 0000000000..65356c3a4f
--- /dev/null
+++ b/inlong-dashboard/src/ui/pages/AgentModule/index.tsx
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useState } from 'react';
+import i18n from '@/i18n';
+import AgentModalTag from './AgentModuleTag';
+import { Container, PageContainer } from '@/ui/components/PageContainer';
+import { Card } from 'antd';
+import { useHistory, useParams } from '@/ui/hooks';
+
+const Comp: React.FC = () => {
+  const tabList = [
+    {
+      tab: i18n.t('pages.ModuleAgent.Agent'),
+      key: 'AGENT',
+      content: <AgentModalTag AgentModalType={'AGENT'} />,
+    },
+    {
+      tab: i18n.t('pages.ModuleAgent.Installer'),
+      key: 'INSTALLER',
+      content: <AgentModalTag AgentModalType={'INSTALLER'} />,
+    },
+  ];
+
+  const history = useHistory();
+
+  const { type } = useParams<Record<string, string>>();
+
+  const [module, setModule] = useState(type || tabList[0].key);
+
+  const tabListMap = tabList.reduce(
+    (acc, item) => ({
+      ...acc,
+      [item.key]: item.content,
+    }),
+    {},
+  );
+
+  const onTabsChange = value => {
+    setModule(value);
+    history.push({
+      pathname: `/agentModule`,
+      state: { type: value },
+    });
+  };
+
+  return (
+    <PageContainer useDefaultBreadcrumb={false} useDefaultContainer={false}>
+      <Container>
+        <Card
+          tabList={tabList}
+          activeTabKey={module}
+          onTabChange={key => {
+            onTabsChange(key);
+          }}
+          headStyle={{ border: 'none' }}
+          tabProps={{ size: 'middle' }}
+        >
+          {tabListMap[module]}
+        </Card>
+      </Container>
+    </PageContainer>
+  );
+};
+
+export default Comp;
diff --git a/inlong-dashboard/src/ui/pages/AgentPackage/AgentPackageTag.tsx 
b/inlong-dashboard/src/ui/pages/AgentPackage/AgentPackageTag.tsx
new file mode 100644
index 0000000000..4920c3b612
--- /dev/null
+++ b/inlong-dashboard/src/ui/pages/AgentPackage/AgentPackageTag.tsx
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useEffect, useState } from 'react';
+import { Button, message, Modal } from 'antd';
+import HighTable from '@/ui/components/HighTable';
+import { useRequest } from '@/ui/hooks';
+import { defaultSize } from '@/configs/pagination';
+import { useColumns, getFormContent } from './config';
+import CreateModal from './CreateModal';
+import i18n from '@/i18n';
+import { useLocation } from 'react-router-dom';
+import request from '@/core/utils/request';
+
+interface AgentPackageProps {
+  AgentPackageType: String;
+}
+
+const Comp: React.FC<AgentPackageProps> = ({ AgentPackageType }) => {
+  const location = useLocation();
+  const type = location.state || AgentPackageType;
+
+  const [query, setQuery] = useState({
+    type: type,
+    keyword: '',
+    pageNum: 1,
+    pageSize: defaultSize,
+  });
+
+  useEffect(() => {
+    setQuery(prev => ({
+      ...prev,
+      type: type,
+    }));
+  }, [type]);
+
+  const [createModal, setCreateModal] = useState<Record<string, unknown>>({
+    open: false,
+  });
+
+  const { data: sourceData = [], run: getList } = useRequest(
+    {
+      url: '/package/list',
+      method: 'POST',
+      data: query,
+    },
+    {
+      refreshDeps: [query],
+    },
+  );
+
+  const pagination = {
+    pageSize: query.pageSize,
+    current: query.pageNum,
+    total: sourceData?.total,
+  };
+
+  const onChange = ({ current: pageNum, pageSize }) => {
+    setQuery(prev => ({
+      ...prev,
+      pageNum,
+      pageSize,
+    }));
+  };
+
+  const onFilter = keyword => {
+    setQuery({
+      ...query,
+      ...keyword,
+    });
+  };
+
+  const openModal = ({ id, type }) => {
+    setCreateModal({ open: true, id, type });
+  };
+
+  const onDelete = async ({ id }) => {
+    Modal.confirm({
+      title: i18n.t('basic.DeleteConfirm'),
+      onOk: async () => {
+        await request({
+          url: `/package/delete/${id}`,
+          method: 'DELETE',
+        });
+        await getList();
+        message.success(i18n.t('basic.DeleteSuccess'));
+      },
+    });
+  };
+
+  const columns = useColumns({ onDelete, openModal });
+
+  return (
+    <>
+      <HighTable
+        filterForm={{
+          content: getFormContent(query),
+          onFilter,
+        }}
+        suffix={
+          <Button type="primary" onClick={() => setCreateModal({ open: true, 
type })}>
+            {i18n.t('pages.PackageAgent.Create')}
+          </Button>
+        }
+        table={{
+          columns: columns,
+          dataSource: sourceData?.list,
+          rowKey: 'id',
+          pagination,
+          onChange,
+        }}
+      />
+
+      <CreateModal
+        {...createModal}
+        open={createModal.open as boolean}
+        onOk={async () => {
+          await getList();
+          setCreateModal({ open: false });
+        }}
+        onCancel={() => setCreateModal({ open: false })}
+      />
+    </>
+  );
+};
+
+export default Comp;
diff --git a/inlong-dashboard/src/ui/pages/AgentPackage/CreateModal.tsx 
b/inlong-dashboard/src/ui/pages/AgentPackage/CreateModal.tsx
new file mode 100644
index 0000000000..6308ff0b6b
--- /dev/null
+++ b/inlong-dashboard/src/ui/pages/AgentPackage/CreateModal.tsx
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useMemo } from 'react';
+import { Button, message, Modal } from 'antd';
+import i18n from '@/i18n';
+import FormGenerator, { useForm } from '@/ui/components/FormGenerator';
+import { useRequest, useUpdateEffect } from '@/ui/hooks';
+import request from '@/core/utils/request';
+import { ModalProps } from 'antd/es/modal';
+
+export interface Props extends ModalProps {
+  // Require when edit
+  id?: string;
+  type?: string;
+}
+
+const Comp: React.FC<Props> = ({ id, type, ...modalProps }) => {
+  const [form] = useForm();
+
+  const content = useMemo(() => {
+    return [
+      {
+        type: 'input',
+        label: i18n.t('pages.PackageAgent.Config.FileName'),
+        name: 'fileName',
+        rules: [{ required: true }],
+      },
+      {
+        type: 'input',
+        label: i18n.t('pages.PackageAgent.Config.DownloadUrl'),
+        name: 'downloadUrl',
+        rules: [{ required: true }],
+      },
+      {
+        type: 'input',
+        label: i18n.t('pages.PackageAgent.Config.Md5'),
+        name: 'md5',
+        rules: [{ required: true }],
+      },
+      {
+        type: 'input',
+        label: i18n.t('pages.PackageAgent.Config.StoragePath'),
+        name: 'storagePath',
+        rules: [{ required: true }],
+      },
+    ];
+  }, []);
+
+  const { data, run: getData } = useRequest(
+    id => ({
+      url: `/package/get/${id}`,
+    }),
+    {
+      manual: true,
+      onSuccess: result => {
+        form.setFieldsValue(result);
+      },
+    },
+  );
+
+  const onOk = async () => {
+    const values = await form.validateFields();
+    const isUpdate = Boolean(id);
+    if (isUpdate) {
+      values.id = id;
+    } else {
+      values.type = type;
+    }
+    await request({
+      url: isUpdate ? '/package/update' : '/package/save',
+      method: 'POST',
+      data: { ...values },
+    });
+    await modalProps?.onOk(values);
+    message.success(i18n.t('basic.OperatingSuccess'));
+  };
+
+  useUpdateEffect(() => {
+    if (modalProps.open) {
+      if (id) {
+        getData(id);
+      }
+    } else {
+      form.resetFields();
+    }
+  }, [modalProps.open]);
+
+  return (
+    <Modal
+      {...modalProps}
+      title={id ? i18n.t('basic.Edit') : i18n.t('basic.Create')}
+      footer={[
+        <Button key="cancel" onClick={e => modalProps.onCancel(e)}>
+          {i18n.t('basic.Cancel')}
+        </Button>,
+        <Button key="save" type="primary" onClick={onOk}>
+          {i18n.t('basic.Save')}
+        </Button>,
+      ]}
+    >
+      <FormGenerator content={content} form={form} initialValues={id ? data : 
{}} useMaxWidth />
+    </Modal>
+  );
+};
+
+export default Comp;
diff --git a/inlong-dashboard/src/ui/pages/AgentPackage/config.tsx 
b/inlong-dashboard/src/ui/pages/AgentPackage/config.tsx
new file mode 100644
index 0000000000..8b680decf1
--- /dev/null
+++ b/inlong-dashboard/src/ui/pages/AgentPackage/config.tsx
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import i18n from '@/i18n';
+import { Button } from 'antd';
+import React from 'react';
+import { timestampFormat } from '@/core/utils';
+
+export const useColumns = ({ onDelete, openModal }) => {
+  const defaultColumns = [
+    {
+      title: i18n.t('pages.PackageAgent.Config.FileName'),
+      dataIndex: 'fileName',
+    },
+    {
+      title: i18n.t('pages.PackageAgent.Config.StoragePath'),
+      dataIndex: 'storagePath',
+    },
+    {
+      title: i18n.t('pages.PackageAgent.Config.DownloadUrl'),
+      dataIndex: 'downloadUrl',
+    },
+    {
+      title: i18n.t('basic.Creator'),
+      dataIndex: 'creator',
+    },
+    {
+      title: i18n.t('basic.Modifier'),
+      dataIndex: 'modifier',
+    },
+    {
+      title: i18n.t('basic.ModifyTime'),
+      dataIndex: 'modifyTime',
+      render: text => text && timestampFormat(text),
+    },
+  ];
+  return defaultColumns.concat([
+    {
+      title: i18n.t('basic.Operating'),
+      dataIndex: 'action',
+      render: (text, record) => (
+        <>
+          <Button type="link" onClick={() => openModal(record)}>
+            {i18n.t('basic.Detail')}
+          </Button>
+          <Button type="link" onClick={() => onDelete(record)}>
+            {i18n.t('basic.Delete')}
+          </Button>
+        </>
+      ),
+    } as any,
+  ]);
+};
+
+export const getFormContent = initialValues => [
+  {
+    type: 'inputsearch',
+    name: 'keyword',
+    initialValue: initialValues.keyword,
+    props: {
+      allowClear: true,
+    },
+  },
+];
diff --git a/inlong-dashboard/src/ui/pages/AgentPackage/index.tsx 
b/inlong-dashboard/src/ui/pages/AgentPackage/index.tsx
new file mode 100644
index 0000000000..6def0b008b
--- /dev/null
+++ b/inlong-dashboard/src/ui/pages/AgentPackage/index.tsx
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useState } from 'react';
+import { Card } from 'antd';
+import { useHistory, useParams } from '@/ui/hooks';
+import { Container, PageContainer } from '@/ui/components/PageContainer';
+import i18n from '@/i18n';
+import AgentPackageTag from '@/ui/pages/AgentPackage/AgentPackageTag';
+
+const Comp: React.FC = () => {
+  const tabList = [
+    {
+      tab: i18n.t('pages.ModuleAgent.Agent'),
+      key: 'AGENT',
+      content: <AgentPackageTag AgentPackageType={'AGENT'} />,
+    },
+    {
+      tab: i18n.t('pages.ModuleAgent.Installer'),
+      key: 'INSTALLER',
+      content: <AgentPackageTag AgentPackageType={'INSTALLER'} />,
+    },
+  ];
+
+  const history = useHistory();
+
+  const { type } = useParams<Record<string, string>>();
+
+  const [module, setModule] = useState(type || tabList[0].key);
+
+  const tabListMap = tabList.reduce(
+    (acc, item) => ({
+      ...acc,
+      [item.key]: item.content,
+    }),
+    {},
+  );
+
+  const onTabsChange = value => {
+    setModule(value);
+    history.push({
+      pathname: `/agentPackage`,
+      state: { type: value },
+    });
+  };
+
+  return (
+    <PageContainer useDefaultBreadcrumb={false} useDefaultContainer={false}>
+      <Container>
+        <Card
+          tabList={tabList}
+          activeTabKey={module}
+          onTabChange={key => {
+            onTabsChange(key);
+          }}
+          headStyle={{ border: 'none' }}
+          tabProps={{ size: 'middle' }}
+        >
+          {tabListMap[module]}
+        </Card>
+      </Container>
+    </PageContainer>
+  );
+};
+
+export default Comp;


Reply via email to