Răsfoiți Sursa

添加告警配置、任务超时、重试、子文件夹匹配

weijianghai 1 lună în urmă
părinte
comite
796b35656f

+ 4 - 0
README.md

@@ -10,6 +10,10 @@ admin
 
 UbLiwooNJF7d#pyt1DR*
 
+## 部署位置
+
+192.168.31.83/data/dingtalk_cms
+
 This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use.
 
 ## Environment Prepare

+ 1 - 0
config/routes.ts

@@ -15,6 +15,7 @@ export default [
   { path: '/app', name: '应用管理', component: '@/pages/AppManage' },
   { path: '/robot', name: '机器人管理', component: '@/pages/RobotManage' },
   { path: '/appTaskLog', name: '应用任务日志', component: '@/pages/AppTaskLog' },
+  { path: '/alertConfig', name: '告警配置', component: '@/pages/AlertConfig' },
   { path: '/', redirect: '/app' },
   { path: '*', layout: false, component: '@/pages/exception/NotFound' },
 ];

+ 1 - 0
pnpm-lock.yaml

@@ -2816,6 +2816,7 @@ packages:
 
   acorn-import-assertions@1.9.0:
     resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
+    deprecated: package has been renamed to acorn-import-attributes
     peerDependencies:
       acorn: ^8
 

+ 3 - 0
scripts/.dockerignore

@@ -0,0 +1,3 @@
+*
+!default.conf
+!dist.zip

+ 4 - 0
scripts/Dockerfile

@@ -0,0 +1,4 @@
+FROM nginx:1.26.2-alpine-slim
+COPY dist.zip dist.zip
+RUN unzip dist.zip && rm -rf dist.zip
+COPY default.conf /etc/nginx/conf.d/

+ 18 - 0
scripts/default.conf

@@ -0,0 +1,18 @@
+server {
+    listen       39101;
+    listen  [::]:39101;
+    server_name localhost;
+
+    access_log access.log;
+    error_log error.log;
+
+    location / {
+        root   /dist;
+        index  index.html index.htm;
+        try_files $uri $uri/ /index.html;
+    }
+    # 后端
+    location /api {
+        proxy_pass http://192.168.31.83:39000;
+    }
+}

+ 6 - 0
scripts/rollback.sh

@@ -0,0 +1,6 @@
+#!/bin/sh
+
+CONTAINER_NAME=dingtalk_cms
+TAG=$1
+docker stop "${CONTAINER_NAME}" && docker rm -f "${CONTAINER_NAME}"
+docker run --name "${CONTAINER_NAME}" --restart=unless-stopped --network=host -d "${CONTAINER_NAME}":"${TAG}"

+ 7 - 0
scripts/update.sh

@@ -0,0 +1,7 @@
+#!/bin/sh
+
+CONTAINER_NAME=dingtalk_cms
+TAG=$(date +%Y%m%d%H%M%S)
+docker stop "${CONTAINER_NAME}" && docker rm -f "${CONTAINER_NAME}"
+docker build -t "${CONTAINER_NAME}":"${TAG}" .
+docker run --name "${CONTAINER_NAME}" --restart=unless-stopped --network=host -d "${CONTAINER_NAME}":"${TAG}"

+ 57 - 34
src/components/AppTaskLogTable/index.tsx

@@ -1,7 +1,8 @@
 import React from 'react';
-import { ProColumns, ProTable } from '@ant-design/pro-components';
+import {ProCard, ProColumns, ProTable} from '@ant-design/pro-components';
 import { appTaskLogStatusEnum, conversationTypeEnum, timeRangeFieldProps } from '@/constants/enums';
 import { listAppTaskLog } from '@/services/api/appTask';
+import {Popover, Typography} from "antd";
 
 type TableListItem = API.AppTaskLogPo;
 
