[Feature][UI Next] Add Workflow Instance (#8356)

* add workflow list manage

* support batch delete

* add condition search

* add interval to update data

* fix table column I18n error

* add icon for table state column

* del redundant comment

* fix delete data paging jump
This commit is contained in:
Devosend 2022-02-12 14:08:22 +08:00 committed by GitHub
parent d9df8319a2
commit 80d2ee7b11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1451 additions and 10 deletions

View File

@ -371,6 +371,7 @@ const project = {
workflow_publish_status: 'Workflow Publish Status',
schedule_publish_status: 'Schedule Publish Status',
workflow_definition: 'Workflow Definition',
workflow_instance: 'Workflow Instance',
id: '#',
status: 'Status',
create_time: 'Create Time',
@ -431,7 +432,45 @@ const project = {
delete_confirm: 'Delete?',
enter_name_tips: 'Please enter name',
confirm_switch_version: 'Confirm Switch To This Version?',
current_version: 'Current Version'
current_version: 'Current Version',
run_type: 'Run Type',
scheduling_time: 'Scheduling Time',
duration: 'Duration',
run_times: 'Run Times',
fault_tolerant_sign: 'Fault-tolerant Sign',
dry_run_flag: 'Dry-run Flag',
executor: 'Executor',
host: 'Host',
start_process: 'Start Process',
execute_from_the_current_node: 'Execute from the current node',
recover_tolerance_fault_process: 'Recover tolerance fault process',
resume_the_suspension_process: 'Resume the suspension process',
execute_from_the_failed_nodes: 'Execute from the failed nodes',
scheduling_execution: 'Scheduling execution',
rerun: 'Rerun',
stop: 'Stop',
pause: 'Pause',
recovery_waiting_thread: 'Recovery waiting thread',
recover_serial_wait: 'Recover serial wait',
recovery_suspend: 'Recovery Suspend',
recovery_failed: 'Recovery Failed',
gantt: 'Gantt',
name: 'Name',
all_status: 'AllStatus',
submit_success: 'Submitted successfully',
running: 'Running',
ready_to_pause: 'Ready to pause',
ready_to_stop: 'Ready to stop',
failed: 'Failed',
need_fault_tolerance: 'Need fault tolerance',
kill: 'Kill',
waiting_for_thread: 'Waiting for thread',
waiting_for_dependence: 'Waiting for dependence',
waiting_for_dependency_to_complete: 'Waiting for dependency to complete',
delay_execution: 'Delay execution',
forced_success: 'Forced success',
serial_wait: 'Serial wait',
executing: 'Executing'
},
task: {
task_name: 'Task Name',

View File

@ -366,6 +366,7 @@ const project = {
workflow_publish_status: '工作流上线状态',
schedule_publish_status: '定时状态',
workflow_definition: '工作流定义',
workflow_instance: '工作流实例',
id: '编号',
status: '状态',
create_time: '创建时间',
@ -430,7 +431,45 @@ const project = {
enter_name_tips: '请输入名称',
switch_version: '切换到该版本',
confirm_switch_version: '确定切换到该版本吗?',
current_version: '当前版本'
current_version: '当前版本',
run_type: '运行类型',
scheduling_time: '调度时间',
duration: '运行时长',
run_times: '运行次数',
fault_tolerant_sign: '容错标识',
dry_run_flag: '空跑标识',
executor: '执行用户',
host: 'Host',
start_process: '启动工作流',
execute_from_the_current_node: '从当前节点开始执行',
recover_tolerance_fault_process: '恢复被容错的工作流',
resume_the_suspension_process: '恢复运行流程',
execute_from_the_failed_nodes: '从失败节点开始执行',
scheduling_execution: '调度执行',
rerun: '重跑',
stop: '停止',
pause: '暂停',
recovery_waiting_thread: '恢复等待线程',
recover_serial_wait: '串行恢复',
recovery_suspend: '恢复运行',
recovery_failed: '恢复失败',
gantt: '甘特图',
name: '名称',
all_status: '全部状态',
submit_success: '提交成功',
running: '正在运行',
ready_to_pause: '准备暂停',
ready_to_stop: '准备停止',
failed: '失败',
need_fault_tolerance: '需要容错',
kill: 'Kill',
waiting_for_thread: '等待线程',
waiting_for_dependence: '等待依赖',
waiting_for_dependency_to_complete: '等待依赖完成',
delay_execution: '延时执行',
forced_success: '强制成功',
serial_wait: '串行等待',
executing: '正在执行'
},
task: {
task_name: '任务名称',

View File

@ -23,7 +23,7 @@ import {
ProcessInstanceReq
} from './types'
export function execute(data: ExecuteReq, code: ProjectCodeReq): any {
export function execute(data: ExecuteReq, code: number): any {
return axios({
url: `/projects/${code}/executors/execute`,
method: 'post',

View File

@ -29,7 +29,7 @@ import {
export function queryProcessInstanceListPaging(
params: ProcessInstanceListReq,
code: CodeReq
code: number
): any {
return axios({
url: `/projects/${code}/process-instances`,
@ -40,7 +40,7 @@ export function queryProcessInstanceListPaging(
export function batchDeleteProcessInstanceByIds(
data: BatchDeleteReq,
code: CodeReq
code: number
): any {
return axios({
url: `/projects/${code}/process-instances/batch-delete`,
@ -101,7 +101,7 @@ export function updateProcessInstance(
})
}
export function deleteProcessInstanceById(id: IdReq, code: CodeReq): any {
export function deleteProcessInstanceById(id: number, code: number): any {
return axios({
url: `/projects/${code}/process-instances/${id}`,
method: 'delete'

View File

@ -34,7 +34,7 @@ interface ProcessInstanceListReq {
interface BatchDeleteReq {
processInstanceIds: string
projectName: string
projectName?: string
alertGroup?: string
createTime?: string
email?: string
@ -82,6 +82,26 @@ interface ProcessInstanceReq {
timeout?: string
}
interface IWorkflowInstance {
id: number
name: string
state: string
commandType: string
scheduleTime?: string
processDefinitionCode?: number
startTime: string
endTime: string
duration?: string
runTimes: number
recovery: string
dryRun: number
executorName: string
host: string
count?: number
disabled?: boolean
buttonType?: string
}
export {
CodeReq,
ProcessInstanceListReq,
@ -90,5 +110,6 @@ export {
TaskReq,
LongestReq,
IdReq,
ProcessInstanceReq
ProcessInstanceReq,
IWorkflowInstance
}

View File

@ -15,6 +15,25 @@
* limitations under the License.
*/
import {
SettingFilled,
SettingOutlined,
CloseCircleOutlined,
PauseCircleOutlined,
CheckCircleOutlined,
EditOutlined,
MinusCircleOutlined,
CheckCircleFilled,
LoadingOutlined,
PauseCircleFilled,
ClockCircleOutlined,
StopFilled,
StopOutlined,
GlobalOutlined,
IssuesCloseOutlined
} from '@vicons/antd'
import { ITaskState } from './types'
/**
* Intelligent display kb m
*/
@ -46,3 +65,252 @@ export const fileTypeArr = [
'ini',
'js'
]
/**
* Operation type
* @desc tooltip
* @code identifier
*/
export const runningType = (t: any) => [
{
desc: `${t('project.workflow.start_process')}`,
code: 'START_PROCESS'
},
{
desc: `${t('project.workflow.execute_from_the_current_node')}`,
code: 'START_CURRENT_TASK_PROCESS'
},
{
desc: `${t('project.workflow.recover_tolerance_fault_process')}`,
code: 'RECOVER_TOLERANCE_FAULT_PROCESS'
},
{
desc: `${t('project.workflow.resume_the_suspension_process')}`,
code: 'RECOVER_SUSPENDED_PROCESS'
},
{
desc: `${t('project.workflow.execute_from_the_failed_nodes')}`,
code: 'START_FAILURE_TASK_PROCESS'
},
{
desc: `${t('project.workflow.complement_data')}`,
code: 'COMPLEMENT_DATA'
},
{
desc: `${t('project.workflow.scheduling_execution')}`,
code: 'SCHEDULER'
},
{
desc: `${t('project.workflow.rerun')}`,
code: 'REPEAT_RUNNING'
},
{
desc: `${t('project.workflow.pause')}`,
code: 'PAUSE'
},
{
desc: `${t('project.workflow.stop')}`,
code: 'STOP'
},
{
desc: `${t('project.workflow.recovery_waiting_thread')}`,
code: 'RECOVER_WAITING_THREAD'
},
{
desc: `${t('project.workflow.recover_serial_wait')}`,
code: 'RECOVER_SERIAL_WAIT'
}
]
/**
* State code table
*/
export const stateType = (t: any) => [
{
value: '',
label: `${t('project.workflow.all_status')}`
},
{
value: 'SUBMITTED_SUCCESS',
label: `${t('project.workflow.submit_success')}`
},
{
value: 'RUNNING_EXECUTION',
label: `${t('project.workflow.running')}`
},
{
value: 'READY_PAUSE',
label: `${t('project.workflow.ready_to_pause')}`
},
{
value: 'PAUSE',
label: `${t('project.workflow.pause')}`
},
{
value: 'READY_STOP',
label: `${t('project.workflow.ready_to_stop')}`
},
{
value: 'STOP',
label: `${t('project.workflow.stop')}`
},
{
value: 'FAILURE',
label: `${t('project.workflow.failed')}`
},
{
value: 'SUCCESS',
label: `${t('project.workflow.success')}`
},
{
value: 'NEED_FAULT_TOLERANCE',
label: `${t('project.workflow.need_fault_tolerance')}`
},
{
value: 'KILL',
label: `${t('project.workflow.kill')}`
},
{
value: 'WAITING_THREAD',
label: `${t('project.workflow.waiting_for_thread')}`
},
{
value: 'WAITING_DEPEND',
label: `${t('project.workflow.waiting_for_dependency_to_complete')}`
},
{
value: 'DELAY_EXECUTION',
label: `${t('project.workflow.delay_execution')}`
},
{
value: 'FORCED_SUCCESS',
label: `${t('project.workflow.forced_success')}`
},
{
value: 'SERIAL_WAIT',
label: `${t('project.workflow.serial_wait')}`
}
]
/**
* Task status
* @id id
* @desc tooltip
* @color color
* @icon icon
* @isSpin is loading (Need to execute the code block to write if judgment)
*/
// TODO: Looking for a more suitable icon
export const tasksState = (t: any): ITaskState => ({
SUBMITTED_SUCCESS: {
id: 0,
desc: `${t('project.workflow.submit_success')}`,
color: '#A9A9A9',
icon: IssuesCloseOutlined,
isSpin: false,
classNames: 'submitted'
},
RUNNING_EXECUTION: {
id: 1,
desc: `${t('project.workflow.executing')}`,
color: '#0097e0',
icon: SettingFilled,
isSpin: true,
classNames: 'executing'
},
READY_PAUSE: {
id: 2,
desc: `${t('project.workflow.ready_to_pause')}`,
color: '#07b1a3',
icon: SettingOutlined,
isSpin: false,
classNames: 'submitted'
},
PAUSE: {
id: 3,
desc: `${t('project.workflow.pause')}`,
color: '#057c72',
icon: PauseCircleOutlined,
isSpin: false,
classNames: 'pause'
},
READY_STOP: {
id: 4,
desc: `${t('project.workflow.ready_to_stop')}`,
color: '#FE0402',
icon: StopFilled,
isSpin: false
},
STOP: {
id: 5,
desc: `${t('project.workflow.stop')}`,
color: '#e90101',
icon: StopOutlined,
isSpin: false
},
FAILURE: {
id: 6,
desc: `${t('project.workflow.failed')}`,
color: '#000000',
icon: CloseCircleOutlined,
isSpin: false,
classNames: 'failed'
},
SUCCESS: {
id: 7,
desc: `${t('project.workflow.success')}`,
color: '#33cc00',
icon: CheckCircleOutlined,
isSpin: false,
classNames: 'success'
},
NEED_FAULT_TOLERANCE: {
id: 8,
desc: `${t('project.workflow.need_fault_tolerance')}`,
color: '#FF8C00',
icon: EditOutlined,
isSpin: false
},
KILL: {
id: 9,
desc: `${t('project.workflow.kill')}`,
color: '#a70202',
icon: MinusCircleOutlined,
isSpin: false
},
WAITING_THREAD: {
id: 10,
desc: `${t('project.workflow.waiting_for_thread')}`,
color: '#912eed',
icon: ClockCircleOutlined,
isSpin: false
},
WAITING_DEPEND: {
id: 11,
desc: `${t('project.workflow.waiting_for_dependence')}`,
color: '#5101be',
icon: GlobalOutlined,
isSpin: false
},
DELAY_EXECUTION: {
id: 12,
desc: `${t('project.workflow.delay_execution')}`,
color: '#5102ce',
icon: PauseCircleFilled,
isSpin: false
},
FORCED_SUCCESS: {
id: 13,
desc: `${t('project.workflow.forced_success')}`,
color: '#5102ce',
icon: CheckCircleFilled,
isSpin: false
},
SERIAL_WAIT: {
id: 14,
desc: `${t('project.workflow.serial_wait')}`,
color: '#5102ce',
icon: LoadingOutlined,
isSpin: false
}
})

View File

@ -0,0 +1,22 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
interface ITaskState {
[key: string]: any
}
export { ITaskState }

View File

@ -0,0 +1,123 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { SearchOutlined } from '@vicons/antd'
import {
NGrid,
NGridItem,
NInput,
NButton,
NDatePicker,
NSelect,
NIcon
} from 'naive-ui'
import { defineComponent, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { format } from 'date-fns'
import { stateType } from '@/utils/common'
export default defineComponent({
name: 'ProcessInstanceCondition',
emits: ['handleSearch'],
setup(props, ctx) {
const searchValRef = ref('')
const executorNameRef = ref('')
const hostRef = ref('')
const stateTypeRef = ref('')
const startEndTimeRef = ref()
const handleSearch = () => {
let startDate = ''
let endDate = ''
if (startEndTimeRef.value) {
startDate = format(
new Date(startEndTimeRef.value[0]),
'yyyy-MM-dd hh:mm:ss'
)
endDate = format(
new Date(startEndTimeRef.value[1]),
'yyyy-MM-dd hh:mm:ss'
)
}
ctx.emit('handleSearch', {
searchVal: searchValRef.value,
executorName: executorNameRef.value,
host: hostRef.value,
stateType: stateTypeRef.value,
startDate,
endDate
})
}
return {
searchValRef,
executorNameRef,
hostRef,
stateTypeRef,
startEndTimeRef,
handleSearch
}
},
render() {
const { t } = useI18n()
const options = stateType(t)
return (
<NGrid xGap={6} cols={24}>
<NGridItem offset={5} span={3}>
<NInput
v-model:value={this.searchValRef}
placeholder={t('project.workflow.name')}
/>
</NGridItem>
<NGridItem span={3}>
<NInput
v-model:value={this.executorNameRef}
placeholder={t('project.workflow.executor')}
/>
</NGridItem>
<NGridItem span={3}>
<NInput
v-model:value={this.hostRef}
placeholder={t('project.workflow.host')}
/>
</NGridItem>
<NGridItem span={3}>
<NSelect
options={options}
defaultValue={''}
v-model:value={this.stateTypeRef}
/>
</NGridItem>
<NGridItem span={6}>
<NDatePicker
type='datetimerange'
clearable
v-model:value={this.startEndTimeRef}
/>
</NGridItem>
<NGridItem span={1}>
<NButton type='primary' onClick={this.handleSearch}>
<NIcon>
<SearchOutlined />
</NIcon>
</NButton>
</NGridItem>
</NGrid>
)
}
})

View File

@ -0,0 +1,296 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineComponent, PropType, toRefs } from 'vue'
import { NSpace, NTooltip, NButton, NIcon, NPopconfirm } from 'naive-ui'
import {
DeleteOutlined,
FormOutlined,
InfoCircleFilled,
SyncOutlined,
CloseOutlined,
CloseCircleOutlined,
PauseCircleOutlined,
ControlOutlined,
PlayCircleOutlined
} from '@vicons/antd'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import type { Router } from 'vue-router'
import { IWorkflowInstance } from '@/service/modules/process-instances/types'
const props = {
row: {
type: Object as PropType<IWorkflowInstance>,
required: true
}
}
export default defineComponent({
name: 'TableAction',
props,
emits: [
'updateList',
'reRun',
'reStore',
'stop',
'suspend',
'deleteInstance'
],
setup(props, ctx) {
const router: Router = useRouter()
const handleEdit = () => {
router.push({
name: 'workflow-instance-detail',
params: { id: props.row!.id },
query: { code: props.row!.processDefinitionCode }
})
}
const handleReRun = () => {
ctx.emit('reRun')
}
const handleReStore = () => {
ctx.emit('reStore')
}
const handleStop = () => {
ctx.emit('stop')
}
const handleSuspend = () => {
ctx.emit('suspend')
}
const handleDeleteInstance = () => {
ctx.emit('deleteInstance')
}
return {
handleEdit,
handleReRun,
handleReStore,
handleStop,
handleSuspend,
handleDeleteInstance,
...toRefs(props)
}
},
render() {
const { t } = useI18n()
const state = this.row?.state
return (
<NSpace>
<NTooltip trigger={'hover'}>
{{
default: () => t('project.workflow.edit'),
trigger: () => (
<NButton
tag='div'
size='tiny'
type='info'
circle
disabled={
(state !== 'SUCCESS' &&
state !== 'PAUSE' &&
state !== 'FAILURE' &&
state !== 'STOP') ||
this.row?.disabled
}
onClick={this.handleEdit}
>
<NIcon>
<FormOutlined />
</NIcon>
</NButton>
)
}}
</NTooltip>
<NTooltip trigger={'hover'}>
{{
default: () => t('project.workflow.rerun'),
trigger: () => {
return (
<NButton
tag='div'
size='tiny'
type='info'
circle
onClick={this.handleReRun}
disabled={
(state !== 'SUCCESS' &&
state !== 'PAUSE' &&
state !== 'FAILURE' &&
state !== 'STOP') ||
this.row?.disabled
}
>
{this.row?.buttonType === 'run' ? (
<span>{this.row?.count}</span>
) : (
<NIcon>
<SyncOutlined />
</NIcon>
)}
</NButton>
)
}
}}
</NTooltip>
<NTooltip trigger={'hover'}>
{{
default: () => t('project.workflow.recovery_failed'),
trigger: () => (
<NButton
tag='div'
size='tiny'
type='primary'
circle
onClick={this.handleReStore}
disabled={state !== 'FAILURE' || this.row?.disabled}
>
{this.row?.buttonType === 'store' ? (
<span>{this.row?.count}</span>
) : (
<NIcon>
<CloseCircleOutlined />
</NIcon>
)}
</NButton>
)
}}
</NTooltip>
<NTooltip trigger={'hover'}>
{{
default: () =>
state === 'PAUSE'
? t('project.workflow.recovery_failed')
: t('project.workflow.stop'),
trigger: () => (
<NButton
tag='div'
size='tiny'
type='error'
circle
onClick={this.handleStop}
disabled={
(state !== 'RUNNING_EXECUTION' && state !== 'PAUSE') ||
this.row?.disabled
}
>
<NIcon>
{state === 'STOP' ? (
<PlayCircleOutlined />
) : (
<CloseOutlined />
)}
</NIcon>
</NButton>
)
}}
</NTooltip>
<NTooltip trigger={'hover'}>
{{
default: () =>
state === 'PAUSE'
? t('project.workflow.recovery_suspend')
: t('project.workflow.pause'),
trigger: () => (
<NButton
tag='div'
size='tiny'
type='warning'
circle
disabled={
(state !== 'RUNNING_EXECUTION' && state !== 'PAUSE') ||
this.row?.disabled
}
onClick={this.handleSuspend}
>
<NIcon>
{state === 'PAUSE' ? (
<PlayCircleOutlined />
) : (
<PauseCircleOutlined />
)}
</NIcon>
</NButton>
)
}}
</NTooltip>
<NTooltip trigger={'hover'}>
{{
default: () => t('project.workflow.delete'),
trigger: () => (
<NButton
tag='div'
size='tiny'
type='error'
circle
disabled={
(state !== 'SUCCESS' &&
state !== 'FAILURE' &&
state !== 'STOP' &&
state !== 'PAUSE') ||
this.row?.disabled
}
>
<NPopconfirm onPositiveClick={this.handleDeleteInstance}>
{{
default: () => t('project.workflow.delete_confirm'),
icon: () => (
<NIcon>
<InfoCircleFilled />
</NIcon>
),
trigger: () => (
<NIcon>
<DeleteOutlined />
</NIcon>
)
}}
</NPopconfirm>
</NButton>
)
}}
</NTooltip>
<NTooltip trigger={'hover'}>
{{
default: () => t('project.workflow.gantt'),
trigger: () => (
<NButton
tag='div'
size='tiny'
type='info'
circle
/* TODO: Goto gantt*/
disabled={this.row?.disabled}
>
<NIcon>
<ControlOutlined />
</NIcon>
</NButton>
)
}}
</NTooltip>
</NSpace>
)
}
})

View File

@ -0,0 +1,96 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.content {
width: 100%;
.card {
margin-bottom: 8px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 10px 0;
.right {
> .search {
.list {
float: right;
margin: 3px 0 3px 4px;
}
}
}
}
}
.table {
table {
width: 100%;
tr {
height: 40px;
font-size: 12px;
th,
td {
&:nth-child(1) {
width: 50px;
text-align: center;
}
}
th {
&:nth-child(1) {
width: 60px;
text-align: center;
}
> span {
font-size: 12px;
color: #555;
}
}
}
}
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.operation {
> div {
> div {
margin-right: 5px !important;
}
}
}
.startup {
align-items: center;
> div:first-child {
width: 86%;
}
}
.links {
color: #2080f0;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}

View File

@ -15,11 +15,133 @@
* limitations under the License.
*/
import { defineComponent } from 'vue'
import { defineComponent, onMounted, onUnmounted, toRefs, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import Card from '@/components/card'
import {
NButton,
NDataTable,
NPagination,
NPopconfirm,
NTooltip
} from 'naive-ui'
import { useTable } from './use-table'
import ProcessInstanceCondition from './components/process-instance-condition'
import { IWorkflowInstanceSearch } from './types'
import styles from './index.module.scss'
export default defineComponent({
name: 'WorkflowInstanceList',
setup() {
return () => <div>WorkflowInstanceList</div>
let setIntervalP: number
const { variables, createColumns, getTableData, batchDeleteInstance } =
useTable()
const requestData = () => {
getTableData()
}
const handleSearch = (params: IWorkflowInstanceSearch) => {
variables.searchVal = params.searchVal
variables.executorName = params.executorName
variables.host = params.host
variables.stateType = params.stateType
variables.startDate = params.startDate
variables.endDate = params.endDate
variables.page = 1
requestData()
}
const handleChangePageSize = () => {
variables.page = 1
requestData()
}
const handleBatchDelete = () => {
batchDeleteInstance()
}
onMounted(() => {
createColumns(variables)
requestData()
// Update timing list data
setIntervalP = setInterval(() => {
requestData()
}, 9000)
})
watch(useI18n().locale, () => {
createColumns(variables)
})
onUnmounted(() => {
clearInterval(setIntervalP)
})
return {
requestData,
handleSearch,
handleChangePageSize,
handleBatchDelete,
...toRefs(variables)
}
},
render() {
const { t } = useI18n()
return (
<div class={styles.content}>
<Card class={styles.card}>
<div class={styles.header}>
<ProcessInstanceCondition onHandleSearch={this.handleSearch} />
</div>
</Card>
<Card title={t('project.workflow.workflow_instance')}>
<NDataTable
rowKey={(row) => row.id}
columns={this.columns}
data={this.tableData}
striped
size={'small'}
class={styles.table}
scrollX={1800}
v-model:checked-row-keys={this.checkedRowKeys}
/>
<div class={styles.pagination}>
<NPagination
v-model:page={this.page}
v-model:page-size={this.pageSize}
page-count={this.totalPage}
show-size-picker
page-sizes={[10, 30, 50]}
show-quick-jumper
onUpdatePage={this.requestData}
onUpdatePageSize={this.handleChangePageSize}
/>
</div>
<NTooltip>
{{
default: () => t('project.workflow.delete'),
trigger: () => (
<NButton
tag='div'
type='primary'
disabled={this.checkedRowKeys.length <= 0}
style='position: absolute; bottom: 10px; left: 10px;'
>
<NPopconfirm onPositiveClick={this.handleBatchDelete}>
{{
default: () => t('project.workflow.delete_confirm'),
trigger: () => t('project.workflow.delete')
}}
</NPopconfirm>
</NButton>
)
}}
</NTooltip>
</Card>
</div>
)
}
})

View File

@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ExecuteReq } from '@/service/modules/executors/types'
interface ICountDownParam extends ExecuteReq {
index: number
buttonType: 'run' | 'store' | 'suspend'
}
interface IWorkflowInstanceSearch {
searchVal: string
executorName: string
host: string
stateType: string
startDate: string
endDate: string
}
export { ICountDownParam, IWorkflowInstanceSearch }

View File

@ -0,0 +1,381 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import _ from 'lodash'
import { format } from 'date-fns'
import { reactive, h, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import type { Router } from 'vue-router'
import { NTooltip, NIcon, NSpin } from 'naive-ui'
import { RowKey } from 'naive-ui/lib/data-table/src/interface'
import {
queryProcessInstanceListPaging,
deleteProcessInstanceById,
batchDeleteProcessInstanceByIds
} from '@/service/modules/process-instances'
import { execute } from '@/service/modules/executors'
import TableAction from './components/table-action'
import { runningType, tasksState } from '@/utils/common'
import { IWorkflowInstance } from '@/service/modules/process-instances/types'
import { ICountDownParam } from './types'
import { ExecuteReq } from '@/service/modules/executors/types'
import styles from './index.module.scss'
export function useTable() {
const { t } = useI18n()
const router: Router = useRouter()
const taskStateIcon = tasksState(t)
const variables = reactive({
columns: [],
checkedRowKeys: [] as Array<RowKey>,
tableData: [] as Array<IWorkflowInstance>,
page: ref(1),
pageSize: ref(10),
totalPage: ref(1),
searchVal: ref(),
executorName: ref(),
host: ref(),
stateType: ref(),
startDate: ref(),
endDate: ref(),
projectCode: ref(Number(router.currentRoute.value.params.projectCode))
})
const createColumns = (variables: any) => {
variables.columns = [
{
type: 'selection'
},
{
title: t('project.workflow.id'),
key: 'id',
width: 50
},
{
title: t('project.workflow.workflow_name'),
key: 'name',
width: 200,
render: (_row: IWorkflowInstance) =>
h(
'a',
{
href: 'javascript:',
class: styles.links,
onClick: () =>
router.push({
name: 'workflow-instance-detail',
params: { id: _row.id },
query: { code: _row.processDefinitionCode }
})
},
{
default: () => {
return _row.name
}
}
)
},
{
title: t('project.workflow.status'),
key: 'state',
render: (_row: IWorkflowInstance) => {
const stateIcon = taskStateIcon[_row.state]
const iconElement = h(
NIcon,
{
size: '18px',
style: 'position: relative; top: 7.5px; left: 7.5px'
},
{
default: () =>
h(stateIcon.icon, {
color: stateIcon.color
})
}
)
return h(
NTooltip,
{},
{
trigger: () => {
if (stateIcon.isSpin) {
return h(
NSpin,
{
small: 'small'
},
{
icon: () => iconElement
}
)
} else {
return iconElement
}
},
default: () => stateIcon!.desc
}
)
}
},
{
title: t('project.workflow.run_type'),
key: 'commandType',
render: (_row: IWorkflowInstance) =>
(
_.filter(runningType(t), (v) => v.code === _row.commandType)[0] ||
{}
).desc
},
{
title: t('project.workflow.scheduling_time'),
key: 'scheduleTime',
render: (_row: IWorkflowInstance) =>
_row.scheduleTime
? format(new Date(_row.scheduleTime), 'yyyy-MM-dd HH:mm:ss')
: '-'
},
{
title: t('project.workflow.start_time'),
key: 'startTime',
render: (_row: IWorkflowInstance) =>
_row.startTime
? format(new Date(_row.startTime), 'yyyy-MM-dd HH:mm:ss')
: '-'
},
{
title: t('project.workflow.end_time'),
key: 'endTime',
render: (_row: IWorkflowInstance) =>
_row.endTime
? format(new Date(_row.endTime), 'yyyy-MM-dd HH:mm:ss')
: '-'
},
{
title: t('project.workflow.duration'),
key: 'duration',
render: (_row: IWorkflowInstance) => _row.duration || '-'
},
{
title: t('project.workflow.run_times'),
key: 'runTimes'
},
{
title: t('project.workflow.fault_tolerant_sign'),
key: 'recovery'
},
{
title: t('project.workflow.dry_run_flag'),
key: 'dryRun',
render: (_row: IWorkflowInstance) => (_row.dryRun === 1 ? 'YES' : 'NO')
},
{
title: t('project.workflow.executor'),
key: 'executorName'
},
{
title: t('project.workflow.host'),
key: 'host'
},
{
title: t('project.workflow.operation'),
key: 'operation',
width: 220,
fixed: 'right',
className: styles.operation,
render: (_row: IWorkflowInstance, index: number) =>
h(TableAction, {
row: _row,
onReRun: () =>
_countDownFn({
index,
processInstanceId: _row.id,
executeType: 'REPEAT_RUNNING',
buttonType: 'run'
}),
onReStore: () =>
_countDownFn({
index,
processInstanceId: _row.id,
executeType: 'START_FAILURE_TASK_PROCESS',
buttonType: 'store'
}),
onStop: () => {
if (_row.state === 'STOP') {
_countDownFn({
index,
processInstanceId: _row.id,
executeType: 'RECOVER_SUSPENDED_PROCESS',
buttonType: 'suspend'
})
} else {
_upExecutorsState({
processInstanceId: _row.id,
executeType: 'STOP'
})
}
},
onSuspend: () => {
if (_row.state === 'PAUSE') {
_countDownFn({
index,
processInstanceId: _row.id,
executeType: 'RECOVER_SUSPENDED_PROCESS',
buttonType: 'suspend'
})
} else {
_upExecutorsState({
processInstanceId: _row.id,
executeType: 'PAUSE'
})
}
},
onDeleteInstance: () => deleteInstance(_row.id)
})
}
]
}
const getTableData = () => {
const params = {
pageNo: variables.page,
pageSize: variables.pageSize,
searchVal: variables.searchVal,
executorName: variables.executorName,
host: variables.host,
stateType: variables.stateType,
startDate: variables.startDate,
endDate: variables.endDate
}
queryProcessInstanceListPaging({ ...params }, variables.projectCode).then(
(res: any) => {
variables.totalPage = res.totalPage
variables.tableData = res.totalList.map((item: any) => {
return { ...item }
})
}
)
}
const deleteInstance = (id: number) => {
deleteProcessInstanceById(id, variables.projectCode)
.then(() => {
window.$message.success(t('project.workflow.success'))
if (variables.tableData.length === 1 && variables.page > 1) {
variables.page -= 1
}
getTableData()
})
.catch((error: any) => {
window.$message.error(error.message || '')
getTableData()
})
}
const batchDeleteInstance = () => {
const data = {
processInstanceIds: _.join(variables.checkedRowKeys, ',')
}
batchDeleteProcessInstanceByIds(data, variables.projectCode)
.then(() => {
window.$message.success(t('project.workflow.success'))
if (
variables.tableData.length === variables.checkedRowKeys.length &&
variables.page > 1
) {
variables.page -= 1
}
variables.checkedRowKeys = []
getTableData()
})
.catch((error: any) => {
window.$message.error(error.message || '')
getTableData()
})
}
/**
* operating
*/
const _upExecutorsState = (param: ExecuteReq) => {
execute(param, variables.projectCode)
.then(() => {
window.$message.success(t('project.workflow.success'))
getTableData()
})
.catch((error: any) => {
window.$message.error(error.message || '')
getTableData()
})
}
/**
* Countdown
*/
const _countDown = (fn: any, index: number) => {
const TIME_COUNT = 10
let timer: number | undefined
let $count: number
if (!timer) {
$count = TIME_COUNT
timer = setInterval(() => {
if ($count > 0 && $count <= TIME_COUNT) {
$count--
variables.tableData[index].count = $count
} else {
fn()
clearInterval(timer)
timer = undefined
}
}, 1000)
}
}
/**
* Countdown method refresh
*/
const _countDownFn = (param: ICountDownParam) => {
const { index } = param
variables.tableData[index].buttonType = param.buttonType
execute(param, variables.projectCode)
.then(() => {
variables.tableData[index].disabled = true
window.$message.success(t('project.workflow.success'))
_countDown(() => {
getTableData()
}, index)
})
.catch((error: any) => {
window.$message.error(error.message)
getTableData()
})
}
return {
variables,
createColumns,
getTableData,
batchDeleteInstance
}
}