diff --git a/.github/workflows.yml b/.github/workflows.yml deleted file mode 100644 index ff7105c..0000000 --- a/.github/workflows.yml +++ /dev/null @@ -1,60 +0,0 @@ -# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created -# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages - -# name: Publish to AliyunOSS - -# on: -# push: -# branches: -# - "deploy" - -# jobs: -# build: -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@v2 -# - uses: actions/setup-node@v2 -# with: -# node-version: 14 -# - run: yarn install -# - run: yarn run build -# - name: Upload AliyunOSS -# uses: fangbinwei/aliyun-oss-website-action@v1 -# with: -# accessKeyId: ${{ secrets.ACCESS_KEY_ID }} -# accessKeySecret: ${{ secrets.ACCESS_KEY_SECRET }} -# bucket: "yaklang-io" -# endpoint: "oss-accelerate-overseas.aliyuncs.com" -# folder: "./build" - -# 新Github-Ci -name: Yarn Build CI - -on: - push: - branches: - - 'develop' - # tags: - # - 'v*' - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@master - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version: 22.7 - - # 打包 - - name: build - run: pnpm install - - run: pnpm run build - - # 静态文件压缩 - - name: pack out file - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - run: tar -zcvf out.tgz dist diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 0000000..1212656 --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,136 @@ +name: Build Preview Frontend + +on: + push: + branches: [ main ] + +jobs: + build_and_pack_frontend: + runs-on: ubuntu-20.04 + steps: + - name: Install required packages + run: | + sudo apt-get update + sudo apt-get install -y zip unzip + + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '22.7' + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 9.9.0 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build project + run: pnpm build + + - name: Get date and commit hash + run: | + echo "DATE=$(date +'%Y%m%d')" >> $GITHUB_ENV + echo "COMMIT_HASH=${{github.sha}}" >> $GITHUB_ENV + + - name: Truncate commit hash + run: | + HASH=${{ env.COMMIT_HASH }} + TRUNCATED_HASH=${HASH:0:8} + echo "TRUNCATED_HASH=${TRUNCATED_HASH}" >> $GITHUB_ENV + + - name: Zip dist directory + run: zip -r preview-dist.zip ./dist + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: preview-dist.zip + path: ./preview-dist.zip + + + release: + needs: + - build_and_pack_frontend + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Download From Artifacts + uses: actions/download-artifact@v3 + with: + name: preview-dist.zip + + - name: Set release name + run: | + DATE=$(date +"%Y%m%d") + SHORT_COMMIT_HASH=$(git rev-parse --short HEAD) + echo "RELEASE_NAME=v${DATE}-${SHORT_COMMIT_HASH}" >> $GITHUB_ENV + + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + with: + tag_name: ${{ env.RELEASE_NAME }} + release_name: Release Preview ${{ env.RELEASE_NAME }} + draft: false + prerelease: false + + - name: Upload release asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: preview-dist.zip + asset_name: preview-dist.zip + asset_content_type: application/zip + + deploy_to_server: + needs: + - build_and_pack_frontend + runs-on: ubuntu-latest + steps: + - name: Download From Artifacts + uses: actions/download-artifact@v3 + with: + name: preview-dist.zip + + - name: Backup existing files + uses: appleboy/ssh-action@master + with: + host: legion-4g.yaklang.io + username: root + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + cd /root/ + if [ -d "preview-dist.zip" ]; then + mv preview-dist.zip preview-dist.zip.$(date +"%Y%m%d").bak + fi + + - name: Upload dist files + uses: appleboy/scp-action@master + with: + host: legion-4g.yaklang.io + username: root + key: ${{ secrets.SSH_PRIVATE_KEY }} + source: preview-dist.zip + target: /root/ + + - name: Deploy + uses: appleboy/ssh-action@master + with: + host: legion-4g.yaklang.io + username: root + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + cd /root/ + unzip -o preview-dist.zip + rm preview-dist.zip \ No newline at end of file diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml new file mode 100644 index 0000000..5d2cd53 --- /dev/null +++ b/.github/workflows/production.yml @@ -0,0 +1,55 @@ +name: Build Production Frontend + +on: + push: + tags: + - 'v*' + +jobs: + build_and_push: + runs-on: ubuntu-20.04 + + steps: + - name: Check out repository + uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '22.7' + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 9.9.0 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build project + run: pnpm build + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: v1ll4n + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Get current date + id: date + run: echo "::set-output name=date::$(date +%F)" + + - name: Build and push Docker image + uses: docker/build-push-action@v2 + with: + context: ./ + file: ./Dockerfile + # 所需要的体系结构,可以在 Available platforms 步骤中获取所有的可用架构 + # platforms: linux/amd64,linux/arm64/v8 + push: true + tags: | + v1ll4n/legion-frontend:${{ steps.date.outputs.date }} + v1ll4n/legion-frontend:latest diff --git a/.github/workflows/workflows.yml b/.github/workflows/workflows.yml deleted file mode 100644 index 0bd46ff..0000000 --- a/.github/workflows/workflows.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Build and Deploy - -on: - push: - # branches: - # - 'feat/*' - tags: - - 'v*' - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - persist-credentials: false - - # 安装 Node.js 和 pnpm - - name: Install Node.js 22.7 - run: | - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash - source ~/.nvm/nvm.sh - nvm install 22.7 - nvm use 22.7 - node -v # 验证 Node 版本 - - - name: Install pnpm 9.9 - run: | - npm install -g pnpm@9.9 - pnpm -v # 验证 pnpm 版本 - - # 安装依赖并构建项目 - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build project - run: pnpm build - - # 调试:列出 src 目录结构 - - name: List files in src directory - run: ls -R src - - # 静态文件压缩 - - name: Pack output files - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - run: tar -zcvf out.tgz dist - - - name: Upload Build Artifact - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - uses: actions/upload-artifact@v3 - with: - name: build-output - path: out.tgz - - # 清理缓存(可选步骤) - - name: Clean pnpm cache - run: pnpm store prune diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6a9104d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM v1ll4n/api-server-base-aes:latest + +COPY dist/index.html /var/www/html/index.html +COPY dist/asset-manifest.json /var/www/html/asset-manifest.json +COPY dist/static /var/www/html/static + + +COPY entrypoint.sh /entrypoint.sh + +RUN chmod +x /entrypoint.sh + +# 覆盖默认的启动命令 +ENTRYPOINT ["/entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..0e48fd2 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,25 @@ +#!/bin/sh +set -e +export DOLLAR='$' + +echo "START TO CONFIG HTTPS" +echo DISABLE_HTTPS: $DISABLE_HTTPS; +if [[ $DISABLE_HTTPS == true ]]; then + echo '***DISABLE HTTPS***'; + export HTTPS_DISABLE_COMMENT='# '; + echo '***HTTPS_DISABLE_COMMENT('$HTTPS_DISABLE_COMMENT')***'; +else + echo '***HTTPS CONFIG ENABLE***'; +fi +echo "-------------------------------------------------" + +env +echo +echo "---------------------- START TO envsubst --------------------------" +envsubst < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf +echo "envsubst FINISHED..." +echo "---------------------- /etc/nginx/conf.d/default.conf --------------------------" +cat /etc/nginx/conf.d/default.conf + + +nginx -g 'daemon off;' diff --git a/src/apis/task/index.ts b/src/apis/task/index.ts index 06971eb..a5c947b 100644 --- a/src/apis/task/index.ts +++ b/src/apis/task/index.ts @@ -137,9 +137,9 @@ const postTaskStart = ( // 任务列表 编辑回显 const getTaskStartEditDispaly = ( - id: string, -): Promise>> => - axios.get>>( + id: number, +): Promise> => + axios.get>( `/task/start/batch-invoking-script-task/fetch?id=${id}`, ); diff --git a/src/apis/task/types.ts b/src/apis/task/types.ts index 7664403..2e476db 100644 --- a/src/apis/task/types.ts +++ b/src/apis/task/types.ts @@ -90,23 +90,24 @@ type TNodeListRequest = Partial<{ type TPromptArgs = Partial<{ target: string; - 'preset-protes': string[]; + 'preset-protes': string; ports: string; 'enable-brute': boolean; 'enbale-cve-baseline': boolean; execution_node: number; plugins: string; 'scheduling-type': number; - timestamp: string[]; + timestamp: number[]; interval_seconds: string; interval_seconds_type: number; + execution_date: number; }>; type TPostTaskStartRequest = Partial<{ task_id: string; task_group: string; script_type: string; - prompt_args: TPromptArgs; + params: TPromptArgs; scanner: string[]; first: boolean; }>; diff --git a/src/pages/TaskPageList/TaskPageList.tsx b/src/pages/TaskPageList/TaskPageList.tsx index c8a7552..e834ed1 100644 --- a/src/pages/TaskPageList/TaskPageList.tsx +++ b/src/pages/TaskPageList/TaskPageList.tsx @@ -88,7 +88,16 @@ const TaskPageList: FC = () => { // 返回更新后的任务列表 return updatedSiderTaskGrounpAllList - .concat(fetchResultdata) + .concat( + fetchResultdata.filter( + (it) => it.name === '默认分组', + ), + ) + .concat( + fetchResultdata.filter( + (it) => it.name !== '默认分组', + ), + ) .filter((it) => it); }; setSiderContextList(transformTaskGroupData); @@ -224,7 +233,10 @@ const TaskPageList: FC = () => { }; }} /> - + ); }; diff --git a/src/pages/TaskPageList/compoment/CreateTaskScriptModal.tsx b/src/pages/TaskPageList/compoment/CreateTaskScriptModal.tsx index e8395e4..c0a088d 100644 --- a/src/pages/TaskPageList/compoment/CreateTaskScriptModal.tsx +++ b/src/pages/TaskPageList/compoment/CreateTaskScriptModal.tsx @@ -11,7 +11,10 @@ import { StartUpScriptModal } from '@/pages/TaskScript/compoment/StartUpScriptMo import { TGetAnalysisScriptReponse } from '@/apis/task/types'; import { getScriptTaskGroup } from '@/apis/task'; -const CreateTaskScriptModal = forwardRef(({}, ref) => { +const CreateTaskScriptModal = forwardRef< + UseModalRefType, + { pageLoad: () => void } +>(({ pageLoad }, ref) => { const [model1] = WizardModal.useModal(); const StartUpScriptModalRef = useRef(null); @@ -112,6 +115,7 @@ const CreateTaskScriptModal = forwardRef(({}, ref) => { ); diff --git a/src/pages/TaskPageList/compoment/TaskOperateTableRender.tsx b/src/pages/TaskPageList/compoment/TaskOperateTableRender.tsx index 60cfe32..ddb02cb 100644 --- a/src/pages/TaskPageList/compoment/TaskOperateTableRender.tsx +++ b/src/pages/TaskPageList/compoment/TaskOperateTableRender.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo } from 'react'; +import { FC, useMemo, useRef } from 'react'; import { Button, message, Popover } from 'antd'; import { InfoCircleOutlined } from '@ant-design/icons'; @@ -14,9 +14,18 @@ import { } from '@/apis/task/types'; import StopUsingIcon from '@/assets/task/StopUsingIcon'; -import { deleteTask, getTaskRun, getTaskStop } from '@/apis/task'; +import { + deleteTask, + getScriptTaskGroup, + getTaskRun, + getTaskStartEditDispaly, + getTaskStop, +} from '@/apis/task'; import { match, P } from 'ts-pattern'; import { UsePageRef } from '@/hooks/usePage'; +import dayjs from 'dayjs'; +import { StartUpScriptModal } from '@/pages/TaskScript/compoment/StartUpScriptModal'; +import { UseModalRefType } from '@/compoments/WizardModal/useModal'; type TCommonTasksColumnsRenderProps = { record: TaskListRequest; @@ -30,12 +39,39 @@ const PublicAndExecutionOperateRender: FC = ({ localRefrech, headerGroupValue, }) => { + const StartUpScriptModalRef = useRef(null); + const itemsRef = useRef(null); const [open, setOpen] = useSafeState({ action: false, delete: false, }); const { status } = record; + // 获取 启动脚本任务 任务组参数 + const { run: runAsyncGroup } = useRequest( + async () => { + const result = await getScriptTaskGroup(); + const { + data: { list }, + } = result; + + const resultList = list?.map((it) => ({ + value: it.name, + label: it.name, + })); + return resultList; + }, + { + manual: true, + onSuccess: async (values) => { + await StartUpScriptModalRef.current?.open( + itemsRef.current, + values, + ); + }, + }, + ); + // 执行 const { loading, runAsync } = useRequest( async (params: StopOnRunTsakResponse) => { @@ -107,7 +143,6 @@ const PublicAndExecutionOperateRender: FC = ({ }, { manual: true }, ); - const headDeleteTask = async () => { if (record.id) { await deleteRunAsync(record.id); @@ -119,15 +154,44 @@ const PublicAndExecutionOperateRender: FC = ({ } }; + // 编辑任务 const onEdit = async () => { - console.log('编辑'); + if (record?.id) { + await getTaskStartEditDispaly(record.id).then(({ data }) => { + const transformModalFormdata = { + ...data, + script_type: '端口与漏洞扫描', + params: { + ...data.params, + 'preset-protes': data?.params?.['preset-protes'] + ? (data?.params['preset-protes']) + .split(', ') + .map((item) => item.trim()) + : [], + timestamp: Array.isArray(data?.params?.timestamp) + ? [ + dayjs.unix(data?.params?.timestamp?.[0]), + dayjs.unix(data?.params?.timestamp?.[0]), + ] + : undefined, + execution_date: data?.params?.execution_date + ? `${dayjs.unix(data?.params?.execution_date)}` + : undefined, + }, + }; + itemsRef.current = transformModalFormdata; + runAsyncGroup(); + }); + } else { + message.error('未获取到当行数据ID'); + } }; const remainingOperate = useMemo(() => { return (
{''} - + @@ -175,7 +239,7 @@ const PublicAndExecutionOperateRender: FC = ({ placement="left" trigger="click" > - + @@ -183,181 +247,196 @@ const PublicAndExecutionOperateRender: FC = ({ ); }, [status, open]); - return match(status) - .with( - P.not( - P.union( - TTaskListStatus.cancel, - TTaskListStatus.disabled, - TTaskListStatus.enabled, - TTaskListStatus.failed, - TTaskListStatus.finish, - TTaskListStatus.running, - TTaskListStatus.success, - TTaskListStatus.waiting, - ), - ), - () => remainingOperate, - ) - .with(P.string, () => ( -
- {(status === 'success' || - status === 'failed' || - status === 'cancel' || - status === 'waiting') && ( - - - -
- } - title={ -
- - - 立即执行该任务? - -
- } - trigger="click" - onOpenChange={(newOpen) => - setOpen((val) => ({ ...val, action: newOpen })) - } - > -
- -
- - )} - {status === 'running' && ( - - - -
- } - title={ -
- - - 停用该任务? - -
- } - trigger="click" - onOpenChange={(newOpen) => - setOpen((val) => ({ ...val, action: newOpen })) - } - > -
- -
- - )} - - - - - - - setOpen((val) => ({ ...val, delete: newOpen })) - } - content={ -
- + +
+ } + title={ +
+ + + 立即执行该任务? + +
+ } + trigger="click" + onOpenChange={(newOpen) => setOpen((val) => ({ ...val, - delete: false, + action: newOpen, })) } > - 取消 - - + + + } + title={ +
+ + + 停用该任务? + +
+ } + trigger="click" + onOpenChange={(newOpen) => + setOpen((val) => ({ + ...val, + action: newOpen, + })) + } > - 确定 - - - } - title={ -
- - - 确认删除任务? +
+ +
+ + )} + + + + + + + setOpen((val) => ({ ...val, delete: newOpen })) + } + content={ +
+ + +
+ } + title={ +
+ + + 确认删除任务? + +
+ } + placement="left" + trigger="click" + > + + -
- } - placement="left" - trigger="click" - > - - - -
- - )) - .with(P.nullish, () => remainingOperate) - .exhaustive(); + + + )) + .with(P.nullish, () => remainingOperate) + .exhaustive()} + + + ); }; // 任务列表 周期任务操作项 @@ -372,12 +451,46 @@ const ExecutionOperateRender: FC = ({ }); const { status } = record; + // 编辑任务 + const onEdit = async () => { + if (record?.id) { + await getTaskStartEditDispaly(record.id).then(({ data }) => { + console.log(data); + // const transformModalFormdata = { + // ...data, + // script_type: '端口与漏洞扫描', + // params: { + // ...data.params, + // 'preset-protes': data?.params?.['preset-protes'] + // ? (data?.params['preset-protes']) + // .split(', ') + // .map((item) => item.trim()) + // : [], + // timestamp: Array.isArray(data?.params?.timestamp) + // ? [ + // dayjs.unix(data?.params?.timestamp?.[0]), + // dayjs.unix(data?.params?.timestamp?.[0]), + // ] + // : undefined, + // execution_date: data?.params?.execution_date + // ? `${dayjs.unix(data?.params?.execution_date)}` + // : undefined, + // }, + // }; + // itemsRef.current = transformModalFormdata; + // runAsyncGroup(); + }); + } else { + message.error('未获取到当行数据ID'); + } + }; + const executionOperateOpearte = useMemo(() => { return (
{''} {/* 编辑操作 */} - + @@ -433,126 +546,138 @@ const ExecutionOperateRender: FC = ({ ); }, [status, open]); - return match(status) - .with( - P.not( - P.union( - TTaskListStatus.cancel, - TTaskListStatus.disabled, - TTaskListStatus.enabled, - TTaskListStatus.failed, - TTaskListStatus.finish, - TTaskListStatus.running, - TTaskListStatus.success, - TTaskListStatus.waiting, - ), - ), - () => executionOperateOpearte, - ) - .with(P.string, (value) => ( -
- {(value === 'waiting' || value === 'disabled') && ( - - - -
- } - title={ -
- - - 立即执行该任务? - -
- } - trigger="click" - onOpenChange={(newOpen) => - setOpen((val) => ({ ...val, action: newOpen })) - } - > -
- -
- - )} - {value === 'enabled' && ( - - - -
- } - title={ -
- - - 结束该任务? - -
- } - trigger="click" - onOpenChange={(newOpen) => - setOpen((val) => ({ ...val, action: newOpen })) - } - > -
- -
- - )} - {value === 'finish' &&
{''}
} - - )) - .with(P.nullish, () => executionOperateOpearte) - .exhaustive(); + return ( + <> + {match(status) + .with( + P.not( + P.union( + TTaskListStatus.cancel, + TTaskListStatus.disabled, + TTaskListStatus.enabled, + TTaskListStatus.failed, + TTaskListStatus.finish, + TTaskListStatus.running, + TTaskListStatus.success, + TTaskListStatus.waiting, + ), + ), + () => executionOperateOpearte, + ) + .with(P.string, (value) => ( +
+ {(value === 'waiting' || value === 'disabled') && ( + + + +
+ } + title={ +
+ + + 立即执行该任务? + +
+ } + trigger="click" + onOpenChange={(newOpen) => + setOpen((val) => ({ + ...val, + action: newOpen, + })) + } + > +
+ +
+ + )} + {value === 'enabled' && ( + + + + + } + title={ +
+ + + 结束该任务? + +
+ } + trigger="click" + onOpenChange={(newOpen) => + setOpen((val) => ({ + ...val, + action: newOpen, + })) + } + > +
+ +
+
+ )} + {value === 'finish' && ( +
{''}
+ )} + + )) + .with(P.nullish, () => executionOperateOpearte) + .exhaustive()} + + ); }; export { PublicAndExecutionOperateRender, ExecutionOperateRender }; diff --git a/src/pages/TaskScript/TaskScript.tsx b/src/pages/TaskScript/TaskScript.tsx index ec38807..922f106 100644 --- a/src/pages/TaskScript/TaskScript.tsx +++ b/src/pages/TaskScript/TaskScript.tsx @@ -6,8 +6,9 @@ import { StartUpScriptModal } from './compoment/StartUpScriptModal'; import { TaskScriptTags } from './compoment/TaskScriptTags'; import { useRequest } from 'ahooks'; import { getAnalysisScript, getScriptTaskGroup } from '@/apis/task'; -import { EmptyBox, WizardModal } from '@/compoments'; -import { Spin } from 'antd'; +import { WizardModal } from '@/compoments'; +import { Button, Input, Spin } from 'antd'; +import { PlusOutlined, SearchOutlined } from '@ant-design/icons'; const TaskScript: FC = () => { const [model1] = WizardModal.useModal(); @@ -55,54 +56,61 @@ const TaskScript: FC = () => { return (
- {Array.isArray(scriptData) && scriptData.length > 0 ? ( - -
- {scriptData?.map((items) => { - return ( -
-
-
- {items?.script_name} -
-
- -
- -
- {items?.description && - items.description?.length > 0 - ? items.description - : '-'} -
+ +
+ } + className="w-54" + /> + +
+
+ {scriptData?.map((items) => { + return ( +
+
+
+ {items?.script_name}
+
+
+
{ - await runAsync(items); - }} + className={`text-clip2 ${styles['content-describe']}`} > - 使用模版 + {items?.description && + items.description?.length > 0 + ? items.description + : '-'}
- ); - })} -
- - ) : ( - - )} + +
{ + await runAsync(items); + }} + > + 使用模版 +
+
+ ); + })} +
+
); diff --git a/src/pages/TaskScript/compoment/AddPlugins.tsx b/src/pages/TaskScript/compoment/AddPlugins.tsx index f95d6b6..c4437b2 100644 --- a/src/pages/TaskScript/compoment/AddPlugins.tsx +++ b/src/pages/TaskScript/compoment/AddPlugins.tsx @@ -13,7 +13,7 @@ import { match, P } from 'ts-pattern'; type TAddPlugins = Partial<{ nodeCardValue: string[]; - execution_node: number; + execution_node: string; value?: Record>; onChange?: (value: Record>) => void; }>; @@ -36,16 +36,16 @@ const AddPlugins: FC = memo( // 判断 设置插件 禁用状态 const isDisabled = match([execution_node, nodeCardValue]) - .with([1, P.nullish], () => true) + .with(['1', P.nullish], () => true) .with( - [1, P.when((arr) => Array.isArray(arr) && arr.length > 0)], + ['1', P.when((arr) => Array.isArray(arr) && arr.length > 0)], () => false, ) .with( - [1, P.when((arr) => Array.isArray(arr) && arr.length <= 0)], + ['1', P.when((arr) => Array.isArray(arr) && arr.length <= 0)], () => true, ) - .with([2, P.nullish], () => false) + .with(['2', P.nullish], () => false) .with([P.nullish, P.nullish], () => true) .otherwise(() => true); @@ -173,6 +173,4 @@ const AddPlugins: FC = memo( }, ); -export default AddPlugins; - export { AddPlugins }; diff --git a/src/pages/TaskScript/compoment/CreateTaskItems.tsx b/src/pages/TaskScript/compoment/CreateTaskItems.tsx new file mode 100644 index 0000000..5937594 --- /dev/null +++ b/src/pages/TaskScript/compoment/CreateTaskItems.tsx @@ -0,0 +1,682 @@ +import { + Input, + Radio, + Select, + Checkbox, + Popover, + Switch, + Space, + DatePicker, + Form, + Button, +} from 'antd'; +import { match } from 'ts-pattern'; +import { NodeCard } from './NodeCard'; +import { AddPlugins } from './AddPlugins'; +import { generateUniqueId } from '@/utils'; +import { + PresetPorts, + presetProtsGroupOptions, + scriptTypeOptions, +} from '../data'; +import { QuestionCircleOutlined } from '@ant-design/icons'; + +import { ChunkUpload } from '@/compoments'; +import { TScannerDataList } from './StartUpScriptModal'; +import { useMemoizedFn } from 'ahooks'; + +type PresetKey = keyof typeof PresetPorts; + +const { Item } = Form; +const { RangePicker } = DatePicker; +const { Compact } = Space; + +const CreateTaskItems = ( + title: string, + scriptTypeValue: '端口与漏洞扫描' | '敏感信息', + scriptGroupList: { value: string; label: string }[], + scannerDataList?: TScannerDataList, +) => { + const schedulingTypeFn = useMemoizedFn( + (schedulingType: '1' | '2' | '3') => { + return match(schedulingType) + .with('1', () => { + return null; + }) + .with('2', () => { + return ( + 执行时间
+ } + > + + + ); + }) + .with('3', () => { + return ( + <> + + 第一次是否执行 +
+ } + name={'first'} + > + + + + + + + 执行周期 + + } + > + + + + + + + + + + + + + + ), + extra: ( + + {({ setFieldValue }) => { + return ( + + ); + }} + + ), + }, + { + key: '2', + label: '设置参数', + style: { + borderBottom: '1px solid #EAECF3', + borderRadius: '0px', + marginBottom: '8px', + }, + children: ( +
+ + + {({ setFieldValue }) => { + return ( + + {scriptTypeValue === + '端口与漏洞扫描' + ? '扫描目标' + : '关键词'} +
+ } + name={[ + 'params', + scriptTypeValue === '端口与漏洞扫描' + ? 'target' + : 'gsil_keyword', + ]} + rules={[ + { + required: true, + message: '请输入或上传扫描目标', + }, + ]} + extra={ +
+ 可将TXT、Excel文件拖入框内或 + { + setFieldValue( + [ + 'params', + scriptTypeValue === + '端口与漏洞扫描' + ? 'target' + : 'gsil_keyword', + ], + fileName, + ); + setFieldValue( + 'param_files', + generateUniqueId(), + ); + }} + > + + + 上传 +
+ } + > + { + setFieldValue( + [ + 'params', + scriptTypeValue === + '端口与漏洞扫描' + ? 'target' + : 'gsil_keyword', + ], + fileName, + ); + }} + /> +
+ ); + }} +
+ {scriptTypeValue === '端口与漏洞扫描' && ( + + {({ setFieldValue }) => { + return ( + + 预设端口 + + } + > + { + const portsValue = e + .map( + (it) => + PresetPorts[ + it as keyof typeof PresetPorts + ], + ) + .join(); + setFieldValue( + ['params', 'ports'], + portsValue, + ); + return e; + }} + /> + + ); + }} + + )} + {scriptTypeValue === '端口与漏洞扫描' && ( + + {({ setFieldValue }) => ( + + 扫描端口 + + + + + } + rules={[ + { + message: '请输入扫描端口', + required: true, + }, + ]} + className="ml-6" + > + { + const value = e.target.value; + const keys = Object.keys( + PresetPorts, + ) as PresetKey[]; + const match = keys.filter((key) => + value.includes( + PresetPorts[key], + ), + ); + + setFieldValue( + ['params', 'preset-protes'], + match, + ); + return value; + }} + /> + + )} + + )} + {scriptTypeValue === '端口与漏洞扫描' && ( + + 弱口令 + + + + + } + name={['params', 'enable-brute']} + className="ml-[48px]" + initialValue={false} + > + + + )} + {scriptTypeValue === '端口与漏洞扫描' && ( + + CVE基线检查 + + + + + } + name={['params', 'enbale-cve-baseline']} + className="ml-2" + initialValue={false} + > + + + )} + 执行节点} + initialValue={'1'} + > + + + + {({ getFieldValue, setFieldValue }) => { + const executionNodeValue = getFieldValue([ + 'params', + 'execution_node', + ]); + executionNodeValue === '2' && + setFieldValue('scanner', undefined); + return ( + executionNodeValue === '1' && + (scannerDataList && + scannerDataList?.length > 6 ? ( + + 节点选择 + + } + rules={[ + { + required: true, + message: '请选择节点', + }, + ]} + initialValue={[ + scannerDataList?.[0]?.name, + ]} + > + + + + prevValues.params?.['scheduling-type'] !== + curValues.params?.['scheduling-type'] + } + > + {({ getFieldValue }) => { + const formType = getFieldValue([ + 'params', + 'scheduling-type', + ]); + return schedulingTypeFn(formType); + }} + + + ), + }, + ]; +}; + +export { CreateTaskItems }; diff --git a/src/pages/TaskScript/compoment/NodeCard.tsx b/src/pages/TaskScript/compoment/NodeCard.tsx index db9848b..70bf9ed 100644 --- a/src/pages/TaskScript/compoment/NodeCard.tsx +++ b/src/pages/TaskScript/compoment/NodeCard.tsx @@ -12,59 +12,50 @@ const NodeCard: FC> = ({ value, onChange, list }) => { return (
{Array.isArray(list) && - list - .concat({ - name: '新建节点', - date: '新建节点', - size: 10, - }) - .map((it) => { - return ( + list.map((it) => { + return ( +
{ + // 勾选状态 + const resultValus = value?.includes(it.name) + ? value.filter((item) => item !== it.name) + : [...(value ?? []), it.name]; + // 放入form内 + onChange?.(resultValus); + }} + >
{ - // 勾选状态 - const resultValus = value?.includes(it.name) - ? value.filter( - (item) => item !== it.name, - ) - : [...(value ?? []), it.name]; - // 放入form内 - onChange?.(resultValus); - }} > -
- -
+ +
-
- {it.name} -
-
- 当前任务量 - - {' '} - {it.size} - -
-
- - {it.date}秒前活跃 -
+
+ {it.name} +
+
+ 当前任务量 + + {' '} + {it.size} + +
+
+ + {it.date}秒前活跃
- ); - })} +
+ ); + })}
); }; diff --git a/src/pages/TaskScript/compoment/StartUpScriptModal.tsx b/src/pages/TaskScript/compoment/StartUpScriptModal.tsx index 8768fb3..80c8f4a 100644 --- a/src/pages/TaskScript/compoment/StartUpScriptModal.tsx +++ b/src/pages/TaskScript/compoment/StartUpScriptModal.tsx @@ -1,39 +1,15 @@ -import { ChunkUpload, WizardModal } from '@/compoments'; +import { WizardModal } from '@/compoments'; import { UseModalRefType } from '@/compoments/WizardModal/useModal'; -import { - Button, - Space, - Collapse, - DatePicker, - Form, - Input, - Radio, - Select, - Checkbox, - Popover, - Switch, - message, -} from 'antd'; -import { CollapseProps } from 'antd/lib'; +import { Button, Collapse, Form, message } from 'antd'; import { forwardRef, useImperativeHandle } from 'react'; -import { match } from 'ts-pattern'; -import { NodeCard } from './NodeCard'; -import { AddPlugins } from './AddPlugins'; import { useRequest, useSafeState } from 'ahooks'; -import { generateUniqueId, randomString } from '@/utils'; +import { randomString } from '@/utils'; import dayjs from 'dayjs'; -import { - PresetPorts, - presetProtsGroupOptions, - scriptTypeOptions, -} from '../data'; -import { QuestionCircleOutlined } from '@ant-design/icons'; import { getNodeList, postTaskStart } from '@/apis/task'; import { TPostTaskStartRequest } from '@/apis/task/types'; - -const { Item } = Form; -const { RangePicker } = DatePicker; -const { Compact } = Space; +import { CreateTaskItems } from './CreateTaskItems'; +import { UsePageRef } from '@/hooks/usePage'; +import { transformaTimeUnit } from '../data'; export type TScannerDataList = { name?: string; @@ -41,685 +17,153 @@ export type TScannerDataList = { date: string | number; }[]; -const schedulingTypeFn = (type: 1 | 2 | 3) => { - return match(type) - .with(1, () => { - return null; - }) - .with(2, () => { - return ( - 执行时间} - > - - - ); - }) - .with(3, () => { - return ( - <> - 第一次是否执行 - } - name={'first'} - > - - - - - - 执行周期}> - - - - - - - - - - - - ), - extra: ( - - {({ setFieldValue }) => { - return ( - - ); - }} - - ), - }, - { - key: '2', - label: '设置参数', - style: { - borderBottom: '1px solid #EAECF3', - borderRadius: '0px', - marginBottom: '8px', + { + manual: true, }, - children: ( -
- - - - {({ setFieldValue }) => { - return ( - - {scriptTypeValue === '端口与漏洞扫描' - ? '扫描目标' - : '关键词'} -
- } - name={[ - 'params', - scriptTypeValue === '端口与漏洞扫描' - ? 'target' - : 'gsil_keyword', - ]} - rules={[ - { - required: true, - message: '请输入或上传扫描目标', - }, - ]} - extra={ -
- 可将TXT、Excel文件拖入框内或 - { - setFieldValue( - [ - 'params', - scriptTypeValue === - '端口与漏洞扫描' - ? 'target' - : 'gsil_keyword', - ], - fileName, - ); - setFieldValue( - ['param_files', 'value'], - generateUniqueId(), - ); - }} - > - - - 上传 -
- } - > - { - setFieldValue( - [ - 'params', - scriptTypeValue === - '端口与漏洞扫描' - ? 'target' - : 'gsil_keyword', - ], - fileName, - ); - }} - /> -
- ); - }} -
- - {scriptTypeValue === '端口与漏洞扫描' && ( - - 预设端口 - - } - > - - - )} + ); - {scriptTypeValue === '端口与漏洞扫描' && ( - - {({ getFieldValue, setFieldsValue }) => { - const presetProtesValue: - | Array - | undefined = getFieldValue([ - 'params', - 'preset-protes', - ]); - - // 确保只在 presetProtesValue 有值时才设置 - if (presetProtesValue) { - setFieldsValue({ - ['params']: { - ...getFieldValue(['params']), - ports: presetProtesValue - .map((it) => PresetPorts?.[it]) - .join(), - }, - }); - } + useImperativeHandle(ref, () => ({ + async open(items, scriptGroupList) { + await runAsync() + .then(() => { + const targetSetFormData = { + task_id: `[${items?.script_name}]-[${dayjs().format('M月DD日')}]-[${randomString(6)}]-`, + script_type: items?.script_type, + ...items, + }; + form.setFieldsValue(targetSetFormData); + setScriptGroupList(scriptGroupList); + model.open(); + }) + .catch((err) => console.error(err)); + }, + })); + + const onOk = async () => { + const values = await form.validateFields(); + + const resultData: TPostTaskStartRequest = { + ...values, + params: { + ...values.params, + end_timestamp: Array.isArray(values?.params?.timestamp) + ? `${dayjs(values?.params?.timestamp?.[0]).unix()}` + : undefined, + start_timestamp: Array.isArray(values?.params?.timestamp) + ? `${dayjs(values?.params?.timestamp?.[1]).unix()}` + : undefined, + plugins: values.params?.plugins?.ScriptName?.join(','), + execution_date: values?.params?.execution_date + ? `${dayjs(values?.params?.execution_date).unix()}` + : undefined, + 'enable-brute': `${values?.params?.['enable-brute']}`, + 'enbale-cve-baseline': `${values?.params?.['enbale-cve-baseline']}`, + 'preset-protes': values?.params?.['preset-protes'] + ? `${values?.params?.['preset-protes']?.join()}` + : undefined, + interval_seconds: + values?.params?.interval_seconds_type && + values?.params?.interval_seconds + ? transformaTimeUnit[ + values?.params?.interval_seconds_type as + | '1' + | '2' + | '3' + | '4' + ] * values?.params?.interval_seconds + : undefined, + }, + param_files: values?.param_files + ? { + target: values?.param_files, + } + : undefined, + concurrent: 20, + task_type: 'batch-invoking-script', + }; - return ( - - 扫描端口 - - - - - } - rules={[ - { - message: '请输入扫描端口', - required: true, - }, - ]} - className="ml-6" - > - - - ); + await postTaskStart(resultData) + .then(() => { + message.success(pageLoad ? '创建成功' : '编辑成功'); + pageLoad?.(); + model?.close(); + }) + .catch((err) => { + message.destroy(); + message.error(`错误: ${err}`); + }); + }; + + return ( + + + + } + width={750} + modal={model} + title={title} + onClose={() => form.resetFields()} + > +
+
+ - - - {({ getFieldValue }) => { - const formType = getFieldValue([ - 'params', - 'scheduling-type', - ]); - return schedulingTypeFn(formType); - }} - +
- ), - }, -]; - -const StartUpScriptModal = forwardRef( - ({ title }, ref) => { - const [model] = WizardModal.useModal(); - const [form] = Form.useForm(); - const scriptTypeValue = Form.useWatch('script_type', form); - - const [scriptGroupList, setScriptGroupList] = useSafeState([]); - - const { data: scannerDataList, runAsync } = useRequest( - async () => { - const result = await getNodeList(); - const { - data: { list }, - } = result; - - const targetNodeList = list?.map((it) => ({ - name: it?.node_id, - size: it?.task_running, - date: it?.updated_at - ? dayjs(new Date().getTime()).unix() - it.updated_at - : '-', - })); - return targetNodeList ?? []; - }, - { - manual: true, - }, - ); - - useImperativeHandle(ref, () => ({ - async open(items, scriptGroupList) { - await runAsync(); - const targetSetFormData = { - task_id: `[${items?.script_type}]-[${dayjs().format('M月DD日')}]-[${randomString(6)}]-`, - script_type: items?.script_type, - }; - form.setFieldsValue(targetSetFormData); - setScriptGroupList(scriptGroupList); - model.open(); - }, - })); - - const onOk = async () => { - const values = await form.validateFields(); - const resultData: TPostTaskStartRequest = { - ...values, - params: { - ...values.params, - end_timestamp: - Array.isArray(values?.prompt_agrs?.mestamp) && - dayjs(values?.prompt_agrs?.mestamp?.[0]).unix(), - start_timestamp: - Array.isArray(values?.prompt_agrs?.mestamp) && - dayjs(values?.prompt_agrs?.mestamp?.[1]).unix(), - plugins: values.params?.plugins?.ScriptName?.join(','), - execution_date: - values?.prompt_agrs?.execution_date && - dayjs(values?.prompt_agrs?.execution_date).unix(), - }, - param_files: { - ...values?.param_files, - key: 'target', - }, - concurrent: 20, - task_type: 'batch-invoking-script', - }; - await postTaskStart(resultData) - .then(() => { - message.success('创建成功'); - model?.close(); - }) - .catch((err) => { - message.destroy(); - message.error(err ?? '创建失败'); - }); - }; - - return ( - - - - - } - width={750} - modal={model} - title={title} - onClose={() => form.resetFields()} - > -
-
- - -
-
- ); - }, -); +
+ ); +}); export { StartUpScriptModal }; diff --git a/src/pages/TaskScript/data.ts b/src/pages/TaskScript/data.ts index e2b4f4a..c647042 100644 --- a/src/pages/TaskScript/data.ts +++ b/src/pages/TaskScript/data.ts @@ -43,3 +43,10 @@ export { presetProtsGroupOptions, targetColorFn, }; + +export const transformaTimeUnit = { + '1': 1, + '2': 60, + '3': 3600, + '4': 3600 * 24, +}; diff --git a/src/pages/taskList/List.tsx b/src/pages/taskList/List.tsx deleted file mode 100644 index 0d647f7..0000000 --- a/src/pages/taskList/List.tsx +++ /dev/null @@ -1,210 +0,0 @@ -import { useRef, type FC } from 'react'; -import { Button, Radio, Spin } from 'antd'; - -import { WizardTable } from '@/compoments'; -import { useRequest, useSafeState } from 'ahooks'; -import { getTaskList, getScriptTaskGroup } from '@/apis/task'; -import { PlusOutlined } from '@ant-design/icons'; - -import TaskSiderDefault from '@/assets/task/taskSiderDefault.png'; -import TaskSiderProject from '@/assets/task/taskSiderProject.png'; - -import TaskSelectdDefualt from '@/assets/task/taskSelectdDefualt.png'; -import TaskSelectdProject from '@/assets/task/taskSelectdProject.png'; - -import { commonTasksColumns } from './compoments/columns'; -import { ListSiderContext } from './compoments/ListSiderContext'; -import { TaskGrounpResponse } from '@/apis/task/types'; - -import { options, siderTaskGrounpAllList } from './utils/data'; -import { UseModalRefType } from '@/compoments/WizardModal/useModal'; -import { StartUpScriptModal as CreateTaskModal } from '../TaskScript/compoment/StartUpScriptModal'; - -const { Group } = Radio; - -const TaskList: FC = () => { - const [page] = WizardTable.usePage(); - - const openCreateTaskModalRef = useRef(null); - - const [headerGroupValue, setHeaderGroupValue] = useSafeState<1 | 2 | 3>(1); - const [siderContextList, setSiderContextList] = useSafeState< - typeof siderTaskGrounpAllList - >(siderTaskGrounpAllList); - const [taskGroupKey, setTaskGroupKey] = useSafeState('全部'); - - // 获取项目组请求 - const { loading: taskGrounpLoading, refreshAsync } = useRequest( - getScriptTaskGroup, - { - onSuccess: (value) => { - const { - data: { list }, - } = value; - const mappingDefalutIcon = ['默认分组']; - const transformTaskGroupData = () => { - // 通过 reduce 计算所有子任务的 count 之和 - const totalTaskCount = - list?.reduce((acc, it: TaskGrounpResponse) => { - return acc + (it.task_ids?.length ?? 0); - }, 0) ?? 0; - - // 将子任务映射到新的任务列表 - const fetchResultdata = list?.map( - (it: TaskGrounpResponse) => { - return { - ...it, - isEdit: false, - count: it.task_ids?.length ?? 0, - defualtIcon: mappingDefalutIcon.includes( - it.name, - ) - ? TaskSiderDefault - : TaskSiderProject, - selectdIcon: mappingDefalutIcon.includes( - it.name, - ) - ? TaskSelectdDefualt - : TaskSelectdProject, - }; - }, - ); - - // 更新全部任务的 count - const updatedSiderTaskGrounpAllList = - siderTaskGrounpAllList.map((item) => { - if (item.name === '全部') { - return { - ...item, - count: totalTaskCount, // 设置全部任务的count - }; - } - return item; - }); - - // 返回更新后的任务列表 - return updatedSiderTaskGrounpAllList - .concat(fetchResultdata) - .filter((it) => it); - }; - setSiderContextList(transformTaskGroupData); - }, - }, - ); - - // 新建分组 - const headAddGroupng = () => { - const hasEdit = siderContextList.some((it) => it.isEdit === true); - - const newItem = { - name: 'ADDINPUT', - defualtIcon: TaskSiderProject, - selectdIcon: TaskSelectdProject, - count: 0, - isEdit: true, - }; - - const targetAddSiderContextList = hasEdit - ? siderContextList - : [...siderContextList, newItem]; - - setSiderContextList(targetAddSiderContextList); - }; - - return ( -
-
- - {/* sider header */} -
-
- - 任务组管理 - -
- {siderContextList.length - 1} -
-
- -
- -
-
- - { - setHeaderGroupValue(e.target.value); - page.onLoad({ task_type: e.target.value }); - }} - /> - ), - options: { - trigger: ( - - ), - }, - }} - request={async (params, filter) => { - const { data } = await getTaskList({ - dto: { - task_type: headerGroupValue, - ...filter, - task_groups: - taskGroupKey === '全部' - ? undefined - : [taskGroupKey], - }, - pagemeta: { - ...params, - }, - }); - - return { - list: data?.list, - pagemeta: data?.pagemeta, - }; - }} - /> - console.log(1)} - /> -
- ); -}; - -export { TaskList }; diff --git a/src/pages/taskList/compoments/DeleteTaskGroupModal.tsx b/src/pages/taskList/compoments/DeleteTaskGroupModal.tsx deleted file mode 100644 index 31eb311..0000000 --- a/src/pages/taskList/compoments/DeleteTaskGroupModal.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { deleteTaskGroup } from '@/apis/task'; -import { FormInstance, Form, Radio, Select, Space } from 'antd'; -import { FC } from 'react'; - -const { Item } = Form; - -const WhetherRadio: FC<{ onChange?: (value: any) => void }> = ({ - onChange, -}) => { - return ( - onChange && onChange(e.target.value)}> - - - - - - ); -}; - -const deleteTaskGroupConfig = ( - form: FormInstance, - groupTaskList: Array>, - name: string, - refreshAsync: () => Promise, -) => { - return { - title: ( -
- 删除【 - - {name.length > 10 ? name.slice(0, 10) + '...' : name} - - 】任务组的同时是否删除组内所有任务 -
- ), - cancelText: '取消', - keyboard: false, - destroyOnClose: true, - afterClose: () => form.resetFields(), - onOk: async () => { - try { - const result = await form.validateFields(); - const targetDeleteTaskGroupResponse = { - group_name: name, - new_group_name: result.group_task, - }; - await deleteTaskGroup(targetDeleteTaskGroupResponse); - await refreshAsync(); - form.resetFields(); - } catch (err) { - console.error(err, 'err'); - return Promise.reject(); - } - }, - - content: ( -
-
- - - - - {({ getFieldValue }) => { - const whetherValue = getFieldValue('whether'); - return ( - whetherValue === 2 && ( - - - - - - - - - - - - -
- ); -}; - -export { DetailDrawer }; diff --git a/src/pages/taskList/compoments/ListSiderContext.tsx b/src/pages/taskList/compoments/ListSiderContext.tsx deleted file mode 100644 index 06323ea..0000000 --- a/src/pages/taskList/compoments/ListSiderContext.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import React, { Dispatch, FC, useRef } from 'react'; -import styles from '../task.module.scss'; - -import DeleteOutlined from '@/assets/task/DeleteOutlined'; -import FormOutlined from '@/assets/task/FormOutlined'; -import { useRequest, useSafeState, useUpdateEffect } from 'ahooks'; -import { match, P } from 'ts-pattern'; -import { Input, InputRef, message, Modal, Spin } from 'antd'; -import { postTaskGrounp } from '@/apis/task'; -import { useForm } from 'antd/es/form/Form'; -import { deleteTaskGroupConfig } from './DeleteTaskGroupModal'; - -interface TListSiderContext { - siderContextList: Array<{ - name: string; - defualtIcon: string; - selectdIcon: string; - count: number; - isEdit: boolean; - }>; - setSiderContextList: Dispatch< - React.SetStateAction - >; - refreshAsync: () => Promise; - onload: (agrs: any) => void; - taskGroupKey: string; - setTaskGroupKey: Dispatch>; -} - -const mappingKeys = ['默认分组', '全部']; - -const ListSiderContext: FC = ({ - siderContextList, - setSiderContextList, - refreshAsync, - onload, - taskGroupKey, - setTaskGroupKey, -}) => { - const [form] = useForm(); - const [modal, contextHolder] = Modal.useModal(); - - const inputRef = useRef(null); - - const [preTaskGroupName, setPreTaskGroupName] = useSafeState(); - - const { runAsync, loading } = useRequest(postTaskGrounp, { - manual: true, - onSuccess: async () => { - message.success('新建任务组成功'); - setPreTaskGroupName(undefined); - await refreshAsync(); - }, - onError: (error) => { - message.destroy(); - setPreTaskGroupName(undefined); - message.error(error.message ?? '新建任务组失败'); - setSiderContextList((values) => values.filter((it) => !it.isEdit)); - }, - }); - - // 编辑任务组 - const handTaskGroupEdit = ( - e: React.MouseEvent, - name: string, - ) => { - e.stopPropagation(); - setSiderContextList((items) => - items.map((value) => - value.name === name ? { ...value, isEdit: true } : { ...value }, - ), - ); - setPreTaskGroupName(name); - }; - - // 删除任务组 - const handTaskGroupDelete = ( - e: React.MouseEvent, - name: string, - ) => { - e.stopPropagation(); - const groupTaskList = siderContextList - .filter((it) => !mappingKeys.includes(it.name)) - .map((it) => ({ label: it.name, value: it.name })); - modal.confirm( - deleteTaskGroupConfig(form, groupTaskList, name, refreshAsync), - ); - }; - - // 确定是否存在操作项 - const operateMemoFn = (items: TListSiderContext['siderContextList'][0]) => { - return match(items.name) - .with( - P.when( - () => - items.name !== taskGroupKey && - !mappingKeys.includes(items.name), - ), - () => ( -
-
- {items.count} -
-
- - handTaskGroupEdit(e, items.name) - } - > - - - - handTaskGroupDelete(e, items.name) - } - > - - -
-
- ), - ) - .with( - P.when(() => items.name === taskGroupKey), - () => ( -
- {items.count} -
- ), - ) - .with(P.string, () => ( -
- {items.count} -
- )) - .otherwise(() => null); - }; - - // 监听是否存在新建分组 - useUpdateEffect(() => { - inputRef && inputRef.current?.focus(); - }, [siderContextList]); - - // 新建分组 回车/失去焦点 事件 - const handAddInputBlur = async () => { - const inputValue = - inputRef.current?.input?.value.replace(/\s+/g, '') ?? ''; - if (inputValue === '') { - setSiderContextList((it) => it.filter((item) => !item.isEdit)); - } else if (inputValue === '' && !preTaskGroupName) { - setSiderContextList((values) => values.filter((it) => !it.isEdit)); - } else { - const targetTaskGroup = !preTaskGroupName - ? { - group_name: inputValue!, - } - : { - group_name: preTaskGroupName, - new_group_name: inputValue, - }; - await runAsync(targetTaskGroup); - } - }; - - return ( -
- {siderContextList.map((item, key) => - item.isEdit ? ( - loading ? ( - - - - ) : ( - - ) - ) : ( -
{ - onload({ task_groups: [item.name] }); - setTaskGroupKey(item.name); - }} - > -
-
- icon -
- {item.name} -
-
- - {operateMemoFn(item)} -
-
- ), - )} - {contextHolder} -
- ); -}; - -export { ListSiderContext }; diff --git a/src/pages/taskList/compoments/TaskOperateTableRender.tsx b/src/pages/taskList/compoments/TaskOperateTableRender.tsx deleted file mode 100644 index ed80f4f..0000000 --- a/src/pages/taskList/compoments/TaskOperateTableRender.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { FC } from 'react'; -import { Button, message, Popover } from 'antd'; -import { InfoCircleOutlined } from '@ant-design/icons'; - -import { useSafeState } from 'ahooks'; - -import PlayCircleOutlined from '@/assets/task/TablePlayCircleOutlined'; -import TableFormOutlined from '@/assets/task/TableFormOutlined'; -import TableDeleteOutlined from '@/assets/task/TableDeleteOutlined'; -import { TaskListRequest } from '@/apis/task/types'; - -import StopUsingIcon from '@/assets/task/StopUsingIcon'; -import { postTaskRun } from '@/apis/task'; - -type TCommonTasksColumnsRenderProps = { - record: TaskListRequest; - refresh: () => void; -}; - -// 任务列表 普通任务 / 定时任务操作项 -const PublicAndExecutionOperateRender: FC = ({ - record, - refresh, -}) => { - const [open, setOpen] = useSafeState(false); - const { status } = record; - // 确定执行操作 - const headImplement = async () => { - if (record.task_id) { - await postTaskRun(record?.task_id); - await refresh(); - message.success('执行成功'); - } else { - message.error('未获取到当前任务ID'); - } - setOpen(false); - }; - - return ( - // taskListStatus.map(it => it.value)?.includes(status ?? "") ? -
- {status === 'success' ? ( - setOpen(open)} - content={ -
- - -
- } - title={ -
- - - 立即执行该任务? - -
- } - trigger="click" - > -
- -
-
- ) : ( - setOpen(open)} - content={ -
- - -
- } - title={ -
- - 停用该任务? -
- } - trigger="click" - > -
- -
-
- )} - - - - - - -
- // : null - ); -}; - -export { PublicAndExecutionOperateRender }; diff --git a/src/pages/taskList/compoments/TaskStatus.tsx b/src/pages/taskList/compoments/TaskStatus.tsx deleted file mode 100644 index ca28e25..0000000 --- a/src/pages/taskList/compoments/TaskStatus.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { TTaskListStatus } from '@/apis/task/types'; -import { InfoCircleOutlined, LoadingOutlined } from '@ant-design/icons'; -import { Tag } from 'antd'; -import { match, P } from 'ts-pattern'; - -type TTaskListStatusType = `${TTaskListStatus}`; // 提取枚举值的联合类型 - -const TaskStatus = (status?: TTaskListStatusType) => - match(status) - .with('failed', () => ( - - 失败 - - )) - .with('cancel', () => ( - - 取消 - - )) - .with('running', () => ( - - 执行中 - - )) - .with('success', () => ( - - 成功 - - )) - .with('waiting', () => ( - - 未开始 - - )) - .with('disabled', () => ( - - 停用 - - )) - .with('enabled', () => ( - - 启用 - - )) - .with('finish', () => ( - - 结束 - - )) - .with(P.string, () => '-') - .with(P.nullish, () => '-') - // TODO 此处 key 可能没有,需要后端调整,最后将 run 方法修改为 exhaustive 方法 确保安全性 - .exhaustive(); -export { TaskStatus }; diff --git a/src/pages/taskList/compoments/columns.tsx b/src/pages/taskList/compoments/columns.tsx deleted file mode 100644 index 1d141e9..0000000 --- a/src/pages/taskList/compoments/columns.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { CreateTableProps } from '@/compoments/WizardTable/types'; -import { TaskStatus } from './TaskStatus'; -import dayjs from 'dayjs'; - -import { PublicAndExecutionOperateRender } from './TaskOperateTableRender'; -import { TaskListRequest } from '@/apis/task/types'; -import { match } from 'ts-pattern'; -import { useNavigate } from 'react-router-dom'; -import { useRequest } from 'ahooks'; -import { getBatchInvokingScriptTaskNode } from '@/apis/task'; -import { taskListStatus } from '../utils/data'; - -type TColumns = { - columnsRender: CreateTableProps['columns']; -}; - -// 任务列表 columns -const commonTasksColumns = ( - headerGroupValue: 1 | 2 | 3, - refresh: () => void, -): CreateTableProps['columns'] => { - const { data: taskNodeData } = useRequest(async () => { - const result = await getBatchInvokingScriptTaskNode(); - const { - data: { list }, - } = result; - const resultData = list.map((it) => ({ label: it, value: it })); - return resultData; - }); - - const navigate = useNavigate(); - // 前往详情页面 - const handGoDetail = (record: TaskListRequest) => { - navigate(`detail/${record.id}`); - }; - - // 任务列表可通用的 cloumns 字段 - const columns: CreateTableProps['columns'] = [ - { - title: '任务名称', - dataIndex: 'task_name', - columnsHeaderFilterType: 'input', - width: 240, - render: (_, record) => ( -
handGoDetail(record)} - > - {record.task_id} -
- ), - }, - { - title: '任务组', - dataIndex: 'task_groups', - width: 240, - render: (_, record) => ( -
{record.task_group}
- ), - }, - { - title: '执行节点', - dataIndex: 'node_ids', - columnsHeaderFilterType: 'checkbox', - wizardColumnsOptions: taskNodeData ?? [], - width: 240, - render: (_, record) => ( -
{record?.scanner?.join('、')}
- ), - }, - ]; - - // 普通任务 时间字段 - const publicTaskTime: TColumns['columnsRender'] = [ - { - title: '创建时间', - width: 240, - dataIndex: 'created_at', - render: (value) => dayjs(value).format('YYYY-MM-DD HH:mm:ss'), - }, - ]; - - // 普通任务 任务状态 - const publicAndExecutionTaskStatus: TColumns['columnsRender'] = [ - { - title: '任务状态', - dataIndex: 'task_status', - width: 240, - columnsHeaderFilterType: 'checkbox', - wizardColumnsOptions: taskListStatus.filter( - (it) => !['disabled', 'cancel', 'enabled'].includes(it.value), - ), - render: (_, record) => TaskStatus(record?.status), - }, - ]; - - // 普通任务 操作项 - const publicAndExecutionOperateRender: TColumns['columnsRender'] = [ - { - title: '操作', - dataIndex: 'id', - fixed: 'right', - width: 140, - render: (_, record) => ( - - ), - }, - ]; - - // 定时任务 时间字段 - const executionTime: TColumns['columnsRender'] = [ - { - title: '执行时间', - width: 240, - dataIndex: 'updated_at', - render: (value) => dayjs(value).format('YYYY-MM-DD HH:mm:ss'), - }, - ]; - - // 周期任务 执行时间段 - const executionTimePeriod: TColumns['columnsRender'] = [ - { - title: '执行时间段', - width: 240, - dataIndex: 'created_at', - render: (value) => dayjs(value).format('YYYY-MM-DD HH:mm:ss'), - }, - ]; - - // 周期任务 任务状态 - const executionTaskStatus: TColumns['columnsRender'] = [ - { - title: '任务状态', - dataIndex: 'task_status', - width: 240, - columnsHeaderFilterType: 'checkbox', - wizardColumnsOptions: taskListStatus.filter((it) => - ['disabled', 'waiting', 'finish', 'enabled'].includes(it.value), - ), - render: (_, record) => TaskStatus(record?.status), - }, - ]; - - return match(headerGroupValue) - .with(1, () => - [ - ...columns, - ...publicAndExecutionTaskStatus, - ...publicTaskTime, - ].concat(publicAndExecutionOperateRender), - ) - .with(2, () => - [ - ...columns, - ...publicAndExecutionTaskStatus, - ...executionTime, - ].concat(publicAndExecutionOperateRender), - ) - .with(3, () => - [...columns, ...executionTaskStatus, ...executionTimePeriod].concat( - publicAndExecutionOperateRender, - ), - ) - .exhaustive(); -}; - -export { commonTasksColumns }; diff --git a/src/pages/taskList/index.ts b/src/pages/taskList/index.ts deleted file mode 100644 index b03aa1d..0000000 --- a/src/pages/taskList/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { TaskList } from './List'; - -export default TaskList; diff --git a/src/pages/taskList/task.module.scss b/src/pages/taskList/task.module.scss deleted file mode 100644 index 00335bc..0000000 --- a/src/pages/taskList/task.module.scss +++ /dev/null @@ -1,51 +0,0 @@ -.tools-list-wrapper { - overflow-y: overlay; - flex: 1; - .tools-list-item { - display: flex; - align-items: center; - width: 100%; - padding: 10px 8px; - border-top: 1px solid #EAECF3; - transition: all 0.3s ease; /* 添加动画 */ - min-height: 49px; - .tools-list-operate { - display: none; - transition: display 0.3s ease; /* 可选:如果动画库支持 display 的过渡 */ - } - - &:last-child { - border-bottom: 1px solid #EAECF3; - } - - &:hover { - border-color: transparent; - border-radius: 8px; - background-color: #f8f8f8; - - & + .tools-list-item { - border-top-color: transparent; - } - - .tools-list-count { - display: none; - } - - .tools-list-operate { - display: block; /* 显示时的过渡效果 */ - } - } - } - - .tools-list-click { - border-color: transparent; - border-radius: 8px; - background-color: #4A94F8 !important; - color: #fff; - transition: all 0.3s ease; /* 点击时添加过渡效果 */ - - & + .tools-list-item { - border-top-color: transparent; - } - } -} \ No newline at end of file diff --git a/src/pages/taskList/utils/data.ts b/src/pages/taskList/utils/data.ts deleted file mode 100644 index c5479f0..0000000 --- a/src/pages/taskList/utils/data.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { TTaskListStatus } from '@/apis/task/types'; -import TaskSelectdAll from '@/assets/task/taskSelectdAll.png'; -import TaskSiderAll from '@/assets/task/taskSiderAll.png'; - -type TTaskListStatusType = `${TTaskListStatus}`; // 提取枚举值的联合类型 - -const options = [ - { - label: '普通任务', - value: 1, - }, - { - label: '定时任务', - value: 2, - }, - { - label: '周期任务', - value: 3, - }, -]; - -const siderTaskGrounpAllList = [ - { - name: '全部', - defualtIcon: TaskSiderAll, - selectdIcon: TaskSelectdAll, - count: 0, - isEdit: false, - }, -]; - -// 定义表格头筛选状态 -const taskListStatus: Array<{ - value: TTaskListStatusType; - label: string; -}> = [ - { - value: 'success', - label: '成功', - }, - { - value: 'failed', - label: '失败', - }, - { - value: 'running', - label: '执行中', - }, - { - value: 'waiting', - label: '未开始', - }, - { - value: 'enabled', - label: '启用', - }, - { - value: 'disabled', - label: '停用', - }, - { - value: 'cancel', - label: '取消', - }, - { - value: 'finish', - label: '结束', - }, -]; - -export { options, siderTaskGrounpAllList, taskListStatus };