Merge pull request #1262 from jumpserver/dev

v2.18.0-rc1
This commit is contained in:
Jiangjie.Bai
2022-01-12 20:35:54 +08:00
committed by GitHub
44 changed files with 1178 additions and 124 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

@@ -21,6 +21,9 @@
<el-form-item :label="this.$t('assets.SSHKey')">
<input type="file" @change="onPrivateKeyLoaded">
</el-form-item>
<el-form-item :label="this.$t('assets.Passphrase')">
<el-input v-model="authInfo.passphrase" type="password" />
</el-form-item>
</el-form>
</Dialog>
</template>
@@ -42,7 +45,8 @@ export default {
return {
authInfo: {
password: '',
private_key: ''
private_key: '',
passphrase: ''
}
}
},
@@ -54,6 +58,7 @@ export default {
}
if (this.authInfo.private_key !== '') {
data.private_key = this.authInfo.private_key
data.passphrase = this.authInfo.passphrase
}
this.$axios.patch(
`/api/v1/assets/accounts/${this.account.id}/`,

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

@@ -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

@@ -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

@@ -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": "系统平台",
@@ -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": "桶名称",
@@ -1302,6 +1310,10 @@
"Name": "名称",
"NodeAmount": "节点数量",
"PasswordLength": "密码长度",
"ChangePassword": "更改密码",
"ModifySSHKey": "修改 SSH Key",
"Addressee": "收件人",
"OnlyMailSend": "当前只支持邮件发送",
"PasswordStrategy": "密码策略",
"SecretKeyStrategy": "密钥策略",
"RegularlyPerform": "定期执行",
@@ -1314,6 +1326,15 @@
"TimerPeriod": "定时执行周期",
"Username": "用户名"
},
"AccountBackupPlan": {
"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",
@@ -222,6 +226,8 @@
"ipDomain": "IP(Domain)",
"HostProtocol": "Host Protocol",
"DatabaseProtocol": "Database Protocol",
"RDBProtocol": "RDS Protocol",
"NoSQLProtocol": "NoSQL Protocol",
"OtherProtocol": "Other Protocol",
"PasswordOrToken": "Password / Token"
},
@@ -283,6 +289,7 @@
"Close": "Close",
"Command filter": "Command filter",
"Comment": "Comment",
"Number": "Number",
"Confirm": "Confirm",
"Create": "Create",
"CreatedBy": "Created by",
@@ -765,6 +772,7 @@
"accountName": "Account name",
"active": "active",
"alive": "alive",
"noAlive": "no alive",
"asset": "Asset",
"target": "Target",
"bucket": "Bucket",
@@ -1259,6 +1267,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 +1283,15 @@
"TimerPeriod": "Timer period",
"Username": "Username"
},
"AccountBackupPlan": {
"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

@@ -4,7 +4,7 @@
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
<strong>Copyright</strong> FIT2CLOUD 飞致云 © 2014-{{ curYear }}
</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

@@ -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

@@ -0,0 +1,54 @@
<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', 'types']],
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
[this.$t('common.Other'), ['recipients', '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('perms.Actions'),
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,46 @@
<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}/`
}
}
}
}
}
</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('assets.Assets')
},
{
id: 'application',
label: this.$t('assets.Applications')
}
]
}
]
}
},
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,57 @@
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 = {
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

@@ -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

@@ -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 } from '@/components/FormFields/Select2'
import { Required } from '@/components/DataForm/rules'
var validatorInterval = (rule, value, callback) => {
@@ -103,6 +103,12 @@ function getFields() {
]
}
const passphrase = {
hidden: (formValue) => {
return formValue.is_ssh_key === false
}
}
const asset_password_rules = {
type: 'group',
items: generatePasswordRulesItemsFields('asset')
@@ -114,11 +120,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 +130,8 @@ function getFields() {
}
const recipients = {
label: i18n.t('xpack.ChangeAuthPlan.Addressee'),
helpText: i18n.t('xpack.ChangeAuthPlan.OnlyMailSend'),
el: {
value: [],
ajax: {
@@ -153,10 +157,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 +231,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,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

@@ -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

@@ -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

@@ -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,7 @@ export const DATABASE = [
type: 'primary',
category: DATABASE_CATEGORY,
has: true,
group: i18n.t('applications.Database')
group: i18n.t('applications.RDBProtocol')
},
{
name: ORACLE,
@@ -85,6 +86,14 @@ export const DATABASE = [
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')
}
]

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: {

View File

@@ -219,7 +219,7 @@ export default {
},
mounted() {
let userAllOrgIds = this.$store.state.users.profile['user_all_orgs']
const currentOrgId = this.$store.getters.currentOrg.id
const currentOrgId = this.$store.getters.currentOrg ? this.$store.getters.currentOrg.id : null
userAllOrgIds = userAllOrgIds ? userAllOrgIds.map(i => i.id) : []
if (userAllOrgIds.length > 0) {
if (userAllOrgIds.includes(currentOrgId)) {

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: {
@@ -131,7 +131,7 @@ export default {
},
mounted() {
let userAllOrgIds = this.$store.state.users.profile['user_all_orgs']
const currentOrgId = this.$store.getters.currentOrg.id
const currentOrgId = this.$store.getters.currentOrg ? this.$store.getters.currentOrg.id : null
userAllOrgIds = userAllOrgIds ? userAllOrgIds.map(i => i.id) : []
if (userAllOrgIds.length > 0) {
if (userAllOrgIds.includes(currentOrgId)) {

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

@@ -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'),

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,140 @@
<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('tickets.status') }}</span>
<span class="item-value">
{{ session.is_finished ? $t('sessions.noAlive') : $t('sessions.alive') }}
</span>
</el-col>
<el-col>
<span class="item-label">{{ $t('sessions.target') }}</span>
<span class="item-value">{{ session.asset }}</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.protocol') }}</span>
<span class="item-value">{{ session.protocol }}</span>
</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()
}
},
destroyed() {
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.$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;
}
</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"