Compare commits

..

7 Commits

Author SHA1 Message Date
“huailei000”
ee55cebf0e fix: 修复临时密码提示翻译 2022-04-29 10:53:55 +08:00
ibuler
2e5096f386 perf: 修改 build 2022-04-29 10:53:55 +08:00
feng626
bf57ee8910 fix: 开放platform 权限 2022-04-29 10:38:44 +08:00
feng626
f86f08f2b6 Merge pull request #1713 from jumpserver/pr@v2.21@workbench_orgs
fix: myorgs -> workbench_orgs
2022-04-25 11:36:57 +08:00
feng626
6eb429823d fix: myorgs -> workbench_orgs 2022-04-25 11:34:37 +08:00
feng626
d6be84026c Merge pull request #1711 from jumpserver/pr@v2.21@fix_temporary_password_text
fix: 修改临时密码提示文案
2022-04-25 11:05:26 +08:00
“huailei000”
4a7680ec7a fix: 修改临时密码提示文案 2022-04-25 03:00:15 +00:00
106 changed files with 819 additions and 1266 deletions

View File

@@ -24,9 +24,9 @@ if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
)
app.listen(port, function () {
// debug(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
if (report) {
// debug(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
}
})

View File

@@ -56,7 +56,7 @@ const responseFake = (url, type, respond) => {
url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
type: type || 'get',
response(req, res) {
// debug('request invoke:' + req.path)
console.log('request invoke:' + req.path)
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
}
}

View File

@@ -59,9 +59,9 @@ module.exports = app => {
mockRoutesLength = mockRoutes.mockRoutesLength
mockStartIndex = mockRoutes.mockStartIndex
// debug(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
} catch (error) {
// debug(chalk.redBright(error))
console.log(chalk.redBright(error))
}
}
})

View File

@@ -25,7 +25,6 @@
"axios": "0.21.1",
"axios-retry": "^3.1.9",
"cron-parser": "^4.0.0",
"crypto-js": "^4.1.1",
"deepmerge": "^4.2.2",
"echarts": "^4.7.0",
"element-ui": "2.13.2",
@@ -33,7 +32,6 @@
"install": "^0.13.0",
"jquery": "^3.5.0",
"js-cookie": "2.2.0",
"jsencrypt": "^3.2.1",
"krry-transfer": "^1.7.3",
"less": "^3.10.3",
"less-loader": "^5.0.0",

View File

