mirror of
https://github.com/jumpserver/lina.git
synced 2025-08-31 06:34:13 +00:00
feat: 拆分ops 和task 菜单
This commit is contained in:
@@ -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",
|
||||
|
@@ -751,7 +751,7 @@
|
||||
"TicketsTodo": "To-doオーダー",
|
||||
"TicketsDone": "すでに勤務単",
|
||||
"TicketsNew": "ワークオーダーを提出する",
|
||||
"BatchCommandLog": "一括コマンド",
|
||||
"tasksLog": "一括コマンド",
|
||||
"CeleryTaskLog": "Celeryタスクログ",
|
||||
"CommandExecutions": "コマンド実行",
|
||||
"CommandFilterCreate": "コマンドフィルタの作成",
|
||||
|
@@ -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": "标签管理",
|
||||
|
@@ -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']
|
||||
}
|
||||
}
|
||||
|
@@ -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: {
|
||||
|
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -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'] }
|
||||
}
|
||||
]
|
32
src/router/console/tasks.js
Normal file
32
src/router/console/tasks.js
Normal 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'] }
|
||||
}
|
||||
]
|
@@ -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
13
src/views/ops/Command.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Command'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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'
|
||||
|
@@ -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'
|
||||
],
|
81
src/views/tasks/TaskList.vue
Normal file
81
src/views/tasks/TaskList.vue
Normal 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>
|
Reference in New Issue
Block a user