Compare commits

..

69 Commits

Author SHA1 Message Date
“怀磊”
c093e7d621 fix: 修复资产授权创建时默认选中了点击的资产和节点问题
修改为指保留最后一次选中的资产或者节点
2022-02-17 18:20:47 +08:00
Jiangjie.Bai
b4183b421f Merge pull request #1316 from jumpserver/dev
v2.19.0-rc3
2022-02-16 16:42:58 +08:00
“怀磊”
9c6281cf02 fix: 修复资产账号导出条数不准确问题 2022-02-16 16:41:10 +08:00
feng626
e7bdf6276d fix: 工单流需要超级管理员配置 2022-02-16 14:50:45 +08:00
“怀磊”
fa860dbd85 fix: 修复命令记录页面筛选数据时tree自动刷新问题 2022-02-15 18:04:42 +08:00
Jiangjie.Bai
2bb4ca3ec0 fix: 修改LDAP SYNC相关参数设置 2022-02-15 17:58:20 +08:00
feng626
93a08d265b fix: 修复工单流只在当前组织管理员或系统管理员显示 2022-02-15 17:01:07 +08:00
Jiangjie.Bai
b7be7784c6 Merge pull request #1310 from jumpserver/dev
v2.19.0-rc2
2022-02-14 18:33:51 +08:00
“怀磊”
375c6bf44b fix: 修复搜索组件在页面刷新后搜索条件是布尔类型时页面不显示中文问题
1、优化获取url中搜索条件的算法
2022-02-14 18:23:03 +08:00
feng626
542e34208e feat: 工单添加redis类型 2022-02-14 17:46:07 +08:00
“怀磊”
f041b0de74 fix: 修复命令记录页面layout布局不能自适应问题 2022-02-14 16:16:59 +08:00
“怀磊”
7ff75280a5 fix: 修复search组件去重搜索不准确问题 2022-02-14 16:16:29 +08:00
ibuler
d76009de3a pref: 优化数据库协议翻译 2022-02-14 15:01:45 +08:00
“怀磊”
851f4508e2 fix: 资产账号替换密码框组件 2022-02-10 15:04:56 +08:00
feng626
a36e5196f8 perf: 工单流显示组织名称 2022-02-10 15:04:09 +08:00
Jiangjie.Bai
d22d16681e Merge pull request #1300 from jumpserver/dev
v2.19.0-rc1
2022-02-10 10:50:21 +08:00
“怀磊”
31c86fb281 feat: table搜索框支持多级自定义搜索 2022-02-09 18:47:24 +08:00
Michael Bai
f83739b496 fix: 修改第三方认证用户进行MFA认证配置项变量名 2022-02-09 10:34:09 +08:00
Michael Bai
1d9eddb11f feat: 增加系统设置(安全)控制第三方认证用户是否进行MFA认证 2022-02-08 17:49:21 +08:00
Michael Bai
da14abc83b feat: 命令过滤规则增加忽略大小写选项 2022-02-08 12:31:48 +08:00
“怀磊”
7b705dfc75 perf: 表单自动滚动到校验出错位置 2022-01-21 18:35:13 +08:00
“怀磊”
4516d83ce4 fix: 修复搜索框不能搜索false选项的问题 2022-01-20 15:05:35 +08:00
Jiangjie.Bai
db753bd1c2 Merge pull request #1292 from jumpserver/dev
v2.18
2022-01-20 13:48:00 +08:00
Michael Bai
ac14e5964d fix: 修复强制启用用户MFA后,个人信息页面设置按钮被禁用的问题 2022-01-20 11:59:56 +08:00
feng626
232c22c8ae fix: 应用改密去掉redis 2022-01-20 11:00:08 +08:00
Jiangjie.Bai
610da9f9b2 Merge pull request #1289 from jumpserver/dev
v2.18.0-rc4
2022-01-19 19:40:47 +08:00
Michael Bai
29631b1eaf fix: 修改IP(Doamin) -> IP(Host)翻译信息 2022-01-19 19:09:13 +08:00
“怀磊”
537887553d fix: 修复资产账号、账号备份、上传ssh密钥组件翻译问题
1、替换了改密计划-资产改密,密码框组件
2022-01-19 14:12:43 +08:00
“怀磊”
c1ddc0f8e0 fix: 修复工单创建应用保存时报错问题 2022-01-19 13:54:48 +08:00
feng626
316b39d15b fix: 修复工单命令复合跳转非当前组织404问题 2022-01-19 11:26:04 +08:00
Jiangjie.Bai
b18b05b8fb Merge pull request #1284 from jumpserver/dev
v2.18.0-rc3
2022-01-18 19:36:45 +08:00
“怀磊”
cd5534c20a feat: 系统用户上传ssh协议i18n翻译 2022-01-18 19:34:00 +08:00
feng626
048fee7ede Merge pull request #1279 from jumpserver/pr@dev@fix_accounts_edit_password_errortip
fix: 修复资产账号修改密码时报错提示重复问题
2022-01-18 19:16:34 +08:00
feng626
083b4bc499 Merge pull request #1282 from jumpserver/pr@dev@translate
fix: 改密翻译修改
2022-01-18 19:15:44 +08:00
怀磊
eb44856807 Merge branch 'dev' into pr@dev@translate 2022-01-18 19:09:39 +08:00
feng626
fff2288069 fix: 改密翻译修改 2022-01-18 19:07:17 +08:00
feng626
d5fc939a40 fix: 改密翻译修改 2022-01-18 19:07:15 +08:00
“怀磊”
6489cbe488 fix:修复应用授权克隆报错问题 2022-01-18 19:03:04 +08:00
“怀磊”
941f1d855e fix: 修复资产账号修改密码时报错提示重复问题 2022-01-18 17:13:14 +08:00
“怀磊”
ff88a0bd42 fix: 修复table搜索框回车搜索时重复搜索问题 2022-01-18 16:01:10 +08:00
Michael Bai
c87412f4c0 fix: 修改审计日志操作日志动作翻译问题 2022-01-18 15:21:11 +08:00
Michael Bai
2da4e4f7a0 fix: 修复应用账号查询问题 2022-01-18 15:03:46 +08:00
Michael Bai
848bdcba36 fix: 修改审计日志中支持i18n的显示字段display 2022-01-18 14:34:26 +08:00
“怀磊”
96206f10a2 perf: 优化table搜索框切换搜索字段后不能回车搜索问题 2022-01-18 14:03:37 +08:00
“怀磊”
a6012eba4a fix: 修复资产授权详情用户组添加会显示更多 2022-01-18 11:21:12 +08:00
Jiangjie.Bai
2570aa63fc Merge pull request #1272 from jumpserver/dev
v2.18.0-rc2
2022-01-17 19:22:44 +08:00
“怀磊”
43797c2f46 fix: 修复工单搜索条件-动作可编辑问题;增加搜索选项关闭动画 2022-01-17 16:31:01 +08:00
feng626
e98e23dad8 perf: 账号备份优化 2022-01-17 15:59:25 +08:00
v-hleihuai
11c845c0fd fix: 修复自定义字段展示,没有label的字段不在列表中展示 2022-01-17 15:49:46 +08:00
feng626
6310fae385 feat: 特权用户添加ssh密钥密码 2022-01-17 15:46:44 +08:00
“怀磊”
c20453a1de feat: 修改工单会话展示字段 2022-01-14 16:57:23 +08:00
Michael Bai
b1733835a4 feat: 修改Copyright信息 2022-01-13 18:56:06 +08:00
ibuler
4e1c921451 perf: 优化 ops 任务名称,支持 i18n 2022-01-13 15:04:34 +08:00
Jiangjie.Bai
0a4238fe93 Merge pull request #1262 from jumpserver/dev
v2.18.0-rc1
2022-01-12 20:35:54 +08:00
“怀磊”
2aad1110d0 feat: 账号备份详情页替换接口 2022-01-12 20:25:56 +08:00
feng626
03031e94c1 feat: 逃生通道 2022-01-12 20:25:56 +08:00
“怀磊”
aa94c1792c feat: 工单列表和详情添加编号字段 2022-01-12 14:34:30 +08:00
“怀磊”
f13bc4fccb fix: 修复上传下载组件展示问题 2022-01-10 17:32:45 +08:00
v-hleihuai
def153cc42 fix: 修改i18n中文国际化 2022-01-10 10:30:07 +08:00
“怀磊”
8add1b7f92 feat: lina 修改时间区间 2022-01-04 15:08:54 +08:00
xinwen
0cafadd230 feat: 资产列表增加 is_active 字段 2021-12-31 17:25:56 +08:00
“怀磊”
61c777532b feat: 工单会话添加状态字段 2021-12-31 17:25:25 +08:00
feng626
e13f2b34ff fix: ticket bug 2021-12-31 16:36:21 +08:00
“怀磊”
fa3daea82d feat: 工单添加会话功能 2021-12-31 16:29:32 +08:00
feng626
16c6f4c77f feat: add passphrase 2021-12-31 14:48:56 +08:00
Michael Bai
31e699bd2d feat: 应用授权增加Action动作控制 2021-12-30 17:24:21 +08:00
“怀磊”
4ecdf1575d fix: 应用管理类型搜索,搜索条件过滤 2021-12-30 10:27:45 +08:00
xinwen
ec00a541f8 feat: 命令记录接口增加 remote_addr 字段 2021-12-28 18:48:09 +08:00
jiangweidong
ef97df09cb feat: 支持 Redis 数据库的纳管 (#1246)
* feat: 支持 Redis 数据库的纳管

* feat: 支持Redis数据库的纳管, 将redis_acl协议合并到redis协议中
2021-12-28 18:16:13 +08:00
72 changed files with 1458 additions and 210 deletions

View File

@@ -46,12 +46,4 @@ server {
## License & Copyright
Copyright (c) 2014-2021 飞致云 FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
https://github.com/jumpserver/lina/blob/dev/LICENSE
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Be consistent with [jumpserver](https://github.com/jumpserver/jumpserver)

View File

@@ -25,7 +25,7 @@
<el-form-item :label="this.$t('assets.Password')">
<el-input v-model="authInfo.password" type="password" show-password />
</el-form-item>
<el-form-item :label="this.$t('assets.SSHKey')">
<el-form-item :label="this.$t('users.SSHKey')">
<el-input v-model="authInfo['private_key']" class="item-textarea" type="textarea" show-password />
</el-form-item>
</el-form>

View File

@@ -8,7 +8,7 @@
@cancel="handleCancel()"
v-on="$listeners"
>
<el-form label-position="right" label-width="80px">
<el-form label-position="right" label-width="90px">
<el-form-item :label="this.$t('assets.Hostname')">
<el-input v-model="account.hostname" readonly />
</el-form-item>
@@ -16,21 +16,27 @@
<el-input v-model="account['username']" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Password')">
<el-input v-model="authInfo.password" type="password" />
<UpdateToken v-model="authInfo.password" />
</el-form-item>
<el-form-item :label="this.$t('assets.SSHKey')">
<input type="file" @change="onPrivateKeyLoaded">
<el-form-item :label="this.$t('assets.SSHSecretKey')">
<UploadKey @input="getFile" />
</el-form-item>
<el-form-item :label="this.$t('assets.Passphrase')">
<UpdateToken v-model="authInfo.passphrase" />
</el-form-item>
</el-form>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
import { Dialog, UploadKey } from '@/components'
import { UpdateToken } from '@/components/FormFields'
export default {
name: 'UpdateSecretInfo',
components: {
Dialog
Dialog,
UploadKey,
UpdateToken
},
props: {
account: {
@@ -42,7 +48,8 @@ export default {
return {
authInfo: {
password: '',
private_key: ''
private_key: '',
passphrase: ''
}
}
},
@@ -54,10 +61,12 @@ export default {
}
if (this.authInfo.private_key !== '') {
data.private_key = this.authInfo.private_key
if (this.authInfo.passphrase) data.passphrase = this.authInfo.passphrase
}
this.$axios.patch(
`/api/v1/assets/accounts/${this.account.id}/`,
data
data,
{ disableFlashErrorMsg: true }
).then(res => {
this.authInfo = { password: '', private_key: '' }
this.$message.success(this.$tc('common.updateSuccessMsg'))
@@ -72,16 +81,8 @@ export default {
handleCancel() {
this.$emit('update:visible', false)
},
onPrivateKeyLoaded(e) {
const vm = this
// TODO 校验文件类型
const reader = new FileReader()
reader.onload = function() {
vm.authInfo.private_key = this.result
}
reader.readAsText(
e.target.files[0]
)
getFile(file) {
this.authInfo.private_key = file
}
}
}

View File

@@ -161,6 +161,7 @@ export default {
watch: {
url(iNew) {
this.$set(this.tableConfig, 'url', iNew)
this.$set(this.headerActions.exportOptions, 'url', iNew.replace('/accounts/', '/account-secrets/'))
}
},
mounted() {

View File

@@ -135,7 +135,7 @@ export default {
mfaVerifyRequired: true
},
searchConfig: {
exclude: ['systemuser', 'asset']
exclude: ['systemuser', 'app']
},
hasSearch: true
}

View File

@@ -18,6 +18,9 @@ export class FormFieldGenerator {
})
}
break
case 'multiple choice':
field.el.choices = fieldRemoteMeta['choices']
break
case 'datetime':
type = 'date-picker'
field.el = {

View File

@@ -274,8 +274,10 @@ export default {
})
},
generatePopoverColumns() {
this.popoverColumns.totalColumnsList = this.totalColumns.map(obj => {
return { prop: obj.prop, label: obj.label }
this.popoverColumns.totalColumnsList = this.totalColumns.filter(obj => {
if (obj.label) {
return { prop: obj.prop, label: obj.label }
}
})
this.popoverColumns.currentCols = this.cleanedColumnsShow.show
this.popoverColumns.minCols = this.cleanedColumnsShow.min

View File

@@ -110,9 +110,11 @@ export default {
this.currentNode = treeNode
this.currentNodeId = treeNode.meta.data.id
query['node'] = this.currentNodeId
query['asset'] = ''
url = `${this.setting.url}${combinator}node_id=${treeNode.meta.data.id}&show_current_asset=${show_current_asset}`
} else if (treeNode.meta.type === 'asset') {
query['asset'] = treeNode.meta.data.id
query['node'] = ''
url = `${this.setting.url}${combinator}asset_id=${treeNode.meta.data.id}&show_current_asset=${show_current_asset}`
}
this.$router.push({ query })

View File

@@ -15,7 +15,7 @@
</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">
<div v-if="option.group" :key="'group:'+option.name" class="dropdown-menu-title" style="width:130px">
{{ option.group }}
</div>
<el-dropdown-item

View File

@@ -23,6 +23,7 @@
<script>
import ElFormRender from './components/el-form-renderer'
import { scrollToError } from '@/utils'
export default {
components: {
ElFormRender
@@ -76,6 +77,7 @@ export default {
this.$emit('submit', form.getFormValue(), form, addContinue)
} else {
this.$emit('invalid', valid)
scrollToError(form.$el)
return false
}
})

View File

@@ -1,6 +1,10 @@
<template>
<div>
<input type="file" @change="Onchange">
<input ref="upLoadFile" type="file" style="display: none" @change="Onchange">
<el-button size="mini" @click.native.stop="onUpLoad">
{{ this.$t('common.SelectFile') }}
</el-button>
<span>{{ fileName }}</span>
<div v-if="tip !== ''">{{ tip }}</div>
<input v-model="value" type="text" hidden v-on="$listeners">
<div>
@@ -23,6 +27,7 @@ export default {
},
data() {
return {
fileName: '',
initial: this.value,
preview: this.value
}
@@ -34,16 +39,21 @@ export default {
}
},
methods: {
onUpLoad() {
this.$refs.upLoadFile.click()
},
onInput(val) {
this.$emit('input', val)
},
Onchange(e) {
if (e.target.files[0] === undefined) {
const upLoadFile = e.target.files[0]
if (upLoadFile === undefined) {
this.$emit('input', this.initial)
return
}
this.$emit('fileChange', e.target.files[0])
this.$emit('input', this.getObjectURL(e.target.files[0]))
this.fileName = upLoadFile?.name || ''
this.$emit('fileChange', upLoadFile)
this.$emit('input', this.getObjectURL(upLoadFile))
},
getObjectURL(file) {
let url = null

View File

@@ -1,6 +1,10 @@
<template>
<div class="upload-key">
<input type="file" @change="onChange">
<input ref="upLoadFile" type="file" style="display: none" @change="onChange">
<el-button size="mini" @click.native.stop="onUpLoad">
{{ this.$t('common.SelectFile') }}
</el-button>
<span>{{ fileName }}</span>
<div v-if="tip !== ''">{{ tip }}</div>
</div>
</template>
@@ -21,12 +25,22 @@ export default {
default: () => 'string'
}
},
data() {
return {
fileName: ''
}
},
methods: {
onUpLoad() {
this.$refs.upLoadFile.click()
},
onChange(e) {
if (e.target.files.length === 0) {
const upLoadFile = e.target.files
if (upLoadFile.length === 0) {
return
}
const vm = this
this.fileName = upLoadFile[0].name || ''
const reader = new FileReader()
reader.onload = function() {
let result = this.result
@@ -36,7 +50,7 @@ export default {
vm.$emit('input', result)
}
reader.readAsText(
e.target.files[0]
upLoadFile[0]
)
}
}

View File

@@ -34,6 +34,7 @@
@blur="focus = false"
@focus="focus = true"
@change="handleConfirm"
@keyup.enter.native="handleConfirm"
/>
</div>
@@ -85,7 +86,11 @@ export default {
if (key === '') {
key = 'search'
}
data[key] = value
if (key.startsWith('search')) {
data['search'] = (data.search ? data.search + ',' : '') + value
} else {
data[key] = value
}
}
return data
},
@@ -108,6 +113,19 @@ export default {
handler(val) {
if (val && val.length > 0) {
const routeFilter = this.checkInTableColumns()
const routerSearch = routeFilter.search || {}
const routerSearchArrs = routerSearch?.value?.split(',') || []
const routerSearchArrsLength = routerSearchArrs.length || 0
if (routerSearch && routerSearchArrsLength > 0) {
for (let i = 0; i < routerSearchArrsLength; i++) {
const cur = routerSearchArrs[i]
routeFilter[`search_${cur}`] = {
...routerSearch,
value: cur
}
}
delete routeFilter.search
}
const asFilterTags = _.cloneDeep(this.filterTags)
this.filterTags = {
...asFilterTags,
@@ -129,37 +147,43 @@ export default {
if (Object.keys(this.filterMaps).length > 0) {
return this.$emit('tagSearch', this.filterMaps)
}
}
, 400)
}, 400)
// this.$nextTick(() => this.$emit('tagSearch', this.filterMaps))
},
methods: {
// 判断url中的查询条件
// 获取url中的查询条件,判断是不是包含在当前查询条件里
checkInTableColumns() {
const routeQuery = this.getUrlQuery ? this.$route?.query : {}
const routeQueryKeys = Object.keys(routeQuery)
const routeQueryKeysLength = routeQueryKeys.length
const keys = {}
if (routeQueryKeys.length < 1) return keys
if (routeQueryKeys.length > 0) {
for (let i = 0; i < routeQueryKeys.length; i++) {
if (routeQueryKeysLength < 1) return keys
if (routeQueryKeysLength > 0) {
for (let i = 0; i < routeQueryKeysLength; i++) {
const key = routeQueryKeys[i]
const valueDecode = decodeURI(routeQuery[key])
let valueDecode = decodeURI(routeQuery[key])
const isSearch = key !== 'search'
for (let k = 0, len = this.options.length; k < len; k++) {
const cur = this.options[k]
const curOptions = this.options || []
for (let k = 0, len = curOptions.length; k < len; k++) {
const cur = curOptions[k]
if (cur?.type === 'boolean') {
valueDecode = !!valueDecode
}
if (key === cur.value || !isSearch) {
const curChildren = cur.children || []
keys[key] = {
...cur,
key,
label: isSearch ? cur.label : '',
value: valueDecode
}
if (isSearch && curChildren.length > 0) {
curChildren.forEach(item => {
for (const item of curChildren) {
if (valueDecode === item.value) {
keys[key].valueLabel = item.label
break
}
})
}
}
}
}
@@ -199,22 +223,28 @@ export default {
this.$nextTick(() => this.$refs.Cascade.handleClear())
},
handleTagClose(evt) {
this.checkUrlFilds(evt)
this.$delete(this.filterTags, evt)
this.checkUrlFilds(evt)
this.$emit('tagSearch', this.filterMaps)
return true
},
handleConfirm() {
if (this.filterValue === '') return
if (this.filterValue && !this.filterKey) {
this.filterKey = 'search'
this.filterKey = 'search' + '_' + this.filterValue
}
const tag = { key: this.filterKey, label: this.keyLabel, value: this.filterValue, valueLabel: this.valueLabel }
this.$set(this.filterTags, this.filterKey, tag)
this.$emit('tagSearch', this.filterMaps)
// 修改查询参数时改变url中保存的参数
if (this.getUrlQuery) {
let newQuery = _.cloneDeep(this.$route.query)
newQuery = { ...newQuery, [this.filterKey]: encodeURI(this.filterValue) }
if (this.filterKey.startsWith('search')) {
newQuery = { ...newQuery, search: encodeURI(this.filterMaps.search) }
} else {
newQuery = { ...newQuery, [this.filterKey]: encodeURI(this.filterValue) }
}
this.$router.replace({ query: newQuery })
}
@@ -247,7 +277,15 @@ export default {
},
// 删除查询条件时改变url
checkUrlFilds(evt) {
const newQuery = _.omit(this.$route.query, evt)
let newQuery = _.omit(this.$route.query, evt)
if (this.getUrlQuery && evt.startsWith('search')) {
if (newQuery.search) delete newQuery.search
const filterMapsSearch = this.filterMaps.search || ''
newQuery = {
...newQuery,
...(filterMapsSearch && { search: encodeURI(filterMapsSearch) })
}
}
this.$router.replace({ query: newQuery })
}
}

View File

@@ -45,6 +45,7 @@
"vmware_client":"Vmware Client",
"custom":"Custom",
"mysql": "MySQL",
"redis": "Redis",
"oracle": "Oracle",
"postgresql": "PostgreSQL",
"mariadb": "MariaDB",
@@ -88,7 +89,9 @@
"cluster": "集群",
"kubernetes":"Kubernetes",
"clusterHelpTextMessage": "例如https://172.16.8.8:8443",
"DBInfo": "数据库信息"
"DBInfo": "数据库信息",
"RDBProtocol": "关系型数据库",
"NoSQLProtocol": "非关系数据库"
},
"assets": {
"AppList": "应用列表",
@@ -168,6 +171,7 @@
"Other": "其它",
"Hardware": "硬件信息",
"Password": "密码",
"Passphrase": "密钥密码",
"PasswordWithoutSpecialCharHelpText": "不能包含特殊字符",
"Pending": "等待",
"Platform": "系统平台",
@@ -220,7 +224,7 @@
"sshKeyFingerprint": "SSH 指纹",
"ip": "IP",
"sshkey": "sshkey",
"SSHKey": "SSH 密钥",
"SSHSecretKey": "SSH 密钥",
"GroupsHelpMessage": "请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)",
"HomeHelpMessage": "默认家目录 /home/系统用户名: /home/username",
"Home": "家目录",
@@ -228,6 +232,8 @@
"ipDomain": "IP(域名)",
"HostProtocol": "主机协议",
"DatabaseProtocol": "数据库协议",
"RDBProtocol": "关系型数据库",
"NoSQLProtocol": "非关系数据库",
"OtherProtocol": "其它协议",
"PasswordOrToken": "密码 / 令牌"
},
@@ -296,6 +302,7 @@
"Close": "关闭",
"Command filter": "命令过滤器",
"Comment": "备注",
"Number": "编号",
"Confirm": "确认",
"Create": "创建",
"CreatedBy": "创建者",
@@ -784,6 +791,7 @@
"accountName": "账户名称",
"active": "激活中",
"alive": "在线",
"noAlive": "离线",
"asset": "资产",
"target": "目标",
"bucket": "桶名称",
@@ -839,6 +847,10 @@
"riskLevels": {
"common": "普通"
},
"SessionID": "会话ID",
"TargetResources": "目标资源",
"UseProtocol": "使用协议",
"SessionState": "会话状态",
"Monitor": "监控",
"XRDPNotSupport": "RDP 客户端会话, 暂不支持监控",
"sessionMonitor": "监控",
@@ -1302,6 +1314,10 @@
"Name": "名称",
"NodeAmount": "节点数量",
"PasswordLength": "密码长度",
"ChangePassword": "更改密码",
"ModifySSHKey": "修改 SSH Key",
"Addressee": "收件人",
"OnlyMailSend": "当前只支持邮件发送",
"PasswordStrategy": "密码策略",
"SecretKeyStrategy": "密钥策略",
"RegularlyPerform": "定期执行",
@@ -1314,6 +1330,17 @@
"TimerPeriod": "定时执行周期",
"Username": "用户名"
},
"AccountBackupPlan": {
"Types": "类型",
"Backup": "备份",
"AccountBackupPlan": "账号备份",
"AccountBackupPlanCreate": "创建账号备份",
"AccountBackupPlanUpdate": "更新账号备份",
"ExecutionDetail": "执行详情",
"MailRecipient": "邮件收件人",
"IsSuccess": "是否成功",
"Reason": "原因"
},
"Cloud": {
"ServerAccountKey": "服务账号密钥",
"IPNetworkSegment": "IP网段",

View File

@@ -40,6 +40,7 @@
"vmware_client":"Vmware Client",
"custom":"Custom",
"mysql": "MySQL",
"redis": "Redis",
"oracle": "Oracle",
"postgresql": "PostgreSQL",
"mariadb": "MariaDB",
@@ -83,7 +84,9 @@
"cluster": "Cluster",
"kubernetes":"Kubernetes",
"clusterHelpTextMessage": "Tips: https://172.16.8.8:8443",
"DBInfo": "Database Info"
"DBInfo": "Database Info",
"RDBProtocol": "RDS Protocol",
"NoSQLProtocol": "NoSQL Protocol"
},
"assets": {
"AppList": "Application list",
@@ -165,6 +168,7 @@
"Os": "Os",
"Other": "Other",
"Password": "Password",
"Passphrase": "Passphrase",
"PasswordWithoutSpecialCharHelpText": "Password can't has special chars ",
"Pending": "Pending",
"Platform": "Platform",
@@ -215,13 +219,16 @@
"sshKeyFingerprint": "SSH fingerprint",
"ip": "IP",
"sshkey": "sshkey",
"SSHSecretKey": "SSh key",
"GroupsHelpMessage": "Please fill in user groups, separated by commas if there are multiple user groups(Please fill in the existing user groups)",
"HomeHelpMessage": "Default home directory: /home/system username",
"Home": "Home",
"LinuxUserAffiliateGroup": "Linux user affiliate group",
"ipDomain": "IP(Domain)",
"ipDomain": "IP(Host)",
"HostProtocol": "Host Protocol",
"DatabaseProtocol": "Database Protocol",
"RDBProtocol": "RDS Protocol",
"NoSQLProtocol": "NoSQL Protocol",
"OtherProtocol": "Other Protocol",
"PasswordOrToken": "Password / Token"
},
@@ -283,6 +290,7 @@
"Close": "Close",
"Command filter": "Command filter",
"Comment": "Comment",
"Number": "Number",
"Confirm": "Confirm",
"Create": "Create",
"CreatedBy": "Created by",
@@ -644,7 +652,7 @@
"Assets": "Assets",
"Audits": "Audits",
"BatchCommand": "Batch Command",
"BatchCommandLog": "Batch Command Log",
"BatchCommandLog": "Batch Command Logs",
"CeleryTaskLog": "Celery task log",
"CommandExecutions": "CommandExecutions ",
"CommandFilterCreate": "Command filter create",
@@ -765,6 +773,7 @@
"accountName": "Account name",
"active": "active",
"alive": "alive",
"noAlive": "no alive",
"asset": "Asset",
"target": "Target",
"bucket": "Bucket",
@@ -820,6 +829,10 @@
"riskLevels": {
"common": "common"
},
"SessionID": "Session ID",
"TargetResources": "Target resources",
"UseProtocol": "Use protocol",
"SessionState": "Session state",
"Monitor": "Monitor",
"XRDPNotSupport": "RDP Client session not support now",
"sessionMonitor": "Session Monitor",
@@ -1259,6 +1272,10 @@
"Name": "Name",
"NodeAmount": "Node",
"PasswordLength": "Password length",
"ChangePassword": "Change password",
"ModifySSHKey": "Modify SSH Key",
"Addressee": "Addressee",
"OnlyMailSend": "Currently only mail sending is supported",
"PasswordStrategy": "Password strategy",
"SecretKeyStrategy": "Secret key strategy",
"RegularlyPerform": "Regularly perform",
@@ -1271,6 +1288,17 @@
"TimerPeriod": "Timer period",
"Username": "Username"
},
"AccountBackupPlan": {
"Types": "Types",
"Backup": "Backup",
"AccountBackupPlan": "Account backup plan",
"AccountBackupPlanreate": "Account backup plan",
"AccountBackupPlanUpdate": "Account backup plan",
"ExecutionDetail": "Execution detail",
"MailRecipient": "Mail recipient",
"IsSuccess": "Is success",
"Reason": "Reason"
},
"Cloud": {
"ServerAccountKey": "Server Account Key",
"IPNetworkSegment": "Ip Network Segment",

View File

@@ -3,8 +3,8 @@
<div class="pull-right">
Version <strong> dev </strong> <span v-if="!publicSettings.XPACK_LICENSE_IS_VALID"> GPLv2. </span>
</div>
<div v-if="!publicSettings.XPACK_LICENSE_IS_VALID" style="padding-left:20px;">
<strong>Copyright</strong> FIT2CLOUD 飞致云 © 2014-2021
<div style="padding-left:20px;">
{{ publicSettings.XPACK_LICENSE_INFO.corporation }}
</div>
</div>
</template>
@@ -12,6 +12,11 @@
import { mapGetters } from 'vuex'
export default {
name: 'Footer',
data() {
return {
curYear: this.$moment().year() || ''
}
},
computed: {
...mapGetters([
'sidebar',

View File

@@ -283,8 +283,8 @@ export default {
let object = this.object
if (!object || Object.keys(object).length === 0) {
if (cloneFrom) {
this.$log.debug('Clone from: ', cloneFrom)
const url = `${this.url}${cloneFrom}/`
const [curUrl, query] = this.url.split('?')
const url = `${curUrl}${cloneFrom}/${query ? ('?' + query) : ''}`
object = await this.getObjectDetail(url)
if (object['name']) {
object.name = this.$t('common.cloneFrom') + ' ' + object.name

View File

@@ -161,5 +161,54 @@ export default [
hidden: true
}
]
},
{
path: 'backup',
component: empty,
redirect: '',
meta: { title: i18n.t('xpack.AccountBackupPlan.AccountBackupPlan') },
children: [
{
path: '',
component: () => import('@/views/accounts/AccountBackupPlan/index.vue'),
name: 'AccountBackupPlanIndex',
meta: { title: i18n.t('xpack.AccountBackupPlan.AccountBackupPlan'), activeMenu: '/accounts/backup' }
},
{
path: '',
component: () => import('@/views/accounts/AccountBackupPlan/AccountBackupPlanList.vue'),
name: 'AccountBackupPlanList',
meta: { title: i18n.t('xpack.AccountBackupPlan.AccountBackupPlan'), activeMenu: '/accounts/backup' },
hidden: true
},
{
path: 'create',
component: () => import('@/views/accounts/AccountBackupPlan/AccountBackupPlanCreateUpdate.vue'),
name: 'AccountBackupPlanCreate',
meta: { title: i18n.t('xpack.AccountBackupPlan.AccountBackupPlanCreate'), activeMenu: '/accounts/backup', action: 'create' },
hidden: true
},
{
path: ':id/update',
component: () => import('@/views/accounts/AccountBackupPlan/AccountBackupPlanCreateUpdate.vue'),
name: 'AccountBackupPlanUpdate',
meta: { title: i18n.t('xpack.AccountBackupPlan.AccountBackupPlanUpdate'), activeMenu: '/accounts/backup', action: 'update' },
hidden: true
},
{
path: ':id',
component: () => import('@/views/accounts/AccountBackupPlan/AccountBackupPlanDetail/index.vue'),
name: 'AccountBackupPlanDetail',
meta: { title: i18n.t('xpack.AccountBackupPlan.AccountBackupPlan'), activeMenu: '/accounts/backup' },
hidden: true
},
{
path: 'plan-execution/:id',
component: () => import('@/views/accounts/AccountBackupPlan/AccountBackupPlanDetail/AccountBackupPlanExecution/AccountBackupPlanExecutionDetail/index.vue'),
name: 'AccountBackupPlanExecutionDetail',
meta: { title: i18n.t('xpack.AccountBackupPlan.ExecutionDetail'), activeMenu: '/accounts/backup' },
hidden: true
}
]
}
]

View File

@@ -115,8 +115,7 @@ export default {
title: this.$t('users.SetMFA'),
attrs: {
type: 'primary',
label: this.$t('common.Setting'),
disabled: this.object.mfa_force_enabled
label: this.$t('common.Setting')
},
callbacks: {
click: function() {

View File

@@ -110,3 +110,24 @@ export function param2Obj(url) {
export function getDateTimeStamp(dateStr) {
return Date.parse(dateStr.replace(/-/gi, '/'))
}
/**
* 自动滚动到错误位置
* @param {*} el 目标元素
* @param {Object} 滚动参数 scrollOption={
* behavior: 'smooth',
* block: 'center'
* }
*/
export const scrollToError = (
el,
scrollOption = {
behavior: 'smooth',
block: 'center'
}
) => {
setTimeout(() => {
const isError = el.getElementsByClassName('is-error')
isError[0].scrollIntoView(scrollOption)
}, 0)
}

View File

@@ -0,0 +1,56 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
</template>
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import getFields from '@/views/accounts/AccountBackupPlan/fields'
import FormTypeField from './components/FormTypeField'
export default {
name: 'AccountBackupPlanUpdate',
components: {
GenericCreateUpdatePage
},
data() {
const fields = getFields.bind(this)()
return {
url: '/api/v1/assets/backup/',
fields: [
[this.$t('common.Basic'), ['name']],
[this.$t('xpack.AccountBackupPlan.Types'), ['types']],
[this.$t('xpack.AccountBackupPlan.Backup'), ['recipients']],
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
[this.$t('common.Other'), ['comment']]
],
initial: {
is_periodic: true,
interval: 24,
types: ['all', 'asset', 'application']
},
fieldsMeta: {
is_periodic: fields.is_periodic,
crontab: fields.crontab,
interval: fields.interval,
recipients: fields.recipients,
types: {
label: this.$t('xpack.AccountBackupPlan.Types'),
component: FormTypeField
}
},
createSuccessNextRoute: { name: 'AccountBackupPlanIndex' },
updateSuccessNextRoute: { name: 'AccountBackupPlanIndex' },
cleanFormValue(data) {
if (data['interval'] === '') {
delete data['interval']
}
return data
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,60 @@
<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: 'AccountBackupPlanExecutionInfo',
components: {
DetailCard
},
props: {
object: {
type: Object,
default: () => ({})
}
},
data() {
return {
}
},
computed: {
detailItems() {
return [
{
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)
},
{
key: this.$t('xpack.AccountBackupPlan.IsSuccess'),
value: this.object.is_success
},
{
key: this.$t('xpack.AccountBackupPlan.Reason'),
value: this.object.reason
},
{
key: this.$t('xpack.ChangeAuthPlan.MailRecipient'),
value: this.object.recipients ? this.object.recipients.map(
i => `${i[0]}` + `${i[1] ? ': ' + this.$t('xpack.ChangeAuthPlan.ContainAttachment') : ''}`).join(', ') : ''
}
]
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,49 @@
<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 AccountBackupPlanExecutionInfo from './AccountBackupPlanExecutionInfo'
export default {
components: {
GenericDetailPage,
AccountBackupPlanExecutionInfo
},
data() {
return {
execution: { id: '' },
config: {
activeMenu: 'AccountBackupPlanExecutionInfo',
actions: {
detailApiUrl: `/api/v1/assets/backup-execution/${this.$route.params.id}/`,
hasUpdate: false,
hasDelete: false
},
submenu: [
{
title: this.$t('common.BasicInfo'),
name: 'AccountBackupPlanExecutionInfo'
}
],
getTitle: this.getExecutionTitle
}
}
},
methods: {
getExecutionTitle() {
return `${this.$route.meta.title}: ${this.execution.id}`
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,84 @@
<template>
<GenericListTable :table-config="tableConfig" :header-actions="headerActions" />
</template>
<script>
import GenericListTable from '@/layout/components/GenericListTable'
export default {
name: 'AccountBackupPlanExecution',
components: {
GenericListTable
},
props: {
object: {
type: Object,
required: true,
default: () => ({})
}
},
data() {
return {
tableConfig: {
url: `/api/v1/assets/backup-execution/?plan_id=${this.object.id}`,
columns: [
'timedelta', 'trigger_display', 'date_start', 'is_success', 'reason', 'actions'
],
columnsMeta: {
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: 'AccountBackupPlanExecutionDetail', 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,93 @@
<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: 'AccountBackupPlanInfo',
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/assets/backup-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.RegularlyPerform'),
value: this.object.crontab,
formatter: (item, val) => {
return <span>{this.object.is_periodic ? val : ''}</span>
}
},
{
key: this.$t('xpack.ChangeAuthPlan.CyclePerform'),
value: this.object.interval,
formatter: (item, val) => {
return <span>{this.object.is_periodic ? val : ''}</span>
}
},
{
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,47 @@
<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 AccountBackupPlanInfo from './AccountBackupPlanInfo'
import AccountBackupPlanExecutionList from './AccountBackupPlanExecution/AccountBackupPlanExecutionList'
export default {
components: {
GenericDetailPage,
AccountBackupPlanInfo,
AccountBackupPlanExecutionList
},
data() {
return {
plan: { name: '', comment: '' },
config: {
activeMenu: 'AccountBackupPlanInfo',
submenu: [
{
title: this.$t('common.BasicInfo'),
name: 'AccountBackupPlanInfo'
},
{
title: this.$t('xpack.ChangeAuthPlan.ExecutionList'),
name: 'AccountBackupPlanExecutionList'
}
],
actions: {
detailApiUrl: `/api/v1/assets/backup/${this.$route.params.id}/`,
deleteApiUrl: `/api/v1/assets/backup/${this.$route.params.id}/`
}
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,95 @@
<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'
export default {
name: 'AccountBackupPlanList',
components: {
GenericListTable
},
data() {
const vm = this
return {
tableConfig: {
url: '/api/v1/assets/backup/',
columns: [
'name', 'is_periodic', 'periodic_display', 'org_name', 'comment', 'actions'
],
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'org_name', 'is_periodic', 'periodic_display', 'actions']
},
columnsMeta: {
name: {
formatter: DetailFormatter,
formatterArgs: {
route: 'AccountBackupPlanDetail'
}
},
is_periodic: {
label: vm.$t('xpack.ChangeAuthPlan.Timer'),
formatterArgs: {
showFalse: false
},
width: '80px'
},
periodic_display: {
label: vm.$t('xpack.ChangeAuthPlan.TimerPeriod'),
showOverflowTooltip: true,
width: '150px'
},
comment: {
width: '90px'
},
actions: {
width: '164px',
formatterArgs: {
onClone: ({ row }) => {
vm.$router.push({ name: 'AccountBackupPlanCreate', query: { clone_from: row.id }})
},
onUpdate: ({ row }) => {
vm.$router.push({ name: 'AccountBackupPlanUpdate', params: { id: row.id }})
},
extraActions: [
{
title: vm.$t('xpack.Execute'),
name: 'execute',
type: 'info',
callback: function({ row }) {
this.$axios.post(
`/api/v1/assets/backup-execution/`,
{ plan: row.id }
).then(res => {
openTaskPage(res['task'])
})
}.bind(this)
}
]
}
}
}
},
headerActions: {
hasRefresh: true,
hasExport: false,
hasImport: false,
hasMoreActions: false,
createRoute: () => {
return {
name: 'AccountBackupPlanCreate'
}
}
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,92 @@
<template>
<el-tree
:data="iData"
show-checkbox
node-key="id"
:default-expand-all="false"
:default-checked-keys="value"
:props="defaultProps"
v-bind="$attrs"
@check="handleCheckChange"
/>
</template>
<script>
export default {
name: 'FormTypeField',
props: {
value: {
type: Array,
default: () => []
},
choices: {
type: Array,
default: () => []
}
},
data() {
return {
defaultProps: {
children: 'children',
label: 'label'
},
fullChoicesTreeNodes: [
{
id: 'all',
label: this.$t('perms.all'),
children: [
{
id: 'asset',
label: this.$t('route.AssetAccount')
},
{
id: 'application',
label: this.$t('route.ApplicationAccount')
}
]
}
]
}
},
computed: {
choicesIDs() {
return this.choices.map((v) => v.value)
},
iData() {
this.$log.debug('choices: ', this.choicesIDs)
const fullTreeNodes = _.cloneDeep(this.fullChoicesTreeNodes)
const treeNodes = this.trimChoicesTreeNodes(fullTreeNodes)
this.$log.debug('choicesTreeNodes: ', treeNodes)
return treeNodes
}
},
methods: {
trimChoicesTreeNodes(treeNodes) {
const newTreeNodes = []
for (const treeNode of treeNodes) {
if (!this.choicesIDs.includes(treeNode.id)) {
continue
}
let children = treeNode.children || []
if (children.length !== 0) {
children = this.trimChoicesTreeNodes(children)
treeNode.children = children
}
newTreeNodes.push(treeNode)
}
return newTreeNodes
},
handleCheckChange(data, obj) {
const checkedKeys = obj.checkedKeys
if (checkedKeys.length !== 0) {
checkedKeys.push('connect')
}
this.$emit('input', checkedKeys)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,59 @@
import i18n from '@/i18n/i18n'
import { CronTab } from '@/components'
var validatorInterval = (rule, value, callback) => {
if (parseInt(value) < 1) {
return callback(new Error(i18n.t('xpack.ChangeAuthPlan.validatorMessage.EnsureThisValueIsGreaterThanOrEqualTo1')))
}
callback()
}
function getFields() {
const recipients = {
label: i18n.t('xpack.ChangeAuthPlan.Addressee'),
helpText: i18n.t('xpack.ChangeAuthPlan.OnlyMailSend'),
el: {
value: [],
ajax: {
url: '/api/v1/users/users/?fields_size=mini',
transformOption: (item) => {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
}
}
}
const is_periodic = {
type: 'switch'
}
const crontab = {
type: 'cronTab',
component: 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 {
is_periodic: is_periodic,
crontab: crontab,
interval: interval,
recipients: recipients
}
}
export default getFields

View File

@@ -0,0 +1,36 @@
<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 AccountBackupPlanList from './AccountBackupPlanList'
export default {
name: 'Index',
components: {
TabPage,
AccountBackupPlanList
},
data() {
return {
config: {
activeMenu: 'AccountBackupPlanList',
submenu: [
{
title: this.$t('xpack.AccountBackupPlan.AccountBackupPlan'),
name: 'AccountBackupPlanList'
}
]
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -17,7 +17,6 @@ export default {
GenericTreeListPage, AccountListTable
},
data() {
const vm = this
return {
isInit: true,
clickedRow: null,
@@ -30,24 +29,25 @@ export default {
url: '/api/v1/assets/accounts/',
treeUrl: '/api/v1/assets/nodes/children/tree/?assets=1',
callback: {
onSelected: function(event, treeNode) {
let url = '/api/v1/assets/accounts/'
if (treeNode.meta.type === 'node') {
const nodeId = treeNode.meta.data.id
url = setUrlParam(url, 'asset', '')
url = setUrlParam(url, 'node', nodeId)
} else if (treeNode.meta.type === 'asset') {
const assetId = treeNode.meta.data.id
url = setUrlParam(url, 'node', '')
url = setUrlParam(url, 'asset', assetId)
}
setTimeout(() => {
vm.accountsUrl = url
}, 100)
}
onSelected: (event, treeNode) => this.getAccountsUrl(event, treeNode)
}
}
}
},
methods: {
getAccountsUrl(event, treeNode) {
let url = '/api/v1/assets/accounts/'
if (treeNode.meta.type === 'node') {
const nodeId = treeNode.meta.data.id
url = setUrlParam(url, 'asset', '')
url = setUrlParam(url, 'node', nodeId)
} else if (treeNode.meta.type === 'asset') {
const assetId = treeNode.meta.data.id
url = setUrlParam(url, 'node', '')
url = setUrlParam(url, 'asset', assetId)
}
this.accountsUrl = url
}
}
}
</script>

View File

@@ -6,7 +6,7 @@
import { GenericListTable } from '@/layout/components'
import { DetailFormatter } from '@/components/TableFormatters'
import { openTaskPage } from '@/utils/jms'
import { DATABASE } from '@/views/perms/const'
import { AppPlanDatabase } from '@/views/perms/const'
export default {
name: 'AppChangeAuthPlanList',
@@ -126,7 +126,7 @@ export default {
type: option.name.toLowerCase()
}})
},
dropdown: DATABASE
dropdown: AppPlanDatabase
}
}
}

View File

@@ -22,7 +22,7 @@ export default {
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'
'app_display', 'system_user_display', 'is_success', 'timedelta', 'date_start', 'reason_display', 'actions'
],
columnsMeta: {
app_display: {
@@ -35,6 +35,9 @@ export default {
return <router-link to={ to } >{ row.app_display }</router-link>
}
},
reason_display: {
label: this.$t('xpack.AccountBackupPlan.Reason')
},
system_user_display: {
label: this.$t('xpack.ChangeAuthPlan.SystemUser')
},

View File

@@ -19,7 +19,7 @@ export default {
[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.ChangeAuthPlan.SecretKeyStrategy'), ['is_ssh_key', 'ssh_key_strategy', 'private_key', 'passphrase']],
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
[this.$t('common.Other'), ['recipients', 'comment']]
],
@@ -38,6 +38,7 @@ export default {
username: fields.username,
assets: fields.assets,
password: fields.password,
passphrase: fields.passphrase,
password_rules: fields.asset_password_rules,
private_key: fields.private_key,
nodes: fields.nodes,

View File

@@ -22,7 +22,7 @@ export default {
tableConfig: {
url: `/api/v1/xpack/change-auth-plan/plan-execution-subtask/?plan_execution_id=${this.object.id}`,
columns: [
'username', 'asset', 'is_success', 'reason', 'timedelta', 'date_start', 'actions'
'username', 'asset', 'is_success', 'timedelta', 'date_start', 'reason_display', 'actions'
],
columnsMeta: {
asset: {
@@ -42,6 +42,9 @@ export default {
return row.timedelta.toFixed(2) + 's'
}
},
reason_display: {
label: this.$t('xpack.AccountBackupPlan.Reason')
},
actions: {
formatterArgs: {
hasDelete: false,

View File

@@ -1,6 +1,6 @@
import i18n from '@/i18n/i18n'
import { AssetSelect, CronTab } from '@/components'
import Select2 from '@/components/FormFields/Select2'
import { AssetSelect, CronTab, UploadKey } from '@/components'
import { Select2, UpdateToken } from '@/components/FormFields'
import { Required } from '@/components/DataForm/rules'
var validatorInterval = (rule, value, callback) => {
@@ -103,6 +103,14 @@ function getFields() {
]
}
const passphrase = {
label: i18n.t('assets.Passphrase'),
component: UpdateToken,
hidden: (formValue) => {
return formValue.is_ssh_key === false
}
}
const asset_password_rules = {
type: 'group',
items: generatePasswordRulesItemsFields('asset')
@@ -114,11 +122,7 @@ function getFields() {
}
const private_key = {
el: {
type: 'textarea',
placeholder: '-----BEGIN OPENSSH PRIVATE KEY-----',
autosize: { minRows: 3 }
},
component: UploadKey,
hidden: (formValue) => {
return formValue.is_ssh_key === false
},
@@ -128,6 +132,8 @@ function getFields() {
}
const recipients = {
label: i18n.t('xpack.ChangeAuthPlan.Addressee'),
helpText: i18n.t('xpack.ChangeAuthPlan.OnlyMailSend'),
el: {
value: [],
ajax: {
@@ -153,10 +159,12 @@ function getFields() {
}
const is_password = {
label: i18n.t('xpack.ChangeAuthPlan.ChangePassword'),
type: 'switch'
}
const is_ssh_key = {
label: i18n.t('xpack.ChangeAuthPlan.ModifySSHKey'),
type: 'switch'
}
@@ -225,6 +233,7 @@ function getFields() {
password_strategy: password_strategy,
ssh_key_strategy: ssh_key_strategy,
private_key: private_key,
passphrase: passphrase,
asset_password_rules: asset_password_rules,
database_password_rules: database_password_rules,
nodes: nodes,

View File

@@ -11,6 +11,41 @@ export default {
},
data() {
const vm = this
const appType = [
{
name: 'mysql',
title: 'MySQL',
has: true,
group: this.$t('assets.RDBProtocol')
},
{
name: 'postgresql',
title: 'PostgreSQL',
has: this.$store.getters.hasValidLicense
},
{
name: 'mariadb',
title: 'MariaDB',
type: 'primary',
has: this.$store.getters.hasValidLicense
},
{
name: 'oracle',
title: 'Oracle',
has: this.$store.getters.hasValidLicense
},
{
name: 'sqlserver',
title: 'SQLServer',
has: this.$store.getters.hasValidLicense
},
{
name: 'redis',
title: 'Redis',
has: true,
group: this.$t('assets.NoSQLProtocol')
}
]
return {
tableConfig: {
url: '/api/v1/applications/applications/?category=db',
@@ -24,8 +59,7 @@ export default {
},
columnsMeta: {
type_display: {
label: this.$t('applications.type'),
width: '120px'
label: this.$t('applications.type')
},
'attrs.host': {
label: this.$t('applications.host'),
@@ -69,43 +103,37 @@ export default {
hasBulkDelete: true,
createRoute: 'DatabaseAppCreate',
searchConfig: {
exclude: ['category', 'type']
exclude: ['category', 'type'],
options: [
{
value: 'type',
label: this.$t('applications.type'),
children: this.getAppType(appType)
}
]
},
moreCreates: {
callback: (item) => {
vm.$router.push({ name: 'DatabaseAppCreate', query: { type: item.name.toLowerCase() }})
},
dropdown: [
{
name: 'MySQL',
title: 'MySQL',
has: true
},
{
name: 'PostgreSQL',
title: 'PostgreSQL',
has: this.$store.getters.hasValidLicense
},
{
name: 'MariaDB',
title: 'MariaDB',
type: 'primary',
has: this.$store.getters.hasValidLicense
},
{
name: 'Oracle',
title: 'Oracle',
has: this.$store.getters.hasValidLicense
},
{
name: 'SQLServer',
title: 'SQLServer',
has: this.$store.getters.hasValidLicense
}
]
dropdown: appType
}
}
}
},
methods: {
getAppType(arr) {
const searchAppType = []
if (arr.length < 1) return searchAppType
arr.forEach((i) => {
const option = {
value: i.name,
label: i.title
}
searchAppType.push(option)
})
return searchAppType
}
}
}
</script>

View File

@@ -15,12 +15,12 @@ export default {
tableConfig: {
url: '/api/v1/applications/applications/?category=cloud',
columns: [
'name', 'type', 'attrs.cluster',
'name', 'type_display', 'attrs.cluster',
'created_by', 'date_created', 'date_updated', 'comment', 'org_name', 'actions'
],
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'type', 'attrs.cluster', 'comment', 'actions']
default: ['name', 'type_display', 'attrs.cluster', 'comment', 'actions']
},
columnsMeta: {
'attrs.cluster': {
@@ -29,8 +29,8 @@ export default {
comment: {
width: '340px'
},
type: {
width: '140px'
type_display: {
label: this.$t('applications.type')
},
actions: {
prop: 'actions',

View File

@@ -17,17 +17,16 @@ export default {
tableConfig: {
url: '/api/v1/applications/applications/?category=remote_app',
columns: [
'name', 'type', 'attrs.asset',
'name', 'type_display', 'attrs.asset',
'created_by', 'date_created', 'date_updated', 'comment', 'org_name', 'actions'
],
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'type', 'attrs.asset', 'comment', 'actions']
default: ['name', 'type_display', 'attrs.asset', 'comment', 'actions']
},
columnsMeta: {
type: {
displayKey: 'get_type_display',
width: '140px'
type_display: {
label: this.$t('applications.type')
},
'attrs.asset': {
label: this.$t('assets.Assets'),
@@ -67,7 +66,14 @@ export default {
hasImport: false,
// createRoute: 'RemoteAppCreate',
searchConfig: {
exclude: ['category', 'type']
exclude: ['category', 'type'],
options: [
{
value: 'type',
label: this.$t('applications.type'),
children: this.getCreateAppType()
}
]
},
moreCreates: {
dropdown: this.getCreateAppType(),
@@ -85,6 +91,8 @@ export default {
const item = { ...REMOTE_APP_TYPE_META_MAP[value] }
item.can = true
item.has = true
item.value = item.name
item.label = item.title
extraMoreActions.push(item)
}
return extraMoreActions

View File

@@ -88,7 +88,7 @@ export default {
'protocols', 'platform', 'hardware_info', 'model',
'cpu_model', 'cpu_cores', 'cpu_count', 'cpu_vcpus',
'disk_info', 'disk_total', 'memory', 'os', 'os_arch',
'os_version', 'number', 'vendor', 'sn',
'os_version', 'number', 'vendor', 'sn', 'is_active',
'connectivity', 'labels_display',
'created_by', 'date_created', 'comment', 'org_name', 'actions'
],

View File

@@ -20,11 +20,17 @@ export default {
return {
tableConfig: {
url: `/api/v1/assets/cmd-filters/${this.object.id}/rules/`,
columns: ['type', 'content', 'action', 'priority', 'pattern', 'comment', 'actions'],
columns: ['type', 'content', 'ignore_case', 'action', 'priority', 'pattern', 'comment', 'actions'],
columnsMeta: {
type: {
width: '100px'
},
ignore_case: {
width: '100px',
formatterArgs: {
showFalse: false
}
},
priority: {
width: '70px'
},

View File

@@ -28,7 +28,7 @@ export default {
action: 0
},
fields: [
[this.$t('common.Basic'), ['filter', 'type', 'content', 'priority', 'action', 'reviewers', 'comment']]
[this.$t('common.Basic'), ['filter', 'type', 'content', 'ignore_case', 'priority', 'action', 'reviewers', 'comment']]
],
fieldsMeta: {
filter: {

View File

@@ -20,7 +20,7 @@ export default {
},
fields: [
[this.$t('common.Basic'), ['name', 'ip', 'port', 'protocol', 'domain']],
[this.$t('assets.Auth'), ['username', 'password', 'private_key']],
[this.$t('assets.Auth'), ['username', 'password', 'private_key', 'passphrase']],
[this.$t('common.Other'), ['is_active', 'comment']]
],
fieldsMeta: {

View File

@@ -27,7 +27,7 @@ export default {
},
fields: [
[this.$t('common.Basic'), ['name', 'protocol', 'username', 'type']],
[this.$t('common.Auth'), ['password', 'private_key']],
[this.$t('common.Auth'), ['password', 'private_key', 'passphrase']],
[this.$t('common.Command filter'), ['cmd_filters']],
[this.$t('common.Other'), ['priority', 'sftp_root', 'comment']]
],
@@ -63,6 +63,9 @@ export default {
}
}
},
passphrase: {
component: UpdateToken
},
private_key: {
component: UploadKey
},

View File

@@ -25,8 +25,8 @@ export default {
auto_push: false
},
fields: [
[this.$t('common.Basic'), ['name', 'login_mode', 'username', 'priority', 'protocol']],
[this.$t('common.Auth'), ['password']],
[this.$t('common.Basic'), ['name', 'username', 'priority', 'protocol']],
[this.$t('common.Auth'), ['login_mode', 'password']],
[this.$t('common.Command filter'), ['cmd_filters']],
[this.$t('common.Other'), ['comment']]
],

View File

@@ -47,6 +47,7 @@ export default {
case 'postgresql':
case 'mariadb':
case 'sqlserver':
case 'redis':
return Database
case 'k8s':
return K8S

View File

@@ -31,7 +31,7 @@ export default {
},
fields: [
[this.$t('common.Basic'), ['name', 'protocol', 'username', 'username_same_with_user']],
[this.$t('common.Auth'), ['login_mode', 'auto_generate_key', 'password', 'private_key']],
[this.$t('common.Auth'), ['login_mode', 'auto_generate_key', 'password', 'private_key', 'passphrase']],
[this.$t('assets.AutoPush'), ['auto_push', 'sudo', 'shell', 'home', 'system_groups']],
[this.$t('common.Command filter'), ['cmd_filters']],
[this.$t('assets.UserSwitch'), ['su_enabled', 'su_from']],
@@ -41,6 +41,7 @@ export default {
login_mode: fields.login_mode,
username: fields.username,
private_key: fields.private_key,
passphrase: fields.passphrase,
username_same_with_user: fields.username_same_with_user,
auto_generate_key: fields.auto_generate_key,
protocol: fields.protocol,

View File

@@ -31,6 +31,8 @@ function getFields() {
this.fieldsMeta.username.rules[0].required = false
} else if (form.username_same_with_user) {
this.fieldsMeta.username.rules[0].required = false
} else if (form.protocol === 'redis') {
this.fieldsMeta.username.rules[0].required = false
} else {
this.fieldsMeta.username.rules[0].required = true
}
@@ -152,7 +154,6 @@ function getFields() {
}
const password = {
helpText: this.$t('assets.PasswordHelpMessage'),
component: UpdateToken,
hidden: form => {
if (form.login_mode !== 'auto' || form.auto_generate_key) {
@@ -164,6 +165,16 @@ function getFields() {
}
}
const passphrase = {
component: UpdateToken,
hidden: (form) => {
if (form.login_mode !== 'auto') {
return true
}
return form.auto_generate_key === true
}
}
const system_groups = {
label: this.$t('assets.LinuxUserAffiliateGroup'),
hidden: (item) => !item.auto_push || item.username_same_with_user,
@@ -184,6 +195,7 @@ function getFields() {
auto_push: auto_push,
update_password: update_password,
password: password,
passphrase: passphrase,
system_groups: system_groups,
type: type
}

View File

@@ -105,7 +105,7 @@ export default {
title: 'MySQL',
type: 'primary',
has: true,
group: this.$t('assets.DatabaseProtocol')
group: this.$t('assets.RDBProtocol')
},
{
name: 'PostgreSQL',
@@ -131,6 +131,13 @@ export default {
type: 'primary',
has: this.$store.getters.hasValidLicense
},
{
name: 'Redis',
title: 'Redis',
type: 'primary',
has: true,
group: this.$t('assets.NoSQLProtocol')
},
{
name: 'K8S',
title: 'K8S',

View File

@@ -18,7 +18,7 @@ export default {
tableConfig: {
url: '/api/v1/audits/login-logs/',
columns: [
'username', 'type', 'backend', 'ip', 'city',
'username', 'type', 'backend_display', 'ip', 'city',
'user_agent', 'mfa', 'reason_display', 'status', 'datetime'
],
columnsMeta: {

View File

@@ -17,7 +17,7 @@ export default {
return {
tableConfig: {
url: '/api/v1/audits/operate-logs/',
columns: ['user', 'action', 'resource_type', 'resource', 'remote_addr', 'datetime'],
columns: ['user', 'action_display', 'resource_type_display', 'resource', 'remote_addr', 'datetime'],
columnsMeta: {
user: {
showOverflowTooltip: true
@@ -35,7 +35,7 @@ export default {
remote_addr: {
width: '140px'
},
action: {
action_display: {
width: '90px'
}
},

View File

@@ -15,10 +15,18 @@ export default {
return {
tableConfig: {
url: '/api/v1/ops/tasks/',
columns: ['name', 'runtimes', 'host_amount', 'is_success', 'date_start', 'time', 'actions'],
columns: [
'name', 'runtimes', 'host_amount', 'is_success',
'date_start', 'time', 'actions'
],
columnsMeta: {
name: {
showOverflowTooltip: true
showOverflowTooltip: true,
formatterArgs: {
getTitle({ row }) {
return row['display_name']
}
}
},
runtimes: {
label: this.$t('ops.runTimes'),

View File

@@ -11,6 +11,7 @@
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import { getDayFuture } from '@/utils/common'
import PermissionFormActionField from '../components/PermissionFormActionField'
export default {
components: {
@@ -29,9 +30,10 @@ export default {
[this.$t('common.Basic'), ['name']],
[this.$t('perms.User'), ['users', 'user_groups']],
[this.$t('assets.Applications'), ['category', 'type', 'applications', 'system_users']],
[this.$t('common.action'), ['actions']],
[this.$t('common.Other'), ['is_active', 'date_start', 'date_expired', 'comment']]
],
url: '/api/v1/perms/application-permissions/',
url: `/api/v1/perms/application-permissions/?category=${this.$route.query.category}&type=${this.$route.query.type}`,
fieldsMeta: {
users: {
el: {
@@ -101,7 +103,9 @@ export default {
label: this.$t('common.dateExpired')
},
actions: {
label: this.$t('perms.Actions')
label: this.$t('perms.Actions'),
component: PermissionFormActionField,
helpText: this.$t('common.actionsTips')
},
is_active: {
type: 'checkbox'

View File

@@ -4,7 +4,7 @@
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import AssetPermissionFormActionField from './components/AssetPermissionFormActionField'
import PermissionFormActionField from '../components/PermissionFormActionField'
import AssetSelect from '@/components/AssetSelect'
import { getDayFuture } from '@/utils/common'
@@ -24,7 +24,6 @@ export default {
return {
initial: {
is_active: true,
actions: ['all', 'connect', 'updownload', 'upload_file', 'download_file'],
date_start: new Date().toISOString(),
date_expired: getDayFuture(36500, new Date()).toISOString(),
nodes: nodesInitial,
@@ -92,7 +91,7 @@ export default {
},
actions: {
label: this.$t('perms.Actions'),
component: AssetPermissionFormActionField,
component: PermissionFormActionField,
helpText: this.$t('common.actionsTips')
},
date_start: {

View File

@@ -70,6 +70,7 @@ export default {
return { label: item.name + '(' + item.username + ')', value: item.id }
}
},
showHasMore: false,
hasObjectsId: this.object.users,
showHasObjects: false,
performAdd: (items) => {

View File

@@ -1,6 +1,6 @@
<template>
<el-tree
:data="data"
:data="iData"
show-checkbox
node-key="id"
:default-expand-all="false"
@@ -13,11 +13,15 @@
<script>
export default {
name: 'AssetPermissionFormActionField',
name: 'PermissionFormActionField',
props: {
value: {
type: Array,
default: () => ['all', 'connect', 'upload_file', 'download_file', 'updownload', 'clipboard_copy_paste', 'clipboard_copy', 'clipboard_paste']
default: () => []
},
choices: {
type: Array,
default: () => []
}
},
data() {
@@ -26,7 +30,7 @@ export default {
children: 'children',
label: 'label'
},
data: [
fullChoicesTreeNodes: [
{
id: 'all',
label: this.$t('perms.all'),
@@ -68,7 +72,34 @@ export default {
]
}
},
computed: {
choicesIDs() {
return this.choices.map((v) => v.value)
},
iData() {
this.$log.debug('choices: ', this.choicesIDs)
const fullTreeNodes = _.cloneDeep(this.fullChoicesTreeNodes)
const treeNodes = this.trimChoicesTreeNodes(fullTreeNodes)
this.$log.debug('choicesTreeNodes: ', treeNodes)
return treeNodes
}
},
methods: {
trimChoicesTreeNodes(treeNodes) {
const newTreeNodes = []
for (const treeNode of treeNodes) {
if (!this.choicesIDs.includes(treeNode.id)) {
continue
}
let children = treeNode.children || []
if (children.length !== 0) {
children = this.trimChoicesTreeNodes(children)
treeNode.children = children
}
newTreeNodes.push(treeNode)
}
return newTreeNodes
},
handleCheckChange(data, obj) {
const checkedKeys = obj.checkedKeys
if (checkedKeys.length !== 0) {

View File

@@ -43,6 +43,7 @@ export const REMOTE_APP = [
]
export const MYSQL = 'mysql'
export const REDIS = 'redis'
export const ORACLE = 'oracle'
export const POSTGRESQL = 'postgresql'
export const MARIADB = 'mariadb'
@@ -56,7 +57,54 @@ export const DATABASE = [
type: 'primary',
category: DATABASE_CATEGORY,
has: true,
group: i18n.t('applications.Database')
group: i18n.t('applications.RDBProtocol')
},
{
name: ORACLE,
title: i18n.t(`applications.applicationsType.${ORACLE}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
},
{
name: POSTGRESQL,
title: i18n.t(`applications.applicationsType.${POSTGRESQL}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
},
{
name: MARIADB,
title: i18n.t(`applications.applicationsType.${MARIADB}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
},
{
name: SQLSERVER,
title: i18n.t(`applications.applicationsType.${SQLSERVER}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: hasLicence
},
{
name: REDIS,
title: i18n.t(`applications.applicationsType.${REDIS}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: true,
group: i18n.t('applications.NoSQLProtocol')
}
]
export const AppPlanDatabase = [
{
name: MYSQL,
title: i18n.t(`applications.applicationsType.${MYSQL}`),
type: 'primary',
category: DATABASE_CATEGORY,
has: true,
group: i18n.t('applications.RDBProtocol')
},
{
name: ORACLE,

View File

@@ -49,7 +49,7 @@ export default {
}
},
columns: [
'expandCol', 'input', 'risk_level', 'user',
'expandCol', 'input', 'risk_level', 'user', 'remote_addr',
'asset', 'system_user', 'session', 'timestamp'
],
extraQuery: {
@@ -76,7 +76,6 @@ export default {
}
},
user: {
width: '140px',
showOverflowTooltip: true
},
asset: {
@@ -210,7 +209,6 @@ export default {
const queryStr = (url.indexOf('?') > -1 ? '&' : '?') + queryUtil.stringify(_query, '=', '&')
const treeUrl = url + queryStr
this.$set(this.treeSetting, 'treeUrl', treeUrl)
this.treeTable.forceRerenderTree()
},
handleDateChange(object) {
this.query = {

View File

@@ -84,7 +84,7 @@ export default {
// 这个页面不去提交auth这些
const removeFields = [
'AUTH_CAS', 'AUTH_OPENID', 'AUTH_WECOM', 'AUTH_DINGTALK',
'AUTH_FEISHU', 'AUTH_RADIUS', 'AUTH_SSO'
'AUTH_FEISHU', 'AUTH_RADIUS', 'AUTH_SSO', 'AUTH_SAML2'
]
for (const i of removeFields) {
delete data[i]

View File

@@ -105,11 +105,17 @@ export default {
settings: {
visible: false,
url: '/api/v1/settings/setting/?category=ldap',
fields: ['AUTH_LDAP_SYNC_IS_PERIODIC', 'AUTH_LDAP_SYNC_INTERVAL', 'AUTH_LDAP_SYNC_CRONTAB'],
fields: ['AUTH_LDAP_SYNC_IS_PERIODIC', 'AUTH_LDAP_SYNC_CRONTAB', 'AUTH_LDAP_SYNC_INTERVAL'],
fieldsMeta: {
AUTH_LDAP_SYNC_IS_PERIODIC: {
type: 'switch'
},
AUTH_LDAP_SYNC_CRONTAB: {
component: CronTab,
helpText: this.$t('xpack.HelpText.CrontabOfCreateUpdatePage')
},
AUTH_LDAP_SYNC_INTERVAL: {
helpText: this.$t('xpack.HelpText.IntervalOfCreateUpdatePage')
}
},
submitMethod: () => 'patch'

View File

@@ -41,6 +41,7 @@ export default {
[
'SECURITY_MFA_AUTH',
'SECURITY_MFA_IN_LOGIN_PAGE',
'SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY',
'SECURITY_LOGIN_CHALLENGE_ENABLED',
'SECURITY_LOGIN_CAPTCHA_ENABLED',
'SECURITY_PASSWORD_EXPIRATION_TIME',

View File

@@ -75,7 +75,7 @@ export default {
key: this.$t('tickets.ApplyFromSession'),
value: this.object.meta.apply_from_session_id,
formatter: function(item, value) {
const to = { name: 'SessionDetail', params: { id: value }}
const to = { name: 'SessionDetail', params: { id: value }, query: { oid: vm.object.org_id }}
return <router-link to={to}>{vm.$t('sessions.session')}</router-link>
}
},

View File

@@ -132,6 +132,10 @@ export default {
{
label: 'SQLServer',
value: 'sqlserver'
},
{
label: 'Redis',
value: 'redis'
}
]
},
@@ -232,10 +236,14 @@ export default {
},
methods: {
performSubmit(validValues) {
const applyCategoryType = validValues.meta.apply_category_type
validValues.meta.apply_category = applyCategoryType && applyCategoryType.length > 0 ? applyCategoryType[0] : ''
validValues.meta.apply_type = applyCategoryType && applyCategoryType.length > 0 ? applyCategoryType[1] : ''
delete validValues.meta['apply_category_type']
const validMeta = validValues.meta
const applyCategoryType = validMeta.apply_category_type
const filter = (len, field) => {
return applyCategoryType && applyCategoryType.length > 0 ? applyCategoryType[len] : validMeta[field]
}
validMeta.apply_category = filter(0, 'apply_category')
validMeta.apply_type = filter(1, 'apply_type')
delete validMeta['apply_category_type']
return this.$axios['post'](`/api/v1/tickets/tickets/open/?type=apply_application`, validValues)
}
}

View File

@@ -40,66 +40,73 @@ export default {
},
computed: {
detailCardItems() {
const obj = this.object || {}
return [
{
key: this.$t('common.Number'),
value: obj.serial_num
},
{
key: this.$t('tickets.status'),
value: this.object.status,
value: obj.status,
formatter: (item, val) => {
return <el-tag type={this.statusMap.type} size='mini'> { this.statusMap.title }</el-tag>
}
},
{
key: this.$t('tickets.type'),
value: this.object.type_display
value: obj.type_display
},
{
key: this.$t('tickets.user'),
value: this.object['applicant_display']
value: obj['applicant_display']
},
{
key: this.$t('tickets.OrgName'),
value: this.object['org_name']
value: obj['org_name']
},
{
key: this.$t('common.dateCreated'),
value: toSafeLocalDateStr(this.object.date_created)
value: toSafeLocalDateStr(obj.date_created)
},
{
key: this.$t('common.Comment'),
value: this.object.comment
value: obj.comment
}
]
},
specialCardItems() {
const meta = this.object.meta || {}
return [
{
key: this.$t('applications.appType'),
value: `${this.object.meta['apply_category_display']} / ${this.object.meta['apply_type_display']} `
value: `${meta['apply_category_display']} / ${meta['apply_type_display']} `
},
{
key: this.$t('applications.appName'),
value: this.object.meta.apply_applications_display.join(', ')
value: meta?.apply_applications_display?.join(', ') || ''
},
{
key: this.$t('tickets.SystemUser'),
value: this.object.meta.apply_system_users_display.join(', ')
value: meta?.apply_system_users_display?.join(', ') || ''
},
{
key: this.$t('common.dateStart'),
value: toSafeLocalDateStr(this.object.meta.apply_date_start)
value: toSafeLocalDateStr(meta.apply_date_start)
},
{
key: this.$t('common.dateExpired'),
value: toSafeLocalDateStr(this.object.meta.apply_date_expired)
value: toSafeLocalDateStr(meta.apply_date_expired)
}
]
},
assignedCardItems() {
const vm = this
const meta = this.object.meta || {}
return [
{
key: this.$t('tickets.PermissionName'),
value: this.object.meta.apply_permission_name,
value: meta.apply_permission_name,
formatter: function(item, value) {
const to = { name: 'ApplicationPermissionDetail', params: { id: vm.object.id }, query: { oid: vm.object.org_id }}
if (vm.object.status === 'closed' && vm.object.state === 'approved') {
@@ -111,19 +118,19 @@ export default {
},
{
key: this.$t('applications.appName'),
value: this.object.meta.apply_applications_display.join(', ')
value: meta?.apply_applications_display?.join(', ') || ''
},
{
key: this.$t('tickets.SystemUser'),
value: this.object.meta.apply_system_users_display.join(', ')
value: meta?.apply_system_users_display?.join(', ') || ''
},
{
key: this.$t('common.dateStart'),
value: toSafeLocalDateStr(this.object.meta.apply_date_start)
value: toSafeLocalDateStr(meta.apply_date_start)
},
{
key: this.$t('common.dateExpired'),
value: toSafeLocalDateStr(this.object.meta.apply_date_expired)
value: toSafeLocalDateStr(meta.apply_date_expired)
}
]
},

View File

@@ -6,7 +6,7 @@
import { GenericCreateUpdatePage } from '@/layout/components'
import Select2 from '@/components/FormFields/Select2'
import { getDaysFuture } from '@/utils/common'
import AssetPermissionFormActionField from '@/views/perms/AssetPermission/components/AssetPermissionFormActionField'
import PermissionFormActionField from '@/views/perms/components/PermissionFormActionField'
export default {
components: {
GenericCreateUpdatePage
@@ -50,7 +50,7 @@ export default {
fieldsMeta: {
apply_actions: {
label: this.$t('perms.Actions'),
component: AssetPermissionFormActionField,
component: PermissionFormActionField,
helpText: this.$t('common.actionsTips')
},
apply_nodes: {

View File

@@ -38,66 +38,73 @@ export default {
},
computed: {
detailCardItems() {
const obj = this.object || {}
return [
{
key: this.$t('common.Number'),
value: obj.serial_num
},
{
key: this.$t('tickets.status'),
value: this.object.state,
value: obj.state,
formatter: (item, val) => {
return <el-tag type={this.statusMap.type} size='mini'> { this.statusMap.title }</el-tag>
}
},
{
key: this.$t('tickets.type'),
value: this.object.type_display
value: obj.type_display
},
{
key: this.$t('tickets.user'),
value: this.object['applicant_display']
value: obj['applicant_display']
},
{
key: this.$t('tickets.OrgName'),
value: this.object.org_name
value: obj.org_name
},
{
key: this.$t('common.dateCreated'),
value: toSafeLocalDateStr(this.object.date_created)
value: toSafeLocalDateStr(obj.date_created)
},
{
key: this.$t('common.Comment'),
value: this.object.comment
value: obj.comment
}
]
},
specialCardItems() {
const meta = this.object.meta || {}
return [
{
key: this.$t('perms.Node'),
value: this.object.meta.apply_nodes_display.join(', ')
value: meta?.apply_nodes_display?.join(', ') || ''
},
{
key: this.$t('tickets.Asset'),
value: this.object.meta.apply_assets_display.join(', ')
value: meta?.apply_assets_display?.join(', ') || ''
},
{
key: this.$t('tickets.SystemUser'),
value: this.object.meta.apply_system_users_display.join(', ')
value: meta?.apply_system_users_display?.join(', ') || ''
},
{
key: this.$t('assets.Action'),
value: forMatAction(this, this.object.meta['apply_actions_display'])
value: forMatAction(this, meta['apply_actions_display'])
},
{
key: this.$t('common.dateStart'),
value: toSafeLocalDateStr(this.object.meta.apply_date_start)
value: toSafeLocalDateStr(meta.apply_date_start)
},
{
key: this.$t('common.dateExpired'),
value: toSafeLocalDateStr(this.object.meta.apply_date_expired)
value: toSafeLocalDateStr(meta.apply_date_expired)
}
]
},
assignedCardItems() {
const vm = this
const meta = this.object.meta || {}
return [
{
key: this.$t('tickets.PermissionName'),
@@ -113,27 +120,27 @@ export default {
},
{
key: this.$t('perms.Node'),
value: this.object.meta.apply_nodes_display.join(', ')
value: meta?.apply_nodes_display?.join(', ') || ''
},
{
key: this.$t('assets.Asset'),
value: this.object.meta.apply_assets_display.join(', ')
value: meta?.apply_assets_display?.join(', ') || ''
},
{
key: this.$t('tickets.SystemUser'),
value: this.object.meta.apply_system_users_display.join(', ')
value: meta?.apply_system_users_display?.join(', ') || ''
},
{
key: this.$t('assets.Action'),
value: forMatAction(this, this.object.meta['apply_actions_display'])
value: forMatAction(this, meta['apply_actions_display'])
},
{
key: this.$t('common.dateStart'),
value: toSafeLocalDateStr(this.object.meta.apply_date_start)
value: toSafeLocalDateStr(meta?.apply_date_start)
},
{
key: this.$t('common.dateExpired'),
value: toSafeLocalDateStr(this.object.meta.apply_date_expired)
value: toSafeLocalDateStr(meta?.apply_date_expired)
}
]
},

View File

@@ -16,17 +16,23 @@ export default {
tableConfig: {
url: '/api/v1/tickets/flows/',
columns: [
'type_display', 'created_by',
'type_display', 'created_by', 'org_name',
'date_created', 'date_updated', 'actions'
],
columnsShow: {
min: ['actions'],
default: [
'type_display', 'created_by',
'type_display', 'created_by', 'org_name',
'date_created', 'date_updated', 'actions'
]
},
columnsMeta: {
org_name: {
formatter: function(row, col, cell) {
var currentOrg = vm.$store.getters.currentOrg
return currentOrg.is_root ? row.org_name : currentOrg.name
}
},
type_display: {
formatter: DetailFormatter,
formatterArgs: {

View File

@@ -50,7 +50,7 @@ export default {
icon: 'fa-gear',
name: 'TicketFlow',
hidden: () => {
return !(vm.$store.getters.currentUserIsSuperAdmin || vm.$store.getters.currentUserIsAdmin)
return !vm.$store.getters.currentUserIsSuperAdmin
}
}
]

View File

@@ -26,6 +26,11 @@ export default {
ticketTableConfig: {
url: this.url,
columns: [
{
prop: 'serial_num',
label: this.$t('common.Number'),
sortable: 'custom'
},
{
prop: 'title',
label: this.$t('tickets.title'),
@@ -119,6 +124,7 @@ export default {
{
value: 'state',
label: this.$t('tickets.action'),
type: 'choice',
children: [
{
default: true,

View File

@@ -9,6 +9,7 @@
</el-col>
<el-col :span="6" :offset="1">
<Steps :object="object" />
<Session :object="object" />
</el-col>
</el-row>
</template>
@@ -17,9 +18,11 @@
import Details from './Details'
import Comments from './Comments'
import Steps from './Steps'
import Session from './Session'
export default {
name: 'GenericTicketDetail',
components: { Steps, Comments, Details },
components: { Steps, Comments, Details, Session },
props: {
object: {
type: Object,

View File

@@ -0,0 +1,156 @@
<template>
<IBox v-if="session.id" v-loading="loading" class="box">
<div slot="header" class="clearfix ibox-title">
<i class="fa fa-rocket" />
{{ $t('sessions.session') }}
</div>
<div class="content">
<el-row class="item">
<el-col>
<span class="item-label">{{ $t('sessions.SessionID') }}</span>
<span class="item-value">{{ session.id }}</span>
</el-col>
<el-col>
<span class="item-label">{{ $t('sessions.TargetResources') }}</span>
<span class="item-value">{{ session.asset }}</span>
</el-col>
<el-col>
<span class="item-label">{{ $t('tickets.SystemUser') }}</span>
<span class="item-value">{{ session.system_user }}</span>
</el-col>
<el-col>
<span class="item-label">{{ $t('sessions.UseProtocol') }}</span>
<span class="item-value">{{ session.protocol }}</span>
</el-col>
<el-col>
<span class="item-label">{{ $t('sessions.remoteAddr') }}</span>
<span class="item-value">{{ session.remote_addr }}</span>
</el-col>
<el-col>
<span class="item-label">{{ $t('sessions.SessionState') }}</span>
<span class="item-value cur-color" :style="{ 'background': session.is_finished ? '#ed5565' : '#1ab394' }" />
</el-col>
</el-row>
</div>
<el-divider />
<div class="bottom-btn">
<el-button
type="danger"
size="small"
:disabled="!session.can_terminate"
@click="onConnect"
>
{{ $t('sessions.terminate') }}
</el-button>
<el-button
type="primary"
size="small"
:disabled="!session.can_join"
@click="onMonitor"
>
{{ $t('sessions.Monitor') }}
</el-button>
</div>
</IBox>
</template>
<script>
import IBox from '@/components/IBox'
export default {
components: { IBox },
props: {
object: {
type: Object,
default: () => {}
}
},
data() {
return {
session: {},
curTimer: null,
loading: false
}
},
created() {
if (this.object.state !== 'open') {
this.init()
}
},
beforeDestroy() {
clearTimeout(this.curTimer)
},
methods: {
init() {
this.loading = true
const url = `/api/v1/tickets/tickets/${this.object.id}/session/`
this.$axios({
url,
method: 'get',
disableFlashErrorMsg: true
}).then(res => {
this.session = res || {}
}).catch(err => {
this.curTimer = setTimeout(() => {
this.init()
}, 1400)
this.$log.debug('error', err)
}).finally(() => {
this.loading = false
})
},
onConnect() {
const url = '/api/v1/terminal/tasks/kill-session-for-ticket/'
const data = [this.session.id] || []
this.$axios.post(url, data).then(res => {
this.$message.success(this.$t('sessions.TerminateTaskSendSuccessMsg'))
this.curTimer = setTimeout(() => {
this.init()
}, 50000)
}).catch(err => {
this.$message.error(err)
})
},
onMonitor() {
const joinUrl = `/luna/monitor/${this.session.id}`
window.open(joinUrl, 'height=600, width=800, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')
}
}
}
</script>
<style lang='scss' scoped>
.box {
margin-top: 15px;
margin-bottom: 15px;
&>>> .el-divider--horizontal {
margin: 10px 0;
}
}
.content {
line-height: 2.5;
font-size: 13px;
color: #676A6C;
.item-label {
font-weight: 700;
}
.item-value {
color: #676A6C;
}
&>>> .el-col {
line-height: 24px;
}
}
.bottom-btn {
text-align: right;
}
.cur-color {
display: inline-block;
width: 12px;
height: 12px;
vertical-align: text-top;
border-radius: 50%;
}
</style>

View File

@@ -10793,6 +10793,13 @@ vue-jest@^3.0.4:
tsconfig "^7.0.0"
vue-template-es2015-compiler "^1.6.0"
vue-json-editor@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/vue-json-editor/-/vue-json-editor-1.4.3.tgz#004c9037ac91f16dd8fc558c9914e5f33a41d71c"
integrity sha512-st9HdXBgCnyEmmfWrZQiKzp4KuYXzmYVUNDn5h6Fa18MrrGS1amnyUFyv7hQFsNBDW27B7BKkdGOqszYT1srAg==
dependencies:
vue "^2.2.6"
vue-loader@^15.7.0:
version "15.9.1"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.1.tgz#bd2ab8f3d281e51d7b81d15390a58424d142243e"
@@ -10850,6 +10857,11 @@ vue@2.6.11:
resolved "https://registry.npm.taobao.org/vue/download/vue-2.6.11.tgz?cache=0&sync_timestamp=1587135947786&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue%2Fdownload%2Fvue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
integrity sha1-dllNh31LEiNEBuhONSdcbVFBJcU=
vue@^2.2.6:
version "2.6.14"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==
vuejs-logger@^1.5.4:
version "1.5.4"
resolved "https://registry.npm.taobao.org/vuejs-logger/download/vuejs-logger-1.5.4.tgz#c8bb12ed29ca90b8087144a44ad852d9bd170c6e"