mirror of
https://github.com/jumpserver/lina.git
synced 2026-01-29 21:28:52 +00:00
Merge pull request #2972 from jumpserver/pr@dev@perf_account_template
perf: 优化账号批量创建
This commit is contained in:
@@ -50,21 +50,21 @@ export default {
|
||||
protocols: [
|
||||
{
|
||||
name: 'ssh',
|
||||
secret_types: ['password', 'ssh_key', 'token', 'api_key']
|
||||
secret_types: ['password', 'ssh_key', 'token', 'access_key']
|
||||
}
|
||||
]
|
||||
},
|
||||
url: '/api/v1/accounts/accounts/',
|
||||
form: this.account || {},
|
||||
form: Object.assign(this.account, { on_invalid: 'skip' }),
|
||||
encryptedFields: ['secret'],
|
||||
fields: [
|
||||
[this.$t('assets.Asset'), ['assets']],
|
||||
[this.$t('common.Basic'), ['name', 'username', 'privileged', 'su_from']],
|
||||
[this.$t('assets.Secret'), [
|
||||
'secret_type', 'secret', 'ssh_key', 'token',
|
||||
'api_key', 'passphrase'
|
||||
'secret_type', 'secret', 'ssh_key',
|
||||
'token', 'access_key', 'passphrase'
|
||||
]],
|
||||
[this.$t('common.Other'), ['push_now', 'strategy', 'is_active', 'comment']]
|
||||
[this.$t('common.Other'), ['push_now', 'on_invalid', 'is_active', 'comment']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
assets: {
|
||||
@@ -78,7 +78,7 @@ export default {
|
||||
return this.platform || this.asset
|
||||
}
|
||||
},
|
||||
strategy: {
|
||||
on_invalid: {
|
||||
rules: [Required],
|
||||
label: this.$t('ops.RunasPolicy'),
|
||||
helpText: this.$t('accounts.BulkCreateStrategy'),
|
||||
@@ -154,11 +154,11 @@ export default {
|
||||
component: UploadSecret,
|
||||
hidden: (formValue) => formValue.secret_type !== 'token'
|
||||
},
|
||||
api_key: {
|
||||
id: 'api_key',
|
||||
access_key: {
|
||||
id: 'access_key',
|
||||
label: this.$t('assets.AccessKey'),
|
||||
component: UploadSecret,
|
||||
hidden: (formValue) => formValue.secret_type !== 'api_key'
|
||||
hidden: (formValue) => formValue.secret_type !== 'access_key'
|
||||
},
|
||||
secret_type: {
|
||||
type: 'radio-group',
|
||||
@@ -210,7 +210,7 @@ export default {
|
||||
},
|
||||
{
|
||||
label: this.$t('assets.AccessKey'),
|
||||
value: 'api_key'
|
||||
value: 'access_key'
|
||||
}
|
||||
]
|
||||
const secretTypes = []
|
||||
|
||||
@@ -59,6 +59,9 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
protocols() {
|
||||
return this.asset ? this.asset.protocol : []
|
||||
},
|
||||
iVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
@@ -66,46 +69,62 @@ export default {
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
},
|
||||
protocols() {
|
||||
return this.asset ? this.asset.protocol : []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addAccount(form) {
|
||||
const formValue = Object.assign({}, form)
|
||||
let assets = []
|
||||
let data, url
|
||||
if (this.asset) {
|
||||
assets = [this.asset.id]
|
||||
data = {
|
||||
asset: this.asset.id,
|
||||
...formValue
|
||||
}
|
||||
url = `/api/v1/accounts/accounts/`
|
||||
} else {
|
||||
assets = formValue.assets
|
||||
data = formValue
|
||||
url = `/api/v1/accounts/accounts/bulk/`
|
||||
if (data.assets.length === 0) {
|
||||
this.$message.error(this.$tc('assets.PleaseSelectAsset'))
|
||||
return
|
||||
}
|
||||
}
|
||||
delete formValue.assets
|
||||
if (assets.length === 0) {
|
||||
this.$message.error(this.$tc('assets.PleaseSelectAsset'))
|
||||
return
|
||||
}
|
||||
const data = []
|
||||
for (const asset of assets) {
|
||||
data.push({
|
||||
...formValue,
|
||||
asset
|
||||
})
|
||||
}
|
||||
this.$axios.post(`/api/v1/accounts/accounts/`, data).then(() => {
|
||||
this.iVisible = false
|
||||
this.$emit('add', true)
|
||||
this.$message.success(this.$tc('common.createSuccessMsg'))
|
||||
}).catch(error => this.setFieldError(error))
|
||||
this.$axios.post(url, data).then((data) => {
|
||||
this.handleResult(data, null)
|
||||
}).catch(error => {
|
||||
this.handleResult(null, error)
|
||||
})
|
||||
},
|
||||
editAccount(form) {
|
||||
const data = { ...form }
|
||||
this.$axios.patch(`/api/v1/accounts/accounts/${this.account.id}/`, data).then(() => {
|
||||
this.iVisible = false
|
||||
this.$emit('add', true)
|
||||
this.$message.success(this.$tc('common.updateSuccessMsg'))
|
||||
}).catch(error => this.setFieldError(error))
|
||||
},
|
||||
handleResult(resp, error) {
|
||||
let bulkCreate = !this.asset
|
||||
if (error && !Array.isArray(error)) {
|
||||
bulkCreate = false
|
||||
}
|
||||
// if (resp && !Array.isArray(resp)) {
|
||||
// bulkCreate = false
|
||||
// }
|
||||
if (!bulkCreate) {
|
||||
if (!error) {
|
||||
this.$message.success(this.$tc('common.createSuccessMsg'))
|
||||
} else {
|
||||
this.setFieldError(error)
|
||||
}
|
||||
} else {
|
||||
let result
|
||||
if (error) {
|
||||
result = error.response.data
|
||||
} else {
|
||||
result = resp
|
||||
}
|
||||
this.$emit('bulk-create-done', result)
|
||||
}
|
||||
},
|
||||
setFieldError(error) {
|
||||
const response = error.response
|
||||
const data = response.data
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
:title="accountCreateUpdateTitle"
|
||||
:visible.sync="showAddDialog"
|
||||
@add="addAccountSuccess"
|
||||
@bulk-create-done="showBulkCreateResult($event)"
|
||||
/>
|
||||
<ResultDialog
|
||||
v-if="showResultDialog"
|
||||
:result="createAccountResults"
|
||||
:visible.sync="showResultDialog"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -32,10 +38,12 @@ import UpdateSecretInfo from './UpdateSecretInfo'
|
||||
import AccountCreateUpdate from './AccountCreateUpdate'
|
||||
import { connectivityMeta } from './const'
|
||||
import { openTaskPage } from '@/utils/jms'
|
||||
import ResultDialog from './BulkCreateResultDialog.vue'
|
||||
|
||||
export default {
|
||||
name: 'AccountListTable',
|
||||
components: {
|
||||
ResultDialog,
|
||||
ListTable,
|
||||
UpdateSecretInfo,
|
||||
ViewSecret,
|
||||
@@ -95,7 +103,9 @@ export default {
|
||||
return {
|
||||
showViewSecretDialog: false,
|
||||
showUpdateSecretDialog: false,
|
||||
showResultDialog: false,
|
||||
showAddDialog: false,
|
||||
createAccountResults: [],
|
||||
accountCreateUpdateTitle: this.$t('assets.AddAccount'),
|
||||
iAsset: this.asset,
|
||||
account: {},
|
||||
@@ -298,11 +308,6 @@ export default {
|
||||
}
|
||||
},
|
||||
...this.headerExtraActions
|
||||
// {
|
||||
// name: 'autocreate',
|
||||
// title: this.$t('accounts.AutoCreate'),
|
||||
// type: 'default'
|
||||
// }
|
||||
],
|
||||
extraMoreActions: [
|
||||
{
|
||||
@@ -365,6 +370,13 @@ export default {
|
||||
},
|
||||
refresh() {
|
||||
this.$refs.ListTable.reloadTable()
|
||||
},
|
||||
showBulkCreateResult(results) {
|
||||
this.showResultDialog = false
|
||||
this.createAccountResults = results
|
||||
setTimeout(() => {
|
||||
this.showResultDialog = true
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
116
src/components/AccountListTable/BulkCreateResultDialog.vue
Normal file
116
src/components/AccountListTable/BulkCreateResultDialog.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:show-cancel="false"
|
||||
:title="title"
|
||||
v-bind="$attrs"
|
||||
@confirm="closeDialog"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<el-alert style="margin-bottom: 10px" type="success">
|
||||
<span v-for="item of summary" :key="item.key"><b>{{ item.label }}</b>: {{ item.value }} </span>
|
||||
</el-alert>
|
||||
<DataTable :config="config" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog/index.vue'
|
||||
import DataTable from '@/components/DataTable/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'ResultDialog',
|
||||
components: {
|
||||
DataTable,
|
||||
Dialog
|
||||
},
|
||||
props: {
|
||||
result: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const errorProp = this.$t('common.Error')
|
||||
const stateMap = {
|
||||
'created': this.$tc('common.Created'),
|
||||
'updated': this.$tc('common.Updated'),
|
||||
'skipped': this.$tc('common.Skipped')
|
||||
}
|
||||
const stateClsMap = {
|
||||
'created': 'color-primary',
|
||||
'updated': 'color-success',
|
||||
'skipped': 'color-default'
|
||||
}
|
||||
return {
|
||||
title: this.$t('accounts.AddAccountResult'),
|
||||
config: {
|
||||
columns: [
|
||||
{
|
||||
prop: 'asset',
|
||||
label: this.$t('assets.Asset')
|
||||
},
|
||||
{
|
||||
prop: 'state',
|
||||
label: this.$t('common.Status'),
|
||||
width: '200px',
|
||||
formatter: (row) => {
|
||||
if (row.error) {
|
||||
return <span class='color-error'>{ errorProp }: { row.error }</span>
|
||||
} else if (row.state) {
|
||||
const colorCls = stateClsMap[row.state]
|
||||
const state = stateMap[row.state]
|
||||
return <span class={ colorCls }>{ state }</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
totalData: this.result
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
summary() {
|
||||
const labels = {
|
||||
total: this.$tc('common.Total'),
|
||||
created: this.$tc('common.Created'),
|
||||
updated: this.$tc('common.Updated'),
|
||||
skipped: this.$tc('common.Skipped'),
|
||||
error: this.$tc('common.Error')
|
||||
}
|
||||
const grouped = _.groupBy(this.result, 'state')
|
||||
const groupedLength = _.mapValues(grouped, 'length')
|
||||
groupedLength['total'] = this.result.length
|
||||
return _.map(groupedLength, (value, key) => {
|
||||
return {
|
||||
label: labels[key],
|
||||
value: value,
|
||||
key: key
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeDialog() {
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.color-error {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.color-primary {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.color-success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.color-default {
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -63,6 +63,9 @@ export default {
|
||||
dateEnd: extraQuery.date_to
|
||||
}, this.headerActions.datePicker)
|
||||
}
|
||||
if (this.$route.query.order) {
|
||||
extraQuery['order'] = this.$route.query.order
|
||||
}
|
||||
return {
|
||||
selectedRows: [],
|
||||
init: false,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"": "",
|
||||
"accounts": {
|
||||
"AddAccountResult": "账号批量添加结果",
|
||||
"BulkCreateStrategy": "创建时对于不符合要求的账号,如:密钥类型不合规,唯一键约束,可选择以上策略。",
|
||||
"AccountTemplate": "账号模版",
|
||||
"HistoryDate": "日期",
|
||||
@@ -442,6 +443,10 @@
|
||||
"ReLoginErr": "登录时长已超过 5 分钟,请重新登录"
|
||||
},
|
||||
"common": {
|
||||
"Created": "已创建",
|
||||
"Updated": "已更新",
|
||||
"Skipped": "已跳过",
|
||||
"Error": "错误",
|
||||
"ServerError": "服务器错误",
|
||||
"CommunityEdition": "社区版",
|
||||
"EnterpriseEdition": "企业版",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import i18n from '@/i18n/i18n'
|
||||
import { message } from '@/utils/message'
|
||||
|
||||
const _ = require('lodash')
|
||||
const moment = require('moment')
|
||||
|
||||
@@ -244,10 +245,10 @@ export function getDayFuture(days, now) {
|
||||
export function getErrorResponseMsg(error) {
|
||||
let msg = ''
|
||||
let data = ''
|
||||
if (error.response.status === 500) {
|
||||
if (error?.response?.status === 500) {
|
||||
data = i18n.t('common.ServerError')
|
||||
} else {
|
||||
data = error.response && error.response.data || error
|
||||
data = error?.response && error?.response.data || error
|
||||
}
|
||||
if (data && (data.error || data.msg || data.detail)) {
|
||||
msg = data.error || data.msg || data.detail
|
||||
|
||||
@@ -137,11 +137,16 @@ export default {
|
||||
handleConfirm() {
|
||||
this.iVisible = false
|
||||
// 过滤掉添加里还没有id的账号
|
||||
const hasIdAccounts = this.accounts.filter(i => i?.id).map(item => item.id)
|
||||
const templates = this.accounts.filter(i => i?.template).map(item => item.template)
|
||||
const newAddAccounts = this.accountsSelected.filter(i => {
|
||||
if (!hasIdAccounts.includes(i.id)) {
|
||||
i.template = true
|
||||
return i
|
||||
return templates.indexOf(i.id) === -1
|
||||
}).map(item => {
|
||||
return {
|
||||
template: item.id,
|
||||
name: item.name,
|
||||
username: item.username,
|
||||
secret_type: item.secret_type.value,
|
||||
privileged: item.privileged
|
||||
}
|
||||
})
|
||||
this.accounts.push(...newAddAccounts)
|
||||
@@ -168,7 +173,7 @@ export default {
|
||||
})
|
||||
if (status) {
|
||||
this.$refs.dataTable.$refs.dataTable.toggleRowSelection(row, false)
|
||||
this.$message.error(this.$t('accounts.SameTypeAccountTip'))
|
||||
this.$message.error(this.$tc('accounts.SameTypeAccountTip'))
|
||||
}
|
||||
return status
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<el-table-column :label="$tc('common.Actions')" align="right" class-name="buttons" fixed="right" width="135">
|
||||
<template v-slot="scope">
|
||||
<el-button icon="el-icon-minus" size="mini" type="danger" @click="removeAccount(scope.row)" />
|
||||
<el-button icon="el-icon-edit" size="mini" type="primary" @click="onEditClick(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>
|
||||
@@ -28,7 +28,7 @@
|
||||
<el-button size="mini" type="primary" @click="onAddClick">
|
||||
{{ $t('common.Add') }}
|
||||
</el-button>
|
||||
<el-button size="mini" type="success" :disabled="!$hasPerm('accounts.view_accounttemplate')" @click="onAddFromTemplateClick">
|
||||
<el-button :disabled="!$hasPerm('accounts.view_accounttemplate')" size="mini" type="success" @click="onAddFromTemplateClick">
|
||||
{{ $t('common.TemplateAdd') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
/>
|
||||
<AccountTemplateDialog
|
||||
v-if="templateDialogVisible"
|
||||
:show-create="false"
|
||||
:asset="object"
|
||||
:show-create="false"
|
||||
:visible.sync="templateDialogVisible"
|
||||
@onConfirm="onConfirm"
|
||||
/>
|
||||
@@ -73,7 +73,7 @@ export default {
|
||||
methods: {
|
||||
onConfirm(data) {
|
||||
data = data?.map(i => {
|
||||
i.template = true
|
||||
i.template = i.id
|
||||
i.asset = this.object.id
|
||||
return i
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user