perf: 创建系统用户时需选择协议 (#631)

* perf: 创建系统用户时需选择协议

* feat: 添加action groups

* perf: 优化创建的按钮

Co-authored-by: Orange <orangemtony@gmail.com>
Co-authored-by: ibuler <ibuler@qq.com>
This commit is contained in:
fit2bot
2021-03-05 15:01:04 +08:00
committed by GitHub
parent e4ec8565f0
commit e453a9a740
19 changed files with 1149 additions and 482 deletions

View File

@@ -1,40 +1,15 @@
<template>
<div :class="grouped ? 'el-button-group' : ''">
<el-button v-for="item in iActions" :key="item.name" :size="size" v-bind="item" @click="handleClick(item.name)">
<el-tooltip v-if="item.tip" effect="dark" :content="item.tip" placement="top">
<i v-if="item.fa" :class="'fa ' + item.fa" />{{ item.title }}
</el-tooltip>
<span v-else>
<i v-if="item.fa" :class="'fa ' + item.fa" />{{ item.title }}
</span>
</el-button>
<el-dropdown v-if="iMoreActions.length > 0" trigger="click" :placement="moreActionsPlacement" @command="handleClick">
<el-button :size="size" :type="moreActionsType" class="btn-more-actions">
{{ iMoreActionsTitle }}<i class="el-icon-arrow-down el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item in iMoreActions" :key="item.name" :command="item.name" v-bind="item" @click="handleClick(item.name)">{{ item.title }} </el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<DataActions :actions="iActions" v-bind="$attrs" />
</template>
<script>
import DataActions from '@/components/DataActions'
export default {
name: 'ActionsGroup',
components: {
DataActions
},
props: {
grouped: {
type: Boolean,
default: false
},
size: {
type: String,
default: 'small'
},
type: {
type: String,
default: ''
},
actions: {
type: Array,
default: () => []
@@ -61,79 +36,22 @@ export default {
},
computed: {
iActions() {
return this.cleanActions(this.actions)
},
iMoreActions() {
return this.cleanActions(this.moreActions)
},
totalActions() {
return [...this.actions, ...this.moreActions]
},
totalNamedActions() {
const actions = {}
for (const action of this.totalActions) {
if (!action || !action.hasOwnProperty('name')) {
continue
}
actions[action.name] = action
const actions = [...this.actions]
if (this.iMoreAction && this.iMoreAction.dropdown.length > 0) {
actions.push(this.iMoreAction)
}
return actions
},
iMoreAction() {
return {
name: 'moreActions',
title: this.iMoreActionsTitle,
dropdown: this.moreActions || []
}
},
iMoreActionsTitle() {
return this.moreActionsTitle || this.$t('common.MoreActions')
}
},
methods: {
handleClick(item) {
const action = this.totalNamedActions[item]
if (action && action.callback) {
action.callback(action)
} else {
this.$log.debug('No callback found')
}
this.$emit('actionClick', item)
},
checkItem(item, attr, defaults) {
if (!item) {
return true
}
let ok = item[attr]
if (ok && typeof ok === 'function') {
ok = ok(item)
} else if (ok == null) {
ok = defaults === undefined ? true : defaults
}
return ok
},
cleanActions(actions) {
const cleanedActions = []
const cloneActions = _.cloneDeep(actions)
for (const v of cloneActions) {
if (!v) {
continue
}
const action = Object.assign({}, v)
// 是否拥有这个action
const has = this.checkItem(action, 'has')
delete action['has']
if (!has) {
continue
}
// 是否有分割线
const divided = this.checkItem(action, 'divided', false)
delete action['divided']
action.divided = divided
// 是否是disabled
const can = this.checkItem(action, 'can')
delete action['can']
action.disabled = !can
cleanedActions.push(action)
// 删掉callback避免前台看到
delete action['callback']
}
return cleanedActions
}
}
}
</script>

View File

@@ -0,0 +1,153 @@
<template>
<div :class="grouped ? 'el-button-group' : 'el-button-ungroup'">
<template v-for="action in iActions">
<el-dropdown v-if="action.dropdown" :key="action.name" class="action-item" trigger="click" placement="bottom-start" @command="handleDropdownCallback">
<el-button :size="size" v-bind="cleanButtonAction(action)">
{{ action.title }}<i class="el-icon-arrow-down el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown">
<template v-for="option in action.dropdown">
<div v-if="option.group" :key="'group:'+option.name" class="dropdown-menu-title">
{{ option.group }}
</div>
<el-dropdown-item
:key="option.name"
:command="[option, action]"
v-bind="option"
>
{{ option.title }}
</el-dropdown-item>
</template>
</el-dropdown-menu>
</el-dropdown>
<el-button v-else :key="action.name" :size="size" v-bind="cleanButtonAction(action)" class="action-item" @click="handleClick(action)">
<el-tooltip v-if="action.tip" effect="dark" :content="action.tip" placement="top">
<i v-if="action.fa" :class="'fa ' + action.fa" />{{ action.title }}
</el-tooltip>
<span v-else>
<i v-if="action.fa" :class="'fa ' + action.fa" />{{ action.title }}
</span>
</el-button>
</template>
</div>
</template>
<script>
export default {
name: 'DataActions',
props: {
grouped: {
type: Boolean,
default: false
},
size: {
type: String,
default: 'small'
},
type: {
type: String,
default: ''
},
actions: {
type: Array,
default: () => []
}
},
computed: {
iActions() {
return this.cleanActions(this.actions)
}
},
methods: {
handleDropdownCallback(command) {
const [option, dropdown] = command
const defaultCallback = () => this.$log.debug('No callback found: ', option, dropdown)
let callback = option.callback
if (!callback) {
callback = dropdown.callback
}
if (!callback) {
callback = defaultCallback
}
return callback(option)
},
handleClick(action) {
if (action && action.callback) {
action.callback(action)
} else {
this.$log.debug('No callback found')
}
this.$emit('actionClick', action)
},
checkItem(item, attr, defaults) {
if (!item) {
return true
}
let ok = item[attr]
if (ok && typeof ok === 'function') {
ok = ok(item)
} else if (ok == null) {
ok = defaults === undefined ? true : defaults
}
return ok
},
cleanButtonAction(action) {
action = _.cloneDeep(action)
delete action['dropdown']
delete action['callback']
delete action['name']
delete action['can']
return action
},
cleanActions(actions) {
const cleanedActions = []
const cloneActions = _.cloneDeep(actions)
for (const v of cloneActions) {
if (!v) {
continue
}
const action = Object.assign({}, v)
// 是否拥有这个action
const has = this.checkItem(action, 'has')
delete action['has']
if (!has) {
continue
}
// 是否有分割线
action.divided = this.checkItem(action, 'divided', false)
// 是否是disabled
const can = this.checkItem(action, 'can')
action.disabled = !can
if (action.dropdown) {
// const dropdown = this.cleanActions(action.dropdown)
action.dropdown = this.cleanActions(action.dropdown)
}
cleanedActions.push(action)
}
return cleanedActions
}
}
}
</script>
<style scoped>
.dropdown-menu-title {
text-align: left;
font-size: 12px;
color: #909399;
line-height: 30px;
padding: 0 6px;
}
.el-button-ungroup .action-item {
margin-left: 4px
}
.el-button-ungroup .action-item:first-child {
margin-left: 0
}
</style>

View File

@@ -1,9 +1,14 @@
<template>
<ActionsGroup v-if="hasLeftActions" :actions="actions" :more-actions="moreActions" :more-actions-title="moreActionsTitle" v-bind="$attrs" class="header-action" />
<DataActions
v-if="hasLeftActions"
:actions="iActions"
v-bind="$attrs"
class="header-action"
/>
</template>
<script>
import ActionsGroup from '@/components/ActionsGroup'
import DataActions from '@/components/DataActions'
import { createSourceIdCache } from '@/api/common'
import { cleanActions } from './utils'
@@ -12,14 +17,14 @@ const defaultFalse = { type: Boolean, default: false }
export default {
name: 'LeftSide',
components: {
ActionsGroup
DataActions
},
props: {
hasLeftActions: defaultTrue,
hasCreate: defaultTrue,
canCreate: defaultTrue,
hasBulkDelete: defaultTrue,
hasBulkUpdate: defaultFalse,
hasLeftActions: defaultTrue,
tableUrl: {
type: String,
default: ''
@@ -54,23 +59,37 @@ export default {
type: String,
default: null
},
moreActionsButton: {
moreCreates: {
type: Object,
default: () => ({})
default: null
}
},
data() {
const defaultActions = [
{
name: 'actionCreate',
title: this.$t('common.Create'),
type: 'primary',
has: this.hasCreate && !this.moreCreates,
can: true,
callback: this.handleCreate
}
]
if (this.moreCreates) {
const defaultMoreCreate = {
name: 'actionMoreCreate',
title: this.$t('common.Create'),
type: 'primary',
has: true,
can: true,
dropdown: [],
callback: this.handleCreate
}
const createCreateAction = Object.assign(defaultMoreCreate, this.moreCreates)
defaultActions.push(createCreateAction)
}
return {
defaultActions: [
{
name: 'actionCreate',
title: this.$t('common.Create'),
type: 'primary',
has: this.hasCreate,
can: this.canCreate,
callback: this.handleCreate
}
],
defaultActions: defaultActions,
defaultMoreActions: [
{
title: this.$t('common.deleteSelected'),
@@ -93,6 +112,9 @@ export default {
}
},
computed: {
iActions() {
return [...this.actions, this.moreAction]
},
actions() {
const actions = [...this.defaultActions, ...this.extraActions]
return cleanActions(actions, true, {
@@ -100,12 +122,17 @@ export default {
reloadTable: this.reloadTable
})
},
moreActions() {
const actions = [...this.defaultMoreActions, ...this.extraMoreActions]
return cleanActions(actions, true, {
moreAction() {
let dropdown = [...this.defaultMoreActions, ...this.extraMoreActions]
dropdown = cleanActions(dropdown, true, {
selectedRows: this.selectedRows,
reloadTable: this.reloadTable
})
return {
name: 'moreActions',
title: this.moreActionsTitle || this.$t('common.MoreActions'),
dropdown: dropdown
}
},
hasSelectedRows() {
return this.selectedRows.length > 0

View File

@@ -84,7 +84,7 @@ export default {
canUpdate: true, // can set function(row, value)
hasDelete: true, // can set function(row, value)
canDelete: true,
hasClone: false,
hasClone: true,
canClone: true,
updateRoute: this.$route.name.replace('List', 'Update'),
cloneRoute: this.$route.name.replace('List', 'Create'),
@@ -163,6 +163,9 @@ export default {
return this.cleanedActions.slice(1, this.cleanedActions.length)
}
},
mounted() {
this.$log.debug('ActionsFormatter', this.colActions)
},
methods: {
cleanBoolean(item, attr) {
const ok = item[attr]

View File

@@ -1,78 +0,0 @@
<template>
<ActionsGroup :size="'mini'" :actions="cleanedActions" :more-actions="cleanMoreActions" v-bind="$attrs" />
</template>
<script>
import ActionsGroup from '@/components/ActionsGroup/index'
import BaseFormatter from './base'
export default {
name: 'CustomActionsFormatterVue',
components: {
ActionsGroup
},
extends: BaseFormatter,
computed: {
cleanedActions() {
if (this.col.actions.actions instanceof Array) {
const copy = _.cloneDeep(this.col.actions.actions)
let actions = [...copy]
actions = actions.map((v) => {
v.has = this.checkBool(v, 'has')
v.can = this.checkBool(v, 'can')
v.callback = this.cleanCallback(v)
return v
})
return actions
}
return []
},
cleanMoreActions() {
if (this.col.actions.extraActions instanceof Array) {
const copy = _.cloneDeep(this.col.actions.extraActions)
let actions = [...copy]
actions = actions.map((v) => {
v.has = this.checkBool(v, 'has')
v.can = this.checkBool(v, 'can')
v.callback = this.cleanCallback(v)
return v
})
return actions
}
return []
}
},
mounted() {
// console.log(this.col)
},
methods: {
checkBool(item, attr, defaults) {
if (!item) {
return false
}
let ok = item[attr]
if (ok && typeof ok === 'function') {
ok = ok(this.row, this.cellValue)
} else if (ok == null) {
ok = defaults === undefined ? true : defaults
}
return ok
},
cleanCallback(item) {
const callback = item.callback
const attrs = {
reload: this.reload,
row: this.row,
col: this.col,
cellValue: this.cellValue,
tableData: this.tableData
}
return () => { return callback.bind(this)(attrs) }
}
}
}
</script>
<style scoped>
</style>

View File

@@ -2,7 +2,6 @@ import DetailFormatter from './DetailFormatter'
import DisplayFormatter from './DisplayFormatter'
import BooleanFormatter from './ChoicesFormatter'
import ActionsFormatter from './ActionsFormatter'
import CustomActionsFormatter from './CustomActionsFormatter'
import DeleteActionFormatter from './DeleteActionFormatter'
import DateFormatter from './DateFormatter'
import SystemUserFormatter from './GrantedSystemUsersShowFormatter'
@@ -15,7 +14,6 @@ export default {
DisplayFormatter,
BooleanFormatter,
ActionsFormatter,
CustomActionsFormatter,
DeleteActionFormatter,
DateFormatter,
SystemUserFormatter,
@@ -29,7 +27,6 @@ export {
DisplayFormatter,
BooleanFormatter,
ActionsFormatter,
CustomActionsFormatter,
DeleteActionFormatter,
DateFormatter,
SystemUserFormatter,

View File

@@ -2,6 +2,9 @@
"": "",
"applications": {
"": "",
"RemoteApp": "远程应用",
"Database": "数据库",
"Cloud": "云",
"applicationsType": {
"chrome": "Chrome",
"mysql_workbench": "MySQL Workbench",
@@ -170,7 +173,10 @@
"HomeHelpMessage": "默认家目录 /home/系统用户名: /home/username",
"Home": "家目录",
"LinuxUserAffiliateGroup": "用户附属组",
"ipDomain": "IP(域名)"
"ipDomain": "IP(域名)",
"HostProtocol": "主机协议",
"DatabaseProtocol": "数据库协议",
"OtherProtocol": "其它协议"
},
"audits": {

View File

@@ -2,6 +2,9 @@
"": "",
"applications": {
"": "",
"RemoteApp": "Remote app",
"Database": "Database",
"Cloud": "Cloud",
"applicationsType": {
"chrome": "Chrome",
"mysql_workbench": "MySQL Workbench",
@@ -170,7 +173,10 @@
"HomeHelpMessage": "Default home directory: /home/system username",
"Home": "Home",
"LinuxUserAffiliateGroup": "Linux user affiliate group",
"ipDomain": "IP(Domain)"
"ipDomain": "IP(Domain)",
"HostProtocol": "Host Protocol",
"DatabaseProtocol": "Database Protocol",
"Other Protocol": "Database Protocol"
},
"audits": {
"Hosts": "Host",

View File

@@ -62,38 +62,38 @@ export default {
hasImport: false,
hasBulkDelete: false,
createRoute: 'DatabaseAppCreate',
moreActionsTitle: this.$t('common.Create'),
moreActionsType: 'primary',
extraMoreActions: [
{
name: 'MySQL',
title: 'MySQL',
type: 'primary',
has: true,
callback: this.createMysql.bind(this)
},
{
name: 'PostgreSQL',
title: 'PostgreSQL',
type: 'primary',
has: this.isValidateLicense,
callback: this.createPostgreSQL.bind(this)
},
{
name: 'MariaDB',
title: 'MariaDB',
type: 'primary',
has: this.isValidateLicense,
callback: this.createMariaDB.bind(this)
},
{
name: 'Oracle',
title: 'Oracle',
type: 'primary',
has: this.isValidateLicense,
callback: this.createOracle.bind(this)
}
]
moreCreates: {
dropdown: [
{
name: 'MySQL',
title: 'MySQL',
type: 'primary',
has: true,
callback: this.createMysql.bind(this)
},
{
name: 'PostgreSQL',
title: 'PostgreSQL',
type: 'primary',
has: this.isValidateLicense,
callback: this.createPostgreSQL.bind(this)
},
{
name: 'MariaDB',
title: 'MariaDB',
type: 'primary',
has: this.isValidateLicense,
callback: this.createMariaDB.bind(this)
},
{
name: 'Oracle',
title: 'Oracle',
type: 'primary',
has: this.isValidateLicense,
callback: this.createOracle.bind(this)
}
]
}
}
}
},

View File

@@ -64,25 +64,26 @@ export default {
hasExport: false,
hasImport: false,
// createRoute: 'RemoteAppCreate',
moreActionsTitle: this.$t('common.Create'),
moreActionsType: 'primary',
extraMoreActions: this.genExtraMoreActions()
moreCreates: {
dropdown: this.getCreateAppType(),
callback: (app) => {
console.log('App: ', app)
vm.$router.push({ name: 'RemoteAppCreate', query: { type: app.name }})
}
}
}
}
},
methods: {
onCallback(type) {
this.$router.push({ name: 'RemoteAppCreate', query: { type: type }})
},
genExtraMoreActions() {
getCreateAppType() {
const extraMoreActions = []
for (const value of ALL_TYPES) {
const item = { ...REMOTE_APP_TYPE_META_MAP[value] }
item.type = 'primary'
item.can = true
item.callback = this.onCallback.bind(this, value)
item.has = true
extraMoreActions.push(item)
}
console.log('core', extraMoreActions)
return extraMoreActions
}
}

View File

@@ -0,0 +1,154 @@
<template>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import UploadKey from '@/components/UploadKey'
import { Required } from '@/components/DataForm/rules'
export default {
name: 'SystemUserCreateUpdate',
components: { GenericCreateUpdatePage },
data() {
return {
initial: {
login_mode: 'auto',
priority: '20',
protocol: this.$route.query.protocol,
username_same_with_user: false,
auto_generate_key: false,
auto_push: false
},
fields: [
[this.$t('common.Basic'), ['name', 'login_mode', 'username', 'username_same_with_user', 'priority', 'protocol']],
[this.$t('common.Auth'), ['update_password', 'password']],
[this.$t('common.Other'), ['comment']]
],
fieldsMeta: {
login_mode: {
helpText: this.$t('assets.LoginModeHelpMessage'),
hidden: (form) => {
if (form.protocol === 'k8s') {
return true
}
},
on: {
input: ([value], updateForm) => {
if (value === 'manual') {
updateForm({ auto_push: false })
updateForm({ auto_generate_key: false })
}
}
}
},
username: {
el: {
disabled: false
},
rules: [Required],
hidden: (form) => {
if (form.login_mode === 'auto') {
this.fieldsMeta.username.rules = [Required]
} else {
this.fieldsMeta.username.rules[0].required = false
}
if (!form.username_same_with_user) {
this.fieldsMeta.username.rules = [Required]
} else {
this.fieldsMeta.username.rules[0].required = false
}
if (['mysql', 'postgresql', 'mariadb', 'oracle'].indexOf(form.protocol) !== -1) {
this.fieldsMeta.username.rules = [Required]
this.fieldsMeta.username.rules[0].required = true
}
}
},
private_key: {
component: UploadKey,
hidden: (form) => {
if (form.login_mode !== 'auto') {
return true
}
if (form.protocol === 'k8s') {
return true
}
return form.auto_generate_key === true
}
},
username_same_with_user: {
type: 'switch',
helpText: this.$t('assets.UsernameHelpMessage'),
hidden: (form) => {
this.fieldsMeta.username.el.disabled = form.username_same_with_user
return form.protocol === 'k8s'
},
el: {
disabled: false
}
},
protocol: {
rules: [Required],
el: {
disabled: true,
style: 'width:100%'
},
on: {
input: ([value], updateForm) => {
if (['ssh', 'rdp'].indexOf(value) === -1) {
updateForm({ auto_push: false })
updateForm({ auto_generate_key: false })
}
}
}
},
priority: {
rules: [Required],
helpText: this.$t('assets.PriorityHelpMessage')
},
update_password: {
label: this.$t('users.UpdatePassword'),
type: 'checkbox',
hidden: (formValue) => {
if (formValue.update_password || formValue.protocol === 'k8s') {
return true
}
if (formValue.login_mode === 'manual') {
return true
}
return !this.$route.params.id
}
},
password: {
helpText: this.$t('assets.PasswordHelpMessage'),
hidden: form => {
if (form.login_mode !== 'auto' || form.protocol === 'k8s' || form.auto_generate_key) {
return true
}
if (!this.$route.params.id) {
return false
}
return !form.update_password
}
}
},
url: '/api/v1/assets/system-users/',
authHiden: false
}
},
method: {
},
mounted() {
const params = this.$route.params
const method = params.id ? 'update' : 'create'
if (method === 'update') {
this.fieldsMeta.username_same_with_user.el.disabled = true
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -0,0 +1,63 @@
<template>
<GenericCreateUpdatePage
:fields="fields"
:initial="initial"
:fields-meta="fieldsMeta"
:url="url"
/>
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import { Required } from '@/components/DataForm/rules'
export default {
name: 'SystemUserCreateUpdate',
components: { GenericCreateUpdatePage },
data() {
return {
initial: {
priority: '20',
protocol: this.$route.query.protocol
},
fields: [
[this.$t('common.Basic'), ['name', 'priority', 'protocol']],
[this.$t('common.Auth'), ['token']],
[this.$t('common.Other'), ['comment']]
],
fieldsMeta: {
token: {
rules: [Required],
el: {
type: 'textarea',
autosize: { minRows: 3 }
}
},
protocol: {
rules: [Required],
el: {
disabled: true,
style: 'width:100%'
}
}
},
url: '/api/v1/assets/system-users/',
authHiden: false
}
},
method: {
},
mounted() {
const params = this.$route.params
const method = params.id ? 'update' : 'create'
if (method === 'update') {
this.fieldsMeta.token.rules[0].required = false
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -0,0 +1,145 @@
<template>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import { Required } from '@/components/DataForm/rules'
export default {
name: 'SystemUserCreateUpdate',
components: { GenericCreateUpdatePage },
data() {
return {
initial: {
login_mode: 'auto',
priority: '20',
protocol: this.$route.query.protocol,
username_same_with_user: false,
auto_generate_key: false,
auto_push: false,
sftp_root: 'tmp',
sudo: '/bin/whoami',
shell: '/bin/bash'
},
fields: [
[this.$t('common.Basic'), ['name', 'login_mode', 'username', 'username_same_with_user', 'priority', 'protocol']],
[this.$t('common.Auth'), ['update_password', 'password', 'ad_domain']],
[this.$t('common.Other'), ['comment']]
],
fieldsMeta: {
login_mode: {
helpText: this.$t('assets.LoginModeHelpMessage'),
hidden: (form) => {
if (form.protocol === 'k8s') {
return true
}
},
on: {
input: ([value], updateForm) => {
if (value === 'manual') {
updateForm({ auto_push: false })
updateForm({ auto_generate_key: false })
}
}
}
},
username: {
el: {
disabled: false
},
rules: [Required],
hidden: (form) => {
if (form.login_mode === 'auto') {
this.fieldsMeta.username.rules = [Required]
} else {
this.fieldsMeta.username.rules[0].required = false
}
if (!form.username_same_with_user) {
this.fieldsMeta.username.rules = [Required]
} else {
this.fieldsMeta.username.rules[0].required = false
}
}
},
username_same_with_user: {
type: 'switch',
helpText: this.$t('assets.UsernameHelpMessage'),
hidden: (form) => {
this.fieldsMeta.username.el.disabled = form.username_same_with_user
return form.protocol === 'k8s'
},
el: {
disabled: false
}
},
protocol: {
rules: [Required],
el: {
style: 'width:100%',
disabled: true
},
on: {
input: ([value], updateForm) => {
if (['ssh', 'rdp'].indexOf(value) === -1) {
updateForm({ auto_push: false })
updateForm({ auto_generate_key: false })
}
}
}
},
ad_domain: {
label: this.$t('assets.AdDomain'),
hidden: (form) => ['rdp'].indexOf(form.protocol) === -1,
helpText: this.$t('assets.AdDomainHelpText')
},
priority: {
rules: [Required],
helpText: this.$t('assets.PriorityHelpMessage')
},
update_password: {
label: this.$t('users.UpdatePassword'),
type: 'checkbox',
hidden: (formValue) => {
if (formValue.update_password || formValue.protocol === 'k8s') {
return true
}
if (formValue.login_mode === 'manual') {
return true
}
return !this.$route.params.id
}
},
password: {
helpText: this.$t('assets.PasswordHelpMessage'),
hidden: form => {
if (form.login_mode !== 'auto' || form.protocol === 'k8s' || form.auto_generate_key) {
return true
}
if (!this.$route.params.id) {
return false
}
return !form.update_password
}
}
},
url: '/api/v1/assets/system-users/',
authHiden: false
}
},
method: {
},
mounted() {
const params = this.$route.params
const method = params.id ? 'update' : 'create'
if (method === 'update') {
this.fieldsMeta.username_same_with_user.el.disabled = true
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -0,0 +1,250 @@
<template>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import UploadKey from '@/components/UploadKey'
import { Select2 } from '@/components'
import { Required } from '@/components/DataForm/rules'
// const asciiProtocols = ['ssh', 'telnet', 'mysql']
const graphProtocols = ['vnc', 'rdp', 'k8s']
export default {
name: 'SystemUserCreateUpdate',
components: { GenericCreateUpdatePage },
data() {
return {
initial: {
login_mode: 'auto',
priority: '20',
protocol: this.$route.query.protocol,
username_same_with_user: false,
auto_generate_key: false,
auto_push: false,
sftp_root: 'tmp',
sudo: '/bin/whoami',
shell: '/bin/bash'
},
fields: [
[this.$t('common.Basic'), ['name', 'login_mode', 'username', 'username_same_with_user', 'priority', 'protocol']],
[this.$t('assets.AutoPush'), ['auto_push', 'sudo', 'shell', 'home', 'system_groups']],
[this.$t('common.Auth'), ['auto_generate_key', 'update_password', 'password', 'private_key', 'token', 'ad_domain']],
[this.$t('common.Command filter'), ['cmd_filters']],
[this.$t('common.Other'), ['sftp_root', 'comment']]
],
fieldsMeta: {
login_mode: {
helpText: this.$t('assets.LoginModeHelpMessage'),
hidden: (form) => {
if (form.protocol === 'k8s') {
return true
}
},
on: {
input: ([value], updateForm) => {
if (value === 'manual') {
updateForm({ auto_push: false })
updateForm({ auto_generate_key: false })
}
}
}
},
username: {
el: {
disabled: false
},
on: {
input: ([value], updateForm) => {
updateForm({ home: `/home/${value}` })
}
},
rules: [Required],
hidden: (form) => {
if (form.login_mode === 'auto') {
this.fieldsMeta.username.rules = [Required]
} else {
this.fieldsMeta.username.rules[0].required = false
}
if (!form.username_same_with_user) {
this.fieldsMeta.username.rules = [Required]
} else {
this.fieldsMeta.username.rules[0].required = false
}
if (['mysql', 'postgresql', 'mariadb', 'oracle'].indexOf(form.protocol) !== -1) {
this.fieldsMeta.username.rules = [Required]
this.fieldsMeta.username.rules[0].required = true
}
}
},
private_key: {
component: UploadKey,
hidden: (form) => {
if (form.login_mode !== 'auto') {
return true
}
if (form.protocol === 'k8s') {
return true
}
return form.auto_generate_key === true
}
},
username_same_with_user: {
type: 'switch',
helpText: this.$t('assets.UsernameHelpMessage'),
hidden: (form) => {
this.fieldsMeta.username.el.disabled = form.username_same_with_user
return form.protocol === 'k8s'
},
el: {
disabled: false
}
},
auto_generate_key: {
type: 'switch',
label: this.$t('assets.AutoGenerateKey'),
hidden: form => {
this.fieldsMeta.auto_generate_key.el.disabled = ['ssh', 'rdp'].indexOf(form.protocol) === -1 || form.login_mode === 'manual'
if (JSON.stringify(this.$route.params) !== '{}') {
return true
}
if (form.protocol === 'k8s') {
return true
}
},
el: {
disabled: false
}
},
token: {
rules: [Required],
el: {
type: 'textarea',
autosize: { minRows: 3 }
},
hidden: form => {
return form.protocol !== 'k8s'
}
},
protocol: {
rules: [Required],
el: {
style: 'width:100%',
disabled: true
},
on: {
input: ([value], updateForm) => {
if (['ssh', 'rdp'].indexOf(value) === -1) {
updateForm({ auto_push: false })
updateForm({ auto_generate_key: false })
}
}
}
},
ad_domain: {
label: this.$t('assets.AdDomain'),
hidden: (form) => ['rdp'].indexOf(form.protocol) === -1,
helpText: this.$t('assets.AdDomainHelpText')
},
cmd_filters: {
component: Select2,
hidden: (form) => graphProtocols.indexOf(form.protocol) !== -1,
el: {
multiple: true,
value: [],
ajax: {
url: '/api/v1/assets/cmd-filters/'
}
}
},
priority: {
rules: [Required],
helpText: this.$t('assets.PriorityHelpMessage')
},
auto_push: {
type: 'switch',
el: {
disabled: false
},
hidden: form => {
this.fieldsMeta.auto_push.el.disabled = ['ssh', 'rdp'].indexOf(form.protocol) === -1 || form.login_mode === 'manual'
},
on: {
input: ([value], updateForm) => {
if (!value) {
updateForm({ auto_generate_key: value })
}
}
}
},
sftp_root: {
rules: [Required],
helpText: this.$t('assets.SFTPHelpMessage'),
hidden: (item) => item.protocol !== 'ssh'
},
sudo: {
rules: [Required],
helpText: this.$t('assets.SudoHelpMessage'),
hidden: (item) => item.protocol !== 'ssh' || !item.auto_push
},
update_password: {
label: this.$t('users.UpdatePassword'),
type: 'checkbox',
hidden: (formValue) => {
if (formValue.update_password || formValue.protocol === 'k8s') {
return true
}
if (formValue.login_mode === 'manual') {
return true
}
return !this.$route.params.id
}
},
password: {
helpText: this.$t('assets.PasswordHelpMessage'),
hidden: form => {
if (form.login_mode !== 'auto' || form.protocol === 'k8s' || form.auto_generate_key) {
return true
}
if (!this.$route.params.id) {
return false
}
return !form.update_password
}
},
shell: {
hidden: (item) => item.protocol !== 'ssh' || !item.auto_push,
rules: [Required]
},
home: {
label: this.$t('assets.Home'),
hidden: (item) => item.protocol !== 'ssh' || !item.auto_push || item.username_same_with_user,
helpText: this.$t('assets.HomeHelpMessage')
},
system_groups: {
label: this.$t('assets.LinuxUserAffiliateGroup'),
hidden: (item) => ['ssh', 'rdp'].indexOf(item.protocol) === -1 || !item.auto_push || item.username_same_with_user,
helpText: this.$t('assets.GroupsHelpMessage')
}
},
url: '/api/v1/assets/system-users/',
authHiden: false
}
},
method: {
},
mounted() {
const params = this.$route.params
const method = params.id ? 'update' : 'create'
if (method === 'update') {
this.fieldsMeta.username_same_with_user.el.disabled = true
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -0,0 +1,123 @@
<template>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import { Required } from '@/components/DataForm/rules'
// const asciiProtocols = ['ssh', 'telnet', 'mysql']
export default {
name: 'SystemUserCreateUpdate',
components: { GenericCreateUpdatePage },
data() {
return {
initial: {
login_mode: 'auto',
priority: '20',
protocol: this.$route.query.protocol,
username_same_with_user: false
},
fields: [
[this.$t('common.Basic'), ['name', 'login_mode', 'username', 'username_same_with_user', 'priority', 'protocol']],
[this.$t('common.Auth'), ['update_password', 'password']],
[this.$t('common.Other'), ['comment']]
],
fieldsMeta: {
login_mode: {
helpText: this.$t('assets.LoginModeHelpMessage')
},
username: {
el: {
disabled: false
},
rules: [Required],
hidden: (form) => {
if (form.login_mode === 'auto') {
this.fieldsMeta.username.rules = [Required]
} else {
this.fieldsMeta.username.rules[0].required = false
}
if (!form.username_same_with_user) {
this.fieldsMeta.username.rules = [Required]
} else {
this.fieldsMeta.username.rules[0].required = false
}
}
},
username_same_with_user: {
type: 'switch',
helpText: this.$t('assets.UsernameHelpMessage'),
hidden: (form) => {
this.fieldsMeta.username.el.disabled = form.username_same_with_user
},
el: {
disabled: false
}
},
protocol: {
rules: [Required],
el: {
disabled: true,
style: 'width:100%'
},
on: {
input: ([value], updateForm) => {
if (['ssh', 'rdp'].indexOf(value) === -1) {
updateForm({ auto_push: false })
updateForm({ auto_generate_key: false })
}
}
}
},
priority: {
rules: [Required],
helpText: this.$t('assets.PriorityHelpMessage')
},
update_password: {
label: this.$t('users.UpdatePassword'),
type: 'checkbox',
hidden: (formValue) => {
if (formValue.update_password || formValue.protocol === 'k8s') {
return true
}
if (formValue.login_mode === 'manual') {
return true
}
return !this.$route.params.id
}
},
password: {
helpText: this.$t('assets.PasswordHelpMessage'),
hidden: form => {
if (form.login_mode !== 'auto' || form.protocol === 'k8s' || form.auto_generate_key) {
return true
}
if (!this.$route.params.id) {
return false
}
return !form.update_password
}
}
},
url: '/api/v1/assets/system-users/',
authHiden: false
}
},
method: {
},
mounted() {
const params = this.$route.params
const method = params.id ? 'update' : 'create'
if (method === 'update') {
this.fieldsMeta.username_same_with_user.el.disabled = true
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -1,245 +1,47 @@
<template>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
<component :is="activePage" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import UploadKey from '@/components/UploadKey'
import { Select2 } from '@/components'
import { Required } from '@/components/DataForm/rules'
// const asciiProtocols = ['ssh', 'telnet', 'mysql']
const graphProtocols = ['vnc', 'rdp', 'k8s']
import SSH from './SystemUserCreate/ssh'
import RDP from './SystemUserCreate/rdp'
import VncAndTelnet from './SystemUserCreate/vncAndTelnet'
import DATABASE from './SystemUserCreate/database'
import K8S from './SystemUserCreate/k8s'
export default {
name: 'SystemUserCreateUpdate',
components: { GenericCreateUpdatePage },
components: { SSH, RDP, VncAndTelnet, DATABASE },
data() {
return {
initial: {
login_mode: 'auto',
priority: '20',
protocol: 'ssh',
username_same_with_user: false,
auto_generate_key: false,
auto_push: false,
sftp_root: 'tmp',
sudo: '/bin/whoami',
shell: '/bin/bash'
},
fields: [
[this.$t('common.Basic'), ['name', 'login_mode', 'username', 'username_same_with_user', 'priority', 'protocol']],
[this.$t('assets.AutoPush'), ['auto_push', 'sudo', 'shell', 'home', 'system_groups']],
[this.$t('common.Auth'), ['auto_generate_key', 'update_password', 'password', 'private_key', 'token', 'ad_domain']],
[this.$t('common.Command filter'), ['cmd_filters']],
[this.$t('common.Other'), ['sftp_root', 'comment']]
],
fieldsMeta: {
login_mode: {
helpText: this.$t('assets.LoginModeHelpMessage'),
hidden: (form) => {
if (form.protocol === 'k8s') {
return true
}
},
on: {
input: ([value], updateForm) => {
if (value === 'manual') {
updateForm({ auto_push: false })
updateForm({ auto_generate_key: false })
}
}
}
},
username: {
el: {
disabled: false
},
on: {
input: ([value], updateForm) => {
updateForm({ home: `/home/${value}` })
}
},
rules: [Required],
hidden: (form) => {
if (form.login_mode === 'auto') {
this.fieldsMeta.username.rules = [Required]
} else {
this.fieldsMeta.username.rules[0].required = false
}
if (!form.username_same_with_user) {
this.fieldsMeta.username.rules = [Required]
} else {
this.fieldsMeta.username.rules[0].required = false
}
if (['mysql', 'postgresql', 'mariadb', 'oracle'].indexOf(form.protocol) !== -1) {
this.fieldsMeta.username.rules = [Required]
this.fieldsMeta.username.rules[0].required = true
}
}
},
private_key: {
component: UploadKey,
hidden: (form) => {
if (form.login_mode !== 'auto') {
return true
}
if (form.protocol === 'k8s') {
return true
}
return form.auto_generate_key === true
}
},
username_same_with_user: {
type: 'switch',
helpText: this.$t('assets.UsernameHelpMessage'),
hidden: (form) => {
this.fieldsMeta.username.el.disabled = form.username_same_with_user
return form.protocol === 'k8s'
},
el: {
disabled: false
}
},
auto_generate_key: {
type: 'switch',
label: this.$t('assets.AutoGenerateKey'),
hidden: form => {
this.fieldsMeta.auto_generate_key.el.disabled = ['ssh', 'rdp'].indexOf(form.protocol) === -1 || form.login_mode === 'manual'
if (JSON.stringify(this.$route.params) !== '{}') {
return true
}
if (form.protocol === 'k8s') {
return true
}
},
el: {
disabled: false
}
},
token: {
rules: [Required],
el: {
type: 'textarea',
autosize: { minRows: 3 }
},
hidden: form => {
return form.protocol !== 'k8s'
}
},
protocol: {
rules: [Required],
el: {
style: 'width:100%'
},
on: {
input: ([value], updateForm) => {
if (['ssh', 'rdp'].indexOf(value) === -1) {
updateForm({ auto_push: false })
updateForm({ auto_generate_key: false })
}
}
}
},
ad_domain: {
label: this.$t('assets.AdDomain'),
hidden: (form) => ['rdp'].indexOf(form.protocol) === -1,
helpText: this.$t('assets.AdDomainHelpText')
},
cmd_filters: {
component: Select2,
hidden: (form) => graphProtocols.indexOf(form.protocol) !== -1,
el: {
multiple: true,
value: [],
ajax: {
url: '/api/v1/assets/cmd-filters/'
}
}
},
priority: {
rules: [Required],
helpText: this.$t('assets.PriorityHelpMessage')
},
auto_push: {
type: 'switch',
el: {
disabled: false
},
hidden: form => {
this.fieldsMeta.auto_push.el.disabled = ['ssh', 'rdp'].indexOf(form.protocol) === -1 || form.login_mode === 'manual'
},
on: {
input: ([value], updateForm) => {
if (!value) {
updateForm({ auto_generate_key: value })
}
}
}
},
sftp_root: {
rules: [Required],
helpText: this.$t('assets.SFTPHelpMessage'),
hidden: (item) => item.protocol !== 'ssh'
},
sudo: {
rules: [Required],
helpText: this.$t('assets.SudoHelpMessage'),
hidden: (item) => item.protocol !== 'ssh' || !item.auto_push
},
update_password: {
label: this.$t('users.UpdatePassword'),
type: 'checkbox',
hidden: (formValue) => {
if (formValue.update_password || formValue.protocol === 'k8s') {
return true
}
if (formValue.login_mode === 'manual') {
return true
}
return !this.$route.params.id
}
},
password: {
helpText: this.$t('assets.PasswordHelpMessage'),
hidden: form => {
if (form.login_mode !== 'auto' || form.protocol === 'k8s' || form.auto_generate_key) {
return true
}
if (!this.$route.params.id) {
return false
}
return !form.update_password
}
},
shell: {
hidden: (item) => item.protocol !== 'ssh' || !item.auto_push,
rules: [Required]
},
home: {
label: this.$t('assets.Home'),
hidden: (item) => item.protocol !== 'ssh' || !item.auto_push || item.username_same_with_user,
helpText: this.$t('assets.HomeHelpMessage')
},
system_groups: {
label: this.$t('assets.LinuxUserAffiliateGroup'),
hidden: (item) => ['ssh', 'rdp'].indexOf(item.protocol) === -1 || !item.auto_push || item.username_same_with_user,
helpText: this.$t('assets.GroupsHelpMessage')
}
},
url: '/api/v1/assets/system-users/',
authHiden: false
}
},
method: {
},
mounted() {
const params = this.$route.params
const method = params.id ? 'update' : 'create'
if (method === 'update') {
this.fieldsMeta.token.rules[0].required = false
this.fieldsMeta.username_same_with_user.el.disabled = true
computed: {
activePage() {
const query = this.$route.query
const protocol = query.protocol
switch (protocol) {
case 'ssh':
return SSH
case 'rdp':
return RDP
case 'vnc':
case 'telnet':
return VncAndTelnet
case 'mysql':
case 'oracle':
case 'postgresql':
case 'mariadb':
return DATABASE
case 'k8s':
return K8S
default:
return SSH
}
}
}
}

View File

@@ -4,12 +4,14 @@
<script>
import { GenericListPage } from '@/layout/components'
import { mapGetters } from 'vuex'
export default {
components: {
GenericListPage
},
data() {
const vm = this
return {
tableConfig: {
url: '/api/v1/assets/system-users/',
@@ -36,17 +38,103 @@ export default {
},
actions: {
formatterArgs: {
hasClone: true
hasClone: true,
onUpdate: ({ row }) => {
vm.$router.push({ name: 'SystemUserUpdate', params: { id: row.id }, query: { protocol: row.protocol }})
},
onClone: ({ row }) => {
vm.$router.push({ name: 'SystemUserCreate', query: { protocol: row.protocol, clone_from: row.id }})
}
}
}
}
},
headerActions: {
hasMoreActions: false,
createRoute: 'SystemUserCreate'
hasCreate: false,
createRoute: 'SystemUserCreate',
moreCreates: {
callback: (option) => {
vm.$router.push({ name: 'SystemUserCreate', query: { protocol: option.type }})
},
dropdown: [
{
title: 'SSH',
name: 'SSH',
divided: true,
type: 'primary',
group: this.$t('assets.HostProtocol'),
has: true
},
{
title: 'Telnet',
name: 'Telnet',
type: 'primary',
has: true
},
{
title: 'RDP',
name: 'RDP',
type: 'primary',
has: true
},
{
title: 'VNC',
name: 'VNC',
type: 'primary',
has: true
},
{
name: 'MySQL',
title: 'MySQL',
divided: true,
type: 'primary',
has: true,
group: this.$t('assets.DatabaseProtocol')
},
{
name: 'PostgreSQL',
title: 'PostgreSQL',
type: 'primary',
has: this.isValidateLicense
},
{
name: 'MariaDB',
title: 'MariaDB',
type: 'primary',
has: this.isValidateLicense
},
{
name: 'Oracle',
title: 'Oracle',
type: 'primary',
has: this.isValidateLicense
},
{
name: 'K8S',
divided: true,
title: 'K8S',
type: 'primary',
has: this.isValidateLicense,
group: this.$t('assets.OtherProtocol')
}
]
}
},
helpMessage: this.$t('assets.SystemUserListHelpMessage')
}
},
computed: {
...mapGetters(['publicSettings', 'currentOrg'])
},
methods: {
isValidateLicense() {
if (this.publicSettings.XPACK_ENABLED) {
return this.publicSettings.XPACK_LICENSE_IS_VALID
}
return false
}
}
}
</script>

View File

@@ -12,6 +12,7 @@ export default {
GenericListPage
},
data() {
const vm = this
return {
title: this.$t('route.ApplicationPermission'),
tableConfig: {
@@ -98,12 +99,16 @@ export default {
// createRoute: 'RemoteAppCreate',
moreActionsTitle: this.$t('common.Create'),
moreActionsType: 'primary',
extraMoreActions: ApplicationTypes
moreCreates: {
callback: (option) => {
vm.$router.push({ name: 'SystemUserCreate', query: { protocol: option.type }})
},
dropdown: ApplicationTypes
}
}
}
},
methods: {
}
}
</script>

View File

@@ -7,7 +7,7 @@ export const VMWARE_CLIENT = 'vmware_client'
export const CUSTOM = 'custom'
export const REMOTEAPP_CATEGORY = 'remote_app'
function isValidateLicense() {
function hasValidLicense() {
if (store.getters.publicSettings.XPACK_ENABLED) {
return store.getters.publicSettings.XPACK_LICENSE_IS_VALID
}
@@ -19,28 +19,30 @@ export const REMOTE_APP = [
name: CHROME,
title: i18n.t(`applications.applicationsType.${CHROME}`),
type: 'primary',
has: isValidateLicense,
group: i18n.t('applications.RemoteApp'),
divided: true,
has: hasValidLicense,
callback: function() { router.push({ name: 'ApplicationPermissionCreate', query: { type: CHROME, category: REMOTEAPP_CATEGORY }}) }
},
{
name: MYSQL_WORKBENCH,
title: i18n.t(`applications.applicationsType.${MYSQL_WORKBENCH}`),
type: 'primary',
has: isValidateLicense,
has: hasValidLicense,
callback: function() { router.push({ name: 'ApplicationPermissionCreate', query: { type: MYSQL_WORKBENCH, category: REMOTEAPP_CATEGORY }}) }
},
{
name: VMWARE_CLIENT,
title: i18n.t(`applications.applicationsType.${VMWARE_CLIENT}`),
type: 'primary',
has: isValidateLicense,
has: hasValidLicense,
callback: function() { router.push({ name: 'ApplicationPermissionCreate', query: { type: VMWARE_CLIENT, category: REMOTEAPP_CATEGORY }}) }
},
{
name: CUSTOM,
title: i18n.t(`applications.applicationsType.${CUSTOM}`),
type: 'primary',
has: isValidateLicense,
has: hasValidLicense,
callback: function() { router.push({ name: 'ApplicationPermissionCreate', query: { type: CUSTOM, category: REMOTEAPP_CATEGORY }}) }
}
]
@@ -57,28 +59,29 @@ export const DATABASE = [
title: i18n.t(`applications.applicationsType.${MYSQL}`),
type: 'primary',
has: true,
divided: isValidateLicense,
group: i18n.t('applications.Database'),
divided: true,
callback: function() { router.push({ name: 'ApplicationPermissionCreate', query: { type: MYSQL, category: DATABASE_CATEGORY }}) }
},
{
name: ORACLE,
title: i18n.t(`applications.applicationsType.${ORACLE}`),
type: 'primary',
has: isValidateLicense,
has: hasValidLicense,
callback: function() { router.push({ name: 'ApplicationPermissionCreate', query: { type: ORACLE, category: DATABASE_CATEGORY }}) }
},
{
name: POSTGRESQL,
title: i18n.t(`applications.applicationsType.${POSTGRESQL}`),
type: 'primary',
has: isValidateLicense,
has: hasValidLicense,
callback: function() { router.push({ name: 'ApplicationPermissionCreate', query: { type: POSTGRESQL, category: DATABASE_CATEGORY }}) }
},
{
name: MARIADB,
title: i18n.t(`applications.applicationsType.${MARIADB}`),
type: 'primary',
has: isValidateLicense,
has: hasValidLicense,
callback: function() { router.push({ name: 'ApplicationPermissionCreate', query: { type: MARIADB, category: DATABASE_CATEGORY }}) }
}
]
@@ -90,6 +93,7 @@ export const CLOUD = [
{
name: KUBERNETES,
title: i18n.t(`applications.applicationsType.${KUBERNETES}`),
group: i18n.t('applications.Cloud'),
divided: true,
type: 'primary',
has: true,