,
+ 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 (
+
+
+ {{
+ default: () => t('project.workflow.edit'),
+ trigger: () => (
+
+
+
+
+
+ )
+ }}
+
+
+ {{
+ default: () => t('project.workflow.rerun'),
+ trigger: () => {
+ return (
+
+ {this.row?.buttonType === 'run' ? (
+ {this.row?.count}
+ ) : (
+
+
+
+ )}
+
+ )
+ }
+ }}
+
+
+ {{
+ default: () => t('project.workflow.recovery_failed'),
+ trigger: () => (
+
+ {this.row?.buttonType === 'store' ? (
+ {this.row?.count}
+ ) : (
+
+
+
+ )}
+
+ )
+ }}
+
+
+ {{
+ default: () =>
+ state === 'PAUSE'
+ ? t('project.workflow.recovery_failed')
+ : t('project.workflow.stop'),
+ trigger: () => (
+
+
+ {state === 'STOP' ? (
+
+ ) : (
+
+ )}
+
+
+ )
+ }}
+
+
+ {{
+ default: () =>
+ state === 'PAUSE'
+ ? t('project.workflow.recovery_suspend')
+ : t('project.workflow.pause'),
+ trigger: () => (
+
+
+ {state === 'PAUSE' ? (
+
+ ) : (
+
+ )}
+
+
+ )
+ }}
+
+
+ {{
+ default: () => t('project.workflow.delete'),
+ trigger: () => (
+
+
+ {{
+ default: () => t('project.workflow.delete_confirm'),
+ icon: () => (
+
+
+
+ ),
+ trigger: () => (
+
+
+
+ )
+ }}
+
+
+ )
+ }}
+
+
+ {{
+ default: () => t('project.workflow.gantt'),
+ trigger: () => (
+
+
+
+
+
+ )
+ }}
+
+
+ )
+ }
+})
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/instance/index.module.scss b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/index.module.scss
new file mode 100644
index 0000000000..7622b8b36c
--- /dev/null
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/index.module.scss
@@ -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;
+ }
+}
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/instance/index.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/index.tsx
index b79c035860..f4ae876274 100644
--- a/dolphinscheduler-ui-next/src/views/projects/workflow/instance/index.tsx
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/index.tsx
@@ -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 () => WorkflowInstanceList
+ 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 (
+
+
+
+
+
+ row.id}
+ columns={this.columns}
+ data={this.tableData}
+ striped
+ size={'small'}
+ class={styles.table}
+ scrollX={1800}
+ v-model:checked-row-keys={this.checkedRowKeys}
+ />
+
+
+ {{
+ default: () => t('project.workflow.delete'),
+ trigger: () => (
+
+
+ {{
+ default: () => t('project.workflow.delete_confirm'),
+ trigger: () => t('project.workflow.delete')
+ }}
+
+
+ )
+ }}
+
+
+
+ )
}
})
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/instance/types.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/types.ts
new file mode 100644
index 0000000000..81894f92cf
--- /dev/null
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/types.ts
@@ -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 }
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/instance/use-table.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/use-table.ts
new file mode 100644
index 0000000000..158117583f
--- /dev/null
+++ b/dolphinscheduler-ui-next/src/views/projects/workflow/instance/use-table.ts
@@ -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,
+ tableData: [] as Array,
+ 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
+ }
+}