@@ -37,60 +38,69 @@ const AppTaskLogTable: React.FC<{
       valueType: 'dateTime',
       search: false,
       sorter: true,
+      fixed: "left",
     },
     {
-      title: '日志id',
-      dataIndex: 'id',
+      title: '应用名称',
+      dataIndex: 'appName',
       copyable: true,
       sorter: true,
-      ellipsis: true,
+      fixed: "left",
     },
     {
-      title: '应用名称',
-      dataIndex: 'appName',
+      title: '任务名称',
+      dataIndex: 'taskName',
       copyable: true,
       sorter: true,
+      fixed: "left",
     },
     {
-      title: 'appId',
-      dataIndex: 'appId',
-      copyable: true,
+      title: '执行结果',
+      dataIndex: 'status',
+      valueEnum: appTaskLogStatusEnum.valueEnum,
       sorter: true,
-      ellipsis: true,
+      fixed: "left",
     },
     {
-      title: '任务id',
-      dataIndex: 'taskId',
+      title: '日志id',
+      dataIndex: 'id',
       copyable: true,
       sorter: true,
-      initialValue: taskId,
-      ellipsis: true,
     },
     {
-      title: '任务名称',
-      dataIndex: 'taskName',
+      title: 'appId',
+      dataIndex: 'appId',
       copyable: true,
       sorter: true,
     },
     {
-      title: '执行结果',
-      dataIndex: 'status',
-      valueEnum: appTaskLogStatusEnum.valueEnum,
+      title: '任务id',
+      dataIndex: 'taskId',
+      copyable: true,
       sorter: true,
+      initialValue: taskId,
     },
     {
-      title: '详情',
+      title: '异常信息',
       dataIndex: 'detail',
       copyable: true,
       search: false,
-      ellipsis: true,
+      render: (text) => (
+        <Popover
+          content={<ProCard wrap style={{maxWidth: 500}}>{text}</ProCard>}
+          trigger="hover"
+        >
+          <Typography.Text ellipsis={true} style={{maxWidth: 100}}>
+            {text}
+          </Typography.Text>
+        </Popover>
+      ),
     },
     {
       title: '机器人编码',
       dataIndex: 'robotCode',
       copyable: true,
       sorter: true,
-      ellipsis: true,
     },
     {
       title: '机器人名称',
@@ -109,7 +119,6 @@ const AppTaskLogTable: React.FC<{
       dataIndex: 'openConversationId',
       copyable: true,
       sorter: true,
-      ellipsis: true,
     },
     {
       title: '接收机器人消息的手机号列表',
@@ -128,7 +137,6 @@ const AppTaskLogTable: React.FC<{
       dataIndex: 'sftpId',
       copyable: true,
       sorter: true,
-      ellipsis: true,
     },
     {
       title: 'sftp ip',
@@ -143,22 +151,36 @@ const AppTaskLogTable: React.FC<{
       sorter: true,
     },
     {
-      title: '数据文件夹',
-      dataIndex: 'dataDir',
-      copyable: true,
-      search: false,
-    },
-    {
-      title: '文件名匹配正则表达式',
-      dataIndex: 'filePattern',
+      title: '要发送的文件',
+      dataIndex: 'files',
       copyable: true,
       search: false,
+      render: (text) => (
+        <Popover
+          content={<ProCard wrap style={{maxWidth: 500}}>{text}</ProCard>}
+          trigger="hover"
+        >
+          <Typography.Text ellipsis={true} style={{maxWidth: 100}}>
+            {text}
+          </Typography.Text>
+        </Popover>
+      ),
     },
     {
-      title: '文件路径',
-      dataIndex: 'filePath',
+      title: '消息id',
+      dataIndex: 'processQueryKeys',
       copyable: true,
       search: false,
+      render: (text) => (
+        <Popover
+          content={<ProCard wrap style={{maxWidth: 500}}>{text}</ProCard>}
+          trigger="hover"
+        >
+          <Typography.Text ellipsis={true} style={{maxWidth: 100}}>
+            {text}
+          </Typography.Text>
+        </Popover>
+      ),
     },
     {
       title: '时间范围',
@@ -181,6 +203,7 @@ const AppTaskLogTable: React.FC<{
         layout: 'vertical',
       }}
       request={getDataSource}
+      scroll={{x: 'max-content'}}
     />
   );
 };

+ 60 - 12
src/constants/enums.ts

@@ -6,8 +6,8 @@ export const appTaskStatusEnum = {
     '0': { text: '已停止', status: 'Default' },
     '1': { text: '运行中', status: 'Success' },
   },
-  STOPPED: 0,
-  RUNNING: 1,
+  STOPPED: '0',
+  RUNNING: '1',
 };
 
 /** 会话类型 */
@@ -16,8 +16,8 @@ export const conversationTypeEnum = {
     '1': '单聊',
     '2': '群聊',
   },
-  SINGLE: 1,
-  GROUP: 2,
+  SINGLE: '1',
+  GROUP: '2',
 };
 
 /** 应用任务执行状态 */
@@ -26,18 +26,18 @@ export const appTaskLogStatusEnum = {
     '0': { text: '失败', status: 'Error' },
     '1': { text: '成功', status: 'Success' },
   },
-  ERROR: 0,
-  SUCCESS: 1,
+  ERROR: '0',
+  SUCCESS: '1',
 };
 
 /** 最近时间范围 */
 export const recentTimeRanges: any = {
-  最近一小时: [moment().subtract(1, 'h'), moment()],
-  今日: [moment().startOf('day'), moment()],
-  昨日: [moment().startOf('day').subtract(1, 'd'), moment().endOf('day').subtract(1, 'd')],
-  最近一周: [moment().subtract(7, 'd'), moment()],
-  本月: [moment().startOf('month'), moment()],
-  上个月: [moment().startOf('month').subtract(1, 'M'), moment().endOf('month').subtract(1, 'M')],
+  '最近一小时': [moment().subtract(1, 'h'), moment()],
+  '今日': [moment().startOf('day'), moment()],
+  '昨日': [moment().startOf('day').subtract(1, 'd'), moment().endOf('day').subtract(1, 'd')],
+  '最近一周': [moment().subtract(7, 'd'), moment()],
+  '本月': [moment().startOf('month'), moment()],
+  '上个月': [moment().startOf('month').subtract(1, 'M'), moment().endOf('month').subtract(1, 'M')],
 };
 
 /** 通用时间范围配置 */
@@ -45,3 +45,51 @@ export const timeRangeFieldProps = {
   disabledDate: (v: any) => v.isAfter(moment()),
   ranges: recentTimeRanges,
 };
