feat: 拆分ops 和task 菜单

This commit is contained in:
Aaron3S
2022-10-28 19:01:53 +08:00
parent f4b09a76b8
commit d8deab799e
24 changed files with 179 additions and 849 deletions

View File

@@ -738,7 +738,8 @@
"SessionsAudit": "Sessions audit",
"SessionList": "Session list",
"BatchCommand": "Batch Command",
"BatchCommandLog": "Batch Command Logs",
"BatchScript": "Batch Script",
"tasksLog": "Batch Command Logs",
"CeleryTaskLog": "Celery task log",
"CommandExecutions": "CommandExecutions ",
"CommandFilterCreate": "Command filter create",
@@ -787,7 +788,8 @@
"FtpLog": "FTP Logs",
"GatewayCreate": "Gateway create",
"GatewayUpdate": "Gateway update",
"JobCenter": "Devops",
"TaskCenter": "Task",
"JobCenter": "Job",
"LabelCreate": "Label create",
"LabelList": "Labels",
"LabelUpdate": "Label update",

View File

@@ -751,7 +751,7 @@
"TicketsTodo": "To-doオーダー",
"TicketsDone": "すでに勤務単",
"TicketsNew": "ワークオーダーを提出する",
"BatchCommandLog": "一括コマンド",
"tasksLog": "一括コマンド",
"CeleryTaskLog": "Celeryタスクログ",
"CommandExecutions": "コマンド実行",
"CommandFilterCreate": "コマンドフィルタの作成",

View File

@@ -766,10 +766,11 @@
"LogsAudit": "日志审计",
"SessionList": "会话记录",
"BatchCommand": "批量命令",
"BatchScript": "批量脚本",
"TicketsTodo": "待办工单",
"TicketsDone": "已办工单",
"TicketsNew": "提交工单",
"BatchCommandLog": "批量命令",
"tasksLog": "批量命令",
"CeleryTaskLog": "Celery任务日志",
"CommandExecutions": "命令执行",
"CommandFilterCreate": "创建命令过滤器",
@@ -820,6 +821,7 @@
"FtpLog": "FTP日志",
"GatewayCreate": "创建网关",
"GatewayUpdate": "更新网关",
"TaskCenter": "任务中心",
"JobCenter": "作业中心",
"LabelCreate": "创建标签",
"LabelList": "标签管理",

View File

@@ -30,10 +30,10 @@ export default [
},
{
path: 'command-execution-log',
name: 'BatchCommandLog',
name: 'tasksLog',
component: () => import('@/views/audits/CommandExecutionList'),
meta: {
title: i18n.t('route.BatchCommandLog'),
title: i18n.t('route.tasksLog'),
permissions: ['ops.view_commandexecution']
}
}

View File

