perf: Create a job that supports adding node parameters (#4412)

* perf: Create a job that supports adding node parameters

* feat: add variables component

* perf: Update file tip position

* feat: Add variable support to job from template

* feat: Parameters can be set when running job

* feat: Supports setting  variable type

* feat: Supports running adhoc with parameters

* feat: Supports running playbook with parameters

* feat: Support setting variables for scheduled tasks

* perf: Translate

---------

Co-authored-by: wangruidong <940853815@qq.com>
This commit is contained in:
fit2bot
2024-11-07 10:41:07 +08:00
committed by GitHub
parent 1d16a165a5
commit b0af35a4b9
17 changed files with 870 additions and 257 deletions

View File

@@ -0,0 +1,93 @@
<template>
<AutoDataForm
ref="AutoDataForm"
class="variable-add"
:submit-btn-text="submitBtnText"
v-bind="$data"
@submit="confirm"
/>
</template>
<script>
import AutoDataForm from '@/components/Form/AutoDataForm/index.vue'
export default {
name: 'VariableCreateForm',
components: {
AutoDataForm
},
props: {
variable: {
type: Object,
default: () => ({})
}
},
data() {
return {
submitBtnText: this.$t('Confirm'),
url: '/api/v1/ops/variable/',
form: Object.assign({ 'on_invalid': 'error' }, this.variable || {}),
fields: [
['', ['name', 'var_name', 'type', 'text_default_value', 'select_default_value', 'extra_args', 'tips', 'required']]
],
fieldsMeta: {
text_default_value: {
label: this.$t('DefaultValue'),
hidden: (formValue) => {
return formValue.type !== 'text'
},
el: {
type: 'input'
}
},
select_default_value: {
label: this.$t('DefaultValue'),
hidden: (formValue) => {
return formValue.type !== 'select'
},
el: { type: 'input' }
},
extra_args: {
hidden: (formValue) => {
return formValue.type !== 'select'
},
el: { type: 'textarea', rows: 4, placeholder: this.$t('ExtraArgsPlaceholder') }
}
},
hasSaveContinue: false,
method: 'get'
}
},
methods: {
confirm(form) {
if (this.variable?.name) {
this.$emit('edit', form)
} else {
this.$emit('add', form)
}
}
}
}
</script>
<style lang='scss' scoped>
.variable-add {
::v-deep .el-form-item {
margin-bottom: 5px;
.help-block {
margin-bottom: 5px;
}
}
::v-deep .form-group-header {
.hr-line-dashed {
margin: 5px 0;
}
h3 {
margin-bottom: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<AutoDataForm
ref="AutoDataForm"
class="variable-set"
:submit-btn-text="submitBtnText"
v-bind="$data"
:fields="fields"
@submit="confirm"
/>
</template>
<script>
import AutoDataForm from '@/components/Form/AutoDataForm/index.vue'
export default {
name: 'VariableSetForm',
components: {
AutoDataForm
},
props: {
formData: {
type: Array,
default: () => ([])
},
queryParam: {
type: String,
default: ''
}
},
data() {
return {
submitBtnText: this.$t('Confirm'),
// 防止缓存form remoteMeta
url: `/api/v1/ops/variable/form_data/?t=${new Date().getTime()}&` + this.queryParam,
form: {},
hasSaveContinue: false,
method: 'get',
hasReset: false
}
},
computed: {
fields() {
return [['', this.formData.map(item => item.var_name)]]
}
},
methods: {
confirm(form) {
this.$emit('confirm', form)
}
}
}
</script>
<style lang='scss' scoped>
.variable-set {
::v-deep .el-form-item {
margin-bottom: 5px;
.help-block {
margin-bottom: 5px;
}
}
::v-deep .form-group-header {
.hr-line-dashed {
margin: 5px 0;
}
h3 {
margin-bottom: 10px;
}
}
}
</style>

View File

@@ -52,6 +52,7 @@ export default {
title: this.$tc('Select'),
name: 'select',
can: true,
type: 'primary',
callback: ({ row }) => {
this.$emit('select', row)
this.iVisible = false

View File

@@ -8,6 +8,12 @@
:visible.sync="showOpenAdhocSaveDialog"
/>
<VariableHelpDialog :visible.sync="showHelpDialog" />
<setVariableDialog
:form-data="variableFormData"
:query-param="variableQueryParam"
:visible.sync="showSetVariableDialog"
@submit="onSubmitVariable"
/>
<AssetTreeTable ref="TreeTable" :tree-setting="treeSetting">
<template slot="table">
<div class="transition-box" style="width: calc(100% - 17px);">
@@ -43,6 +49,7 @@ import Page from '@/layout/components/Page'
import AdhocOpenDialog from './AdhocOpenDialog.vue'
import AdhocSaveDialog from './AdhocSaveDialog.vue'
import VariableHelpDialog from './VariableHelpDialog.vue'
import setVariableDialog from '@/views/ops/Template/components/setVariableDialog'
import { createJob, getJob, getTaskDetail, StopJob } from '@/api/ops'
export default {
@@ -51,6 +58,7 @@ export default {
VariableHelpDialog,
AdhocSaveDialog,
AdhocOpenDialog,
setVariableDialog,
AssetTreeTable,
Page,
QuickJobTerm,
@@ -70,6 +78,7 @@ export default {
showHelpDialog: false,
showOpenAdhocDialog: false,
showOpenAdhocSaveDialog: false,
showSetVariableDialog: false,
DataZTree: 0,
runas: '',
runasPolicy: 'skip',
@@ -93,6 +102,10 @@ export default {
type: 'primary'
},
callback: () => {
if (this.variableFormData.length !== 0) {
this.showSetVariableDialog = true
return
}
this.execute()
}
},
@@ -297,7 +310,9 @@ export default {
}
}
},
iShowTree: true
iShowTree: true,
variableFormData: [],
variableQueryParam: ''
}
},
computed: {
@@ -339,6 +354,10 @@ export default {
}
},
onSelectAdhoc(adhoc) {
this.variableFormData = adhoc?.variable.map((data) => {
return data.form_data
})
this.variableQueryParam = 'adhoc=' + adhoc.id
this.command = adhoc.args
},
enableWS() {
@@ -432,7 +451,6 @@ export default {
this.$message.error(this.$tc('RequiredRunas'))
return
}
const data = {
assets: hosts,
nodes: nodes,
@@ -447,6 +465,9 @@ export default {
if (this.chdir) {
data.chdir = this.chdir
}
if (this.parameters) {
data.parameters = this.parameters
}
createJob(data).then(res => {
this.executionInfo.timeCost = 0
this.executionInfo.status = { value: 'running', label: this.$t('Running') }
@@ -460,7 +481,7 @@ export default {
stop() {
StopJob({ task_id: this.currentTaskId }).then(() => {
this.xterm.write('\x1b[31m' +
this.$tc('StopLogOutput').replace('currentTaskId', this.currentTaskId) + '\x1b[0m')
this.$tc('StopLogOutput').replace('currentTaskId', this.currentTaskId) + '\x1b[0m')
this.xterm.write(this.wrapperError(''))
this.getTaskStatus()
}).catch((e) => {
@@ -476,6 +497,11 @@ export default {
}
this.toolbar.left.run.isVisible = this.executionInfo.status.value === 'running'
this.toolbar.left.stop.isVisible = this.executionInfo.status.value !== 'running'
},
onSubmitVariable(parameters) {
this.parameters = parameters
this.showSetVariableDialog = false
this.execute()
}
}
}

View File

@@ -535,7 +535,7 @@ export default {
.empty-file-tip {
position: absolute;
right: 20%;
right: calc(50% - 230px);
top: 50%;
font-size: 18px;
color: #c5c9cc;

View File

@@ -2,6 +2,13 @@
<div v-if="ready">
<VariableHelpDialog :visible.sync="showHelpDialog" />
<GenericCreateUpdatePage ref="form" v-bind="$data" />
<setVariableDialog
v-if="showVariableDialog"
:form-data="formData"
:query-param="queryParam"
:visible.sync="showVariableDialog"
@submit="setPeriodicParams"
/>
</div>
</template>
@@ -13,9 +20,13 @@ import i18n from '@/i18n/i18n'
import VariableHelpDialog from '@/views/ops/Adhoc/VariableHelpDialog.vue'
import { Required } from '@/components/Form/DataForm/rules'
import { crontab, interval } from '@/views/accounts/const'
import LoadTemplateLink from '@/views/ops/Job/components/loadTemplateLink'
import Variable from '@/views/ops/Template/components/Variable'
import setVariableDialog from '@/views/ops/Template/components/setVariableDialog.vue'
export default {
components: {
setVariableDialog,
GenericCreateUpdatePage,
VariableHelpDialog
},
@@ -27,9 +38,9 @@ export default {
url: '/api/v1/ops/jobs/',
fields: [
[this.$t('Basic'), ['name', 'type', 'instant']],
[this.$t('Task'), ['module', 'args', 'playbook', 'chdir', 'timeout']],
[this.$t('Asset'), ['assets', 'runas', 'runas_policy']],
[this.$t('Plan'), ['run_after_save', 'is_periodic', 'interval', 'crontab']],
[this.$t('Asset'), ['assets', 'nodes', 'runas', 'runas_policy']],
[this.$t('Task'), ['module', 'argsLoadFromTemplate', 'args', 'playbook', 'variable', 'chdir', 'timeout']],
[this.$t('Plan'), ['is_periodic', 'interval', 'crontab', 'periodic_variable']],
[this.$t('Other'), ['comment']]
],
initial: {
@@ -88,13 +99,26 @@ export default {
return { label: item.name, value: item.id }
}
}
},
on: {
change: ([event], updateForm) => {
this.queryParam = `playbook=${event.pk}`
this.$axios.get(`/api/v1/ops/playbooks/${event.pk}/`,
).then(data => {
data?.variable.map(item => {
delete item.job
delete item.playbook
return item
})
updateForm({ variable: data.variable })
})
}
}
},
assets: {
type: 'assetSelect',
component: AssetSelect,
label: this.$t('Asset'),
rules: [Required],
el: {
baseUrl: '/api/v1/perms/users/self/assets/',
baseNodeUrl: '/api/v1/perms/users/self/nodes/',
@@ -102,6 +126,38 @@ export default {
value: []
}
},
nodes: {
el: {
value: [],
ajax: {
url: '/api/v1/perms/users/self/nodes/',
filterOption: (item) => {
if (item.value !== 'favorite') {
return item
}
},
transformOption: (item) => {
return { label: item.full_value || item.name, value: item.id }
}
}
}
},
argsLoadFromTemplate: {
label: this.$t('Templates'),
hidden: (formValue) => {
return formValue.type !== 'adhoc'
},
component: LoadTemplateLink,
on: {
change: ([event], updateForm) => {
updateForm({
args: event.args,
module: event.module.value,
variable: event.variable
})
}
}
},
args: {
rules: [Required],
hidden: (formValue) => {
@@ -125,6 +181,25 @@ export default {
]
}
},
variable: {
component: Variable,
on: {
input: ([event], updateForm) => {
this.formData = event.map(item => {
return item.form_data
})
if (event.length > 0) {
if (event[0].job) {
this.queryParam = `job=${event[0].job}`
} else if (event[0].adhoc) {
this.queryParam = `adhoc=${event[0].adhoc}`
} else if (event[0].playbook) {
this.queryParam = `playbook=${event[0].playbook}`
}
}
}
}
},
timeout: {
helpText: i18n.t('TimeoutHelpText')
},
@@ -149,13 +224,42 @@ export default {
type: 'switch',
hidden: () => {
return this.instantTask
},
on: {
change: ([event], updateForm) => {
if (this.formData.length > 0) {
this.showVariableDialog = event
}
}
}
},
periodic_variable: {
hidden: () => {
return true
}
},
interval,
crontab
},
createSuccessNextRoute: { name: 'JobManagement' },
updateSuccessNextRoute: { name: 'JobManagement' }
updateSuccessNextRoute: { name: 'JobManagement' },
cleanFormValue: (data) => {
Object.assign(data, { periodic_variable: this.periodicVariableValue })
return data
},
moreButtons: [
{
title: this.$t('ExecuteAfterSaving'),
callback: (value, form, btn) => {
form.value.instant = true
this.submitForm(form, btn)
}
}
],
formData: [],
queryParam: '',
showVariableDialog: false,
periodicVariableValue: {}
}
},
mounted() {
@@ -192,7 +296,20 @@ export default {
this.ready = true
}
},
methods: {}
methods: {
submitForm(form, btn) {
form.validate((valid) => {
if (valid) {
btn.loading = true
}
})
this.$refs.form.$refs.createUpdateForm.$refs.form.$refs.dataForm.submitForm('form', false)
},
setPeriodicParams(data) {
this.showVariableDialog = false
this.periodicVariableValue = data
}
}
}
</script>

View File

@@ -1,129 +1,16 @@
<template>
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<JobRunDialog v-if="showJobRunDialog" :item="item" :visible.sync="showJobRunDialog" @submit="runJob" />
<BaseJob />
</div>
</template>
<script>
import JobRunDialog from '@/views/ops/Job/JobRunDialog'
import GenericListTable from '@/layout/components/GenericListTable'
import { openTaskPage } from '@/utils/jms'
import { ActionsFormatter, DateFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
import BaseJob from './BaseJob'
export default {
name: 'Adhoc',
components: {
GenericListTable,
JobRunDialog
},
data() {
return {
item: {},
tableConfig: {
url: '/api/v1/ops/jobs/?type=adhoc',
columnsShow: {
min: ['name', 'actions'],
default: [
'name', 'type', 'asset_amount', 'average_time_cost',
'summary', 'date_last_run', 'actions'
]
},
columns: [
'name', 'type', 'summary', 'average_time_cost', 'asset_amount',
'date_last_run', 'comment', 'date_updated', 'date_created', 'actions'
],
columnsMeta: {
name: {
width: '140px',
formatter: DetailFormatter,
formatterArgs: {
can: true,
getRoute: ({ row }) => ({
name: 'JobDetail',
params: { id: row.id }
})
}
},
type: {
width: '96px',
formatter: (row) => {
return row.type.label
}
},
comment: {
width: '240px'
},
summary: {
label: this.$t('Summary'),
formatter: (row) => {
return row.summary['success'] + '/' + row.summary['total']
}
},
average_time_cost: {
formatter: (row) => {
return row.average_time_cost.toFixed(2) + 's'
}
},
asset_amount: {
label: this.$t('AssetsOfNumber'),
formatter: (row) => {
return row.assets.length
}
},
date_last_run: {
width: '140px',
formatter: DateFormatter
},
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: true,
canUpdate: this.$hasPerm('ops.change_job') && !this.$store.getters.currentOrgIsRoot,
updateRoute: 'JobUpdate',
hasDelete: true,
canDelete: this.$hasPerm('ops.delete_job'),
hasClone: false,
extraActions: [
{
title: this.$t('Run'),
name: 'run',
can: this.$hasPerm('ops.add_jobexecution') && !this.$store.getters.currentOrgIsRoot,
callback: ({ row }) => {
if (row?.use_parameter_define && row?.parameters_define) {
const params = JSON.parse(row.parameters_define)
if (Object.keys(params).length > 0) {
this.item = row
this.showJobRunDialog = true
}
} else {
this.runJob(row)
}
}
}
]
}
}
}
},
headerActions: {
createRoute: 'JobCreate',
hasRefresh: true,
hasExport: false,
hasImport: false
},
showJobRunDialog: false
}
},
methods: {
runJob(row, parameters) {
this.$axios.post('/api/v1/ops/job-executions/', {
job: row.id,
parameters: parameters
}).then((resp) => {
openTaskPage(resp.task_id)
})
}
BaseJob
}
}
</script>

View File

@@ -0,0 +1,166 @@
<template>
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<JobRunDialog v-if="showJobRunDialog" :item="item" :visible.sync="showJobRunDialog" @submit="runJob" />
<setVariableDialog
v-if="showVariableDialog"
:form-data="formData"
:query-param="'job=' + item.id"
:visible.sync="showVariableDialog"
@submit="runJobWithParams"
/>
</div>
</template>
<script>
import JobRunDialog from '@/views/ops/Job/JobRunDialog'
import GenericListTable from '@/layout/components/GenericListTable'
import setVariableDialog from '@/views/ops/Template/components/setVariableDialog'
import { openTaskPage } from '@/utils/jms'
import { ActionsFormatter, DateFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
export default {
components: {
GenericListTable,
JobRunDialog,
setVariableDialog
},
props: {
type: {
type: String,
default: 'adhoc'
}
},
data() {
return {
item: {},
tableConfig: {
url: `/api/v1/ops/jobs/?type=${this.type}`,
columnsShow: {
min: ['name', 'actions'],
default: [
'name', 'type', 'asset_amount', 'average_time_cost',
'summary', 'date_last_run', 'actions'
]
},
columns: [
'name', 'type', 'summary', 'average_time_cost', 'asset_amount',
'date_last_run', 'comment', 'date_updated', 'date_created', 'actions'
],
columnsMeta: {
name: {
width: '140px',
formatter: DetailFormatter,
formatterArgs: {
can: true,
getRoute: ({ row }) => ({
name: 'JobDetail',
params: { id: row.id }
})
}
},
type: {
width: '96px',
formatter: (row) => {
return row.type.label
}
},
comment: {
width: '240px'
},
summary: {
label: this.$t('Summary'),
formatter: (row) => {
return row.summary['success'] + '/' + row.summary['total']
}
},
average_time_cost: {
formatter: (row) => {
return row.average_time_cost.toFixed(2) + 's'
}
},
asset_amount: {
label: this.$t('AssetsOfNumber'),
formatter: (row) => {
return row.assets.length
}
},
date_last_run: {
width: '140px',
formatter: DateFormatter
},
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: true,
canUpdate: this.$hasPerm('ops.change_job') && !this.$store.getters.currentOrgIsRoot,
updateRoute: 'JobUpdate',
hasDelete: true,
canDelete: this.$hasPerm('ops.delete_job'),
hasClone: false,
extraActions: [
{
title: this.$t('Run'),
name: 'run',
order: 5,
type: 'primary',
can: this.$hasPerm('ops.add_jobexecution') && !this.$store.getters.currentOrgIsRoot,
callback: ({ row }) => {
this.item = row
if (row?.use_parameter_define && row?.parameters_define) {
const params = JSON.parse(row.parameters_define)
if (Object.keys(params).length > 0) {
this.showJobRunDialog = true
}
} else if (row?.variable?.length) {
this.showVariableDialog = true
} else {
this.runJob(row)
}
}
}
]
}
}
}
},
headerActions: {
createRoute: () => {
return {
name: 'JobCreate',
query: {
type: this.type
}
}
},
hasRefresh: true,
hasExport: false,
hasImport: false
},
showJobRunDialog: false,
showVariableDialog: false
}
},
computed: {
formData() {
return this.item.variable.map((data) => {
return data.form_data
})
}
},
methods: {
runJob(row, parameters) {
this.$axios.post('/api/v1/ops/job-executions/', {
job: row.id,
parameters: parameters
}).then((resp) => {
openTaskPage(resp.task_id)
})
},
runJobWithParams(parameters) {
this.runJob(this.item, parameters)
}
}
}
</script>

View File

@@ -1,136 +1,16 @@
<template>
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<JobRunDialog v-if="showJobRunDialog" :item="item" :visible.sync="showJobRunDialog" @submit="runJob" />
<BaseJob :type="'playbook'" />
</div>
</template>
<script>
import JobRunDialog from '@/views/ops/Job/JobRunDialog'
import GenericListTable from '@/layout/components/GenericListTable'
import { openTaskPage } from '@/utils/jms'
import { ActionsFormatter, DateFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
import BaseJob from './BaseJob'
export default {
name: 'PlayBook',
components: {
GenericListTable,
JobRunDialog
},
data() {
return {
item: {},
tableConfig: {
url: '/api/v1/ops/jobs/?type=playbook',
columnsShow: {
min: ['name', 'actions'],
default: [
'name', 'type', 'asset_amount', 'average_time_cost',
'summary', 'date_last_run', 'actions'
]
},
columns: [
'name', 'type', 'summary', 'average_time_cost', 'asset_amount',
'date_last_run', 'comment', 'date_updated', 'date_created', 'actions'
],
columnsMeta: {
name: {
width: '140px',
formatter: DetailFormatter,
formatterArgs: {
can: true,
getRoute: ({ row }) => ({
name: 'JobDetail',
params: { id: row.id }
})
}
},
type: {
width: '96px',
formatter: (row) => {
return row.type.label
}
},
comment: {
width: '240px'
},
summary: {
label: this.$t('Summary'),
formatter: (row) => {
return row.summary['success'] + '/' + row.summary['total']
}
},
average_time_cost: {
formatter: (row) => {
return row.average_time_cost.toFixed(2) + 's'
}
},
asset_amount: {
label: this.$t('AssetsOfNumber'),
formatter: (row) => {
return row.assets.length
}
},
date_last_run: {
width: '140px',
formatter: DateFormatter
},
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: true,
canUpdate: this.$hasPerm('ops.change_job') && !this.$store.getters.currentOrgIsRoot,
updateRoute: 'JobUpdate',
hasDelete: true,
canDelete: this.$hasPerm('ops.delete_job'),
hasClone: false,
extraActions: [
{
title: this.$t('Run'),
name: 'run',
can: this.$hasPerm('ops.add_jobexecution') && !this.$store.getters.currentOrgIsRoot,
callback: ({ row }) => {
if (row?.use_parameter_define && row?.parameters_define) {
const params = JSON.parse(row.parameters_define)
if (Object.keys(params).length > 0) {
this.item = row
this.showJobRunDialog = true
}
} else {
this.runJob(row)
}
}
}
]
}
}
}
},
headerActions: {
hasRefresh: true,
hasExport: false,
hasImport: false,
createRoute: () => {
return {
name: 'JobCreate',
query: {
type: 'playbook'
}
}
}
},
showJobRunDialog: false
}
},
methods: {
runJob(row, parameters) {
this.$axios.post('/api/v1/ops/job-executions/', {
job: row.id,
parameters: parameters
}).then((resp) => {
openTaskPage(resp.task_id)
})
}
BaseJob
}
}
</script>

View File

@@ -0,0 +1,41 @@
<template>
<div>
<AdhocOpenDialog v-if="showOpenAdhocDialog" :visible.sync="showOpenAdhocDialog" @select="onSelectAdhoc" />
<el-link :underline="false" type="default" @click="onClick()">
{{ $t('LoadTemplate') }}
</el-link>
</div>
</template>
<script>
import AdhocOpenDialog from '@/views/ops/Adhoc/AdhocOpenDialog'
export default {
name: 'LoadTemplateLink',
components: {
AdhocOpenDialog
},
data() {
return {
showOpenAdhocDialog: false
}
},
methods: {
onClick() {
this.showOpenAdhocDialog = true
},
onSelectAdhoc(adhoc) {
adhoc?.variable.map(item => {
delete item.id
delete item.job
delete item.adhoc
return item
})
this.$emit('change', adhoc)
}
}
}
</script>
<style>
</style>

View File

@@ -5,6 +5,7 @@
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import CodeEditor from '@/components/Form/FormFields/CodeEditor'
import Variable from '@/views/ops/Template/components/Variable'
export default {
components: {
@@ -14,7 +15,7 @@ export default {
return {
url: '/api/v1/ops/adhocs/',
fields: [
[this.$t('Basic'), ['name', 'module', 'args', 'comment', 'scope']]
[this.$t('Basic'), ['name', 'scope', 'module', 'args', 'variable', 'comment']]
],
initial: {
module: 'shell',
@@ -23,6 +24,9 @@ export default {
fieldsMeta: {
args: {
component: CodeEditor
},
variable: {
component: Variable
}
},
createSuccessNextRoute: {

View File

@@ -13,7 +13,7 @@ export default {
return {
url: '/api/v1/ops/playbooks/',
fields: [
[this.$t('Basic'), ['name', 'comment', 'scope']]
[this.$t('Basic'), ['name', 'scope', 'comment']]
],
createSuccessNextRoute: {
name: 'Template'

View File

@@ -6,7 +6,7 @@
:title="$tc('NewFile')"
:visible.sync="iVisible"
top="1vh"
width="20%"
width="40%"
@confirm="onConfirm"
>
<el-form>
@@ -45,7 +45,8 @@ export default {
}
}
},
mounted() {},
mounted() {
},
methods: {
onConfirm() {
this.$emit('confirm', this.name)

View File

@@ -34,6 +34,13 @@
</el-tab-pane>
</el-tabs>
<div style="display: flex;margin-top:10px;justify-content: space-between" />
<el-form ref="form" label-position="left" label-width="30px">
<div class="form-content">
<el-form-item label="" prop="variable">
<Variable :value.sync="variables" @input="setVariable" />
</el-form-item>
</div>
</el-form>
</div>
</template>
</TreeTable>
@@ -46,13 +53,15 @@ import CodeEditor from '@/components/Form/FormFields/CodeEditor'
import item from '@/layout/components/NavLeft/Item'
import NewNodeDialog from '@/views/ops/Template/Playbook/PlaybookDetail/Editor/NewNodeDialog.vue'
import { renameFile } from '@/api/ops'
import Variable from '@/views/ops/Template/components/Variable'
export default {
name: 'CommandExecution',
components: {
NewNodeDialog,
TreeTable,
CodeEditor
CodeEditor,
Variable
},
props: {
object: {
@@ -146,7 +155,8 @@ export default {
},
iShowTree: true,
activeEditorId: '',
openedEditor: {}
openedEditor: {},
variables: []
}
},
computed: {
@@ -167,6 +177,7 @@ export default {
}
},
mounted() {
this.variables = this.object?.variable
this.onOpenEditor({ id: 'main.yml', name: 'main.yml' })
},
methods: {
@@ -286,6 +297,12 @@ export default {
},
hasChange(editor) {
return editor.value !== editor.originValue
},
setVariable(variables) {
this.$axios.patch(`/api/v1/ops/playbooks/${this.object.id}/`,
{ variable: variables }).catch(err => {
this.$message.error(this.$tc('UpdateErrorMsg') + ' ' + err)
})
}
}
}

View File

@@ -0,0 +1,73 @@
<template>
<Dialog
v-if="iVisible"
:destroy-on-close="true"
:show-cancel="false"
:show-confirm="false"
:title="$tc('AddVariable')"
:visible.sync="iVisible"
width="800px"
>
<VariableCreateForm
:variable="variable"
@add="addVariable"
@edit="editVariable"
/>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
import VariableCreateForm from '@/components/Apps/VariableCreateUpdateForm'
export default {
name: 'AddVariableDialog',
components: {
Dialog,
VariableCreateForm
},
props: {
visible: {
type: Boolean,
default: false
},
variable: {
type: Object,
default: () => ({})
},
variables: {
type: Array,
default: () => ([])
}
},
computed: {
iVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
}
},
methods: {
addVariable(variable) {
const i = this.variables.findIndex(item => item.var_name === variable.var_name)
if (i !== -1) {
this.variables.splice(i, 1)
}
this.variables.push(variable)
this.iVisible = false
},
editVariable(form) {
const i = this.variables.findIndex(item => item.var_name === this.variable.var_name)
this.variables.splice(i, 1, form)
this.iVisible = false
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,167 @@
<template>
<div>
<div class="variables el-data-table">
<el-table :data="variables" class="el-table--fit el-table--border">
<el-table-column :label="$tc('Name')" prop="name" />
<el-table-column :label="$tc('VariableName')" prop="var_name" />
<el-table-column :label="$tc('Actions')" align="center" class-name="buttons" fixed="right" width="135">
<template v-slot="scope">
<el-button icon="el-icon-minus" size="mini" type="danger" @click="removeVariable(scope.row)" />
<el-button
:disabled="!!scope.row.template"
icon="el-icon-edit"
size="mini"
type="primary"
@click="onEditClick(scope.row)"
/>
</template>
</el-table-column>
</el-table>
<div class="actions">
<el-button size="mini" type="primary" @click="onAddClick">
{{ $t('Add') }}
</el-button>
</div>
<AddVariableDialog
:variable="variable"
:variables="variables"
:visible.sync="addVariableDialogVisible"
/>
</div>
</div>
</template>
<script>
import AddVariableDialog from './AddVariableDialog'
export default {
name: 'Variable',
components: {
AddVariableDialog
},
props: {
value: {
type: [Array],
default: () => []
}
},
data() {
return {
variable: {},
initial: false,
addVariableDialogVisible: false
}
},
computed: {
variables: {
get() {
return this.value
},
set(val) {
this.$emit('update:value', val)
this.$emit('change', val)
}
}
},
watch: {
variables: {
handler(value) {
if (value.length > 0 || this.initial) {
this.$emit('input', value)
}
if (value) {
this.initial = true
}
},
immediate: true,
deep: true
}
},
methods: {
removeVariable(variable) {
this.variables = this.variables.filter((item) => {
if (variable.id && item.id) {
return item.id !== variable.id
} else if (variable.var_name && item.var_name) {
return item.var_name !== variable.var_name
} else {
return variable.name !== item.name
}
})
},
onEditClick(variable) {
this.variable = variable
setTimeout(() => {
this.addVariableDialogVisible = true
})
},
onAddClick() {
this.variable = null
setTimeout(() => {
this.addVariableDialogVisible = true
})
}
}
}
</script>
<style lang="scss" scoped>
.el-data-table ::v-deep .el-table {
.table {
margin-top: 15px;
}
.el-table__row {
&.selected-row {
background-color: #f5f7fa;
}
& > td {
line-height: 1.5;
padding: 6px 0;
font-size: 13px;
border-right: none;
* {
vertical-align: middle;
}
.el-checkbox {
vertical-align: super;
}
& > div > span {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
}
.el-table__header > thead > tr > th {
padding: 6px 0;
background-color: #ffffff;
font-size: 13px;
line-height: 1.5;
border-right: none;
.cell {
white-space: nowrap !important;
overflow: hidden;
text-overflow: ellipsis;
&:hover {
border-right: 2px solid #EBEEF5;
}
}
}
}
.el-data-table ::v-deep .el-table .el-table__header > thead > tr .is-sortable {
padding: 5px 0;
.cell {
padding-top: 3px !important;
}
}
</style>

View File

@@ -0,0 +1,66 @@
<template>
<Dialog
v-if="iVisible"
:destroy-on-close="true"
:show-cancel="false"
:show-confirm="false"
:title="$tc('setVariable')"
:visible.sync="iVisible"
width="800px"
>
<VariableSetForm
:form-data="formData"
:query-param="queryParam"
@confirm="handleConfirm"
/>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
import VariableSetForm from '@/components/Apps/VariableSetForm'
export default {
name: 'SetVariableDialog',
components: {
Dialog,
VariableSetForm
},
props: {
visible: {
type: Boolean,
default: false
},
formData: {
type: Array,
default: () => ([])
},
queryParam: {
type: String,
default: ''
}
},
data() {
return {}
},
computed: {
iVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
}
},
methods: {
handleConfirm(variable) {
this.$emit('submit', variable)
}
}
}
</script>
<style scoped>
</style>