+
+/** 是否 */
+export const booleanEnum = {
+  valueEnum: {
+    '0': '否',
+    '1': '是',
+  },
+  FALSE: '0',
+  TRUE: '1',
+};
+
+/** 子文件夹匹配方式 */
+export const subdirectoryMethodEnum = {
+  valueEnum: {
+    MONTH: '月yyyyMM',
+    DAY: '天yyyyMMdd',
+    REG_EXP: "正则表达式匹配最新子文件夹",
+  },
+  MONTH: 'MONTH',
+  DAY: 'DAY',
+  REG_EXP: 'REG_EXP',
+}
+
+/** 文件匹配方式 */
+export const fileMethodEnum = {
+  valueEnum: {
+    ALL: '文件夹下所有文件',
+    MONTH: '月yyyyMM',
+    DAY: '天yyyyMMdd',
+    REG_EXP: "正则表达式匹配最新文件",
+  },
+  ALL: 'ALL',
+  MONTH: 'MONTH',
+  DAY: 'DAY',
+  REG_EXP: 'REG_EXP',
+}
+
+/** 任务告警方式 */
+export const taskAlertTypeEnum = {
+  valueEnum: {
+    DEFAULT: '系统默认',
+    TASK: '同任务',
+    CUSTOM: '自定义',
+  },
+  DEFAULT: 'DEFAULT',
+  TASK: 'TASK',
+  CUSTOM: 'CUSTOM',
+}

+ 36 - 0
src/pages/AlertConfig/index.tsx

@@ -0,0 +1,36 @@
+import {ProCard, ProForm, ProFormText} from "@ant-design/pro-components";
+import {isEmpty} from "lodash-es";
+import {getAlertConfig, updateAlertConfig} from "@/services/api/alertConfig";
+import {App} from "antd";
+import {phoneList, userIdList} from "@/constants/rules";
+
+/** 告警配置页面 */
+export default () => {
+  const { message } = App.useApp();
+
+  const onFinish = async (values: any) => {
+    if (isEmpty(values)) {
+      return;
+    }
+
+    await updateAlertConfig(values);
+    message.success('修改成功');
+  };
+
+  const getData = async () => {
+    const res = await getAlertConfig();
+    return res?.data
+  }
+
+  return (
+    <ProCard layout="center">
+      <ProForm size="large" onFinish={onFinish} request={getData}>
+        <ProFormText name="robotCode" label="机器人编码" width={800} />
+        <ProFormText name="robotSecret" label="机器人密钥" width={800} />
+        <ProFormText name="openConversationId" label="群id" width={800} />
+        <ProFormText name="phones" label="手机号列表" width={800} rules={[phoneList]} />
+        <ProFormText name="userIds" label="userId列表" width={800} rules={[userIdList]} />
+      </ProForm>
+    </ProCard>
+  );
+};

+ 230 - 21
src/pages/AppManage/components/AddAppTask/index.tsx

@@ -3,14 +3,20 @@ import { App, Button } from 'antd';
 import {
   ActionType,
   ModalForm,
-  ProFormDependency,
+  ProFormDependency, ProFormDigit,
   ProFormRadio,
   ProFormSelect,
   ProFormText,
 } from '@ant-design/pro-components';
 import { phoneList, userIdList, validCron, whitespaceForbidden } from '@/constants/rules';
 import { addAppTask, updateAppTask } from '@/services/api/appTask';
-import { conversationTypeEnum } from '@/constants/enums';
+import {
+  taskAlertTypeEnum,
+  booleanEnum,
+  conversationTypeEnum,
+  fileMethodEnum,
+  subdirectoryMethodEnum
+} from '@/constants/enums';
 import { getAppRobotOptions } from '@/services/api/appRobot';
 import { getAppSftpOptions } from '@/services/api/appSftp';
 
