mirror of
https://github.com/jumpserver/lina.git
synced 2025-11-07 09:58:38 +00:00
Compare commits
113 Commits
pr@dev@fix
...
v4.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2e964d953 | ||
|
|
0ad4945b10 | ||
|
|
fcdd090e2d | ||
|
|
e47ddb5355 | ||
|
|
475a77497f | ||
|
|
5d1301c871 | ||
|
|
772f40e13d | ||
|
|
1b2f7ad28c | ||
|
|
84778881f6 | ||
|
|
d6d0338666 | ||
|
|
de6f477d05 | ||
|
|
03e07ee280 | ||
|
|
35e2273603 | ||
|
|
7a138388b2 | ||
|
|
b1f42dd6bf | ||
|
|
2eeba4ee4f | ||
|
|
d91579e848 | ||
|
|
13751f14f3 | ||
|
|
7ea00e17bd | ||
|
|
ec78370ecc | ||
|
|
c9f62ae5d3 | ||
|
|
b80b69dd26 | ||
|
|
ac49415061 | ||
|
|
9edb64837d | ||
|
|
671fa65930 | ||
|
|
0da81ea60d | ||
|
|
c610d396d6 | ||
|
|
0ed02ca7f7 | ||
|
|
c22981e2c1 | ||
|
|
b0af35a4b9 | ||
|
|
1d16a165a5 | ||
|
|
f1f4242ad6 | ||
|
|
bff8f10cd5 | ||
|
|
305e44ea1c | ||
|
|
a636bb2037 | ||
|
|
56aa3caa83 | ||
|
|
9b6f54c1ed | ||
|
|
611341307b | ||
|
|
f8479c53ff | ||
|
|
e25bf46659 | ||
|
|
6d07307e56 | ||
|
|
6fb7fe8fa1 | ||
|
|
19cd497095 | ||
|
|
19b1dc0dbc | ||
|
|
77ef172a23 | ||
|
|
4596887bf1 | ||
|
|
0a3dc30c85 | ||
|
|
51d24bc8e5 | ||
|
|
1b15a4d043 | ||
|
|
7d3f818242 | ||
|
|
4e26f18d77 | ||
|
|
b22613617a | ||
|
|
e971cbf4a8 | ||
|
|
4672abae35 | ||
|
|
ba36d72602 | ||
|
|
4bfbbba4c5 | ||
|
|
ea038ce43a | ||
|
|
e16b19666c | ||
|
|
c7f5409eb6 | ||
|
|
fdbd7d2222 | ||
|
|
ddbaeeafea | ||
|
|
efb0e9dacb | ||
|
|
f6f8301ad5 | ||
|
|
9a63ae63d4 | ||
|
|
1e007ccda3 | ||
|
|
d1d0b06b53 | ||
|
|
5fb70d2f24 | ||
|
|
b54a95430f | ||
|
|
4d8b4c45af | ||
|
|
a6d642df60 | ||
|
|
2e74f1522f | ||
|
|
fe615e0314 | ||
|
|
09f734e6fc | ||
|
|
3117046342 | ||
|
|
b68aecb5cc | ||
|
|
1c9b155d97 | ||
|
|
75b1be9864 | ||
|
|
615c3c1cf4 | ||
|
|
4d82231af4 | ||
|
|
c6cf6571b6 | ||
|
|
8ea990d070 | ||
|
|
f4a32170d5 | ||
|
|
073508675e | ||
|
|
1d6ca0a93a | ||
|
|
36aea652d6 | ||
|
|
1a42ce90ab | ||
|
|
31a401b55d | ||
|
|
582a84178d | ||
|
|
9b9f7c936c | ||
|
|
2a6100957f | ||
|
|
16606d6a27 | ||
|
|
0a612f50e6 | ||
|
|
fe36fa9390 | ||
|
|
ba109900ec | ||
|
|
ec7768267f | ||
|
|
cc58b374ab | ||
|
|
04ffbb8fd6 | ||
|
|
49880f6739 | ||
|
|
e6f98d58c4 | ||
|
|
fd1f16d43c | ||
|
|
968b2415b1 | ||
|
|
776090d6ba | ||
|
|
3a37952288 | ||
|
|
62b8fc0e3b | ||
|
|
b2028869cb | ||
|
|
5277a725f8 | ||
|
|
f137788c1a | ||
|
|
f7d17c8de7 | ||
|
|
feea70b0be | ||
|
|
04696ef3d6 | ||
|
|
1731f4f788 | ||
|
|
6f25d93909 | ||
|
|
46461ec324 |
28
.github/workflows/llm-code-review.yml
vendored
Normal file
28
.github/workflows/llm-code-review.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: LLM Code Review
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
llm-code-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: fit2cloud/LLM-CodeReview-Action@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }}
|
||||
OPENAI_API_KEY: ${{ secrets.ALIYUN_LLM_API_KEY }}
|
||||
LANGUAGE: English
|
||||
OPENAI_API_ENDPOINT: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
MODEL: qwen2-1.5b-instruct
|
||||
PROMPT: "Please check the following code differences for any irregularities, potential issues, or optimization suggestions, and provide your answers in English."
|
||||
top_p: 1
|
||||
temperature: 1
|
||||
# max_tokens: 10000
|
||||
MAX_PATCH_LENGTH: 10000
|
||||
IGNORE_PATTERNS: "/node_modules,*.md,/dist,/.github"
|
||||
FILE_PATTERNS: "*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html"
|
||||
@@ -53,7 +53,7 @@ export function createJob(form) {
|
||||
})
|
||||
}
|
||||
|
||||
export function StopJob(form) {
|
||||
export function stopJob(form) {
|
||||
return request({
|
||||
url: '/api/v1/ops/job-executions/stop/',
|
||||
method: 'post',
|
||||
@@ -71,3 +71,10 @@ export function JobUploadFile(form) {
|
||||
})
|
||||
}
|
||||
|
||||
export function auditUpdateJob(id, form) {
|
||||
return request({
|
||||
url: `/api/v1/audits/jobs/${id}/`,
|
||||
method: 'patch',
|
||||
data: form
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:visible.sync="visible"
|
||||
class="dialog-content"
|
||||
v-bind="$attrs"
|
||||
width="600px"
|
||||
width="740px"
|
||||
@confirm="visible = false"
|
||||
v-on="$listeners"
|
||||
>
|
||||
@@ -52,11 +52,21 @@
|
||||
<el-row :gutter="24" style="margin: 0 auto;">
|
||||
<el-col :md="24" :sm="24" style="display: flex; align-items: center; margin-bottom: 20px;">
|
||||
<el-input
|
||||
v-if="subTypeSelected !== 'face'"
|
||||
v-model="secretValue"
|
||||
:placeholder="inputPlaceholder"
|
||||
:show-password="showPassword"
|
||||
@keyup.enter.native="handleConfirm"
|
||||
/>
|
||||
|
||||
<iframe
|
||||
v-if="isFaceCaptureVisible && subTypeSelected ==='face' && faceCaptureUrl"
|
||||
:src="faceCaptureUrl"
|
||||
allow="camera"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
style="width: 100%; height: 800px;border: none;"
|
||||
/>
|
||||
|
||||
<span v-if="subTypeSelected === 'sms'" style="margin: -1px 0 0 20px;">
|
||||
<el-button
|
||||
:disabled="smsBtnDisabled"
|
||||
@@ -72,9 +82,24 @@
|
||||
</el-row>
|
||||
<el-row :gutter="24" style="margin: 10px auto;">
|
||||
<el-col :md="24" :sm="24">
|
||||
<el-button class="confirm-btn" size="mini" type="primary" @click="handleConfirm">
|
||||
<el-button
|
||||
v-if="subTypeSelected!=='face'"
|
||||
class="confirm-btn"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleConfirm"
|
||||
>
|
||||
{{ this.$t('Confirm') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="subTypeSelected==='face'&&!isFaceCaptureVisible"
|
||||
class="confirm-btn"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleFaceCapture"
|
||||
>
|
||||
开始人脸识别
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
@@ -113,7 +138,10 @@ export default {
|
||||
visible: false,
|
||||
callback: null,
|
||||
cancel: null,
|
||||
processing: false
|
||||
processing: false,
|
||||
isFaceCaptureVisible: false,
|
||||
faceToken: null,
|
||||
faceCaptureUrl: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -129,6 +157,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
handleSubTypeChange(val) {
|
||||
if (val !== 'face') {
|
||||
this.isFaceCaptureVisible = false
|
||||
}
|
||||
|
||||
this.inputPlaceholder = this.subTypeChoices.filter(item => item.name === val)[0]?.placeholder
|
||||
this.smsWidth = val === 'sms' ? 6 : 0
|
||||
},
|
||||
@@ -192,6 +224,29 @@ export default {
|
||||
this.$message.error(this.$tc('FailedToSendVerificationCode'))
|
||||
})
|
||||
},
|
||||
startFaceCapture() {
|
||||
const url = '/api/v1/authentication/mfa/face/context/'
|
||||
this.$axios.post(url).then(data => {
|
||||
const token = data['token']
|
||||
this.faceCaptureUrl = '/facelive/capture?token=' + token
|
||||
this.isFaceCaptureVisible = true
|
||||
|
||||
const timer = setInterval(() => {
|
||||
this.$axios.get(url + `?token=${token}`).then(data => {
|
||||
if (data['is_finished']) {
|
||||
clearInterval(timer)
|
||||
this.isFaceCaptureVisible = false
|
||||
this.handleConfirm()
|
||||
}
|
||||
})
|
||||
}, 1000)
|
||||
}).catch(() => {
|
||||
this.$message.error(this.$tc('FailedToStartFaceCapture'))
|
||||
})
|
||||
},
|
||||
handleFaceCapture() {
|
||||
this.startFaceCapture()
|
||||
},
|
||||
handleConfirm() {
|
||||
if (this.confirmTypeRequired === 'relogin') {
|
||||
return this.logout()
|
||||
@@ -214,6 +269,8 @@ export default {
|
||||
})
|
||||
}).catch((err) => {
|
||||
this.$message.error(err.message || this.$tc('ConfirmFailed'))
|
||||
this.faceCaptureUrl = null
|
||||
this.isFaceCaptureVisible = false
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -221,21 +278,21 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dialog-content ::v-deep .el-dialog__footer {
|
||||
padding: 0;
|
||||
}
|
||||
.dialog-content ::v-deep .el-dialog__footer {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dialog-content ::v-deep .el-dialog {
|
||||
padding: 8px;
|
||||
.dialog-content ::v-deep .el-dialog {
|
||||
padding: 8px;
|
||||
|
||||
.el-dialog__body {
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
line-height: 20px;
|
||||
}
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
111
src/components/Apps/VariableCreateUpdateForm/index.vue
Normal file
111
src/components/Apps/VariableCreateUpdateForm/index.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<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') },
|
||||
rules: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
try {
|
||||
const lines = value.split('\n')
|
||||
const regex = /^[^:]+:[^:]+$/
|
||||
for (const line of lines) {
|
||||
if (!regex.test(line.trim())) {
|
||||
callback(new Error(this.$t('ExtraArgsFormatError')))
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
callback(new Error(this.$t('ExtraArgsFormatError')))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
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>
|
||||
74
src/components/Apps/VariableSetForm/index.vue
Normal file
74
src/components/Apps/VariableSetForm/index.vue
Normal 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>
|
||||
@@ -92,7 +92,7 @@ export default {
|
||||
)
|
||||
}
|
||||
return (
|
||||
<span title={this.displayValue}>{this.displayValue}</span>
|
||||
<span style='white-space: pre-wrap;' title={this.displayValue}>{this.displayValue}</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import BasicTree from '@/components/Form/FormFields/BasicTree.vue'
|
||||
import JsonEditor from '@/components/Form/FormFields/JsonEditor.vue'
|
||||
import { assignIfNot, toSentenceCase } from '@/utils/common'
|
||||
import TagInput from '@/components/Form/FormFields/TagInput.vue'
|
||||
import TransferSelect from '@/components/Form/FormFields/TransferSelect.vue'
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
export class FormFieldGenerator {
|
||||
@@ -135,9 +134,6 @@ export class FormFieldGenerator {
|
||||
case 'comment':
|
||||
field.el.type = 'textarea'
|
||||
break
|
||||
case 'users':
|
||||
field.component = TransferSelect
|
||||
field.el.label = field.label
|
||||
}
|
||||
return field
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ export default {
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:value', val)
|
||||
this.$emit('change', val)
|
||||
this.$emit('input', val)
|
||||
}
|
||||
},
|
||||
iOptions() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-input v-model="iValue" v-bind="$attrs" v-on="$listeners">
|
||||
<el-input v-bind="$attrs" v-on="$listeners">
|
||||
<template slot="append">{{ iUnit }}</template>
|
||||
</el-input>
|
||||
</template>
|
||||
@@ -30,9 +30,6 @@ export default {
|
||||
computed: {
|
||||
iUnit() {
|
||||
return this.displayMapper[this.unit] || this.unit
|
||||
},
|
||||
iValue() {
|
||||
return this.$attrs.value ? this.$attrs.value : this.defaultValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="!loading" :class="showSetting ? 'show-setting' : 'hide-setting'">
|
||||
<div :class="showSetting ? 'show-setting' : 'hide-setting'">
|
||||
<div v-for="(item, index) in items" :key="item.name" class="protocol-item">
|
||||
<el-input
|
||||
v-model="item.port"
|
||||
@@ -114,8 +114,7 @@ export default {
|
||||
name: '',
|
||||
items: [],
|
||||
currentProtocol: {},
|
||||
showDialog: false,
|
||||
loading: false
|
||||
showDialog: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -144,10 +143,8 @@ export default {
|
||||
watch: {
|
||||
choices: {
|
||||
handler(value, oldValue) {
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.setDefaultItems(value)
|
||||
this.loading = false
|
||||
},)
|
||||
},
|
||||
deep: true,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-if="showColumnSettingPopover"
|
||||
:cancel-title="$tc('RestoreDefault')"
|
||||
:destroy-on-close="true"
|
||||
:title="$tc('TableSetting')"
|
||||
:title="$tc('ListPreference')"
|
||||
:visible.sync="showColumnSettingPopover"
|
||||
top="10%"
|
||||
width="50%"
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
:table-url="tableUrl"
|
||||
v-bind="headerActions"
|
||||
/>
|
||||
<el-row :gutter="10" type="flex" class="the-row">
|
||||
<IBox v-if="totalData.length === 0" style="width: 100%">
|
||||
<el-row :gutter="10" class="the-row">
|
||||
<IBox v-if="totalData.length === 0">
|
||||
<el-empty :description="$t('NoData')" :image-size="200" class="no-data" style="padding: 20px" />
|
||||
</IBox>
|
||||
<el-col v-for="(d, index) in totalData" :key="index" style="max-width: 550px; width: 400px">
|
||||
<el-col v-for="(d, index) in totalData" :key="index" :lg="8" :md="12" :sm="24" style="min-width: 335px;">
|
||||
<el-card
|
||||
:body-style="{ 'text-align': 'center', 'padding': '15px' }"
|
||||
:class="{'is-disabled': isDisabled(d)}"
|
||||
@@ -56,7 +56,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
<Pagination
|
||||
v-show="pagination"
|
||||
v-show="pagination && total > paginationSize"
|
||||
ref="pagination"
|
||||
class="pagination"
|
||||
v-bind="$data"
|
||||
@@ -234,8 +234,6 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.the-row {
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
margin-top: 15px;
|
||||
max-width: 1600px;
|
||||
text-align: center;
|
||||
@@ -249,13 +247,12 @@ export default {
|
||||
|
||||
::v-deep .el-card__body {
|
||||
height: 100%;
|
||||
padding: unset !important;
|
||||
|
||||
.el-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
margin-top: 0;
|
||||
height: 190px;
|
||||
height: 100%;
|
||||
|
||||
.image {
|
||||
display: flex;
|
||||
@@ -278,7 +275,6 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 10px;
|
||||
|
||||
.one-line {
|
||||
display: flex;
|
||||
@@ -300,14 +296,15 @@ export default {
|
||||
|
||||
.comment {
|
||||
display: -webkit-box;
|
||||
height: 120px;
|
||||
font-size: 12px;
|
||||
padding: 10px 0;
|
||||
line-height: 13px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tag-zone {
|
||||
@@ -315,7 +312,6 @@ export default {
|
||||
height: 30%;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-top: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ export default {
|
||||
{
|
||||
name: 'actionSetting',
|
||||
icon: 'system-setting',
|
||||
tip: this.$t('TableSetting'),
|
||||
tip: this.$t('ListPreference'),
|
||||
has: this.hasColumnSetting,
|
||||
callback: this.handleTableSettingClick.bind(this)
|
||||
},
|
||||
|
||||
@@ -24,6 +24,9 @@ export default {
|
||||
},
|
||||
isDisplay(row) {
|
||||
return true
|
||||
},
|
||||
callback({ row }) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,10 +52,11 @@ export default {
|
||||
methods: {
|
||||
onChange(val) {
|
||||
this.$axios.patch(this.patchUrl, this.patchData).then(res => {
|
||||
this.$message.success(this.$t('updateSuccessMsg'))
|
||||
this.formatterArgs.callback(this.row)
|
||||
this.$message.success(this.$t('UpdateSuccessMsg'))
|
||||
}).catch(err => {
|
||||
this.value = !val
|
||||
this.$message.error(this.$t('updateErrorMsg' + ' ' + err))
|
||||
this.$message.error(this.$t('UpdateErrorMsg' + ' ' + err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,10 +234,12 @@ export default {
|
||||
delete routeFilter.search
|
||||
}
|
||||
const asFilterTags = _.cloneDeep(this.filterTags)
|
||||
this.filterTags = {
|
||||
...asFilterTags,
|
||||
...routeFilter
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.filterTags = {
|
||||
...asFilterTags,
|
||||
...routeFilter
|
||||
}
|
||||
}, 100)
|
||||
},
|
||||
getValueLabel(key, value) {
|
||||
for (const field of this.options) {
|
||||
|
||||
@@ -52,7 +52,7 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
.app-main {
|
||||
background-color: #f3f3f4;
|
||||
height: 100% !important;
|
||||
height: 100vh !important;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
|
||||
@@ -116,7 +116,6 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
height: calc(100vh - 50px);
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
|
||||
|
||||
@@ -80,15 +80,5 @@ export default [
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'job-execution-log',
|
||||
name: 'JobExecutionLog',
|
||||
component: () => import('@/views/audits/JobExecutionLogList'),
|
||||
meta: {
|
||||
title: i18n.t('JobExecutionLog'),
|
||||
icon: 'task',
|
||||
permissions: ['audits.view_joblog']
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3,6 +3,7 @@ import i18n from '@/i18n/i18n'
|
||||
|
||||
import SessionRoutes from './sessions'
|
||||
import LogRoutes from './audits'
|
||||
import JobRoutes from './jobs'
|
||||
import empty from '@/layout/empty'
|
||||
import store from '@/store'
|
||||
|
||||
@@ -54,6 +55,18 @@ export default {
|
||||
permissions: []
|
||||
},
|
||||
children: LogRoutes
|
||||
},
|
||||
{
|
||||
path: '/audit/jobs',
|
||||
component: empty,
|
||||
redirect: '',
|
||||
name: 'AuditsJobs',
|
||||
meta: {
|
||||
title: i18n.t('JobsAudit'),
|
||||
icon: 'job',
|
||||
permissions: ['audits.view_joblog']
|
||||
},
|
||||
children: JobRoutes
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
53
src/router/audit/jobs.js
Normal file
53
src/router/audit/jobs.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import i18n from '@/i18n/i18n'
|
||||
import empty from '@/layout/empty'
|
||||
|
||||
export default [
|
||||
{
|
||||
path: 'job-list',
|
||||
name: 'JobList',
|
||||
component: empty,
|
||||
redirect: {
|
||||
name: 'JobList'
|
||||
},
|
||||
meta: {
|
||||
title: i18n.t('JobList'),
|
||||
icon: 'task',
|
||||
permissions: ['audits.view_joblog']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'AuditJobList',
|
||||
component: () => import('@/views/audits/JobPeriodTaskList'),
|
||||
meta: {
|
||||
title: i18n.t('JobList'),
|
||||
permissions: []
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'job-execution-log',
|
||||
name: 'JobExecutionLog',
|
||||
component: empty,
|
||||
redirect: {
|
||||
name: 'AuditJobExecutionLog'
|
||||
},
|
||||
meta: {
|
||||
title: i18n.t('JobExecutionLog'),
|
||||
icon: 'history',
|
||||
permissions: ['audits.view_joblog']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'AuditJobExecutionLog',
|
||||
component: () => import('@/views/audits/JobExecutionLogList'),
|
||||
meta: {
|
||||
title: i18n.t('JobExecutionLog'),
|
||||
permissions: ['audits.view_joblog']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -128,6 +128,8 @@ export function getErrorResponseMsg(error) {
|
||||
}).filter(i => i).join('; ')
|
||||
} else if (typeof data === 'string') {
|
||||
return data
|
||||
} else {
|
||||
msg = error.toString()
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const moment = require('moment')
|
||||
import { getLangCode } from '@/i18n/utils'
|
||||
import store from '@/store'
|
||||
|
||||
function getTimeUnits(u) {
|
||||
const units = {
|
||||
@@ -124,3 +125,8 @@ export function formatDate(inputTime) {
|
||||
// return y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second
|
||||
return y + '-' + m + '-' + d + 'T' + h + ':' + minute + ':' + second
|
||||
}
|
||||
|
||||
export function getDefaultExpiredDays() {
|
||||
const years = store.getters.publicSettings.DEFAULT_EXPIRED_YEARS
|
||||
return getDayFuture(years * 365, new Date()).toISOString()
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ export const getChangeSecretFields = () => {
|
||||
is_periodic,
|
||||
accounts: {
|
||||
label: i18n.t('Accounts'),
|
||||
component: TagInput
|
||||
component: TagInput,
|
||||
helpText: i18n.t('ChangeSecretAccountHelpText')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ import InputWithUnit from '@/components/Form/FormFields/InputWithUnit.vue'
|
||||
import store from '@/store'
|
||||
|
||||
const validatorInterval = (rule, value, callback) => {
|
||||
if (parseInt(value) < 1) {
|
||||
return callback(new Error(i18n.t('EnsureThisValueIsGreaterThanOrEqualTo1')))
|
||||
// value只能是 null 或者 >=1
|
||||
if (value === null || (!isNaN(value) && Number(value) >= 1)) {
|
||||
callback()
|
||||
} else {
|
||||
callback(new Error(i18n.t('EnsureThisValueIsGreaterThanOrEqualTo1')))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
export const crontab = {
|
||||
|
||||
@@ -76,14 +76,20 @@ export default {
|
||||
},
|
||||
callbacks: {
|
||||
change: function(val) {
|
||||
this.$axios.patch(
|
||||
`/api/v1/xpack/cloud/sync-instance-tasks/${this.object.task.id}/`,
|
||||
{ 'sync_ip_type': val }
|
||||
).then(res => {
|
||||
this.$message.success(this.$t('UpdateSuccessMsg'))
|
||||
}).catch(err => {
|
||||
this.$message.error(this.$t('UpdateErrorMsg' + ' ' + err))
|
||||
})
|
||||
this.updateTaskData({ 'sync_ip_type': val })
|
||||
}.bind(this)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$t('ReleaseAssets'),
|
||||
type: 'switch',
|
||||
attrs: {
|
||||
model: this.object.task.release_assets,
|
||||
disabled: !this.hasEditPerm()
|
||||
},
|
||||
callbacks: {
|
||||
change: function(val) {
|
||||
this.updateTaskData({ 'release_assets': val })
|
||||
}.bind(this)
|
||||
}
|
||||
}
|
||||
@@ -197,6 +203,16 @@ export default {
|
||||
methods: {
|
||||
hasEditPerm() {
|
||||
return this.$hasPerm('xpack.change_account') && this.$hasPerm('xpack.change_syncinstancetask')
|
||||
},
|
||||
updateTaskData(data) {
|
||||
this.$axios.patch(
|
||||
`/api/v1/xpack/cloud/sync-instance-tasks/${this.object.task.id}/`,
|
||||
data
|
||||
).then(res => {
|
||||
this.$message.success(this.$tc('UpdateSuccessMsg'))
|
||||
}).catch(err => {
|
||||
this.$message.error(this.$tc('UpdateErrorMsg' + ' ' + err))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,13 +73,7 @@ export default {
|
||||
btn.loading = true
|
||||
}
|
||||
})
|
||||
|
||||
if (form.value.interval && typeof form.value.interval === 'number') {
|
||||
form.value.interval = parseInt(form.value.interval, 10)
|
||||
} else {
|
||||
form.value.interval = 24
|
||||
}
|
||||
|
||||
form.value.interval = parseInt(form.value.interval, 10)
|
||||
this.$refs.form.$refs.form.dataForm.submitForm('form', false)
|
||||
},
|
||||
handleSubmitSuccess(res) {
|
||||
|
||||
@@ -39,7 +39,8 @@ export default {
|
||||
submitBtnSize: 'mini',
|
||||
submitBtnText: this.$t('Add'),
|
||||
hasReset: false,
|
||||
onSubmit: () => {},
|
||||
onSubmit: () => {
|
||||
},
|
||||
submitMethod: () => 'post',
|
||||
getUrl: () => '',
|
||||
cleanFormValue(data) {
|
||||
@@ -86,7 +87,11 @@ export default {
|
||||
this.formConfig.fieldsMeta.protocols.el.hidden = true
|
||||
}
|
||||
this.resourceType = val
|
||||
this.formConfig.fieldsMeta.value.el.ajax.url = url
|
||||
if (url) {
|
||||
this.formConfig.fieldsMeta.value.el.ajax.url = url
|
||||
} else {
|
||||
this.formConfig.fieldsMeta.attr.el.remote = false
|
||||
}
|
||||
this.formConfig.fieldsMeta.value.el.options = options
|
||||
}
|
||||
}
|
||||
@@ -151,21 +156,31 @@ export default {
|
||||
tableConfig: {
|
||||
columns: [
|
||||
{ prop: 'attr', label: this.$t('ResourceType'), formatter: tableFormatter('resource_type') },
|
||||
{ prop: 'value', label: this.$t('Resource'), formatter: tableFormatter('resource', () => { return this.globalResource }) },
|
||||
{
|
||||
prop: 'value', label: this.$t('Resource'), formatter: tableFormatter('resource', () => {
|
||||
return this.globalResource
|
||||
})
|
||||
},
|
||||
{ prop: 'protocols', label: this.$t('Other'), formatter: tableFormatter('protocols') },
|
||||
{ prop: 'action', label: this.$t('Action'), align: 'center', width: '100px', formatter: (row, col, cellValue, index) => {
|
||||
return (
|
||||
<div className='input-button'>
|
||||
<el-button
|
||||
icon='el-icon-minus'
|
||||
size='mini'
|
||||
style={{ 'flexShrink': 0 }}
|
||||
type='danger'
|
||||
onClick={ this.handleDelete(index) }
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} }
|
||||
{
|
||||
prop: 'action',
|
||||
label: this.$t('Action'),
|
||||
align: 'center',
|
||||
width: '100px',
|
||||
formatter: (row, col, cellValue, index) => {
|
||||
return (
|
||||
<div className='input-button'>
|
||||
<el-button
|
||||
icon='el-icon-minus'
|
||||
size='mini'
|
||||
style={{ 'flexShrink': 0 }}
|
||||
type='danger'
|
||||
onClick={this.handleDelete(index)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
],
|
||||
totalData: this.value || [],
|
||||
hasPagination: false
|
||||
@@ -177,7 +192,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.nameOptions.map((o) => { this.globalResource[o.value] = o.label })
|
||||
this.nameOptions.map((o) => {
|
||||
this.globalResource[o.value] = o.label
|
||||
})
|
||||
},
|
||||
onSubmit() {
|
||||
this.$emit('input', this.tableConfig.totalData)
|
||||
@@ -218,9 +235,11 @@ export default {
|
||||
::v-deep .el-form-item:nth-child(-n+3) {
|
||||
width: 43.5%;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item:last-child {
|
||||
width: 6%;
|
||||
}
|
||||
|
||||
.action-input {
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<GenericListPage :header-actions="headerActions" :table-config="tableConfig" />
|
||||
<GenericListPage ref="ListPage" :header-actions="headerActions" :table-config="tableConfig" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import GenericListPage from '@/layout/components/GenericListPage'
|
||||
import { ActionsFormatter } from '@/components/Table/TableFormatters'
|
||||
import { openTaskPage } from '@/utils/jms'
|
||||
import { stopJob } from '@/api/ops'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -45,6 +46,20 @@ export default {
|
||||
callback: ({ row }) => {
|
||||
openTaskPage(row.task_id)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$t('Stop'),
|
||||
name: 'stop',
|
||||
can: ({ row }) => {
|
||||
return !row.is_finished
|
||||
},
|
||||
type: 'danger',
|
||||
callback: ({ row }) => {
|
||||
stopJob({ task_id: row.task_id }).then(() => {
|
||||
this.$refs.ListPage.reloadTable()
|
||||
this.$message.success(this.$t('StopJobMsg'))
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
88
src/views/audits/JobPeriodTaskList.vue
Normal file
88
src/views/audits/JobPeriodTaskList.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div>
|
||||
<GenericListPage ref="ListPage" :header-actions="headerActions" :table-config="tableConfig" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/jsx">
|
||||
import GenericListPage from '@/layout/components/GenericListPage'
|
||||
import { SwitchFormatter } from '@/components/Table/TableFormatters'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericListPage
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
tableConfig: {
|
||||
url: '/api/v1/audits/jobs/',
|
||||
columnsShow: {
|
||||
min: ['name', 'material'],
|
||||
default: [
|
||||
'name', 'material', 'type', 'crontab', 'interval', 'created_by', 'is_periodic_display', 'is_periodic'
|
||||
]
|
||||
},
|
||||
columns: [
|
||||
'name', 'args', 'material', 'type', 'crontab', 'interval', 'date_last_run', 'summary',
|
||||
'created_by', 'is_periodic_display', 'is_periodic'
|
||||
],
|
||||
columnsMeta: {
|
||||
actions: {
|
||||
has: false
|
||||
},
|
||||
name: {
|
||||
formatter: (row) => row.name
|
||||
},
|
||||
material: {
|
||||
width: '200px',
|
||||
label: this.$t('Command')
|
||||
},
|
||||
summary: {
|
||||
width: '130px',
|
||||
label: `${this.$t('Success')}/${this.$t('Total')}`,
|
||||
formatter: (row) => {
|
||||
return <div>
|
||||
<span Class='text-primary'>{row.summary.success}</span>/
|
||||
<span>{row.summary.total}</span>
|
||||
</div>
|
||||
}
|
||||
},
|
||||
is_periodic_display: {
|
||||
width: '100px',
|
||||
label: this.$t('Periodic')
|
||||
},
|
||||
is_periodic: {
|
||||
width: '140px',
|
||||
label: `${this.$t('Enable')}/${this.$t('Disable')}`,
|
||||
formatter: SwitchFormatter,
|
||||
formatterArgs: {
|
||||
isDisplay(row) {
|
||||
return row.is_periodic
|
||||
},
|
||||
getPatchUrl(row) {
|
||||
return `/api/v1/audits/jobs/${row.id}/`
|
||||
},
|
||||
getPatchData(row) {
|
||||
return {
|
||||
is_periodic: !row.is_periodic
|
||||
}
|
||||
},
|
||||
callback() {
|
||||
vm.$refs.ListPage.reloadTable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
hasImport: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -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
|
||||
|
||||
@@ -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,7 +49,8 @@ import Page from '@/layout/components/Page'
|
||||
import AdhocOpenDialog from './AdhocOpenDialog.vue'
|
||||
import AdhocSaveDialog from './AdhocSaveDialog.vue'
|
||||
import VariableHelpDialog from './VariableHelpDialog.vue'
|
||||
import { createJob, getJob, getTaskDetail, StopJob } from '@/api/ops'
|
||||
import setVariableDialog from '@/views/ops/Template/components/setVariableDialog'
|
||||
import { createJob, getJob, getTaskDetail, stopJob } from '@/api/ops'
|
||||
|
||||
export default {
|
||||
name: 'CommandExecution',
|
||||
@@ -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: {
|
||||
@@ -308,6 +323,13 @@ export default {
|
||||
return this.$refs.TreeTable.$refs.TreeList.$refs.AutoDataZTree.$refs.AutoDataZTree.$refs.dataztree.$refs.ztree
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
command(iNew, iOld) {
|
||||
if (iNew === '') {
|
||||
this.variableFormData = []
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.enableWS()
|
||||
this.initData()
|
||||
@@ -339,6 +361,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 +458,6 @@ export default {
|
||||
this.$message.error(this.$tc('RequiredRunas'))
|
||||
return
|
||||
}
|
||||
|
||||
const data = {
|
||||
assets: hosts,
|
||||
nodes: nodes,
|
||||
@@ -447,6 +472,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') }
|
||||
@@ -458,9 +486,9 @@ export default {
|
||||
})
|
||||
},
|
||||
stop() {
|
||||
StopJob({ task_id: this.currentTaskId }).then(() => {
|
||||
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 +504,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,14 @@ export default {
|
||||
columnsShow: {
|
||||
min: ['material', 'actions'],
|
||||
default: [
|
||||
'id', 'job', 'material', 'job_type', 'is_finished', 'is_success',
|
||||
'id', 'material', 'job_type', 'is_finished', 'is_success',
|
||||
'time_cost', 'date_created', 'actions'
|
||||
]
|
||||
},
|
||||
columnsMeta: {
|
||||
material: {
|
||||
width: '500px'
|
||||
},
|
||||
id: {
|
||||
formatter(row) {
|
||||
return row.id.slice(0, 8)
|
||||
@@ -44,6 +47,7 @@ export default {
|
||||
}
|
||||
},
|
||||
is_finished: {
|
||||
width: '100px',
|
||||
formatter: (row) => {
|
||||
if (row.is_finished) {
|
||||
return <i Class='fa fa-check text-primary'/>
|
||||
@@ -52,6 +56,7 @@ export default {
|
||||
}
|
||||
},
|
||||
is_success: {
|
||||
width: '100px',
|
||||
formatter: (row) => {
|
||||
if (!row.is_finished) {
|
||||
return <i Class='fa fa fa-spinner fa-spin'/>
|
||||
|
||||
@@ -535,7 +535,7 @@ export default {
|
||||
|
||||
.empty-file-tip {
|
||||
position: absolute;
|
||||
right: 20%;
|
||||
right: calc(50% - 230px);
|
||||
top: 50%;
|
||||
font-size: 18px;
|
||||
color: #c5c9cc;
|
||||
|
||||
@@ -13,6 +13,8 @@ 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'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -27,8 +29,8 @@ 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('Asset'), ['assets', 'nodes', 'runas', 'runas_policy']],
|
||||
[this.$t('Task'), ['module', 'argsLoadFromTemplate', 'args', 'playbook', 'variable', 'chdir', 'timeout', 'parameters']],
|
||||
[this.$t('Plan'), ['run_after_save', 'is_periodic', 'interval', 'crontab']],
|
||||
[this.$t('Other'), ['comment']]
|
||||
],
|
||||
@@ -88,13 +90,27 @@ 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
|
||||
delete item.id
|
||||
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 +118,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 +173,9 @@ export default {
|
||||
]
|
||||
}
|
||||
},
|
||||
variable: {
|
||||
component: Variable
|
||||
},
|
||||
timeout: {
|
||||
helpText: i18n.t('TimeoutHelpText')
|
||||
},
|
||||
@@ -142,7 +193,7 @@ export default {
|
||||
run_after_save: {
|
||||
type: 'checkbox',
|
||||
hidden: (formValue) => {
|
||||
return this.instantTask
|
||||
return true
|
||||
}
|
||||
},
|
||||
is_periodic: {
|
||||
@@ -151,11 +202,34 @@ export default {
|
||||
return this.instantTask
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
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.run_after_save = true
|
||||
const parameters = form.value.variable.reduce((acc, item) => {
|
||||
acc[item.var_name] = item.default_value
|
||||
return acc
|
||||
}, {})
|
||||
form.value['parameters'] = parameters
|
||||
this.submitForm(form, btn)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -192,7 +266,16 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -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>
|
||||
|
||||
167
src/views/ops/Job/components/BaseJob.vue
Normal file
167
src/views/ops/Job/components/BaseJob.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<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) => {
|
||||
this.showVariableDialog = false
|
||||
openTaskPage(resp.task_id)
|
||||
})
|
||||
},
|
||||
runJobWithParams(parameters) {
|
||||
this.runJob(this.item, parameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -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>
|
||||
|
||||
41
src/views/ops/Job/components/loadTemplateLink.vue
Normal file
41
src/views/ops/Job/components/loadTemplateLink.vue
Normal 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>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="16" :sm="24">
|
||||
<AutoDetailCard :fields="detailFields" :object="object" :url="url" />
|
||||
<AutoDetailCard :excludes="excludes" :object="object" :url="url" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -21,7 +21,8 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
url: `/api/v1/ops/adhocs/${this.object.id}/`
|
||||
url: `/api/v1/ops/adhocs/${this.object.id}/`,
|
||||
excludes: ['variable']
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -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: {
|
||||
@@ -30,6 +34,16 @@ export default {
|
||||
},
|
||||
updateSuccessNextRoute: {
|
||||
name: 'Template'
|
||||
},
|
||||
cleanFormValue(value) {
|
||||
const isClone = this?.$route?.query.clone_from !== undefined
|
||||
if (isClone) {
|
||||
value?.variable.map((item) => {
|
||||
delete item.id
|
||||
delete item.adhoc
|
||||
})
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,29 @@ export default {
|
||||
return {
|
||||
url: '/api/v1/ops/playbooks/',
|
||||
fields: [
|
||||
[this.$t('Basic'), ['name', 'comment', 'scope']]
|
||||
[this.$t('Basic'), ['name', 'scope', 'comment', 'variable']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
variable: {
|
||||
hidden: () => true
|
||||
}
|
||||
},
|
||||
createSuccessNextRoute: {
|
||||
name: 'Template'
|
||||
},
|
||||
updateSuccessNextRoute: {
|
||||
name: 'Template'
|
||||
},
|
||||
cleanFormValue(value) {
|
||||
const isClone = this?.$route?.query.clone_from !== undefined
|
||||
if (isClone) {
|
||||
value?.variable.map((item) => {
|
||||
delete item.id
|
||||
delete item.playbook
|
||||
delete item.job
|
||||
})
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -23,7 +23,7 @@ export default {
|
||||
return {
|
||||
url: `/api/v1/ops/playbooks/${this.object.id}/`,
|
||||
excludes: [
|
||||
'path', 'create_method', 'vcs_url'
|
||||
'variable', 'path', 'create_method', 'vcs_url'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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" :disable-edit.sync="disableEdit" @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,15 @@ export default {
|
||||
},
|
||||
hasChange(editor) {
|
||||
return editor.value !== editor.originValue
|
||||
},
|
||||
setVariable(variables) {
|
||||
if (this.disableEdit) {
|
||||
return
|
||||
}
|
||||
this.$axios.patch(`/api/v1/ops/playbooks/${this.object.id}/`,
|
||||
{ variable: variables }).catch(err => {
|
||||
this.$message.error(this.$tc('UpdateErrorMsg') + ' ' + err)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
78
src/views/ops/Template/components/AddVariableDialog.vue
Normal file
78
src/views/ops/Template/components/AddVariableDialog.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<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.name === variable.name || 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)
|
||||
const count = this.variables.filter(value => value.var_name === form.var_name || value.name === form.name).length
|
||||
// 不允许有相同的变量名
|
||||
if (count > 1) {
|
||||
this.variables.splice(i, 1)
|
||||
}
|
||||
this.iVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
175
src/views/ops/Template/components/Variable.vue
Normal file
175
src/views/ops/Template/components/Variable.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="variables el-data-table">
|
||||
<el-table :data="variables" class="el-table--fit el-table--border">
|
||||
<el-table-column show-overflow-tooltip :label="$tc('Name')" prop="name" />
|
||||
<el-table-column show-overflow-tooltip :label="$tc('VariableName')" prop="var_name" />
|
||||
<el-table-column show-overflow-tooltip :label="$tc('DefaultValue')" prop="default_value" />
|
||||
<el-table-column v-if="!disableEdit" :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 v-if="!disableEdit" 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: () => []
|
||||
},
|
||||
disableEdit: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
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) {
|
||||
value.map((item) => {
|
||||
item.default_value = item.text_default_value || item.select_default_value
|
||||
})
|
||||
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>
|
||||
66
src/views/ops/Template/components/setVariableDialog.vue
Normal file
66
src/views/ops/Template/components/setVariableDialog.vue
Normal 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>
|
||||
@@ -11,7 +11,6 @@
|
||||
<script>
|
||||
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||
import AssetSelect from '@/components/Apps/AssetSelect'
|
||||
import { getDayFuture } from '@/utils/time'
|
||||
import AccountFormatter from './components/AccountFormatter'
|
||||
import { AllAccount } from '../const'
|
||||
import ProtocolsSelect from '@/components/Form/FormFields/AllOrSpec.vue'
|
||||
@@ -32,9 +31,6 @@ export default {
|
||||
}
|
||||
return {
|
||||
initial: {
|
||||
is_active: true,
|
||||
date_start: new Date().toISOString(),
|
||||
date_expired: getDayFuture(25550, new Date()).toISOString(),
|
||||
nodes: nodesInitial,
|
||||
assets: assetsInitial,
|
||||
accounts: [AllAccount]
|
||||
|
||||
@@ -32,13 +32,21 @@ export default {
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
columnsMeta: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
columnsExclude: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableConfig: {
|
||||
url: this.url,
|
||||
columnsExclude: ['terminal'],
|
||||
columnsExclude: ['terminal', ...this.columnsExclude],
|
||||
columnsShow: this.columnsShow,
|
||||
columnsMeta: {
|
||||
id: {
|
||||
@@ -137,7 +145,8 @@ export default {
|
||||
hasUpdate: false,
|
||||
extraActions: this.extraActions
|
||||
}
|
||||
}
|
||||
},
|
||||
...this.columnsMeta
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BaseList :extra-actions="extraActions" :url="url" />
|
||||
<BaseList :extra-actions="extraActions" :url="url" :columns-meta="columnsMeta" :columns-exclude="columnsExclude" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -42,7 +42,13 @@ export default {
|
||||
download(`/api/v1/terminal/sessions/${row.id}/replay/download/`)
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
columnsExclude: ['has_command'],
|
||||
columnsMeta: {
|
||||
command_amount: {
|
||||
label: this.$t('CommandsTotal')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BaseList :extra-actions="extraActions" :url="url" />
|
||||
<BaseList :extra-actions="extraActions" :url="url" :columns-meta="columnsMeta" :columns-exclude="columnsExclude" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -100,12 +100,14 @@ export default {
|
||||
window.open(monitorUrl, '_blank', 'height=600, width=850, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
columnsExclude: ['has_command'],
|
||||
columnsMeta: {
|
||||
command_amount: {
|
||||
label: this.$t('CommandsTotal')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -23,6 +23,9 @@ export default {
|
||||
'AUTH_PASSKEY', 'FIDO_SERVER_ID', 'FIDO_SERVER_NAME'
|
||||
],
|
||||
fieldsMeta: {
|
||||
'FIDO_SERVER_ID': {
|
||||
placeholder: 'js.example.org'
|
||||
}
|
||||
},
|
||||
submitMethod() {
|
||||
return 'patch'
|
||||
@@ -30,8 +33,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
61
src/views/settings/Feature/Vault/Azure.vue
Normal file
61
src/views/settings/Feature/Vault/Azure.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<BaseKV :config="$data" :title="$tc('AzureKeyVault')" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseKV from './Base.vue'
|
||||
|
||||
export default {
|
||||
name: 'AzureKV',
|
||||
components: {
|
||||
BaseKV
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
url: `/api/v1/settings/setting/?category=azure_kv`,
|
||||
hasDetailInMsg: false,
|
||||
visible: false,
|
||||
moreButtons: [
|
||||
{
|
||||
title: this.$t('Test'),
|
||||
loading: false,
|
||||
callback: function(value, form, btn) {
|
||||
btn.loading = true
|
||||
vm.$axios.post(
|
||||
'/api/v1/settings/vault/azure/testing/',
|
||||
value
|
||||
).then(res => {
|
||||
vm.$message.success(res['msg'])
|
||||
}).catch(() => {
|
||||
vm.$log.error('err occur')
|
||||
}).finally(() => { btn.loading = false })
|
||||
}
|
||||
}
|
||||
],
|
||||
encryptedFields: ['VAULT_AZURE_CLIENT_SECRET'],
|
||||
fields: [
|
||||
[this.$t('AccountStorage'),
|
||||
[
|
||||
'VAULT_AZURE_HOST',
|
||||
'VAULT_AZURE_CLIENT_ID',
|
||||
'VAULT_AZURE_CLIENT_SECRET',
|
||||
'VAULT_AZURE_TENANT_ID'
|
||||
]
|
||||
]
|
||||
],
|
||||
fieldsMeta: {
|
||||
},
|
||||
submitMethod() {
|
||||
return 'patch'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
63
src/views/settings/Feature/Vault/Base.vue
Normal file
63
src/views/settings/Feature/Vault/Base.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-button size="mini" type="primary" icon="el-icon-setting" @click="visible=true">{{ $t('Setting') }}</el-button>
|
||||
<Dialog
|
||||
v-if="visible"
|
||||
:destroy-on-close="true"
|
||||
:show-cancel="false"
|
||||
:show-confirm="false"
|
||||
:title="title"
|
||||
:visible.sync="visible"
|
||||
width="70%"
|
||||
@confirm="onConfirm()"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<GenericCreateUpdateForm ref="form" v-bind="iConfig" @submitSuccess="submitSuccess" />
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog/index.vue'
|
||||
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'Base',
|
||||
components: {
|
||||
Dialog,
|
||||
GenericCreateUpdateForm
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iConfig() {
|
||||
return this.config
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onConfirm() {
|
||||
},
|
||||
submitSuccess(res) {
|
||||
this.$emit('input', !!res[this.enableField])
|
||||
this.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
64
src/views/settings/Feature/Vault/HCP.vue
Normal file
64
src/views/settings/Feature/Vault/HCP.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<BaseKV :config="$data" :title="$tc('HashicorpVault')" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseKV from './Base.vue'
|
||||
|
||||
export default {
|
||||
name: 'HashiCorpKV',
|
||||
components: {
|
||||
BaseKV
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
url: `/api/v1/settings/setting/?category=hcp`,
|
||||
hasDetailInMsg: false,
|
||||
visible: false,
|
||||
moreButtons: [
|
||||
{
|
||||
title: this.$t('Test'),
|
||||
loading: false,
|
||||
callback: function(value, form, btn) {
|
||||
btn.loading = true
|
||||
vm.$axios.post(
|
||||
'/api/v1/settings/vault/hcp/testing/',
|
||||
value
|
||||
).then(res => {
|
||||
vm.$message.success(res['msg'])
|
||||
}).catch(() => {
|
||||
vm.$log.error('err occur')
|
||||
}).finally(() => { btn.loading = false })
|
||||
}
|
||||
}
|
||||
],
|
||||
encryptedFields: ['VAULT_HCP_TOKEN'],
|
||||
fields: [
|
||||
[this.$t('AccountStorage'),
|
||||
[
|
||||
'VAULT_HCP_HOST',
|
||||
'VAULT_HCP_TOKEN',
|
||||
'VAULT_HCP_MOUNT_POINT'
|
||||
]
|
||||
]
|
||||
],
|
||||
fieldsMeta: {
|
||||
VAULT_HCP_MOUNT_POINT: {
|
||||
helpText: this.$t('VaultHCPMountPoint'),
|
||||
helpTextAsTip: true
|
||||
}
|
||||
},
|
||||
submitMethod() {
|
||||
return 'patch'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -12,6 +12,8 @@ import { GenericCreateUpdateForm } from '@/layout/components'
|
||||
import IBox from '@/components/IBox/index.vue'
|
||||
import { openTaskPage } from '@/utils/jms'
|
||||
import store from '@/store'
|
||||
import HashiCorpKV from './HCP.vue'
|
||||
import AzureKV from './Azure.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -25,24 +27,6 @@ export default {
|
||||
hasReset: false,
|
||||
helpText: this.$t('VaultHelpText'),
|
||||
moreButtons: [
|
||||
{
|
||||
title: this.$t('Test'),
|
||||
loading: false,
|
||||
disabled: !store.getters.publicSettings['VAULT_ENABLED'],
|
||||
callback: function(value, form, btn) {
|
||||
btn.loading = true
|
||||
vm.$axios.post(
|
||||
'/api/v1/settings/vault/testing/',
|
||||
value
|
||||
).then(res => {
|
||||
vm.$message.success(res['msg'])
|
||||
}).catch(() => {
|
||||
vm.$log.error('err occur')
|
||||
}).finally(() => {
|
||||
btn.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$t('Sync'),
|
||||
loading: false,
|
||||
@@ -62,17 +46,9 @@ export default {
|
||||
}
|
||||
}
|
||||
],
|
||||
encryptedFields: ['VAULT_HCP_TOKEN'],
|
||||
fields: [
|
||||
[this.$t('Backend'),
|
||||
[
|
||||
'VAULT_ENABLED',
|
||||
'VAULT_HCP_HOST',
|
||||
'VAULT_HCP_TOKEN',
|
||||
'VAULT_HCP_MOUNT_POINT'
|
||||
]
|
||||
],
|
||||
[this.$t('History'), ['HISTORY_ACCOUNT_CLEAN_LIMIT']]
|
||||
[this.$t('Basic'), ['VAULT_ENABLED', 'VAULT_BACKEND', 'HISTORY_ACCOUNT_CLEAN_LIMIT']],
|
||||
[this.$t('Provider'), ['HCP', 'AZURE']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
HISTORY_ACCOUNT_CLEAN_LIMIT: {
|
||||
@@ -83,21 +59,25 @@ export default {
|
||||
VAULT_ENABLED: {
|
||||
disabled: true
|
||||
},
|
||||
VAULT_HCP_HOST: {
|
||||
VAULT_BACKEND: {
|
||||
label: this.$t('Provider'),
|
||||
hidden: (formValue) => {
|
||||
return !formValue.VAULT_ENABLED
|
||||
return !formValue.VAULT_ENABLED || formValue['VAULT_BACKEND'] === 'local'
|
||||
},
|
||||
disabled: true
|
||||
},
|
||||
HCP: {
|
||||
label: this.$t('HashicorpVault'),
|
||||
component: HashiCorpKV,
|
||||
hidden: (formValue) => {
|
||||
return !formValue.VAULT_ENABLED || formValue['VAULT_BACKEND'] !== 'hcp'
|
||||
}
|
||||
},
|
||||
VAULT_HCP_TOKEN: {
|
||||
AZURE: {
|
||||
label: this.$t('AzureKeyVault'),
|
||||
component: AzureKV,
|
||||
hidden: (formValue) => {
|
||||
return !formValue.VAULT_ENABLED
|
||||
}
|
||||
},
|
||||
VAULT_HCP_MOUNT_POINT: {
|
||||
helpText: this.$t('VaultHCPMountPoint'),
|
||||
helpTextAsTip: true,
|
||||
hidden: (formValue) => {
|
||||
return !formValue.VAULT_ENABLED
|
||||
return !formValue.VAULT_ENABLED || formValue['VAULT_BACKEND'] !== 'azure'
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9,7 +9,7 @@
|
||||
<script>
|
||||
import TabPage from '@/layout/components/TabPage/index.vue'
|
||||
import Announcement from './Announcement.vue'
|
||||
import Vault from './Vault.vue'
|
||||
import Vault from './Vault/index.vue'
|
||||
import Ticket from './Ticket.vue'
|
||||
import Ops from './Ops.vue'
|
||||
import Chat from './Chat.vue'
|
||||
|
||||
@@ -114,8 +114,8 @@ export default {
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: this.$t('SubscriptionID'),
|
||||
value: this.licenseData.subscription_id
|
||||
key: this.$t('SerialNumber'),
|
||||
value: this.licenseData?.serial_no || ''
|
||||
},
|
||||
{
|
||||
key: this.$t('Corporation'),
|
||||
@@ -133,10 +133,6 @@ export default {
|
||||
key: this.$t('Edition'),
|
||||
value: this.licenseData.edition
|
||||
},
|
||||
{
|
||||
key: this.$t('SerialNumber'),
|
||||
value: this.licenseData?.serial_no || ''
|
||||
},
|
||||
{
|
||||
key: this.$t('Comment'),
|
||||
value: this.licenseData?.remark || ''
|
||||
|
||||
@@ -23,7 +23,7 @@ export default {
|
||||
[
|
||||
this.$t('Port'),
|
||||
[
|
||||
'http_port', 'https_port', 'ssh_port', 'rdp_port'
|
||||
'http_port', 'https_port', 'ssh_port', 'rdp_port','vnc_port'
|
||||
]
|
||||
],
|
||||
[
|
||||
|
||||
@@ -23,7 +23,7 @@ export default {
|
||||
min: ['name', 'actions'],
|
||||
default: [
|
||||
'name', 'host', 'actions',
|
||||
'http_port', 'https_port', 'ssh_port', 'rdp_port',
|
||||
'http_port', 'https_port', 'ssh_port', 'rdp_port', 'vnc_port',
|
||||
'mysql_port', 'mariadb_port', 'postgresql_port',
|
||||
'redis_port', 'sqlserver_port', 'oracle_port_range', 'is_active'
|
||||
]
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<el-form-item style="float: right">
|
||||
<template v-if="hasActionPerm">
|
||||
<el-button
|
||||
:disabled="object.status.value === 'closed'"
|
||||
:disabled="isDisabled || object.status.value === 'closed'"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="handleApprove"
|
||||
@@ -33,7 +33,7 @@
|
||||
<i class="fa fa-check" /> {{ $t('Accept') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
:disabled="object.status.value === 'closed'"
|
||||
:disabled="isDisabled || object.status.value === 'closed'"
|
||||
size="small"
|
||||
type="warning"
|
||||
@click="handleReject"
|
||||
@@ -43,7 +43,7 @@
|
||||
</template>
|
||||
<el-button
|
||||
v-if="isSelfTicket"
|
||||
:disabled="object.status.value === 'closed'"
|
||||
:disabled="isDisabled || object.status.value === 'closed'"
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="handleClose"
|
||||
@@ -92,6 +92,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isDisabled: false,
|
||||
comments: '',
|
||||
type_api: '',
|
||||
imageUrl: require('@/assets/img/avatar.png'),
|
||||
@@ -154,17 +155,35 @@ export default {
|
||||
this.createComment(function() {
|
||||
})
|
||||
const url = `/api/v1/tickets/${this.type_api}/${this.object.id}/approve/`
|
||||
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err))
|
||||
this.$axios.put(url).then(res => {
|
||||
this.reloadPage()
|
||||
}).catch(err => {
|
||||
this.$message.error(err)
|
||||
}).finally(() => {
|
||||
this.isDisabled = false
|
||||
})
|
||||
},
|
||||
defaultReject() {
|
||||
this.createComment(function() {
|
||||
})
|
||||
const url = `/api/v1/tickets/${this.type_api}/${this.object.id}/reject/`
|
||||
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err))
|
||||
this.$axios.put(url).then(res => {
|
||||
this.reloadPage()
|
||||
}).catch(err => {
|
||||
this.$message.error(err)
|
||||
}).finally(() => {
|
||||
this.isDisabled = false
|
||||
})
|
||||
},
|
||||
defaultClose() {
|
||||
const url = `/api/v1/tickets/${this.type_api}/${this.object.id}/close/`
|
||||
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err))
|
||||
this.$axios.put(url).then(res => {
|
||||
this.reloadPage()
|
||||
}).catch(err => {
|
||||
this.$message.error(err)
|
||||
}).finally(() => {
|
||||
this.isDisabled = false
|
||||
})
|
||||
},
|
||||
createComment(successCallback) {
|
||||
const commentText = this.form.comments
|
||||
@@ -185,17 +204,42 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
handleAction(actionType) {
|
||||
if (this.isDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isDisabled = true
|
||||
let handler
|
||||
switch (actionType) {
|
||||
case 'approve':
|
||||
handler = this.approve || this.defaultApprove
|
||||
break
|
||||
case 'reject':
|
||||
handler = this.reject || this.defaultReject
|
||||
break
|
||||
case 'close':
|
||||
handler = this.close || this.defaultClose
|
||||
break
|
||||
default:
|
||||
handler = null
|
||||
break
|
||||
}
|
||||
|
||||
if (handler) {
|
||||
handler()
|
||||
} else {
|
||||
this.$message.error('No handler for action')
|
||||
}
|
||||
},
|
||||
handleApprove() {
|
||||
const handler = this.approve || this.defaultApprove
|
||||
handler()
|
||||
this.handleAction('approve')
|
||||
},
|
||||
handleReject() {
|
||||
const handler = this.reject || this.defaultReject
|
||||
handler()
|
||||
this.handleAction('reject')
|
||||
},
|
||||
handleClose() {
|
||||
const handler = this.close || this.defaultClose
|
||||
handler()
|
||||
this.handleAction('close')
|
||||
},
|
||||
handleComment() {
|
||||
this.createComment(
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
<script>
|
||||
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||
import TransSelect from '@/components/Form/FormFields/TransferSelect.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -23,7 +22,6 @@ export default {
|
||||
],
|
||||
fieldsMeta: {
|
||||
users: {
|
||||
component: TransSelect,
|
||||
el: {
|
||||
url: '/api/v1/users/users/?fields_size=mini&order=name',
|
||||
ajax: {
|
||||
|
||||
@@ -9,6 +9,7 @@ import rules from '@/components/Form/DataForm/rules'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { Select2 } from '@/components'
|
||||
import store from '@/store'
|
||||
import { MFASystemSetting, MFALevel } from '../const'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -44,10 +45,7 @@ export default {
|
||||
}
|
||||
},
|
||||
mfa_level: {
|
||||
disabled: false,
|
||||
tips: {
|
||||
2: this.$t('forceEnableMFAHelpText')
|
||||
}
|
||||
disabled: false
|
||||
},
|
||||
email: {
|
||||
rules: [
|
||||
@@ -176,6 +174,7 @@ export default {
|
||||
if (obj?.id) {
|
||||
obj.org_roles = obj.org_roles?.map(({ id }) => id)
|
||||
obj.system_roles = obj.system_roles?.map(({ id }) => id)
|
||||
obj.mfa_level.value = this.initial.mfa_level || obj.mfa_level.value
|
||||
}
|
||||
return obj
|
||||
},
|
||||
@@ -193,6 +192,9 @@ export default {
|
||||
if (value.source !== 'local') {
|
||||
delete value.need_update_password
|
||||
}
|
||||
if ([MFALevel.allUsers, MFALevel.onlyAdminUsers].indexOf(value.mfa_level) > -1) {
|
||||
delete value.mfa_level
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -231,11 +233,26 @@ export default {
|
||||
this.initial.org_roles = roles.filter(role => role.name === 'OrgUser').map(role => role.id)
|
||||
},
|
||||
disableMFAFieldIfNeed(user) {
|
||||
let options = null
|
||||
let mfa_level = null
|
||||
// SECURITY_MFA_AUTH 0 不开启 1 全局开启 2 管理员开启
|
||||
const securityMFAAuth = store.getters.publicSettings['SECURITY_MFA_AUTH']
|
||||
const adminUserIsNeed = (user?.is_superuser || user?.is_org_admin) && this.$route.meta.action === 'update' &&
|
||||
store.getters.publicSettings['SECURITY_MFA_AUTH'] === 2
|
||||
if (store.getters.publicSettings['SECURITY_MFA_AUTH'] === 1 || adminUserIsNeed) {
|
||||
this.fieldsMeta['mfa_level'].disabled = true
|
||||
securityMFAAuth === MFASystemSetting.onlyAdminUsers
|
||||
if (securityMFAAuth === MFASystemSetting.allUsers) {
|
||||
options = [{ 'value': MFALevel.allUsers, 'label': this.$t('MFAAllUsers') }]
|
||||
mfa_level = MFALevel.allUsers
|
||||
}
|
||||
if (securityMFAAuth === MFASystemSetting.onlyAdminUsers && adminUserIsNeed) {
|
||||
options = [{
|
||||
'value': MFALevel.onlyAdminUsers,
|
||||
'label': this.$t('MFAOnlyAdminUsers')
|
||||
}]
|
||||
mfa_level = MFALevel.onlyAdminUsers
|
||||
}
|
||||
if (mfa_level !== null && options !== null) {
|
||||
this.fieldsMeta['mfa_level'].options = options
|
||||
this.initial.mfa_level = mfa_level
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import { createSourceIdCache } from '@/api/common'
|
||||
import { getDayFuture } from '@/utils/time'
|
||||
import InviteUsersDialog from './components/InviteUsersDialog'
|
||||
import AmountFormatter from '@/components/Table/TableFormatters/AmountFormatter.vue'
|
||||
import store from '@/store'
|
||||
import { MFASystemSetting } from '../const'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -65,7 +67,17 @@ export default {
|
||||
}
|
||||
},
|
||||
mfa_level: {
|
||||
width: '130px'
|
||||
width: '130px',
|
||||
formatter: (row) => {
|
||||
const securityMFAAuth = store.getters.publicSettings['SECURITY_MFA_AUTH']
|
||||
if (securityMFAAuth === MFASystemSetting.allUsers) {
|
||||
return this.$t('MFAAllUsers')
|
||||
} else if (securityMFAAuth === MFASystemSetting.onlyAdminUsers && (row?.is_superuser || row?.is_org_admin)) {
|
||||
return this.$t('MFAOnlyAdminUsers')
|
||||
} else {
|
||||
return row['mfa_level'].label
|
||||
}
|
||||
}
|
||||
},
|
||||
source: {
|
||||
width: '120px',
|
||||
|
||||
@@ -100,3 +100,13 @@ export const userJSONSelectMeta = (vm, withoutOrgRole = false) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
export const MFALevel = {
|
||||
allUsers: 3,
|
||||
onlyAdminUsers: 4
|
||||
}
|
||||
|
||||
export const MFASystemSetting = {
|
||||
notEnable: 0,
|
||||
allUsers: 1,
|
||||
onlyAdminUsers: 2
|
||||
}
|
||||
|
||||
@@ -74,6 +74,11 @@ module.exports = {
|
||||
target: 'http://127.0.0.1:4200',
|
||||
changeOrigin: true
|
||||
},
|
||||
'/facelive/': {
|
||||
target: 'http://localhost:9999',
|
||||
changeOrigin: true,
|
||||
ws: true
|
||||
},
|
||||
'^/(core|static|media)/': {
|
||||
target: process.env.VUE_APP_CORE_HOST || 'http://127.0.0.1:8080',
|
||||
changeOrigin: true
|
||||
@@ -81,8 +86,7 @@ module.exports = {
|
||||
},
|
||||
after: require('./mock/mock-server.js')
|
||||
},
|
||||
css: {
|
||||
},
|
||||
css: {},
|
||||
configureWebpack: {
|
||||
// provide the app's title in webpack's name field, so that
|
||||
// it can be accessed in index.html to inject the correct title.
|
||||
@@ -90,7 +94,8 @@ module.exports = {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve('src'),
|
||||
elementCss: resolve('node_modules/element-ui/lib/theme-chalk/index.css'),
|
||||
elementCss: resolve(
|
||||
'node_modules/element-ui/lib/theme-chalk/index.css'),
|
||||
elementLocale: resolve('node_modules/element-ui/lib/locale/lang/en.js')
|
||||
},
|
||||
extensions: ['.vue', '.js', '.json']
|
||||
@@ -148,7 +153,7 @@ module.exports = {
|
||||
.end()
|
||||
|
||||
config
|
||||
// https://webpack.js.org/configuration/devtool/#development
|
||||
// https://webpack.js.org/configuration/devtool/#development
|
||||
.when(process.env.NODE_ENV === 'development',
|
||||
config => config.devtool('cheap-source-map')
|
||||
)
|
||||
@@ -159,10 +164,11 @@ module.exports = {
|
||||
config
|
||||
.plugin('ScriptExtHtmlWebpackPlugin')
|
||||
.after('html')
|
||||
.use('script-ext-html-webpack-plugin', [{
|
||||
// `runtime` must same as runtimeChunk name. default is `runtime`
|
||||
inline: /runtime\..*\.js$/
|
||||
}])
|
||||
.use('script-ext-html-webpack-plugin', [
|
||||
{
|
||||
// `runtime` must same as runtimeChunk name. default is `runtime`
|
||||
inline: /runtime\..*\.js$/
|
||||
}])
|
||||
.end()
|
||||
config
|
||||
.optimization.splitChunks({
|
||||
|
||||
Reference in New Issue
Block a user