feat: 改密计划支持数据库改密 (#991)

* feat: 改密计划支持数据库改密

* perf: 修改应用账号

* merge: dev

* perf: 暂存修改数据库

* fix: bug

* fix: bug

* perf: 优化系统用户详情,增加应用列表

* perf: 修改clone

* fix: 修复更新系统用户bug

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: feng626 <1304903146@qq.com>
This commit is contained in:
fit2cloud-jiangweidong
2021-09-09 04:04:40 -04:00
committed by GitHub
parent 411fca98b0
commit 52e2d58567
33 changed files with 1510 additions and 232 deletions

View File

@@ -17,7 +17,7 @@
<div>
<el-form label-position="right" label-width="80px" :model="authInfo">
<el-form-item :label="this.$t('applications.appName')">
<el-input v-model="account['app_name']" readonly />
<el-input v-model="account['app_display']" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Username')">
<el-input v-model="account['username']" readonly />
@@ -62,8 +62,7 @@ export default {
},
methods: {
getAuthInfo() {
console.log(this.account)
const url = `/api/v1/applications/account-secrets/${this.account.uid}/`
const url = `/api/v1/applications/account-secrets/${this.account.id}/`
this.$axios.get(url, { disableFlashErrorMsg: true }).then(resp => {
this.authInfo = resp
this.showAuthInfo = true

View File

@@ -48,11 +48,11 @@ export default {
tableConfig: {
url: this.url,
columns: [
'app_name', 'username', 'category_display',
'app_display', 'username', 'category_display',
'type_display', 'systemuser', 'actions'
],
columnsMeta: {
app_name: {
app_display: {
showOverflowTooltip: true,
formatter: DetailFormatter,
formatterArgs: {

View File

@@ -23,5 +23,6 @@ export { default as Switcher } from './FormFields/Swicher'
export { default as SummaryCard } from './SummaryCard'
export { default as UploadField } from './FormFields/UploadField'
export { default as AccountListTable } from './AccountListTable/index'
export { default as AppAccountListTable } from './AppAccountListTable'
export { default as AssetRelationCard } from './AssetRelationCard'
export { default as MFAVerifyDialog } from './MFAVerifyDialog'

View File

@@ -30,9 +30,11 @@
"applications": {
"": "",
"updateAccountMsg": "请更新系统用户的账号信息",
"associateApplication": "关联应用",
"RemoteApp": "远程应用",
"Database": "数据库",
"Cloud": "云",
"App": "应用",
"applicationsType": {
"chrome": "Chrome",
"mysql_workbench": "MySQL Workbench",
@@ -84,6 +86,7 @@
"DBInfo": "数据库信息"
},
"assets": {
"AppList": "应用列表",
"AssociateSystemUsers": "关联系统用户",
"AssociateAssets": "关联资产",
"AssociateNodes": "关联节点",
@@ -1127,17 +1130,31 @@
"xpack": {
"Admin": "管理员",
"Asset": "资产",
"Database": "数据库",
"AssetCount": "资产数量",
"Auditor": "审计员",
"ChangeAuthPlan": {
"AddAsset": "添加资产",
"AddNode": "添加节点",
"AddSystemUser": "添加系统用户",
"Asset": "资产",
"Database": "数据库",
"DatabaseId": "数据库Id",
"AppAmount": "应用数量",
"SystemUserAmount": "系统用户数量",
"SystemUser": "系统用户",
"SystemUserId": "系统用户Id",
"AssetAmount": "资产数量",
"AssetAndNode": "资产和节点",
"ChangeAuthPlan": "改密计划",
"ChangeAuthPlanCreate": "创建改密计划",
"ChangeAuthPlanUpdate": "更新改密计划",
"AssetChangeAuthPlan": "资产改密计划",
"AppChangeAuthPlan": "应用改密计划",
"AssetChangeAuthPlanCreate": "创建资产改密计划",
"AppChangeAuthPlanCreate": "创建应用改密计划",
"AssetChangeAuthPlanUpdate": "更新资产改密计划",
"AppChangeAuthPlanUpdate": "更新应用改密计划",
"SymbolSet": "特殊符号集合",
"SymbolSetHelpText": "请输入此类型数据库支持的特殊符号集合,若生成的随机密码中有此类数据库不支持的特殊字符,改密计划将会失败",
"CyclePerform": "周期执行",
"DateJoined": "创建日期",
"DateStart": "开始日期",

View File

@@ -29,9 +29,11 @@
"applications": {
"": "",
"updateAccountMsg": "Please update system user account info",
"associateApplication": "Associate application",
"RemoteApp": "Remote app",
"Database": "Database",
"Cloud": "Cloud",
"App": "Application",
"applicationsType": {
"chrome": "Chrome",
"mysql_workbench": "MySQL Workbench",
@@ -83,6 +85,7 @@
"DBInfo": "Database Info"
},
"assets": {
"AppList": "Application list",
"AssociateSystemUsers": "Associate system users",
"AssociateAssets": "Associate assets",
"AssociateNodes": "Associate nodes",
@@ -1097,17 +1100,29 @@
"xpack": {
"Admin": "Admin",
"Asset": "Asset",
"Database": "Database",
"AssetCount": "Asset count",
"Auditor": "Auditor",
"ChangeAuthPlan": {
"AddAsset": "Add asset",
"AddNode": "Add node",
"AddSystemUser": "Add systemuser",
"Asset": "Asset",
"Database": "Database",
"DatabaseId": "Database Id",
"SystemUser": "SystemUser",
"SystemUserId": "SystemUser Id",
"AssetAmount": "Asset",
"AssetAndNode": "Asset and Node",
"ChangeAuthPlan": "Change Auth Plan",
"ChangeAuthPlanCreate": "Create change auth plan",
"ChangeAuthPlanUpdate": "Update change auth plan",
"AssetChangeAuthPlan": "Asset Change Auth Plan",
"AppChangeAuthPlan": "App Change Auth Plan",
"AssetChangeAuthPlanCreate": "Create Asset change auth plan",
"AppChangeAuthPlanCreate": "Create App change auth plan",
"AssetChangeAuthPlanUpdate": "Update Asset change auth plan",
"AppChangeAuthPlanUpdate": "Update App change auth plan",
"SymbolSet": "Special symbol set",
"SymbolSetHelpText": "Please enter the special symbol set supported by this type of database. If there are special characters in the generated random password that are not supported by this type of database, the password change plan will fail",
"CyclePerform": "Cycle perform",
"DateJoined": "Date joined",
"DateStart": "Date start",

View File

@@ -8,9 +8,21 @@
</template>
<div>
<el-tabs v-if="submenu.length > 0" slot="submenu" v-model="iActiveMenu" class="page-submenu" @tab-click="handleTabClick">
<el-tabs
v-if="submenu.length > 0"
slot="submenu"
v-model="iActiveMenu"
class="page-submenu"
@tab-click="handleTabClick"
>
<template v-for="item in submenu">
<el-tab-pane :key="item.name" :label-content="item.labelContent" :name="item.name" :disabled="item.disabled">
<el-tab-pane
v-if="checkShow(item)"
:key="item.name"
:label-content="item.labelContent"
:name="item.name"
:disabled="item.disabled"
>
<span slot="label">
<i v-if="item.icon" class="fa " :class="item.icon" />
{{ item.title }}
@@ -68,6 +80,13 @@ export default {
this.iActiveMenu = this.getPropActiveTab()
},
methods: {
checkShow(item) {
let hidden = item.hidden
if (typeof hidden === 'function') {
hidden = hidden()
}
return !hidden
},
handleTabClick(tab) {
this.$emit('tab-click', tab)
this.$emit('update:activeMenu', tab.name)
@@ -104,15 +123,16 @@ export default {
</script>
<style scoped>
.page-submenu >>> .el-tabs__header {
background-color: white;
margin-left: -25px;
padding-left: 25px;
margin-right: -25px;
padding-right: 25px;
margin-top: -30px;
}
.page-submenu >>> .el-tabs__nav-wrap {
position: static;
}
.page-submenu >>> .el-tabs__header {
background-color: white;
margin-left: -25px;
padding-left: 25px;
margin-right: -25px;
padding-right: 25px;
margin-top: -30px;
}
.page-submenu >>> .el-tabs__nav-wrap {
position: static;
}
</style>

View File

@@ -79,40 +79,83 @@ export default [
{
path: 'change-auth-plan',
component: empty,
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan/plan' },
redirect: '',
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan') },
children: [
{
path: '',
component: () => import('@/views/accounts/ChangeAuthPlan/index.vue'),
name: 'ChangeAuthPlanIndex',
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan' }
},
{
path: 'plan',
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanList.vue'),
name: 'ChangeAuthPlanList',
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan/plan' }
component: () => import('@/views/accounts/ChangeAuthPlan/AssetChangeAuthPlan/ChangeAuthPlanList.vue'),
name: 'AssetChangeAuthPlanList',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AssetChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan' },
hidden: true
},
{
path: 'plan/create',
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanCreateUpdate.vue'),
name: 'ChangeAuthPlanCreate',
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlanCreate'), activeMenu: '/accounts/change-auth-plan/plan', action: 'create' },
component: () => import('@/views/accounts/ChangeAuthPlan/AssetChangeAuthPlan/ChangeAuthPlanCreateUpdate.vue'),
name: 'AssetChangeAuthPlanCreate',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AssetChangeAuthPlanCreate'), activeMenu: '/accounts/change-auth-plan', action: 'create' },
hidden: true
},
{
path: 'plan/:id/update',
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanCreateUpdate.vue'),
name: 'ChangeAuthPlanUpdate',
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlanUpdate'), activeMenu: '/accounts/change-auth-plan/plan', action: 'update' },
component: () => import('@/views/accounts/ChangeAuthPlan/AssetChangeAuthPlan/ChangeAuthPlanCreateUpdate.vue'),
name: 'AssetChangeAuthPlanUpdate',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AssetChangeAuthPlanUpdate'), activeMenu: '/accounts/change-auth-plan', action: 'update' },
hidden: true
},
{
path: 'plan/:id',
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanDetail/index.vue'),
name: 'ChangeAuthPlanDetail',
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan/plan' },
component: () => import('@/views/accounts/ChangeAuthPlan/AssetChangeAuthPlan/ChangeAuthPlanDetail/index.vue'),
name: 'AssetChangeAuthPlanDetail',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AssetChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan' },
hidden: true
},
{
path: 'plan-execution/:id',
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanDetail/ChangeAuthPlanExecution/ChangeAuthPlanExecutionDetail/index.vue'),
component: () => import('@/views/accounts/ChangeAuthPlan/AssetChangeAuthPlan/ChangeAuthPlanDetail/ChangeAuthPlanExecution/ChangeAuthPlanExecutionDetail/index.vue'),
name: 'ChangeAuthPlanExecutionDetail',
meta: { title: i18n.t('xpack.ChangeAuthPlan.ExecutionDetail'), activeMenu: '/accounts/change-auth-plan/plan' },
meta: { title: i18n.t('xpack.ChangeAuthPlan.ExecutionDetail'), activeMenu: '/accounts/change-auth-plan' },
hidden: true
},
{
path: 'app-plan',
component: () => import('@/views/accounts/ChangeAuthPlan/AppChangeAuthPlan/AppChangeAuthPlanList.vue'),
name: 'AppChangeAuthPlanList',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AppChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan' },
hidden: true
},
{
path: 'app-plan/create',
component: () => import('@/views/accounts/ChangeAuthPlan/AppChangeAuthPlan/AppChangeAuthPlanCreateUpdate.vue'),
name: 'AppChangeAuthPlanCreate',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AssetChangeAuthPlanCreate'), activeMenu: '/accounts/change-auth-plan', action: 'create' },
hidden: true
},
{
path: 'app-plan/:id',
component: () => import('@/views/accounts/ChangeAuthPlan/AppChangeAuthPlan/ChangeAuthPlanDetail/index.vue'),
name: 'AppChangeAuthPlanDetail',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AppChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan' },
hidden: true
},
{
path: 'app-plan/:id/update',
component: () => import('@/views/accounts/ChangeAuthPlan/AppChangeAuthPlan/AppChangeAuthPlanCreateUpdate.vue'),
name: 'AppChangeAuthPlanUpdate',
meta: { title: i18n.t('xpack.ChangeAuthPlan.AppChangeAuthPlanUpdate'), activeMenu: '/accounts/change-auth-plan', action: 'update' },
hidden: true
},
{
path: 'app-plan-execution/:id',
component: () => import('@/views/accounts/ChangeAuthPlan/AppChangeAuthPlan/ChangeAuthPlanDetail/AppChangeAuthPlanExecution/ChangeAuthPlanExecutionDetail/index.vue'),
name: 'AppChangeAuthPlanExecutionDetail',
meta: { title: i18n.t('xpack.ChangeAuthPlan.ExecutionDetail'), activeMenu: '/accounts/change-auth-plan' },
hidden: true
}
]

View File

@@ -0,0 +1,106 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import getFields from '@/views/accounts/ChangeAuthPlan/fields'
export default {
name: 'AppChangeAuthPlanCreateUpdate',
components: {
GenericCreateUpdatePage
},
data() {
const fields = getFields.bind(this)()
return {
url: '/api/v1/xpack/change-auth-plan/app-plan/',
fields: [
[this.$t('common.Basic'), ['name']],
[this.$t('assets.Applications'), ['category', 'type', 'apps', 'system_users']],
[this.$t('xpack.ChangeAuthPlan.PasswordStrategy'), ['password_strategy', 'password', 'password_rules']],
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
[this.$t('common.Other'), ['comment']]
],
initial: {
type: this.$route.query.type,
category: this.$route.query.category,
password_strategy: 'custom',
is_periodic: true,
password_rules: {
length: 30
},
interval: 24
},
fieldsMeta: {
type: {
type: 'select',
disabled: true
},
category: {
hidden: () => true
},
apps: {
label: this.$t('assets.Applications'),
el: {
value: [],
ajax: {
url: `/api/v1/applications/applications/?category=${this.$route.query.category}&type=${this.$route.query.type}`,
transformOption: (item) => {
return { label: item.name + ' (' + item.type_display + ')', value: item.id }
}
}
}
},
system_users: {
el: {
value: [],
ajax: {
url: (function() {
let url = '/api/v1/assets/system-users/'
const queryType = this.$route.query.type
if (this.$route.query.category === 'remote_app') {
url += `?protocol=rdp`
} else if (queryType) {
url += `?protocol=${queryType}`
}
return url
}.bind(this)()),
transformOption: (item) => {
if (this.$route.query.type === 'k8s') {
return { label: item.name, value: item.id }
}
const username = item.username || '*'
return { label: item.name + '(' + username + ')', value: item.id }
}
}
}
},
password: fields.password,
password_rules: fields.database_password_rules,
is_periodic: fields.is_periodic,
password_strategy: fields.password_strategy,
crontab: fields.crontab,
interval: fields.interval
},
createSuccessNextRoute: { name: 'ChangeAuthPlanIndex' },
updateSuccessNextRoute: { name: 'ChangeAuthPlanIndex' },
cleanFormValue(data) {
if (data['password_strategy'] === 'custom') {
delete data['password_rules']
} else {
delete data['password']
}
if (data['interval'] === '') {
delete data['interval']
}
return data
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,129 @@
<template>
<GenericListTable :table-config="tableConfig" :header-actions="headerActions" />
</template>
<script>
import { GenericListTable } from '@/layout/components'
import { DetailFormatter } from '@/components/TableFormatters'
import { openTaskPage } from '@/utils/jms'
import { DATABASE } from '@/views/perms/const'
export default {
name: 'AppChangeAuthPlanList',
components: {
GenericListTable
},
data() {
const vm = this
return {
tableConfig: {
url: '/api/v1/xpack/change-auth-plan/app-plan/',
columns: [
'name', 'password_strategy_display',
'periodic_display', 'run_times', 'comment', 'org_name', 'actions'
],
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'password_strategy_display', 'periodic_display', 'run_times', 'actions']
},
columnsMeta: {
name: {
formatter: DetailFormatter,
formatterArgs: {
route: 'AppChangeAuthPlanDetail'
}
},
systemuser_display: {
label: vm.$t('xpack.ChangeAuthPlan.SystemUser'),
width: '300px',
showOverflowTooltip: true
},
password_strategy_display: {
label: vm.$t('xpack.ChangeAuthPlan.PasswordStrategy'),
width: '220px',
showOverflowTooltip: true
},
periodic_display: {
label: vm.$t('xpack.ChangeAuthPlan.Timer'),
showOverflowTooltip: true,
width: '150px'
},
run_times: {
label: vm.$t('xpack.ChangeAuthPlan.ExecutionTimes'),
width: '87px',
formatter: DetailFormatter,
formatterArgs: {
route: 'AppChangeAuthPlanDetail',
routeQuery: {
activeTab: 'AppChangeAuthPlanExecutionList'
}
}
},
comment: {
width: '90px'
},
actions: {
width: '164px',
formatterArgs: {
onClone: ({ row }) => {
vm.$router.push({
name: 'AppChangeAuthPlanCreate',
query: {
clone_from: row.id,
category: row.category.toLowerCase(),
type: row.type.toLowerCase()
}
})
},
onUpdate: ({ row }) => {
vm.$router.push({
name: 'AppChangeAuthPlanUpdate',
params: { id: row.id },
query: {
category: row.category.toLowerCase(),
type: row.type.toLowerCase()
}
})
},
extraActions: [
{
title: vm.$t('xpack.Execute'),
name: 'execute',
type: 'info',
callback: function({ row }) {
this.$axios.post(
`/api/v1/xpack/change-auth-plan/app-plan-execution/`,
{ plan: row.id }
).then(res => {
openTaskPage(res['task'])
})
}.bind(this)
}
]
}
}
}
},
headerActions: {
hasRefresh: true,
hasExport: false,
hasImport: false,
hasMoreActions: false,
moreCreates: {
callback: (option) => {
vm.$router.push({ name: 'AppChangeAuthPlanCreate', query: {
category: option.category.toLowerCase(),
type: option.name.toLowerCase()
}})
},
dropdown: DATABASE
}
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,68 @@
<template>
<el-row :gutter="20">
<el-col :md="14" :sm="24">
<DetailCard :items="detailItems" />
</el-col>
</el-row>
</template>
<script>
import DetailCard from '@/components/DetailCard'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'AppChangeAuthPlanExecutionInfo',
components: {
DetailCard
},
props: {
object: {
type: Object,
default: () => ({})
}
},
data() {
return {
}
},
computed: {
detailItems() {
return [
{
key: this.$t('xpack.ChangeAuthPlan.Database'),
value: this.object.apps_display.join(', ')
},
{
key: this.$t('xpack.ChangeAuthPlan.SystemUser'),
value: this.object.system_users_display.join(', ')
},
{
key: this.$t('xpack.ChangeAuthPlan.AppAmount'),
value: this.object.apps_amount
},
{
key: this.$t('xpack.ChangeAuthPlan.SystemUserAmount'),
value: this.object.system_users_amount
},
{
key: this.$t('xpack.ChangeAuthPlan.PasswordStrategy'),
value: this.object.password_strategy_display
},
{
key: this.$t('xpack.ChangeAuthPlan.TimeDelta'),
value: this.object.timedelta.toFixed(2) + 's'
},
{
key: this.$t('xpack.ChangeAuthPlan.DateStart'),
value: toSafeLocalDateStr(this.object.date_start)
}
]
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,92 @@
<template>
<GenericListTable :table-config="tableConfig" :header-actions="headerActions" />
</template>
<script>
import GenericListTable from '@/layout/components/GenericListTable'
export default {
name: 'AppChangeAuthPlanExecutionTaskList',
components: {
GenericListTable
},
props: {
object: {
type: Object,
required: true,
default: () => ({})
}
},
data() {
return {
tableConfig: {
url: `/api/v1/xpack/change-auth-plan/app-plan-execution-subtask/?plan_execution_id=${this.object.id}`,
columns: [
'app_display', 'system_user_display', 'is_success', 'reason', 'timedelta', 'date_start', 'actions'
],
columnsMeta: {
app_display: {
label: this.$t('xpack.ChangeAuthPlan.Database'),
formatter: function(row, column, cellValue, index) {
const to = {
name: 'DatabaseAppDetail',
params: { id: row.app }
}
return <router-link to={ to } >{ row.app_display }</router-link>
}
},
system_user_display: {
label: this.$t('xpack.ChangeAuthPlan.SystemUser')
},
is_success: {
label: this.$t('xpack.ChangeAuthPlan.Success')
},
timedelta: {
label: this.$t('xpack.ChangeAuthPlan.TimeDelta'),
width: '90px',
formatter: function(row) {
return row.timedelta.toFixed(2) + 's'
}
},
actions: {
formatterArgs: {
hasDelete: false,
hasUpdate: false,
hasClone: false,
extraActions: [
{
name: 'retry',
type: 'info',
title: this.$t('xpack.ChangeAuthPlan.Retry'),
callback: function({ row, tableData }) {
this.$axios.put(
`/api/v1/xpack/change-auth-plan/app-plan-execution-subtask/${row.id}/`,
).then(res => {
window.open(`/#/ops/celery/task/${res.task}/log/`, '_blank', 'toolbar=yes, width=900, height=600')
})
}.bind(this)
}
]
}
}
}
},
headerActions: {
hasSearch: true,
hasRefresh: true,
hasLeftActions: true,
hasRightActions: true,
hasExport: false,
hasImport: false,
hasCreate: false,
hasBulkDelete: false,
hasBulkUpdate: false
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,55 @@
<template>
<GenericDetailPage :object.sync="execution" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
<keep-alive>
<component :is="config.activeMenu" :object="execution" />
</keep-alive>
</GenericDetailPage>
</template>
<script>
import { GenericDetailPage } from '@/layout/components'
import AppChangeAuthPlanExecutionInfo from './ChangeAuthPlanExecutionInfo'
import AppChangeAuthPlanExecutionTaskList from './ChangeAuthPlanExecutionTaskList'
export default {
name: 'AppChangeAuthPlanExecutionDetail',
components: {
GenericDetailPage,
AppChangeAuthPlanExecutionInfo,
AppChangeAuthPlanExecutionTaskList
},
data() {
return {
execution: { id: '' },
config: {
activeMenu: 'AppChangeAuthPlanExecutionInfo',
actions: {
hasUpdate: false,
hasDelete: false
},
submenu: [
{
title: this.$t('common.BasicInfo'),
name: 'AppChangeAuthPlanExecutionInfo'
},
{
title: this.$t('xpack.ChangeAuthPlan.TaskList'),
name: 'AppChangeAuthPlanExecutionTaskList'
}
],
getTitle: this.getExecutionTitle
}
}
},
methods: {
getExecutionTitle() {
return `${this.$route.meta.title}: ${this.execution.id}`
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,122 @@
<template>
<GenericListTable :table-config="tableConfig" :header-actions="headerActions" />
</template>
<script>
import GenericListTable from '@/layout/components/GenericListTable'
export default {
name: 'AppChangeAuthPlanExecutionList',
components: {
GenericListTable
},
props: {
object: {
type: Object,
required: true,
default: () => ({})
}
},
data() {
return {
tableConfig: {
url: `/api/v1/xpack/change-auth-plan/app-plan-execution/?plan_id=${this.object.id}`,
columns: [
'username', 'result_summary', 'password_strategy_display', 'timedelta', 'trigger_display',
'apps_amount', 'system_users_amount', 'date_start', 'actions'
],
columnsShow: {
min: ['actions'],
default: [
'username', 'password_strategy_display',
'timedelta', 'trigger_display', 'date_start', 'actions', 'result_summary'
]
},
columnsMeta: {
username: {
label: this.$t('xpack.ChangeAuthPlan.Username')
},
apps_amount: {
label: this.$t('xpack.ChangeAuthPlan.AppAmount')
},
system_users_amount: {
label: this.$t('xpack.ChangeAuthPlan.SystemUserAmount')
},
systemusers: {
label: this.$t('xpack.ChangeAuthPlan.SystemUser')
},
result_summary: {
label: this.$t('xpack.ChangeAuthPlan.Result'),
width: '80px',
showOverflowTooltip: true,
formatter: function(row) {
const summary = <div>
<span class='text-primary'>{row.result_summary.succeed}</span>/
<span class='text-danger'>{row.result_summary.failed}</span>/
<span>{row.result_summary.total}</span>
</div>
return summary
}
},
password_strategy_display: {
label: this.$t('xpack.ChangeAuthPlan.PasswordStrategy'),
width: '220px',
showOverflowTooltip: true
},
timedelta: {
label: this.$t('xpack.ChangeAuthPlan.TimeDelta'),
width: '90px',
formatter: function(row) {
return row.timedelta.toFixed(2) + 's'
}
},
date_start: {
showOverflowTooltip: true
},
actions: {
formatterArgs: {
hasDelete: false,
hasUpdate: false,
hasClone: false,
extraActions: [
{
name: 'log',
type: 'primary',
title: this.$t('xpack.ChangeAuthPlan.Log'),
callback: function({ row }) {
window.open(`/#/ops/celery/task/${row.id}/log/`, '_blank', 'toolbar=yes, width=900, height=600')
}
},
{
name: 'detail',
title: this.$t('xpack.ChangeAuthPlan.Detail'),
type: 'info',
callback: function({ row }) {
return this.$router.push({ name: 'AppChangeAuthPlanExecutionDetail', params: { id: row.id }})
}
}
]
}
}
}
},
headerActions: {
hasSearch: true,
hasRefresh: true,
hasRightActions: true,
hasLeftActions: true,
hasMoreActions: false,
hasExport: false,
hasImport: false,
hasCreate: false,
hasBulkDelete: false,
hasBulkUpdate: false
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,96 @@
<template>
<el-row :gutter="20">
<el-col :md="14" :sm="24">
<DetailCard :items="detailItems" />
</el-col>
<el-col :md="10" :sm="24">
<QuickActions :actions="quickActions" type="primary" />
</el-col>
</el-row>
</template>
<script>
import { DetailCard, QuickActions } from '@/components'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'AppChangeAuthPlanInfo',
components: {
DetailCard,
QuickActions
},
props: {
object: {
type: Object,
required: true,
default: () => ({})
}
},
data() {
return {
quickActions: [
{
title: this.$t('xpack.ChangeAuthPlan.ManualExecutePlan'),
attrs: {
type: 'primary',
label: this.$t('xpack.ChangeAuthPlan.Execute')
},
callbacks: {
click: function() {
this.$axios.post(
`/api/v1/xpack/change-auth-plan/app-plan-execution/`,
{ plan: this.object.id }
).then(res => {
window.open(`/#/ops/celery/task/${res.task}/log/`, '_blank', 'toolbar=yes, width=900, height=600')
})
}.bind(this)
}
}
]
}
},
computed: {
detailItems() {
return [
{
key: this.$t('xpack.ChangeAuthPlan.Name'),
value: this.object.name
},
{
key: this.$t('xpack.ChangeAuthPlan.SystemUser'),
value: this.object.systemuser_display
},
{
key: this.$t('xpack.ChangeAuthPlan.PasswordStrategy'),
value: this.object.password_strategy_display
},
{
key: this.$t('xpack.ChangeAuthPlan.RegularlyPerform'),
value: this.object.crontab
},
{
key: this.$t('xpack.ChangeAuthPlan.CyclePerform'),
value: this.object.interval
},
{
key: this.$t('xpack.ChangeAuthPlan.DateJoined'),
value: toSafeLocalDateStr(this.object.date_created)
},
{
key: this.$t('xpack.ChangeAuthPlan.DateUpdated'),
value: toSafeLocalDateStr(this.object.date_updated)
},
{
key: this.$t('common.Comment'),
value: this.object.comment
}
]
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,105 @@
<template>
<el-row :gutter="20">
<el-col :md="14" :sm="24">
<GenericListTable ref="listTable" :table-config="tableConfig" :header-actions="headerActions" />
</el-col>
<el-col :md="10" :sm="24">
<RelationCard type="info" style="margin-top: 15px" v-bind="systemuserRelationConfig" />
</el-col>
</el-row>
</template>
<script>
import GenericListTable from '@/layout/components/GenericListTable'
import RelationCard from '@/components/RelationCard/index'
import { DeleteActionFormatter } from '@/components/TableFormatters'
export default {
name: 'ChangeAuthPlanDatabase',
components: {
GenericListTable, RelationCard
},
props: {
object: {
type: Object,
required: true,
default: () => ({})
}
},
data() {
return {
tableConfig: {
url: `/api/v1/xpack/change-auth-plan/app-plan/${this.object.id}/systemusers/`,
columns: [
'name', 'username', 'delete_action'
],
columnsMeta: {
delete_action: {
prop: 'id',
label: this.$t('common.Actions'),
align: 'center',
width: 150,
objects: this.object.system_users,
formatter: DeleteActionFormatter,
onDelete: function(col, row, cellValue, reload) {
this.$axios.patch(
`/api/v1/xpack/change-auth-plan/app-plan/${this.object.id}/systemusers/?action=remove`,
{ system_users: [row.id] }
).then(res => {
this.$message.success(this.$t('common.deleteSuccessMsg'))
reload()
}).catch(error => {
this.$message.error(this.$t('common.deleteErrorMsg') + ' ' + error)
})
}.bind(this)
}
},
tableAttrs: {
border: false
}
},
headerActions: {
hasSearch: true,
hasRefresh: true,
hasLeftActions: true,
hasRightActions: true,
hasExport: false,
hasImport: false,
hasCreate: false,
hasMoreActions: false
},
systemuserRelationConfig: {
icon: 'fa-edit',
title: this.$t('xpack.ChangeAuthPlan.AddSystemUser'),
objectsAjax: {
url: `/api/v1/assets/system-users/?protocol=${this.object.type}`,
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
},
// hasObjectsId: this.object.systemuser,
showHasObjects: false,
performAdd: (items, that) => {
const relationUrl = `/api/v1/xpack/change-auth-plan/app-plan/${this.object.id}/systemusers/?action=add`
const systemusers = items.map(v => v.value)
const iHasObjects = that.iHasObjects.map(v => v.value)
const data = {
system_users: Array.from(new Set([...iHasObjects, ...systemusers]))
}
return this.$axios.patch(relationUrl, data)
},
onAddSuccess: (objects, that) => {
that.iHasObjects = [...that.iHasObjects, ...objects]
that.$refs.select2.clearSelected()
this.$message.success(this.$t('common.updateSuccessMsg'))
this.$refs.listTable.$refs.ListTable.reloadTable()
}
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,50 @@
<template>
<GenericDetailPage :object.sync="plan" :active-menu.sync="config.activeMenu" v-bind="config">
<keep-alive>
<component :is="config.activeMenu" :object="plan" />
</keep-alive>
</GenericDetailPage>
</template>
<script>
import { GenericDetailPage } from '@/layout/components'
import ChangeAuthPlanDatabase from './ChangeAuthPlanApp/index'
import AppChangeAuthPlanInfo from './AppChangeAuthPlanInfo'
import AppChangeAuthPlanExecutionList from './AppChangeAuthPlanExecution/ChangeAuthPlanExecutionList'
export default {
name: 'AppChangeAuthPlanDetail',
components: {
GenericDetailPage,
ChangeAuthPlanDatabase,
AppChangeAuthPlanInfo,
AppChangeAuthPlanExecutionList
},
data() {
return {
plan: { name: '', comment: '' },
config: {
activeMenu: 'AppChangeAuthPlanInfo',
submenu: [
{
title: this.$t('common.BasicInfo'),
name: 'AppChangeAuthPlanInfo'
},
{
title: this.$t('xpack.ChangeAuthPlan.SystemUser'),
name: 'ChangeAuthPlanDatabase'
},
{
title: this.$t('xpack.ChangeAuthPlan.ExecutionList'),
name: 'AppChangeAuthPlanExecutionList'
}
]
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,72 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import getFields from '@/views/accounts/ChangeAuthPlan/fields'
export default {
name: 'AssetChangeAuthPlanCreateUpdate',
components: {
GenericCreateUpdatePage
},
data() {
const fields = getFields.bind(this)()
return {
url: '/api/v1/xpack/change-auth-plan/plan/',
fields: [
[this.$t('common.Basic'), ['name']],
[this.$t('xpack.Asset'), ['username', 'assets', 'nodes']],
[this.$t('xpack.ChangeAuthPlan.PasswordStrategy'), ['is_password', 'password_strategy', 'password', 'password_rules']],
[this.$t('xpack.ChangeAuthPlan.SecretKeyStrategy'), ['is_ssh_key', 'ssh_key_strategy', 'private_key']],
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
[this.$t('common.Other'), ['comment']]
],
initial: {
password_strategy: 'custom',
ssh_key_strategy: 'add',
is_periodic: true,
is_password: true,
is_ssh_key: false,
password_rules: {
length: 30
},
interval: 24
},
fieldsMeta: {
username: fields.username,
assets: fields.assets,
password: fields.password,
password_rules: fields.asset_password_rules,
private_key: fields.private_key,
nodes: fields.nodes,
is_periodic: fields.is_periodic,
is_password: fields.is_password,
is_ssh_key: fields.is_ssh_key,
password_strategy: fields.password_strategy,
ssh_key_strategy: fields.ssh_key_strategy,
crontab: fields.crontab,
interval: fields.interval
},
createSuccessNextRoute: { name: 'ChangeAuthPlanIndex' },
updateSuccessNextRoute: { name: 'ChangeAuthPlanIndex' },
cleanFormValue(data) {
if (data['password_strategy'] === 'custom') {
delete data['password_rules']
} else {
delete data['password']
}
if (data['interval'] === '') {
delete data['interval']
}
return data
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -103,7 +103,7 @@ export default {
disabled: this.$store.getters.currentOrgIsRoot,
hasObjectsId: this.object.nodes,
performAdd: (items, that) => {
const relationUrl = `/api/v1/xpack/change-auth-plan/plan/${this.object.id}/`
const relationUrl = `/api/v1/xpack/change-auth-plan/plan/${this.object.id}/nodes/?action=add`
const nodes = items.map(v => v.value)
const iHasObjects = that.iHasObjects.map(v => v.value)
const data = {
@@ -118,13 +118,10 @@ export default {
this.$refs.listTable.$refs.ListTable.reloadTable()
},
performDelete: (item) => {
const nodes = this.object.nodes
const deleteNode = item.value
nodes.splice(nodes.indexOf(deleteNode), 1)
const data = {
nodes: nodes
nodes: [item.value]
}
const relationUrl = `/api/v1/xpack/change-auth-plan/plan/${this.object.id}/`
const relationUrl = `/api/v1/xpack/change-auth-plan/plan/${this.object.id}/nodes/?action=remove`
return this.$axios.patch(relationUrl, data)
},
onDeleteSuccess: (obj, that) => {

View File

@@ -1,16 +1,16 @@
<template>
<GenericListPage :table-config="tableConfig" :header-actions="headerActions" />
<GenericListTable :table-config="tableConfig" :header-actions="headerActions" />
</template>
<script>
import { GenericListPage } from '@/layout/components'
import { GenericListTable } from '@/layout/components'
import { DetailFormatter } from '@/components/TableFormatters'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'ChangeAuthPlanList',
name: 'AssetChangeAuthPlanList',
components: {
GenericListPage
GenericListTable
},
data() {
const vm = this
@@ -26,6 +26,12 @@ export default {
default: ['name', 'username', 'password_strategy_display', 'periodic_display', 'run_times', 'actions']
},
columnsMeta: {
name: {
formatter: DetailFormatter,
formatterArgs: {
route: 'AssetChangeAuthPlanDetail'
}
},
username: {
showOverflowTooltip: true
},
@@ -52,9 +58,9 @@ export default {
width: '87px',
formatter: DetailFormatter,
formatterArgs: {
route: 'ChangeAuthPlanDetail',
route: 'AssetChangeAuthPlanDetail',
routeQuery: {
activeTab: 'ChangeAuthPlanExecutionList'
activeTab: 'AssetChangeAuthPlanExecutionList'
}
}
},
@@ -64,6 +70,12 @@ export default {
actions: {
width: '164px',
formatterArgs: {
onClone: ({ row }) => {
vm.$router.push({ name: 'AssetChangeAuthPlanCreate', query: { clone_from: row.id }})
},
onUpdate: ({ row }) => {
vm.$router.push({ name: 'AssetChangeAuthPlanUpdate', params: { id: row.id }})
},
extraActions: [
{
title: vm.$t('xpack.Execute'),
@@ -87,7 +99,12 @@ export default {
hasRefresh: true,
hasExport: false,
hasImport: false,
hasMoreActions: false
hasMoreActions: false,
createRoute: () => {
return {
name: 'AssetChangeAuthPlanCreate'
}
}
}
}
}

View File

@@ -1,164 +0,0 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import { AssetSelect } from '@/components'
import { Required } from '@/components/DataForm/rules'
export default {
name: 'ChangeAuthPlanCreateUpdate',
components: {
GenericCreateUpdatePage
},
data() {
var validatorInterval = (rule, value, callback) => {
if (parseInt(value) < 1) {
return callback(new Error(this.$t('xpack.ChangeAuthPlan.validatorMessage.EnsureThisValueIsGreaterThanOrEqualTo1')))
}
callback()
}
return {
url: '/api/v1/xpack/change-auth-plan/plan/',
fields: [
[this.$t('common.Basic'), ['name']],
[this.$t('xpack.Asset'), ['username', 'assets', 'nodes']],
[this.$t('xpack.ChangeAuthPlan.PasswordStrategy'), ['is_password', 'password_strategy', 'password', 'password_rules']],
[this.$t('xpack.ChangeAuthPlan.SecretKeyStrategy'), ['is_ssh_key', 'ssh_key_strategy', 'private_key']],
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
[this.$t('common.Other'), ['comment']]
],
initial: {
password_strategy: 'custom',
ssh_key_strategy: 'add',
is_periodic: true,
is_password: true,
is_ssh_key: false,
password_rules: {
length: 30
},
interval: 24
},
fieldsMeta: {
username: {
helpText: this.$t('xpack.ChangeAuthPlan.HelpText.UsernameOfCreateUpdatePage')
},
assets: {
type: 'assetSelect',
component: AssetSelect,
rules: [
{ required: false }
],
label: this.$t('xpack.Asset')
},
password: {
hidden: (formValue) => {
return formValue.password_strategy !== 'custom' || formValue.is_password === false
},
rules: [
{ required: this.$route.meta.action === 'create', message: this.$t('common.fieldRequiredError'), trigger: 'blur' }
]
},
password_rules: {
type: 'group',
items: this.generatePasswordRulesItemsFields()
},
private_key: {
el: {
type: 'textarea',
placeholder: '-----BEGIN OPENSSH PRIVATE KEY-----',
autosize: { minRows: 3 }
},
hidden: (formValue) => {
return formValue.is_ssh_key === false
},
rules: [
{ required: this.$route.meta.action === 'create', message: this.$t('common.fieldRequiredError'), trigger: 'blur' }
]
},
nodes: {
label: this.$t('xpack.Node'),
el: {
value: [],
ajax: {
url: '/api/v1/assets/nodes/',
transformOption: (item) => {
return { label: item.full_value, value: item.id }
}
}
}
},
is_periodic: {
type: 'switch'
},
is_password: {
type: 'switch'
},
is_ssh_key: {
type: 'switch'
},
password_strategy: {
label: this.$t('xpack.ChangeAuthPlan.PasswordStrategy'),
hidden: (formValue) => {
return formValue.is_password === false
}
},
ssh_key_strategy: {
label: this.$t('xpack.ChangeAuthPlan.SecretKeyStrategy'),
hidden: (formValue) => {
return formValue.is_ssh_key === false
}
},
crontab: {
label: this.$t('xpack.RegularlyPerform'),
hidden: (formValue) => {
return formValue.is_periodic === false
},
helpText: this.$t('xpack.HelpText.CrontabOfCreateUpdatePage')
},
interval: {
label: this.$t('xpack.CyclePerform'),
hidden: (formValue) => {
return formValue.is_periodic === false
},
helpText: this.$t('xpack.HelpText.IntervalOfCreateUpdatePage'),
rules: [
{ validator: validatorInterval }
]
}
},
cleanFormValue(data) {
if (data['password_strategy'] === 'custom') {
delete data['password_rules']
} else {
delete data['password']
}
if (data['interval'] === '') {
delete data['interval']
}
return data
}
}
},
methods: {
generatePasswordRulesItemsFields() {
const itemsFields = []
const items = [
{ id: 'length', prop: 'length', label: this.$t('xpack.ChangeAuthPlan.PasswordLength') }
]
items.forEach((item, index, array) => {
itemsFields.push({
id: item.id, prop: item.prop, el: {}, attrs: {}, type: 'input', label: item.label, rules: [Required],
hidden: (formValue) => { return ['random_one', 'random_all'].indexOf(formValue.password_strategy) === -1 || formValue.is_password === false }
})
})
return itemsFields
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,225 @@
import i18n from '@/i18n/i18n'
import { AssetSelect } from '@/components'
import Select2 from '@/components/FormFields/Select2'
import { Required } from '@/components/DataForm/rules'
var validatorInterval = (rule, value, callback) => {
if (parseInt(value) < 1) {
return callback(new Error(i18n.t('xpack.ChangeAuthPlan.validatorMessage.EnsureThisValueIsGreaterThanOrEqualTo1')))
}
callback()
}
function getAssetPasswordRulesItems() {
return [
{
id: 'length', prop: 'length', label: i18n.t('xpack.ChangeAuthPlan.PasswordLength'),
rules: [Required], hidden: (formValue) => {
return ['random_one', 'random_all'].indexOf(formValue.password_strategy) === -1 || !formValue.is_password
}
}
]
}
function getDatabasePasswordRulesItems() {
return [
{
id: 'length', prop: 'length', label: i18n.t('xpack.ChangeAuthPlan.PasswordLength'),
rules: [Required], hidden: (formValue) => {
return ['random_one', 'random_all'].indexOf(formValue.password_strategy) === -1
}
},
{
id: 'symbol_set', prop: 'symbol_set',
label: i18n.t('xpack.ChangeAuthPlan.SymbolSet'),
helpText: i18n.t('xpack.ChangeAuthPlan.SymbolSetHelpText'),
hidden: (formValue) => {
return ['random_one', 'random_all'].indexOf(formValue.password_strategy) === -1
}
}
]
}
function generatePasswordRulesItemsFields(obType) {
const itemsFields = []
let items
if (obType === 'asset') {
items = getAssetPasswordRulesItems()
} else if (obType === 'database') {
items = getDatabasePasswordRulesItems()
}
items.forEach((item, index, array) => {
itemsFields.push({
id: item.id, prop: item.prop, el: {}, attrs: {}, type: 'input', label: item.label, rules: item.rules, helpText: item.helpText,
hidden: item.hidden })
})
return itemsFields
}
function getFields() {
const username = {
helpText: i18n.t('xpack.ChangeAuthPlan.HelpText.UsernameOfCreateUpdatePage')
}
const assets = {
type: 'assetSelect',
component: AssetSelect,
rules: [
{ required: false }
],
label: i18n.t('xpack.Asset')
}
const database = {
component: Select2,
rules: [
{ required: true }
],
label: i18n.t('xpack.Database'),
el: {
multiple: false,
value: [],
ajax: {
url: '/api/v1/applications/applications/?category=db',
transformOption: (item) => {
return { label: item.name + '(' + item.type_display + ')', value: item.id, protocol: item.type }
}
}
},
on: {
changeOptions: ([event], updateform) => {
updateform({ systemuser: [] })
this.fieldsMeta.systemuser.el.ajax.url = `/api/v1/assets/system-users/?protocol=${event[0].protocol}`
}
}
}
const password = {
hidden: (formValue) => {
return formValue.password_strategy !== 'custom' || formValue.is_password === false
},
rules: [
{ required: this.$route.meta.action === 'create', message: i18n.t('common.fieldRequiredError'), trigger: 'blur' }
]
}
const asset_password_rules = {
type: 'group',
items: generatePasswordRulesItemsFields('asset')
}
const database_password_rules = {
type: 'group',
items: generatePasswordRulesItemsFields('database')
}
const private_key = {
el: {
type: 'textarea',
placeholder: '-----BEGIN OPENSSH PRIVATE KEY-----',
autosize: { minRows: 3 }
},
hidden: (formValue) => {
return formValue.is_ssh_key === false
},
rules: [
{ required: this.$route.meta.action === 'create', message: this.$t('common.fieldRequiredError'), trigger: 'blur' }
]
}
const nodes = {
label: i18n.t('xpack.Node'),
el: {
value: [],
ajax: {
url: '/api/v1/assets/nodes/',
transformOption: (item) => {
return { label: item.full_value, value: item.id }
}
}
}
}
const is_password = {
type: 'switch'
}
const is_ssh_key = {
type: 'switch'
}
const password_strategy = {
label: i18n.t('xpack.ChangeAuthPlan.PasswordStrategy'),
hidden: (formValue) => {
return formValue.is_password === false
}
}
const ssh_key_strategy = {
label: i18n.t('xpack.ChangeAuthPlan.SecretKeyStrategy'),
hidden: (formValue) => {
return formValue.is_ssh_key === false
}
}
const systemuser = {
component: Select2,
label: i18n.t('xpack.ChangeAuthPlan.SystemUser'),
rules: [
{ required: true }
],
el: {
value: [],
ajax: {
url: '/api/v1/assets/system-users/',
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
}
}
}
const is_periodic = {
type: 'switch'
}
const crontab = {
label: i18n.t('xpack.RegularlyPerform'),
hidden: (formValue) => {
return formValue.is_periodic === false
},
helpText: i18n.t('xpack.HelpText.CrontabOfCreateUpdatePage')
}
const interval = {
label: i18n.t('xpack.CyclePerform'),
hidden: (formValue) => {
return formValue.is_periodic === false
},
helpText: i18n.t('xpack.HelpText.IntervalOfCreateUpdatePage'),
rules: [
{ validator: validatorInterval }
]
}
return {
username: username,
assets: assets,
database: database,
systemuser: systemuser,
password: password,
password_strategy: password_strategy,
ssh_key_strategy: ssh_key_strategy,
private_key: private_key,
asset_password_rules: asset_password_rules,
database_password_rules: database_password_rules,
nodes: nodes,
is_password: is_password,
is_periodic: is_periodic,
is_ssh_key: is_ssh_key,
crontab: crontab,
interval: interval
}
}
export default getFields

View File

@@ -0,0 +1,42 @@
<template>
<TabPage :active-menu.sync="config.activeMenu" :submenu="config.submenu">
<keep-alive>
<component :is="config.activeMenu" />
</keep-alive>
</TabPage>
</template>
<script>
import { TabPage } from '@/layout/components'
import AssetChangeAuthPlanList from './AssetChangeAuthPlan/ChangeAuthPlanList'
import AppChangeAuthPlanList from './AppChangeAuthPlan/AppChangeAuthPlanList'
export default {
name: 'Index',
components: {
TabPage,
AssetChangeAuthPlanList,
AppChangeAuthPlanList
},
data() {
return {
config: {
activeMenu: 'AssetChangeAuthPlanList',
submenu: [
{
title: this.$t('xpack.ChangeAuthPlan.AssetChangeAuthPlan'),
name: 'AssetChangeAuthPlanList'
},
{
title: this.$t('xpack.ChangeAuthPlan.AppChangeAuthPlan'),
name: 'AppChangeAuthPlanList'
}
]
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,35 @@
<template>
<el-row :gutter="20">
<el-col :span="20">
<AppAccountListTable ref="ListTable" :url="accountUrl" :has-import="false" :has-clone="false" />
</el-col>
<el-col :span="4" />
</el-row>
</template>
<script>
import { AppAccountListTable } from '@/components'
export default {
name: 'AccountList',
components: {
AppAccountListTable
},
props: {
object: {
type: Object,
default: () => {}
}
},
data() {
return {
accountUrl: `/api/v1/applications/accounts/?systemuser=${this.object.id}`
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -0,0 +1,126 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="16">
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
</el-col>
<el-col :span="8">
<RelationCard ref="assetSelect" type="primary" style="margin-top: 15px" v-bind="appRelationConfig" />
</el-col>
</el-row>
</div>
</template>
<script>
import ListTable from '@/components/ListTable'
import { DetailFormatter } from '@/components/TableFormatters'
import RelationCard from '@/components/RelationCard'
export default {
name: 'AssetList',
components: {
RelationCard,
ListTable
},
props: {
object: {
type: Object,
default: () => {}
}
},
data() {
const vm = this
return {
tableConfig: {
name: 'AppList',
url: `/api/v1/applications/accounts/?systemuser=${this.object.id}`,
columns: ['app_display', 'actions'],
columnsMeta: {
app_display: {
label: this.$t('applications.App'),
showOverflowTooltip: true,
formatter: DetailFormatter,
formatterArgs: {
getRoute({ row }) {
const categoryRouteMapper = {
'remote_app': 'RemoteAppDetail',
'db': 'DatabaseAppDetail',
'cloud': 'KubernetesAppDetail'
}
const name = categoryRouteMapper[row.category]
return {
name: name,
params: { id: row.app }
}
}
}
},
actions: {
formatterArgs: {
hasUpdate: false, // can set function(row, value)
hasDelete: false, // can set function(row, value)
hasClone: false,
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'Delete',
title: this.$t('common.Delete'),
type: 'danger',
can: !this.$store.getters.currentOrgIsRoot,
callback: (val) => {
this.$axios.delete(`/api/v1/applications/accounts/${val.row.id}/`).then(() => {
this.$message.success(this.$t('common.deleteSuccessMsg'))
this.$refs.ListTable.reloadTable()
})
}
}
]
}
}
}
},
headerActions: {
hasLeftActions: false,
hasBulkDelete: false,
hasImport: false,
hasCreate: false
},
appRelationConfig: {
icon: 'fa-edit',
title: this.$t('applications.associateApplication'),
disabled: this.$store.getters.currentOrgIsRoot,
objectsAjax: {
url: `/api/v1/applications/applications/?type=${vm.object.protocol}`,
transformOption: (item) => {
return { label: item.name + ' (' + item.type_display + ')', value: item.id }
}
},
showHasObjects: false,
performAdd: (items) => {
const objectId = this.object.id
const relationUrl = `/api/v1/applications/accounts/`
const appsId = items.map(v => v.value)
const data = []
for (const appId of appsId) {
data.push({ systemuser: objectId, app: appId })
}
return this.$axios.post(relationUrl, data)
},
onAddSuccess: (objects, that) => {
this.$log.debug('Select value', that.select2.value)
that.iHasObjects = [...that.iHasObjects, ...objects]
that.$refs.select2.clearSelected()
this.$message.success(this.$t('common.updateSuccessMsg'))
this.$refs.ListTable.reloadTable()
}
}
}
},
methods: {
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -11,13 +11,18 @@ import { GenericDetailPage, TabPage } from '@/layout/components'
import Detail from './Detail.vue'
import AssetList from './AssetList.vue'
import AccountList from './AccountList.vue'
import AppList from './AppList'
import AppAccountList from './AppAccountList'
export default {
components: {
GenericDetailPage,
TabPage,
Detail,
AssetList,
AccountList
AccountList,
AppList,
AppAccountList
},
data() {
const vm = this
@@ -32,11 +37,31 @@ export default {
},
{
title: this.$t('assets.AssetList'),
name: 'AssetList'
name: 'AssetList',
hidden: () => {
return !vm.systemUser['is_asset_protocol']
}
},
{
title: this.$t('assets.AccountList'),
name: 'AccountList'
name: 'AccountList',
hidden: () => {
return !vm.systemUser['is_asset_protocol']
}
},
{
title: this.$t('assets.AppList'),
name: 'AppList',
hidden: () => {
return vm.systemUser['is_asset_protocol']
}
},
{
title: this.$t('assets.AccountList'),
name: 'AppAccountList',
hidden: () => {
return vm.systemUser['is_asset_protocol']
}
}
],
hasRightSide: true,

View File

@@ -1,13 +0,0 @@
<template>
<h1 />
</template>
<script>
export default {
name: 'Vault'
}
</script>
<style scoped>
</style>