@@ -26,7 +32,7 @@ const UpdateAppTask: React.FC<{
   /** 提交数据 */
   const onFinish = async (values: any) => {
     if (
-      conversationTypeEnum.SINGLE.toString() === values?.conversationType &&
+      conversationTypeEnum.SINGLE === values?.conversationType &&
       !values?.phones &&
       !values?.userIds
     ) {
@@ -96,16 +102,14 @@ const UpdateAppTask: React.FC<{
       <ProFormRadio.Group
         name="conversationType"
         label="会话类型"
-        initialValue={
-          record?.conversationType ? record.conversationType.toString() : record?.conversationType
-        }
+        initialValue={record?.conversationType}
         valueEnum={conversationTypeEnum.valueEnum}
         rules={[{ required: true }]}
       />
       <ProFormDependency name={['conversationType']}>
         {({ conversationType }) => {
           // 单聊表单项
-          if (conversationTypeEnum.SINGLE.toString() === conversationType) {
+          if (conversationTypeEnum.SINGLE === conversationType) {
             return (
               <>
                 <ProFormText
@@ -125,7 +129,7 @@ const UpdateAppTask: React.FC<{
           }
 
           // 群聊表单项
-          if (conversationTypeEnum.GROUP.toString() === conversationType) {
+          if (conversationTypeEnum.GROUP === conversationType) {
             return (
               <ProFormText
                 name="openConversationId"
@@ -160,26 +164,231 @@ const UpdateAppTask: React.FC<{
         }}
       />
       <ProFormText
-        name="dataDir"
-        label="数据文件夹(绝对路径)"
-        initialValue={record?.dataDir}
+        name="masterDir"
+        label="数据文件夹(绝对路径)"
+        initialValue={record?.masterDir}
         rules={[
           {
             required: true,
           },
-          whitespaceForbidden,
         ]}
       />
-      <ProFormText
-        name="filePattern"
-        label="文件名匹配正则表达式"
-        initialValue={record?.filePattern}
-        rules={[
-          {
-            required: true,
-          },
-        ]}
+      <ProFormRadio.Group
+        name="hasSubdirectory"
+        label="是否有子文件夹"
+        initialValue={record?.hasSubdirectory}
+        valueEnum={booleanEnum.valueEnum}
+        rules={[{ required: true }]}
+      />
+      <ProFormDependency name={['hasSubdirectory']}>
+        {({ hasSubdirectory }) => {
+          if (booleanEnum.TRUE === hasSubdirectory) {
+            return (
+              <>
+                <ProFormRadio.Group
+                  name="subdirectoryMethod"
+                  label="子文件夹匹配方式"
+                  initialValue={record?.subdirectoryMethod}
+                  valueEnum={subdirectoryMethodEnum.valueEnum}
+                  rules={[{ required: true }]}
+                />
+                <ProFormDependency name={['subdirectoryMethod']}>
+                  {({ subdirectoryMethod }) => {
+                    let t;
+                    if (fileMethodEnum.MONTH === subdirectoryMethod) {
+                      t = '月'
+                    } else if (fileMethodEnum.DAY === subdirectoryMethod) {
+                      t = '天'
+                    }
+                    if (subdirectoryMethodEnum.MONTH === subdirectoryMethod
+                      || subdirectoryMethodEnum.DAY === subdirectoryMethod) {
+                      return (
+                        <ProFormDigit
+                          name={'dirTimeDelay'}
+                          label={'子文件夹时延(' + t + ')'}
+                          initialValue={record?.dirTimeDelay ?? 0}
+                          fieldProps={{ precision: 0 }}
+                          rules={[{ required: true }]}
+                          min={Number.MIN_SAFE_INTEGER}
+                        />
+                      )
+                    }
+                    if (subdirectoryMethodEnum.REG_EXP === subdirectoryMethod) {
+                      return (
+                        <ProFormText
+                          name="subdirectoryPattern"
+                          label="匹配最新子文件夹正则表达式"
+                          initialValue={record?.subdirectoryPattern}
+                          rules={[
+                            {
+                              required: true,
+                            },
+                          ]}
+                        />
+                      )
+                    }
+                    return (<></>);
+                  }}
+                </ProFormDependency>
+              </>
+            )
+          }
+          return (<></>);
+        }}
+      </ProFormDependency>
+      <ProFormRadio.Group
+        name="fileToText"
+        label="txt、md文件是否转为文本"
+        initialValue={record?.fileToText}
+        valueEnum={booleanEnum.valueEnum}
+        rules={[{ required: true }]}
+      />
+      <ProFormRadio.Group
+        name="fileMethod"
+        label="文件匹配方式"
+        initialValue={record?.fileMethod}
+        valueEnum={fileMethodEnum.valueEnum}
+        rules={[{ required: true }]}
       />
+      <ProFormDependency name={['fileMethod']}>
+        {({ fileMethod }) => {
+          let t;
+          if (fileMethodEnum.MONTH === fileMethod) {
+            t = '月'
+          } else if (fileMethodEnum.DAY === fileMethod) {
+            t = '天'
+          }
+          if (fileMethodEnum.MONTH === fileMethod
+            || fileMethodEnum.DAY === fileMethod) {
+            return (
+              <>
+                <ProFormDigit
+                  name={'fileTimeDelay'}
+                  label={'文件时延(' + t + ')'}
+                  initialValue={record?.fileTimeDelay ?? 0}
+                  fieldProps={{ precision: 0 }}
+                  rules={[{ required: true }]}
+                  min={Number.MIN_SAFE_INTEGER}
+                />
+                <ProFormText
+                  name="filePrefix"
+                  label="文件前缀"
+                  initialValue={record?.filePrefix}
+                  rules={[
+                    {
+                      required: true,
+                    },
+                  ]}
+                />
+                <ProFormText
+                  name="fileExtension"
+                  label="文件扩展名"
+                  initialValue={record?.fileExtension}
+                  rules={[
+                    {
+                      required: true,
+                    },
+                  ]}
+                />
+              </>
+            )
+          }
+          if (fileMethodEnum.REG_EXP === fileMethod) {
+            return (
+              <ProFormText
+                name="filePattern"
+                label="文件匹配正则表达式"
+                initialValue={record?.filePattern}
+                rules={[
+                  {
+                    required: true,
+                  },
+                ]}
+              />
+            )
+          }
+          return (<></>);
+        }}
+      </ProFormDependency>
+      <ProFormDigit
+        name={'taskTimeout'}
+        label={'任务超时时间(秒)'}
+        initialValue={record?.taskTimeout ?? 60}
+        fieldProps={{ precision: 0 }}
+        rules={[{ required: true }]}
+        min={1}
+      />
+      <ProFormDigit
+        name={'maxRetryTimes'}
+        label={'最大失败重试次数'}
+        initialValue={record?.maxRetryTimes ?? 0}
+        fieldProps={{ precision: 0 }}
+        rules={[{ required: true }]}
+        min={0}
+      />
+      <ProFormDigit
+        name={'retryInterval'}
+        label={'重试间隔(秒)'}
+        initialValue={record?.retryInterval ?? 3600}
+        fieldProps={{ precision: 0 }}
+        rules={[{ required: true }]}
+        min={0}
+      />
+      <ProFormRadio.Group
+        name="alertType"
+        label="告警方式"
+        initialValue={record?.alertType ?? taskAlertTypeEnum.DEFAULT}
+        valueEnum={taskAlertTypeEnum.valueEnum}
+        rules={[{ required: true }]}
+      />
+      <ProFormDependency name={['alertType']}>
+        {({ alertType }) => {
+          // 自定义
+          if (taskAlertTypeEnum.CUSTOM === alertType) {
+            return (
+              <>
+                <ProFormSelect
+                  name="alertRobotCode"
+                  label="告警机器人"
+                  initialValue={record?.robotCode}
+                  showSearch
+                  rules={[{ required: true }]}
+                  debounceTime={1000}
+                  // @ts-ignore
+                  request={async ({ keyWords = '' }) => {
+                    const res = await getAppRobotOptions({ appId });
+                    return res?.data?.filter(({ value, label }) => {
+                      return value?.includes(keyWords) || label?.includes(keyWords);
+                    });
+                  }}
+                />
+                <ProFormText
+                  name="alertOpenConversationId"
+                  label="告警群id"
+                  initialValue={record?.alertOpenConversationId}
+                  rules={[
+                    whitespaceForbidden,
+                  ]}
+                />
+                <ProFormText
+                  name="alertPhones"
+                  label="告警手机号列表"
+                  initialValue={record?.alertPhones}
+                  rules={[phoneList]}
+                />
+                <ProFormText
+                  name="alertUserIds"
+                  label="告警userId列表"
+                  initialValue={record?.alertUserIds}
+                  rules={[userIdList]}
+                />
+              </>
+            );
+          }
+
+          return <></>;
+        }}
+      </ProFormDependency>
     </ModalForm>
   );
 };

+ 2 - 0
src/pages/AppManage/components/AppRobotCommandManage/index.tsx

@@ -56,6 +56,7 @@ const AppRobotCommandManage: React.FC<{
       copyable: true,
       sorter: true,
       editable: false,
+      fixed: "left",
     },
     {
       title: 'url',
@@ -175,6 +176,7 @@ const AppRobotCommandManage: React.FC<{
           onSave,
           onDelete,
         }}
+        scroll={{x: 'max-content'}}
       />
     </Modal>
   );

+ 3 - 0
src/pages/AppManage/components/AppRobotManage/index.tsx

@@ -48,12 +48,14 @@ const AppRobotManage: React.FC<{
       dataIndex: 'robotCode',
       copyable: true,
       sorter: true,
+      fixed: "left",
     },
     {
       title: '机器人名称',
       dataIndex: 'robotName',
       copyable: true,
       sorter: true,
+      fixed: "left",
     },
     {
       title: '机器人密钥',
@@ -133,6 +135,7 @@ const AppRobotManage: React.FC<{
             robotCodes={selectedRowKeys as string[]}
           />
         )}
+        scroll={{x: 'max-content'}}
       />
       <AppRobotCommandManage
         open={isAppRobotCommandManageOpen}

+ 3 - 0
src/pages/AppManage/components/AppSftpManage/index.tsx

@@ -49,6 +49,7 @@ const AppSftpManage: React.FC<{
       copyable: true,
       sorter: true,
       editable: false,
+      fixed: "left",
     },
     {
       title: 'ip',
@@ -63,6 +64,7 @@ const AppSftpManage: React.FC<{
           whitespaceForbidden,
         ],
       },
+      fixed: "left",
     },
     {
       title: '端口',
@@ -207,6 +209,7 @@ const AppSftpManage: React.FC<{
           onSave,
           onDelete,
         }}
+        scroll={{x: 'max-content'}}
       />
     </Modal>
   );

+ 119 - 14
src/pages/AppManage/components/AppTaskManage/index.tsx

@@ -2,7 +2,14 @@ import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
 import React, { useRef } from 'react';
 import { Modal, Space } from 'antd';
 import { listAppTask } from '@/services/api/appTask';
-import { appTaskStatusEnum, conversationTypeEnum } from '@/constants/enums';
+import {
+  taskAlertTypeEnum,
+  appTaskStatusEnum,
+  booleanEnum,
+  conversationTypeEnum,
+  fileMethodEnum,
+  subdirectoryMethodEnum
+} from '@/constants/enums';
 import UpdateAppTask from '@/pages/AppManage/components/AddAppTask';
 import StopAppTask from '@/pages/AppManage/components/StopAppTask';
 import EnableAppTask from '@/pages/AppManage/components/EnableAppTask';
@@ -49,25 +56,27 @@ const AppTaskManage: React.FC<{
       dataIndex: 'taskId',
       copyable: true,
       sorter: true,
+      fixed: "left",
     },
     {
       title: '任务名称',
       dataIndex: 'taskName',
       copyable: true,
       sorter: true,
-    },
-    {
-      title: '任务描述',
-      dataIndex: 'description',
-      copyable: true,
-      search: false,
-      ellipsis: true,
+      fixed: "left",
     },
     {
       title: '任务状态',
       dataIndex: 'status',
       valueEnum: appTaskStatusEnum.valueEnum,
       sorter: true,
+      fixed: "left",
+    },
+    {
+      title: '任务描述',
+      dataIndex: 'description',
+      copyable: true,
+      search: false,
     },
     {
       title: 'cron表达式',
@@ -124,17 +133,110 @@ const AppTaskManage: React.FC<{
       sorter: true,
     },
     {
-      title: '数据文件夹',
-      dataIndex: 'dataDir',
+      title: '数据主文件夹',
+      dataIndex: 'masterDir',
+      copyable: true,
+      search: false,
+    },
+    {
+      title: '是否有子文件夹',
+      dataIndex: 'hasSubdirectory',
+      valueEnum: booleanEnum.valueEnum,
+    },
+    {
+      title: '子文件夹匹配方式',
+      dataIndex: 'subdirectoryMethod',
+      valueEnum: subdirectoryMethodEnum.valueEnum,
+    },
+    {
+      title: '子文件夹时延',
+      dataIndex: 'dirTimeDelay',
+      search: false,
+    },
+    {
+      title: '子文件夹匹配正则表达式',
+      dataIndex: 'subdirectoryPattern',
       copyable: true,
       search: false,
     },
     {
-      title: '文件名匹配正则表达式',
+      title: 'txt、md文件是否转为文本',
+      dataIndex: 'fileToText',
+      valueEnum: booleanEnum.valueEnum,
+      search: false,
+    },
+    {
+      title: '文件匹配方式',
+      dataIndex: 'fileMethod',
+      valueEnum: fileMethodEnum.valueEnum,
+      search: false,
+    },
+    {
+      title: '文件时延',
+      dataIndex: 'fileTimeDelay',
+      search: false,
+    },
+    {
+      title: '文件前缀',
+      dataIndex: 'filePrefix',
+      search: false,
+    },
+    {
+      title: '文件扩展名',
+      dataIndex: 'fileExtension',
+      search: false,
+    },
+    {
+      title: '文件匹配正则表达式',
       dataIndex: 'filePattern',
       copyable: true,
       search: false,
     },
+    {
+      title: '任务超时时间(秒)',
+      dataIndex: 'taskTimeout',
+      search: false,
+    },
+    {
+      title: '最大失败重试次数',
+      dataIndex: 'maxRetryTimes',
+      search: false,
+    },
+    {
+      title: '重试间隔(秒)',
+      dataIndex: 'retryInterval',
+      search: false,
+    },
+    {
+      title: '告警方式',
+      dataIndex: 'alertType',
+      valueEnum: taskAlertTypeEnum.valueEnum,
+    },
+    {
+      title: '告警机器人编码',
+      dataIndex: 'alertRobotCode',
+      copyable: true,
+    },
+    {
+      title: '告警机器人名称',
+      dataIndex: 'alertRobotName',
+      copyable: true,
+    },
+    {
+      title: '告警群id',
+      dataIndex: 'alertOpenConversationId',
+      copyable: true,
+    },
+    {
+      title: '告警手机号列表',
+      dataIndex: 'alertPhones',
+      copyable: true,
+    },
+    {
+      title: '告警userId列表',
+      dataIndex: 'alertUserIds',
+      copyable: true,
+    },
     {
       title: '创建时间',
       dataIndex: 'createTime',
@@ -154,15 +256,17 @@ const AppTaskManage: React.FC<{
       key: 'option',
       valueType: 'option',
       render: (text, record) => (
-        <Space direction="vertical">
+        <Space>
           <UpdateAppTask actionRef={actionRef} appId={appId} record={record} />
-          {appTaskStatusEnum.STOPPED === record?.status ? (
+          {
+            // @ts-ignore
+            appTaskStatusEnum.STOPPED === record?.status ? (
             <EnableAppTask actionRef={actionRef} taskId={record?.taskId as string} />
           ) : (
             <StopAppTask actionRef={actionRef} taskId={record?.taskId as string} />
           )}
           <DeleteAppTask actionRef={actionRef} taskId={record?.taskId as string} />
-          <RunAppTask actionRef={actionRef} taskId={record?.taskId as string} />
+          <RunAppTask taskId={record?.taskId as string} />
           <AppTaskLogModal taskId={record?.taskId} />
         </Space>
       ),
@@ -192,6 +296,7 @@ const AppTaskManage: React.FC<{
         }}
         toolBarRender={() => [<UpdateAppTask key={'add'} actionRef={actionRef} appId={appId} />]}
         request={getDataSource}
+        scroll={{x: 'max-content'}}
       />
     </Modal>
   );

+ 3 - 0
src/pages/AppManage/components/AvailableRobot/index.tsx

@@ -45,12 +45,14 @@ const AvailableRobot: React.FC<{
       dataIndex: 'robotCode',
       copyable: true,
       sorter: true,
+      fixed: "left",
     },
     {
       title: '机器人名称',
       dataIndex: 'robotName',
       copyable: true,
       sorter: true,
+      fixed: "left",
     },
     {
       title: '机器人密钥',
@@ -109,6 +111,7 @@ const AvailableRobot: React.FC<{
             robotCodes={selectedRowKeys as string[]}
           />
         )}
+        scroll={{x: 'max-content'}}
       />
     </Modal>
   );

+ 1 - 5
src/pages/AppManage/components/RunAppTask/index.tsx

@@ -1,23 +1,19 @@
 import { App, Button, Popconfirm } from 'antd';
-import type { MutableRefObject } from 'react';
 import React from 'react';
-import { ActionType } from '@ant-design/pro-components';
 import { runAppTask } from '@/services/api/appTask';
 
 /** 执行应用任务组件 */
 const RunAppTask: React.FC<{
   /** Table action 的引用,便于自定义触发 */
-  actionRef: MutableRefObject<ActionType | undefined>;
   /** 任务id */
   taskId: string;
-}> = ({ actionRef, taskId }) => {
+}> = ({taskId }) => {
   const { message } = App.useApp();
 
   const onConfirm = async () => {
     try {
       await runAppTask({ taskId });
       message.success('执行成功');
-      actionRef.current?.reload();
       return Promise.resolve();
     } catch (e) {
       return Promise.reject();

+ 2 - 0
src/pages/AppManage/index.tsx

@@ -70,6 +70,7 @@ export default () => {
           whitespaceForbidden,
         ],
       },
+      fixed: "left",
     },
     {
       title: 'appId',
@@ -202,6 +203,7 @@ export default () => {
           onSave,
           onDelete,
         }}
+        scroll={{x: 'max-content'}}
       />
       <AppRobotManage open={isAppRobotManageOpen} setOpen={setIsAppRobotManageOpen} appId={appId} />
       <AppSftpManage open={isAppSftpManageOpen} setOpen={setIsAppSftpManageOpen} appId={appId} />

+ 3 - 0
src/pages/RobotManage/index.tsx

@@ -40,6 +40,7 @@ export default () => {
       copyable: true,
       sorter: true,
       editable: false,
+      fixed: "left",
     },
     {
       title: '机器人名称',
@@ -54,6 +55,7 @@ export default () => {
           whitespaceForbidden,
         ],
       },
+      fixed: "left",
     },
     {
       title: '机器人密钥',
@@ -153,6 +155,7 @@ export default () => {
         onSave,
         onDelete,
       }}
+      scroll={{x: 'max-content'}}
     />
   );
 };

+ 26 - 0
src/services/api/alertConfig.ts

@@ -0,0 +1,26 @@
+// @ts-ignore
+/* eslint-disable */
+import { request } from '@umijs/max';
+
+/** 获取默认告警配置 POST /api/alertConfig/getAlertConfig */
+export async function getAlertConfig(options?: { [key: string]: any }) {
+  return request<API.RAlertConfigPo>('/api/alertConfig/getAlertConfig', {
+    method: 'POST',
+    ...(options || {}),
+  });
+}
+
+/** 更新默认告警配置 POST /api/alertConfig/updateAlertConfig */
+export async function updateAlertConfig(
+  body: API.AlertConfigDto,
+  options?: { [key: string]: any },
+) {
+  return request<API.RObject>('/api/alertConfig/updateAlertConfig', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    data: body,
+    ...(options || {}),
+  });
+}

+ 2 - 0
src/services/api/index.ts

@@ -10,6 +10,7 @@ import * as appSftp from './appSftp';
 import * as appRobotCommand from './appRobotCommand';
 import * as appRobot from './appRobot';
 import * as app from './app';
+import * as alertConfig from './alertConfig';
 export default {
   robot,
   open,
@@ -19,4 +20,5 @@ export default {
   appRobotCommand,
   appRobot,
   app,
+  alertConfig,
 };

+ 155 - 14
src/services/api/typings.d.ts

@@ -62,10 +62,44 @@ declare namespace API {
     userIds?: string;
     /** sftp id */
     sftpId: string;
-    /** 数据文件夹 */
-    dataDir: string;
+    /** 数据主文件夹 */
+    masterDir: string;
+    /** 是否有子文件夹,0否1是 */
+    hasSubdirectory: number;
+    /** 子文件夹匹配方式 */
+    subdirectoryMethod?: 'MONTH' | 'DAY' | 'REG_EXP';
+    /** 子文件夹时延 */
+    dirTimeDelay?: number;
+    /** 匹配最新子文件夹正则表达式 */
+    subdirectoryPattern?: string;
+    /** 文件是否转为文本,0否1是 */
+    fileToText: number;
+    /** 文件匹配方式 */
+    fileMethod: 'ALL' | 'MONTH' | 'DAY' | 'REG_EXP';
+    /** 文件时延 */
+    fileTimeDelay?: number;
+    /** 文件前缀 */
+    filePrefix?: string;
+    /** 文件扩展名 */
+    fileExtension?: string;
     /** 文件名匹配正则表达式 */
-    filePattern: string;
+    filePattern?: string;
+    /** 任务超时时间秒 */
+    taskTimeout: number;
+    /** 最大失败重试次数 */
+    maxRetryTimes: number;
+    /** 重试间隔秒 */
+    retryInterval: number;
+    /** 告警方式 */
+    alertType: 'DEFAULT' | 'TASK' | 'CUSTOM';
+    /** 告警机器人编码 */
+    alertRobotCode?: string;
+    /** 告警群id */
+    alertOpenConversationId?: string;
+    /** 接收告警的手机号列表 */
+    alertPhones?: string;
+    /** 接收告警的用户的userId列表 */
+    alertUserIds?: string;
   };
 
   type AddRobotDto = {
@@ -77,6 +111,33 @@ declare namespace API {
     robotName: string;
   };
 
+  type AlertConfigDto = {
+    /** 机器人编码 */
+    robotCode?: string;
+    /** 机器人密钥 */
+    robotSecret?: string;
+    /** 群id */
+    openConversationId?: string;
+    /** 接收机器人消息的用户的手机号列表 */
+    phones?: string;
+    /** 接收机器人消息的用户的userId列表 */
+    userIds?: string;
+  };
+
+  type AlertConfigPo = {
+    id?: number;
+    /** 机器人编码 */
+    robotCode?: string;
+    /** 机器人密钥 */
+    robotSecret?: string;
+    /** 群id */
+    openConversationId?: string;
+    /** 接收机器人消息的用户的手机号列表 */
+    phones?: string;
+    /** 接收机器人消息的用户的userId列表 */
+    userIds?: string;
+  };
+
   type AppTaskLogPo = {
     /** 日志id */
     id?: string;
@@ -106,18 +167,16 @@ declare namespace API {
     host?: string;
     /** sftp端口 */
     port?: number;
-    /** 数据文件夹 */
-    dataDir?: string;
-    /** 文件名匹配正则表达式 */
-    filePattern?: string;
-    /** 文件路径 */
-    filePath?: string;
+    /** 要发送的文件 */
+    files?: string;
     /** 任务执行状态,0失败1成功 */
     status?: number;
     /** 详情 */
     detail?: string;
     /** 执行时间 */
     createTime?: string;
+    /** 消息id列表 */
+    processQueryKeys?: string;
   };
 
   type BatchRecallOtoMessagesDto = {
@@ -523,10 +582,46 @@ declare namespace API {
     sftpId?: string;
     /** 主机 */
     host?: string;
-    /** 数据文件夹 */
-    dataDir?: string;
+    /** 数据主文件夹 */
+    masterDir?: string;
+    /** 是否有子文件夹,0否1是 */
+    hasSubdirectory?: number;
+    /** 子文件夹匹配方式 */
+    subdirectoryMethod?: string;
+    /** 子文件夹时延 */
+    dirTimeDelay?: number;
+    /** 匹配最新子文件夹正则表达式 */
+    subdirectoryPattern?: string;
+    /** 文件是否转为文本,0否1是 */
+    fileToText?: number;
+    /** 文件匹配方式 */
+    fileMethod?: string;
+    /** 文件时延 */
+    fileTimeDelay?: number;
+    /** 文件前缀 */
+    filePrefix?: string;
+    /** 文件扩展名 */
+    fileExtension?: string;
     /** 文件名匹配正则表达式 */
     filePattern?: string;
+    /** 任务超时时间秒 */
+    taskTimeout?: number;
+    /** 失败重试次数 */
+    maxRetryTimes?: number;
+    /** 重试间隔秒 */
+    retryInterval?: number;
+    /** 告警方式 */
+    alertType?: string;
+    /** 告警机器人名称 */
+    alertRobotName?: string;
+    /** 告警机器人编码 */
+    alertRobotCode?: string;
+    /** 告警群id */
+    alertOpenConversationId?: string;
+    /** 接收告警的手机号列表 */
+    alertPhones?: string;
+    /** 接收告警的用户的userId列表 */
+    alertUserIds?: string;
     /** 创建时间 */
     createTime?: string;
     /** 更新时间 */
@@ -670,6 +765,18 @@ declare namespace API {
     list?: ListRobotVo[];
   };
 
+  type RAlertConfigPo = {
+    /** 是否成功 */
+    success?: boolean;
+    /** 错误码 */
+    code?: number;
+    /** 提示信息 */
+    message?: string;
+    data?: AlertConfigPo;
+    /** 请求跟踪id */
+    traceId?: string;
+  };
+
   type RBatchRecallOtoMessagesVo = {
     /** 是否成功 */
     success?: boolean;
@@ -1024,10 +1131,44 @@ declare namespace API {
     userIds?: string;
     /** sftp id */
     sftpId: string;
-    /** 数据文件夹 */
-    dataDir: string;
+    /** 数据主文件夹 */
+    masterDir: string;
+    /** 是否有子文件夹,0否1是 */
+    hasSubdirectory: number;
+    /** 子文件夹匹配方式 */
+    subdirectoryMethod?: 'MONTH' | 'DAY' | 'REG_EXP';
+    /** 子文件夹时延 */
+    dirTimeDelay?: number;
+    /** 匹配最新子文件夹正则表达式 */
+    subdirectoryPattern?: string;
+    /** 文件是否转为文本,0否1是 */
+    fileToText: number;
+    /** 文件匹配方式 */
+    fileMethod: 'ALL' | 'MONTH' | 'DAY' | 'REG_EXP';
+    /** 文件时延 */
+    fileTimeDelay?: number;
+    /** 文件前缀 */
+    filePrefix?: string;
+    /** 文件扩展名 */
+    fileExtension?: string;
     /** 文件名匹配正则表达式 */
-    filePattern: string;
+    filePattern?: string;
+    /** 任务超时时间秒 */
+    taskTimeout: number;
+    /** 最大失败重试次数 */
+    maxRetryTimes: number;
+    /** 重试间隔秒 */
+    retryInterval: number;
+    /** 告警方式 */
+    alertType: 'DEFAULT' | 'TASK' | 'CUSTOM';
+    /** 告警机器人编码 */
+    alertRobotCode?: string;
+    /** 告警群id */
+    alertOpenConversationId?: string;
+    /** 接收告警的手机号列表 */
+    alertPhones?: string;
+    /** 接收告警的用户的userId列表 */
+    alertUserIds?: string;
     /** 任务id */
     taskId: string;
   };