mirror of
https://github.com/xhongc/music-tag-web.git
synced 2026-02-02 17:59:07 +08:00
feature: 子主流程嵌套的子流程也要创建执行历史
This commit is contained in:
@@ -2,8 +2,13 @@
|
||||

|
||||
|
||||
## todo list
|
||||
- [ ] 子流程状态显示
|
||||
- [ ] 主流程嵌套的子流程也要创建执行历史
|
||||
- [x] 子流程状态显示
|
||||
- [x] 主流程嵌套的子流程也要创建执行历史
|
||||
- [ ] 任务管理
|
||||
- [ ] 定时任务和周期任务
|
||||
- [ ] 暂停,停止,跳过等人工干预操作
|
||||
- [ ] 节点重试功能
|
||||
-
|
||||
## install tips
|
||||
sudo apt-get install libmysqlclient-dev
|
||||
python3-dev
|
||||
@@ -0,0 +1,62 @@
|
||||
# Generated by Django 2.2.6 on 2022-06-16 16:14
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django_mysql.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flow', '0004_auto_20220226_1202'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SubProcessRun',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='作业名称')),
|
||||
('description', models.CharField(blank=True, max_length=255, null=True, verbose_name='作业描述')),
|
||||
('run_type', models.CharField(max_length=32, verbose_name='调度类型')),
|
||||
('gateways', django_mysql.models.JSONField(default=dict, verbose_name='网关信息')),
|
||||
('constants', django_mysql.models.JSONField(default=dict, verbose_name='内部变量信息')),
|
||||
('dag', django_mysql.models.JSONField(default=dict, verbose_name='DAG')),
|
||||
('create_by', models.CharField(max_length=64, null=True, verbose_name='创建者')),
|
||||
('create_time', models.DateTimeField(default=datetime.datetime.now, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
||||
('update_by', models.CharField(max_length=64, null=True, verbose_name='修改人')),
|
||||
('root_id', models.CharField(max_length=255, verbose_name='根节点uuid')),
|
||||
('process', models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sub_run', to='flow.Process')),
|
||||
('process_run', models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sub', to='flow.Process')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SubNodeRun',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='节点名称')),
|
||||
('uuid', models.CharField(max_length=255, unique=True, verbose_name='UUID')),
|
||||
('description', models.CharField(blank=True, max_length=255, null=True, verbose_name='节点描述')),
|
||||
('show', models.BooleanField(default=True, verbose_name='是否显示')),
|
||||
('top', models.IntegerField(default=300)),
|
||||
('left', models.IntegerField(default=300)),
|
||||
('ico', models.CharField(blank=True, max_length=64, null=True, verbose_name='icon')),
|
||||
('fail_retry_count', models.IntegerField(default=0, verbose_name='失败重试次数')),
|
||||
('fail_offset', models.IntegerField(default=0, verbose_name='失败重试间隔')),
|
||||
('fail_offset_unit', models.CharField(choices=[('seconds', '秒'), ('hours', '时'), ('minutes', '分')], max_length=32, verbose_name='重试间隔单位')),
|
||||
('node_type', models.IntegerField(default=2)),
|
||||
('component_code', models.CharField(max_length=255, verbose_name='插件名称')),
|
||||
('is_skip_fail', models.BooleanField(default=False, verbose_name='忽略失败')),
|
||||
('is_timeout_alarm', models.BooleanField(default=False, verbose_name='超时告警')),
|
||||
('inputs', django_mysql.models.JSONField(default=dict, verbose_name='输入参数')),
|
||||
('outputs', django_mysql.models.JSONField(default=dict, verbose_name='输出参数')),
|
||||
('content', models.IntegerField(default=0, verbose_name='模板id')),
|
||||
('subprocess_run', models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sub_nodes_run', to='flow.SubProcessRun')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
24
applications/flow/migrations/0006_auto_20220616_1616.py
Normal file
24
applications/flow/migrations/0006_auto_20220616_1616.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 2.2.6 on 2022-06-16 16:16
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flow', '0005_subnoderun_subprocessrun'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='noderun',
|
||||
name='process_run',
|
||||
field=models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='nodes_run', to='flow.ProcessRun'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subnoderun',
|
||||
name='subprocess_run',
|
||||
field=models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sub_nodes_run', to='flow.SubProcessRun'),
|
||||
),
|
||||
]
|
||||
@@ -98,8 +98,37 @@ class ProcessRun(models.Model):
|
||||
root_id = models.CharField("根节点uuid", max_length=255)
|
||||
|
||||
|
||||
class SubProcessRun(models.Model):
|
||||
process_run = models.ForeignKey(Process, on_delete=models.CASCADE, null=True, db_constraint=False,
|
||||
related_name="sub")
|
||||
process = models.ForeignKey(Process, on_delete=models.SET_NULL, null=True, db_constraint=False,
|
||||
related_name="sub_run")
|
||||
name = models.CharField("作业名称", max_length=255, blank=False, null=False)
|
||||
description = models.CharField("作业描述", max_length=255, blank=True, null=True)
|
||||
run_type = models.CharField("调度类型", max_length=32)
|
||||
gateways = JSONField("网关信息", default=dict)
|
||||
constants = JSONField("内部变量信息", default=dict)
|
||||
dag = JSONField("DAG", default=dict)
|
||||
|
||||
create_by = models.CharField("创建者", max_length=64, null=True)
|
||||
create_time = models.DateTimeField("创建时间", default=datetime.now)
|
||||
update_time = models.DateTimeField("修改时间", auto_now=True)
|
||||
update_by = models.CharField("修改人", max_length=64, null=True)
|
||||
|
||||
root_id = models.CharField("根节点uuid", max_length=255)
|
||||
|
||||
|
||||
class SubNodeRun(BaseNode):
|
||||
subprocess_run = models.ForeignKey(SubProcessRun, on_delete=models.CASCADE, null=True, db_constraint=False,
|
||||
related_name="sub_nodes_run")
|
||||
|
||||
@staticmethod
|
||||
def field_names():
|
||||
return [field.name for field in NodeRun._meta.get_fields() if field.name not in ["id"]]
|
||||
|
||||
|
||||
class NodeRun(BaseNode):
|
||||
process_run = models.ForeignKey(ProcessRun, on_delete=models.SET_NULL, null=True, db_constraint=False,
|
||||
process_run = models.ForeignKey(ProcessRun, on_delete=models.CASCADE, null=True, db_constraint=False,
|
||||
related_name="nodes_run")
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -8,7 +8,7 @@ from pipeline.eri.runtime import BambooDjangoRuntime
|
||||
from rest_framework import serializers
|
||||
|
||||
from applications.flow.constants import PIPELINE_STATE_TO_FLOW_STATE
|
||||
from applications.flow.models import Process, Node, ProcessRun, NodeRun, NodeTemplate
|
||||
from applications.flow.models import Process, Node, ProcessRun, NodeRun, NodeTemplate, SubProcessRun, SubNodeRun
|
||||
from applications.utils.uuid_helper import get_uuid
|
||||
|
||||
|
||||
@@ -161,6 +161,23 @@ class ListProcessRunViewSetsSerializer(serializers.ModelSerializer):
|
||||
return process_state
|
||||
|
||||
|
||||
class ListSubProcessRunViewSetsSerializer(serializers.ModelSerializer):
|
||||
state = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = SubProcessRun
|
||||
fields = "__all__"
|
||||
|
||||
def get_state(self, obj):
|
||||
runtime = BambooDjangoRuntime()
|
||||
process_info = api.get_pipeline_states(runtime, root_id=obj.root_id)
|
||||
try:
|
||||
process_state = PIPELINE_STATE_TO_FLOW_STATE.get(process_info.data[obj.root_id]["state"])
|
||||
except Exception:
|
||||
process_state = "error"
|
||||
return process_state
|
||||
|
||||
|
||||
class RetrieveProcessViewSetsSerializer(serializers.ModelSerializer):
|
||||
pipeline_tree = serializers.SerializerMethodField()
|
||||
|
||||
@@ -239,7 +256,7 @@ class RetrieveProcessRunViewSetsSerializer(serializers.ModelSerializer):
|
||||
outputs = output_data.data.get("outputs", "")
|
||||
if node["node_type"] == 3:
|
||||
# todo先简单判断node有fail,process就为fail
|
||||
if State.objects.filter(parent_id=node["uuid"],name="FAILED").exists():
|
||||
if State.objects.filter(parent_id=node["uuid"], name="FAILED").exists():
|
||||
flow_state = "fail"
|
||||
# todo先简单判断node有fail,process就为fail
|
||||
if flow_state == "fail":
|
||||
@@ -271,6 +288,65 @@ class RetrieveProcessRunViewSetsSerializer(serializers.ModelSerializer):
|
||||
fields = ("id", "name", "description", "run_type", "pipeline_tree")
|
||||
|
||||
|
||||
class RetrieveSubProcessRunViewSetsSerializer(serializers.ModelSerializer):
|
||||
pipeline_tree = serializers.SerializerMethodField()
|
||||
|
||||
def get_pipeline_tree(self, obj):
|
||||
lines = []
|
||||
nodes = []
|
||||
for _from, to_list in obj.dag.items():
|
||||
for _to in to_list:
|
||||
lines.append({
|
||||
"from": _from,
|
||||
"to": _to
|
||||
})
|
||||
runtime = BambooDjangoRuntime()
|
||||
process_info = api.get_pipeline_states(runtime, root_id=obj.root_id)
|
||||
process_state = PIPELINE_STATE_TO_FLOW_STATE.get(process_info.data[obj.root_id]["state"])
|
||||
state_map = process_info.data[obj.root_id]["children"]
|
||||
node_list = SubNodeRun.objects.filter(subprocess_run_id=obj.id).values()
|
||||
for node in node_list:
|
||||
pipeline_state = state_map.get(node["uuid"], {}).get("state", "READY")
|
||||
flow_state = PIPELINE_STATE_TO_FLOW_STATE[pipeline_state]
|
||||
outputs = ""
|
||||
# print(flow_state)
|
||||
if node["node_type"] not in [0, 1] and flow_state not in ["wait"]:
|
||||
output_data = api.get_execution_data_outputs(runtime, node_id=node["uuid"])
|
||||
outputs = output_data.data.get("outputs", "")
|
||||
if node["node_type"] == 3:
|
||||
# todo先简单判断node有fail,process就为fail
|
||||
if State.objects.filter(parent_id=node["uuid"], name="FAILED").exists():
|
||||
flow_state = "fail"
|
||||
# todo先简单判断node有fail,process就为fail
|
||||
if flow_state == "fail":
|
||||
process_state = "fail"
|
||||
nodes.append({"show": node["show"],
|
||||
"top": node["top"],
|
||||
"left": node["left"],
|
||||
"ico": node["ico"],
|
||||
"type": node["node_type"],
|
||||
"name": node["name"],
|
||||
"state": flow_state,
|
||||
"content": node["content"],
|
||||
"node_data": {
|
||||
"inputs": node["inputs"],
|
||||
"outputs": outputs,
|
||||
"run_mark": 0,
|
||||
"node_name": node["name"],
|
||||
"description": node["description"],
|
||||
"fail_retry_count": node["fail_retry_count"],
|
||||
"fail_offset": node["fail_offset"],
|
||||
"fail_offset_unit": node["fail_offset_unit"],
|
||||
"is_skip_fail": node["is_skip_fail"],
|
||||
"is_timeout_alarm": node["is_timeout_alarm"]},
|
||||
"uuid": node["uuid"]})
|
||||
return {"lines": lines, "nodes": nodes, "process_state": process_state}
|
||||
|
||||
class Meta:
|
||||
model = SubProcessRun
|
||||
fields = ("id", "name", "description", "run_type", "pipeline_tree")
|
||||
|
||||
|
||||
class ExecuteProcessSerializer(serializers.Serializer):
|
||||
process_id = serializers.IntegerField(required=True)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from . import views
|
||||
flow_router = DefaultRouter()
|
||||
flow_router.register(r"flow", viewset=views.ProcessViewSets, base_name="flow")
|
||||
flow_router.register(r"run", viewset=views.ProcessRunViewSets, base_name="run")
|
||||
flow_router.register(r"sub_run", viewset=views.SubProcessRunViewSets, base_name="sub_run")
|
||||
flow_router.register(r"test", viewset=views.TestViewSets, base_name="test")
|
||||
|
||||
node_router = DefaultRouter()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from applications.flow.models import ProcessRun, NodeRun, Process
|
||||
from applications.flow.models import ProcessRun, NodeRun, Process, Node, SubProcessRun, SubNodeRun
|
||||
from applications.utils.dag_helper import PipelineBuilder, instance_dag
|
||||
|
||||
|
||||
def build_and_create_process(process_id):
|
||||
"""构建pipeline和创建运行时数据"""
|
||||
p_builder = PipelineBuilder(process_id)
|
||||
pipeline = p_builder.build()
|
||||
|
||||
@@ -19,5 +20,24 @@ def build_and_create_process(process_id):
|
||||
_node = {k: v for k, v in node.__dict__.items() if k in NodeRun.field_names()}
|
||||
_node["uuid"] = process_run_uuid[pipeline_id].id
|
||||
node_run_bulk.append(NodeRun(process_run=process_run, **_node))
|
||||
if node.node_type == Node.SUB_PROCESS_NODE:
|
||||
create_subprocess(node.content, process_run.id, process_run_uuid, pipeline["id"])
|
||||
NodeRun.objects.bulk_create(node_run_bulk, batch_size=500)
|
||||
return pipeline
|
||||
|
||||
|
||||
def create_subprocess(process_id, process_run_id, process_run_uuid, root_id):
|
||||
process = Process.objects.filter(id=process_id).first()
|
||||
process_run_data = process.clone_data
|
||||
process_run_data["dag"] = instance_dag(process_run_data["dag"], process_run_uuid)
|
||||
process_run = SubProcessRun.objects.create(process_id=process_id, process_run_id=process_run_id, root_id=root_id,
|
||||
**process_run_data)
|
||||
subprocess_node_map = Node.objects.filter(process_id=process_id).in_bulk(field_name="uuid")
|
||||
node_run_bulk = []
|
||||
for pipeline_id, node in subprocess_node_map.items():
|
||||
_node = {k: v for k, v in node.__dict__.items() if k in NodeRun.field_names()}
|
||||
_node["uuid"] = process_run_uuid[pipeline_id].id
|
||||
node_run_bulk.append(SubNodeRun(subprocess_run=process_run, **_node))
|
||||
if node.node_type == Node.SUB_PROCESS_NODE:
|
||||
create_subprocess(node.content, process_run_id, process_run_uuid, root_id)
|
||||
SubNodeRun.objects.bulk_create(node_run_bulk, batch_size=500)
|
||||
|
||||
@@ -12,10 +12,11 @@ from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from applications.flow.filters import NodeTemplateFilter
|
||||
from applications.flow.models import Process, Node, ProcessRun, NodeRun, NodeTemplate
|
||||
from applications.flow.models import Process, Node, ProcessRun, NodeRun, NodeTemplate, SubProcessRun
|
||||
from applications.flow.serializers import ProcessViewSetsSerializer, ListProcessViewSetsSerializer, \
|
||||
RetrieveProcessViewSetsSerializer, ExecuteProcessSerializer, ListProcessRunViewSetsSerializer, \
|
||||
RetrieveProcessRunViewSetsSerializer, NodeTemplateSerializer
|
||||
RetrieveProcessRunViewSetsSerializer, NodeTemplateSerializer, ListSubProcessRunViewSetsSerializer, \
|
||||
RetrieveSubProcessRunViewSetsSerializer
|
||||
from applications.utils.dag_helper import DAG, instance_dag, PipelineBuilder
|
||||
from component.drf.viewsets import GenericViewSet
|
||||
|
||||
@@ -45,7 +46,7 @@ class ProcessViewSets(mixins.ListModelMixin,
|
||||
# 执行
|
||||
runtime = BambooDjangoRuntime()
|
||||
api.run_pipeline(runtime=runtime, pipeline=pipeline)
|
||||
|
||||
|
||||
Process.objects.filter(id=process_id).update(total_run_count=F("total_run_count") + 1)
|
||||
|
||||
return Response({})
|
||||
@@ -65,6 +66,18 @@ class ProcessRunViewSets(mixins.ListModelMixin,
|
||||
return ExecuteProcessSerializer
|
||||
|
||||
|
||||
class SubProcessRunViewSets(mixins.ListModelMixin,
|
||||
mixins.RetrieveModelMixin,
|
||||
GenericViewSet):
|
||||
queryset = SubProcessRun.objects.order_by("-update_time")
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == "list":
|
||||
return ListSubProcessRunViewSetsSerializer
|
||||
elif self.action == "retrieve":
|
||||
return RetrieveSubProcessRunViewSetsSerializer
|
||||
|
||||
|
||||
class TestViewSets(GenericViewSet):
|
||||
def list(self, request, *args, **kwargs):
|
||||
random_list = [1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
|
||||
|
||||
@@ -212,6 +212,7 @@ class PipelineBuilder:
|
||||
self.instance = self.setup_instance()
|
||||
|
||||
def setup_instance(self):
|
||||
"""将节点转换成bamboo实例"""
|
||||
pipeline_instance = {}
|
||||
for p_id, node in self.node_map.items():
|
||||
if node.node_type == Node.START_NODE:
|
||||
@@ -244,6 +245,8 @@ class PipelineBuilder:
|
||||
p_builder = PipelineBuilder(process_id)
|
||||
pipeline = p_builder.build(is_subprocess=True)
|
||||
pipeline_instance[p_id] = pipeline
|
||||
# 子流程的pid一并加入pipeline_instance
|
||||
pipeline_instance.update(p_builder.instance)
|
||||
else:
|
||||
act = ServiceActivity(component_code="http_request")
|
||||
act.component.inputs.inputs = Var(type=Var.PLAIN, value=node.inputs)
|
||||
|
||||
@@ -2,10 +2,10 @@ import {GET, POST, reUrl} from '../../axiosconfig/axiosconfig'
|
||||
|
||||
export default {
|
||||
list: function(params) {
|
||||
return GET(reUrl + '/node_run/', params)
|
||||
return GET(reUrl + '/process/sub_run/', params)
|
||||
},
|
||||
retrieve: function(id, params) {
|
||||
return GET(reUrl + '/node_run/' + JSON.stringify(id) + '/', params)
|
||||
return GET(reUrl + '/process/sub_run/' + JSON.stringify(id) + '/', params)
|
||||
},
|
||||
control: function(params) {
|
||||
return POST(reUrl + '/node_run/control/', params)
|
||||
|
||||
@@ -1,168 +1,704 @@
|
||||
<template>
|
||||
<div id="jobDetail" v-bkloading="{ isLoading: jobDetailLoading, zIndex: 10 }">
|
||||
<div id="jobFlowViewDetail" v-bkloading="{ isLoading: formLoading, zIndex: 999999 }">
|
||||
<div class="box">
|
||||
<p class="title">基本信息</p>
|
||||
<bk-container>
|
||||
<bk-form :label-width="130">
|
||||
<bk-row>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="作业名称:">{{form.name}}</bk-form-item>
|
||||
<bk-form-item label="作业流名称:">{{ form.name }}</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="作业状态:">
|
||||
<span v-if="form.state !== ''">{{stateList[stateList.findIndex(e => e.name === form.state)].label}}</span>
|
||||
<bk-form-item label="作业流状态:"><span
|
||||
v-if="form.hasOwnProperty('state')">{{ stateList[stateList.findIndex(e => e.name === form.state)].label }}</span>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="启动人:">{{form.executor}}</bk-form-item>
|
||||
<bk-form-item label="启动人:">{{ form.executor }}</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="Agent:">{{form.station}}</bk-form-item>
|
||||
<bk-form-item label="跑批系统:">{{ form.category_name }}</bk-form-item>
|
||||
</bk-col>
|
||||
</bk-row>
|
||||
<bk-row>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="计划开始时间:">{{form.eta}}</bk-form-item>
|
||||
<bk-form-item label="计划开始时间:">{{ form.eta }}</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="实际开始时间:">{{form.start_time}}</bk-form-item>
|
||||
<bk-form-item label="实际开始时间:">{{ form.start_time }}</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="完成时间:">{{form.end_time}}</bk-form-item>
|
||||
<bk-form-item label="完成时间:">{{ form.end_time }}</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="总共耗时:">{{form.total_time}}</bk-form-item>
|
||||
<bk-form-item label="总共耗时:">{{ form.total_time }}</bk-form-item>
|
||||
</bk-col>
|
||||
</bk-row>
|
||||
</bk-form>
|
||||
</bk-container>
|
||||
</div>
|
||||
<div class="box">
|
||||
<p class="title">执行日志</p>
|
||||
<editor :height="'200px'" ref="editorLog" :codes="form.log" :read-only="true" :language="'shell'"></editor>
|
||||
<!-- <bk-input :type="'textarea'" :rows="10" ext-cls="custom-textarea" v-model="form.log" :disabled="true"></bk-input> -->
|
||||
<p class="title">执行详情</p>
|
||||
<div id="content" v-bkloading="{ isLoading: mainLoading, zIndex: 999 }">
|
||||
<div class="left-statusList">
|
||||
<statusList style="position: absolute;left: 20px;top: 15px;"></statusList>
|
||||
</div>
|
||||
<div class="right-canvas">
|
||||
<div id="main" ref="main"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<p class="title">执行脚本</p>
|
||||
<editor :height="'200px'" ref="editorScript" :codes="form.script_content" :read-only="true" :language="'shell'"></editor>
|
||||
<!-- <bk-input :type="'textarea'" :rows="10" ext-cls="custom-textarea" v-model="form.script_content" :disabled="true"></bk-input> -->
|
||||
</div>
|
||||
<div class="box">
|
||||
<p class="title">前置命令检测</p>
|
||||
<editor :height="'200px'" ref="editorPrecommd" :read-only="true" :language="'json'"></editor>
|
||||
<div class="node-drawer">
|
||||
<bk-sideslider :is-show.sync="nodeDrawer.show" :quick-close="true" :title="nodeDrawer.title"
|
||||
:width="nodeDrawer.width" ext-cls="custom-sidelider">
|
||||
<node-info slot="content" :node-data="nodeData" :key="nodeSliderKey">
|
||||
</node-info>
|
||||
</bk-sideslider>
|
||||
</div>
|
||||
<bk-dialog title="连线模式选择"
|
||||
v-model="flowModeDialog.show"
|
||||
:confirm-fn="handleFlowAddEdgeConfirm"
|
||||
ext-cls="add-mode-dialog"
|
||||
:mask-close="false"
|
||||
header-position="left">
|
||||
<add-mode-dialog :key="flowModeDialogKey" ref="addModeDialog"></add-mode-dialog>
|
||||
<bk-dialog v-model="flowModeDialog.childDialog.show"
|
||||
:mask-close="false"
|
||||
:width="flowModeDialog.childDialog.width"
|
||||
header-position="left"
|
||||
:render-directive="'if'"
|
||||
:position="{ top: 50 }"
|
||||
ext-cls="pre-flow-canvas-dialog"
|
||||
:confirm-fn="handlePreFlowNOdeAddConfirm"
|
||||
:show-footer="flowModeDialog.childDialog.footerShow">
|
||||
<div slot="header">
|
||||
<span style="color: #313237;">当前作业流:{{ flowModeDialog.childDialog.title }}</span>
|
||||
<span class="iconfont icon-mianxingtubiao-wenti"
|
||||
style="margin-left: 4px;color: #979BA5;font-size: 16px;"
|
||||
v-bk-tooltips="flowModeTipConfig"></span>
|
||||
</div>
|
||||
<pre-flow-canvas :options="flowModeDialog.curObj" :pre-edges="flowModeDialog.preEdges"
|
||||
ref="preFlowCanvas"></pre-flow-canvas>
|
||||
</bk-dialog>
|
||||
</bk-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import editor from '@/components/monacoEditor'
|
||||
import {
|
||||
deepClone, getUUID
|
||||
} from '../../../common/util.js'
|
||||
import registerFactory from '@/components/graph/graph.js'
|
||||
import G6 from '@antv/g6'
|
||||
import statusList from './job_flow_view_detail/statusList.vue'
|
||||
import nodeInfo from './job_flow_view_detail/nodeInfo.vue'
|
||||
import addModeDialog from '@/views/job_flow_mgmt/single_job_flow/addModeDialog.vue'
|
||||
import preFlowCanvas from '@/views/job_flow_mgmt/single_job_flow/preFlowCanvas.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
editor
|
||||
statusList,
|
||||
nodeInfo,
|
||||
addModeDialog, // 前置作业流连线模式选择弹窗
|
||||
preFlowCanvas // 前置作业流详情画布
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timer: null, // 轮询定时器
|
||||
jobDetailLoading: false,
|
||||
form: {
|
||||
name: '', // 作业名称
|
||||
state: '', // 状态
|
||||
executor: '', // 启动人
|
||||
station: '', // agent
|
||||
eta: '', // 计划开始时间
|
||||
start_time: '', // 实际开始时间
|
||||
end_time: '', // 完成时间
|
||||
total_time: '', // 总共耗时
|
||||
log: '', // 执行日志
|
||||
script_content: '', // 执行脚本
|
||||
upstream_nodes: [], // 先行作业/作业流
|
||||
downstream_nodes: [] // 后续作业/作业流
|
||||
formLoading: false,
|
||||
nodeSliderKey: 0,
|
||||
flowModeDialogKey: 0, // 前置作业流连线弹窗组件key
|
||||
form: {},
|
||||
graph: null,
|
||||
mainLoading: false,
|
||||
opreateFlag: false,
|
||||
tooltip: null, // 内容超出提示
|
||||
menu: null, // 右键菜单
|
||||
cfg: {}, // 配置项
|
||||
nodeDrawer: {
|
||||
title: '',
|
||||
show: false,
|
||||
width: 600
|
||||
},
|
||||
stateList: [{
|
||||
id: 1,
|
||||
name: 'wait',
|
||||
label: '等待'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'run',
|
||||
label: '正在执行'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'fail',
|
||||
label: '失败'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'error',
|
||||
label: '错误'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'success',
|
||||
label: '成功'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'pause',
|
||||
label: '挂起'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'stop',
|
||||
label: '终止'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'cancel',
|
||||
label: '取消'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: 'need_confirm',
|
||||
label: '待复核'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'ignore',
|
||||
label: '忽略'
|
||||
}
|
||||
nodeData: {},
|
||||
timer: null,
|
||||
flowModeDialog: { // 前置作业流连线模式选择弹窗
|
||||
show: false,
|
||||
curObj: {}, // 当前前置作业流的信息
|
||||
preEdges: [], // 当前前置作业流节点的出线集
|
||||
childDialog: { // 当前前置作业流节点的详情弹框
|
||||
footerShow: false,
|
||||
show: false,
|
||||
width: 960,
|
||||
title: ''
|
||||
}
|
||||
},
|
||||
flowModeTipConfig: {
|
||||
content: '选择前置作业流中的某个节点作为前置依赖连线,不可重复选择节点连线,不可选择该作业流中的前置作业流节点!',
|
||||
placement: 'right',
|
||||
width: 300,
|
||||
zIndex: 999999
|
||||
// delay: [0, 60000]
|
||||
},
|
||||
stateList: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'wait',
|
||||
label: '等待'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'run',
|
||||
label: '正在执行'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'fail',
|
||||
label: '失败'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'error',
|
||||
label: '错误'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'success',
|
||||
label: '成功'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'pause',
|
||||
label: '挂起'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'cancel',
|
||||
label: '取消'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'positive',
|
||||
label: '就绪'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: 'stop',
|
||||
label: '终止'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'need_confirm',
|
||||
label: '待审核'
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: 'ignore',
|
||||
label: '忽略'
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: 'exists_need_confirm',
|
||||
label: '正在执行(存在审核)'
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: 'exists_error',
|
||||
label: '正在执行(存在错误)'
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: 'exists_fail',
|
||||
label: '正在执行(存在失败)'
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
name: 'exists_stop',
|
||||
label: '正在执行(存在终止)'
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
name: 'exists_pause',
|
||||
label: '正在执行(存在挂起)'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 首屏刷新
|
||||
this.handleLoad(true)
|
||||
// 不需要清空画布,首屏刷新
|
||||
this.handleLoad(false, true)
|
||||
},
|
||||
mounted() {
|
||||
// 创建画布
|
||||
this.$nextTick(() => {
|
||||
this.createGraphic()
|
||||
this.initGraphEvent()
|
||||
})
|
||||
// 轮询画布
|
||||
this.timer = setInterval(() => {
|
||||
// 轮询刷新,非首屏
|
||||
this.handleLoad(false)
|
||||
}, 10000)
|
||||
// 不需要清空画布,非首屏刷新
|
||||
this.handleLoad(false, false)
|
||||
}, 3000)
|
||||
window.addEventListener('resize', this.handleChangeCavasSize, false)
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.handleChangeCavasSize, false)
|
||||
this.graph.destroy()
|
||||
clearInterval(this.timer)
|
||||
},
|
||||
methods: {
|
||||
handleLoad(first = false) {
|
||||
if (first) {
|
||||
this.jobDetailLoading = true
|
||||
// 处理改变画布大小
|
||||
handleChangeCavasSize() {
|
||||
this.graph.changeSize(this.$refs.main.clientWidth, 550)
|
||||
this.graph.fitView([20, 30, 30, 80])
|
||||
},
|
||||
handlePreFlowNOdeAddConfirm() {
|
||||
if (!this.$refs.preFlowCanvas.currentChooseNode) {
|
||||
this.$cwMessage('当前作业节点未选择,至少选择一个作业节点!', 'warning')
|
||||
} else {
|
||||
const _this = this
|
||||
setTimeout(() => {
|
||||
const label = `${_this.$refs.preFlowCanvas.currentChooseNode.name}→${_this.flowModeDialog.curObj.targetNode.getModel().name}`
|
||||
this.graph.addItem('edge', {
|
||||
id: getUUID(32, 16),
|
||||
source: _this.flowModeDialog.curObj.sourceNode.get('id'),
|
||||
target: _this.flowModeDialog.curObj.targetNode.get('id'),
|
||||
label: label.length > 10 ? `${label.substr(0, 10)}...` : label,
|
||||
rely_node: {
|
||||
name: label,
|
||||
label: label.length > 10 ? `${label.substr(0, 10)}...` : label,
|
||||
content: _this.$refs.preFlowCanvas.currentChooseNode.content,
|
||||
id: _this.$refs.preFlowCanvas.currentChooseNode.id
|
||||
},
|
||||
gateWay: {
|
||||
name: '', // 分支名
|
||||
expression: '' // 条件表达式
|
||||
}
|
||||
})
|
||||
_this.flowModeDialog.childDialog.show = false
|
||||
_this.flowModeDialog.show = false
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
handleFlowAddEdgeConfirm() {
|
||||
if (this.$refs.addModeDialog.modeValue === 'flow') {
|
||||
setTimeout(() => {
|
||||
this.graph.addItem('edge', {
|
||||
id: getUUID(32, 16),
|
||||
source: this.flowModeDialog.curObj.sourceNode.get('id'),
|
||||
target: this.flowModeDialog.curObj.targetNode.get('id'),
|
||||
gateWay: {
|
||||
name: '', // 分支名
|
||||
expression: '' // 条件表达式
|
||||
}
|
||||
})
|
||||
}, 100)
|
||||
this.flowModeDialog.show = false
|
||||
} else {
|
||||
const edges = this.flowModeDialog.curObj.sourceNode.getEdges()
|
||||
// 表明已有其他前置连线,收集前置节点连线
|
||||
if (edges.length) {
|
||||
this.flowModeDialog.preEdges = edges.filter(item => {
|
||||
return item.getModel().hasOwnProperty('label')
|
||||
})
|
||||
}
|
||||
this.flowModeDialog.childDialog.title = this.flowModeDialog.curObj.sourceNode.getModel().name
|
||||
this.flowModeDialog.childDialog.footerShow = true
|
||||
this.flowModeDialog.childDialog.show = true
|
||||
}
|
||||
},
|
||||
handleOpenFlowDrawer(e) {
|
||||
this.flowModeDialog.preEdges = []
|
||||
const edges = e.item.getEdges()
|
||||
console.log(1234, e.item.getModel())
|
||||
// 表明已有其他前置连线,收集前置节点连线
|
||||
if (edges.length) {
|
||||
this.flowModeDialog.preEdges = edges.filter(item => {
|
||||
return item.getModel().hasOwnProperty('label')
|
||||
})
|
||||
}
|
||||
this.flowModeDialog.curObj = {sourceNode: e.item}
|
||||
this.flowModeDialog.childDialog.title = e.item.getModel().name
|
||||
this.flowModeDialog.childDialog.footerShow = false
|
||||
this.flowModeDialog.childDialog.show = true
|
||||
},
|
||||
initGraphEvent() {
|
||||
this.graph.on('node:click', e => {
|
||||
const model = e.item.get('model')
|
||||
// 开始节点,结束节点,作业流节点不做处理
|
||||
console.log(model.nodeType)
|
||||
if (model.nodeType === 0 || model.nodeType === 1) {
|
||||
return false
|
||||
}
|
||||
if (model.nodeType === 3) {
|
||||
this.handleOpenFlowDrawer(e)
|
||||
} else {
|
||||
this.nodeData = {
|
||||
data: deepClone(model.node_data), // 深拷贝节点数据
|
||||
log: model.log,
|
||||
state: model.state,
|
||||
script_content: model.script_content,
|
||||
start_time: model.start_time,
|
||||
end_time: model.end_time,
|
||||
id: model.id
|
||||
}
|
||||
this.nodeSliderKey += 1
|
||||
this.nodeDrawer.show = true
|
||||
this.nodeDrawer.title = model.name
|
||||
}
|
||||
})
|
||||
},
|
||||
createGraphic() {
|
||||
// 创建菜单
|
||||
this.createMenu()
|
||||
// 初始化配置项
|
||||
this.initOption()
|
||||
// 创建graph实例
|
||||
this.graph = new G6.Graph(this.cfg)
|
||||
},
|
||||
renderCanvas(detail, first) {
|
||||
if (first) {
|
||||
this.mainLoading = true
|
||||
}
|
||||
const _this = this
|
||||
setTimeout(() => {
|
||||
const data = {
|
||||
edges: _this.form.pipeline_tree.lines.map(line => {
|
||||
return {
|
||||
detail: detail,
|
||||
id: getUUID(32, 16),
|
||||
source: line.from,
|
||||
target: line.to
|
||||
}
|
||||
}),
|
||||
nodes: _this.form.pipeline_tree.nodes.map((node, index) => {
|
||||
let style = {}
|
||||
if (node.type === 0 || node.type === 1) {
|
||||
style = {
|
||||
fill: '#fff',
|
||||
r: 24
|
||||
}
|
||||
} else {
|
||||
style = {
|
||||
width: 154,
|
||||
height: 40,
|
||||
radius: 20,
|
||||
iconCfg: {
|
||||
fill: '#3a84ff'
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
...node,
|
||||
detail: detail,
|
||||
label: node.name.length > 8 ? `${node.name.substr(0, 8)}...` : node
|
||||
.name,
|
||||
name: node.name,
|
||||
icon: node.ico,
|
||||
id: node.hasOwnProperty('end_uuid') ? node.end_uuid : node.uuid,
|
||||
jobId: node.id,
|
||||
x: Number(node.left),
|
||||
y: Number(node.top),
|
||||
nodeType: node.type,
|
||||
state: node.state,
|
||||
type: (node.type === 0 || node.type === 1) ? 'circle-node'
|
||||
: 'rect-node',
|
||||
labelCfg: {
|
||||
style: {
|
||||
textAlign: (node.type === 0 || node.type === 1) ? 'center'
|
||||
: 'left'
|
||||
}
|
||||
},
|
||||
style: {
|
||||
...style
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
_this.graph.read(data)
|
||||
// _this.graph.fitCenter()
|
||||
_this.mainLoading = false
|
||||
}, 2000)
|
||||
},
|
||||
initOption() {
|
||||
// 工厂函数注册自定义节点
|
||||
this.cfg = registerFactory(G6, {
|
||||
width: this.$refs.main.clientWidth,
|
||||
height: 550,
|
||||
animate: true, // Boolean,可选,切换布局时是否使用动画过度
|
||||
maxZoom: 1, // 最大缩放比例
|
||||
fitView: true,
|
||||
// fitView: true,
|
||||
// layout: {
|
||||
// type: 'xxx'
|
||||
// },
|
||||
// layout: {
|
||||
// type: 'dagre',
|
||||
// rankdir: 'LR', // 可选,默认为图的中心
|
||||
// align: 'DL', // 可选
|
||||
// nodesep: 20, // 可选
|
||||
// ranksep: 50, // 可选
|
||||
// controlPoints: false, // 可选
|
||||
// },
|
||||
defaultNode: {
|
||||
type: 'rect-node',
|
||||
style: {
|
||||
radius: 10
|
||||
},
|
||||
labelCfg: {
|
||||
fontSize: 20
|
||||
}
|
||||
},
|
||||
defaultEdge: {
|
||||
type: 'polyline-edge', // 扩展了内置边, 有边的事件
|
||||
// type: 'cubic-vertical-edge', // 扩展了内置边, 有边的事件
|
||||
style: {
|
||||
radius: 0, // 拐弯弧度
|
||||
offset: 15, // 拐弯处距离节点的最小距离
|
||||
stroke: '#aab7c3',
|
||||
lineAppendWidth: 10, // 防止线太细没法点中
|
||||
endArrow: {
|
||||
path: 'M 0,0 L 4,3 L 3,0 L 4,-3 Z',
|
||||
fill: '#aab7c3',
|
||||
stroke: '#aab7c3'
|
||||
},
|
||||
zIndex: 999999
|
||||
}
|
||||
},
|
||||
// 覆盖全局样式
|
||||
nodeStateStyles: {
|
||||
'nodeState:default': {
|
||||
opacity: 1,
|
||||
fill: '#fff',
|
||||
stroke: '#DCDEE5',
|
||||
labelCfg: {
|
||||
style: {
|
||||
fill: '#333333'
|
||||
}
|
||||
}
|
||||
},
|
||||
'nodeState:hover': {
|
||||
opacity: 0.8
|
||||
},
|
||||
'nodeState:selected': {
|
||||
opacity: 0.9,
|
||||
stroke: 'rgb(58,132,255)',
|
||||
labelCfg: {
|
||||
style: {
|
||||
fill: 'rgb(58,132,255)'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// linkCenter: true,
|
||||
plugins: [this.tooltip, this.menu],
|
||||
modes: {
|
||||
// 允许拖拽画布、缩放画布、拖拽节点
|
||||
default: [
|
||||
'drag-canvas', // 官方内置的行为
|
||||
'zoom-canvas',
|
||||
'hover-node',
|
||||
'drag-node',
|
||||
'hover-edge'
|
||||
// 'select-node'
|
||||
]
|
||||
}
|
||||
})
|
||||
},
|
||||
// 创建菜单
|
||||
createMenu() {
|
||||
const _this = this
|
||||
// 创建内容超出提示
|
||||
this.tooltip = new G6.Tooltip({
|
||||
offsetX: 20,
|
||||
offsetY: -20,
|
||||
itemTypes: ['node'],
|
||||
// 自定义 tooltip 内容
|
||||
getContent: (e) => {
|
||||
const outDiv = document.createElement('div')
|
||||
outDiv.style.width = 'fit-content'
|
||||
outDiv.innerHTML = `<ul><li>${e.item.getModel().name}</li></ul>`
|
||||
return outDiv
|
||||
},
|
||||
shouldBegin(e) {
|
||||
const model = e.item.get('model')
|
||||
// 触发方式,只有在内容超出8个字符的情况下才触发
|
||||
if (model.nodeType === 0 || model.nodeType === 1 || model.name.length <= 8) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
// 创建右键菜单
|
||||
this.menu = new G6.Menu({
|
||||
offsetX: 20,
|
||||
offsetY: 20,
|
||||
itemTypes: ['node'],
|
||||
getContent(e) {
|
||||
const model = e.item.get('model')
|
||||
const outDiv = document.createElement('div')
|
||||
outDiv.style.width = '60px'
|
||||
outDiv.style.cursor = 'pointer'
|
||||
outDiv.innerHTML = _this.renderRightMenu(model)
|
||||
return outDiv
|
||||
},
|
||||
shouldBegin(e) {
|
||||
const model = e.item.get('model')
|
||||
// 触发方式,开始节点和结束节点或节点状态为成功的都不触发
|
||||
if (model.nodeType === 0 || model.nodeType === 1 || model.state === 'ignore') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
handleMenuClick(target, item) {
|
||||
const model = item.get('model')
|
||||
const {
|
||||
id
|
||||
} = target
|
||||
_this.handleOperation(id, model.jobId)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 处理执行节点操作
|
||||
handleOperation(str, id) {
|
||||
const contentMap = {
|
||||
'pause': {
|
||||
preState: '等待',
|
||||
content: '作业暂停执行,不会继续后面的执行',
|
||||
width: 450
|
||||
},
|
||||
'resume': {
|
||||
preState: '挂起',
|
||||
content: '恢复挂起作业流',
|
||||
width: 400
|
||||
},
|
||||
'stop': {
|
||||
preState: '进行中',
|
||||
content: '终止后不会继续后面的执行,并且无法恢复。会强制终止此作业',
|
||||
width: 400
|
||||
},
|
||||
'cancel': {
|
||||
preState: '除了正在执行',
|
||||
content: '将作业状态置为取消,可以继续往下执行',
|
||||
width: 400
|
||||
},
|
||||
'replay': {
|
||||
preState: '已完成、错误、失败,终止、取消',
|
||||
content: '复制一份该作业,并放入原作业流中。如果新的作业成功,那么对作业流就是成功了',
|
||||
width: 650
|
||||
},
|
||||
'release': {
|
||||
preState: '未执行、等待',
|
||||
content: '释放此作业的被依赖关系(包括时间依赖)',
|
||||
width: 400
|
||||
},
|
||||
'success': {
|
||||
preState: '错误,失败,终止',
|
||||
content: '针对错误,失败,终止的作业,设置为成功',
|
||||
width: 400
|
||||
},
|
||||
'confirm': {
|
||||
preState: '待复核',
|
||||
content: '针对待复核的作业,设置为等待',
|
||||
width: 400
|
||||
}
|
||||
}
|
||||
this.$bkInfo({
|
||||
type: 'primary',
|
||||
title: `执行前状态:${contentMap[str].preState}`,
|
||||
subTitle: `功能说明:${contentMap[str].content}`,
|
||||
width: contentMap[str].width,
|
||||
confirmLoading: false,
|
||||
confirmFn: async() => {
|
||||
this.mainLoading = true
|
||||
// 解决操作执行过程中,由于轮询接口渲染画布而导致mainLoading刷新。
|
||||
// 加入opreateFlag保证轮询过程中不会刷新画布
|
||||
this.opreateFlag = true
|
||||
this.$api.nodeRun.control({
|
||||
'event': str,
|
||||
'ids': [id]
|
||||
}).then(res => {
|
||||
if (res.result) {
|
||||
this.$cwMessage('操作成功!', 'success')
|
||||
// 清空画布并重新获取数据, 首屏刷新
|
||||
this.opreateFlag = false
|
||||
this.handleLoad(true, true)
|
||||
} else {
|
||||
this.$cwMessage(res.message, 'error')
|
||||
// 操作执行接口调用结束,放开轮询
|
||||
this.opreateFlag = false
|
||||
this.mainLoading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 处理根据节点状态渲染左键菜单
|
||||
renderRightMenu(model) {
|
||||
// 当前状态为等待wait,可执行操作为挂起(暂停)pause,释放依赖release,取消cancel
|
||||
if (model.state === 'wait') {
|
||||
return `<p id="pause" class="right-click-menu">挂起</p>
|
||||
<p id="release" class="right-click-menu">释放依赖</p>
|
||||
<p id="cancel" class="right-click-menu">取消</p>`
|
||||
}
|
||||
// 当前状态为暂停(挂起)pause,可执行操作为恢复resume,取消cancel
|
||||
if (model.state === 'pause') {
|
||||
return `<p id="resume" class="right-click-menu">恢复</p>
|
||||
<p id="cancel" class="right-click-menu">取消</p>`
|
||||
}
|
||||
// 当前状态为取消cancel,可执行的操作为重新执行replay
|
||||
if (model.state === 'cancel') {
|
||||
return '<p id="replay" class="right-click-menu">重新执行</p>'
|
||||
}
|
||||
// 当前状态为失败fail或错误error或终止stop,可执行的操作为取消cancel,强制成功success,重新执行replay
|
||||
if (model.state === 'fail' || model.state === 'error' || model.state === 'stop') {
|
||||
return `<p id="cancel" class="right-click-menu">取消</p>
|
||||
<p id="success" class="right-click-menu">强制成功</p>
|
||||
<p id="replay" class="right-click-menu">重新执行</p>`
|
||||
}
|
||||
// 当前状态为成功,可执行的操作为取消cancel,重新执行replay
|
||||
if (model.state === 'success') {
|
||||
return `<p id="cancel" class="right-click-menu">取消</p>
|
||||
<p id="replay" class="right-click-menu">重新执行</p>`
|
||||
}
|
||||
// 当前状态为待复核need_confirm,可执行的操作为取消cancel,复核confirm
|
||||
if (model.state === 'need_confirm') {
|
||||
return `<p id="cancel" class="right-click-menu">取消</p>
|
||||
<p id="confirm" class="right-click-menu">复核</p>`
|
||||
}
|
||||
// 当前状态为正在执行run,可执行的操作为终止stop
|
||||
if (model.state === 'run') {
|
||||
return '<p id="stop" class="right-click-menu">终止</p>'
|
||||
}
|
||||
},
|
||||
handleLoad(clear = false, first = false) {
|
||||
// 操作进行中,不做轮询
|
||||
if (this.opreateFlag) {
|
||||
return false
|
||||
}
|
||||
if (first) {
|
||||
this.formLoading = true
|
||||
}
|
||||
// 在操作接口未调用结束的情况下不做轮询
|
||||
this.$api.nodeRun.retrieve(parseInt(this.$route.query.id)).then(res => {
|
||||
if (res.result) {
|
||||
this.form = res.data
|
||||
if (this.form.hasOwnProperty('log')) {
|
||||
this.$refs.editorLog.monacoEditor.setValue(this.form.log)
|
||||
}
|
||||
if (this.form.hasOwnProperty('script_content')) {
|
||||
this.$refs.editorScript.monacoEditor.setValue(this.form.script_content)
|
||||
}
|
||||
if (this.form.hasOwnProperty('pre_commands')) {
|
||||
this.$refs.editorPrecommd.monacoEditor.setValue(JSON.stringify(this.form.pre_commands))
|
||||
this.$refs.editor.monacoEditor.setValue(this.form.pre_commands)
|
||||
}
|
||||
// 是否需要清空画布重新渲染
|
||||
if (clear) {
|
||||
this.graph.clear()
|
||||
}
|
||||
// this.nodeDrawer.show = false
|
||||
this.renderCanvas(true, first)
|
||||
const processState = res.data.pipeline_tree.process_state
|
||||
if (processState === 'success' || processState === 'fail') {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
} else {
|
||||
this.$cwMessage(res.message, 'error')
|
||||
}
|
||||
this.jobDetailLoading = false
|
||||
this.formLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -170,34 +706,92 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#jobDetail {
|
||||
padding: 20px;
|
||||
#jobFlowViewDetail {
|
||||
padding: 20px;
|
||||
|
||||
.box {
|
||||
.title {
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
color: #63656E;
|
||||
font-weight: bold;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
.node-drawer {
|
||||
|
||||
.customTable {
|
||||
/deep/ .bk-table-empty-block {
|
||||
background-color: #fff;
|
||||
// height: 100%;
|
||||
/deep/ .custom-sidelider {
|
||||
.bk-sideslider-wrapper {
|
||||
.bk-sideslider-content {
|
||||
height: calc(100% - 60px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-textarea {
|
||||
/deep/ textarea {
|
||||
padding: 20px;
|
||||
background-color: rgb(49, 50, 56) !important;
|
||||
color: #C4C6CC !important;
|
||||
}
|
||||
}
|
||||
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.box {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.title {
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
color: #63656E;
|
||||
font-weight: bold;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.customTable {
|
||||
/deep/ .bk-table-empty-block {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-textarea {
|
||||
/deep/ textarea {
|
||||
padding: 20px;
|
||||
background-color: rgb(49, 50, 56) !important;
|
||||
color: #C4C6CC !important;
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
overflow: hidden;
|
||||
height: 550px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background-image: linear-gradient(90deg, rgba(180, 180, 180, 0.15) 10%, rgba(0, 0, 0, 0) 10%), linear-gradient(rgba(180, 180, 180, 0.15) 10%, rgba(0, 0, 0, 0) 10%);
|
||||
background-size: 10px 10px;
|
||||
display: flex;
|
||||
|
||||
.left-statusList {
|
||||
height: 100%;
|
||||
width: 150px;
|
||||
// padding-left: 20px;
|
||||
// padding-top: 15px;
|
||||
}
|
||||
|
||||
.right-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
#main {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
/deep/ .right-click-menu:hover {
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
/deep/ .right-click-menu {
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background-color: #3a84ff;
|
||||
margin-bottom: 8px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
/deep/ .right-click-menu:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
{
|
||||
name: 'jobflowview',
|
||||
label: '作业流视图'
|
||||
},
|
||||
{
|
||||
name: 'jobview',
|
||||
label: '子作业流视图'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -23,19 +23,9 @@
|
||||
<bk-container :margin="0">
|
||||
<bk-form :label-width="110">
|
||||
<bk-row>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="作业名">
|
||||
<bk-input :placeholder="'请输入作业名称'" v-model="searchForm.name" clearable></bk-input>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="执行者">
|
||||
<bk-input :placeholder="'请输入执行者'" v-model="searchForm.executor" clearable></bk-input>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="作业流名">
|
||||
<bk-input :placeholder="'请输入作业流名称'" v-model="searchForm.process_run_name" clearable></bk-input>
|
||||
<bk-input :placeholder="'请输入作业流名称'" v-model="searchForm.name" clearable></bk-input>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
@@ -48,43 +38,50 @@
|
||||
</bk-select>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
</bk-row>
|
||||
<bk-row style="margin-top: 24px;">
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="Agent">
|
||||
<bk-input :placeholder="'请输入Agent'" v-model="searchForm.station" clearable></bk-input>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="计划开始">
|
||||
<bk-date-picker :value="searchForm.eta" :placeholder="'选择日期时间'" :type="'datetimerange'"
|
||||
format="yyyy-MM-dd HH:mm:ss" style="width: 100%;" :transfer="true" @change="handleEtaChange"></bk-date-picker>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
<!-- 未支持 -->
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="实际开始">
|
||||
<bk-date-picker :value="searchForm.startTime" :placeholder="'选择日期时间'" :type="'datetimerange'"
|
||||
format="yyyy-MM-dd HH:mm:ss" style="width: 100%;" :transfer="true" @change="handleStartTimeChange"></bk-date-picker>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="完成时间">
|
||||
<bk-date-picker :value="searchForm.endTime" :placeholder="'选择日期时间'" :type="'datetimerange'"
|
||||
format="yyyy-MM-dd HH:mm:ss" style="width: 100%;" :transfer="true" @change="handleEndTimeChange"></bk-date-picker>
|
||||
<bk-form-item label="作业总数">
|
||||
<bk-input :placeholder="'请输入作业总数'" v-model="searchForm.total_job_count" clearable></bk-input>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
</bk-row>
|
||||
<bk-row style="margin-top: 24px;">
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="已复核人">
|
||||
<bk-input :placeholder="'请输入已复核人'" v-model="searchForm.confirm_users"></bk-input>
|
||||
<bk-form-item label="未执行作业数">
|
||||
<bk-input :placeholder="'请输入未执行作业数'" v-model="searchForm.total_not_execute_job_count"
|
||||
clearable></bk-input>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="待复核人数">
|
||||
<bk-input :placeholder="'请输入待复核人数'" v-model="searchForm.need_confirm"></bk-input>
|
||||
<bk-form-item label="释放依赖">
|
||||
<bk-select class="header-select" :clearable="true" style="background-color: #fff;"
|
||||
v-model="searchForm.is_release_dependency">
|
||||
<bk-option v-for="(item, index) in replyList" :key="index" :id="item.value"
|
||||
:name="item.label">
|
||||
</bk-option>
|
||||
</bk-select>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="实际开始">
|
||||
<bk-date-picker :value="searchForm.start_time" :placeholder="'选择日期时间'" :type="'datetimerange'"
|
||||
format="yyyy-MM-dd HH:mm:ss" style="width: 100%;" :transfer="true" @change="handleStartTimeChange"></bk-date-picker>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="完成时间">
|
||||
<bk-date-picker :value="searchForm.end_time" :placeholder="'选择日期时间'" :type="'datetimerange'"
|
||||
format="yyyy-MM-dd HH:mm:ss" style="width: 100%;" :transfer="true" @change="handleEndTimeChange"></bk-date-picker>
|
||||
</bk-form-item>
|
||||
</bk-col>
|
||||
</bk-row>
|
||||
<bk-row style="margin-top: 24px;">
|
||||
<bk-col :span="6">
|
||||
<bk-form-item label="跑批系统">
|
||||
<bk-select :clearable="true" style="background-color: #fff;" v-model="searchForm.category"
|
||||
@@ -101,6 +98,7 @@
|
||||
<bk-button style="margin-left: 8px;" @click="handleOpenSeniorSearch">取消</bk-button>
|
||||
</bk-row>
|
||||
</bk-form>
|
||||
|
||||
</bk-container>
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,13 +144,13 @@
|
||||
data() {
|
||||
const fields = [{
|
||||
id: 'name',
|
||||
label: '作业名',
|
||||
label: '作业流名',
|
||||
overflowTooltip: true,
|
||||
sortable: false
|
||||
}, {
|
||||
id: 'process',
|
||||
label: '所属作业流名',
|
||||
overflowTooltip: true,
|
||||
id: 'run_type',
|
||||
label: '调度方式',
|
||||
overflowTooltip: false,
|
||||
sortable: false
|
||||
}, {
|
||||
id: 'state',
|
||||
@@ -160,30 +158,20 @@
|
||||
overflowTooltip: false,
|
||||
sortable: false
|
||||
}, {
|
||||
id: 'category_name',
|
||||
label: '跑批系统',
|
||||
id: 'create_time',
|
||||
label: '创建时间',
|
||||
overflowTooltip: false,
|
||||
sortable: true
|
||||
}, {
|
||||
id: 'need_confirm',
|
||||
label: '待复核人数',
|
||||
id: 'total_not_execute_job_count',
|
||||
label: '未执行作业数',
|
||||
overflowTooltip: false,
|
||||
sortable: false
|
||||
}, {
|
||||
id: 'confirm_users',
|
||||
label: '已复核人',
|
||||
overflowTooltip: false,
|
||||
sortable: false
|
||||
}, {
|
||||
id: 'station',
|
||||
label: 'Agent',
|
||||
id: 'is_release_dependency',
|
||||
label: '是否释放依赖',
|
||||
overflowTooltip: true,
|
||||
sortable: false
|
||||
}, {
|
||||
id: 'executor',
|
||||
label: '执行者',
|
||||
overflowTooltip: true,
|
||||
sortable: false
|
||||
sortable: true
|
||||
}, {
|
||||
id: 'eta',
|
||||
label: '计划开始时间',
|
||||
@@ -192,7 +180,7 @@
|
||||
}, {
|
||||
id: 'start_time',
|
||||
label: '实际开始时间',
|
||||
overflowTooltip: true,
|
||||
overflowTooltip: false,
|
||||
sortable: true
|
||||
}, {
|
||||
id: 'end_time',
|
||||
@@ -205,7 +193,7 @@
|
||||
setting: {
|
||||
size: 'small', // 表格大小
|
||||
fields: fields, // 表格所有列
|
||||
selectedFields: fields // 表格当前显示列
|
||||
selectedFields: fields.slice(0, 4) // 表格当前显示列
|
||||
},
|
||||
opreateFlag: false,
|
||||
midSearchForm: {},
|
||||
|
||||
Reference in New Issue
Block a user