@@ -7,7 +7,7 @@ import i18n from '@/i18n/i18n'
export default [
{
path: '/ops/celery/task/:id/log/',
component: () => import('@/views/ops/CeleryTaskLog'),
component: () => import('@/views/tasks/CeleryTaskLog'),
name: 'CeleryTaskLog',
hidden: true,
meta: {
@@ -17,7 +17,7 @@ export default [
},
{
path: '/ops/task/task/:id/log/',
component: () => import('@/views/ops/CeleryTaskLog'),
component: () => import('@/views/tasks/CeleryTaskLog'),
name: 'TaskLog',
hidden: true,
meta: {

View File

@@ -6,7 +6,7 @@ import store from '@/store'
import UsersRoute from './users'
import AssetsRoute from './assets'
import PermsRoute from './perms'
import OpsRoutes from './ops'
import TaskRoutes from './tasks'
import AclRoutes from './acls'
import AccountRoutes from './accounts'
@@ -88,14 +88,14 @@ export default {
children: AclRoutes
},
{
path: '/console/ops',
path: '/console/tasks',
component: empty,
name: 'JobCenter',
name: 'TaskCenter',
meta: {
title: i18n.t('route.JobCenter'),
icon: 'coffee'
title: i18n.t('route.TaskCenter'),
icon: 'tasks'
},
children: OpsRoutes
children: TaskRoutes
}
]
}

View File

@@ -1,64 +0,0 @@
import i18n from '@/i18n/i18n'
import { BASE_URL } from '@/utils/common'
import empty from '@/layout/empty'
export default [
{
path: 'tasks',
component: empty,
meta: { title: i18n.t('route.TaskList') },
children: [
{
path: '',
name: 'TaskList',
component: () => import('@/views/ops/TaskList'),
meta: { title: i18n.t('route.TaskList'), permissions: [] }
},
{
path: ':id',
component: () => import('@/views/ops/TaskDetail'),
name: 'TaskDetail',
hidden: true,
meta: { title: i18n.t('route.TaskDetail'), permissions: [] }
}
]
},
{
path: 'adhoc/:id',
component: () => import('@/views/ops/TaskDetail/AdhocDetail'),
name: 'AdhocDetail',
hidden: true,
meta: {
title: i18n.t('route.TaskDetail'),
permissions: ['ops.view_adhoc'],
activeMenu: '/ops/tasks'
}
},
{
path: 'executions/:id',
component: () => import('@/views/ops/TaskDetail/HistoryExecutionDetail'),
name: 'HistoryExecutionDetail',
hidden: true,
meta: {
title: i18n.t('route.TaskDetail'),
permissions: ['ops.view_adhocexecution'],
activeMenu: '/console/ops/tasks'
}
},
{
path: 'command-executions/create',
name: 'BatchCommand',
component: () => import('@/views/ops/CommandExecution'),
meta: {
title: i18n.t('route.BatchCommand'),
permissions: ['ops.add_adhocexecution'],
hidden: ({ settings }) => !settings['SECURITY_COMMAND_EXECUTION']
}
},
{
path: `${BASE_URL}/core/flower/?_=${Date.now()}`,
name: 'TaskMonitor',
// component: () => window.open(`/core/flower?_=${Date.now()}`),
meta: { title: i18n.t('route.TaskMonitor'), permissions: ['ops.view_taskmonitor'] }
}
]

View File

@@ -0,0 +1,32 @@
import i18n from '@/i18n/i18n'
import { BASE_URL } from '@/utils/common'
import empty from '@/layout/empty'
export default [
{
path: 'tasks',
component: empty,
meta: { title: i18n.t('route.TaskList') },
children: [
{
path: '',
name: 'TaskList',
component: () => import('@/views/tasks/TaskList'),
meta: { title: i18n.t('route.TaskList'), permissions: [] }
},
{
path: ':id',
component: () => import('@/views/tasks/TaskDetail'),
name: 'TaskDetail',
hidden: true,
meta: { title: i18n.t('route.TaskDetail'), permissions: [] }
}
]
},
{
path: `${BASE_URL}/core/flower/?_=${Date.now()}`,
name: 'TaskMonitor',
// component: () => window.open(`/core/flower?_=${Date.now()}`),
meta: { title: i18n.t('route.TaskMonitor'), permissions: ['ops.view_taskmonitor'] }
}
]

View File

@@ -42,26 +42,7 @@ export default {
permissions: ['perms.view_myassets']
}
},
{
path: '/workbench/ops',
component: empty,
meta: {
permissions: ['ops.add_commandexecution'],
hidden: ({ settings }) => !settings['SECURITY_COMMAND_EXECUTION']
},
children: [
{
path: '',
name: 'CommandExecutions',
component: () => import('@/views/ops/CommandExecution'),
meta: {
title: i18n.t('route.BatchCommand'),
icon: 'terminal',
permissions: ['ops.add_commandexecution']
}
}
]
},
{
path: `external-luna`,
component: empty,
@@ -97,6 +78,38 @@ export default {
}
}
]
},
{
path: '/workbench/ops',
component: empty,
name: 'JobCenter',
meta: {
title: i18n.t('route.JobCenter'),
icon: 'coffee',
permissions: []
},
children: [
{
path: 'a',
name: 'CommandExecutions2',
component: () => import('@/views/ops/Command'),
meta: {
title: i18n.t('route.BatchCommand'),
icon: 'terminal',
permissions: []
}
},
{
path: '',
name: 'CommandExecutions',
component: () => import('@/views/ops/Command'),
meta: {
title: i18n.t('route.BatchScript'),
icon: 'book',
permissions: []
}
}
]
}
]
}

13
src/views/ops/Command.vue Normal file
View File

