mirror of
https://github.com/jumpserver/lina.git
synced 2026-01-15 14:24:39 +00:00
Merge branch 'v3' of github.com:jumpserver/lina into v3
This commit is contained in:
@@ -58,7 +58,7 @@
|
||||
"nprogress": "0.2.0",
|
||||
"path-to-regexp": "2.4.0",
|
||||
"vue": "2.6.10",
|
||||
"vue-codemirror-lite": "^1.0.4",
|
||||
"vue-codemirror": "4.0.6",
|
||||
"vue-cookie": "^1.1.4",
|
||||
"vue-echarts": "^5.0.0-beta.0",
|
||||
"vue-i18n": "^8.15.5",
|
||||
|
||||
76
src/components/FormFields/CodeEditor.vue
Normal file
76
src/components/FormFields/CodeEditor.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div style="font-size: 12px">
|
||||
<div
|
||||
v-if="toolbar.length>0"
|
||||
style="height: 100%;width: 100%;vertical-align: middle;display: inline-block;background-color: #1ab394"
|
||||
>
|
||||
<template v-for="(item,index) in toolbar">
|
||||
<el-button v-if="item.type==='button'" :key="index" size="mini">
|
||||
<i :class="item.icon" />
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
<codemirror ref="myCm" v-model="iValue" :options="cmOptions" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { codemirror } from 'vue-codemirror'
|
||||
|
||||
import 'codemirror/mode/shell/shell'
|
||||
import 'codemirror/mode/powershell/powershell'
|
||||
import 'codemirror/mode/python/python'
|
||||
import 'codemirror/mode/ruby/ruby'
|
||||
|
||||
// theme css
|
||||
import 'codemirror/theme/base16-dark.css'
|
||||
import 'codemirror/theme/base16-light.css'
|
||||
import 'codemirror/theme/idea.css'
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
codemirror
|
||||
},
|
||||
props: {
|
||||
toolbar: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
value: {
|
||||
type: [String, Object],
|
||||
default: () => ''
|
||||
},
|
||||
cmOptions: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
tabSize: 4,
|
||||
mode: 'shell',
|
||||
theme: 'base16-dark',
|
||||
lineNumbers: true,
|
||||
line: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
iValue: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -361,6 +361,7 @@
|
||||
"TableColSettingInfo": "请选择您想显示的列表详细信息。",
|
||||
"Add": "添加",
|
||||
"TemplateAdd": "模版添加",
|
||||
"Task": "任务",
|
||||
"UpdateAssetDetail": "配置更多信息",
|
||||
"AddSuccessMsg": "添加成功",
|
||||
"Auth": "认证",
|
||||
@@ -682,7 +683,16 @@
|
||||
"PENDING": "等待中",
|
||||
"RUNNING": "运行中",
|
||||
"SUCCESS": "成功",
|
||||
"FAILURE": "失败"
|
||||
"FAILURE": "失败",
|
||||
"script": "脚本列表",
|
||||
"privilegeOnly": "仅选择特权账号",
|
||||
"privilegeFirst": "优先选择特权账号",
|
||||
"skip": "忽略当前资产",
|
||||
"instantAdhoc": "即时命令",
|
||||
"myAdhoc": "我的命令",
|
||||
"history": "历史记录",
|
||||
"createAdhoc": "创建命令",
|
||||
"AdhocDetail": "命令详情"
|
||||
},
|
||||
"perms": {
|
||||
"": "",
|
||||
@@ -862,6 +872,9 @@
|
||||
"GatewayUpdate": "更新网关",
|
||||
"TaskCenter": "任务中心",
|
||||
"JobCenter": "作业中心",
|
||||
"JobList": "作业列表",
|
||||
"JobCreate": "创建作业",
|
||||
"JobUpdate": "更新作业",
|
||||
"LabelCreate": "创建标签",
|
||||
"LabelList": "标签管理",
|
||||
"LabelUpdate": "更新标签",
|
||||
|
||||
@@ -90,24 +90,120 @@ export default {
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'a',
|
||||
name: 'CommandExecutions2',
|
||||
component: () => import('@/views/ops/Command'),
|
||||
path: 'adhoc',
|
||||
name: 'Adhoc',
|
||||
component: () => import('@/views/ops/Adhoc'),
|
||||
meta: {
|
||||
title: i18n.t('route.BatchCommand'),
|
||||
icon: 'terminal',
|
||||
permissions: []
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
name: 'CommandExecutions',
|
||||
component: () => import('@/views/ops/Command'),
|
||||
path: 'command/:id',
|
||||
component: () => import('@/views/ops/Adhoc/my/AdhocDetail'),
|
||||
name: 'AdhocDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('route.AdhocDetail'),
|
||||
permissions: [],
|
||||
activeMenu: '/workbench/ops/adhoc'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'command/:id/update',
|
||||
name: 'AdhocUpdate',
|
||||
component: () => import('@/views/ops/Adhoc/my/AdhocUpdateCreate'),
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('route.updateAdhoc'),
|
||||
permissions: [],
|
||||
activeMenu: '/workbench/ops/adhoc'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'command/create',
|
||||
name: 'AdhocCreate',
|
||||
hidden: true,
|
||||
component: () => import('@/views/ops/Adhoc/my/AdhocUpdateCreate'),
|
||||
meta: {
|
||||
title: i18n.t('ops.createAdhoc'),
|
||||
permissions: [],
|
||||
activeMenu: '/workbench/ops/adhoc'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'playbook',
|
||||
name: 'Playbook',
|
||||
component: () => import('@/views/ops/Playbook'),
|
||||
meta: {
|
||||
title: i18n.t('route.BatchScript'),
|
||||
icon: 'book',
|
||||
permissions: []
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'flow/create',
|
||||
name: 'PlaybookCreate',
|
||||
hidden: true,
|
||||
component: () => import('@/views/ops/Playbook/PlaybookUpdateCreate'),
|
||||
meta: {
|
||||
title: i18n.t('route.PlaybookCreate'),
|
||||
permissions: [],
|
||||
activeMenu: '/workbench/ops/playbook'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'job',
|
||||
name: 'Job',
|
||||
component: empty,
|
||||
redirect: '',
|
||||
meta: {
|
||||
title: i18n.t('route.JobList'),
|
||||
permissions: []
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'JobList',
|
||||
component: () => import('@/views/ops/Job'),
|
||||
meta: {
|
||||
title: i18n.t('route.JobList'),
|
||||
permissions: []
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/ops/Job/JobUpdateCreate'),
|
||||
name: 'JobCreate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('route.JobCreate'),
|
||||
permissions: [],
|
||||
activeMenu: '/workbench/ops/job'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'update',
|
||||
component: () => import('@/views/ops/Job/JobUpdateCreate'),
|
||||
name: 'JobUpdate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('route.JobUpdate'),
|
||||
permissions: [],
|
||||
activeMenu: '/workbench/ops/job'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/ops/Job/JobDetail'),
|
||||
name: 'JobDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('route.JobDetail'),
|
||||
permissions: [],
|
||||
activeMenu: '/workbench/ops/job'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
tableConfig: {
|
||||
url: `/api/v1/xpack/change-auth-plan/plan/${this.object.id}/assets/`,
|
||||
url: `/api/v1/assets/automation/${this.object.id}/assets/`,
|
||||
columns: [
|
||||
'name', 'ip', 'delete_action'
|
||||
],
|
||||
@@ -51,7 +51,7 @@ export default {
|
||||
formatter: DeleteActionFormatter,
|
||||
onDelete: function(col, row, cellValue, reload) {
|
||||
this.$axios.patch(
|
||||
`/api/v1/xpack/change-auth-plan/plan/${this.object.id}/asset/remove/`,
|
||||
`/api/v1/assets/automation/${this.object.id}/asset/remove/`,
|
||||
{ assets: [row.id] }
|
||||
).then(res => {
|
||||
this.$message.success(this.$tc('common.deleteSuccessMsg'))
|
||||
@@ -84,7 +84,7 @@ export default {
|
||||
return this.object.assets.indexOf(row.id) === -1
|
||||
},
|
||||
performAdd: (items, that) => {
|
||||
const relationUrl = `/api/v1/xpack/change-auth-plan/plan/${this.object.id}/asset/add/`
|
||||
const relationUrl = `/api/v1/assets/automation/${this.object.id}/asset/add/`
|
||||
const data = {
|
||||
assets: items
|
||||
}
|
||||
@@ -108,7 +108,7 @@ export default {
|
||||
disabled: this.$store.getters.currentOrgIsRoot,
|
||||
hasObjectsId: this.object.nodes,
|
||||
performAdd: (items, that) => {
|
||||
const relationUrl = `/api/v1/xpack/change-auth-plan/plan/${this.object.id}/nodes/?action=add`
|
||||
const relationUrl = `/api/v1/assets/automation/${this.object.id}/nodes/?action=add`
|
||||
const nodes = items.map(v => v.value)
|
||||
const iHasObjects = that.iHasObjects.map(v => v.value)
|
||||
const data = {
|
||||
@@ -126,7 +126,7 @@ export default {
|
||||
const data = {
|
||||
nodes: [item.value]
|
||||
}
|
||||
const relationUrl = `/api/v1/xpack/change-auth-plan/plan/${this.object.id}/nodes/?action=remove`
|
||||
const relationUrl = `/api/v1/assets/automation/${this.object.id}/nodes/?action=remove`
|
||||
return this.$axios.patch(relationUrl, data)
|
||||
},
|
||||
onDeleteSuccess: (obj, that) => {
|
||||
|
||||
@@ -28,25 +28,17 @@ export default {
|
||||
computed: {
|
||||
detailItems() {
|
||||
return [
|
||||
{
|
||||
key: this.$t('xpack.ChangeAuthPlan.Username'),
|
||||
value: this.object.username
|
||||
},
|
||||
{
|
||||
key: this.$t('xpack.ChangeAuthPlan.AssetAmount'),
|
||||
value: this.object.assets_amount
|
||||
value: this.object.snapshot.asset_amount
|
||||
},
|
||||
{
|
||||
key: this.$t('xpack.ChangeAuthPlan.NodeAmount'),
|
||||
value: this.object.nodes_amount
|
||||
value: this.object.snapshot.node_amount
|
||||
},
|
||||
{
|
||||
key: this.$t('xpack.ChangeAuthPlan.PasswordStrategy'),
|
||||
value: this.object.password_strategy_display
|
||||
},
|
||||
{
|
||||
key: this.$t('xpack.ChangeAuthPlan.TimeDelta'),
|
||||
value: this.object.timedelta.toFixed(2) + 's'
|
||||
value: this.object.trigger_display
|
||||
},
|
||||
{
|
||||
key: this.$t('xpack.ChangeAuthPlan.DateStart'),
|
||||
|
||||
@@ -19,12 +19,11 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
tableConfig: {
|
||||
url: `/api/v1/assets/change-secret-records/?execution_id=${this.object.id}`,
|
||||
columns: [
|
||||
'username', 'asset', 'is_success', 'timedelta', 'date_start', 'reason_display', 'actions'
|
||||
'asset', 'account', 'date_started', 'date_finished', 'status', 'error'
|
||||
],
|
||||
columnsMeta: {
|
||||
asset: {
|
||||
@@ -33,12 +32,28 @@ export default {
|
||||
formatterArgs: {
|
||||
can: this.$hasPerm('assets.view_asset'),
|
||||
getTitle({ row }) {
|
||||
return row.asset_info.name
|
||||
return row.asset.name
|
||||
},
|
||||
getRoute({ row }) {
|
||||
return {
|
||||
name: 'AssetDetail',
|
||||
params: { id: row.asset }
|
||||
params: { id: row.asset.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
account: {
|
||||
label: this.$t('users.Username'),
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
can: this.$hasPerm('assets.view_account'),
|
||||
getTitle({ row }) {
|
||||
return row.account.name
|
||||
},
|
||||
getRoute({ row }) {
|
||||
return {
|
||||
name: 'AssetDetail',
|
||||
params: { id: row.account.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,28 +70,6 @@ export default {
|
||||
},
|
||||
reason_display: {
|
||||
label: this.$t('xpack.AccountBackupPlan.Reason')
|
||||
},
|
||||
actions: {
|
||||
formatterArgs: {
|
||||
hasDelete: false,
|
||||
hasUpdate: false,
|
||||
hasClone: false,
|
||||
extraActions: [
|
||||
{
|
||||
name: 'retry',
|
||||
type: 'info',
|
||||
title: this.$t('xpack.ChangeAuthPlan.Retry'),
|
||||
can: vm.$hasPerm('xpack.change_changeauthplantask'),
|
||||
callback: function({ row, tableData }) {
|
||||
this.$axios.put(
|
||||
`/api/v1/assets/change-secret-records/${row.id}/`,
|
||||
).then(res => {
|
||||
window.open(`/#/ops/celery/task/${res.task}/log/`, '_blank', 'toolbar=yes, width=900, height=600')
|
||||
})
|
||||
}.bind(this)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -21,6 +21,7 @@ export default {
|
||||
return {
|
||||
execution: { id: '' },
|
||||
config: {
|
||||
url: '/api/v1/assets/automation-executions/',
|
||||
activeMenu: 'ChangeAuthPlanExecutionInfo',
|
||||
actions: {
|
||||
hasUpdate: false,
|
||||
@@ -30,12 +31,12 @@ export default {
|
||||
{
|
||||
title: this.$t('common.BasicInfo'),
|
||||
name: 'ChangeAuthPlanExecutionInfo',
|
||||
hidden: () => !this.$hasPerm('xpack.view_changeauthplanexecution')
|
||||
hidden: () => !this.$hasPerm('assets.view_automationexecution')
|
||||
},
|
||||
{
|
||||
title: this.$t('xpack.ChangeAuthPlan.TaskList'),
|
||||
name: 'ChangeAuthPlanExecutionTaskList',
|
||||
hidden: () => !this.$hasPerm('xpack.view_changeauthplantask')
|
||||
hidden: () => !this.$hasPerm('assets.view_changesecretrecord')
|
||||
}
|
||||
],
|
||||
getTitle: this.getExecutionTitle
|
||||
|
||||
@@ -18,41 +18,32 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
console.log('this', this)
|
||||
return {
|
||||
tableConfig: {
|
||||
url: `/api/v1/xpack/change-auth-plan/plan-execution/?plan_id=${this.object.id}`,
|
||||
url: `/api/v1/assets/automation-executions/?automation_id=${this.object.id}`,
|
||||
columns: [
|
||||
'username', 'assets_amount', 'nodes_amount', 'result_summary', 'password_strategy_display',
|
||||
'timedelta', 'trigger_display', 'date_start', 'actions'
|
||||
'asset_amount', 'node_amount', 'status',
|
||||
'trigger_display', 'date_start', 'actions'
|
||||
],
|
||||
columnsMeta: {
|
||||
username: {
|
||||
label: this.$t('xpack.ChangeAuthPlan.Username')
|
||||
},
|
||||
assets_amount: {
|
||||
asset_amount: {
|
||||
label: this.$t('xpack.ChangeAuthPlan.AssetAmount'),
|
||||
width: '80px'
|
||||
},
|
||||
nodes_amount: {
|
||||
label: this.$t('xpack.ChangeAuthPlan.NodeAmount'),
|
||||
width: '80px'
|
||||
},
|
||||
result_summary: {
|
||||
label: this.$t('xpack.ChangeAuthPlan.Result'),
|
||||
width: '80px',
|
||||
showOverflowTooltip: true,
|
||||
formatter: function(row) {
|
||||
const summary = <div>
|
||||
<span class='text-primary'>{row.result_summary.succeed}</span>/
|
||||
<span class='text-danger'>{row.result_summary.failed}</span>/
|
||||
<span>{row.result_summary.total}</span>
|
||||
</div>
|
||||
return summary
|
||||
return <span>{ row.snapshot.asset_amount }</span>
|
||||
}
|
||||
},
|
||||
password_strategy_display: {
|
||||
label: this.$t('xpack.ChangeAuthPlan.PasswordStrategy'),
|
||||
width: '220px',
|
||||
node_amount: {
|
||||
label: this.$t('xpack.ChangeAuthPlan.NodeAmount'),
|
||||
width: '80px',
|
||||
formatter: function(row) {
|
||||
return <span>{ row.snapshot.node_amount }</span>
|
||||
}
|
||||
},
|
||||
status: {
|
||||
label: this.$t('xpack.ChangeAuthPlan.Result'),
|
||||
width: '80px',
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
timedelta: {
|
||||
|
||||
@@ -34,12 +34,12 @@ export default {
|
||||
attrs: {
|
||||
type: 'primary',
|
||||
label: this.$t('xpack.ChangeAuthPlan.Execute'),
|
||||
disabled: !this.$hasPerm('xpack.add_changeauthplanexecution')
|
||||
disabled: !this.$hasPerm('assets.add_automationexecution')
|
||||
},
|
||||
callbacks: {
|
||||
click: function() {
|
||||
this.$axios.post(
|
||||
`/api/v1/xpack/change-auth-plan/plan-execution/`,
|
||||
`/api/v1/assets/automation-executions/`,
|
||||
{ plan: this.object.id }
|
||||
).then(res => {
|
||||
window.open(`/#/ops/celery/task/${res.task}/log/`, '_blank', 'toolbar=yes, width=900, height=600')
|
||||
@@ -59,19 +59,19 @@ export default {
|
||||
},
|
||||
{
|
||||
key: this.$t('xpack.ChangeAuthPlan.Username'),
|
||||
value: this.object.username
|
||||
value: this.object.accounts.join(', ')
|
||||
},
|
||||
{
|
||||
key: this.$t('xpack.ChangeAuthPlan.AssetAmount'),
|
||||
value: this.object.assets_amount
|
||||
value: this.object.assets.length
|
||||
},
|
||||
{
|
||||
key: this.$t('xpack.ChangeAuthPlan.NodeAmount'),
|
||||
value: this.object.nodes_amount
|
||||
value: this.object.nodes.length
|
||||
},
|
||||
{
|
||||
key: this.$t('xpack.ChangeAuthPlan.PasswordStrategy'),
|
||||
value: this.object.password_strategy_display
|
||||
value: this.object.secret_strategy.label
|
||||
},
|
||||
{
|
||||
key: this.$t('xpack.ChangeAuthPlan.RegularlyPerform'),
|
||||
|
||||
@@ -24,21 +24,22 @@ export default {
|
||||
plan: { name: '', username: '', comment: '' },
|
||||
config: {
|
||||
activeMenu: 'ChangeAuthPlanInfo',
|
||||
url: '/api/v1/assets/change-secret-automations/',
|
||||
submenu: [
|
||||
{
|
||||
title: this.$t('common.BasicInfo'),
|
||||
name: 'ChangeAuthPlanInfo',
|
||||
hidden: () => !this.$hasPerm('xpack.view_changeauthplan')
|
||||
hidden: () => !this.$hasPerm('assets.view_changesecretautomation')
|
||||
},
|
||||
{
|
||||
title: this.$t('xpack.ChangeAuthPlan.AssetAndNode'),
|
||||
name: 'ChangeAuthPlanAsset',
|
||||
hidden: () => !this.$hasPerm('xpack.change_changeauthplan')
|
||||
hidden: () => !this.$hasPerm('assets.change_changesecretautomation')
|
||||
},
|
||||
{
|
||||
title: this.$t('xpack.ChangeAuthPlan.ExecutionList'),
|
||||
name: 'ChangeAuthPlanExecutionList',
|
||||
hidden: () => !this.$hasPerm('xpack.view_changeauthplanexecution')
|
||||
hidden: () => !this.$hasPerm('assets.view_automationexecution')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -92,7 +92,10 @@ export default {
|
||||
callback: function({ row }) {
|
||||
this.$axios.post(
|
||||
`/api/v1/assets/automation-executions/`,
|
||||
{ plan: row.id }
|
||||
{
|
||||
automation: row.id,
|
||||
type: row.type
|
||||
}
|
||||
).then(res => {
|
||||
openTaskPage(res['task'])
|
||||
})
|
||||
|
||||
@@ -126,8 +126,7 @@ export const getFields = () => {
|
||||
},
|
||||
accounts: {
|
||||
label: i18n.t('common.Username'),
|
||||
component: TagInput,
|
||||
helpText: i18n.t('xpack.ChangeAuthPlan.HelpText.UsernameOfCreateUpdatePage')
|
||||
component: TagInput
|
||||
},
|
||||
secret: {
|
||||
hidden: ({ secret_strategy, secret_type }) => (secret_strategy !== 'specific' || secret_type !== 'password')
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:fields-meta="fieldsMeta"
|
||||
:clean-form-value="cleanFormValue"
|
||||
:after-get-form-value="afterGetFormValue"
|
||||
:after-get-remote-meta="handleAfterGetRemoteMeta"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -74,6 +75,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleAfterGetRemoteMeta(meta) {
|
||||
this.fieldsMeta.su_method.options = meta?.su_method?.choices || []
|
||||
},
|
||||
async setCategories() {
|
||||
const category = this.$route.query.category
|
||||
const type = this.$route.query.type
|
||||
|
||||
@@ -72,6 +72,7 @@ export const platformFieldsMeta = (vm) => {
|
||||
},
|
||||
su_method: {
|
||||
type: 'select',
|
||||
options: [],
|
||||
hidden: (form) => !form['su_enabled']
|
||||
}
|
||||
}
|
||||
|
||||
278
src/views/ops/Adhoc/InstantAdhoc.vue
Normal file
278
src/views/ops/Adhoc/InstantAdhoc.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<el-collapse-transition>
|
||||
<div style="display: flex;justify-items: center; flex-wrap: nowrap;justify-content:space-between;">
|
||||
<el-form ref="form" label-width="80px">
|
||||
<el-form-item label="选择资产">
|
||||
<el-input />
|
||||
</el-form-item>
|
||||
<el-form-item label="选择账号">
|
||||
<el-input />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div :style="iShowTree?('display: flex;width: 80%;'):('display: flex;width:100%;')">
|
||||
<IBox class="transition-box" style="width: calc(100% - 17px);">
|
||||
<div style="margin-bottom: 20px">
|
||||
<CodeEditor />
|
||||
</div>
|
||||
Output
|
||||
<Term ref="xterm" />
|
||||
</IBox>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Term from '@/components/Term'
|
||||
import IBox from '@/components/IBox'
|
||||
import CodeEditor from '@/components/FormFields/CodeEditor'
|
||||
|
||||
export default {
|
||||
name: 'CommandExecution',
|
||||
components: {
|
||||
Term,
|
||||
IBox,
|
||||
CodeEditor
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
languageOptions: [
|
||||
{
|
||||
label: 'Shell',
|
||||
value: 'shell'
|
||||
},
|
||||
{
|
||||
label: 'Python',
|
||||
value: 'python'
|
||||
},
|
||||
{
|
||||
label: 'Ruby',
|
||||
value: 'ruby'
|
||||
},
|
||||
{
|
||||
label: 'PowerShell',
|
||||
value: 'powershell'
|
||||
}
|
||||
],
|
||||
DataZTree: 0,
|
||||
codeMirrorOptions: {
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
mode: 'shell'
|
||||
},
|
||||
treeSetting: {
|
||||
treeUrl: '/api/v1/perms/users/nodes-with-assets/tree/',
|
||||
showRefresh: true,
|
||||
showMenu: false,
|
||||
showSearch: true,
|
||||
customTreeHeader: true,
|
||||
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)
|
||||
},
|
||||
async: {
|
||||
enable: false
|
||||
}
|
||||
},
|
||||
iShowTree: true,
|
||||
actions: '',
|
||||
options: [],
|
||||
ws: '',
|
||||
wsConnected: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
codemirror() {
|
||||
return this.$refs.myCm.codemirror
|
||||
},
|
||||
zTree() {
|
||||
return this.$refs.AutoDataZTree.$refs.dataztree.$refs.ztree.zTree
|
||||
},
|
||||
xterm() {
|
||||
return this.$refs.xterm.xterm
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchAccountUsernames()
|
||||
this.xterm.write(this.$t('ops.selectAssetsMessage'))
|
||||
this.enableWS()
|
||||
},
|
||||
methods: {
|
||||
fetchAccountUsernames() {
|
||||
const temp = {}
|
||||
this.$axios.get('/api/v1/assets/accounts/',).then(data => {
|
||||
data.forEach((item) => {
|
||||
temp[item.username] = {}
|
||||
})
|
||||
for (const key of Object.keys(temp)) {
|
||||
this.accountConfig.usernameOptions.push({ 'label': key, 'value': key })
|
||||
}
|
||||
})
|
||||
},
|
||||
onChangeLanguage() {
|
||||
this.codemirror.setOption('mode', this.scriptMeta.language)
|
||||
},
|
||||
onOpenScriptListDialog() {
|
||||
this.showScriptListDialog = true
|
||||
},
|
||||
onOpenScriptSaveDialog() {
|
||||
this.showScriptAddDialog = true
|
||||
},
|
||||
onOpenAccountPolicyDialog() {
|
||||
this.showAccountPolicyDialog = true
|
||||
},
|
||||
onSelectScript(scriptId) {
|
||||
this.$axios.get(`/api/v1/ops/scripts/${scriptId}`).then((data) => {
|
||||
this.scriptMeta.content = data['content']
|
||||
})
|
||||
},
|
||||
onSelectAccountPolicy(policy) {
|
||||
this.accountConfig.accountPolicy = policy
|
||||
},
|
||||
getSelectedAssetsNode() {
|
||||
const nodes = this.$refs.AutoDataZTree.$refs.dataztree.$refs.ztree.getCheckedNodes()
|
||||
const assetsNode = []
|
||||
nodes.forEach(function(node) {
|
||||
if (node.meta.type === 'asset' && !node.isHidden) {
|
||||
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
|
||||
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.accountConfig.selectedUsername
|
||||
const command = this.scriptMeta.content
|
||||
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>
|
||||
|
||||
> > > .el-input input {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.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: calc(100% - 17px);
|
||||
height: 480px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.tree-box {
|
||||
margin-right: 2px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
</style>
|
||||
66
src/views/ops/Adhoc/MyAdhoc.vue
Normal file
66
src/views/ops/Adhoc/MyAdhoc.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<GenericListTable :table-config="tableConfig" :header-actions="headerActions" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericListTable from '@/layout/components/GenericListTable'
|
||||
import { ActionsFormatter } from '@/components/TableFormatters'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericListTable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableConfig: {
|
||||
url: '/api/v1/ops/adhocs/',
|
||||
columns: [
|
||||
'name', 'module', 'date_updated', 'date_created', 'actions'
|
||||
],
|
||||
columnsMeta: {
|
||||
name: {
|
||||
formatterArgs: {
|
||||
can: true,
|
||||
route: 'AdhocDetail'
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
formatter: ActionsFormatter,
|
||||
formatterArgs: {
|
||||
hasUpdate: true,
|
||||
canUpdate: true,
|
||||
updateRoute: 'AdhocUpdate',
|
||||
hasDelete: true,
|
||||
canDelete: true,
|
||||
hasClone: false,
|
||||
extraActions: [
|
||||
{
|
||||
title: this.$t('run'),
|
||||
name: 'run',
|
||||
type: 'running',
|
||||
can: true,
|
||||
callback: ({ row }) => {
|
||||
this.$router.push({ name: 'JobCreate', query: { type: 'adhoc', id: row.id }})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
canCreate: true,
|
||||
createRoute: 'AdhocCreate',
|
||||
hasRefresh: true,
|
||||
hasExport: false,
|
||||
hasImport: false,
|
||||
hasMoreActions: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
51
src/views/ops/Adhoc/index.vue
Normal file
51
src/views/ops/Adhoc/index.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<TabPage
|
||||
:submenu="submenu"
|
||||
:active-menu.sync="activeMenu"
|
||||
>
|
||||
<component :is="activeMenu" :query="{type:'adhoc'}" />
|
||||
</TabPage>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { TabPage } from '@/layout/components'
|
||||
import MyAdhoc from '@/views/ops/Adhoc/MyAdhoc'
|
||||
import InstantAdhoc from '@/views/ops/Adhoc/InstantAdhoc'
|
||||
import JobHistory from '@/views/ops/Job/JobDetail/JobHistory'
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
components: {
|
||||
TabPage,
|
||||
MyAdhoc,
|
||||
InstantAdhoc,
|
||||
JobHistory
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeMenu: 'MyAdhoc',
|
||||
submenu: [
|
||||
{
|
||||
title: this.$t('ops.instantAdhoc'),
|
||||
name: 'InstantAdhoc'
|
||||
},
|
||||
{
|
||||
title: this.$t('ops.myAdhoc'),
|
||||
name: 'MyAdhoc'
|
||||
},
|
||||
{
|
||||
title: this.$t('ops.history'),
|
||||
name: 'JobHistory'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
52
src/views/ops/Adhoc/my/AdhocDetail/AdhocDetail.vue
Normal file
52
src/views/ops/Adhoc/my/AdhocDetail/AdhocDetail.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="14" :sm="24">
|
||||
<DetailCard :title="cardTitle" :items="detailCardItems" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script type="text/jsx">
|
||||
import DetailCard from '@/components/DetailCard'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DetailCard
|
||||
},
|
||||
props: {
|
||||
object: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
cardTitle() {
|
||||
return this.object.name
|
||||
},
|
||||
detailCardItems() {
|
||||
return [
|
||||
{
|
||||
key: this.$t('common.Name'),
|
||||
value: this.object.name
|
||||
},
|
||||
{
|
||||
key: 'Module',
|
||||
value: this.object.module
|
||||
},
|
||||
{
|
||||
key: 'content',
|
||||
value: this.object.args
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
42
src/views/ops/Adhoc/my/AdhocDetail/index.vue
Normal file
42
src/views/ops/Adhoc/my/AdhocDetail/index.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<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 } from '@/layout/components'
|
||||
import AdhocDetail from '@/views/ops/Adhoc/my/AdhocDetail/AdhocDetail'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericDetailPage,
|
||||
AdhocDetail
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
AdhocDetail: {},
|
||||
config: {
|
||||
getTitle(row) {
|
||||
return row['name']
|
||||
},
|
||||
url: '/api/v1/ops/adhocs/',
|
||||
activeMenu: 'AdhocDetail',
|
||||
submenu: [
|
||||
{
|
||||
title: this.$t('ops.AdhocDetail'),
|
||||
name: 'AdhocDetail'
|
||||
}
|
||||
],
|
||||
hasRightSide: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
49
src/views/ops/Adhoc/my/AdhocUpdateCreate.vue
Normal file
49
src/views/ops/Adhoc/my/AdhocUpdateCreate.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<GenericCreateUpdatePage v-bind="$data" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||
import CodeEditor from '@/components/FormFields/CodeEditor'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericCreateUpdatePage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cmOptions: {
|
||||
tabSize: 4,
|
||||
mode: 'shell',
|
||||
theme: 'base16-light',
|
||||
lineNumbers: true,
|
||||
line: true
|
||||
},
|
||||
url: '/api/v1/ops/adhocs/',
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['name', 'module', 'args']]
|
||||
],
|
||||
initial: {
|
||||
module: 'shell',
|
||||
args: ''
|
||||
},
|
||||
fieldsMeta: {
|
||||
args: {
|
||||
label: 'content',
|
||||
component: CodeEditor
|
||||
}
|
||||
},
|
||||
createSuccessNextRoute: {
|
||||
name: 'Adhoc'
|
||||
},
|
||||
updateSuccessNextRoute: {
|
||||
name: 'Adhoc'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
44
src/views/ops/Job/JobDetail/JobDetail.vue
Normal file
44
src/views/ops/Job/JobDetail/JobDetail.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="14" :sm="24">
|
||||
<DetailCard :title="cardTitle" :items="detailCardItems" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script type="text/jsx">
|
||||
import DetailCard from '@/components/DetailCard'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DetailCard
|
||||
},
|
||||
props: {
|
||||
object: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
cardTitle() {
|
||||
return this.object.name
|
||||
},
|
||||
detailCardItems() {
|
||||
return [
|
||||
{
|
||||
key: this.$t('common.Name'),
|
||||
value: this.object.name
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
113
src/views/ops/Job/JobDetail/JobHistory.vue
Normal file
113
src/views/ops/Job/JobDetail/JobHistory.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div>
|
||||
<GenericListTable :table-config="tableConfig" :header-actions="headerActions" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericListTable from '@/layout/components/GenericListTable'
|
||||
import { ActionsFormatter } from '@/components/TableFormatters'
|
||||
import { openTaskPage } from '@/utils/jms'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericListTable
|
||||
},
|
||||
props: {
|
||||
object: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
query: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showLogViewer: false,
|
||||
showLogId: '',
|
||||
tableConfig: {
|
||||
url: `/api/v1/ops/job-executions/`,
|
||||
columns: [
|
||||
'date_start', 'is_finished', 'is_success', 'time_cost', 'short_id', 'actions'
|
||||
],
|
||||
columnsMeta: {
|
||||
is_finished: {
|
||||
label: this.$t('ops.isFinished'),
|
||||
width: '96px',
|
||||
formatter: (row) => {
|
||||
if (row.is_finished) {
|
||||
return <i Class='fa fa-check text-primary' />
|
||||
}
|
||||
return <i Class='fa fa-times text-danger' />
|
||||
},
|
||||
formatterArgs: {
|
||||
width: '14px'
|
||||
}
|
||||
},
|
||||
is_success: {
|
||||
label: this.$t('ops.isSuccess'),
|
||||
width: '96px',
|
||||
formatter: (row) => {
|
||||
if (!row.is_finished) {
|
||||
return <i Class='fa fa fa-spinner fa-spin' />
|
||||
}
|
||||
if (row.is_success) {
|
||||
return <i Class='fa fa-check text-primary' />
|
||||
}
|
||||
return <i Class='fa fa-times text-danger' />
|
||||
},
|
||||
formatterArgs: {
|
||||
width: '14px'
|
||||
}
|
||||
},
|
||||
time_cost: {
|
||||
label: this.$t('ops.time'),
|
||||
width: '100px',
|
||||
formatter: function(row) {
|
||||
if (row.time_cost) {
|
||||
return row.time_cost.toFixed(2) + 's'
|
||||
}
|
||||
return '-'
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
formatter: ActionsFormatter,
|
||||
formatterArgs: {
|
||||
hasUpdate: false,
|
||||
hasDelete: false,
|
||||
hasClone: false,
|
||||
extraActions: [
|
||||
{
|
||||
name: 'showLog',
|
||||
title: this.$t('ops.output'),
|
||||
can: true,
|
||||
callback: ({ row }) => {
|
||||
openTaskPage(row.task_id)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasRightActions: false,
|
||||
hasLeftActions: false
|
||||
}
|
||||
}
|
||||
}, mounted() {
|
||||
if (this.object) {
|
||||
this.tableConfig.url += `?job_id=${this.object.id}`
|
||||
}
|
||||
if (this.query) {
|
||||
this.tableConfig.url += `?type=${this.query.type}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
48
src/views/ops/Job/JobDetail/index.vue
Normal file
48
src/views/ops/Job/JobDetail/index.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<GenericDetailPage :object.sync="JobDetail" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
|
||||
<keep-alive>
|
||||
<component :is="config.activeMenu" :object="JobDetail" />
|
||||
</keep-alive>
|
||||
</GenericDetailPage>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { GenericDetailPage } from '@/layout/components'
|
||||
import JobDetail from '@/views/ops/Job/JobDetail/JobDetail'
|
||||
import JobHistory from '@/views/ops/Job/JobDetail/JobHistory'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericDetailPage,
|
||||
JobDetail,
|
||||
JobHistory
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
JobDetail: {},
|
||||
config: {
|
||||
getTitle(row) {
|
||||
return row['name']
|
||||
},
|
||||
url: '/api/v1/ops/jobs/',
|
||||
activeMenu: 'JobDetail',
|
||||
submenu: [
|
||||
{
|
||||
title: this.$t('ops.AdhocDetail'),
|
||||
name: 'JobDetail'
|
||||
},
|
||||
{
|
||||
title: this.$t('ops.History'),
|
||||
name: 'JobHistory'
|
||||
}
|
||||
],
|
||||
hasRightSide: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
124
src/views/ops/Job/JobUpdateCreate.vue
Normal file
124
src/views/ops/Job/JobUpdateCreate.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div v-if="ready">
|
||||
<GenericCreateUpdatePage v-bind="$data" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||
import CodeEditor from '@/components/FormFields/CodeEditor'
|
||||
import AssetSelect from '@/components/AssetSelect'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericCreateUpdatePage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ready: false,
|
||||
instantTask: false,
|
||||
jobType: '',
|
||||
url: '/api/v1/ops/jobs/',
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['name', 'type', 'instant']],
|
||||
[this.$t('common.Task'), ['module', 'args', 'playbook']],
|
||||
['资产', ['assets', 'runas', 'runas_policy']],
|
||||
[this.$t('common.Plan'), ['runAfterSave']]
|
||||
],
|
||||
initial: {
|
||||
type: 'adhoc',
|
||||
module: 'shell',
|
||||
args: '',
|
||||
runas_policy: 'skip',
|
||||
runAfterSave: false,
|
||||
instant: false
|
||||
},
|
||||
fieldsMeta: {
|
||||
name: {
|
||||
hidden: (formValue) => {
|
||||
return this.instantTask
|
||||
}
|
||||
},
|
||||
type: {},
|
||||
module: {
|
||||
hidden: (formValue) => {
|
||||
return formValue.type !== 'adhoc'
|
||||
}
|
||||
},
|
||||
playbook: {
|
||||
hidden: (formValue) => {
|
||||
return formValue.type !== 'playbook'
|
||||
},
|
||||
el: {
|
||||
multiple: false,
|
||||
value: [],
|
||||
ajax: {
|
||||
url: '/api/v1/ops/playbooks/',
|
||||
transformOption: (item) => {
|
||||
return { label: item.name, value: item.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
assets: {
|
||||
type: 'assetSelect',
|
||||
component: AssetSelect,
|
||||
label: this.$t('perms.Asset'),
|
||||
rules: [{
|
||||
required: false
|
||||
}],
|
||||
el: {
|
||||
value: []
|
||||
}
|
||||
},
|
||||
args: {
|
||||
hidden: (formValue) => {
|
||||
return formValue.type !== 'adhoc'
|
||||
},
|
||||
component: CodeEditor
|
||||
},
|
||||
instant: {
|
||||
hidden: () => {
|
||||
return true
|
||||
}
|
||||
},
|
||||
runAfterSave: {
|
||||
label: '保存后立即执行',
|
||||
type: 'checkbox',
|
||||
hidden: (formValue) => {
|
||||
return formValue.instant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.$route.query && this.$route.query.type && this.$route.query.id) {
|
||||
this.initial.type = 'adhoc'
|
||||
switch (this.$route.query.type) {
|
||||
case 'adhoc':
|
||||
this.initial.type = 'adhoc'
|
||||
this.$axios.get(`/api/v1/ops/adhocs/${this.$route.query.id}`).then((data) => {
|
||||
this.initial.module = data.module
|
||||
this.initial.args = data.args
|
||||
this.initial.instant = true
|
||||
this.initial.runAfterSave = true
|
||||
this.instantTask = true
|
||||
this.createSuccessNextRoute = { name: 'Adhoc' }
|
||||
this.ready = true
|
||||
})
|
||||
break
|
||||
case 'playbook':
|
||||
break
|
||||
}
|
||||
} else {
|
||||
this.ready = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
75
src/views/ops/Job/index.vue
Normal file
75
src/views/ops/Job/index.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<GenericListPage :table-config="tableConfig" :header-actions="headerActions" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericListPage from '@/layout/components/GenericListPage'
|
||||
import { ActionsFormatter } from '@/components/TableFormatters'
|
||||
import { openTaskPage } from '@/utils/jms'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericListPage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableConfig: {
|
||||
url: '/api/v1/ops/jobs/',
|
||||
columns: [
|
||||
'name', 'type', 'date_updated', 'date_created', 'actions'
|
||||
],
|
||||
columnsMeta: {
|
||||
name: {
|
||||
formatterArgs: {
|
||||
can: true
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
formatter: ActionsFormatter,
|
||||
formatterArgs: {
|
||||
hasUpdate: true,
|
||||
canUpdate: true,
|
||||
updateRoute: 'JobUpdate',
|
||||
hasDelete: true,
|
||||
canDelete: true,
|
||||
hasClone: false,
|
||||
extraActions: [
|
||||
{
|
||||
title: '执行',
|
||||
name: 'run',
|
||||
type: 'running',
|
||||
can: true,
|
||||
callback: ({ row }) => {
|
||||
this.runJob(row)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
canCreate: true,
|
||||
createRoute: 'JobCreate',
|
||||
hasRefresh: true,
|
||||
hasExport: false,
|
||||
hasImport: false,
|
||||
hasMoreActions: false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
runJob(row) {
|
||||
this.$axios.post('/api/v1/ops/job-executions/', { job: row.id }).then(data => {
|
||||
this.$axios.get(`/api/v1/ops/job-executions/${data.id}/`).then(d => {
|
||||
openTaskPage(d.task_id)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Command'
|
||||
}
|
||||
</script>
|
||||
|
||||
74
src/views/ops/Playbook/PlaybookList.vue
Normal file
74
src/views/ops/Playbook/PlaybookList.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div>
|
||||
<GenericListTable :table-config="tableConfig" :header-actions="headerActions" />
|
||||
<UploadDialog :visible.sync="uploadDialogVisible" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericListTable from '@/layout/components/GenericListTable'
|
||||
import { ActionsFormatter } from '@/components/TableFormatters'
|
||||
import UploadDialog from '@/views/ops/Playbook/UploadDialog'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UploadDialog,
|
||||
GenericListTable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
uploadDialogVisible: false,
|
||||
tableConfig: {
|
||||
url: '/api/v1/ops/playbooks/',
|
||||
columns: [
|
||||
'name', 'path', 'date_updated', 'date_created', 'actions'
|
||||
],
|
||||
columnsMeta: {
|
||||
name: {
|
||||
formatterArgs: {
|
||||
can: true,
|
||||
route: 'AdhocDetail'
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
formatter: ActionsFormatter,
|
||||
formatterArgs: {
|
||||
hasUpdate: true,
|
||||
canUpdate: true,
|
||||
updateRoute: 'AdhocUpdate',
|
||||
hasDelete: true,
|
||||
canDelete: true,
|
||||
hasClone: false,
|
||||
extraActions: [
|
||||
{
|
||||
title: '执行',
|
||||
name: 'run',
|
||||
type: 'running',
|
||||
can: true,
|
||||
callback: ({ row }) => {
|
||||
this.$router.push({ name: 'JobCreate', query: { type: 'adhoc', id: row.id }})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
canCreate: true,
|
||||
hasRefresh: true,
|
||||
hasExport: false,
|
||||
hasImport: false,
|
||||
hasMoreActions: false,
|
||||
onCreate: () => {
|
||||
this.uploadDialogVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
49
src/views/ops/Playbook/PlaybookUpdateCreate.vue
Normal file
49
src/views/ops/Playbook/PlaybookUpdateCreate.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<GenericCreateUpdatePage v-bind="$data" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||
import CodeEditor from '@/components/FormFields/CodeEditor'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericCreateUpdatePage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cmOptions: {
|
||||
tabSize: 4,
|
||||
mode: 'shell',
|
||||
theme: 'base16-light',
|
||||
lineNumbers: true,
|
||||
line: true
|
||||
},
|
||||
url: '/api/v1/ops/adhocs/',
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['name', 'module', 'args']]
|
||||
],
|
||||
initial: {
|
||||
module: 'shell',
|
||||
args: ''
|
||||
},
|
||||
fieldsMeta: {
|
||||
args: {
|
||||
label: 'content',
|
||||
component: CodeEditor
|
||||
}
|
||||
},
|
||||
createSuccessNextRoute: {
|
||||
name: 'Adhoc'
|
||||
},
|
||||
updateSuccessNextRoute: {
|
||||
name: 'Adhoc'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
101
src/views/ops/Playbook/UploadDialog.vue
Normal file
101
src/views/ops/Playbook/UploadDialog.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<Dialog
|
||||
title="离线上传"
|
||||
v-bind="$attrs"
|
||||
@confirm="onSubmit"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<el-form label-position="top">
|
||||
<el-form-item
|
||||
:label="$tc('common.Upload' )"
|
||||
:label-width="'100px'"
|
||||
class="file-uploader"
|
||||
>
|
||||
<el-upload
|
||||
ref="upload"
|
||||
drag
|
||||
action="string"
|
||||
list-type="text/csv"
|
||||
:limit="1"
|
||||
:auto-upload="false"
|
||||
:on-change="onFileChange"
|
||||
:before-upload="beforeUpload"
|
||||
accept=".zip"
|
||||
>
|
||||
<i class="el-icon-upload" />
|
||||
<div class="el-upload__text">
|
||||
{{ $t('common.imExport.dragUploadFileInfo') }}
|
||||
</div>
|
||||
<div slot="tip" class="el-upload__tip">
|
||||
<span :class="{'hasError': hasFileFormatOrSizeError }">
|
||||
{{ $t('terminal.uploadZipTips') }}
|
||||
</span>
|
||||
<div v-if="renderError" class="hasError">{{ renderError }}</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Dialog } from '@/components'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Dialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasFileFormatOrSizeError: false,
|
||||
renderError: '',
|
||||
file: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onFileChange(file, fileList) {
|
||||
if (file.status !== 'ready') {
|
||||
return
|
||||
}
|
||||
this.file = file
|
||||
},
|
||||
beforeUpload(file) {
|
||||
},
|
||||
onSubmit() {
|
||||
if (!this.file) {
|
||||
return
|
||||
}
|
||||
const form = new FormData()
|
||||
form.append('path', this.file.raw)
|
||||
this.$axios.post(
|
||||
'/api/v1/ops/playbooks/',
|
||||
form,
|
||||
{
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
disableFlashErrorMsg: true
|
||||
}
|
||||
).then(res => {
|
||||
this.$message.success('上传成功')
|
||||
this.$emit('update:visible', false)
|
||||
}).catch(err => {
|
||||
this.$message.error(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.file-uploader.el-form-item {
|
||||
margin-bottom: 0;
|
||||
|
||||
> > > .el-upload {
|
||||
width: 100%;
|
||||
|
||||
.el-upload-dragger {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
45
src/views/ops/Playbook/index.vue
Normal file
45
src/views/ops/Playbook/index.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<TabPage
|
||||
:submenu="submenu"
|
||||
:active-menu.sync="activeMenu"
|
||||
>
|
||||
<component :is="activeMenu" />
|
||||
</TabPage>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { TabPage } from '@/layout/components'
|
||||
import PlaybookList from '@/views/ops/Playbook/PlaybookList'
|
||||
import History from '@/views/ops/Playbook/HIstory'
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
components: {
|
||||
TabPage,
|
||||
PlaybookList,
|
||||
History
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeMenu: 'PlaybookList',
|
||||
submenu: [
|
||||
{
|
||||
title: 'Playbook',
|
||||
name: 'PlaybookList'
|
||||
},
|
||||
{
|
||||
title: this.$t('ops.history'),
|
||||
name: 'History'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -14,7 +14,7 @@ export default {
|
||||
tableConfig: {
|
||||
url: '/api/v1/ops/tasks/',
|
||||
columns: [
|
||||
'name', 'queue', 'comment', 'count', 'state', 'last_published_time'
|
||||
'name', 'display_name', 'queue', 'comment', 'count', 'state', 'last_published_time'
|
||||
],
|
||||
columnsMeta: {
|
||||
name: {
|
||||
@@ -22,6 +22,12 @@ export default {
|
||||
can: true
|
||||
}
|
||||
},
|
||||
display_name: {
|
||||
label: 'display_name',
|
||||
formatter: row => {
|
||||
return row.meta.display_name ? row.meta.display_name : '-'
|
||||
}
|
||||
},
|
||||
comment: {
|
||||
label: 'comment',
|
||||
formatter: (row) => {
|
||||
|
||||
Reference in New Issue
Block a user