@@ -68,15 +68,9 @@ export function importLdapUser(data) {
})
}
export function getPublicSettings(isOpen) {
let url
if (isOpen) {
url = '/api/v1/settings/public/open/'
} else {
url = '/api/v1/settings/public/'
}
export function getPublicSettings() {
return request({
url: url,
url: '/api/v1/settings/public/',
method: 'get'
})
}

View File

@@ -1,8 +1,8 @@
<template>
<div>
<UserConfirmDialog
@UserConfirmDone="getAuthInfo"
@UserConfirmCancel="exit"
<MFAVerifyDialog
@MFAVerifyDone="getAuthInfo"
@MFAVerifyCancel="exit"
/>
<Dialog
:title="dialogTitle"
@@ -36,12 +36,12 @@
<script>
import Dialog from '@/components/Dialog'
import UserConfirmDialog from '@/components/UserConfirmDialog'
import MFAVerifyDialog from '@/components/MFAVerifyDialog'
export default {
name: 'ShowSecretInfo',
components: {
Dialog,
UserConfirmDialog
MFAVerifyDialog
},
props: {
account: {
@@ -60,6 +60,9 @@ export default {
showAuthInfo: false
}
},
mounted() {
this.getAuthInfo()
},
methods: {
getAuthInfo() {
const url = `/api/v1/assets/account-secrets/${this.account.id}/`

View File

@@ -31,7 +31,6 @@
<script>
import Dialog from '@/components/Dialog'
import { UpdateToken, UploadKey } from '@/components/FormFields'
import { encryptPassword } from '@/utils/crypto'
export default {
name: 'UpdateSecretInfo',
components: {
@@ -62,10 +61,10 @@ export default {
handleConfirm() {
const data = {}
if (this.authInfo.password !== '') {
data.password = encryptPassword(this.authInfo.password)
data.password = this.authInfo.password
}
if (this.authInfo.private_key !== '') {
data.private_key = encryptPassword(this.authInfo.private_key)
data.private_key = this.authInfo.private_key
if (this.authInfo.passphrase) data.passphrase = this.authInfo.passphrase
}
this.$axios.patch(

View File

@@ -146,7 +146,9 @@ export default {
vm.showUpdateSecretDialog = false
setTimeout(() => {
vm.showUpdateSecretDialog = true
console.log('Show update1: ', vm.showUpdateSecretDialog)
})
console.log('Show update2: ', vm.showUpdateSecretDialog)
}
}
]
@@ -156,8 +158,7 @@ export default {
},
headerActions: {
hasLeftActions: this.hasLeftActions,
hasMoreActions: true,
hasCreate: false,
hasMoreActions: false,
hasImport: false,
hasExport: this.$hasPerm('assets.view_assetaccountsecret'),
exportOptions: {

View File

@@ -12,7 +12,7 @@
<el-link :href="announcement.link" target="_blank" class="link-more">
{{ $t('common.ViewMore') }}
</el-link>
<i class="fa fa-external-link" />
<i class="fa fa-share-square-o" />
</span>
</el-alert>
</template>

View File

@@ -1,8 +1,8 @@
<template>
<div>
<UserConfirmDialog
@UserConfirmDone="getAuthInfo"
@UserConfirmCancel="exit"
<MFAVerifyDialog
@MFAVerifyDone="getAuthInfo"
@MFAVerifyCancel="exit"
/>
<Dialog
:title="dialogTitle"
@@ -33,12 +33,12 @@
<script>
import Dialog from '@/components/Dialog'
import UserConfirmDialog from '@/components/UserConfirmDialog'
import MFAVerifyDialog from '@/components/MFAVerifyDialog'
export default {
name: 'ShowSecretInfo',
components: {
Dialog,
UserConfirmDialog
MFAVerifyDialog
},
props: {
account: {
@@ -57,6 +57,9 @@ export default {
showAuthInfo: false
}
},
mounted() {
this.getAuthInfo()
},
methods: {
getAuthInfo() {
const url = `/api/v1/applications/account-secrets/${this.account.id}/`

View File

@@ -38,10 +38,6 @@ export default {
hasClone: {
type: Boolean,
default: false
},
systemUserDisabled: {
type: Boolean,
default: true
}
},
data() {
@@ -88,7 +84,7 @@ export default {
showOverflowTooltip: true,
formatter: DetailFormatter,
formatterArgs: {
can: this.systemUserDisabled && this.$hasPerm('assets.view_systemuser'),
can: this.$hasPerm('assets.view_systemuser'),
getTitle({ row }) {
return row.systemuser_display
},

View File

@@ -1,7 +1,6 @@
import Vue from 'vue'
import Select2 from '@/components/FormFields/Select2'
import NestedField from '@/components/AutoDataForm/components/NestedField'
import Swicher from '@/components/FormFields/Swicher'
import rules from '@/components/DataForm/rules'
import { assignIfNot } from '@/utils/common'
@@ -46,8 +45,7 @@ export class FormFieldGenerator {
}
break
case 'boolean':
type = ''
field.component = Swicher
type = 'checkbox'
break
case 'nested object':
type = 'nestedField'

View File

@@ -85,9 +85,6 @@ export default {
{ label: i18n.t('common.No'), value: false }
]
}
if (option.value === 'id') {
option.label = 'ID'
}
vm.internalOptions.push(option)
}
},

View File

@@ -82,8 +82,8 @@ export default {
},
methods: {
refreshTree: function() {
// const refreshIconRef = $('#tree-refresh')
// refreshIconRef.click()
const refreshIconRef = $('#tree-refresh')
refreshIconRef.click()
},
editTreeNode: function() {
this.hideRMenu()
@@ -112,17 +112,16 @@ export default {
}
let url = ''
const query = Object.assign({}, this.$route.query)
const objectId = treeNode.meta.data.id
if (treeNode.meta.type === 'node') {
this.currentNode = treeNode
this.currentNodeId = treeNode.meta.data.id
query['node'] = this.currentNodeId
query['asset'] = ''
url = `${this.setting.url}${combinator}node_id=${objectId}&show_current_asset=${show_current_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=${objectId}&show_current_asset=${show_current_asset}`
url = `${this.setting.url}${combinator}asset_id=${treeNode.meta.data.id}&show_current_asset=${show_current_asset}`
}
this.$router.push({ query })
this.$emit('urlChange', url)
@@ -152,12 +151,11 @@ export default {
url,
{ 'value': treeNode.name }
).then(res => {
let assetsAmount = treeNode.meta.data['assetsAmount']
let assetsAmount = treeNode.meta.data.assetsAmount
if (!assetsAmount) {
assetsAmount = 0
}
treeNode.name = treeNode.name + ' (' + assetsAmount + ')'
treeNode.meta.data = res
this.zTree.updateNode(treeNode)
this.$message.success(this.$t('common.updateSuccessMsg'))
}).finally(() => { this.refreshTree() })
@@ -229,7 +227,7 @@ export default {
this.$message.success(this.$t('common.updateSuccessMsg'))
}).catch(error => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
}).finally()
}).finally(() => this.refreshTree())
},
createTreeNode: function() {
this.hideRMenu()
@@ -245,14 +243,13 @@ export default {
id: data['key'],
name: data['value'],
pId: parentNode.id,
isParent: true,
meta: {
data: data,
type: 'node'
data: data
}
}
newNode.checked = this.zTree.getSelectedNodes()[0].checked
this.zTree.addNodes(parentNode, 0, newNode)
// vm.$refs.dataztree.refresh()
const node = this.zTree.getNodeByParam('id', newNode.id, parentNode)
this.currentNodeId = node.meta.data.id || newNode.id
this.zTree.editName(node)

View File

@@ -238,7 +238,7 @@ export default {
updateContabValue(name, value, from) {
this.contabValueObj[name] = value
if (from && from !== name) {
// debug(`来自组件 ${from} 改变了 ${name} ${value}`)
console.log(`来自组件 ${from} 改变了 ${name} ${value}`)
this.changeRadio(name, value)
}
},

View File

@@ -53,7 +53,7 @@ export default {
}
} catch (error) {
this.isShow = false
// debug(error, 'error')
console.log(error, 'error')
}
}
}

View File

@@ -146,7 +146,7 @@ export default {
* - el-form 的 resetFields 不会触发 input & change 事件,无法监听
* - bug1: https://github.com/FEMessage/el-data-table/issues/176#issuecomment-587280825
* - bug2:
* 0. 建议先在监听器 watch.value 里 // debug(v.name, oldV.name)
* 0. 建议先在监听器 watch.value 里 console.log(v.name, oldV.name)
* 1. 打开 basic 示例
* 2. 在 label 为 name 的输入框里输入 1此时 log'1' ''
* 3. 点击 reset 按钮,此时 log 两条数据: '1' '1', '' ''

View File

@@ -97,7 +97,7 @@ export default {
},
handleClick(button) {
const callback = button.callback || function(values, form) {
// debug('Click ', button.title, ': ', values)
// console.log('Click ', button.title, ': ', values)
}
const form = this.$refs['form']
const values = form.getFormValue()

View File

@@ -72,7 +72,7 @@ export default {
},
methods: {
defaultCallback: function(action) {
// debug(action)
// console.log(action)
}
}
}

View File

@@ -1,5 +1,5 @@
<template>
<Password
<password
:value="value"
v-bind="iAttrs"
class="el-input password-input"
@@ -32,8 +32,7 @@ export default {
const defaultAttrs = {
secureLength: 7,
defaultClass: 'el-input__inner',
toggle: true,
showStrengthMeter: false
toggle: true
}
return Object.assign(defaultAttrs, this.attrs)
}

View File

@@ -66,7 +66,6 @@ export default {
data() {
return {
attrs: {
showStrengthMeter: true
}
}
},

View File

@@ -1,9 +1,9 @@
<template>
<div>
<UserConfirmDialog
<MFAVerifyDialog
v-if="mfaDialogShow"
@UserConfirmDone="showExportDialog"
@UserConfirmCancel="handleExportCancel"
@MFAVerifyDone="showExportDialog"
@MFAVerifyCancel="handleExportCancel"
/>
<Dialog
v-if="exportDialogShow"
@@ -31,7 +31,7 @@
<script>
import Dialog from '@/components/Dialog'
import UserConfirmDialog from '@/components/UserConfirmDialog'
import MFAVerifyDialog from '@/components/MFAVerifyDialog'
import { createSourceIdCache } from '@/api/common'
import * as queryUtil from '@/components/DataTable/compenents/el-data-table/utils/query'
@@ -39,7 +39,7 @@ export default {
name: 'ExportDialog',
components: {
Dialog,
UserConfirmDialog
MFAVerifyDialog
},
props: {
selectedRows: {

View File

@@ -83,7 +83,7 @@ export default {
default: () => ''
},
canImportCreate: {
type: [Boolean, Function],
type: Boolean,
default: false
},
canImportUpdate: {
@@ -205,9 +205,8 @@ export default {
}
return this.url.indexOf('?') === -1 ? `${this.url}?${query}` : `${this.url}&${query}`
},
// eslint-disable-next-line handle-callback-err
catchError(error) {
// debug(error)
console.log(error)
},
onSuccess(msg) {
this.errorMsg = ''

View File

@@ -78,7 +78,7 @@ export default {
default: () => []
},
canCreate: {
type: [Boolean, Function],
type: Boolean,
default: false
},
canBulkUpdate: {

View File

@@ -171,10 +171,7 @@ export default {
float: right;
}
.mobile .search.right {
float: none;
}
.mobile .search.right .action-search {
width: 100%;
float: left;
}
.mobile .right-side {
padding-top: 5px;

View File

@@ -1,5 +1,5 @@
export function cleanActions(actions, canDefaults, { selectedRows, reloadTable }) {
// debug('Start clean actions: ', selectedRows.length, reloadTable)
// console.log('Start clean actions: ', selectedRows.length, reloadTable)
const cleanedActions = []
const cloneActions = _.cloneDeep(actions)
cloneActions.forEach((action) => {

View File

@@ -84,22 +84,15 @@ export default {
extraQuery: this.extraQuery
})
const formatterArgs = {
'columnsMeta.actions.formatterArgs.canUpdate': () => {
return this.hasActionPerm('change') && !this.currentOrgIsRoot
},
'columnsMeta.actions.formatterArgs.canUpdate': 'change',
'columnsMeta.actions.formatterArgs.canDelete': 'delete',
'columnsMeta.actions.formatterArgs.canClone': () => {
return this.hasActionPerm('add') && !this.currentOrgIsRoot
},
'columnsMeta.actions.formatterArgs.canClone': 'add',
'columnsMeta.name.formatterArgs.can': 'view'
}
for (const [arg, action] of Object.entries(formatterArgs)) {
const notSet = _.get(config, arg) === undefined
const isFunction = typeof action === 'function'
if (notSet) {
const hasActionPerm = isFunction ? action() : this.hasActionPerm(action)
_.set(config, arg, hasActionPerm)
_.set(config, arg, this.hasActionPerm(action))
}
}
this.$log.debug('Header actions', this.headerActions)

View File

@@ -0,0 +1,77 @@
<template>
<Dialog
:title="$t('common.MFAVerify')"
:width="'50'"
:show-confirm="false"
:show-cancel="false"
:visible.sync="visible"
:destroy-on-close="true"
v-bind="$attrs"
v-on="$listeners"
>
<el-row :gutter="20">
<el-col :md="4" :sm="24">
<div style="line-height: 34px;text-align: center">MFA</div>
</el-col>
<el-col :md="14" :sm="24">
<el-input v-model="MFAToken" />
<span class="help-tips help-block">{{ $t('common.MFARequireForSecurity') }}</span>
</el-col>
<el-col :md="4" :sm="24">
<el-button size="mini" type="primary" style="line-height:20px " @click="verifyMFA">
{{ this.$t('common.Confirm') }}
</el-button>
</el-col>
</el-row>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
export default {
name: 'MFAVerifyDialog',
components: {
Dialog
},
data() {
return {
MFAToken: '',
visible: false
}
},
watch: {
visible(val) {
if (!val) {
this.$emit('MFAVerifyCancel', true)
}
}
},
mounted() {
this.$axios.get('/api/v1/authentication/otp/verify/', { disableFlashErrorMsg: true }).then(() => {
this.$emit('MFAVerifyDone', true)
}).catch(err => {
this.$log.debug('Verify otp code error: ', err)
this.visible = true
})
},
methods: {
verifyMFA() {
if (this.MFAToken.length !== 6) {
return this.$message.error(this.$tc('common.MFAErrorMsg'))
}
this.$axios.post(
`/api/v1/authentication/otp/verify/`, {
code: this.MFAToken
}
).then(res => {
this.$emit('MFAVerifyDone', true)
})
}
}
}
</script>
<style scoped>
</style>

View File

@@ -61,6 +61,10 @@ const defaultDeleteCallback = function({ row, col, cellValue, reload }) {
done()
reload()
this.$message.success(this.$t('common.deleteSuccessMsg'))
} catch (error) {
if (!error.response || !error.response.data || !error.response.data.msg) {
this.$message.error(this.$t('common.deleteErrorMsg') + ' ' + error)
}
} finally {
instance.confirmButtonLoading = false
}

View File

@@ -17,7 +17,7 @@ export default {
}
// const locale = this.$i18n.locale
// const value = dt.toLocaleString(locale, { hourCycle: 'h23' })
// debug(this.$i18n.locale)
// console.log(this.$i18n.locale)
return {
value: value
}

View File

@@ -106,9 +106,35 @@ export default {
handler(val) {
if (val && val.length > 0) {
const routeFilter = this.checkInTableColumns()
this.filterTagSearch(routeFilter)
const routerSearch = routeFilter.search || {}
let routerSearchAttrs = []
if (typeof routerSearch?.value === 'string') {
routerSearchAttrs = routerSearch?.value?.split(',') || []
}
for (const attr of routerSearchAttrs) {
routeFilter[`search_${attr}`] = {
...routerSearch,
value: attr
}
}
if (routerSearchAttrs.length !== 0) {
delete routeFilter.search
}
const asFilterTags = _.cloneDeep(this.filterTags)
this.filterTags = {
...asFilterTags,
...routeFilter
}
if (Object.keys(routeFilter).length > 0) {
setTimeout(() => {
return this.$emit('tagSearch', this.filterMaps)
}, 490)
}
}
},
immediate: true,
deep: true
}
},
@@ -123,90 +149,43 @@ export default {
methods: {
// 获取url中的查询条件判断是不是包含在当前查询条件里
checkInTableColumns() {
const searchFieldOptions = {}
const queryInfoValues = this.options.map((i) => i.value)
const routeQuery = this.getUrlQuery ? this.$route?.query : {}
const routeQueryKeysLength = Object.keys(routeQuery).length
if (routeQueryKeysLength < 1) return searchFieldOptions
for (const [key, value] of Object.entries(routeQuery)) {
const valueDecode = decodeURI(value)
const isSearch = key === 'search'
if (isSearch) {
searchFieldOptions[key] = {
key,
label: '',
value: valueDecode
}
continue
}
if (queryInfoValues.includes(key)) {
searchFieldOptions[key] = this.getInQueryInfoFields(key, value)
}
const routeQueryKeys = Object.keys(routeQuery)
const routeQueryKeysLength = routeQueryKeys.length
const keys = {}
if (routeQueryKeysLength < 1) {
return keys
}
return searchFieldOptions
},
getInQueryInfoFields(key, value) {
let searchFieldOption = {}
let valueDecode = decodeURI(value)
const currentOptions = this.options || []
for (const [key, value] of Object.entries(routeQuery)) {
let valueDecode = decodeURI(value)
const isSearch = key !== 'search'
const curOptions = this.options || []
for (let k = 0, len = currentOptions.length; k < len; k++) {
const current = currentOptions[k]
if (key === current.value) {
const curChildren = current.children || []
if (current?.type === 'boolean') {
for (let k = 0, len = curOptions.length; k < len; k++) {
const cur = curOptions[k]
if (cur?.type === 'boolean') {
valueDecode = !!valueDecode
}
searchFieldOption = {
...current,
key,
label: current.label,
value: valueDecode
}
if (curChildren.length > 0) {
for (const item of curChildren) {
if (valueDecode === item.value) {
searchFieldOption.valueLabel = item.label
break
if (key === cur.value || !isSearch) {
const curChildren = cur.children || []
keys[key] = {
...cur,
key,
label: isSearch ? cur.label : '',
value: valueDecode
}
if (isSearch && curChildren.length > 0) {
for (const item of curChildren) {
if (valueDecode === item.value) {
keys[key].valueLabel = item.label
break
}
}
}
}
break
}
}
return searchFieldOption
},
filterTagSearch(routeFilter) {
const routerSearch = routeFilter.search || {}
let routerSearchAttrs = []
if (typeof routerSearch?.value === 'string') {
routerSearchAttrs = routerSearch?.value?.split(',') || []
}
for (const attr of routerSearchAttrs) {
routeFilter[`search_${attr}`] = {
...routerSearch,
value: attr
}
}
if (routerSearchAttrs.length !== 0) {
delete routeFilter.search
}
const asFilterTags = _.cloneDeep(this.filterTags)
this.filterTags = {
...asFilterTags,
...routeFilter
}
if (Object.keys(routeFilter).length > 0) {
setTimeout(() => {
return this.$emit('tagSearch', this.filterMaps)
}, 490)
}
return keys
},
getValueLabel(key, value) {
for (const field of this.options) {

View File

@@ -77,7 +77,7 @@ export default {
}
},
mounted() {
// debug(this.treeSetting)
// console.log(this.treeSetting)
},
methods: {
handleUrlChange(url) {

View File

@@ -1,105 +0,0 @@
<template>
<Dialog
:title="$t('common.CurrentUserVerify')"
:width="'50'"
:show-confirm="false"
:show-cancel="false"
:visible.sync="visible"
:destroy-on-close="true"
v-bind="$attrs"
v-on="$listeners"
>
<el-row :gutter="20">
<el-col :md="4" :sm="24">
<div style="line-height: 34px;text-align: center">{{ Label }}</div>
</el-col>
<el-col :md="14" :sm="24">
<el-input v-model="SecretKey" :show-password="showPassword" />
<span class="help-tips help-block">{{ HelpText }}</span>
</el-col>
<el-col :md="4" :sm="24">
<el-button size="mini" type="primary" style="line-height:20px " @click="userConfirm">
{{ this.$t('common.Confirm') }}
</el-button>
</el-col>
</el-row>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
export default {
name: 'UserConfirmDialog',
components: {
Dialog
},
data() {
return {
Label: '',
HelpText: '',
ConfirmType: '',
SecretKey: '',
visible: false
}
},
computed: {
showPassword() {
if (this.ConfirmType === 'password') {
return true
}
return false
}
},
watch: {
visible(val) {
if (!val) {
this.$emit('UserConfirmCancel', true)
}
}
},
mounted() {
this.$axios.get('/api/v1/authentication/confirm/', { disableFlashErrorMsg: true }).then(() => {
this.$emit('UserConfirmDone', true)
}).catch(err => {
this.$log.debug('Verify otp code error: ', err)
const backends = err.response.data.backends
backends.sort((a, b) => b.level - a.level)
this.ConfirmType = backends[0].name
if (this.ConfirmType === 'relogin') {
this.visible = false
return this.$message.error(this.$t('auth.ReLoginErr'))
}
if (this.ConfirmType === 'mfa') {
this.Label = 'MFA'
this.HelpText = this.$t('common.MFARequireForSecurity')
this.visible = true
} else if (this.ConfirmType === 'password') {
this.Label = this.$t('setting.password')
this.HelpText = this.$t('common.PasswordRequireForSecurity')
this.visible = true
}
})
},
methods: {
userConfirm() {
if (this.ConfirmType === 'mfa' && this.SecretKey.length !== 6) {
return this.$message.error(this.$tc('common.MFAErrorMsg'))
}
this.$axios.post(
`/api/v1/authentication/confirm/`, {
confirm_type: this.ConfirmType,
secret_key: this.SecretKey
}
).then(res => {
this.$emit('UserConfirmDone', true)
})
}
}
}
</script>
<style scoped>
</style>

View File

@@ -25,6 +25,6 @@ export { default as UploadField } from './FormFields/UploadField'
export { default as AccountListTable } from './AccountListTable/index'
export { default as AppAccountListTable } from './AppAccountListTable'
export { default as AssetRelationCard } from './AssetRelationCard'
export { default as UserConfirmDialog } from './UserConfirmDialog'
export { default as MFAVerifyDialog } from './MFAVerifyDialog'
export { default as Announcement } from './Announcement'
export { default as CronTab } from './CronTab'

View File

@@ -18,7 +18,7 @@ router.beforeEach(async(to, from, next) => {
next()
} catch (e) {
const msg = 'Start service error: ' + e
// debug(e)
console.log(e)
}
})

View File

@@ -246,12 +246,9 @@
},
"auth": {
"LoginRequiredMsg": "You account has logout, Please login again",
"ReLogin": "Re-Login",
"ReLoginErr": "Login time has exceeded 5 minutes, please login again"
"ReLogin": "Re-Login"
},
"common": {
"ChangeViewHelpText": "Click to change view",
"Component": "component",
"PrivateCloud": "Private cloud",
"PublicCloud": "Public cloud",
"Correlation": "Correlation",
@@ -270,7 +267,6 @@
"Database": "Database",
"Params": "Params",
"MFAVerify": "Verify MFA",
"CurrentUserVerify": "Verify Current User",
"ViewSecret": "View secret",
"ConnectWebSocketError": "Connect Websocket failed",
"Nothing": "Nothing",
@@ -417,7 +413,7 @@
"downloadUpdateTemplateMsg": "Download update template",
"onlyCSVFilesTips": "Only csv supported",
"updateSuccessMsg": "Update success, total: {count}",
"dragUploadFileInfo": "Drag file here or click here to upload",
"dragUploadFileInfo": "Drag file here or click to upload",
"uploadCsvLth10MHelpText": "csv/xlsx files with a size less than 10M",
"hasImportErrorItemMsg": "There is an error item, click the x icon to view the details, and continue to import after editing"
},
@@ -437,7 +433,6 @@
"Logout": "Logout",
"Profile": "Profile",
"Support": "Support",
"Download": "Download",
"UserPage": "User page",
"View": "View",
"EnterpriseEdition": "Enterprise edition"
@@ -696,7 +691,7 @@
"AssetPermissionUpdate": "Asset permissions update",
"AssetUpdate": "Asset update",
"Assets": "Assets",
"LogsAudit": "Logs audit",
"LogsAudits": "Logs audit",
"SessionsAudit": "Sessions audit",
"SessionList": "Session list",
"BatchCommand": "Batch Command",
@@ -893,12 +888,12 @@
"UseProtocol": "Use protocol",
"SessionState": "Session state",
"Monitor": "Monitor",
"RazorNotSupport": "RDP Client session not support now",
"XRDPNotSupport": "RDP Client session not support now",
"sessionMonitor": "Session Monitor",
"TerminateTaskSendSuccessMsg": "Terminate task has been send, Please check later",
"helpText": {
"esUrl": "Tip: If you have multiple hosts, use comma (, ) to split (eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com)",
"esIndex":"Es provides the default index: jumpserver. If you choose to build an index by date, this blank is the index prefix",
"esIndex":"Es provides the default index: jumpserver",
"esDocType": "Es provides the default document type: command"
}
},
@@ -1075,7 +1070,6 @@
"refreshLdapCache":"Refreshing Ldap cache ",
"LicenseExpired": "License expired",
"LicenseWillBe": "License will expire at ",
"LicenseReachedAssetAmountLimit": "The number of assets has exceeded the license limit",
"Expire": "Expire",
"WeCom": "WeCom",
"DingTalk": "DingTalk",
@@ -1383,7 +1377,6 @@
"HuaweiPrivatecloud": "Huawei Private Cloud",
"OpenStack": "OpenStack",
"GCP": "Google Cloud Platform",
"FC": "Fusion Compute",
"AWS_China": "AWS(China)",
"AWS_Int": "AWS(International)",
"HuaweiCloud": "Huawei Cloud",

View File

@@ -251,12 +251,9 @@
},
"auth": {
"LoginRequiredMsg": "アカウントが終了しました。ログインし直してください",
"ReLogin": "再ログイン",
"ReLoginErr": "ログイン時間が 5 分を超えました。もう一度ログインしてください"
"ReLogin": "再ログイン"
},
"common": {
"ChangeViewHelpText": "クリックしてさまざまなビューにアクセス",
"Component": "コンポーネント",
"PrivateCloud": "プライベートクラウド",
"PublicCloud": "パブリッククラウド",
"Correlation": "関連",
@@ -278,7 +275,6 @@
"DateUpdated": "更新日",
"ApprovaLevel": "承認情報",
"MFAVerify": "MFAの検証",
"CurrentUserVerify": "現在のユーザー検証",
"ViewSecret": "パスワードの確認",
"ConnectWebSocketError": "Webソケット接続に失敗しました",
"Action": "アクション",
@@ -429,7 +425,7 @@
"onlyCSVFilesTips": "Csvファイルのインポートのみサポート",
"updateSuccessMsg": "更新のインポートに成功しました。合計:{count}",
"uploadCsvLth10MHelpText": "Csv/xlsxのみアップロードでき、10m以下です",
"dragUploadFileInfo": "ここにファイルをドラッグするか、ここをクリックしてアップロードしてください",
"dragUploadFileInfo": "ファイルをここにドラッグするか、アップロードをクリックします",
"hasImportErrorItemMsg": "インポート失敗項目があります。左側のxをクリックして失敗原因を確認し、表をクリックして編集した後、失敗項目のインポートを続けることができます"
},
"fileType": "ファイルタイプ",
@@ -449,7 +445,6 @@
"Logout": "ログインを終了する",
"Profile": "個人情報",
"Support": "サポート",
"Download": "ダウンロード",
"UserPage": "ユーザービュー",
"View": "ビュー",
"EnterpriseEdition": "企業版"
@@ -914,12 +909,12 @@
"UseProtocol": "使用契約",
"SessionState": "セッションステータス",
"Monitor": "モニタリング",
"RazorNotSupport": "RDPクライアントセッションは、監視をサポートしていません",
"XRDPNotSupport": "RDPクライアントセッションは、監視をサポートしていません",
"sessionMonitor": "モニタリング",
"TerminateTaskSendSuccessMsg": "最終タスクが発行されました。後で更新して確認してください。",
"helpText": {
"esUrl": "ヒント: 複数のホストがある場合は、カンマ ( , ) で分割します。 (Eg: http://www.jumpserver.a.com:3000、http://www.jumpserver.b.com:3000)",
"esIndex": "Esはデフォルトindexを提供します。日付による索引の作成を選択した場合、この空は索引接頭辞です。",
"esIndex": "Esはデフォルトindexを提供します。",
"esDocType": "Esデフォルトのドキュメントタイプ: command"
}
},
@@ -1103,7 +1098,6 @@
"refreshLdapCache": "Ldapキャッシュを更新します。後でお願いします。",
"LicenseExpired": "ライセンスが期限切れです",
"LicenseWillBe": "ライセンスはまもなく",
"LicenseReachedAssetAmountLimit": "アセットの数がライセンス制限を超えました",
"Expire": "期限切れ",
"WeCom": "企業wechat",
"DingTalk": "ホッチキス",
@@ -1426,7 +1420,6 @@
"HuaweiPrivatecloud": "ファーウェイプライベートクラウド",
"OpenStack": "OpenStack",
"GCP": "Googleクラウド",
"FC": "Fusion Compute",
"AWS_China": "AWS(中国)",
"AWS_Int": "AWS (国際)",
"HuaweiCloud": "ファーウェイ雲",

View File

@@ -251,11 +251,9 @@
},
"auth": {
"LoginRequiredMsg": "账号已退出,请重新登录",
"ReLogin": "重新登录",
"ReLoginErr": "登录时长已超过 5 分钟,请重新登录"
"ReLogin": "重新登录"
},
"common": {
"ChangeViewHelpText": "点击切换不同视图",
"Component": "组件",
"PrivateCloud": "私有云",
"PublicCloud": "公有云",
@@ -278,7 +276,6 @@
"DateUpdated": "更新日期",
"ApprovaLevel": "审批信息",
"MFAVerify": "验证 MFA",
"CurrentUserVerify": "验证当前用户",
"ViewSecret": "查看密码",
"ConnectWebSocketError": "连接 WebSocket 失败",
"Action": "动作",
@@ -429,7 +426,7 @@
"onlyCSVFilesTips": "仅支持csv文件导入",
"updateSuccessMsg": "导入更新成功,总共:{count}",
"uploadCsvLth10MHelpText": "只能上传 csv/xlsx, 且不超过 10M",
"dragUploadFileInfo": "将文件拖到此处,或点击此处上传",
"dragUploadFileInfo": "将文件拖到此处,或点击上传",
"hasImportErrorItemMsg": "存在导入失败项,点击左侧 x 查看失败原因,点击表格编辑后,可以继续导入失败项"
},
"fileType": "文件类型",
@@ -449,7 +446,6 @@
"Logout": "退出登录",
"Profile": "个人信息",
"Support": "支持",
"Download": "下载",
"UserPage": "用户视图",
"View": "视图",
"EnterpriseEdition": "企业版"
@@ -914,13 +910,13 @@
"UseProtocol": "使用协议",
"SessionState": "会话状态",
"Monitor": "监控",
"RazorNotSupport": "RDP 客户端会话, 暂不支持监控",
"XRDPNotSupport": "RDP 客户端会话, 暂不支持监控",
"sessionMonitor": "监控",
"TerminateTaskSendSuccessMsg": "终断任务已下发,请稍后刷新查看",
"helpText": {
"esUrl": "提示:如果有多台主机,请使用逗号 ( , ) 进行分割。eg: http://www.jumpserver.a.com:3000,http://www.jumpserver.b.com:3000",
"esIndex": "es 提供默认 indexjumpserver。如果开启按日期建立索引,那么输入的值会作为索引前缀",
"esDocType": "es 默认文档类型command"
"esIndex": "es提供默认indexjumpserver",
"esDocType": "es默认文档类型command"
}
},
"setting": {
@@ -1103,7 +1099,6 @@
"refreshLdapCache":"刷新Ldap缓存请稍后",
"LicenseExpired": "许可证已经过期",
"LicenseWillBe": "许可证即将在 ",
"LicenseReachedAssetAmountLimit": "资产数量已经超过许可证数量限制",
"Expire": " 过期",
"WeCom": "企业微信",
"DingTalk": "钉钉",
@@ -1426,7 +1421,6 @@
"HuaweiPrivatecloud": "华为私有云",
"OpenStack": "OpenStack",
"GCP": "谷歌云",
"FC": "Fusion Compute",
"AWS_China": "AWS(中国)",
"AWS_Int": "AWS(国际)",
"HuaweiCloud": "华为云",

View File

@@ -1,9 +1,11 @@
<template>
<div class="footer" :class="device" :style="style">
<div class="pull-right version">
<div class="footer" :style="style">
<div class="pull-right">
Version <strong> dev </strong> <span v-if="!publicSettings.XPACK_LICENSE_IS_VALID"> GPLv2. </span>
</div>
<div>{{ corporation }}</div>
<div style="padding-left:20px;">
{{ publicSettings.XPACK_LICENSE_INFO.corporation }}
</div>
</div>
</template>
<script>
@@ -25,10 +27,7 @@ export default {
if (this.device === 'mobile') {
return ''
}
return this.sidebar.opened ? ('margin-left: 220px;') : ('margin-left: 54px')
},
corporation() {
return this.publicSettings.XPACK_LICENSE_INFO.corporation
return this.sidebar.opened ? ('margin-left: 210px;') : ('margin-left: 54px')
}
}
}
@@ -39,9 +38,9 @@ export default {
height: 35px !important;
}
.pull-right {
float: right
float: right!important;
}
.footer {
.footer{
position: fixed;
bottom: 0;
left: 0;
@@ -56,11 +55,4 @@ export default {
font-size: 13px;
}
}
.mobile.footer {
text-align: center;
}
.mobile.footer .pull-right{
float: none;
display: block;
}
</style>

View File

@@ -16,8 +16,6 @@
<script>
import AutoDataForm from '@/components/AutoDataForm'
import { getUpdateObjURL } from '@/utils/common'
import { encryptPassword } from '@/utils/crypto'
export default {
name: 'GenericCreateUpdateForm',
components: {
@@ -138,16 +136,6 @@ export default {
if (params.id) {
url = getUpdateObjURL(url, params.id)
}
const clone_from = this.$route.query['clone_from']
const query = clone_from ? `clone_from=${clone_from}` : ''
if (query) {
if (url.indexOf('?') === -1) {
url = `${url}?${query}`
} else {
url = `${url}&${query}`
}
}
return url
}
},
@@ -230,10 +218,6 @@ export default {
hasDetailInMsg: {
type: Boolean,
default: true
},
encryptedFields: {
type: Array,
default: () => ['password', 'token', 'private_key']
}
},
data() {
@@ -281,27 +265,10 @@ export default {
isUpdateMethod() {
return ['put', 'patch'].indexOf(this.method.toLowerCase()) > -1
},
encryptFields(values) {
// 批量提交clean 后可能是个数组
if (values instanceof Array) {
return values.map((item) => this.encryptFields(item))
}
values = { ...values }
for (const field of this.encryptedFields) {
let value = values[field]
if (!value || typeof value !== 'string') {
continue
}
value = encryptPassword(value)
values[field] = value
}
return values
},
handleSubmit(values, formName, addContinue) {
let handler = this.onSubmit || this.defaultOnSubmit
handler = handler.bind(this)
values = this.cleanFormValue(values)
values = this.encryptFields(values)
return handler(values, formName, addContinue)
},
defaultOnSubmit(validValues, formName, addContinue) {

View File

@@ -98,11 +98,11 @@ export default {
const vm = this
return {
submitMethod: () => 'patch',
cleanFormValue: (value) => {
cleanFormValue: function(value) {
const filterValue = {}
Object.keys(value)
.filter((key) => vm.checkedFields?.includes(key))
.forEach((key) => { filterValue[key] = value[key] })
Object.keys(value).filter((key) => vm.checkedFields?.includes(key)).forEach((key) => {
filterValue[key] = value[key]
})
const formValue = []
let object = {}
for (const row of vm.selectedRows) {

View File

@@ -6,7 +6,6 @@
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="docs">{{ $t('common.nav.Docs') }}</el-dropdown-item>
<el-dropdown-item command="support">{{ $t('common.nav.Support') }}</el-dropdown-item>
<el-dropdown-item command="toolsDownload">{{ $t('common.nav.Download') }}</el-dropdown-item>
<el-dropdown-item v-if="!hasLicence" command="enterprise">{{ $t('common.nav.EnterpriseEdition') }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
@@ -44,9 +43,6 @@ export default {
case 'enterprise':
window.open('https://jumpserver.org/enterprise.html', '_blank')
break
case 'toolsDownload':
window.open('/core/download/', '_blank')
break
default:
window.open(this.URLSite.HELP_DOCUMENT_URL, '_blank')
break

View File

@@ -1,40 +1,31 @@
<template>
<el-tooltip
v-model="iShowTip"
:manual="true"
:content="tipText"
class="item"
effect="dark"
placement="bottom-start"
<el-menu
:default-active="currentViewRoute.name"
class="menu-main"
:class="mode"
:mode="mode"
@select="handleSelectView"
>
<el-menu
:default-active="currentViewRoute.name"
class="menu-main"
:class="mode"
:mode="mode"
@select="handleSelectView"
<el-submenu
index="2"
popper-class="view-switcher"
>
<el-submenu
index="2"
popper-class="view-switcher"
<template slot="title">
<span class="title-label">
<i class="fa fa-bars" />
<span>{{ $t('common.nav.View') }}</span>
</span>
</template>
<el-menu-item
v-for="view of views"
:key="view.name"
:index="view.name"
>
<template slot="title">
<span class="title-label">
<i class="fa fa-bars" />
<span>{{ $t('common.nav.View') }}</span>
</span>
</template>
<el-menu-item
v-for="view of views"
:key="view.name"
:index="view.name"
>
<i v-if="mode === 'horizontal'" class="icons" :class="view.meta.icon" />
<span slot="title" class="icons-title">{{ view.meta.title }}</span>
</el-menu-item>
</el-submenu>
</el-menu>
</el-tooltip>
<i v-if="mode === 'horizontal'" class="icons" :class="view.meta.icon" />
<span slot="title" class="icons-title">{{ view.meta.title }}</span>
</el-menu-item>
</el-submenu>
</el-menu>
</template>
<script>
@@ -55,7 +46,6 @@ export default {
},
data() {
return {
tipText: this.$t('common.ChangeViewHelpText'),
showTip: true
}
},
@@ -82,36 +72,6 @@ export default {
},
currentView() {
return this.viewsMapper[this.currentViewRoute.name]
},
tipHasRead: {
set(val) {
localStorage.setItem('viewSwitcherTip', val)
},
get() {
return localStorage.getItem('viewSwitcherTip')
}
},
iShowTip: {
get() {
if (this.mode !== 'horizontal') {
return false
}
if (this.views.length < 2) {
return false
}
if (this.tipHasRead) {
return false
}
return this.showTip
},
set(val) {
this.showTip = val
}
}
},
created() {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile/i.test(navigator.userAgent)) {
this.showTip = false
}
},
methods: {
@@ -120,10 +80,6 @@ export default {
localStorage.setItem('PreView', key)
// Next 之前要重置 init 状态,否则这些路由守卫就不走了
await store.dispatch('app/reset')
if (!this.tipHasRead) {
this.tipHasRead = '1'
this.iShowTip = false
}
this.$router.push(routeName)
}
}
@@ -153,6 +109,7 @@ export default {
padding: 10px 10px;
text-align: center;
height: 70px;
width: 70px;
&:hover {
color: inherit;
i {
@@ -160,10 +117,10 @@ export default {
}
}
&:first-child {
margin-left: 16px;
margin-left: 20px;
}
&:last-child {
margin-right: 16px;
margin-right: 20px;
}
}
.el-submenu.is-opened {

View File

@@ -16,7 +16,7 @@
<SystemSetting />
</el-tooltip>
</li>
<li v-if="ticketsEnabled" class="header-item header-hover">
<li v-if="this.$hasLicense() && this.$hasPerm('tickets.view_ticket')" class="header-item header-hover">
<Tickets />
</li>
<li class="header-item active-menu">
@@ -69,12 +69,7 @@ export default {
computed: {
...mapGetters([
'sidebar', 'publicSettings', 'currentOrgRoles', 'currentViewRoute'
]),
ticketsEnabled() {
return this.publicSettings['TICKETS_ENABLED'] &&
this.$hasLicense() &&
this.$hasPerm('tickets.view_ticket')
}
])
},
methods: {
toggleSideBar() {

View File

@@ -6,7 +6,7 @@
</div>
<div class="active-mobile">
<ViewSwitcher mode="vertical" class="mobile-view-switch" />
<Organization v-if="$hasLicense()" class="organization" />
<Organization class="organization" />
</div>
<div class="nav-title" :class="{'collapsed': isCollapse}">
{{ isTitle }}

View File

@@ -1,10 +1,7 @@
<template>
<div v-if="!loading">
<el-alert v-if="licenseMsg" type="error">
{{ licenseMsg }} !
<router-link :to="{ name: 'License' }" style="padding-left: 5px">
{{ $t('common.View') }} <i class="fa fa-external-link" />
</router-link>
<el-alert v-if="isExpire" type="error">
{{ isExpire }}
</el-alert>
</div>
</template>
@@ -14,7 +11,7 @@ import { toSafeLocalDateStr } from '@/utils/common'
import { mapGetters } from 'vuex'
export default {
name: 'LicenseRelatedTip',
name: 'LicenseExpireTip',
data() {
return {
loading: true,
@@ -26,15 +23,8 @@ export default {
'publicSettings',
'currentUser'
]),
licenseMsg() {
if (this.isExpire) {
return this.isExpire
} else {
return this.reachedAssetAmountLimit
}
},
isExpire() {
if (!this.publicSettings.XPACK_ENABLED || !this.$hasPerm('settings.change_license')) {
if (!this.publicSettings.XPACK_ENABLED || this.currentUser.role !== 'Admin') {
return false
}
const intervalDays = this.getIntervalDays(this.licenseData.date_expired)
@@ -45,19 +35,10 @@ export default {
return this.$t('setting.LicenseWillBe') + this.licenseData.date_expired + this.$t('setting.Expire')
}
return false
},
reachedAssetAmountLimit() {
if (!this.publicSettings.XPACK_ENABLED || !this.$hasPerm('settings.change_license')) {
return false
}
if (this.licenseData['current_asset_count'] > this.licenseData.asset_count) {
return this.$t('setting.LicenseReachedAssetAmountLimit')
}
return false
}
},
mounted() {
if (this.publicSettings.XPACK_ENABLED && this.$hasPerm('settings.change_license')) {
if (this.publicSettings.XPACK_ENABLED && this.currentUser.role === 'Admin') {
this.$axios.get('/api/v1/xpack/license/detail').then(res => {
this.licenseData = res
}).finally(() => {

View File

@@ -1,7 +1,7 @@
<template>
<div>
<slot name="globalNotification">
<LicenseRelatedTip />
<LicenseExpireTip />
<PasswordExpireTip />
</slot>
<div class="page-heading">
@@ -21,12 +21,12 @@
</template>
<script>
import LicenseRelatedTip from '@/layout/components/Page/LicenseRelatedTip'
import LicenseExpireTip from '@/layout/components/Page/LicenseExpireTip'
import PasswordExpireTip from '@/layout/components/Page/PasswordExpireTip'
export default {
name: 'PageHeading',
components: {
LicenseRelatedTip,
LicenseExpireTip,
PasswordExpireTip
},
props: {

View File

@@ -126,7 +126,7 @@ function cleanRoute(tmp, parent) {
} else {
tmp.meta.fullPath = parentFullPath ? parentFullPath + '/' + tmp.path : parentFullPath
}
// debug('Full path: ', tmp.meta.fullPath)
// console.log('Full path: ', tmp.meta.fullPath)
}
// 设置默认active menu
if (tmp.meta.type === 'crud' && !tmp.meta.activeMenu) {

View File

@@ -32,25 +32,24 @@ const actions = {
commit('CHANGE_SETTING', data)
},
// get user Profile
getPublicSettings({ commit, state }, isOpen) {
getPublicSettings({ commit, state }) {
return new Promise((resolve, reject) => {
getPublicSettings(isOpen).then(response => {
const data = response || {}
if (isOpen) {
const faviconURL = data['LOGO_URLS']?.favicon
let link = document.querySelector("link[rel*='icon']")
if (!link) {
link = document.createElement('link')
link.type = 'image/x-icon'
link.rel = 'shortcut icon'
document.getElementsByTagName('head')[0].appendChild(link)
}
if (faviconURL) {
link.href = faviconURL
}
// 动态修改Title
document.title = data['LOGIN_TITLE']
getPublicSettings().then(response => {
const data = response.data || {}
const faviconURL = data['LOGO_URLS']?.favicon
let link = document.querySelector("link[rel*='icon']")
if (!link) {
link = document.createElement('link')
link.type = 'image/x-icon'
link.rel = 'shortcut icon'
document.getElementsByTagName('head')[0].appendChild(link)
}
if (faviconURL) {
link.href = faviconURL
}
// 动态修改Title
document.title = data['LOGIN_TITLE']
commit('SET_PUBLIC_SETTINGS', data)
resolve(response)
}).catch(error => {

View File

@@ -82,7 +82,7 @@ const actions = {
commit('SET_PROFILE', response)
resolve(response)
}).catch(error => {
// debug(error)
// console.log(error)
reject(error)
})
})

View File

@@ -3,7 +3,7 @@
/* fade */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s;
transition: opacity 0.28s;
}
.fade-enter,
@@ -14,7 +14,7 @@
/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all .2s;
transition: all .5s;
}
.fade-transform-enter {
@@ -30,7 +30,7 @@
/* breadcrumb transition */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all .25s;
transition: all .5s;
}
.breadcrumb-enter,
@@ -40,7 +40,7 @@
}
.breadcrumb-move {
transition: all .25s;
transition: all .5s;
}
.breadcrumb-leave-active {

View File

@@ -1,19 +1,15 @@
import VueCookie from 'vue-cookie'
const TOKEN_KEY = 'csrftoken'
const CURRENT_ORG_KEY = 'jms_current_org'
const CURRENT_ROLE_KEY = 'jms_current_role'
let cookieNamePrefix = VueCookie.get('SESSION_COOKIE_NAME_PREFIX')
if (!cookieNamePrefix || ['""', "''"].indexOf(cookieNamePrefix) > -1) {
cookieNamePrefix = ''
}
const TOKEN_KEY = `${cookieNamePrefix}csrftoken`
export function getTokenFromCookie() {
return VueCookie.get(TOKEN_KEY)
}
export function setTokenToCookie(value, expires) {
return VueCookie.set(TOKEN_KEY, value, { expires: expires })
let cookieNamePrefix = VueCookie.get('SESSION_COOKIE_NAME_PREFIX')
if (!cookieNamePrefix || ['""', "''"].indexOf(cookieNamePrefix) > -1) {
cookieNamePrefix = ''
}
return VueCookie.get(cookieNamePrefix + TOKEN_KEY)
}
export function getCurrentRoleLocal(username) {

View File

@@ -160,8 +160,6 @@ export function hasUUID(s) {
}
export function replaceUUID(s, n) {
const index = s.search(uuidPattern)
if (index > 0) return s.substr(0, index)
return s.replace(uuidPattern, n)
}

View File

@@ -1,57 +0,0 @@
import { JSEncrypt } from 'jsencrypt'
import CryptoJS from 'crypto-js'
import VueCookie from 'vue-cookie'
export function fillKey(key) {
let keySize = 128
// 如果超过 key 16 位, 最大取 32 位,需要更改填充
if (key.length > 16) {
key = key.slice(0, 32)
keySize = keySize * 2
}
const filledKeyLength = keySize / 8
if (key.length >= filledKeyLength) {
return key.slice(0, filledKeyLength)
}
const filledKey = Buffer.alloc(keySize / 8)
const keys = Buffer.from(key)
for (let i = 0; i < keys.length; i++) {
filledKey[i] = keys[i]
}
return filledKey
}
export function aesEncrypt(text, originKey) {
const key = CryptoJS.enc.Utf8.parse(fillKey(originKey))
return CryptoJS.AES.encrypt(text, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.ZeroPadding
}).toString()
}
export function rsaEncrypt(text, pubKey) {
const jsEncrypt = new JSEncrypt()
jsEncrypt.setPublicKey(pubKey)
return jsEncrypt.encrypt(text)
}
export function getCookie(name) {
return VueCookie.get(name)
}
export function encryptPassword(password) {
if (!password) {
return ''
}
const aesKey = (Math.random() + 1).toString(36).substring(2)
// public key 是 base64 存储的
const rsaPublicKeyText = getCookie('jms_public_key')
.replaceAll('"', '')
const rsaPublicKey = atob(rsaPublicKeyText)
const keyCipher = rsaEncrypt(aesKey, rsaPublicKey)
const passwordCipher = aesEncrypt(password, aesKey)
return `${keyCipher}:${passwordCipher}`
}
window.aesEncrypt = aesEncrypt

View File

@@ -36,7 +36,7 @@ function beforeRequestAddTimezone(config) {
try {
config.headers['X-TZ'] = Intl.DateTimeFormat().resolvedOptions().timeZone
} catch (e) {
// debug('Current browser not support Intl tools')
console.log('Current browser not support Intl tools')
}
}
@@ -51,7 +51,7 @@ service.interceptors.request.use(
},
error => {
// do something with request error
// debug(error) // for debug
console.log(error) // for debug
return Promise.reject(error)
}
)

View File

@@ -2,13 +2,12 @@
import store from '@/store'
import router, { resetRouter } from '@/router'
import Vue from 'vue'
import VueCookie from 'vue-cookie'
import { Message } from 'element-ui'
import 'nprogress/nprogress.css' // progress bar style
import { getTokenFromCookie } from '@/utils/auth'
import orgUtil from '@/utils/org'
import orgs from '@/api/orgs'
import { getPropView, isViewHasOrgs } from '@/utils/jms'
import request from '@/utils/request'
const whiteList = ['/login', process.env.VUE_APP_LOGIN_PATH] // no redirect whitelist
@@ -20,25 +19,15 @@ async function checkLogin({ to, from, next }) {
if (whiteList.indexOf(to.path) !== -1) {
next()
}
// Determine whether the user has logged in
const sessionExpire = VueCookie.get('jms_session_expire')
if (!sessionExpire) {
request.get(process.env['VUE_APP_LOGOUT_PATH']).finally(() => {
// determine whether the user has logged in
const hasToken = getTokenFromCookie()
if (!hasToken) {
setTimeout(() => {
window.location = process.env.VUE_APP_LOGIN_PATH
})
return reject('No session mark found in cookie')
} else if (sessionExpire === 'close') {
let startTime = new Date().getTime()
setInterval(() => {
const endTime = new Date().getTime()
const delta = (endTime - startTime)
startTime = endTime
Vue.$log.debug('Set session expire: ', delta)
VueCookie.set('jms_session_expire', 'close', { expires: '2m' })
}, 10 * 1000)
} else if (sessionExpire === 'age') {
Vue.$log.debug('Session expire on age')
}, 100)
return reject('No token found in cookie')
}
try {
return await store.dispatch('users/getProfile')
} catch (e) {
@@ -53,11 +42,11 @@ async function checkLogin({ to, from, next }) {
}
}
async function getPublicSetting({ to, from, next }, isOpen) {
async function getPublicSetting({ to, from, next }) {
// 获取Public settings
const publicSettings = store.getters.publicSettings
if (!publicSettings || !isOpen) {
await store.dispatch('settings/getPublicSettings', isOpen)
if (!publicSettings) {
await store.dispatch('settings/getPublicSettings')
}
}
@@ -153,10 +142,8 @@ export async function startup({ to, from, next }) {
await store.dispatch('app/init')
// set page title
// await getOpenPublicSetting({ to, from, next })
await getPublicSetting({ to, from, next }, true)
await getPublicSetting({ to, from, next })
await checkLogin({ to, from, next })
await getPublicSetting({ to, from, next }, false)
await changeCurrentViewIfNeed({ to, from, next })
await changeCurrentOrgIfNeed({ to, from, next })
await generatePageRoutes({ to, from, next })

View File

@@ -1,7 +1,7 @@
<template>
<GenericTreeListPage ref="TreeTablePage" :tree-setting="treeSetting">
<template #table>
<AccountListTable ref="table" :url="accountsUrl" :has-left-actions="true" />
<AccountListTable ref="table" :url="accountsUrl" />
</template>
</GenericTreeListPage>
</template>

View File

@@ -7,7 +7,6 @@ import { GenericCreateUpdatePage } from '@/layout/components'
import { REMOTE_APP_TYPE_FIELDS_MAP, REMOTE_APP_TYPE_META_MAP, REMOTE_APP_PATH_DEFAULT_MAP } from './const'
import rules from '@/components/DataForm/rules'
import { UpdateToken } from '@/components/FormFields'
import { encryptPassword } from '@/utils/crypto'
export default {
components: {
@@ -70,20 +69,9 @@ export default {
}
return `${url}?type=${this.$route.query.type}`
},
cleanFormValue(values) {
values.category = 'remote_app'
const encryptedFields = [
'chrome_password', 'custom_password',
'mysql_workbench_password', 'vmware_password'
]
const attrs = values.attrs
for (const item of encryptedFields) {
const value = attrs[item]
if (value) {
attrs[item] = encryptPassword(value)
}
}
return values
cleanFormValue(value) {
value.category = 'remote_app'
return value
}
}
},

View File

@@ -2,7 +2,7 @@
<div>
<el-row :gutter="24">
<el-col :md="16" :sm="24">
<AccountListTable ref="ListTable" :url="assetUserUrl" :has-import="false" :has-clone="false" :has-left-actions="true" />
<AccountListTable ref="ListTable" :url="assetUserUrl" :has-import="false" :has-clone="false" />
</el-col>
<el-col :md="8" :sm="24">
<QuickActions type="primary" :actions="quickActions" />

View File

@@ -1,45 +1,79 @@
<template>
<div>
<GenericTreeListPage
ref="TreeList"
:table-config="tableConfig"
:help-message="helpMessage"
:header-actions="headerActions"
:tree-setting="treeSetting"
>
<TreeMenu
slot="rMenu"
:tree="treeRef"
@showAll="showAll"
/>
<GenericTreeListPage ref="TreeList" :table-config="tableConfig" :help-message="helpMessage" :header-actions="headerActions" :tree-setting="treeSetting">
<div slot="rMenu">
<!-- <li class="divider" />-->
<li id="m_add_asset_to_node" v-perms="'assets.add_assettonode'" class="rmenu" tabindex="-1" @click="rMenuAddAssetToNode">
<i class="fa fa-clone" /> {{ this.$t('tree.AddAssetToNode') }}
</li>
<li id="m_move_asset_to_node" v-perms="'assets.move_assettonode'" class="rmenu" tabindex="-1" @click="rMenuMoveAssetToNode">
<i class="fa fa-scissors" /> {{ this.$t('tree.MoveAssetToNode') }}
</li>
<li v-if="$hasPerm('assets.move_assettonode | assets.add_assettonode')" class="divider" />
<li id="m_update_node_asset_hardware_info" v-perms="'assets.refresh_assethardwareinfo'" class="rmenu" tabindex="-1" @click="rMenuUpdateNodeAssetHardwareInfo">
<i class="fa fa-refresh" /> {{ this.$t('tree.UpdateNodeAssetHardwareInfo') }}
</li>
<li id="m_test_node_asset_connectivity" v-perms="'assets.test_assetconnectivity'" class="rmenu" tabindex="-1" @click="rMenuTestNodeAssetConnectivity">
<i class="fa fa-link" /> {{ this.$t('tree.TestNodeAssetConnectivity') }}
</li>
<li v-if="$hasPerm('assets.add_assettonode | assets.test_assetconnectivity')" class="divider" />
<li id="m_show_asset_only_current_node" class="rmenu" tabindex="-1" @click="rMenuShowAssetOnlyCurrentNode">
<i class="fa fa-indent" /> {{ this.$t('tree.ShowAssetOnlyCurrentNode') }}
</li>
<li id="m_show_asset_all_children_node" class="rmenu" tabindex="-1" @click="rMenuShowAssetAllChildrenNode">
<i class="fa fa-align-justify" /> {{ this.$t('tree.ShowAssetAllChildrenNode') }}
</li>
<li class="divider" />
<li id="m_check_assets_amount" v-perms="'assets.change_node'" class="rmenu" tabindex="-1" @click="rCheckAssetsAmount">
<i class="fa fa-clone" /> {{ this.$t('tree.CheckAssetsAmount') }}
</li>
<li id="m_show_node_info" class="rmenu" tabindex="-1" @click="rMenuShowNodeInfo">
<i class="fa fa-info-circle" /> {{ this.$t('tree.ShowNodeInfo') }}
</li>
</div>
</GenericTreeListPage>
<Dialog width="30%" :title="this.$t('assets.NodeInformation')" :visible.sync="nodeInfoDialogSetting.dialogVisible" :show-cancel="false" :show-confirm="false">
<el-row v-for="item in nodeInfoDialogSetting.items" :key="'card-' + item.key" :gutter="10" class="item">
<el-col :md="6" :sm="24"><div class="item-label"><label>{{ item.label }}: </label></div></el-col>
<el-col :md="18" :sm="24"><div class="item-text">{{ item.value }}</div></el-col>
</el-row>
</Dialog>
<AssetBulkUpdateDialog
:visible.sync="updateSelectedDialogSetting.visible"
v-bind="updateSelectedDialogSetting"
/>
<NodeAssetsUpdateDialog
:visible.sync="nodeAssetsUpdateDialog.visible"
v-bind="nodeAssetsUpdateDialog"
/>
</div>
</template>
<script>
import GenericTreeListPage from '@/layout/components/GenericTreeListPage/index'
import { DetailFormatter, ActionsFormatter, TagsFormatter } from '@/components/TableFormatters'
import {
DetailFormatter,
ActionsFormatter,
TagsFormatter
} from '@/components/TableFormatters'
import $ from '@/utils/jquery-vendor'
import Dialog from '@/components/Dialog'
import { mapGetters } from 'vuex'
import { connectivityMeta } from '@/components/AccountListTable/const'
import { openTaskPage } from '@/utils/jms'
import AssetBulkUpdateDialog from './AssetBulkUpdateDialog'
import TreeMenu from './TreeMenu'
import NodeAssetsUpdateDialog from './NodeAssetsUpdateDialog'
export default {
components: {
GenericTreeListPage,
Dialog,
AssetBulkUpdateDialog,
TreeMenu
NodeAssetsUpdateDialog
},
data() {
const vm = this
return {
treeRef: null,
treeSetting: {
showMenu: true,
showRefresh: true,
@@ -124,10 +158,7 @@ export default {
type: 'primary',
can: vm.$hasPerm('assets.refresh_assethardwareinfo'),
callback: function({ cellValue, tableData, row }) {
return this.$router.push({
name: 'AssetMoreInformationEdit',
params: { id: row.id }
})
return this.$router.push({ name: 'AssetMoreInformationEdit', params: { id: row.id }})
}
}
]
@@ -198,8 +229,8 @@ export default {
title: this.$t('common.updateSelected'),
can: ({ selectedRows }) => {
return selectedRows.length > 0 &&
!vm.currentOrgIsRoot &&
vm.$hasPerm('assets.change_asset')
!vm.currentOrgIsRoot &&
vm.$hasPerm('assets.change_asset')
},
callback: ({ selectedRows }) => {
vm.updateSelectedDialogSetting.selectedRows = selectedRows
@@ -214,8 +245,8 @@ export default {
return false
}
return selectedRows.length > 0 &&
!vm.currentOrgIsRoot &&
vm.$hasPerm('assets.change_node')
!vm.currentOrgIsRoot &&
vm.$hasPerm('assets.change_node')
},
callback: function({ selectedRows, reloadTable }) {
const assetsId = []
@@ -238,9 +269,18 @@ export default {
]
},
helpMessage: this.$t('assets.AssetListHelpMessage'),
nodeInfoDialogSetting: {
dialogVisible: false,
items: []
},
updateSelectedDialogSetting: {
visible: false,
selectedRows: []
},
nodeAssetsUpdateDialog: {
visible: false,
action: 'add',
selectNode: null
}
}
},
@@ -253,7 +293,6 @@ export default {
this.treeSetting.showCreate = this.$hasPerm('assets.add_node')
this.treeSetting.showUpdate = this.$hasPerm('assets.change_node')
this.treeSetting.showDelete = this.$hasPerm('assets.delete_node')
this.treeRef = this.$refs.TreeList
},
methods: {
decorateRMenu() {
@@ -266,18 +305,145 @@ export default {
$('#m_show_asset_only_current_node').css('color', '#606266')
}
},
showAll({ node, showCurrentAsset }) {
this.$cookie.set('show_current_asset', showCurrentAsset, 1)
hideRMenu() {
this.$refs.TreeList.hideRMenu()
},
getSelectedNodes() {
return this.$refs.TreeList.getSelectedNodes()
},
rMenuAddAssetToNode() {
this.nodeAssetsUpdateDialog.visible = true
this.nodeAssetsUpdateDialog.action = 'add'
this.nodeAssetsUpdateDialog.selectNode = this.getSelectedNodes()[0]
},
rMenuMoveAssetToNode() {
this.nodeAssetsUpdateDialog.visible = true
this.nodeAssetsUpdateDialog.action = 'move'
this.nodeAssetsUpdateDialog.selectNode = this.getSelectedNodes()[0]
},
rMenuUpdateNodeAssetHardwareInfo: function() {
this.hideRMenu()
const currentNode = this.getSelectedNodes()[0]
if (!currentNode) {
return
}
this.$axios.post(
`/api/v1/assets/nodes/${currentNode.meta.data.id}/tasks/`,
{ 'action': 'refresh' }
).then((res) => {
openTaskPage(res['task'])
}).catch(error => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
})
},
rMenuTestNodeAssetConnectivity: function() {
this.hideRMenu()
const currentNode = this.getSelectedNodes()[0]
if (!currentNode) {
return
}
this.$axios.post(
`/api/v1/assets/nodes/${currentNode.meta.data.id}/tasks/`,
{ 'action': 'test' }
).then((res) => {
openTaskPage(res['task'])
}).catch(error => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
})
},
rMenuShowAssetOnlyCurrentNode: function() {
this.hideRMenu()
const currentNode = this.getSelectedNodes()[0]
if (!currentNode) {
return
}
this.$cookie.set('show_current_asset', '1', 1)
this.decorateRMenu()
const url = `${this.treeSetting.url}?node_id=${node.meta.data.id}&show_current_asset=${showCurrentAsset}`
const url = `${this.treeSetting.url}?node_id=${currentNode.meta.data.id}&show_current_asset=1`
this.$refs.TreeList.$refs.TreeTable.handleUrlChange(url)
},
rMenuShowAssetAllChildrenNode: function() {
this.hideRMenu()
const currentNode = this.getSelectedNodes()[0]
if (!currentNode) {
return
}
this.$cookie.set('show_current_asset', '0', 1)
this.decorateRMenu()
const url = `${this.treeSetting.url}?node_id=${currentNode.meta.data.id}&show_current_asset=0`
this.$refs.TreeList.$refs.TreeTable.handleUrlChange(url)
},
rMenuShowNodeInfo: function() {
this.hideRMenu()
const currentNode = this.getSelectedNodes()[0]
if (!currentNode) {
return
}
this.$axios.get(
`/api/v1/assets/nodes/${currentNode.meta.data.id}/`
).then(res => {
this.nodeInfoDialogSetting.dialogVisible = true
this.nodeInfoDialogSetting.items = [
{ key: 'id', label: 'ID', value: res.id },
{ key: 'name', label: this.$t('assets.Name'), value: res.name },
{ key: 'fullName', label: this.$t('assets.FullName'), value: res.full_value }
]
}).catch(error => {
this.$message.error(this.$t('common.ErrorMsg' + ' ' + error))
})
},
rCheckAssetsAmount: function() {
this.$axios.post(
`/api/v1/assets/nodes/check_assets_amount_task/`
).then(res => {
openTaskPage(res['task'])
}).catch(error => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
})
}
}
}
</script>
<style lang="scss" scoped>
.asset-select-dialog >>> .transition-box:first-child {
background-color: #f3f3f3;
}
.rmenu {
font-size: 12px;
padding: 0 16px;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #606266;
height: 24px;
line-height: 24px;
box-sizing: border-box;
cursor: pointer;
}
.rmenu > a:hover, .dropdown-menu > a:focus {
color: #262626;
text-decoration: none;
background-color: #f5f5f5;
}
.rmenu:hover{
background-color: #f5f7fa;
}
.divider{
margin: 1px 0;
list-style: none outside none;
background-color: #e5e5e5;
height: 1px
}
.asset-select-dialog >>> .transition-box:first-child {
background-color: #f3f3f3 ;
}
.el-row {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
</style>

View File

@@ -95,7 +95,6 @@ export default {
computed: {
iVisible: {
set(val) {
this.$parent?.hideMenu()
this.$emit('update:visible', val)
},
get() {
@@ -134,7 +133,6 @@ export default {
$('#tree-refresh').trigger('click')
this.$message.success(this.$t('common.updateSuccessMsg'))
}).catch(error => {
this.$parent?.hideMenu()
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
})
},

View File

@@ -1,227 +0,0 @@
<template>
<div>
<li id="m_add_asset_to_node" v-perms="'assets.add_assettonode'" class="rmenu" tabindex="-1" @click="rMenuAddAssetToNode">
<i class="fa fa-clone" /> {{ this.$t('tree.AddAssetToNode') }}
</li>
<li id="m_move_asset_to_node" v-perms="'assets.move_assettonode'" class="rmenu" tabindex="-1" @click="rMenuMoveAssetToNode">
<i class="fa fa-scissors" /> {{ this.$t('tree.MoveAssetToNode') }}
</li>
<li v-if="$hasPerm('assets.move_assettonode | assets.add_assettonode')" class="divider" />
<li id="m_update_node_asset_hardware_info" v-perms="'assets.refresh_assethardwareinfo'" class="rmenu" tabindex="-1" @click="rMenuUpdateNodeAssetHardwareInfo">
<i class="fa fa-refresh" /> {{ this.$t('tree.UpdateNodeAssetHardwareInfo') }}
</li>
<li id="m_test_node_asset_connectivity" v-perms="'assets.test_assetconnectivity'" class="rmenu" tabindex="-1" @click="rMenuTestNodeAssetConnectivity">
<i class="fa fa-link" /> {{ this.$t('tree.TestNodeAssetConnectivity') }}
</li>
<li v-if="$hasPerm('assets.add_assettonode | assets.test_assetconnectivity')" class="divider" />
<li id="m_show_asset_only_current_node" class="rmenu" tabindex="-1" @click="rMenuShowAssetOnlyCurrentNode">
<i class="fa fa-indent" /> {{ this.$t('tree.ShowAssetOnlyCurrentNode') }}
</li>
<li id="m_show_asset_all_children_node" class="rmenu" tabindex="-1" @click="rMenuShowAssetAllChildrenNode">
<i class="fa fa-align-justify" /> {{ this.$t('tree.ShowAssetAllChildrenNode') }}
</li>
<li class="divider" />
<li id="m_check_assets_amount" v-perms="'assets.change_node'" class="rmenu" tabindex="-1" @click="rCheckAssetsAmount">
<i class="fa fa-clone" /> {{ this.$t('tree.CheckAssetsAmount') }}
</li>
<li id="m_show_node_info" class="rmenu" tabindex="-1" @click="rMenuShowNodeInfo">
<i class="fa fa-info-circle" /> {{ this.$t('tree.ShowNodeInfo') }}
</li>
<NodeAssetsUpdateDialog
:visible.sync="nodeAssetsUpdateDialog.visible"
v-bind="nodeAssetsUpdateDialog"
/>
<Dialog
width="50%"
:title="this.$t('assets.NodeInformation')"
:visible.sync="nodeInfoDialogSetting.dialogVisible"
:show-cancel="false"
:show-confirm="false"
>
<el-row
v-for="item in nodeInfoDialogSetting.items"
:key="'card-' + item.key"
:gutter="10"
class="item"
>
<el-col :md="6" :sm="24">
<div class="item-label"><label>{{ item.label }}: </label></div>
</el-col>
<el-col :md="18" :sm="24">
<div class="item-text">{{ item.value }}</div>
</el-col>
</el-row>
</Dialog>
</div>
</template>
<script>
import { openTaskPage } from '@/utils/jms'
import NodeAssetsUpdateDialog from './NodeAssetsUpdateDialog'
import Dialog from '@/components/Dialog'
export default {
name: 'TreeMenu',
components: {
NodeAssetsUpdateDialog,
Dialog
},
props: {
tree: {
type: Object,
require: true,
default: () => ({})
}
},
data() {
return {
nodeAssetsUpdateDialog: {
visible: false,
action: 'add',
selectNode: null
},
nodeInfoDialogSetting: {
dialogVisible: false,
items: []
}
}
},
methods: {
rMenuAddAssetToNode() {
this.nodeAssetsUpdateDialog.visible = true
this.nodeAssetsUpdateDialog.action = 'add'
this.nodeAssetsUpdateDialog.selectNode = this.getSelectedNodes()[0]
},
rMenuMoveAssetToNode() {
this.nodeAssetsUpdateDialog.visible = true
this.nodeAssetsUpdateDialog.action = 'move'
this.nodeAssetsUpdateDialog.selectNode = this.getSelectedNodes()[0]
},
rMenuUpdateNodeAssetHardwareInfo() {
this.hideMenu()
const currentNode = this.getSelectedNodes()[0]
if (!currentNode) {
return
}
this.$axios.post(
`/api/v1/assets/nodes/${currentNode.meta.data.id}/tasks/`,
{ 'action': 'refresh' }
).then((res) => {
openTaskPage(res['task'])
}).catch(error => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
})
},
rMenuTestNodeAssetConnectivity() {
this.hideMenu()
const currentNode = this.getSelectedNodes()[0]
if (!currentNode) {
return
}
this.$axios.post(
`/api/v1/assets/nodes/${currentNode.meta.data.id}/tasks/`,
{ 'action': 'test' }
).then((res) => {
openTaskPage(res['task'])
}).catch(error => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
})
},
rMenuShowAssetOnlyCurrentNode() {
this.hideMenu()
const currentNode = this.getSelectedNodes()[0]
if (!currentNode) {
return
}
this.$emit('showAll', { node: currentNode, showCurrentAsset: 1 })
},
rMenuShowAssetAllChildrenNode() {
this.hideMenu()
const currentNode = this.getSelectedNodes()[0]
if (!currentNode) {
return
}
this.$emit('showAll', { node: currentNode, showCurrentAsset: 0 })
},
async rMenuShowNodeInfo() {
const currentNode = this.getSelectedNodes()[0]
if (!currentNode) return
try {
const res = await this.$axios.get(`/api/v1/assets/nodes/${currentNode.meta.data.id}/`)
this.nodeInfoDialogSetting.dialogVisible = true
this.nodeInfoDialogSetting.items = [
{ key: 'id', label: 'ID', value: res.id },
{ key: 'name', label: this.$t('assets.Name'), value: res.name },
{ key: 'fullName', label: this.$t('assets.FullName'), value: res.full_value }
]
} catch (error) {
this.$message.error(this.$t('common.ErrorMsg' + ' ' + error))
}
},
rCheckAssetsAmount() {
this.$axios.post(
`/api/v1/assets/nodes/check_assets_amount_task/`
).then(res => {
openTaskPage(res['task'])
}).catch(error => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
})
},
hideMenu() {
// debug('Tree: ', this.tree)
this.tree.hideRMenu()
},
getSelectedNodes() {
return this.tree.getSelectedNodes()
}
}
}
</script>
<style lang="scss" scoped>
.rmenu {
font-size: 12px;
padding: 0 16px;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #606266;
height: 24px;
line-height: 24px;
box-sizing: border-box;
cursor: pointer;
}
div.rMenu li {
margin: 6px 0;
cursor: pointer;
list-style: none outside none;
}
.rmenu > a:hover, .dropdown-menu > a:focus {
color: #262626;
text-decoration: none;
background-color: #f5f5f5;
}
.rmenu:hover {
background-color: #f5f7fa;
}
.divider {
margin: 1px 0;
list-style: none outside none;
background-color: #e5e5e5;
height: 1px
}
.el-row {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
</style>

View File

@@ -10,7 +10,6 @@ import { GenericCreateUpdatePage } from '@/layout/components'
import { Required } from '@/components/DataForm/rules'
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun } from '../const'
import { UploadKey } from '@/components'
import { encryptPassword } from '@/utils/crypto'
export default {
components: {
@@ -32,7 +31,6 @@ export default {
],
fieldsMeta: {
attrs: {
encryptedFields: ['access_key_secret'],
fields: accountProviderAttrs.attrs,
fieldsMeta: {
service_account_key: {
@@ -63,20 +61,6 @@ export default {
url = `${url}${params.id}/`
}
return `${url}?provider=${accountProvider}`
},
cleanFormValue(values) {
const encryptedFields = [
'access_key_secret', 'password', 'client_secret',
'oc_password', 'sc_password'
]
const attrs = values.attrs
for (const item of encryptedFields) {
const value = attrs[item]
if (value) {
attrs[item] = encryptPassword(value)
}
}
return values
}
}
},

View File

@@ -4,7 +4,7 @@
<script type="text/jsx">
import GenericListTable from '@/layout/components/GenericListTable'
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun, aws_china, aws_international, huaweicloud, qcloud, azure, azure_international, vmware, nutanix, qingcloud_private, huaweicloud_private, openstack, gcp, baiducloud, jdcloud, fc } from '../const'
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun, aws_china, aws_international, huaweicloud, qcloud, azure, azure_international, vmware, nutanix, qingcloud_private, huaweicloud_private, openstack, gcp, baiducloud, jdcloud } from '../const'
export default {
name: 'AccountList',
@@ -134,10 +134,6 @@ export default {
{
name: nutanix,
title: ACCOUNT_PROVIDER_ATTRS_MAP[nutanix].title
},
{
name: fc,
title: ACCOUNT_PROVIDER_ATTRS_MAP[fc].title
}
]
}

View File

@@ -13,7 +13,6 @@ export const qingcloud_private = 'qingcloud_private'
export const huaweicloud_private = 'huaweicloud_private'
export const openstack = 'openstack'
export const gcp = 'gcp'
export const fc = 'fc'
export const baiducloud = 'baiducloud'
export const jdcloud = 'jdcloud'
@@ -92,10 +91,5 @@ export const ACCOUNT_PROVIDER_ATTRS_MAP = {
name: gcp,
title: i18n.t('xpack.Cloud.GCP'),
attrs: ['service_account_key']
},
[fc]: {
name: fc,
title: i18n.t('xpack.Cloud.FC'),
attrs: ['api_endpoint', 'username', 'password']
}
}

View File

@@ -4,10 +4,7 @@
<DetailCard :items="detailCardItems" />
</el-col>
<el-col :md="10" :sm="24">
<RelationCard ref="users" v-bind="userRelationConfig" />
<RelationCard ref="userGroups" type="info" v-bind="userGroupsRelationConfig" class="card-margin" />
<RelationCard ref="applications" type="warning" v-bind="applicationsRelationConfig" class="card-margin" />
<RelationCard ref="systemUsers" type="danger" v-bind="systemUserRelationConfig" class="card-margin" />
<RelationCard ref="systemUserRelation" v-bind="systemUserRelationConfig" />
</el-col>
</el-row>
</template>
@@ -29,58 +26,42 @@ export default {
}
},
data() {
const disabled = !this.$hasPerm('assets.change_commandfilter')
const defaultTransformOption = (item, filed) => {
const currentFiled = item[filed] ? item[filed] : filed === 'username' ? '*' : ''
return { label: item.name + '(' + currentFiled + ')', value: item.id }
}
return {
userRelationConfig: {
disabled,
icon: 'fa-user',
title: this.$t('common.User'),
objectsAjax: {
url: '/api/v1/users/users/?fields_size=mini',
transformOption: (item) => defaultTransformOption(item, 'username')
},
hasObjectsId: this.object.users,
performAdd: item => this.performAddHandle(item, 'users'),
performDelete: item => this.performDeleteHandle(item, 'users')
},
userGroupsRelationConfig: {
disabled,
icon: 'fa-users',
title: this.$t('users.UserGroups'),
objectsAjax: {
url: '/api/v1/users/groups/'
},
hasObjectsId: this.object.user_groups,
performAdd: item => this.performAddHandle(item, 'user_groups'),
performDelete: item => this.performDeleteHandle(item, 'user_groups')
},
applicationsRelationConfig: {
disabled,
icon: 'fa-th',
title: this.$t('assets.Applications'),
objectsAjax: {
url: `/api/v1/applications/applications/?category__in=db,cloud`,
transformOption: (item) => defaultTransformOption(item, 'type_display')
},
hasObjectsId: this.object.applications,
performAdd: item => this.performAddHandle(item, 'applications'),
performDelete: item => this.performDeleteHandle(item, 'applications')
},
systemUserRelationConfig: {
disabled,
icon: 'fa-info-circle',
title: this.$t('assets.SystemUser'),
icon: 'fa-info',
title: this.$t('perms.addSystemUserToThisPermission'),
objectsAjax: {
url: `/api/v1/assets/system-users/?protocol__in=ssh,telnet,mysql,postgresql,mariadb,oracle,sqlserver,k8s`,
transformOption: (item) => defaultTransformOption(item, 'username')
transformOption: (item) => {
const username = item.username || '*'
return { label: item.name + '(' + username + ')', value: item.id }
}
},
hasObjectsId: this.object.system_users,
performAdd: item => this.performAddHandle(item, 'system_users'),
performDelete: item => this.performDeleteHandle(item, 'system_users')
performAdd: (items) => {
const newData = []
const value = this.$refs.systemUserRelation.iHasObjects
value.map(v => {
newData.push(v.value)
})
const relationUrl = `/api/v1/assets/cmd-filters/${this.object.id}/`
items.map(v => {
newData.push(v.value)
})
return this.$axios.patch(relationUrl, { system_users: newData })
},
performDelete: (item) => {
const itemId = item.value
const newData = []
const value = this.$refs.systemUserRelation.iHasObjects
value.map(v => {
if (v.value !== itemId) {
newData.push(v.value)
}
})
const relationUrl = `/api/v1/assets/cmd-filters/${this.object.id}/`
return this.$axios.patch(relationUrl, { system_users: newData })
}
}
}
},
@@ -109,38 +90,10 @@ export default {
}
]
}
},
methods: {
performAddHandle(item, updateField) {
const newDatas = []
const updateFieldRef = _.camelCase(updateField)
const options = this.$refs[updateFieldRef].iHasObjects
const relationUrl = `/api/v1/assets/cmd-filters/${this.object.id}/`
options.forEach(v => newDatas.push(v.value))
item.forEach(v => newDatas.push(v.value))
return this.$axios.patch(relationUrl, { [updateField]: newDatas })
},
performDeleteHandle(item, updateField) {
const newDatas = []
const itemId = item?.value || ''
const updateFieldRef = _.camelCase(updateField)
const options = this.$refs[updateFieldRef].iHasObjects
const relationUrl = `/api/v1/assets/cmd-filters/${this.object.id}/`
options.forEach(v => {
if (v.value !== itemId) {
newDatas.push(v.value)
}
})
return this.$axios.patch(relationUrl, { [updateField]: newDatas })
}
}
}
</script>
<style lang='less' scoped>
.card-margin {
margin-top: 15px;
}
</style>

View File

@@ -23,7 +23,7 @@ export default {
},
actions: {
formatterArgs: {
canClone: () => vm.$hasPerm('assets.add_platform'),
canClone: vm.$hasPerm('assets.add_platform'),
canUpdate: ({ row }) => !row.internal && vm.$hasPerm('assets.change_platform'),
canDelete: ({ row }) => !row.internal && vm.$hasPerm('assets.delete_platform')
}
@@ -34,10 +34,7 @@ export default {
hasRightActions: true,
hasMoreActions: false,
hasBulkDelete: false,
createRoute: 'PlatformCreate',
canCreate: () => {
return this.$hasPerm('assets.add_platform')
}
createRoute: 'PlatformCreate'
}
}
}

View File

@@ -29,7 +29,6 @@ export default {
[this.$t('common.Command filter'), ['cmd_filters']],
[this.$t('common.Other'), ['priority', 'comment']]
],
encryptedFields: ['password'],
fieldsMeta: {
login_mode: fields.login_mode,
username: fields.username,

View File

@@ -1,13 +1,7 @@
<template>
<el-row :gutter="20">
<el-col :md="20" :sm="24">
<AppAccountListTable
ref="ListTable"
:url="accountUrl"
:has-import="false"
:has-clone="false"
:system-user-disabled="systemUserDisabled"
/>
<AppAccountListTable ref="ListTable" :url="accountUrl" :has-import="false" :has-clone="false" />
</el-col>
<el-col :md="4" :sm="24" />
</el-row>
@@ -30,8 +24,7 @@ export default {
},
data() {
return {
accountUrl: `/api/v1/applications/accounts/?systemuser=${this.object.id}`,
systemUserDisabled: false
accountUrl: `/api/v1/applications/accounts/?systemuser=${this.object.id}`
}
}
}

View File

@@ -1,6 +1,6 @@
<template>
<div>
<el-alert type="success">
<el-alert type="info">
<b>{{ Tips.title }}</b>: <span>{{ Tips.body }}</span>
</el-alert>
<el-row :gutter="20">

View File

@@ -116,7 +116,7 @@ export default {
this.loading = false
})
} else {
// debug('error submit!!')
console.log('error submit!!')
return false
}
})

View File

@@ -0,0 +1,90 @@
<template>
<el-card class="box-card" shadow="never">
<div slot="header" class="title">
<span>{{ $t('common.Announcement') }}</span>
</div>
<ul class="content">
<li v-if="announcement.content" class="item">
<p class="item-title">{{ announcement.subject }}</p>
<p class="item-content">{{ announcement.content }}</p>
<span v-if="announcement.link">
<el-link :href="announcement.link" target="_blank" class="item-url">
{{ $t('common.ViewMore') }}
</el-link>
<i class="fa fa-share-square-o icon-url" />
</span>
</li>
<li v-else class="other">{{ $t('common.noAnnouncement') }}</li>
</ul>
</el-card>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'Announcement',
data() {
return {
content: ''
}
},
computed: {
...mapGetters([
'publicSettings'
]),
announcement() {
const ann = this.publicSettings?.ANNOUNCEMENT || {}
return {
id: ann?.ID,
subject: ann?.SUBJECT || '',
content: ann?.CONTENT || '',
link: ann?.LINK
}
}
}
}
</script>
<style lang="scss" scoped>
ul,li {
padding: 0;
margin: 0;
list-style: none
}
.box-card {
margin-bottom: 20px;
}
.title {
font-weight: 500;
}
.content {
width: 100%;
.item {
margin-bottom: 16px;
cursor: pointer;
vertical-align: middle;
.item-title {
display: inline-block;
color: #8b9db6;
text-align: center;
font-size: 15px;
vertical-align: middle;
margin-left: -10px;
}
.item-content {
white-space: pre-wrap;
margin: 0;
}
}
.item-url {
color: #8b9db6!important;
}
.other {
text-align: center;
}
.icon-url {
vertical-align: text-bottom;
}
}
</style>

View File

@@ -46,13 +46,13 @@ export default {
},
computed: {
cardTitle() {
return this.object.display_name
return this.object.name
},
detailCardItems() {
return [
{
key: this.$t('common.Name'),
value: this.object.display_name
value: this.object.name
},
{
key: this.$t('common.dateCreated'),

View File

@@ -25,9 +25,6 @@ export default {
return {
TaskDetail: {},
config: {
getTitle(row) {
return row['display_name']
},
activeMenu: 'TaskDetail',
submenu: [
{

View File

@@ -6,11 +6,6 @@
import { timeOffset, toSafeLocalDateStr } from '@/utils/common'
import { GenericListPage } from '@/layout/components'
import { openTaskPage } from '@/utils/jms'
const performDelete = function({ row }) {
const id = row.id
const url = `${this.url}${id}/`
return this.$axios.delete(url)
}
export default {
components: {
@@ -88,27 +83,6 @@ export default {
hasUpdate: false,
hasClone: false,
canDelete: this.$hasPerm('ops.delete_task'),
onDelete: function({ row, col, cellValue, reload }) {
const msg = this.$t('common.deleteWarningMsg') + ` "${row.display_name || row.name}" ` + '?'
const title = this.$t('common.Info')
this.$alert(msg, title, {
type: 'warning',
confirmButtonClass: 'el-button--danger',
showCancelButton: true,
beforeClose: async(action, instance, done) => {
if (action !== 'confirm') return done()
instance.confirmButtonLoading = true
try {
await performDelete.bind(this)({ row: row, col: col })
done()
reload()
this.$message.success(this.$t('common.deleteSuccessMsg'))
} finally {
instance.confirmButtonLoading = false
}
}
})
},
extraActions: [
{
name: 'run',

View File

@@ -33,8 +33,7 @@ export default {
title: this.$t('common.Active'),
type: 'switcher',
attrs: {
model: this.object.is_active,
disabled: !this.$hasPerm('perms.change_applicationpermission')
model: this.object.is_active
},
callbacks: {
change: function(val) {

View File

@@ -74,7 +74,7 @@ export default {
this.$message.error(this.$t('common.PleaseAgreeToTheTerms'))
return Promise.reject()
}
return this.$axios['patch'](this.url, validValues)
return this.$axios['put'](this.url, validValues)
},
onPerformSuccess() {
this.$message.success(this.$t('common.updateSuccessMsg'))

View File

@@ -1,11 +1,5 @@
<template>
<Page v-bind="$attrs">
<UserConfirmDialog
v-if="showPasswordDialog"
:visible.sync="showPasswordDialog"
@UserConfirmDone="verifyDone"
@UserConfirmCancel="exit"
/>
<div>
<el-row :gutter="20">
<el-col :md="14" :sm="24">
@@ -26,6 +20,28 @@
/>
</el-col>
</el-row>
<Dialog
width="50"
top="20vh"
:title="this.$t('common.PasswordConfirm')"
:visible.sync="showPasswordDialog"
:show-confirm="false"
:show-cancel="false"
:destroy-on-close="true"
>
<el-row :gutter="20">
<el-col :md="4" :sm="24">
<div style="line-height: 34px">{{ $t('assets.Password') }}</div>
</el-col>
<el-col :md="14" :sm="24">
<el-input v-model="passwordInput" type="password" />
<span class="help-tips help-block">{{ $t('common.PasswordRequireForSecurity') }}</span>
</el-col>
<el-col :md="4" :sm="24">
<el-button size="mini" type="primary" style="line-height:20px " @click="passConfirm">{{ this.$t('common.Confirm') }}</el-button>
</el-col>
</el-row>
</Dialog>
</div>
</Page>
</template>
@@ -34,7 +50,7 @@
import Page from '@/layout/components/Page'
import DetailCard from '@/components/DetailCard'
import QuickActions from '@/components/QuickActions'
import UserConfirmDialog from '@/components/UserConfirmDialog'
import Dialog from '@/components/Dialog'
import { toSafeLocalDateStr } from '@/utils/common'
import store from '@/store'
@@ -44,7 +60,7 @@ export default {
Page,
DetailCard,
QuickActions,
UserConfirmDialog
Dialog
},
props: {
object: {
@@ -56,6 +72,7 @@ export default {
return {
url: `/api/v1/users/profile/`,
showPasswordDialog: false,
passwordInput: '',
currentEdit: '',
authQuickActions: [
{
@@ -308,19 +325,23 @@ export default {
}
return backendList
},
verifyDone() {
if (!this.object[`is_${this.currentEdit}_bound`]) {
window.location.href = `/core/auth/${this.currentEdit}/qr/bind/?redirect_url=${this.$route.fullPath}`
} else {
this.$axios.post(`/api/v1/authentication/${this.currentEdit}/qr/unbind/`).then(res => {
this.$message.success(this.$t('common.updateSuccessMsg'))
this.$store.dispatch('users/getProfile')
})
}
passConfirm() {
this.$axios.post(
`/api/v1/authentication/password/verify/`, {
password: this.passwordInput
}
).then(res => {
if (!this.object[`is_${this.currentEdit}_bound`]) {
window.location.href = `/core/auth/${this.currentEdit}/qr/bind/?redirect_url=${this.$route.fullPath}`
} else {
this.$axios.post(`/api/v1/authentication/${this.currentEdit}/qr/unbind/`).then(res => {
this.$message.success(this.$t('common.updateSuccessMsg'))
this.$store.dispatch('users/getProfile')
})
}
})
this.passwordInput = ''
this.showPasswordDialog = false
},
exit() {
this.$emit('update:visible', false)
}
}
}

View File

@@ -2,7 +2,6 @@
<IBox>
<GenericCreateUpdateForm
:fields="fields"
:encrypted-fields="encryptedFields"
:fields-meta="fieldsMeta"
:initial="object"
:url="url"
@@ -18,7 +17,6 @@ import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm
import UserPassword from '@/components/FormFields/UserPassword'
import { IBox } from '@/components'
import rules from '@/components/DataForm/rules'
import { PasswordInput } from '@/components/FormFields'
export default {
name: 'PasswordUpdate',
@@ -36,11 +34,12 @@ export default {
return {
url: '/api/v1/users/profile/password/',
fields: ['old_password', 'new_password', 'new_password_again'],
encryptedFields: ['old_password', 'new_password', 'new_password_again'],
fieldsMeta: {
old_password: {
label: this.$t('users.OldPassword'),
component: PasswordInput
el: {
type: 'password'
}
},
new_password: {
label: this.$t('users.NewPassword'),
@@ -49,7 +48,9 @@ export default {
},
new_password_again: {
label: this.$t('users.ConfirmPassword'),
component: PasswordInput
el: {
type: 'password'
}
}
},
updateSuccessNextRoute: {

View File

@@ -31,12 +31,6 @@ export default {
]
},
columnsMeta: {
is_finished: {
width: '150px',
formatterArgs: {
showFalse: false
}
}
}
},
headerActions: {

View File

@@ -42,7 +42,7 @@ export default {
},
tip: ({ row }) => {
if (row.login_from === 'RT') {
return this.$t('sessions.RazorNotSupport')
return this.$t('sessions.XRDPNotSupport')
}
return ''
},

View File

@@ -21,9 +21,7 @@ export default {
settings: {
url: '/api/v1/settings/setting/?category=cas',
fields: [
[this.$t('common.Basic'), [
'AUTH_CAS', 'CAS_SERVER_URL', 'CAS_ROOT_PROXIED_AS', 'CAS_VERSION'
]],
[this.$t('common.Basic'), ['AUTH_CAS', 'CAS_SERVER_URL', 'CAS_ROOT_PROXIED_AS', 'CAS_VERSION']],
[this.$t('common.Other'), [
'CAS_LOGOUT_COMPLETELY', 'CAS_USERNAME_ATTRIBUTE',
'CAS_APPLY_ATTRIBUTES_TO_USER', 'CAS_RENAME_ATTRIBUTES',

View File

@@ -35,7 +35,6 @@ export default {
}
}
],
encryptedFields: ['DINGTALK_APPSECRET'],
fields: [
[
this.$t('common.BasicInfo'),

View File

@@ -37,7 +37,6 @@ export default {
}
}
],
encryptedFields: ['FEISHU_APP_SECRET'],
fields: [
[
this.$t('common.BasicInfo'),

View File

@@ -8,8 +8,7 @@
<script>
import BaseAuth from './Base'
import { JsonEditor, UpdateToken } from '@/components/FormFields'
import { JsonRequired } from '@/components/DataForm/rules'
import { UpdateToken } from '@/components/FormFields'
export default {
name: 'OIDC',
@@ -20,21 +19,21 @@ export default {
return {
settings: {
url: '/api/v1/settings/setting/?category=oidc',
encryptedFields: ['AUTH_OPENID_CLIENT_SECRET'],
fields: [
[this.$t('common.Basic'), [
'AUTH_OPENID', 'BASE_SITE_URL', 'AUTH_OPENID_CLIENT_ID',
'AUTH_OPENID_CLIENT_SECRET', 'AUTH_OPENID_CLIENT_AUTH_METHOD'
'AUTH_OPENID_CLIENT_SECRET'
]],
[this.$t('common.Params'), [
'AUTH_OPENID_KEYCLOAK', 'AUTH_OPENID_SERVER_URL', 'AUTH_OPENID_REALM_NAME',
'AUTH_OPENID_KEYCLOAK',
'AUTH_OPENID_SERVER_URL', 'AUTH_OPENID_REALM_NAME',
'AUTH_OPENID_PROVIDER_ENDPOINT', 'AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT',
'AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT', 'AUTH_OPENID_PROVIDER_JWKS_ENDPOINT',
'AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT', 'AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT',
'AUTH_OPENID_PROVIDER_SIGNATURE_ALG', 'AUTH_OPENID_PROVIDER_SIGNATURE_KEY',
'AUTH_OPENID_SCOPES', 'AUTH_OPENID_ID_TOKEN_MAX_AGE', 'AUTH_OPENID_ID_TOKEN_INCLUDE_CLAIMS',
'AUTH_OPENID_USE_STATE', 'AUTH_OPENID_USE_NONCE', 'AUTH_OPENID_ALWAYS_UPDATE_USER',
'AUTH_OPENID_IGNORE_SSL_VERIFICATION', 'AUTH_OPENID_SHARE_SESSION', 'AUTH_OPENID_USER_ATTR_MAP'
'AUTH_OPENID_IGNORE_SSL_VERIFICATION', 'AUTH_OPENID_SHARE_SESSION'
]]
],
fieldsMeta: {
@@ -108,24 +107,9 @@ export default {
'AUTH_OPENID_IGNORE_SSL_VERIFICATION': {
},
'AUTH_OPENID_SHARE_SESSION': {
},
'AUTH_OPENID_USER_ATTR_MAP': {
component: JsonEditor,
label: this.$t('setting.authLdapUserAttrMap'),
rules: [JsonRequired]
}
},
submitMethod: () => 'patch',
afterGetFormValue(obj) {
obj.AUTH_OPENID_USER_ATTR_MAP = JSON.stringify(obj.AUTH_OPENID_USER_ATTR_MAP)
return obj
},
cleanFormValue(data) {
if (data['AUTH_OPENID_USER_ATTR_MAP']) {
data['AUTH_OPENID_USER_ATTR_MAP'] = JSON.parse(data['AUTH_OPENID_USER_ATTR_MAP'])
}
return data
}
submitMethod: () => 'patch'
}
}
}

View File

@@ -19,7 +19,6 @@ export default {
return {
settings: {
url: '/api/v1/settings/setting/?category=radius',
encryptedFields: ['RADIUS_SECRET'],
fields: [
[this.$t('common.Basic'), ['AUTH_RADIUS', 'RADIUS_SERVER', 'RADIUS_PORT', 'RADIUS_SECRET']],
[this.$t('common.Other'), ['OTP_IN_RADIUS']]

View File

@@ -36,7 +36,6 @@ export default {
}
}
],
encryptedFields: ['WECOM_SECRET'],
fields: [
[
this.$t('common.BasicInfo'),

View File

@@ -23,13 +23,12 @@ export default {
fields: [
[
this.$t('common.BasicInfo'), [
'SITE_URL', // 'USER_GUIDE_URL',
'SITE_URL', 'USER_GUIDE_URL',
'GLOBAL_ORG_DISPLAY_NAME'
]
],
[
this.$t('setting.Feature'), [
'TICKETS_ENABLED',
'ANNOUNCEMENT_ENABLED'
]
]
@@ -43,11 +42,6 @@ export default {
return !this.$store.getters.hasValidLicense
}
},
TICKETS_ENABLED: {
hidden: () => {
return !this.$store.getters.hasValidLicense
}
},
ANNOUNCEMENT_ENABLED: {
// label: '公告',
component: Announcement

View File

@@ -31,7 +31,6 @@ export default {
data() {
return {
visible: false,
encryptedFields: ['EMAIL_HOST_PASSWORD'],
fields: [
[
this.$t('common.BasicInfo'),

View File

@@ -16,24 +16,43 @@
@error="handlerListTableXHRError($event)"
/>
<div slot="footer">
<el-button size="small" @click="hiddenDialog">{{ $t('common.Cancel') }}</el-button>
<el-button type="primary" size="small" :loading="dialogLdapUserImportLoginStatus" @click="importUserClick">{{ $t('common.Import') }}</el-button>
<el-button type="primary" size="small" :loading="dialogLdapUserImportAllLoginStatus" @click="importAllUserClick">{{ $t('common.ImportAll') }}</el-button>
<el-button @click="hiddenDialog">{{ $t('common.Cancel') }}</el-button>
<el-button type="primary" :loading="dialogLdapUserImportLoginStatus" @click="importUserClick">{{ $t('common.Import') }}</el-button>
<el-button type="primary" :loading="dialogLdapUserImportAllLoginStatus" @click="importAllUserClick">{{ $t('common.ImportAll') }}</el-button>
</div>
</Dialog>
<Dialog
:title="$t('setting.SyncSetting')"
:visible.sync="settings.visible"
:destroy-on-close="true"
:show-cancel="false"
:show-confirm="false"
width="50%"
top="10%"
>
<GenericCreateUpdateForm
v-bind="settings"
:has-detail-in-msg="false"
/>
</Dialog>
</div>
</template>
<script>
import ListTable from '@/components/ListTable'
import { GenericCreateUpdateForm } from '@/layout/components'
import Dialog from '@/components/Dialog'
import Select2 from '@/components/FormFields/Select2'
import { importLdapUser, refreshLdapUserCache, startLdapUserCache } from '@/api/settings'
import { CronTab } from '@/components'
import { Required } from '@/components/DataForm/rules'
export default {
name: 'ImportDialog',
components: {
ListTable,
Dialog
Dialog,
GenericCreateUpdateForm
},
data() {
return {
@@ -54,11 +73,21 @@ export default {
this.refreshed = true
}
reloadTable()
}
},
extraActions: [
{
name: 'setting',
title: this.$t('setting.SyncSetting'),
type: 'primary',
callback: function() {
this.settings.visible = true
}.bind(this)
}
]
},
tableConfig: {
url: '/api/v1/settings/ldap/users/',
columns: ['username', 'name', 'email', 'groups', 'existing'],
columns: ['username', 'name', 'email', 'existing'],
columnsMeta: {
username: {
label: this.$t('users.Username'),
@@ -68,13 +97,6 @@ export default {
label: this.$t('users.Name'),
width: '180px'
},
groups: {
label: this.$t('users.UserGroups'),
showOverflowTooltip: true,
formatter: function(row) {
return <span> {row.groups.join(' | ')} </span>
}
},
email: {
label: this.$t('users.Email')
},
@@ -83,6 +105,41 @@ export default {
width: '120px'
}
}
},
settings: {
visible: false,
url: '/api/v1/settings/setting/?category=ldap',
fields: ['AUTH_LDAP_SYNC_ORG_ID', 'AUTH_LDAP_SYNC_IS_PERIODIC', 'AUTH_LDAP_SYNC_CRONTAB', 'AUTH_LDAP_SYNC_INTERVAL'],
fieldsMeta: {
AUTH_LDAP_SYNC_ORG_ID: {
component: Select2,
rules: [Required],
el: {
multiple: false,
ajax: {
url: '/api/v1/orgs/orgs/',
transformOption: (item) => {
return { label: item.name, value: item.id }
}
}
},
hidden: (formValue) => {
return !this.$hasLicense()
}
},
AUTH_LDAP_SYNC_IS_PERIODIC: {
type: 'switch'
},
AUTH_LDAP_SYNC_CRONTAB: {
component: CronTab,
helpText: this.$t('xpack.HelpText.CrontabOfCreateUpdatePage')
},
AUTH_LDAP_SYNC_INTERVAL: {
rules: [Required],
helpText: this.$t('xpack.HelpText.IntervalOfCreateUpdatePage')
}
},
submitMethod: () => 'patch'
}
}
},

View File

@@ -1,75 +0,0 @@
<template>
<Dialog
:title="$t('setting.SyncSetting')"
:destroy-on-close="true"
:show-cancel="false"
:show-confirm="false"
width="50%"
top="10%"
v-bind="$attrs"
v-on="$listeners"
>
<GenericCreateUpdateForm
v-bind="settings"
:has-detail-in-msg="false"
/>
</Dialog>
</template>
<script>
import { GenericCreateUpdateForm } from '@/layout/components'
import { Dialog } from '@/components'
import Select2 from '@/components/FormFields/Select2'
import { Required } from '@/components/DataForm/rules'
import { CronTab } from '@/components'
export default {
name: 'SyncSettingDialog',
components: {
GenericCreateUpdateForm,
Dialog
},
data() {
return {
settings: {
visible: false,
url: '/api/v1/settings/setting/?category=ldap',
fields: ['AUTH_LDAP_SYNC_ORG_ID', 'AUTH_LDAP_SYNC_IS_PERIODIC', 'AUTH_LDAP_SYNC_CRONTAB', 'AUTH_LDAP_SYNC_INTERVAL'],
fieldsMeta: {
AUTH_LDAP_SYNC_ORG_ID: {
component: Select2,
rules: [Required],
el: {
multiple: false,
ajax: {
url: '/api/v1/orgs/orgs/',
transformOption: (item) => {
return { label: item.name, value: item.id }
}
}
},
hidden: (formValue) => {
return !this.$hasLicense()
}
},
AUTH_LDAP_SYNC_IS_PERIODIC: {
type: 'switch'
},
AUTH_LDAP_SYNC_CRONTAB: {
component: CronTab,
helpText: this.$t('xpack.HelpText.CrontabOfCreateUpdatePage')
},
AUTH_LDAP_SYNC_INTERVAL: {
rules: [Required],
helpText: this.$t('xpack.HelpText.IntervalOfCreateUpdatePage')
}
},
submitMethod: () => 'patch'
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -3,7 +3,6 @@
<GenericCreateUpdateForm v-bind="$data" />
<ImportDialog :visible.sync="dialogLdapUserImport" />
<TestLoginDialog :visible.sync="dialogTest" />
<SyncSettingDialog :visible.sync="dialogSyncSetting" />
</IBox>
</template>
<script>
@@ -11,7 +10,6 @@ import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm
import { testLdapSetting } from '@/api/settings'
import ImportDialog from './ImportDialog'
import TestLoginDialog from './TestLoginDialog'
import SyncSettingDialog from './SyncSettingDialog'
import { IBox } from '@/components'
import rules from '@/components/DataForm/rules'
import { JsonRequired } from '@/components/DataForm/rules'
@@ -23,15 +21,12 @@ export default {
GenericCreateUpdateForm,
IBox,
ImportDialog,
TestLoginDialog,
SyncSettingDialog
TestLoginDialog
},
data() {
return {
dialogTest: false,
dialogLdapUserImport: false,
dialogSyncSetting: false,
encryptedFields: ['AUTH_LDAP_BIND_PASSWORD'],
fields: [
[
this.$t('setting.LDAPServerInfo'),
@@ -100,12 +95,6 @@ export default {
callback: function(value, form) {
this.dialogLdapUserImport = true
}.bind(this)
},
{
title: this.$t('setting.SyncSetting'),
callback: function(value, form) {
this.dialogSyncSetting = true
}.bind(this)
}
],
submitMethod: () => 'patch',

View File

@@ -101,16 +101,8 @@ export default {
).then(newSub => {
const msgType = this.idMessageTypeMapper[newSub.message_type]
msgType.receivers = newSub.receivers
this.tableData.forEach(i => {
for (const item of i.children) {
if (item.id === newSub.message_type) {
item.receivers = newSub.receivers
break
}
}
})
}).catch(() => {
// debug(err)
}).catch(err => {
console.log(err)
})
},
getNameDisplay(header) {

View File

@@ -27,9 +27,9 @@ export default {
]
],
[
`RDP ${comp}(Razor)`,
`RDP ${comp}(XRDP)`,
[
'TERMINAL_RAZOR_ENABLED'
'XRDP_ENABLED'
]
],
[
@@ -46,7 +46,7 @@ export default {
TERMINAL_TELNET_REGEX: {
type: 'input'
},
TERMINAL_RAZOR_ENABLED: {
XRDP_ENABLED: {
helpText: this.$i18n.t('common.Info') + ': ' + this.$i18n.t('setting.SettingInEndpointHelpText'),
hidden: () => {
return !this.$store.getters.hasValidLicense

View File

@@ -21,10 +21,7 @@ export default {
[this.$t('common.Basic'), ['name', 'host']],
[
this.$t('applications.port'),
[
'http_port', 'https_port', 'ssh_port', 'rdp_port',
'mysql_port', 'mariadb_port', 'postgresql_port', 'redis_port'
]
['http_port', 'https_port', 'ssh_port', 'rdp_port', 'mysql_port', 'mariadb_port', 'postgresql_port']
],
[this.$t('common.Other'), ['comment']]
],

View File

@@ -18,7 +18,7 @@ export default {
'name', 'host',
'http_port', 'https_port', 'ssh_port',
'rdp_port', 'mysql_port', 'mariadb_port',
'postgresql_port', 'redis_port',
'postgresql_port',
'date_created', 'comment', 'actions'
],
columnsShow: {

View File

@@ -89,7 +89,6 @@ export default {
guacamole: 'Guacamole',
lion: 'Lion',
xrdp: 'XRDP',
razor: 'Razor',
core: 'Core',
celery: 'Celery',
magnus: 'Magnus'

View File

@@ -31,7 +31,7 @@ export default {
helpText: this.$t('sessions.SetToDefaultStorage')
},
meta: {
fields: ['HOSTS', 'INDEX_BY_DATE', 'INDEX', 'IGNORE_VERIFY_CERTS'],
fields: ['HOSTS', 'INDEX', 'IGNORE_VERIFY_CERTS'],
fieldsMeta: {
HOSTS: {
helpText: this.$t('sessions.helpText.esUrl')
@@ -61,7 +61,6 @@ export default {
return validValues
},
cleanFormValue(value) {
value.meta.INDEX = value.meta?.INDEX?.toLowerCase()
value.meta.HOSTS = value.meta.HOSTS.split(',').map(item => (item.trim()))
return value
}

View File

@@ -11,7 +11,6 @@
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import { STORAGE_TYPE_META_MAP } from '../../../sessions/const'
import { UpdateToken } from '@/components/FormFields'
import { encryptPassword } from '@/utils/crypto'
export default {
name: 'ReplayStorageUpdate',
@@ -49,7 +48,7 @@ export default {
meta: {
fields: storageTypeMeta.meta,
fieldsMeta: {
SECRET_KEY: {
ACCESS_KEY: {
component: UpdateToken
}
}
@@ -57,19 +56,16 @@ export default {
is_default: {
helpText: this.$t('sessions.SetToDefaultStorage')
}
},
cleanFormValue(values) {
const encryptedFields = ['SECRET_KEY', 'ACCOUNT_KEY']
const meta = values.meta
for (const item of encryptedFields) {
const val = meta[item]
if (val) {
meta[item] = encryptPassword(val)
}
}
return values
}
}
},
computed: {
},
mounted() {
},
methods: {
}
}
</script>

View File

@@ -79,7 +79,6 @@ export default {
onSubmit: function(validValues) {
const url = '/api/v1/terminal/terminals/'
const msg = this.$t('common.updateSuccessMsg')
validValues = Object.values(validValues)
this.$axios.patch(url, validValues).then((res) => {
this.$message.success(msg)
this.dialogSettings.visible = false
@@ -109,16 +108,11 @@ export default {
},
columns: [
'name', 'remote_addr', 'session_online',
'stat.cpu_load', 'stat.disk_used', 'stat.memory_used',
'status', 'is_active', 'is_alive', 'actions'
'stat.cpu_load',
'stat.disk_used', 'stat.memory_used',
'status',
'is_active', 'is_alive', 'actions'
],
columnsShow: {
min: ['name'],
default: [
'name', 'session_online', 'stat.cpu_load', 'stat.disk_used',
'stat.memory_used', 'status'
]
},
columnsMeta: {
name: {
sortable: 'custom',
@@ -180,6 +174,7 @@ export default {
},
headerActions: {
hasCreate: false,
hasBulkDelete: false,
hasUpload: false,
hasExport: false,
hasImport: false,

Some files were not shown because too many files have changed in this diff Show More