Merge pull request #3057 from jumpserver/dev

v3.2.0
This commit is contained in:
Jiangjie.Bai
2023-04-20 18:22:46 +08:00
committed by GitHub
37 changed files with 190 additions and 152 deletions

View File

@@ -60,7 +60,7 @@ export default {
]
},
url: '/api/v1/accounts/accounts/',
form: Object.assign({ 'on_invalid': 'skip' }, this.account || {}),
form: Object.assign({ 'on_invalid': 'error' }, this.account || {}),
encryptedFields: ['secret'],
fields: [
[this.$t('assets.Asset'), ['assets']],

View File

@@ -97,16 +97,17 @@ export default {
}
}
this.$axios.post(url, data, {
disableFlashErrorMsg: true
disableFlashErrorMsg: iVisible
}).then((data) => {
this.handleResult(data, null)
this.iVisible = iVisible
if (!iVisible) {
this.$emit('add', true)
}
}).catch(error => {
this.iVisible = iVisible
this.handleResult(null, error)
})
this.iVisible = iVisible
if (!iVisible) {
this.$emit('add', true)
}
},
editAccount(form) {
const data = { ...form }

View File

@@ -321,7 +321,6 @@ export default {
{
name: 'add-template',
title: this.$t('common.TemplateAdd'),
type: 'primary',
has: !(this.platform || this.asset),
can: () => {
return vm.$hasPerm('accounts.add_account') && !this.$store.getters.currentOrgIsRoot

View File

@@ -113,4 +113,8 @@ export default {
.color-default {
}
::v-deep .el-data-table .el-table .el-table__row > td > div > span {
white-space: inherit;
}
</style>

View File

@@ -0,0 +1,36 @@
<template>
<div>
{{ value? trueText : falseText }}
</div>
</template>
<script>
export default {
props: {
value: {
type: [String, Boolean],
default: () => false
},
trueText: {
type: String,
default: function() {
return this.$t('common.Yes')
}
},
falseText: {
type: String,
default: function() {
return this.$t('common.No')
}
}
},
data() {
return {}
}
}
</script>
<style scoped>
</style>

View File

@@ -285,7 +285,9 @@ export default {
const data = []
// eslint-disable-next-line prefer-const
for (let [key, value] of Object.entries(errorData)) {
if (typeof value === 'object') {
if (Array.isArray(value)) {
value = JSON.stringify(value)
} else if (typeof value === 'object') {
value = this.beautifyErrorData(value)
}
let label = this.tableColumnNameMapper[key]

View File

@@ -63,9 +63,9 @@ export default {
this.inEditMode = true
},
getCellValue(val) {
let v
let v = ''
if (val && typeof val === 'object') {
v = val['name'] || val['display_name'] || ''
v = val['name'] || val['display_name'] || JSON.stringify(val)
}
return v || val
},

View File

@@ -141,7 +141,7 @@
"users": "User",
"Rules": "Rule",
"Action": "Action",
"ip_group_help_text": "The format is a comma-separated string, * means match all. Example: 192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64",
"ip_group_help_text": "* means match all. Example: 192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64",
"apply_login_account": "Apply login account"
},
"applications": {
@@ -1720,7 +1720,9 @@
"UpdateNodeAssetHardwareInfo": "Update node asset hardware information"
},
"users": {
"SetStatus": "Set status",
"Set": "Set",
"NotSet": "Not set",
"Login": "Users login",
"InviteSuccess": "Invite success",
"inputPhone": "Please enter your mobile phone number",

View File

@@ -120,7 +120,7 @@
"AccountUsername": "アカウント (ユーザー名)",
"username": "ユーザー名",
"ip_group": "IPグループ",
"ip_group_help_text": "コンマ区切りの文字列で、 * はすべてにマッチすることを示します。例: 192.168.10.1、192.168.1.0/24、10.1.1-10.1.1. 20、2001:db8:2de::e 13、2001:db8:1a:1110::/64",
"ip_group_help_text": "* はすべてにマッチすることを示します。例: 192.168.10.1、192.168.1.0/24、10.1.1-10.1.1. 20、2001:db8:2de::e 13、2001:db8:1a:1110::/64",
"action": "アクション",
"Rules": "ルール",
"Action": "アクション",
@@ -1712,7 +1712,9 @@
"UpdateNodeAssetHardwareInfo": "ノード資産ハードウェア情報の更新"
},
"users": {
"SetStatus": "ステータスの設定",
"Set": "設定",
"NotSet": "未設定",
"Login": "ユーザー登録",
"InviteSuccess": "成功招待",
"inputPhone": "携帯電話の番号をお願いします",

View File

@@ -121,7 +121,7 @@
"reviewer": "审批人",
"username": "用户名",
"ip_group": "IP 组",
"ip_group_help_text": "格式为逗号分隔的字符串, * 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64",
"ip_group_help_text": "* 表示匹配所有。例如: 192.168.10.1, 192.168.1.0/24, 10.1.1.1-10.1.1.20, 2001:db8:2de::e13, 2001:db8:1a:1110::/64",
"action": "动作",
"Rules": "规则",
"Action": "动作",
@@ -1708,7 +1708,9 @@
"UpdateNodeAssetHardwareInfo": "更新节点资产硬件信息"
},
"users": {
"SetStatus": "设置状态",
"Set": "已设置",
"NotSet": "未设置",
"Login": "用户登录",
"InviteSuccess": "邀请成功",
"inputPhone": "请输入手机号码",

View File

@@ -62,6 +62,7 @@
:title="currentMsg.content.subject"
:visible.sync="msgDetailVisible"
@cancel="cancelRead"
@close="markAsRead([currentMsg])"
@confirm="markAsRead([currentMsg])"
>
<div class="msg-detail">

View File

@@ -132,7 +132,7 @@ export default {
}
},
{
path: 'update',
path: ':id/update',
component: () => import('@/views/ops/Job/JobUpdateCreate'),
name: 'JobUpdate',
hidden: true,
@@ -217,11 +217,22 @@ export default {
activeMenu: '/workbench/ops/templates'
}
},
{
path: 'playbook/create',
name: 'PlaybookCreate',
hidden: true,
component: () => import('@/views/ops/Template/Playbook/PlaybookCreateUpdate'),
meta: {
title: i18n.t('ops.PlaybookCreate'),
permissions: ['ops.add_playbook'],
activeMenu: '/workbench/ops/templates'
}
},
{
path: 'playbook/:id/update',
name: 'PlaybookUpdate',
hidden: true,
component: () => import('@/views/ops/Template/Playbook/PlaybookUpdate'),
component: () => import('@/views/ops/Template/Playbook/PlaybookCreateUpdate'),
meta: {
title: i18n.t('ops.PlaybookUpdate'),
permissions: ['ops.change_playbook'],

View File

@@ -196,7 +196,7 @@ export default {
callbacks: Object.freeze({
change: (value) => {
const relationUrl = `/api/v1/accounts/accounts/${this.object.id}/`
return this.$axios.patch(relationUrl, { su_from: value })
return this.$axios.patch(relationUrl, { su_from: value, name: this.object.name })
}
})
}
@@ -205,7 +205,7 @@ export default {
url: `/api/v1/accounts/accounts/${this.object.id}`,
excludes: [
'template', 'privileged', 'secret',
'passphrase', 'spec_info'
'passphrase', 'spec_info', 'params'
],
formatters: {
asset: (item, value) => {
@@ -214,6 +214,9 @@ export default {
params: { id: this.object.asset.id }
}
return <router-link to={route} >{ value }</router-link>
},
su_from: (item, value) => {
return <span>{value?.name ? value?.name + `(${value?.username})` : ''}</span>
}
}
}

View File

@@ -143,6 +143,7 @@ export default {
that.select2.disabledValues.splice(i, 1)
}
this.$message.success(this.$tc('common.deleteSuccessMsg'))
window.location.reload()
this.$refs.listTable.reloadTable()
}
}

View File

@@ -5,9 +5,7 @@
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import rules from '@/components/DataForm/rules'
import {
afterGetFormValueForHandleUserAssetAccount, cleanFormValueForHandleUserAssetAccount, UserAssetAccountFieldInitial
} from '../common'
import { cleanFormValueForHandleUserAssetAccount } from '../common'
export default {
name: 'AclCreateUpdate',
@@ -16,9 +14,7 @@ export default {
},
data() {
return {
initial: {
...UserAssetAccountFieldInitial
},
initial: {},
fields: [
[this.$t('common.Basic'), ['name', 'priority']],
[this.$t('acl.users'), ['users']],
@@ -56,7 +52,6 @@ export default {
}
},
url: '/api/v1/acls/login-asset-acls/',
afterGetFormValue: afterGetFormValueForHandleUserAssetAccount,
cleanFormValue: cleanFormValueForHandleUserAssetAccount
}
},

View File

@@ -12,8 +12,6 @@
import { GenericCreateUpdatePage } from '@/layout/components'
import rules from '@/components/DataForm/rules'
import {
UserAssetAccountFieldInitial,
afterGetFormValueForHandleUserAssetAccount,
cleanFormValueForHandleUserAssetAccount
} from '../../common'
@@ -25,8 +23,7 @@ export default {
data() {
return {
initial: {
is_active: true,
...UserAssetAccountFieldInitial
is_active: true
},
fields: [
[this.$t('common.Basic'), ['name']],
@@ -79,7 +76,6 @@ export default {
type: 'checkbox'
}
},
afterGetFormValue: afterGetFormValueForHandleUserAssetAccount,
cleanFormValue: cleanFormValueForHandleUserAssetAccount
}
}

View File

@@ -46,7 +46,7 @@ export default {
type: 'input',
el: {
type: 'textarea',
placeholder: 'rm.*|reboot|shutdown',
placeholder: commandPlaceholder,
rows: 4
},
helpText: ''

View File

@@ -20,7 +20,7 @@ export default {
initial: {
action: 'reject',
rules: {
ip_group: '*'
ip_group: ['*']
},
user: this.$route.query.user,
users: {
@@ -119,7 +119,6 @@ export default {
}
},
afterGetFormValue(validValues) {
validValues.rules.ip_group = validValues.rules.ip_group.toString()
if (!this.$route.query.user) {
validValues.users.username_group = validValues.users.username_group.toString()
}

View File

@@ -6,7 +6,7 @@
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import { assetFieldsMeta } from '@/views/assets/const'
import { encryptPassword } from '@/utils/crypto'
import { setUrlParam, getUpdateObjURL } from '@/utils/common'
import { getUpdateObjURL, setUrlParam } from '@/utils/common'
export default {
components: { GenericCreateUpdatePage },
@@ -51,6 +51,7 @@ export default {
initial: {},
platform: {},
url: '/api/v1/assets/hosts/',
hasReset: false,
createSuccessNextRoute: this.createSuccessNextRoute,
updateSuccessNextRoute: this.updateSuccessNextRoute,
hasDetailInMsg: false,
@@ -87,7 +88,8 @@ export default {
iConfig() {
const { addFields, addFieldsMeta, defaultConfig } = this
let url = this.url
if (this.$route.query.platform) {
const { id = '' } = this.$route.params
if (this.$route.query.platform && !id) {
url = setUrlParam(url, 'platform', this.$route.query.platform)
}
// 过滤类型为null, undefined 的元素

View File

@@ -166,7 +166,11 @@ export default {
'address',
{
key: this.$t('assets.Protocols'),
value: this.object.protocols.map(i => i.name + '/' + i.port).join(',')
value: this.object.protocols.map(
i => (
this.object.address.startsWith('https://') ? 'https' : i.name
) + '/' + i.port
).join(',')
},
{
key: this.$t('assets.Domain'),

View File

@@ -57,7 +57,7 @@ export default {
params: { id: this.$route.params.id },
query: {
platform: this.asset.platform.id,
platform_type: this.asset.type.value
type: this.asset.type.value
}
})
}

View File

@@ -102,7 +102,8 @@ export default {
route.query.type = row.type.value
route.query.category = row.type.category
}
vm.$router.push(route)
const { href } = vm.$router.resolve(route)
window.open(href, '_blank')
}
const extraQuery = this.$route.params?.extraQuery || {}
return {

View File

@@ -159,7 +159,9 @@ export default {
category: platform.category.value
}
this.$router.push({ name: route, query })
const router = { name: route, query }
const { href } = this.$router.resolve(router)
window.open(href, '_blank')
}
}
}

View File

@@ -70,7 +70,7 @@ export default {
{ 'name': 'ssh', 'port': 22, 'primary': true, 'default': false, 'required': false },
{ 'name': 'telnet', 'port': 23, 'primary': false, 'default': false, 'required': false },
{ 'name': 'vnc', 'port': 5900, 'primary': false, 'default': false, 'required': false },
{ 'name': 'rdp', 'port': 3306, 'primary': false, 'default': false, 'required': false }
{ 'name': 'rdp', 'port': 3389, 'primary': false, 'default': false, 'required': false }
]
}
},

View File

@@ -11,6 +11,7 @@ export default {
BaseAssetCreateUpdate
},
data() {
const platformType = this.$route.query.type
return {
url: '/api/v1/assets/gateways/',
updateInitial: async(initial) => {
@@ -23,6 +24,18 @@ export default {
addFieldsMeta: {
domain: {
disabled: true
},
platform: {
disabled: true,
el: {
multiple: false,
ajax: {
url: `/api/v1/assets/platforms/?type=${platformType}`,
transformOption: (item) => {
return { label: item.name, value: item.id }
}
}
}
}
},
createSuccessNextRoute: {

View File

@@ -3,14 +3,12 @@
<el-button
v-if="hasButton"
:disabled="!canSetting"
size="mini"
size="small"
class="setting"
:icon="icon"
type="primary"
@click="onSetting"
>
{{ btnText }}
</el-button>
/>
<Dialog
v-if="isVisible"
width="60%"
@@ -19,8 +17,6 @@
:show-cancel="false"
:show-confirm="false"
:destroy-on-close="true"
v-bind="$attrs"
v-on="$listeners"
>
<AutoDataForm
ref="autoDataForm"
@@ -35,7 +31,8 @@
</template>
<script>
import { Dialog, AutoDataForm } from '@/components'
import Dialog from '../../../components/Dialog'
import AutoDataForm from '../../../components/AutoDataForm'
import { DynamicInput } from '@/components/FormFields'
export default {
@@ -57,12 +54,12 @@ export default {
btnText: {
type: String,
default: function() {
return this.$t('common.Setting')
return ''
}
},
icon: {
type: String,
default: ''
default: 'el-icon-setting'
},
url: {
type: String,
@@ -167,4 +164,8 @@ export default {
</script>
<style lang="scss" scoped>
.setting {
height: 34px;
padding-top: 10px;
}
</style>

View File

@@ -8,6 +8,7 @@
:fields-meta="fieldsMeta"
:has-detail-in-msg="false"
:initial="initial"
:has-reset="false"
:url="url"
/>
</div>
@@ -113,6 +114,7 @@ export default {
async updateSuMethods(constrains) {
this.suMethodLimits = constrains['su_methods'] || []
this.updateSuMethodOptions()
this.initial.su_method = this.suMethodLimits[0]
},
async setCategories() {
const category = this.$route.query.category
@@ -170,5 +172,17 @@ export default {
width: 100%;
}
}
>>> .itemMethodKey.el-form-item {
display: inline-block;
width: 100%;
.el-form-item__content {
width: 70%;
}
}
>>> .itemParamsKey.el-form-item {
display: inline-block;
position: absolute;
right: 20px;
}
</style>

View File

@@ -24,6 +24,23 @@ export default {
const vm = this
return {
loading: true,
nameComponentMap: {
host: {
icon: 'fa-inbox'
},
device: {
icon: 'fa-microchip'
},
database: {
icon: 'fa-database'
},
cloud: {
icon: 'fa-cloud'
},
web: {
icon: 'fa-globe'
}
},
tab: {
submenu: [],
activeMenu: 'host'
@@ -86,6 +103,7 @@ export default {
}
},
headerActions: {
hasBulkDelete: false,
hasRightActions: true,
createRoute: 'PlatformCreate',
canCreate: () => {
@@ -134,12 +152,13 @@ export default {
},
async setCategories() {
const state = await this.$store.dispatch('assets/getAssetCategories')
this.tab.submenu = state.assetCategories.map(item => {
return {
title: item.label,
name: item.value
}
})
for (const item of state.assetCategories) {
const name = item.value
this.nameComponentMap[name]['name'] = name
this.nameComponentMap[name]['title'] = item.label
}
this.tab.submenu = _.toArray(this.nameComponentMap)
}
}
}

View File

@@ -32,7 +32,7 @@ export const platformFieldsMeta = (vm) => {
gather_facts_method: {},
push_account_method: {},
push_account_params: {
label: vm.$t('assets.PushParams')
label: ''
},
change_secret_method: {
on: {
@@ -42,7 +42,7 @@ export const platformFieldsMeta = (vm) => {
}
},
change_secret_params: {
label: vm.$t('assets.ChangeSecretParams'),
label: '',
el: {
title: vm.$t('assets.ChangeSecretParams'),
method: 'change_secret_posix'
@@ -125,6 +125,8 @@ export const setAutomations = (vm) => {
_.set(autoFieldsMeta, `${itemMethodKey}.hidden`, (formValue) => {
return !formValue[itemEnabledKey] || !formValue['ansible_enabled']
})
// 设置 enableMethod className
_.set(autoFieldsMeta, `${itemMethodKey}.attrs.class`, 'itemMethodKey')
// 设置 enableParams Hidden
_.set(autoFieldsMeta, `${itemParamsKey}.hidden`, (formValue) => {
return !formValue[itemEnabledKey] || !formValue['ansible_enabled']
@@ -139,6 +141,10 @@ export const setAutomations = (vm) => {
// 设置 params 类型字段的组件和组件参数
if (needSettingParamsFields.includes(item)) {
// 设置 enableParams label
_.set(autoFieldsMeta, `${itemParamsKey}.label`, '')
// 设置 enableParams className
_.set(autoFieldsMeta, `${itemParamsKey}.attrs.class`, 'itemParamsKey')
_.set(autoFieldsMeta, `${itemParamsKey}.component`, AutomationParamsSetting)
_.set(autoFieldsMeta, `${itemParamsKey}.el.method`, initial[itemMethodKey])
}

View File

@@ -29,7 +29,7 @@ export const filterSelectValues = (values) => {
export const assetFieldsMeta = (vm) => {
const platformProtocols = []
const secretTypes = []
const platformType = vm?.$route.query.type
const platformType = vm?.$route.query?.type
return {
address: {
rules: [rules.specialEmojiCheck, rules.RequiredChange]

View File

@@ -2,7 +2,6 @@
<div>
<GenericListTable ref="list" :table-config="tableConfig" :header-actions="headerActions" />
<UploadDialog v-if="uploadDialogVisible" :visible.sync="uploadDialogVisible" @completed="refreshTable" />
<CreatePlaybookDialog v-if="createDialogVisible" :visible.sync="createDialogVisible" @completed="refreshTable" />
</div>
</template>
@@ -10,13 +9,11 @@
import GenericListTable from '@/layout/components/GenericListTable'
import UploadDialog from '@/views/ops/Template/Playbook/UploadDialog'
import { ActionsFormatter } from '@/components/TableFormatters'
import CreatePlaybookDialog from '@/views/ops/Template/Playbook/CreatePlaybookDialog.vue'
export default {
components: {
UploadDialog,
GenericListTable,
CreatePlaybookDialog
GenericListTable
},
data() {
return {
@@ -49,18 +46,16 @@ export default {
}
},
headerActions: {
createRoute: 'PlaybookCreate',
hasRefresh: true,
hasExport: false,
hasImport: false,
hasMoreActions: true,
onCreate: () => {
this.uploadDialogVisible = true
},
moreCreates: {
callback: (item) => {
switch (item.name) {
case 'create':
this.createDialogVisible = true
this.$router.push({ name: 'PlaybookCreate' })
break
case 'upload':
this.uploadDialogVisible = true

View File

@@ -1,79 +0,0 @@
<template>
<Dialog
:title="$tc('common.Create')"
:visible.sync="iVisible"
width="50%"
top="1vh"
:show-cancel="false"
:show-confirm="false"
>
<GenericCreateUpdateForm
ref="createUpdateForm"
v-bind="$data"
:on-perform-success="onSubmitSuccess"
v-on="$listeners"
/>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
import { GenericCreateUpdateForm } from '@/layout/components'
export default {
components: {
Dialog,
GenericCreateUpdateForm
},
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
hasSaveContinue: false,
url: '/api/v1/ops/playbooks/',
fields: [
['', ['name', 'comment']]
],
createSuccessNextRoute: {
name: 'Template'
},
updateSuccessNextRoute: {
name: 'Template'
}
}
},
computed: {
iVisible: {
set(val) {
this.$emit('update:visible', val)
},
get() {
return this.visible
}
}
},
methods: {
onSubmitSuccess() {
this.iVisible = false
this.$emit('completed')
}
}
}
</script>
<style lang="scss" scoped>
.el-row-divider {
margin-bottom: 20px;
}
.select-prop-label {
float: right;
padding-right: 30px;
}
</style>

View File

@@ -15,6 +15,7 @@
<script>
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm'
import { IBox } from '@/components'
import TextReadonly from '@/components/FormFields/TextReadonly.vue'
export default {
name: 'SecretKeyUpdate',
@@ -34,8 +35,12 @@ export default {
fields: ['has_secret_key', 'new_secret_key', 'new_secret_key_again'],
fieldsMeta: {
has_secret_key: {
label: this.$t('users.Set'),
type: 'switch',
label: this.$t('users.SetStatus'),
component: TextReadonly,
el: {
trueText: this.$t('users.Set'),
falseText: this.$t('users.NotSet')
},
disabled: true
},
new_secret_key: {

View File

@@ -26,7 +26,7 @@ export default {
return {
tableConfig: {
url: this.url,
columnsExtra: ['index'],
columnsExtra: ['index', 'duration'],
columnsShow: {
min: ['id', 'actions'],
default: [

View File

@@ -1,7 +1,7 @@
<template>
<Dialog
:title="$tc('common.OfflineUpload')"
:show-cancel="false"
:title="$tc('common.OfflineUpload')"
v-bind="$attrs"
@cancel="onCancel"
@confirm="onSubmit"
@@ -82,12 +82,13 @@ export default {
disableFlashErrorMsg: true
}
).then(res => {
this.$message.success(this.$t('terminal.UploadSucceed'))
this.$message.success(this.$tc('terminal.UploadSucceed'))
this.$emit('update:visible', false)
this.$emit('upload-event', res)
}).catch(err => {
const error = err.response.data?.error || this.$t('terminal.UploadFailed')
this.$message.error(error)
const error = err.response.data
const msg = error?.message || error?.detail || error?.error || JSON.stringify(error)
this.$message.error(msg)
})
setTimeout(() => {

View File

@@ -41,7 +41,7 @@ export default {
},
{
key: this.$t('assets.Account'),
value: object.rel_snapshot.apply_run_account
value: object.apply_run_account
},
{
key: this.$t('tickets.ApplyRunCommand'),