@@ -0,0 +1,13 @@
<template>
<div />
</template>
<script>
export default {
name: 'Command'
}
</script>
<style scoped>
</style>

View File

@@ -1,279 +0,0 @@
<template>
<Page>
<el-collapse-transition>
<div style="display: flex;justify-items: center; flex-wrap: nowrap;justify-content:space-between;">
<div v-show="iShowTree" :style="iShowTree?('width:250px;'):('width:0;')" class="transition-box">
<AutoDataZTree
ref="AutoDataZTree"
:key="DataZTree"
:setting="treeSetting"
class="auto-data-ztree"
/>
</div>
<div :style="iShowTree?('display: flex;width: calc(100% - 250px);'):('display: flex;width:100%;')">
<div class="mini">
<div style="display:block" class="mini-button" @click="iShowTree=!iShowTree">
<i v-show="iShowTree" class="fa fa-angle-left fa-x" /><i v-show="!iShowTree" class="fa fa-angle-right fa-x" />
</div>
</div>
<IBox class="transition-box" style="width: calc(100% - 17px);">
<Term ref="xterm" />
<div style="display: flex;margin-top:10px;justify-content: space-between">
<div>
<CodeMirror :options="codeMirrorOptions" @change="handleActionChange" />
</div>
<div style="display: flex;flex-direction: column ;justify-content: space-between">
<el-select v-model="selectedSystemUser" :placeholder="this.$t('ops.PleaseSelect')" @change="handleSystemUserChange">
<el-option
v-for="item in options"
:key="item.id"
:disabled="item.protocol !== 'ssh' || item.login_mode!== 'auto'"
:label="`${item.name}(${item.username})`"
:value="item.id"
/>
</el-select>
<el-button type="primary" size="small" @click="execute">{{ this.$t('ops.Execute') }}</el-button>
</div>
</div>
</IBox>
</div>
</div>
</el-collapse-transition>
</Page>
</template>
<script>
import AutoDataZTree from '@/components/AutoDataZTree'
import Term from '@/components/Term'
import IBox from '@/components/IBox'
import CodeMirror from '@/components/CodeMirror'
import Page from '@/layout/components/Page'
export default {
name: 'CommandExecution',
components: {
Term,
AutoDataZTree,
IBox,
Page,
CodeMirror
},
data() {
return {
DataZTree: 0,
codeMirrorOptions: {
lineNumbers: true,
lineWrapping: true,
mode: 'shell'
},
treeSetting: {
treeUrl: '',
showRefresh: true,
showMenu: false,
check: {
enable: true
},
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
edit: {
enable: true,
showRemoveBtn: false,
showRenameBtn: false,
drag: {
isCopy: true,
isMove: true
}
},
callback: {
onCheck: this.onCheck.bind(this),
onClick: this.onClick.bind(this),
onSelected: this.onSelected.bind(this)
},
async: {
enable: false
}
},
iShowTree: true,
actions: '',
options: [],
selectedSystemUser: '',
basicUrl: '/api/v1/perms/users/nodes-with-assets/tree/?cache_policy=1',
ws: '',
wsConnected: false
}
},
computed: {
zTree() {
return this.$refs.AutoDataZTree.$refs.dataztree.$refs.ztree.zTree
},
xterm() {
return this.$refs.xterm.xterm
}
},
mounted() {
this.$axios.get('/api/v1/perms/system-users-permission/').then(res => {
if (res.length === 0) {
this.handleSystemUserChange('')
return
}
for (const i in res) {
// :disabled="item.protocol !== 'ssh'&& item.login_mode!=='auto'"
if (res[i].protocol === 'ssh' && res[i].login_mode === 'auto') {
this.handleSystemUserChange(res[i].id)
this.selectedSystemUser = res[i].id
break
}
}
this.options = res
})
this.xterm.write(this.$t('ops.selectAssetsMessage'))
this.enableWS()
},
methods: {
handleActionChange(val) {
this.actions = val
},
onClick(event, treeId, treeNode, clickFlag) {
// if (treeNode.meta.type === 'asset') {
// const protocolsStr = treeNode.meta.data.protocols + ''
// if (protocolsStr.indexOf('ssh/') === -1) {
// // Don't Support SSH
// }
// }
},
onSelected(event, treeNode) {
},
handleSystemUserChange(id) {
this.treeSetting.treeUrl = `${this.basicUrl}&system_user=${id}`
this.xterm.clear()
this.DataZTree++
},
getSelectedAssetsNode() {
const nodes = this.$refs.AutoDataZTree.$refs.dataztree.$refs.ztree.getCheckedNodes()
const assetsNodeId = []
const assetsNode = []
nodes.forEach(function(node) {
if (node.meta.type === 'asset' && !node.isHidden) {
const protocolsStr = node.meta.data.protocols + ''
if (assetsNodeId.indexOf(node.id) === -1 && protocolsStr.indexOf('ssh') > -1) {
assetsNodeId.push(node.id)
assetsNode.push(node)
}
}
})
return assetsNode
},
onCheck(e, treeId, treeNode) {
const nodes = this.getSelectedAssetsNode()
const nodes_names = nodes.map(function(node) {
return node.name
})
let message = this.$t('ops.selectedAssets')
message += nodes_names.join(', ')
message += '\r\n'
message += this.$t('ops.inTotal') + `${nodes_names.length} \r\n`
this.xterm.clear()
this.xterm.write(message)
},
enableWS() {
const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws'
const port = document.location.port ? ':' + document.location.port : ''
const url = '/ws/ops/tasks/log/'
const wsURL = scheme + '://' + document.location.hostname + port + url
// const failOverPort = '8070'
// const failOverWsURL = scheme + '://' + document.location.hostname + ':' + failOverPort + url
this.ws = new WebSocket(wsURL)
this.ws.onerror = (e) => {
this.xterm.write(this.wrapperError('Connect websocket server error'))
}
this.setWsCallback()
},
setWsCallback() {
this.ws.onmessage = (e) => {
const data = JSON.parse(e.data)
let message = data.message
message = message.replace(/Task ops\.tasks\.run_command_execution.*/, '')
this.xterm.write(message)
}
},
wrapperError(msg) {
return `\r\n${msg}\r\n`
},
writeExecutionOutput(taskId) {
let msg = this.$t('assets.Pending')
this.xterm.write(msg)
msg = JSON.stringify({ task: taskId })
this.ws.send(msg)
},
execute() {
const size = 'rows=' + this.xterm.rows + '&cols=' + this.xterm.cols
const url = '/api/v1/ops/command-executions/?' + size
const runAs = this.selectedSystemUser
const command = this.actions
const hosts = this.getSelectedAssetsNode().map(function(node) {
return node.id
})
if (hosts.length === 0) {
this.xterm.write(this.wrapperError(this.$t('assets.UnselectedAssets')))
return
}
if (!command) {
this.xterm.write(this.wrapperError(this.$t('assets.NoInputCommand')))
return
}
if (!runAs) {
this.xterm.write(this.wrapperError(this.$t('assets.NoSystemUserWasSelected')))
return
}
const data = {
hosts: hosts,
run_as: runAs,
command: command
}
this.$axios.post(
url, data
).then(res => {
this.writeExecutionOutput(res.id)
})
}
}
}
</script>
<style lang="scss" scoped>
.mini-button{
width: 12px;
float: right;
text-align: center;
padding: 5px 0;
background-color: var(--color-primary);
border-color: var(--color-primary);
color: #FFFFFF;
border-radius: 3px;
}
.el-tree{
background-color: inherit !important;
}
.mini{
margin-right: 5px;
width: 12px !important;
}
.auto-data-ztree {
overflow: auto;
/*border-right: solid 1px red;*/
}
.vue-codemirror-wrap ::v-deep .CodeMirror{
width: 600px;
height: 100px;
border: 1px solid #eee;
}
</style>

View File

@@ -1,138 +0,0 @@
<template>
<el-row :gutter="20">
<el-col :md="14" :sm="24">
<DetailCard :title="cardTitle" :items="detailCardItems" />
</el-col>
<el-col :md="10" :sm="24">
<RunInfoCard type="danger" style="margin-top: 15px" v-bind="RunFailedConfig" />
<RunInfoCard type="info" v-bind="RunSuccessConfig" style="margin-top: 15px" />
</el-col>
</el-row>
</template>
<script>
import DetailCard from '@/components/DetailCard/index'
import { toSafeLocalDateStr } from '@/utils/common'
import RunInfoCard from '../../RunInfoCard'
import { toLastFailureDisplay, toLastSucessDisplay } from '../business'
export default {
name: 'AdhocDetail',
components: {
DetailCard,
RunInfoCard
},
props: {
object: {
type: Object,
default: () => ({})
}
},
data() {
return {
RunSuccessConfig: {
icon: 'fa-info',
title: this.$t('ops.lastRunSuccessHosts'),
contents: toLastSucessDisplay(this.object.latest_execution)
},
RunFailedConfig: {
icon: 'fa-info',
title: this.$t('ops.lastRunFailedHosts'),
contents: toLastFailureDisplay(this.object.latest_execution)
}
}
},
computed: {
cardTitle() {
return `${this.object.task_name}: ${this.object.short_id}`
},
detailCardItems() {
return [
{
key: this.$t('ops.hosts'),
value: JSON.stringify(this.object.hosts.length)
},
{
key: this.$t('ops.pattern'),
value: this.object.pattern
},
{
key: this.$t('ops.options'),
value: this.disPlayOptions(this.object.options)
// value: this.object.options
},
{
key: this.$t('ops.runAs'),
value: this.disPlayRunAs(this.object.run_as_admin, this.object.run_as)
},
{
key: this.$t('ops.become'),
value: this.object.become_display
},
{
key: this.$t('common.createBy'),
value: this.object.created_by
},
{
key: this.$t('common.dateCreated'),
value: toSafeLocalDateStr(this.object.date_created)
},
{
key: this.$t('ops.runTimes'),
value: this.object.run_times
},
{
key: this.$t('ops.lastRun'),
value: this.object.latest_execution.last_run
},
{
key: this.$t('ops.timeDelta'),
value: this.object.latest_execution.timedelta
},
{
key: this.$t('ops.isFinished'),
value: this.object.latest_execution.is_finished
},
{
key: this.$t('ops.isSuccess'),
value: this.object.latest_execution.is_success
},
{
key: this.$t('ops.tasks'),
value: this.toContentsDisplay(this.object.tasks),
formatter(row, value) {
return (<div>{
value.map((content) => {
return <div>{ content }</div>
})}
</div>)
}
}
]
}
},
methods: {
disPlayRunAs(run_as_admin, run_as) {
if (run_as_admin) {
return 'Admin'
}
return run_as
},
disPlayOptions(options) {
return options.replace(/:/g, '=').replace(/'/g, '').replace('{', '').replace('}', '')
},
toContentsDisplay(contents) {
const lines = []
for (let i = 0; i < contents.length; i++) {
const content = contents[i]
lines.push(`${i}. ${content.name} ::: ${content.action.module}`)
}
return lines
}
}
}
</script>
<style lang="less" scoped>
</style>

View File

@@ -1,106 +0,0 @@
<template>
<ListTable :table-config="tableConfig" :header-actions="headerActions" />
</template>
<script>
import ListTable from '@/components/ListTable'
import { ActionsFormatter } from '@/components/TableFormatters'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'AdhocExecutionHistory',
components: {
ListTable
},
data() {
return {
tableConfig: {
url: `/api/v1/ops/adhoc-executions/?adhoc=${this.$route.params.id}`,
columns: [
'date_start', 'stat', 'ratio', 'is_finished', 'is_success', 'timedelta', 'adhoc_short_id', 'actions'
],
columnsMeta: {
date_start: {
formatter: function(row) {
return toSafeLocalDateStr(row.date_start)
}
},
stat: {
label: this.$t('ops.stat'),
align: 'center',
width: '100px',
formatter: function(row) {
const summary = <div>
<span class='text-primary'>{row.stat.success}</span>/
<span class='text-danger'>{row.stat.failed}</span>/
<span>{row.stat.total}</span>
</div>
return summary
}
},
ratio: {
label: this.$t('ops.ratio'),
align: 'center',
width: '80px',
formatter: function(row) {
const ratio = (row.stat.success / row.stat.total) * 100
if (ratio === 100) {
return <span class='text-navy'>{ratio + '%'}</span>
}
return <span class='text-danger'>{ratio + '%'}</span>
}
},
is_finished: {
align: 'center',
width: '100px',
label: this.$t('ops.isFinished')
},
is_success: {
align: 'center',
width: '100px',
label: this.$t('ops.isSuccess')
},
timedelta: {
label: this.$t('ops.time'),
width: '100px',
formatter: function(row) {
return row.timedelta.toFixed(2) + 's'
}
},
adhoc_short_id: {
label: this.$t('ops.version')
},
actions: {
prop: 'id',
formatter: ActionsFormatter,
formatterArgs: {
hasEdit: false,
hasDelete: false,
hasClone: false,
hasUpdate: false,
extraActions: [
{
name: 'detail',
title: this.$t('ops.detail'),
type: 'primary',
callback: function({ row, tableData }) {
return this.$router.push({ name: 'HistoryExecutionDetail', params: { id: row.id }})
}
}
]
}
}
}
},
headerActions: {
hasLeftActions: false,
hasRightActions: false
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,47 +0,0 @@
<template>
<GenericDetailPage :object.sync="AdhocDetail" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
<keep-alive>
<component :is="config.activeMenu" :object="AdhocDetail" />
</keep-alive>
</GenericDetailPage>
</template>
<script>
import { GenericDetailPage, TabPage } from '@/layout/components'
import AdhocExecutionHistory from './AdhocExecutionHistory'
import AdhocDetail from './AdhocDetail'
export default {
components: {
GenericDetailPage,
AdhocExecutionHistory,
AdhocDetail,
TabPage
},
data() {
return {
AdhocDetail: {},
config: {
activeMenu: 'AdhocDetail',
title: this.$t('ops.taskDetail'),
submenu: [
{
title: this.$t('ops.versionDetail'),
name: 'AdhocDetail'
},
{
title: this.$t('ops.versionRunExecution'),
name: 'AdhocExecutionHistory',
hidden: () => !this.$hasPerm('ops.view_adhocexecution')
}
],
hasRightSide: false
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,179 +0,0 @@
<template>
<GenericListPage :table-config="tableConfig" :header-actions="headerActions" />
</template>
<script type="text/jsx">
// import { timeOffset, toSafeLocalDateStr } from '@/utils/common'
import { GenericListPage } from '@/layout/components'
// import { openTaskPage } from '@/utils/jms'
export default {
components: {
GenericListPage
},
data() {
return {
tableConfig: {
url: '/api/v1/ops/tasks/',
columns: [
'name', 'queue', 'comment', 'count', 'state', 'last_published_time'
],
columnsMeta: {
name: {
formatterArgs: {
can: true
}
// formatter: (row) => {
// return row.meta.verbose_name != null ? row.meta.verbose_name : row.name
// }
},
comment: {
label: 'comment',
formatter: (row) => {
return row.meta.comment
}
},
queue: {
label: 'queue',
formatter: (row) => {
return row.meta.queue
}
},
last_published_time: {
width: '210px',
formatter: (row) => {
return row.last_published_time != null ? row.last_published_time : '-'
}
},
count: {
label: 'success/total',
width: '120px',
formatter: (row) => {
return <div>
<span Class='text-primary'>{row.success_count}</span>/
<span>{row.publish_count}</span>
</div>
}
},
state: {
width: '60px',
align: 'center',
formatter: (row) => {
switch (row.state) {
case 'green':
return <i Class='fa fa-circle-o text-primary' />
case 'yellow':
return <i Class='fa fa-circle-o text-warning' />
case 'red':
return <i Class='fa fa-circle-o text-danger' />
}
}
},
// runtimes: {
// label: this.$t('ops.runTimes'),
// width: '120px',
// formatter: function(row) {
// return (<div>
// <span Class='text-primary'>{row.summary.success}</span>/
// <span Class='text-danger'>{row.summary.failed}</span>/
// <span>{row.summary.total}</span>
// </div>)
// }
// },
// host_amount: {
// label: this.$t('ops.hosts'),
// width: '65px',
// formatter: function(row) {
// return _.get(row, 'latest_execution.hosts_amount', 0)
// }
// },
// is_success: {
// label: this.$t('ops.success'),
// align: 'center',
// width: '80px',
// formatter: row => {
// if (_.get(row, 'latest_execution.is_success', false)) {
// return <i Class='fa fa-check text-primary'/>
// }
// return <i Class='fa fa-times text-danger'/>
// }
// },
// date_start: {
// label: this.$t('ops.date'),
// width: '150px',
// formatter: function(row) {
// if (_.get(row, 'latest_execution.date_start', false)) {
// return toSafeLocalDateStr(row.latest_execution.date_start)
// }
// return ''
// }
// },
// time: {
// label: this.$t('ops.time'),
// width: '100px',
// formatter: function(row) {
// if (_.get(row, 'latest_execution.date_start', false)) {
// return timeOffset(row.latest_execution.date_start, row.latest_execution.date_finished)
// }
// return ''
// }
// },
actions: {
prop: 'id',
formatterArgs: {
// hasUpdate: false,
// hasClone: false,
// canDelete: this.$hasPerm('ops.delete_task'),
// onDelete: function({ row, col, cellValue, reload }) {
// const msg = this.$t('common.deleteWarningMsg') + ` "${row.display_name || row.name}" ` + '?'
// const title = this.$t('common.Info')
// this.$alert(msg, title, {
// type: 'warning',
// confirmButtonClass: 'el-button--danger',
// showCancelButton: true,
// beforeClose: async(action, instance, done) => {
// if (action !== 'confirm') return done()
// instance.confirmButtonLoading = true
// try {
// await performDelete.bind(this)({ row: row, col: col })
// done()
// reload()
// this.$message.success(this.$t('common.deleteSuccessMsg'))
// } finally {
// instance.confirmButtonLoading = false
// }
// }
// })
// },
// extraActions: [
// {
// name: 'run',
// can: this.$hasPerm('ops.add_adhoc'),
// title: this.$t('ops.run'),
// type: 'primary',
// callback: function({ row, tableData }) {
// this.$axios.get(
// `/api/v1/ops/tasks/${row.id}/run/`
// ).then(res => {
// openTaskPage(res['task'])
// })
// }
// }
// ]
}
}
}
},
headerActions: {
hasRightActions: false,
hasCreate: false
}
}
},
methods: {}
}
</script>
<style lang="less" scoped>
</style>

View File

@@ -13,7 +13,7 @@
<script type="text/jsx">
import DetailCard from '@/components/DetailCard'
import { toSafeLocalDateStr } from '@/utils/common'
import RunInfoCard from '../../RunInfoCard'
import RunInfoCard from '../../../tasks/RunInfoCard'
import { toLastFailureDisplay, toLastSucessDisplay } from '../business'
import { openTaskPage } from '@/utils/jms'

View File

@@ -19,7 +19,7 @@ export default {
data() {
return {
tableConfig: {
url: `/api/v1/ops/tasks/${this.object.id}/executions/`,
url: `/api/v1/ops/task-executions/?task_id=${this.object.id}`,
columns: [
'id', 'state', 'is_finished', 'date_published', 'date_start', 'date_finished', 'actions'
],

View File

@@ -0,0 +1,81 @@
<template>
<GenericListPage :table-config="tableConfig" :header-actions="headerActions" />
</template>
<script type="text/jsx">
import { GenericListPage } from '@/layout/components'
export default {
components: {
GenericListPage
},
data() {
return {
tableConfig: {
url: '/api/v1/ops/tasks/',
columns: [
'name', 'queue', 'comment', 'count', 'state', 'last_published_time'
],
columnsMeta: {
name: {
formatterArgs: {
can: true
}
},
comment: {
label: 'comment',
formatter: (row) => {
return row.meta.comment
}
},
queue: {
label: 'queue',
formatter: (row) => {
return row.meta.queue
}
},
last_published_time: {
width: '210px',
formatter: (row) => {
return row.last_published_time != null ? row.last_published_time : '-'
}
},
count: {
label: 'success/total',
width: '120px',
formatter: (row) => {
return <div>
<span Class='text-primary'>{row.success_count}</span>/
<span>{row.publish_count}</span>
</div>
}
},
state: {
width: '60px',
align: 'center',
formatter: (row) => {
switch (row.state) {
case 'green':
return <i Class='fa fa-circle-o text-primary' />
case 'yellow':
return <i Class='fa fa-circle-o text-warning' />
case 'red':
return <i Class='fa fa-circle-o text-danger' />
}
}
}
}
},
headerActions: {
hasRightActions: false,
hasLeftActions: false
}
}
},
methods: {}
}
</script>
<style lang="less" scoped>
</style>