Compare commits

..

3 Commits

Author SHA1 Message Date
ibuler
48beb502bd fix(settings): 修复系统设置中邮件没有更改就无法更新的问题 2021-06-28 19:04:49 +08:00
ibuler
383a799c6a fix: 修复会话详情中播放bug 2021-06-18 18:07:45 +08:00
ibuler
c378b2bf0d fix(infra): 修复基础平台等可以删除更新问题 2021-06-18 11:05:17 +08:00
160 changed files with 2920 additions and 3244 deletions

View File

@@ -23,5 +23,4 @@ VUE_APP_LOGOUT_PATH = '/core/auth/logout/'
# Dev server for core proxy
VUE_APP_CORE_HOST = 'http://localhost:8080'
VUE_APP_CORE_WS = 'ws://localhost:8070'
VUE_APP_ENV = 'development'

View File

@@ -12,8 +12,8 @@ RUN npm config set sass_binary_site=${SASS_BINARY_SITE}
RUN npm config set registry ${NPM_REGISTRY}
RUN yarn config set registry ${NPM_REGISTRY}
COPY package.json yarn.lock /data/
RUN yarn install
RUN npm rebuild node-sass
COPY utils /data/utils/
RUN ls && cd utils && bash -xieu build.sh dep
ADD . /data
RUN cd utils && bash -xieu build.sh build

BIN
dump.rdb Normal file

Binary file not shown.

View File

@@ -57,26 +57,6 @@ export function TestReplayStorage(id) {
})
}
function SetToDefaultStorage(url) {
return request({
url: url,
method: 'patch',
data: { 'is_default': true }
})
}
export function SetToDefaultCommandStorage(id) {
return SetToDefaultStorage(
`/api/v1/terminal/command-storages/${id}/`,
)
}
export function SetToDefaultReplayStorage(id) {
return SetToDefaultStorage(
`/api/v1/terminal/replay-storages/${id}/`,
)
}
export function getReplayStorage(id) {
return request({
url: `/api/v1/terminal/replay-storages/${id}/`,

View File

@@ -1,83 +0,0 @@
<template>
<div>
<MFAVerifyDialog
@MFAVerifyDone="getAuthInfo"
@MFAVerifyCancel="exit"
/>
<Dialog
:title="dialogTitle"
:show-confirm="false"
:show-cancel="false"
:destroy-on-close="true"
:width="'50'"
:visible.sync="showAuthInfo"
v-bind="$attrs"
v-on="$listeners"
>
<div>
<el-form label-position="right" label-width="80px" :model="authInfo">
<el-form-item :label="this.$t('assets.Hostname')">
<el-input v-model="account.hostname" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Username')">
<el-input v-model="account['username']" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Password')">
<el-input v-model="authInfo.password" type="password" show-password />
</el-form-item>
<el-form-item :label="this.$t('assets.SSHKey')">
<el-input v-model="authInfo['private_key']" type="password" show-password />
</el-form-item>
</el-form>
</div>
</Dialog>
</div>
</template>
<script>
import Dialog from '@/components/Dialog'
import MFAVerifyDialog from '@/components/MFAVerifyDialog'
export default {
name: 'ShowSecretInfo',
components: {
Dialog,
MFAVerifyDialog
},
props: {
account: {
type: Object,
default: () => ({})
},
visible: {
type: Boolean,
default: false
}
},
data() {
return {
dialogTitle: this.$t('common.ViewSecret'),
authInfo: {},
showAuthInfo: false
}
},
mounted() {
this.getAuthInfo()
},
methods: {
getAuthInfo() {
const url = `/api/v1/assets/account-secrets/${this.account.id}/`
this.$axios.get(url, { disableFlashErrorMsg: true }).then(resp => {
this.authInfo = resp
this.showAuthInfo = true
})
},
exit() {
this.$emit('update:visible', false)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,92 +0,0 @@
<template>
<Dialog
width="50"
:title="this.$t('assets.UpdateAssetUserToken')"
:destroy-on-close="true"
v-bind="$attrs"
@confirm="handleConfirm()"
@cancel="handleCancel()"
v-on="$listeners"
>
<el-form label-position="right" label-width="80px">
<el-form-item :label="this.$t('assets.Hostname')">
<el-input v-model="account.hostname" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Username')">
<el-input v-model="account['username']" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Password')">
<el-input v-model="authInfo.password" type="password" />
</el-form-item>
<el-form-item :label="this.$t('assets.SSHKey')">
<input type="file" @change="onPrivateKeyLoaded">
</el-form-item>
</el-form>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
export default {
name: 'UpdateSecretInfo',
components: {
Dialog
},
props: {
account: {
type: Object,
default: () => ({})
}
},
data() {
return {
authInfo: {
password: '',
private_key: ''
}
}
},
methods: {
handleConfirm() {
const data = {}
if (this.authInfo.password !== '') {
data.password = this.authInfo.password
}
if (this.authInfo.private_key !== '') {
data.private_key = this.authInfo.private_key
}
this.$axios.patch(
`/api/v1/assets/accounts/${this.account.id}/`,
data
).then(res => {
this.authInfo = { password: '', private_key: '' }
this.$message.success(this.$tc('common.updateSuccessMsg'))
this.$emit('updateAuthDone', res)
this.$emit('update:visible', false)
}).catch(err => {
const errMsg = Object.values(err.response.data).join(', ')
this.$message.error(this.$tc('common.updateErrorMsg') + ' ' + errMsg)
this.$emit('update:visible', true)
})
},
handleCancel() {
this.$emit('update:visible', false)
},
onPrivateKeyLoaded(e) {
const vm = this
// TODO 校验文件类型
const reader = new FileReader()
reader.onload = function() {
vm.authInfo.private_key = this.result
}
reader.readAsText(
e.target.files[0]
)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,31 +0,0 @@
import { ChoicesFormatter } from '@/components/TableFormatters'
import { toSafeLocalDateStr } from '@/utils/common'
import i18n from '@/i18n/i18n'
export const connectivityMeta = {
label: i18n.t('assets.Reachable'),
formatter: ChoicesFormatter,
formatterArgs: {
iconChoices: {
ok: 'fa-check text-primary',
failed: 'fa-times text-danger',
unknown: 'fa-circle text-warning'
},
hasTips: true,
getTips: ({ row, cellValue }) => {
const mapper = {
'ok': i18n.t('assets.Reachable'),
'failed': i18n.t('assets.Unreachable'),
'unknown': i18n.t('assets.Unknown')
}
let tips = mapper[cellValue]
if (row['date_verified']) {
const datetime = toSafeLocalDateStr(row['date_verified'])
tips += '<br> ' + datetime
}
return tips
}
},
width: '90px',
align: 'center'
}

View File

@@ -1,184 +0,0 @@
<template>
<div>
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
<ShowSecretInfo v-if="showViewSecretDialog" :visible.sync="showViewSecretDialog" :account="account" />
<UpdateSecretInfo :visible.sync="showUpdateSecretDialog" :account="account" @updateAuthDone="onUpdateAuthDone" />
</div>
</template>
<script>
import ListTable from '@/components/ListTable/index'
import { ActionsFormatter, DetailFormatter, DisplayFormatter } from '@/components/TableFormatters'
import ShowSecretInfo from './ShowSecretInfo'
import UpdateSecretInfo from './UpdateSecretInfo'
import { connectivityMeta } from './const'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'AccountListTable',
components: {
ListTable,
UpdateSecretInfo,
ShowSecretInfo
},
props: {
url: {
type: String,
required: true
},
exportUrl: {
type: String,
default() {
return this.url.replace('/assets/accounts/', '/assets/account-secrets/')
}
},
hasLeftActions: {
type: Boolean,
default: false
},
otherActions: {
type: Array,
default: null
},
hasClone: {
type: Boolean,
default: false
}
},
data() {
return {
showViewSecretDialog: false,
showUpdateSecretDialog: false,
account: {},
tableConfig: {
url: this.url,
columns: [
'hostname', 'ip', 'username', 'version', 'connectivity',
'systemuser', 'date_created', 'date_updated', 'actions'
],
columnsShow: {
min: ['username', 'actions'],
default: ['hostname', 'ip', 'username', 'version', 'actions']
},
columnsMeta: {
hostname: {
prop: 'hostname',
label: this.$t('assets.Hostname'),
showOverflowTooltip: true,
formatter: DetailFormatter,
formatterArgs: {
getRoute({ row }) {
return {
name: 'AssetDetail',
params: { id: row.asset }
}
}
}
},
ip: {
width: '120px'
},
username: {
showOverflowTooltip: true
},
systemuser: {
formatter: DisplayFormatter
},
version: {
width: '70px'
},
connectivity: connectivityMeta,
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: false, // can set function(row, value)
hasDelete: false, // can set function(row, value)
hasClone: this.hasClone,
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'View',
title: this.$t('common.View'),
type: 'primary',
callback: function({ row }) {
this.account = row
this.showViewSecretDialog = true
}.bind(this)
},
{
name: 'Delete',
title: this.$t('common.Delete'),
type: 'primary',
callback: ({ row }) => {
this.$axios.delete(`/api/v1/assets/accounts/${row.id}/`).then(() => {
this.$message.success(this.$tc('common.deleteSuccessMsg'))
this.$refs.ListTable.reloadTable()
})
}
},
{
name: 'Test',
title: this.$t('common.Test'),
callback: ({ row }) => {
this.$axios.post(
`/api/v1/assets/accounts/${row.id}/verify/`,
{ action: 'test' }
).then(res => {
openTaskPage(res['task'])
})
}
},
{
name: 'Update',
title: this.$t('common.Update'),
can: !this.$store.getters.currentOrgIsRoot,
callback: function({ row }) {
this.account = row
this.showUpdateSecretDialog = true
}.bind(this)
}
]
}
}
}
},
headerActions: {
hasLeftActions: this.hasLeftActions,
hasMoreActions: false,
hasImport: false,
hasExport: true,
exportOptions: {
url: this.exportUrl,
mfaVerifyRequired: true
},
searchConfig: {
exclude: ['systemuser', 'asset']
},
hasSearch: true
}
}
},
watch: {
url(iNew) {
this.$set(this.tableConfig, 'url', iNew)
}
},
mounted() {
if (this.otherActions) {
const actionColumn = this.tableConfig.columns[this.tableConfig.columns.length - 1]
for (const item of this.otherActions) {
actionColumn.formatterArgs.extraActions.push(item)
}
}
},
methods: {
onUpdateAuthDone(account) {
Object.assign(this.account, account)
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -1,81 +0,0 @@
<template>
<div>
<MFAVerifyDialog
@MFAVerifyDone="getAuthInfo"
@MFAVerifyCancel="exit"
/>
<Dialog
:title="dialogTitle"
:show-confirm="false"
:show-cancel="false"
:destroy-on-close="true"
:width="'50'"
:visible.sync="showAuthInfo"
v-bind="$attrs"
v-on="$listeners"
>
<div>
<el-form label-position="right" label-width="80px" :model="authInfo">
<el-form-item :label="this.$t('applications.appName')">
<el-input v-model="account['app_name']" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Username')">
<el-input v-model="account['username']" readonly />
</el-form-item>
<el-form-item :label="this.$t('assets.Password')">
<el-input v-model="authInfo.password" type="password" show-password />
</el-form-item>
</el-form>
</div>
</Dialog>
</div>
</template>
<script>
import Dialog from '@/components/Dialog'
import MFAVerifyDialog from '@/components/MFAVerifyDialog'
export default {
name: 'ShowSecretInfo',
components: {
Dialog,
MFAVerifyDialog
},
props: {
account: {
type: Object,
default: () => ({})
},
visible: {
type: Boolean,
default: false
}
},
data() {
return {
dialogTitle: this.$t('common.ViewSecret'),
authInfo: {},
showAuthInfo: false
}
},
mounted() {
this.getAuthInfo()
},
methods: {
getAuthInfo() {
console.log(this.account)
const url = `/api/v1/applications/account-secrets/${this.account.uid}/`
this.$axios.get(url, { disableFlashErrorMsg: true }).then(resp => {
this.authInfo = resp
this.showAuthInfo = true
})
},
exit() {
this.$emit('update:visible', false)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,31 +0,0 @@
import { ChoicesFormatter } from '@/components/TableFormatters'
import { toSafeLocalDateStr } from '@/utils/common'
import i18n from '@/i18n/i18n'
export const connectivityMeta = {
label: i18n.t('assets.Reachable'),
formatter: ChoicesFormatter,
formatterArgs: {
iconChoices: {
ok: 'fa-check text-primary',
failed: 'fa-times text-danger',
unknown: 'fa-circle text-warning'
},
hasTips: true,
getTips: ({ row, cellValue }) => {
const mapper = {
'ok': i18n.t('assets.Reachable'),
'failed': i18n.t('assets.Unreachable'),
'unknown': i18n.t('assets.Unknown')
}
let tips = mapper[cellValue]
if (row['date_verified']) {
const datetime = toSafeLocalDateStr(row['date_verified'])
tips += '<br> ' + datetime
}
return tips
}
},
width: '90px',
align: 'center'
}

View File

@@ -1,167 +0,0 @@
<template>
<div>
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
<ShowSecretInfo v-if="showViewSecretDialog" :visible.sync="showViewSecretDialog" :account="account" />
</div>
</template>
<script>
import ListTable from '@/components/ListTable/index'
import { ActionsFormatter, DetailFormatter } from '@/components/TableFormatters'
import ShowSecretInfo from './ShowSecretInfo'
export default {
name: 'Detail',
components: {
ListTable,
ShowSecretInfo
},
props: {
url: {
type: String,
required: true
},
exportUrl: {
type: String,
default() {
return this.url.replace('/applications/accounts/', '/applications/account-secrets/')
}
},
hasLeftActions: {
type: Boolean,
default: false
},
otherActions: {
type: Array,
default: null
},
hasClone: {
type: Boolean,
default: false
}
},
data() {
return {
showViewSecretDialog: false,
showUpdateSecretDialog: false,
account: {},
tableConfig: {
url: this.url,
columns: [
'app_name', 'username', 'category_display',
'type_display', 'systemuser', 'actions'
],
columnsMeta: {
app_name: {
showOverflowTooltip: true,
formatter: DetailFormatter,
formatterArgs: {
getRoute({ row }) {
switch (row['category']) {
case 'remote_app':
return {
name: 'RemoteAppDetail',
params: { id: row.app }
}
case 'db':
return {
name: 'DatabaseAppDetail',
params: { id: row.app }
}
default:
return {
name: 'KubernetesAppDetail',
params: { id: row.app }
}
}
}
}
},
username: {
showOverflowTooltip: true
},
systemuser: {
showOverflowTooltip: true,
formatter: DetailFormatter,
formatterArgs: {
getTitle({ row }) {
return row.systemuser_display
},
getRoute({ row }) {
return {
name: 'SystemUserDetail',
params: { id: row.systemuser }
}
}
}
},
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: false, // can set function(row, value)
hasDelete: false, // can set function(row, value)
hasClone: this.hasClone,
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'View',
title: this.$t('common.View'),
type: 'primary',
callback: function({ row }) {
this.account = row
this.showViewSecretDialog = true
}.bind(this)
},
{
name: 'Update',
title: this.$t('common.Update'),
can: !this.$store.getters.currentOrgIsRoot,
callback: function({ row }) {
this.$message.success(this.$tc('applications.updateAccountMsg'))
}.bind(this)
}
]
}
}
}
},
headerActions: {
hasLeftActions: this.hasLeftActions,
hasMoreActions: false,
hasImport: false,
hasExport: true,
exportOptions: {
url: this.exportUrl,
mfaVerifyRequired: true
},
searchConfig: {
exclude: ['systemuser', 'asset']
},
hasSearch: true
}
}
},
watch: {
url(iNew) {
this.$set(this.tableConfig, 'url', iNew)
}
},
mounted() {
if (this.otherActions) {
const actionColumn = this.tableConfig.columns[this.tableConfig.columns.length - 1]
for (const item of this.otherActions) {
actionColumn.formatterArgs.extraActions.push(item)
}
}
},
methods: {
onUpdateAuthDone(account) {
Object.assign(this.account, account)
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -76,7 +76,7 @@ export default {
select2Config: select2Config,
dialogSelect2Config: select2Config,
tableConfig: {
url: '/api/v1/assets/assets/?fields_size=mini',
url: '/api/v1/assets/assets/',
hasTree: true,
canSelect: this.canSelect,
columns: [
@@ -94,18 +94,6 @@ export default {
prop: 'ip',
label: this.$t('assets.ipDomain'),
sortable: 'custom'
},
{
prop: 'platform',
label: this.$t('assets.Platform'),
sortable: true
},
{
prop: 'protocols',
formatter: function(row) {
return <span> {row.protocols.toString()} </span>
},
label: this.$t('assets.Protocols')
}
],
listeners: {

View File

@@ -0,0 +1,529 @@
<template>
<div>
<div>
<ListTable ref="ListTable" :table-config="iTableConfig" :header-actions="headerActions" />
<Dialog v-if="showMFADialog" width="50" :title="this.$t('common.MFAConfirm')" :visible.sync="showMFADialog" :show-confirm="false" :show-cancel="false" :destroy-on-close="true">
<div v-if="MFAConfirmed">
<el-form label-position="right" label-width="80px" :model="MFAInfo">
<el-form-item :label="this.$t('assets.Hostname')">
<el-input v-model="MFAInfo.hostname" disabled />
</el-form-item>
<el-form-item :label="this.$t('assets.Username')">
<el-input v-model="MFAInfo.username" disabled />
</el-form-item>
<el-form-item :label="this.$t('assets.Password')">
<el-input v-model="MFAInfo.password" type="password" show-password />
</el-form-item>
</el-form>
</div>
<el-row v-else :gutter="20">
<el-col :span="4">
<div style="line-height: 34px;text-align: center">MFA</div>
</el-col>
<el-col :span="14">
<el-input v-model="MFAInput" />
<span class="help-tips help-block">{{ $t('common.MFARequireForSecurity') }}</span>
</el-col>
<el-col :span="4">
<el-button size="mini" type="primary" style="line-height:20px " @click="MFAConfirm">{{ this.$t('common.Confirm') }}</el-button>
</el-col>
</el-row>
</Dialog>
<Dialog width="50" :title="this.$t('assets.UpdateAssetUserToken')" :visible.sync="showDialog" @confirm="handleConfirm()" @cancel="handleCancel()">
<el-form label-position="right" label-width="80px" :model="dialogInfo">
<el-form-item :label="this.$t('assets.Hostname')">
<el-input v-model="dialogInfo.hostname" disabled />
</el-form-item>
<el-form-item :label="this.$t('assets.Username')">
<el-input v-model="dialogInfo.username" disabled />
</el-form-item>
<el-form-item :label="this.$t('assets.Password')">
<el-input v-model="dialogInfo.password" type="password" />
</el-form-item>
<el-form-item :label="this.$t('assets.sshkey')">
<input type="file" @change="Onchange">
</el-form-item>
</el-form>
</Dialog>
<Dialog :title="$t('common.Export')" :visible.sync="showExportDialog" :destroy-on-close="true" @confirm="handleExportConfirm()" @cancel="handleExportCancel()">
<el-form label-position="left" style="padding-left: 50px">
<el-form-item :label="$t('common.fileType' )" :label-width="'100px'">
<el-radio-group v-model="exportTypeOption">
<el-radio v-for="option of exportTypeOptions" :key="option.value" style="padding: 10px 20px;" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="this.$t('common.imExport.ExportRange')" :label-width="'100px'">
<el-radio-group v-model="exportOption">
<el-radio v-for="option of exportOptions" :key="option.value" class="export-item" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</Dialog>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import ListTable from '@/components/ListTable/index'
import Dialog from '@/components/Dialog'
import { createSourceIdCache } from '@/api/common'
import * as queryUtil from '@/components/DataTable/compenents/el-data-table/utils/query'
import { ActionsFormatter, DateFormatter } from '@/components/TableFormatters'
export default {
name: 'Detail',
components: {
ListTable,
Dialog
},
props: {
url: {
type: String,
required: true
},
searchExclude: {
type: Array,
default: () => []
},
extraQuery: {
type: Object,
default: () => ({})
},
canExportAll: {
type: Boolean,
default: true
},
canExportSelected: {
type: Boolean,
default: true
},
canExportFiltered: {
type: Boolean,
default: true
},
hasLeftActions: {
type: Boolean,
default: false
},
otherActions: {
type: Array,
default: null
},
handleExport: {
type: Function,
default: null
},
handleImport: {
type: Function,
default: null
},
hasImport: {
type: Boolean,
default: true
},
hasExport: {
type: Boolean,
default: true
},
hasClone: {
type: Boolean,
default: true
},
tableConfig: {
type: Object,
default: () => ({})
}
},
data() {
return {
MFAConfirmed: false,
MFAInput: '',
MFAInfo: {
asset: '',
username: '',
hostname: '',
password: ''
},
showDialog: false,
showMFADialog: false,
dialogInfo: {
asset: '',
username: '',
hostname: '',
password: '',
private_key: ''
},
selectedRows: '',
dialogStatus: '',
showExportDialog: false,
exportOption: 'all',
exportTypeOption: 'csv',
defaultTableConfig: {
url: this.url,
columns: ['hostname', 'ip', 'username', 'version', 'date_created', 'actions'],
columnsMeta: {
'hostname': {
label: this.$t('assets.Hostname'),
showOverflowTooltip: true
},
'ip': {
label: this.$t('assets.ip'),
width: '120px'
},
'username': {
label: this.$t('assets.Username'),
showOverflowTooltip: true
},
'version': {
label: this.$t('assets.Version'),
width: '70px'
},
'date_created': {
label: this.$t('assets.date_joined'),
formatter: DateFormatter
},
'actions': {
label: this.$t('common.Action'),
align: 'center',
width: 150,
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: false, // can set function(row, value)
hasDelete: false, // can set function(row, value)
hasClone: this.hasClone,
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'View',
title: this.$t('common.View'),
type: 'primary',
callback: function(val) {
this.dialogStatus = 'viewAutoInfo'
this.MFAInfo.asset = val.row.id
if (!this.needMFAVerify) {
this.showMFADialog = true
this.MFAConfirmed = true
this.$axios.get(`/api/v1/assets/asset-user-auth-infos/${this.MFAInfo.asset}/`).then(res => {
this.MFAConfirmed = true
this.MFAInfo.hostname = res.hostname
this.MFAInfo.password = res.password
this.MFAInfo.username = res.username
})
} else {
this.showMFADialog = true
}
}.bind(this)
},
{
name: 'Delete',
title: this.$t('common.Delete'),
type: 'primary',
callback: (val) => {
this.$axios.delete(`/api/v1/assets/asset-users/${val.row.id}/`).then(() => {
this.$message.success(this.$t('common.deleteSuccessMsg'))
this.$refs.ListTable.reloadTable()
})
}
},
{
name: 'Test',
title: this.$t('common.Test'),
callback: (val) => {
this.$axios.post(
`/api/v1/assets/asset-users/tasks/?id=${val.row.id}`,
{ action: 'test' }
).then(res => {
window.open(`/#/ops/celery/task/${res.task}/log/`, '', 'width=900,height=600')
})
}
},
{
name: 'Update',
title: this.$t('common.Update'),
can: !this.$store.getters.currentOrgIsRoot,
callback: function(val) {
this.showDialog = true
this.dialogInfo.asset = val.row.asset
this.dialogInfo.hostname = val.row.hostname
this.dialogInfo.username = val.row.username
}.bind(this)
}
]
}
}
},
extraQuery: this.extraQuery || { latest: 1 }
},
headerActions: {
hasLeftActions: this.hasLeftActions,
hasMoreActions: false,
hasImport: this.hasImport,
hasExport: this.hasExport,
hasSearch: true,
searchConfig: {
exclude: this.searchExclude,
options: [
{
label: this.$t('assets.OnlyLatestVersion'),
value: 'latest',
children: [
{
label: this.$t('common.Yes'),
value: 1
},
{
label: this.$t('common.No'),
value: 0
}
]
}
]
}
}
}
},
computed: {
...mapGetters([
'MFA_TTl',
'MFAVerifyAt',
'publicSettings'
]),
needMFAVerify() {
if (!this.publicSettings.SECURITY_VIEW_AUTH_NEED_MFA) {
return false
}
const ttl = this.publicSettings.SECURITY_MFA_VERIFY_TTL
const now = new Date()
return !(this.MFAVerifyAt && (now - this.MFAVerifyAt) < ttl * 1000)
},
iTableConfig() {
const columnsMeta = Object.assign({}, this.defaultTableConfig.columnsMeta, this.tableConfig.columnsMeta || {})
const config = Object.assign(this.defaultTableConfig, this.tableConfig)
config.columnsMeta = columnsMeta
return config
},
exportOptions() {
return [
{
label: this.$t('common.imExport.ExportAll'),
value: 'all',
can: this.canExportAll
},
{
label: this.$t('common.imExport.ExportOnlySelectedItems'),
value: 'selected',
can: this.selectedRows.length > 0 && this.canExportSelected
},
{
label: this.$t('common.imExport.ExportOnlyFiltered'),
value: 'filtered',
can: this.tableHasQuery() && this.canExportFiltered
}
]
},
exportTypeOptions() {
return [
{
label: 'CSV',
value: 'csv',
can: true
},
{
label: 'Excel',
value: 'xlsx',
can: true
}
]
}
},
watch: {
url(iNew) {
this.$set(this.iTableConfig, 'url', iNew)
}
},
mounted() {
if (this.otherActions) {
const actionColumn = this.iTableConfig.columns[this.iTableConfig.columns.length - 1]
for (const item of this.otherActions) {
actionColumn.formatterArgs.extraActions.push(item)
}
}
},
created() {
this.headerActions.handleExport = this.handleExport || this.defaultHandleExport
if (this.handleImport) {
this.headerActions.handleImport = this.handleImport
}
},
methods: {
MFAConfirm() {
if (this.MFAInput.length !== 6) {
return this.$message.error(this.$t('common.MFAErrorMsg'))
}
this.$axios.post(
`/api/v1/authentication/otp/verify/`, {
code: this.MFAInput
}
).then(
res => {
this.$store.dispatch('users/setMFAVerify')
if (this.dialogStatus === 'export') {
this.showMFADialog = false
this.showExportDialog = true
} else {
this.$axios.get(`/api/v1/assets/asset-user-auth-infos/${this.MFAInfo.asset}/`).then(res => {
this.MFAConfirmed = true
this.MFAInfo.hostname = res.hostname
this.MFAInfo.password = res.password
this.MFAInfo.username = res.username
})
}
}
)
},
handleMFAConfirm() {
this.MFAInfo = {
asset: '',
username: '',
hostname: '',
password: ''
}
this.MFAInput = ''
this.showMFADialog = false
this.MFAConfirmed = false
},
handleCancel() {
this.dialogInfo = {
asset: '',
username: '',
hostname: '',
password: '',
private_key: ''
}
this.showDialog = false
this.$refs.ListTable.reloadTable()
},
Onchange(e) {
const vm = this
// TODO 校验文件类型
const reader = new FileReader()
reader.onload = function() {
vm.dialogInfo.private_key = this.result
}
reader.readAsText(
e.target.files[0]
)
},
handleConfirm() {
const data = {
asset: this.dialogInfo.asset,
username: this.dialogInfo.username
}
if (this.dialogInfo.password !== '') {
data.password = this.dialogInfo.password
}
if (this.dialogInfo.private_key !== '') {
data.private_key = this.dialogInfo.private_key
}
this.$axios.post(
`/api/v1/assets/asset-users/`,
data
).then(res => {
this.$message.success(this.$t('common.updateSuccessMsg'))
}).catch(err => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + err))
})
this.dialogInfo = {
asset: '',
username: '',
hostname: '',
password: '',
private_key: ''
}
this.showDialog = false
this.$refs.ListTable.reloadTable()
},
tableQuery() {
const listTableRef = this.$refs.ListTable
if (!listTableRef) {
return {}
}
const query = listTableRef.dataTable.getQuery()
delete query['limit']
delete query['offset']
delete query['date_from']
delete query['date_to']
return query
},
tableHasQuery() {
return Object.keys(this.tableQuery()).length > 0
},
defaultHandleExport({ selectedRows }) {
this.selectedRows = selectedRows
this.dialogStatus = 'export'
if (!this.needMFAVerify) {
this.showMFADialog = false
this.showExportDialog = true
} else {
this.showMFADialog = true
}
},
downloadCsv(url) {
const a = document.createElement('a')
a.href = url
a.click()
window.URL.revokeObjectURL(url)
},
async performExport(selectRows, exportOption, q) {
const url = `/api/v1/assets/asset-user-auth-infos/`
const query = Object.assign({}, q)
if (exportOption === 'selected') {
const resources = []
const data = selectRows
for (let index = 0; index < data.length; index++) {
resources.push(data[index].id)
}
const spm = await createSourceIdCache(resources)
query['spm'] = spm.spm
} else if (exportOption === 'filtered') {
// console.log(listTableRef)
// console.log(listTableRef.dataTable)
// delete query['limit']
// delete query['offset']
}
query['format'] = this.exportTypeOption
const queryStr =
(url.indexOf('?') > -1 ? '&' : '?') +
queryUtil.stringify(query, '=', '&')
return this.downloadCsv(url + queryStr)
},
async performExportConfirm() {
const listTableRef = this.$refs.ListTable
const query = listTableRef.dataTable.getQuery()
delete query['limit']
delete query['offset']
return this.performExport(this.selectedRows, this.exportOption, query)
},
async handleExportConfirm() {
await this.performExportConfirm()
this.showExportDialog = false
},
handleExportCancel() {
this.showExportDialog = false
}
}
}
</script>
<style lang='scss' scoped>
.export-item {
width: 100%;
display: block;
padding: 10px 20px;
}
.export-form >>> .el-form-item__label {
line-height: 2
}
</style>

View File

@@ -9,7 +9,7 @@
</template>
<script>
import DataForm from '@/components/DataForm'
import { DataForm } from '@/components'
export default {
name: 'NestedField',

View File

@@ -170,12 +170,12 @@ export default {
col.filters = column.choices.map(item => {
if (typeof (item.value) === 'boolean') {
if (item.value) {
return { text: item['display_name'], value: 'True' }
return { text: item.display_name, value: 'True' }
} else {
return { text: item['display_name'], value: 'False' }
return { text: item.display_name, value: 'False' }
}
}
return { text: item['display_name'], value: item.value }
return { text: item.display_name, value: item.value }
})
col.sortable = false
col['column-key'] = col.prop
@@ -223,7 +223,7 @@ export default {
defaultColumnsNames = totalColumnsNames.filter(n => defaultColumnsNames.indexOf(n) > -1)
// 最小列
const minColumnsNames = _.get(this.iConfig, 'columnsShow.min', ['actions', 'id'])
const minColumnsNames = _.get(this.iConfig, 'columnsShow.min', ['action', 'id'])
.filter(n => defaultColumnsNames.indexOf(n) > -1)
// 应该显示的列

View File

@@ -40,7 +40,7 @@ export default {
autoParam: ['id=key', 'name=n', 'level=lv'],
type: 'get',
headers: {
'X-JMS-ORG': this.$store.getters.currentOrg.id
'X-JMS-ORG': JSON.parse(this.$cookie.get('jms_current_org')) ? JSON.parse(this.$cookie.get('jms_current_org')).id : ''
}
},
callback: {
@@ -89,7 +89,7 @@ export default {
return
}
if (currentNode) {
currentNode.name = currentNode.meta.data.value
currentNode.name = currentNode.meta.node.value
}
this.zTree.editName(currentNode)
},
@@ -108,12 +108,12 @@ export default {
const query = Object.assign({}, this.$route.query)
if (treeNode.meta.type === 'node') {
this.currentNode = treeNode
this.currentNodeId = treeNode.meta.data.id
this.currentNodeId = treeNode.meta.node.id
query['node'] = this.currentNodeId
url = `${this.setting.url}${combinator}node_id=${treeNode.meta.data.id}&show_current_asset=${show_current_asset}`
url = `${this.setting.url}${combinator}node_id=${treeNode.meta.node.id}&show_current_asset=${show_current_asset}`
} else if (treeNode.meta.type === 'asset') {
query['asset'] = treeNode.meta.data.id
url = `${this.setting.url}${combinator}asset_id=${treeNode.meta.data.id}&show_current_asset=${show_current_asset}`
query['asset'] = treeNode.meta.asset.id
url = `${this.setting.url}${combinator}asset_id=${treeNode.meta.asset.id}&show_current_asset=${show_current_asset}`
}
this.$router.push({ query })
this.$emit('urlChange', url)
@@ -125,7 +125,7 @@ export default {
return
}
this.$axios.delete(
`${this.treeSetting.nodeUrl}${currentNode.meta.data.id}/`
`${this.treeSetting.nodeUrl}${currentNode.meta.node.id}/`
).then(() => {
this.$message.success(this.$t('common.deleteSuccessMsg'))
this.zTree.removeNode(currentNode)
@@ -143,7 +143,7 @@ export default {
url,
{ 'value': treeNode.name }
).then(res => {
let assetsAmount = treeNode.meta.data.assetsAmount
let assetsAmount = treeNode.meta.node.assetsAmount
if (!assetsAmount) {
assetsAmount = 0
}
@@ -208,9 +208,9 @@ export default {
onDrop: function(event, treeId, treeNodes, targetNode, moveType) {
const treeNodesIds = []
$.each(treeNodes, function(index, value) {
treeNodesIds.push(value.meta.data.id)
treeNodesIds.push(value.meta.node.id)
})
const theUrl = `${this.treeSetting.nodeUrl}${targetNode.meta.data.id}/children/add/`
const theUrl = `${this.treeSetting.nodeUrl}${targetNode.meta.node.id}/children/add/`
this.$axios.put(
theUrl, {
nodes: treeNodesIds
@@ -229,21 +229,21 @@ export default {
}
this.zTree.expandNode(parentNode, true, false, true, false)
// http://localhost/api/v1/assets/nodes/85aa4ee2-0bd9-41db-9079-aa3646448d0c/children/
const url = `${this.treeSetting.nodeUrl}${parentNode.meta.data.id}/children/`
const url = `${this.treeSetting.nodeUrl}${parentNode.meta.node.id}/children/`
this.$axios.post(url, {}).then(data => {
const newNode = {
id: data['key'],
name: data['value'],
pId: parentNode.id,
meta: {
data: data
'node': 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.currentNodeId = node.meta.node.id || newNode.id
this.zTree.editName(node)
this.$message.success(this.$t('common.createSuccessMsg'))
}).catch(error => {

View File

@@ -840,7 +840,7 @@ export default {
this.$emit('selection-change', val)
},
totalData(val) {
if (val && val.length !== this.total) {
if (val) {
this.page = defaultFirstPage
this.total = val.length
this.getList()

View File

@@ -28,20 +28,13 @@ export default {
return this.formatter(this.item, this.value)
}
if (typeof this.value === 'boolean') {
return (
<span class='item-value'>{this.toChoicesDisplay(this.value)}</span>
)
return <span>{this.toChoicesDisplay(this.value)}</span>
}
return (
<span class='item-value'>{this.value}</span>
)
return <span>{this.value}</span>
}
}
</script>
<style scoped>
.item-value {
word-break: break-word;
}
</style>

View File

@@ -2,7 +2,6 @@
<el-select
ref="select"
v-model="iValue"
v-loading="!initialized"
v-loadmore="loadMore"
:options="iOptions"
:remote="remote"
@@ -98,7 +97,7 @@ export default {
return {
loading: false,
initialized: false,
iValue: this.multiple ? [] : '',
iValue: this.value ? this.value : [],
defaultParams: _.cloneDeep(defaultParams),
params: _.cloneDeep(defaultParams),
iOptions: this.options || [],
@@ -162,14 +161,11 @@ export default {
this.iValue = iNew
}
},
async mounted() {
mounted() {
// this.$log.debug('Select2 url is: ', this.iAjax.url)
if (!this.initialized) {
await this.initialSelect()
setTimeout(() => {
this.initialized = true
this.iValue = this.value
})
this.initialSelect()
this.initialized = true
}
this.$nextTick(() => {
// 因为elform存在问题这个来清楚验证
@@ -247,13 +243,9 @@ export default {
// this.$log.debug('Select ajax config', this.iAjax)
if (this.iAjax.url) {
if (this.value && this.value.length !== 0) {
this.$log.debug('Start init select2 value, ', this.value)
let value = this.value
if (!Array.isArray(value)) {
value = [value]
}
const data = await createSourceIdCache(value)
this.params.spm = data['spm']
this.$log.debug('Start init select2 value')
const data = await createSourceIdCache(this.value)
this.params.spm = data.spm
await this.getInitialOptions()
}
await this.getOptions()

View File

@@ -22,12 +22,8 @@ export default {
}
},
rules(item) {
let userIsOrgAdmin = item.el.userIsOrgAdmin
// undefined 个人信息更新或用户更改密码页面,使用当前用户;否则使用更新用户表单中传递的值
userIsOrgAdmin = userIsOrgAdmin === undefined ? store.getters.currentUserIsAdmin : userIsOrgAdmin
const passwordRule = store.getters.publicSettings.PASSWORD_RULE
const validatePassword = function(rule, value, callback) {
const validatePassword = (rule, value, callback) => {
if (!value) {
return callback()
}
@@ -50,10 +46,7 @@ export default {
return callback(new Error(msg))
}
}
let secureLength = passwordRule ? passwordRule.SECURITY_PASSWORD_MIN_LENGTH : 7
if (userIsOrgAdmin) {
secureLength = passwordRule ? passwordRule.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH : 7
}
const secureLength = passwordRule ? passwordRule.SECURITY_PASSWORD_MIN_LENGTH : 7
if (value.length < secureLength) {
return callback(new Error(i18n.t('common.password.MIN_LENGTH_ERROR', [secureLength])))
}
@@ -66,12 +59,17 @@ export default {
data() {
return {
attrs: {
secureLength: 7
}
}
},
computed: {
...mapGetters(['publicSettings'])
},
created() {
const passwordRule = this.publicSettings.PASSWORD_RULE || {}
this.attrs.secureLength = passwordRule ? passwordRule.SECURITY_PASSWORD_MIN_LENGTH : 7
},
methods: {
handleInput(value) {
this.$emit('input', value)

View File

@@ -27,7 +27,7 @@ export default {
vm.tableConfig.initialUrl = vm.tableConfig.url
}
const initialUrl = vm.tableConfig.initialUrl
const nodeId = node.meta.data.id
const nodeId = node.meta.node.id
const url = initialUrl.replace('/assets/', `/nodes/${nodeId}/assets/`)
vm.tableConfig.url = url
}

View File

@@ -1,45 +1,29 @@
<template>
<div>
<MFAVerifyDialog
v-if="mfaDialogShow"
@MFAVerifyDone="showExportDialog"
@MFAVerifyCancel="handleExportCancel"
/>
<Dialog
v-if="exportDialogShow"
:title="$t('common.Export')"
:visible.sync="exportDialogShow"
:destroy-on-close="true"
@confirm="handleExportConfirm()"
@cancel="handleExportCancel()"
>
<el-form label-position="left" style="padding-left: 50px">
<el-form-item :label="$t('common.fileType' )" :label-width="'100px'">
<el-radio-group v-model="exportTypeOption">
<el-radio v-for="option of exportTypeOptions" :key="option.value" style="padding: 10px 20px;" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item class="export-form" :label="this.$t('common.imExport.ExportRange')" :label-width="'100px'">
<el-radio-group v-model="exportOption">
<el-radio v-for="option of exportOptions" :key="option.value" class="export-item" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</Dialog>
</div>
<Dialog v-if="showExportDialog" :title="$t('common.Export')" :visible.sync="showExportDialog" :destroy-on-close="true" @confirm="handleExportConfirm()" @cancel="handleExportCancel()">
<el-form label-position="left" style="padding-left: 50px">
<el-form-item :label="$t('common.fileType' )" :label-width="'100px'">
<el-radio-group v-model="exportTypeOption">
<el-radio v-for="option of exportTypeOptions" :key="option.value" style="padding: 10px 20px;" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item class="export-form" :label="this.$t('common.imExport.ExportRange')" :label-width="'100px'">
<el-radio-group v-model="exportOption">
<el-radio v-for="option of exportOptions" :key="option.value" class="export-item" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
import MFAVerifyDialog from '@/components/MFAVerifyDialog'
import { createSourceIdCache } from '@/api/common'
import * as queryUtil from '@/components/DataTable/compenents/el-data-table/utils/query'
export default {
name: 'ExportDialog',
components: {
Dialog,
MFAVerifyDialog
Dialog
},
props: {
selectedRows: {
@@ -48,20 +32,12 @@ export default {
},
url: {
type: String,
default: ''
},
beforeExport: {
type: Function,
default: () => {}
},
mfaVerifyRequired: {
type: Boolean,
default: false
default: () => ''
},
performExport: {
type: Function,
default(selectedRows, exportOptions, query, exportType) {
return this.defaultPerformExport(selectedRows, exportOptions, query, exportType)
default(selectedRows, exportOptions, query) {
return this.defaultPerformExport(selectedRows, exportOptions, query)
}
},
canExportAll: {
@@ -79,12 +55,10 @@ export default {
},
data() {
return {
exportDialogShow: false,
showExportDialog: false,
exportOption: 'all',
exportTypeOption: 'csv',
meta: {},
mfaVerified: false,
mfaDialogShow: false
meta: {}
}
},
computed: {
@@ -141,33 +115,20 @@ export default {
}
},
mounted() {
this.$eventBus.$on('showExportDialog', ({ selectedRows, url, name }) => {
// Todo: 没有时间了,只能先这么处理了
if (url === this.url || url.indexOf(this.url) > -1 || url.indexOf('account') > -1) {
this.showExportDialog()
this.$eventBus.$on('showExportDialog', ({ selectedRows, url }) => {
if (url === this.url) {
this.showExportDialog = true
}
})
},
methods: {
showExportDialog() {
if (!this.mfaVerifyRequired) {
this.exportDialogShow = true
return
}
// 这是需要校验 MFA 的
if (!this.mfaDialogShow) {
this.mfaDialogShow = true
} else {
this.exportDialogShow = true
}
},
downloadCsv(url) {
const a = document.createElement('a')
a.href = url
a.click()
window.URL.revokeObjectURL(url)
},
async defaultPerformExport(selectRows, exportOption, q, exportTypeOption) {
async defaultPerformExport(selectRows, exportOption, q) {
const url = (process.env.VUE_APP_ENV === 'production') ? (`${this.url}`) : (`${process.env.VUE_APP_BASE_API}${this.url}`)
const query = Object.assign({}, q)
if (exportOption === 'selected') {
@@ -178,8 +139,13 @@ export default {
}
const spm = await createSourceIdCache(resources)
query['spm'] = spm.spm
} else if (exportOption === 'filtered') {
// console.log(listTableRef)
// console.log(listTableRef.dataTable)
// delete query['limit']
// delete query['offset']
}
query['format'] = exportTypeOption
query['format'] = this.exportTypeOption
const queryStr =
(url.indexOf('?') > -1 ? '&' : '?') +
queryUtil.stringify(query, '=', '&')
@@ -187,20 +153,17 @@ export default {
},
async handleExport() {
const listTableRef = this.$parent.$parent.$parent.$parent
const query = listTableRef['dataTable'].getQuery()
const query = listTableRef.dataTable.getQuery()
delete query['limit']
delete query['offset']
await this.beforeExport()
return this.performExport(this.selectedRows, this.exportOption, query, this.exportTypeOption)
return this.performExport(this.selectedRows, this.exportOption, query)
},
async handleExportConfirm() {
await this.handleExport()
this.exportDialogShow = false
this.mfaDialogShow = false
this.showExportDialog = false
},
handleExportCancel() {
this.exportDialogShow = false
this.mfaDialogShow = false
this.showExportDialog = false
}
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div>
<ExportDialog :selected-rows="selectedRows" v-bind="exportOptions" v-on="$listeners" />
<ImportDialog :selected-rows="selectedRows" v-bind="importOptions" v-on="$listeners" />
<ExportDialog :selected-rows="selectedRows" :url="url" v-bind="$attrs" v-on="$listeners" />
<ImportDialog :selected-rows="selectedRows" :url="url" v-bind="$attrs" v-on="$listeners" />
</div>
</template>
@@ -10,7 +10,7 @@ import ExportDialog from './ExportDialog'
import ImportDialog from './ImportDialog'
export default {
name: 'ImExportDialog',
name: 'DialogAction',
components: {
ExportDialog,
ImportDialog
@@ -20,13 +20,9 @@ export default {
type: Array,
default: () => []
},
exportOptions: {
type: Object,
default: () => ({})
},
importOptions: {
type: Object,
default: () => ({})
url: {
type: String,
default: () => ''
}
},
data() {

View File

@@ -33,7 +33,7 @@
<script>
import DataTable from '@/components/DataTable'
import { sleep, getUpdateObjURL } from '@/utils/common'
import { sleep } from '@/utils/common'
import { EditableInputFormatter, StatusFormatter } from '@/components/TableFormatters'
export default {
name: 'ImportTable',
@@ -350,7 +350,7 @@ export default {
}
},
async performUpdateObject(item) {
const updateUrl = getUpdateObjURL(this.url, item.id)
const updateUrl = `${this.url}${item.id}/`
return this.$axios.put(
updateUrl,
item,

View File

@@ -24,16 +24,6 @@ export default {
hasLeftActions: defaultTrue,
hasCreate: defaultTrue,
canCreate: defaultTrue,
createRoute: {
type: [String, Object, Function],
default: function() {
return this.$route.name.replace('List', 'Create')
}
},
createInNewPage: {
type: Boolean,
default: false
},
hasBulkDelete: defaultTrue,
hasBulkUpdate: defaultFalse,
hasMoreActions: defaultTrue,
@@ -41,6 +31,12 @@ export default {
type: String,
default: ''
},
createRoute: {
type: [String, Object, Function],
default: function() {
return this.$route.name.replace('List', 'Create')
}
},
reloadTable: {
type: Function,
default: () => {}
@@ -162,13 +158,8 @@ export default {
} else if (typeof this.createRoute === 'object') {
route = this.createRoute
}
this.$router.push(route)
this.$log.debug('handle create')
if (this.createInNewPage) {
const { href } = this.$router.resolve(route)
window.open(href, '_blank')
} else {
this.$router.push(route)
}
},
defaultBulkDeleteCallback({ selectedRows, reloadTable }) {
const msg = this.$t('common.deleteWarningMsg') + ' ' + selectedRows.length + ' ' + this.$t('common.rows') + ' ?'

View File

@@ -1,12 +1,7 @@
<template>
<div>
<ActionsGroup :is-fa="true" :actions="rightSideActions" class="right-side-actions right-side-item" />
<ImExportDialog
:selected-rows="selectedRows"
:export-options="iExportOptions"
:import-options="iImportOptions"
v-bind="$attrs"
/>
<ActionsGroup :is-fa="true" :actions="rightSideActions" :url="tableUrl" class="right-side-actions right-side-item" />
<ImExportDialog :selected-rows="selectedRows" :url="tableUrl" v-bind="$attrs" />
</div>
</template>
@@ -14,7 +9,6 @@
import ActionsGroup from '@/components/ActionsGroup'
import ImExportDialog from './ImExportDialog'
import { cleanActions } from './utils'
import { assignIfNot } from '@/utils/common'
const defaultTrue = { type: Boolean, default: true }
@@ -30,32 +24,24 @@ export default {
default: ''
},
hasExport: defaultTrue,
exportOptions: {
type: Object,
default: () => ({})
},
handleExportClick: {
handleExport: {
type: Function,
default: function({ selectedRows }) {
this.$eventBus.$emit('showExportDialog', { selectedRows, url: this.tableUrl, name: this.name })
this.$eventBus.$emit('showExportDialog', { selectedRows, url: this.tableUrl })
}
},
hasImport: defaultTrue,
importOptions: {
type: Object,
default: () => ({})
},
handleImportClick: {
handleImport: {
type: Function,
default: function({ selectedRows }) {
this.$eventBus.$emit('showImportDialog', { selectedRows, url: this.tableUrl, name: this.name })
this.$eventBus.$emit('showImportDialog', { selectedRows, url: this.tableUrl })
}
},
hasColumnSetting: defaultTrue,
handleTableSettingClick: {
handleColumnConfig: {
type: Function,
default: function({ selectedRows }) {
this.$eventBus.$emit('showColumnSettingPopover', { url: this.tableUrl, row: selectedRows, name: this.name })
this.$eventBus.$emit('showColumnSettingPopover', { url: this.tableUrl })
}
},
hasRefresh: defaultTrue,
@@ -75,12 +61,13 @@ export default {
data() {
return {
defaultRightSideActions: [
{ name: 'actionColumnSetting', fa: 'fa-cog', tip: this.$t('common.CustomCol'), has: this.hasColumnSetting, callback: this.handleTableSettingClick.bind(this) },
{ name: 'actionExport', fa: 'fa-download', tip: this.$t('common.Export'), has: this.hasExport, callback: this.handleExportClick.bind(this) },
{ name: 'actionImport', fa: 'fa-upload', tip: this.$t('common.Import'), has: this.hasImport, callback: this.handleImportClick.bind(this) },
{ name: 'actionColumnSetting', fa: 'fa-cog', tip: this.$t('common.CustomCol'), has: this.hasColumnSetting, callback: this.handleColumnConfig.bind(this) },
{ name: 'actionExport', fa: 'fa-download', tip: this.$t('common.Export'), has: this.hasExport, callback: this.handleExport.bind(this) },
{ name: 'actionImport', fa: 'fa-upload', tip: this.$t('common.Import'), has: this.hasImport, callback: this.handleImport.bind(this) },
{ name: 'actionRefresh', fa: 'fa-refresh', tip: this.$t('common.Refresh'), has: this.hasRefresh, callback: this.handleRefresh }
],
dialogExportVisible: false
dialogExportVisible: false,
exportValue: 2
}
},
computed: {
@@ -94,19 +81,18 @@ export default {
},
hasSelectedRows() {
return this.selectedRows.length > 0
},
iImportOptions() {
return assignIfNot(this.importOptions, { url: this.tableUrl })
},
iExportOptions() {
const options = assignIfNot(this.exportOptions, { url: this.tableUrl })
return options
}
},
methods: {
handleTagSearch(val) {
this.searchTable(val)
},
// handleExport({ selectedRows }) {
// this.$eventBus.$emit('showExportDialog', { selectedRows })
// },
// handleImport({ selectedRows }) {
// this.$eventBus.$emit('showImportDialog', { selectedRows })
// },
handleRefresh() {
this.reloadTable()
}

View File

@@ -1,21 +1,8 @@
<template>
<div>
<TableAction
:table-url="tableUrl"
:search-table="search"
:date-pick="handleDateChange"
:selected-rows="selectedRows"
:reload-table="reloadTable"
v-bind="headerActions"
/>
<TableAction :table-url="iTableConfig.url" :search-table="search" :date-pick="handleDateChange" v-bind="headerActions" :selected-rows="selectedRows" :reload-table="reloadTable" />
<IBox class="table-content">
<AutoDataTable
ref="dataTable"
:filter-table="filter"
:config="iTableConfig"
@selection-change="handleSelectionChange"
v-on="$listeners"
/>
<AutoDataTable ref="dataTable" :filter-table="filter" :config="iTableConfig" @selection-change="handleSelectionChange" v-on="$listeners" />
</IBox>
</div>
</template>
@@ -63,9 +50,6 @@ export default {
this.$log.debug('Header actions', this.headerActions)
this.$log.debug('ListTable: iTableConfig change', config)
return config
},
tableUrl() {
return this.iTableConfig.url
}
},
watch: {
@@ -129,6 +113,15 @@ export default {
& >>> .el-table__header thead > tr > th {
background-color: white;
}
/*& >>> .el-table--striped .el-table__body tr.el-table__row--striped td {*/
/*background: white;*/
/*}*/
/*& >>> .el-table th, .el-table tr {*/
/*background-color: red;*/
/*!*background-color: #FAFAFA;*!*/
/*}*/
}
//修改颜色

View File

@@ -1,77 +0,0 @@
<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 :span="4">
<div style="line-height: 34px;text-align: center">MFA</div>
</el-col>
<el-col :span="14">
<el-input v-model="MFAToken" />
<span class="help-tips help-block">{{ $t('common.MFARequireForSecurity') }}</span>
</el-col>
<el-col :span="4">
<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

@@ -2,11 +2,15 @@
<el-card shadow="never">
<div slot="header" class="summary-header">
<span class="header-title">{{ title }}</span>
<span class="pull-right right-side">
<slot name="header-right">
<el-tag :type="rightSideLabel.type || 'success'" effect="dark" size="mini">{{ rightSideLabel.title }}</el-tag>
</slot>
</span>
</div>
<slot>
<h1 class="no-margins">
<span v-if="body.disabled" class="disabled-link">{{ body.count }}</span>
<router-link v-else :to="body.route">
<router-link :to="body.route">
<span>{{ body.count }}</span>
</router-link>
</h1>
@@ -70,8 +74,4 @@ export default {
.no-margins {
margin: 0 !important;
}
.disabled-link {
color: #428bca;
}
</style>

View File

@@ -8,12 +8,9 @@ import BaseFormatter from './base'
const defaultPerformDelete = function({ row, col }) {
const id = row.id
const url = new URL(this.url, location.origin)
url.pathname += `${id}/`
const deleteUrl = url.href
return this.$axios.delete(deleteUrl)
const url = `${this.url}${id}/`
return this.$axios.delete(url)
}
const defaultUpdateCallback = function({ row, col }) {
const id = row.id
let route = { params: { id: id }}

View File

@@ -1,7 +1,7 @@
<template>
<div>
<el-tooltip v-if="formatterArgs.hasTips" placement="bottom" effect="dark">
<div slot="content" v-html="tips" />
<div slot="content">{{ tipStatus }}<br>{{ tipTime }}</div>
<i :class="'fa ' + iconClass" />
</el-tooltip>
<i v-else :class="'fa ' + iconClass" />
@@ -10,6 +10,7 @@
<script>
import BaseFormatter from './base'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'ChoicesFormatter',
extends: BaseFormatter,
@@ -22,12 +23,21 @@ export default {
true: 'fa-check text-primary',
false: 'fa-times text-danger'
},
getIconKey({ row, cellValue }) {
return cellValue
typeChange(val) {
return !!val
},
hasTips: false,
getTips: ({ row, cellValue }) => {
return cellValue
tipStatus(val, vm) {
if (!val) {
return vm.$t('assets.Unknown')
}
if (val.status === 0) {
return vm.$t('assets.Unreachable')
} else if (val.status === 1) {
return vm.$t('assets.Reachable')
} else if (val.status === 2) {
return vm.$t('assets.Unknown')
}
}
}
}
@@ -40,11 +50,18 @@ export default {
},
computed: {
iconClass() {
const key = this.formatterArgs.getIconKey({ row: this.row, cellValue: this.cellValue })
const key = this.formatterArgs.typeChange(this.cellValue)
return this.formatterArgs.iconChoices[key]
},
tips() {
return this.formatterArgs.getTips({ cellValue: this.cellValue, row: this.row })
tipStatus() {
const vm = this
return this.formatterArgs.tipStatus(this.cellValue, vm)
},
tipTime() {
if (!this.cellValue) {
return ''
}
return toSafeLocalDateStr(this.cellValue.datetime)
}
}
}

View File

@@ -20,7 +20,7 @@ export default {
defaultOnDelete(col, row, cellValue, reload) {
const url = col.deleteUrl + cellValue
this.$axios.delete(url).then(res => {
this.$message.success(this.$tc('common.deleteSuccessMsg'))
this.$message.success(this.$t('common.deleteSuccessMsg'))
reload()
}).catch(error => {
this.$message.error(this.$t('common.deleteErrorMsg') + ' ' + error)

View File

@@ -77,9 +77,9 @@ export default {
}
},
methods: {
handleUrlChange(url) {
this.$set(this.iTableConfig, 'url', url)
this.$emit('urlChange', url)
handleUrlChange(_url) {
this.$set(this.iTableConfig, 'url', _url)
this.$emit('urlChange', _url)
this.forceRerender()
},
forceRerender() {

View File

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

View File

@@ -29,7 +29,6 @@
},
"applications": {
"": "",
"updateAccountMsg": "请更新系统用户的账号信息",
"RemoteApp": "远程应用",
"Database": "数据库",
"Cloud": "云",
@@ -84,14 +83,11 @@
"DBInfo": "数据库信息"
},
"assets": {
"AssociateSystemUsers": "关联系统用户",
"AssociateAssets": "关联资产",
"AssociateNodes": "关联节点",
"Action": "动作",
"ActiveSelected": "激活所选",
"AdminUser": "特权用户",
"AdminUser": "管理用户",
"AdminUserDetail": "管理用户详情",
"AdminUserListHelpMessage": "<b>特权用户</b> 是资产已存在的, 并且拥有 高级权限 的系统用户, 如 root拥有 `NOPASSWD: ALL` sudo 权限的用户 JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等。",
"AdminUserListHelpMessage": "管理用户是资产(被控服务器)上的 root,或拥有 NOPASSWD: ALL sudo 权限的用户 JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等。\n",
"Asset": "资产",
"HardwareInfo": "硬件信息",
"AssetDetail": "资产详情",
@@ -102,11 +98,11 @@
"TestGatewayHelpMessage": "如果使用了nat端口映射请设置为ssh真实监听的端口",
"SshPort": "SSH 端口",
"AssetNumber": "资产编号",
"AssetUserList": "账号列表",
"AssetUserList": "资产用户列表",
"Assets": "资产",
"Auth": "认证",
"AccountList": "账号列表",
"AutoGenerateKey": "自动生成",
"AutoGenerateKey": "自动生成密钥",
"AutoPush": "自动推送",
"BasePlatform": "基础平台",
"Basic": "基本",
@@ -119,7 +115,6 @@
"CommandFilterRules": "命令过滤器规则",
"Comment": "备注",
"Cpu": "CPU",
"CommonUser": "普通用户",
"CreatedBy": "创建者",
"Database": "数据库",
"DateJoined": "创建日期",
@@ -161,11 +156,9 @@
"PriorityHelpMessage": "1-100, 1最低优先级100最高优先级。授权多个用户时高优先级的系统用户将会作为默认登录用户",
"Protocol": "协议",
"Protocols": "协议组",
"LoginOption": "登录选项",
"PublicIp": "公网IP",
"Push": "推送",
"PushSystemUserNow": "推送系统用户",
"PushAllSystemUsersToAsset": "推送所有系统用户到资产",
"QuickUpdate": "快速更新",
"Reachable": "可连接",
"Unreachable": "不可连接",
@@ -185,17 +178,15 @@
"PasswordHelpMessage": "密码或密钥密码",
"SystemUser": "系统用户",
"SystemUserDetail": "系统用户详情",
"SystemUserListHelpMessage": "<b>系统用户</b> 是JumpServer 登录资产时使用的账号,如 root `ssh root@host`,而不是使用该用户名登录资产ssh admin@host)`;<br><b>特权用户</b> 是资产已存在的, 并且拥有 高级权限 的系统用户, JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等;</br><b>普通用户</b> 可以在资产上预先存在,也可以由 特权用户 来自动创建。",
"DynamicUsername": "动态用户名",
"SystemUserListHelpMessage": "系统用户是 JumpServer 跳转登录资产时使用的用户,可以理解为登录资产用户,如 websadba`ssh web@some-host`,而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host` 简单来说是用户使用自己的用户名登录 JumpServerJumpServer 使用系统用户登录资产。 系统用户创建时,如果选择了自动推送,JumpServer 使用 Ansible 自动推送系统用户到资产中,如果资产(交换机)不支持 Ansible请手动填写账号密码。\n",
"SystemUsers": "系统用户",
"Test": "测试",
"TestAssetsConnective": "测试资产可连接性",
"TestAllSystemUsersConnective": "测试所有系统用户可连接性",
"TestConnection": "测试连接",
"Type": "类型",
"UnselectedAssets": "未选择资产或所选择的资产不支持SSH协议连接",
"UnselectedNodes": "未选择节点",
"UpdateAssetUserToken": "更新账号认证信息",
"UpdateAssetUserToken": "更新资产用户认证信息",
"Username": "用户名",
"UsernameHelpMessage": "用户名是动态的,登录资产时使用当前用户的用户名登录",
"Value": "值",
@@ -206,7 +197,6 @@
"sshKeyFingerprint": "SSH 指纹",
"ip": "IP",
"sshkey": "sshkey",
"SSHKey": "SSH 密钥",
"GroupsHelpMessage": "请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)",
"HomeHelpMessage": "默认家目录 /home/系统用户名: /home/username",
"Home": "家目录",
@@ -230,8 +220,6 @@
"ReLogin": "重新登录"
},
"common": {
"MFAVerify": "验证 MFA",
"ViewSecret": "查看密码",
"ConnectWebSocketError": "连接 WebSocket 失败",
"Action": "动作",
"RequestTickets": "申请工单",
@@ -250,9 +238,6 @@
"bind": "绑定",
"unbind": "解绑",
"PushSelected":"推送所选",
"PushSelectedSystemUsersToAsset": "推送所选系统用户到资产",
"TestSelected": "测试所选",
"TestSelectedSystemUsersConnective": "测试所选系统用户可连接性",
"BadRequestErrorMsg": "请求错误,请检查填写内容",
"BadRoleErrorMsg": "请求错误,无该操作权限",
"BadConflictErrorMsg": "正在刷新中,请稍后再试",
@@ -653,7 +638,6 @@
"SystemUserDetail": "系统用户详情",
"SystemUserList": "系统用户",
"SystemUserUpdate": "更新系统用户",
"AssetUserList": "资产用户",
"TaskDetail": "任务详情",
"TaskList": "任务列表",
"TaskMonitor": "任务监控",
@@ -679,10 +663,6 @@
"SiteMessageList": "站内信"
},
"sessions": {
"SetToDefaultStorage": "设置为默认存储",
"SetToDefault": "设为默认",
"SetSuccess": "设置成功",
"SetFailed": "设置失败",
"StorageConfiguration": "存储配置",
"accountKey": "账户密钥",
"accountName": "账户名称",
@@ -894,8 +874,6 @@
"DingTalk": "钉钉",
"dingTalkTest": "测试",
"weComTest": "测试",
"FeiShu": "飞书",
"feiShuTest": "测试",
"setting": "设置"
},
"tickets": {
@@ -986,7 +964,6 @@
"Guide": "向导",
"setWeCom": "设置企业微信认证",
"setDingTalk": "设置钉钉认证",
"setFeiShu": "设置飞书认证",
"HelpText": {
"MFAOfUserFirstLoginPersonalInformationImprovementPage": "启用多因子认证,使账号更加安全。<br/> 启用之后您将会在下次登录时进入多因子认证绑定流程;您也可以在(个人信息->快速修改->更改多因子设置)中直接绑定!",
"MFAOfUserFirstLoginUserGuidePage": "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:设置复杂密码,并启用多因子认证)",
@@ -1118,7 +1095,6 @@
"NodeAmount": "节点数量",
"PasswordLength": "密码长度",
"PasswordStrategy": "密码策略",
"SecretKeyStrategy": "密钥策略",
"RegularlyPerform": "定期执行",
"Result": "结果",
"Retry": "重试",
@@ -1129,11 +1105,8 @@
"Username": "用户名"
},
"Cloud": {
"IPNetworkSegment": "IP网段",
"Aliyun": "阿里云",
"Qcloud": "腾讯云",
"QingyunPrivatecloud": "青云私有云",
"HuaweiPrivatecloud": "华为私有云",
"AWS_China": "AWS(中国)",
"AWS_Int": "AWS(国际)",
"HuaweiCloud": "华为云",
@@ -1161,7 +1134,7 @@
"Name": "名称",
"Account":"账户",
"Node": "节点",
"AdminUser":"特权用户",
"AdminUser":"管理用户",
"Periodic":"执行周期",
"PeriodicPerform":"定时执行",
"RegularlyPerform": "定期执行",
@@ -1223,7 +1196,7 @@
"users_amount": "用户数量",
"groups_amount": "用户组数量",
"assets_amount": "资产数量",
"admin_users_amount": "特权用户数量",
"admin_users_amount": "管理用户数量",
"system_users_amount": "系统用户数量",
"applications_amount": "应用数量",
"asset_perms_amount": "资产授权数量",

View File

@@ -1,9 +1,9 @@
{
"": "",
"accounts": {
"PleaseClickLeftAssetToViewAssetAccount": "Asset account list, please click on the assets on the left to view",
"PleaseClickLeftAssetToViewAssetAccount": "Asset account list, please click on the assets on the left to view",
"PleaseClickLeftApplicationToViewApplicationAccount": "Application account list, please click on the application on the left to view",
"PleaseClickLeftAssetToViewGatheredUser": "Gathered user list, please click on the assets on the left to view"
"PleaseClickLeftAssetToViewGatheredUser": "Gathered user list please click on the assets on the left to view"
},
"acl": {
"name": "Name",
@@ -28,7 +28,6 @@
},
"applications": {
"": "",
"updateAccountMsg": "Please update system user account info",
"RemoteApp": "Remote app",
"Database": "Database",
"Cloud": "Cloud",
@@ -83,21 +82,16 @@
"DBInfo": "Database Info"
},
"assets": {
"AssociateSystemUsers": "Associate system users",
"AssociateAssets": "Associate assets",
"AssociateNodes": "Associate nodes",
"Action": "Action",
"ActiveSelected": "Active selected",
"AdminUser": "Admin user",
"ReplaceNodeAssetsAdminUser":"Replace node assets admin user with this",
"AdminUserDetail": "Admin user detail",
"DynamicUsername": "Dynamic username",
"AdminUserListHelpMessage": "Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, JumpServer users of the system using the user to `push system user`, `get assets hardware information`, etc.\n",
"Asset": "Asset",
"HardwareInfo": "Hardware info",
"Hardware": "Hardware",
"AccountList": "Account list",
"LoginOption": "Login option",
"AssetDetail": "Asset detail",
"AssetList": "Asset list",
"AssetListHelpMessage": "The left side is the asset tree, right click to create, delete, and change the tree node, authorization asset is also organized as a node, and the right side is the asset under that node\n",
@@ -121,7 +115,6 @@
"CommandFilterRules": "Command filter rules",
"Comment": "Comment",
"Cpu": "Cpu",
"CommonUser": "Common user",
"CreatedBy": "Created by",
"Database": "Database",
"DateJoined": "Date joined",
@@ -165,7 +158,6 @@
"PublicIp": "Public ip",
"Push": "Push",
"PushSystemUserNow": "Push system user now",
"PushAllSystemUsersToAsset": "Push all system users to asset",
"QuickUpdate": "Quick update",
"Reachable": "Reachable",
"Unreachable": "Unreachable",
@@ -181,15 +173,14 @@
"Rules": "Rules",
"SFTPHelpMessage": "SFTP root dir, tmp, home or custom",
"SerialNumber": "Serial number",
"SudoHelpMessage": "Use comma split multi command, ex: /bin/whoami, /bin/ifconfig",
"SudoHelpMessage": "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig",
"PasswordHelpMessage": "Password or private key password",
"SystemUser": "System user",
"SystemUserDetail": "System user detail",
"SystemUserListHelpMessage": "<b>System user</b> is the account JumpServer used to log into the asset, such as using root `ssh root@host`, rather than the current user usernamessh admin@host)`;<br><b>Admin user</b> is the account that already exists on an asset, and have privileged permissions, JumpServer using this create common system user, and gather hardware Etc;</br><b>Common user</b> can pre-exist assets or created automatically by the admin user.",
"SystemUserListHelpMessage": "System user is JumpServer jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); In simple terms, users log into JumpServer using their own username, and JumpServer uses system users to log into assets. When system users are created, if you choose auto push JumpServer to use Ansible push system users into the asset, if the asset (Switch) does not support ansible, please manually fill in the account password.\n",
"SystemUsers": "System users",
"Test": "Test",
"TestAssetsConnective": "Test assets connective",
"TestAllSystemUsersConnective": "Test all system users connective",
"TestConnection": "Test connection",
"Type": "Type",
"UnselectedAssets": "No asset selected or the selected asset does not support SSH protocol connection",
@@ -228,8 +219,6 @@
"ReLogin": "Re-Login"
},
"common": {
"MFAVerify": "Verify MFA",
"ViewSecret": "View secret",
"ConnectWebSocketError": "Connect Websocket failed",
"Nothing": "Nothing",
"Action": "Action",
@@ -245,9 +234,6 @@
"Add": "Add",
"PleaseAgreeToTheTerms": "Please agree to the terms",
"PushSelected":"Push selected",
"PushSelectedSystemUsersToAsset": "Push selected system users to asset",
"TestSelected": "Test selected",
"TestSelectedSystemUsersConnective": "Test selected system users connective",
"UpdateAssetDetail": "Update more detail",
"AddSuccessMsg": "Add success",
"Auth": "Authorization",
@@ -264,7 +250,7 @@
"Confirm": "Confirm",
"Create": "Create",
"CreatedBy": "Created by",
"CrontabHelpTips": "eg: Every Sunday 03:05 run <5 3 * * 0> <br>Tips:Using 5 digits linux crontab expressions<min hour day month week> (<a href='https://tool.lu/crontab/' target='_blank'>Online tools</a>) <br>Note:If both Regularly perform and Cycle perform are set, give priority to Regularly perform",
"CrontabHelpTips": "eg: Every Sunday 03:05 run <5 3 * * 0> <br>Tips:Using 5 digits linux crontab expressions<min hour day month week> (<a href='https://tool.lu/crontab/' target='_blank'>Online tools</a>) <br>Note:If both Regularly perform and Cycle perform are set,give priority to Regularly perform",
"DateEnd": "End date",
"Resource": "Resource",
"DateLast24Hours": "Last 24 hours",
@@ -306,7 +292,7 @@
"RemoveSuccessMsg": "Remove success",
"Reset": "Reset",
"Search": "Search",
"MFAErrorMsg": "MFA Error, please check",
"MFAErrorMsg": "MFA Errorplease check",
"InputEmailAddress": "Please enter your email address",
"Select": "Select",
"SelectFile": "Select file",
@@ -675,10 +661,6 @@
"SiteMessageList": "Site message"
},
"sessions": {
"SetToDefaultStorage": "Set to default storage",
"SetToDefault": "Set to default",
"SetSuccess": "Set success",
"SetFailed": "Set failed",
"StorageConfiguration": "Storage configuration",
"accountKey": "Account key",
"accountName": "Account name",
@@ -743,7 +725,7 @@
"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)",
"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",
"esDocType": "Es provides the default document type: command",
"s3Endpoint": "S3: http://s3.{REGION_NAME}.amazonaws.com<br>S3(China): http://s3.{REGION_NAME}.amazonaws.com.cn<br>Example: http://s3.cn-north-1.amazonaws.com.cn",
@@ -810,7 +792,7 @@
"ApiKeyList": "The API key is used to sign the request header. The header of each request is different. Please refer to the usage documentation",
"authLdapSearchFilter": "Choice may be (cn|uid|sAMAccountName)=%(user)s)",
"authLdapSearchOu": "Use | split User OUs",
"authLdapUserAttrMap": "User attr map present how to map LDAP user attr to jumpserver, username, name, email is jumpserver attr",
"authLdapUserAttrMap": "User attr map present how to map LDAP user attr to jumpserver, username,name,email is jumpserver attr",
"emailCustomUserCreatedBody": "Tips:When creating a user, send the content of the email",
"emailCustomUserCreatedHonorific": "Tips: When creating a user, send the honorific of the email (eg:Hello)",
"emailCustomUserCreatedSignature": "Tips: Email signature (eg:jumpserver)",
@@ -887,8 +869,6 @@
"DingTalk": "DingTalk",
"dingTalkTest": "Test",
"weComTest": "Test",
"FeiShu": "FeiShu",
"feiShuTest": "Test",
"setting": "Setting"
},
@@ -969,7 +949,6 @@
"DatePasswordLastUpdated": "Date password last updated",
"setWeCom": "Set wecom login",
"setDingTalk": "Set dingtalk login",
"setFeiShu": "Set feishu login",
"DatePasswordUpdated": "Date password updated",
"DescribeOfGuide": "Welcome to JumpServer. Click here for more information",
"Email": "Email",
@@ -1120,11 +1099,8 @@
"Username": "Username"
},
"Cloud": {
"IPNetworkSegment": "Ip Network Segment",
"Aliyun": "Ali Cloud",
"Qcloud": "Tencent Cloud",
"QingyunPrivatecloud": "Qingyun Private Cloud",
"HuaweiPrivatecloud": "Huawei Private Cloud",
"AWS_China": "AWS(China)",
"AWS_Int": "AWS(International)",
"HuaweiCloud": "Huawei Cloud",

View File

@@ -15,7 +15,6 @@
</template>
<script>
import AutoDataForm from '@/components/AutoDataForm'
import { getUpdateObjURL } from '@/utils/common'
export default {
name: 'GenericCreateUpdateForm',
components: {
@@ -130,31 +129,36 @@ export default {
const params = this.$route.params
let url = this.url
if (params.id) {
url = getUpdateObjURL(url, params.id)
url = `${url}${params.id}/`
}
return url
}
},
emitPerformSuccessMsg: {
onPerformSuccess: {
type: Function,
default(method, res, addContinue) {
default(res, method, vm, addContinue) {
let msg = method === 'post' ? this.createSuccessMsg : this.updateSuccessMsg
if (addContinue) {
msg = this.saveSuccessContinueMsg
}
let msgLinkName = this.$tc('common.Resource')
let msgLinkName = this.$t('common.Resource')
if (res.name) {
msgLinkName = res.name
} else if (res.hostname) {
msgLinkName = res.hostname
}
const detailRoute = this.objectDetailRoute
detailRoute['params'] = { 'id': res.id }
const route = this.getNextRoute(res, method)
this.$emit('submitSuccess', res)
const h = this.$createElement
this.$log.debug('router is: ', detailRoute)
if (this.hasDetailInMsg) {
this.$message({
message: h('p', null, [
h('el-link', {
on: {
click: () => this.$router.push(this.objectDetailRoute)
click: () => this.$router.push(detailRoute)
},
style: { 'vertical-align': 'top' }
}, msgLinkName),
@@ -170,18 +174,6 @@ export default {
} else {
this.$message.success(msg)
}
}
},
onPerformSuccess: {
type: Function,
default(res, method, vm, addContinue) {
const route = this.getNextRoute(res, method)
if (!(route.params && route.params.id)) {
route['params'] = { 'id': res.id }
}
this.$emit('submitSuccess', res)
this.emitPerformSuccessMsg(method, res, addContinue)
if (!addContinue) {
setTimeout(() => this.$router.push(route), 100)
}
@@ -226,7 +218,6 @@ export default {
return this.getMethod(this)
},
iUrl() {
// 更新或创建的url
return this.getUrl()
},
iHasSaveContinue() {
@@ -274,7 +265,7 @@ export default {
return Object.assign(this.form, this.initial)
}
let object = this.object
if (!object || Object.keys(object).length === 0) {
if (!object) {
if (cloneFrom) {
this.$log.debug('Clone from: ', cloneFrom)
const url = `${this.url}${cloneFrom}/`

View File

@@ -175,12 +175,8 @@ export default {
},
defaultUpdate() {
const id = this.$route.params.id
let route = this.validActions.updateRoute
if (typeof route === 'string') {
route = { name: route, params: {}}
}
route.params.id = id
this.$router.push(route)
const routeName = this.validActions.updateRoute
this.$router.push({ name: routeName, params: { id: id }})
},
getObject() {
const url = this.validActions.detailApiUrl

View File

@@ -1,5 +1,5 @@
<template>
<ListTable ref="ListTable" v-bind="$attrs" v-on="$listeners" />
<ListTable ref="ListTable" v-bind="iAttrs" v-on="$listeners" />
</template>
<script>
@@ -11,15 +11,16 @@ export default {
ListTable
},
computed: {
...mapGetters(['currentOrgIsRoot'])
},
created() {
const headerActions = this.$attrs['header-actions'] || {}
if (headerActions.canCreate === undefined && this.currentOrgIsRoot) {
_.set(this.$attrs, 'header-actions.canCreate', false)
}
if (headerActions.hasImport === undefined && this.currentOrgIsRoot) {
_.set(this.$attrs, 'header-actions.hasImport', false)
...mapGetters(['currentOrgIsRoot']),
iAttrs() {
const attrs = _.cloneDeep(this.$attrs)
const canCreate = _.get(attrs, 'header-actions.canCreate', null)
this.$log.debug('Can create: ', canCreate)
if (canCreate === null && this.currentOrgIsRoot) {
_.set(attrs, 'header-actions.canCreate', false)
}
this.$log.debug('List table Attrs: ', attrs)
return attrs
}
}
}

View File

@@ -1,8 +1,11 @@
<template>
<Page v-bind="$attrs">
<Page>
<el-alert v-if="helpMessage" type="success"> {{ helpMessage }} </el-alert>
<TreeTable
ref="TreeTable"
v-bind="$attrs"
:table-config="tableConfig"
:header-actions="iHeaderActions"
:tree-setting="treeSetting"
v-on="$listeners"
>
<template #table>
@@ -24,16 +27,23 @@ export default {
components: {
Page, TreeTable
},
computed: {
...mapGetters(['currentOrgIsRoot'])
},
created() {
const headerActions = this.$attrs['header-actions'] || {}
if (headerActions.canCreate === undefined && this.currentOrgIsRoot) {
_.set(this.$attrs, 'header-actions.canCreate', false)
props: {
...TreeTable.props,
helpMessage: {
type: String,
default: null
}
if (headerActions.hasImport === undefined && this.currentOrgIsRoot) {
_.set(this.$attrs, 'header-actions.hasImport', false)
},
computed: {
...mapGetters(['currentOrg']),
iHeaderActions() {
const attrs = _.cloneDeep(this.headerActions)
const canCreate = _.get(attrs, 'canCreate', null)
// this.$log.debug('Current org: ', this.currentOrg)
if (canCreate === null && this.currentOrg && this.currentOrg.is_root) {
_.set(attrs, 'canCreate', false)
}
return attrs
}
},
methods: {

View File

@@ -15,7 +15,7 @@
<el-option
v-for="item in userAdminOrgList"
:key="item.id"
:selected="item.id === currentOrg.id"
selected="item.id == currentOrg.id"
:label="item.name"
:value="item.id"
/>
@@ -48,8 +48,10 @@ export default {
},
methods: {
needShow() {
const hasValidLicense = this.$store.getters.hasValidLicense
return !this.isCollapse && this.inAdminPage && hasValidLicense
const otherOrgs = this.userAdminOrgList.filter(org => {
return !org.is_root && !org.is_default
})
return !this.isCollapse && otherOrgs.length > 0 && this.inAdminPage
},
changeOrg(orgId) {
orgUtil.changeOrg(orgId)

View File

@@ -82,6 +82,7 @@ export default {
case 'userPage':
if (this.currentOrgUsePagePerm) {
this.$store.dispatch('users/setCurrentRole', rolec.USER)
// console.log('Switch to: ', rolec.USER)
window.location.href = `/ui/`
}
break
@@ -95,6 +96,11 @@ export default {
}
},
logout() {
// Clean Status
const statusList = ['currentOrg', 'currentRole', 'jms_current_org', 'jms_current_role', 'sidebarStatus', 'X-JMS-ORG', 'activeTab']
for (const i in statusList) {
this.$cookie.delete(statusList[i])
}
}
}
}

View File

@@ -7,7 +7,6 @@ export { default as GenericDetailPage } from './GenericDetailPage'
export { default as TabPage } from './TabPage'
export { default as Footer } from './Footer'
export { default as GenericListPage } from './GenericListPage'
export { default as GenericListTable } from './GenericListTable'
export { default as GenericCreateUpdatePage } from './GenericCreateUpdatePage'
export { default as GenericCreateUpdateForm } from './GenericCreateUpdateForm'
export { default as GenericUpdateFormDialog } from './GenericUpdateFormDialog'

View File

@@ -38,73 +38,57 @@ export default [
]
},
{
path: 'databases',
path: 'database-apps',
name: 'DatabaseAppList',
component: empty,
meta: { title: i18n.t('route.DatabaseApp') },
children: [
{
path: '',
name: 'DatabaseAppList',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppList'),
meta: { title: i18n.t('route.DatabaseApp') }
},
{
path: 'create',
name: 'DatabaseAppCreate',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppCreateUpdate'),
meta: { title: i18n.t('route.DatabaseAppCreate'), activeMenu: '/applications/databases', action: 'create' },
hidden: true
},
{
path: ':id/update',
name: 'DatabaseAppUpdate',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppCreateUpdate'),
meta: { title: i18n.t('route.DatabaseAppUpdate'), activeMenu: '/applications/databases', action: 'update' },
hidden: true
},
{
path: ':id',
name: 'DatabaseAppDetail',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppDetail/index'),
meta: { title: i18n.t('route.DatabaseAppDetail'), activeMenu: '/applications/databases' },
hidden: true
}
]
component: () => import('@/views/applications/DatabaseApp/DatabaseAppList'),
meta: { title: i18n.t('route.DatabaseApp') }
},
{
path: 'kubernetes',
path: 'database-apps/create',
name: 'DatabaseAppCreate',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppCreateUpdate'),
meta: { title: i18n.t('route.DatabaseAppCreate'), activeMenu: '/applications/database-apps', action: 'create' },
hidden: true
},
{
path: 'database-apps/:id/update',
name: 'DatabaseAppUpdate',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppCreateUpdate'),
meta: { title: i18n.t('route.DatabaseAppUpdate'), activeMenu: '/applications/database-apps', action: 'update' },
hidden: true
},
{
path: 'database-apps/:id',
name: 'DatabaseAppDetail',
component: () => import('@/views/applications/DatabaseApp/DatabaseAppDetail/index'),
meta: { title: i18n.t('route.DatabaseAppDetail'), activeMenu: '/applications/database-apps' },
hidden: true
},
{
path: 'kubernetes-apps',
name: 'KubernetesAppList',
component: empty,
meta: { title: i18n.t('route.KubernetesApp') },
children: [
{
path: '',
name: 'KubernetesAppList',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppList'),
meta: { title: i18n.t('route.KubernetesApp') }
},
{
path: 'create',
name: 'KubernetesAppCreate',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppCreateUpdate'),
meta: { title: i18n.t('route.KubernetesAppCreate'), activeMenu: '/applications/kubernetes', action: 'create' },
hidden: true
},
{
path: ':id/update',
name: 'KubernetesAppUpdate',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppCreateUpdate'),
meta: { title: i18n.t('route.KubernetesAppUpdate'), activeMenu: '/applications/kubernetes', action: 'update' },
hidden: true
},
{
path: ':id',
name: 'KubernetesAppDetail',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppDetail/index'),
meta: { title: i18n.t('route.KubernetesAppDetail'), activeMenu: '/applications/kubernetes' },
hidden: true
}
]
component: () => import('@/views/applications/KubernetesApp/KubernetesAppList'),
meta: { title: i18n.t('route.KubernetesApp') }
},
{
path: 'kubernetes-apps/create',
name: 'KubernetesAppCreate',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppCreateUpdate'),
meta: { title: i18n.t('route.KubernetesAppCreate'), activeMenu: '/applications/kubernetes-apps', action: 'create' },
hidden: true
},
{
path: 'kubernetes-apps/:id/update',
name: 'KubernetesAppUpdate',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppCreateUpdate'),
meta: { title: i18n.t('route.KubernetesAppUpdate'), activeMenu: '/applications/kubernetes-apps', action: 'update' },
hidden: true
},
{
path: 'kubernetes-apps/:id',
name: 'KubernetesAppDetail',
component: () => import('@/views/applications/KubernetesApp/KubernetesAppDetail/index'),
meta: { title: i18n.t('route.KubernetesAppDetail'), activeMenu: '/applications/kubernetes-apps' },
hidden: true
}
]

View File

@@ -101,6 +101,41 @@ export default [
}
]
},
{
path: 'admin-users',
component: empty,
redirect: '',
meta: { title: i18n.t('route.AdminUserList') },
children: [
{
path: '',
name: 'AdminUserList',
component: () => import('@/views/assets/AdminUser/AdminUserList'),
meta: { title: i18n.t('route.AdminUserList'), activeMenu: '/assets/admin-users' }
},
{
path: 'create',
component: () => import('@/views/assets/AdminUser/AdminUserCreateUpdate.vue'), // Parent router-view
name: 'AdminUserCreate',
meta: { title: i18n.t('route.AdminUserCreate'), activeMenu: '/assets/admin-users' },
hidden: true
},
{
path: ':id/update',
component: () => import('@/views/assets/AdminUser/AdminUserCreateUpdate.vue'), // Parent router-view
name: 'AdminUserUpdate',
meta: { title: i18n.t('route.AdminUserUpdate'), activeMenu: '/assets/admin-users' },
hidden: true
},
{
path: ':id',
component: () => import('@/views/assets/AdminUser/AdminUserDetail/index.vue'), // Parent router-view
name: 'AdminUserDetail',
meta: { title: i18n.t('route.AdminUserDetail'), activeMenu: '/assets/admin-users' },
hidden: true
}
]
},
{
path: 'system-users',
component: empty,
@@ -110,20 +145,20 @@ export default [
{
path: '',
name: 'SystemUserList',
component: () => import('@/views/assets/SystemUser/SystemUserList/index.vue'),
component: () => import('@/views/assets/SystemUser/SystemUserList.vue'),
meta: { title: i18n.t('route.SystemUserList'), activeMenu: '/assets/system-users' }
},
{
path: 'create',
name: 'SystemUserCreate',
component: () => import('@/views/assets/SystemUser/SystemUserCreateUpdate/index.vue'),
component: () => import('@/views/assets/SystemUser/SystemUserCreateUpdate.vue'),
meta: { title: i18n.t('route.SystemUserCreate'), activeMenu: '/assets/system-users' },
hidden: true
},
{
path: ':id/update',
name: 'SystemUserUpdate',
component: () => import('@/views/assets/SystemUser/SystemUserCreateUpdate/index.vue'),
component: () => import('@/views/assets/SystemUser/SystemUserCreateUpdate.vue'),
meta: { title: i18n.t('route.SystemUserUpdate'), activeMenu: '/assets/system-users' },
hidden: true
},

View File

@@ -3,8 +3,10 @@ const getters = {
device: state => state.app.device,
token: state => state.users.token,
currentOrg: state => state.users.currentOrg,
currentOrgIsDefault: state => state.users.currentOrg['is_default'],
currentOrgIsRoot: state => state.users.currentOrg['is_root'],
currentOrgIsDefault: state => state.users.currentOrg.is_default,
currentOrgIsRoot: state => {
return state.users.currentOrg && state.users.currentOrg.is_root
},
currentRole: state => state.users.currentRole,
currentUser: state => state.users.profile,
permission_routes: state => state.permission.routes,
@@ -14,15 +16,16 @@ const getters = {
currentOrgRoles: state => state.users.roles,
currentOrgPerms: state => state.users.perms,
MFAVerifyAt: state => state.users.MFAVerifyAt,
MFA_TTl: state => state.settings.publicSettings['SECURITY_MFA_VERIFY_TTL'],
MFA_TTl: state => state.settings.publicSettings.SECURITY_MFA_VERIFY_TTL,
tableConfig: state => state.table.tableConfig,
currentUserIsSuperAdmin: state => state.users.isSuperAdmin,
currentUserIsAdmin: state => state.users.isAdmin,
currentUserIsSuperAdmin: state => {
return state.users.sysRole === 'Admin'
},
hasValidLicense: state => state.settings.hasValidLicense,
userAdminOrgList: (state, getters) => {
let orgs = state.users.orgs
if (!getters.hasValidLicense) {
orgs = orgs.filter(org => !org['is_root'])
orgs = orgs.filter(org => !org.is_root)
}
return orgs
}

View File

@@ -35,7 +35,7 @@ const actions = {
getPublicSettings({ commit, state }) {
return new Promise((resolve, reject) => {
getPublicSettings().then(response => {
const faviconURL = response.data['LOGO_URLS'].favicon
const faviconURL = response.data.LOGO_URLS.favicon
let link = document.querySelector("link[rel*='icon']")
if (!link) {
link = document.createElement('link')
@@ -46,7 +46,7 @@ const actions = {
link.href = faviconURL
// 动态修改Title
document.title = response.data['LOGIN_TITLE']
document.title = response.data.LOGIN_TITLE
commit('SET_PUBLIC_SETTINGS', response.data)
resolve(response)
}).catch(error => {

View File

@@ -1,28 +1,24 @@
import Vue from 'vue'
function getTableConfigFromLocal() {
const configs = localStorage.getItem('tableConfig')
try {
return JSON.parse(configs)
} catch (e) {
return {}
}
function getTableConfigfromCookie() {
return localStorage.getItem('tableConfig') ? JSON.parse(localStorage.getItem('tableConfig')) : {}
}
const state = {
tableConfig: getTableConfigFromLocal()
tableConfig: getTableConfigfromCookie()
}
const mutations = {
SET_TABLE_CONFIG: (state, item) => {
const _tableConfig = getTableConfigFromLocal()
Vue.set(_tableConfig, item.key, item.value)
SET_TABLE_CONFIG: (state, tableConfig) => {
const _tableConfig = localStorage.getItem('tableConfig') ? JSON.parse(localStorage.getItem('tableConfig')) : {}
Vue.set(_tableConfig, tableConfig.key, tableConfig.value)
localStorage.setItem('tableConfig', JSON.stringify(_tableConfig))
}
}
const actions = {
}
export default {

View File

@@ -1,10 +1,10 @@
import { logout, getProfile } from '@/api/users'
import {
getCurrentOrgLocal,
getCurrentRoleLocal,
getTokenFromCookie,
saveCurrentOrgLocal,
saveCurrentRoleLocal
getCurrentOrgFromCookie,
saveCurrentOrgToCookie,
getCurrentRoleFromCookie,
saveCurrentRoleToCookie
} from '@/utils/auth'
import { resetRouter } from '@/router'
import rolec from '@/utils/role'
@@ -12,19 +12,15 @@ import rolec from '@/utils/role'
const getDefaultState = () => {
return {
token: getTokenFromCookie(),
currentOrg: '',
currentRole: '',
currentOrg: getCurrentOrgFromCookie(),
currentRole: getCurrentRoleFromCookie(),
profile: {},
username: '',
roles: {},
sysRole: '',
orgs: [],
perms: 0b00000000,
MFAVerifyAt: null,
isSuperAdmin: false,
isAdmin: false,
hasAdminPerm: false,
hasAuditPerm: false
isSuperAdmin: false
}
}
@@ -39,12 +35,6 @@ const mutations = {
},
SET_PROFILE: (state, profile) => {
state.profile = profile
const username = profile.username
state.username = username
state.currentOrg = getCurrentOrgLocal(username)
state.currentRole = getCurrentRoleLocal(username)
state.isAdmin = profile['is_org_admin']
state.isSuperAdmin = profile['is_superuser']
},
SET_ORGS: (state, orgs) => {
state.orgs = orgs
@@ -63,23 +53,20 @@ const mutations = {
},
SET_ROLES(state, roles) {
state.roles = roles
// rolec.PERM_ADMIN &
},
SET_SYS_ROLE(state, role) {
state.sysRole = role
},
SET_PERMS(state, perms) {
state.perms = perms
state.hasAdmin = (perms & rolec.PERM_ADMIN) === rolec.PERM_ADMIN
state.hasAudit = (perms & rolec.PERM_AUDIT) === rolec.PERM_AUDIT
},
SET_CURRENT_ORG(state, org) {
saveCurrentOrgToCookie(org)
state.currentOrg = org
saveCurrentOrgLocal(state.username, org)
},
SET_CURRENT_ROLE(state, role) {
saveCurrentRoleToCookie(role)
state.currentRole = role
saveCurrentRoleLocal(state.username, role)
},
SET_MFA_VERIFY(state) {
state.MFAVerifyAt = (new Date()).valueOf()

View File

@@ -323,31 +323,6 @@ website: http://code.google.com/p/jquerytree/
display: inline-block;
color: #676a6c;
}
.ztree li span.button.chrome_ico_docu::before {
content: "\f268";
font-family: FontAwesome;
padding-top: 10px;
padding-left: 2px;
display: inline-block;
color: #676a6c;
}
.ztree li span.button.database_ico_docu::before {
content: "\f1c0";
font-family: FontAwesome;
padding-top: 10px;
padding-left: 2px;
display: inline-block;
color: #676a6c;
}
.ztree li span.button.cloud_ico_docu::before {
content: "\f0c2";
font-family: FontAwesome;
padding-top: 10px;
padding-left: 2px;
display: inline-block;
color: #676a6c;
}
.ztree li span.button.windows_ico_docu::before {
content: "\f17a";
font-family: FontAwesome;

View File

@@ -24,7 +24,7 @@ export default {
refresh: () => {},
onSelected: function(event, treeNode) {
if (treeNode.meta.type === 'node') {
const currentNodeId = treeNode.meta.data.id
const currentNodeId = treeNode.meta.node.id
this.tableConfig.url = `/api/v1/perms/users/nodes/${currentNodeId}/assets/?cache_policy=1`
}
}.bind(this)

View File

@@ -1,6 +1,16 @@
<template>
<IBox>
<GenericCreateUpdateForm v-bind="$data" />
<GenericCreateUpdateForm
:fields="fields"
:fields-meta="fieldsMeta"
:initial="object"
:url="url"
:update-success-next-route="updateSuccessNextRoute"
:clean-form-value="cleanFormValue"
:get-method="getMethod"
:on-perform-success="onPerformSuccess"
:perform-submit="performSubmit"
/>
</IBox>
</template>
@@ -72,24 +82,24 @@ export default {
delete value['mfa_level']
}
return value
},
performSubmit(validValues) {
if (!validValues.terms) {
this.$message.error(this.$t('common.PleaseAgreeToTheTerms'))
return Promise.reject()
}
return this.$axios['put'](this.url, validValues)
},
onPerformSuccess() {
this.$message.success(this.$t('common.updateSuccessMsg'))
setTimeout(() => this.$router.push({ name: 'UserGuide' }), 100)
},
getMethod() {
return 'put'
}
}
},
methods: {
getMethod() {
return 'put'
},
performSubmit(validValues) {
if (!validValues.terms) {
this.$message.error(this.$t('common.PleaseAgreeToTheTerms'))
return Promise.reject()
}
return this.$axios['put'](this.url, validValues)
},
onPerformSuccess() {
this.$message.success(this.$t('common.updateSuccessMsg'))
setTimeout(() => this.$router.push({ name: 'UserGuide' }), 100)
}
}
}
</script>

View File

@@ -88,21 +88,6 @@ export default {
}.bind(this)
}
},
{
title: this.$t('users.setFeiShu'),
attrs: {
type: 'primary',
label: this.$store.state.users.profile.is_feishu_bound ? this.$t('common.unbind') : this.$t('common.bind'),
disabled: this.$store.state.users.profile.source !== 'local'
},
has: this.$store.getters.publicSettings.AUTH_FEISHU,
callbacks: {
click: function() {
this.currentEdit = 'feishu'
this.showPasswordDialog = true
}.bind(this)
}
},
{
title: this.$t('users.SetMFA'),
attrs: {

View File

@@ -8,32 +8,34 @@ export function getTokenFromCookie() {
return VueCookie.get(TOKEN_KEY)
}
export function getCurrentRoleLocal(username) {
const key = CURRENT_ROLE_KEY + '_' + username
const role = localStorage.getItem(key)
export function getCurrentRoleFromCookie() {
const role = VueCookie.get(CURRENT_ROLE_KEY)
if (role) {
return parseInt(role) || null
}
return role
}
export function saveCurrentRoleLocal(username, role) {
const key = CURRENT_ROLE_KEY + '_' + username
return localStorage.setItem(key, role)
export function saveCurrentRoleToCookie(role) {
// console.log('Save current role to cookie: ', role)
return VueCookie.set(CURRENT_ROLE_KEY, role, 14)
}
export function getCurrentOrgLocal(username) {
const key = CURRENT_ORG_KEY + '_' + username
const value = localStorage.getItem(key)
export function getCurrentOrgFromCookie() {
let org = null
try {
return JSON.parse(value)
org = JSON.parse(VueCookie.get(CURRENT_ORG_KEY))
} catch (e) {
return null
// console.log('Current org in cookie: ', org)
}
return org
}
export function saveCurrentOrgLocal(username, org) {
const key = CURRENT_ORG_KEY + '_' + username
localStorage.setItem(key, JSON.stringify(org))
export function saveCurrentOrgToCookie(org) {
VueCookie.set(CURRENT_ORG_KEY, JSON.stringify(org), 14)
VueCookie.set('X-JMS-ORG', org.id)
}
export function removeCurrentOrg() {
return VueCookie.remove(CURRENT_ORG_KEY)
}

View File

@@ -214,22 +214,11 @@ export function newURL(url) {
if (url.indexOf('//') > -1) {
obj = new URL(url)
} else {
obj = new URL(url, location.origin)
obj = new URL(url, 'http://localhost')
}
return obj
}
export function getUpdateObjURL(url, objId) {
const urlObj = new URL(url, location.origin)
let pathname = urlObj.pathname
if (!pathname.endsWith('/')) {
pathname += '/'
}
pathname += `${objId}/`
urlObj.pathname = pathname
return urlObj.href
}
export const assignIfNot = _.partialRight(_.assignInWith, customizer)
const scheme = document.location.protocol

View File

@@ -1,3 +0,0 @@
export function openTaskPage(taskId) {
window.open(`/#/ops/celery/task/${taskId}/log/`, '', 'width=900,height=600')
}

View File

@@ -35,7 +35,6 @@ async function checkLogin({ to, from, next }) {
try {
return await store.dispatch('users/getProfile')
} catch (e) {
console.log(e)
const status = e.response.status
if (status === 401 || status === 403) {
setTimeout(() => {

View File

@@ -1,109 +1,401 @@
<template>
<GenericTreeListPage ref="TreeTablePage" :tree-setting="treeSetting">
<template #table>
<AppAccountListTable ref="table" :url="accountsUrl" />
</template>
</GenericTreeListPage>
<Page>
<el-row>
<el-col :span="11">
<GenericListTable ref="LeftTable" class="application-table" :header-actions="leftTable.headerActions" :table-config="leftTable.tableConfig" @row-click="leftTable.tableConfig.rowClick" />
</el-col>
<el-col :span="13">
<GenericListTable v-if="!isInit" ref="RightTable" class="application-user-table" :header-actions="rightTable.headerActions" :table-config="rightTable.tableConfig" />
<div v-else class="noDataR">
<div class="hintWrap">
<div>{{ $t('accounts.PleaseClickLeftApplicationToViewApplicationAccount') }}</div>
</div>
</div>
</el-col>
</el-row>
<Dialog v-if="showMFADialog" width="50" :title="this.$t('common.MFAConfirm')" :visible.sync="showMFADialog" :show-confirm="false" :show-cancel="false" :destroy-on-close="true">
<div v-if="MFAConfirmed">
<el-form label-position="right" label-width="80px" :model="MFAInfo">
<el-form-item :label="this.$t('assets.Applications')">
<el-input v-model="MFAInfo.application" disabled />
</el-form-item>
<el-form-item :label="this.$t('assets.Username')">
<el-input v-model="MFAInfo.username" disabled />
</el-form-item>
<el-form-item :label="this.$t('assets.PasswordOrToken')">
<el-input v-model="MFAInfo.password" type="password" show-password />
</el-form-item>
</el-form>
</div>
<el-row v-else :gutter="20">
<el-col :span="4">
<div style="line-height: 34px;text-align: center">MFA</div>
</el-col>
<el-col :span="14">
<el-input v-model="MFAInput" />
<span class="help-tips help-block">{{ $t('common.MFARequireForSecurity') }}</span>
</el-col>
<el-col :span="4">
<el-button size="mini" type="primary" style="line-height:20px " @click="MFAConfirm">{{ this.$t('common.Confirm') }}</el-button>
</el-col>
</el-row>
</Dialog>
<Dialog :title="$t('common.Export')" :visible.sync="showExportDialog" :destroy-on-close="true" @confirm="handleExportConfirm()" @cancel="handleExportCancel()">
<el-form label-position="left" style="padding-left: 50px">
<el-form-item :label="$t('common.fileType' )" :label-width="'100px'">
<el-radio-group v-model="exportTypeOption">
<el-radio v-for="option of exportTypeOptions" :key="option.value" style="padding: 10px 20px;" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="this.$t('common.imExport.ExportRange')" :label-width="'100px'">
<el-radio-group v-model="exportOption">
<el-radio v-for="option of exportOptions" :key="option.value" class="export-item" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</Dialog>
</Page>
</template>
<script>
import GenericTreeListPage from '@/layout/components/GenericTreeListPage'
import AppAccountListTable from '@/components/AppAccountListTable'
import { setUrlParam } from '@/utils/common'
import Page from '@/layout/components/Page'
import GenericListTable from '@/layout/components/GenericListTable'
import { ActionsFormatter, DetailFormatter } from '@/components/TableFormatters'
import Dialog from '@/components/Dialog'
import { mapGetters } from 'vuex'
import { createSourceIdCache } from '@/api/common'
import * as queryUtil from '@/components/DataTable/compenents/el-data-table/utils/query'
export default {
name: 'AssetAccountList',
components: {
GenericTreeListPage, AppAccountListTable
GenericListTable, Page, Dialog
},
data() {
const vm = this
return {
isInit: true,
clickedRow: null,
iShowTree: true,
accountsUrl: '/api/v1/applications/accounts/',
treeSetting: {
async: false,
showMenu: false,
showRefresh: true,
showAssets: false,
treeUrl: '/api/v1/applications/applications/tree/',
callback: {
onSelected: function(event, treeNode) {
let url = '/api/v1/applications/accounts/'
const nodeId = treeNode.id
const value = treeNode.meta.data?.value
if (treeNode.meta.type === 'category') {
url = setUrlParam(url, 'category', value)
url = setUrlParam(url, 'type', '')
} else if (treeNode.meta.type === 'type') {
url = setUrlParam(url, 'category', '')
url = setUrlParam(url, 'type', value)
} else if (treeNode.meta.type === 'application') {
url = setUrlParam(url, 'category', '')
url = setUrlParam(url, 'type', '')
url = setUrlParam(url, 'app', nodeId)
showMFADialog: false,
MFAConfirmed: false,
MFAInput: '',
MFAInfo: {
systemUser: '',
application: '',
username: '',
password: ''
},
selectedRows: '',
showExportDialog: false,
dialogStatus: '',
exportOption: 'all',
exportTypeOption: 'csv',
clickedRow: {},
leftTable: {
tableConfig: {
url: '/api/v1/applications/applications/',
columns: [
'name', 'category_display', 'type_display', 'comment', 'org_name'
],
columnsShow: {
min: ['name'],
default: ['name', 'category_display', 'type_display']
},
columnsMeta: {
name: {
formatter: DetailFormatter,
formatterArgs: {
getRoute({ row, col, cellValue }) {
return {
'db': 'DatabaseAppDetail', 'remote_app': 'RemoteAppDetail', 'cloud': 'KubernetesAppDetail'
}[row.category]
}
},
showOverflowTooltip: true,
sortable: false
}
},
tableAttrs: {
stripe: false, // 斑马纹表格
border: true, // 表格边框
fit: true, // 宽度自适应,
tooltipEffect: 'dark',
rowClassName({ row, rowIndex }) {
if (row === vm.clickedRow) {
return 'row-clicked'
}
return ''
}
},
rowClick: function(row, column, event) {
vm.rightTable.tableConfig.url = `/api/v1/applications/application-users/?application_id=${row.id}`
vm.rightTable.tableConfig.extraQuery.application_id = row.id
vm.clickedRow = row
vm.MFAInfo.application = row.name
vm.isInit = false
}
},
headerActions: {
hasLeftActions: false,
hasCreate: false,
hasExport: false,
hasImport: false,
hasBulkDelete: false,
hasBulkUpdate: false
}
},
rightTable: {
tableConfig: {
url: `/api/v1/applications/application-users/?application_id=`,
extraQuery: {
application_id: ''
},
columns: [
'name', 'username', 'username_same_with_user', 'protocol', 'login_mode', 'priority', 'comment', 'org_name', 'actions'
],
columnsShow: {
min: ['name', 'username', 'actions'],
default: ['name', 'username', 'date_created', 'actions']
},
columnsMeta: {
name: {
formatter: DetailFormatter,
formatterArgs: {
route: 'SystemUserDetail'
},
showOverflowTooltip: true,
sortable: false
},
protocol: {
sortable: false
},
login_mode: {
sortable: false
},
actions: {
label: this.$t('common.Action'),
align: 'center',
width: 150,
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: false, // can set function(row, value)
hasDelete: false, // can set function(row, value)
hasClone: false,
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'View',
title: this.$t('common.View'),
type: 'primary',
callback: function(val) {
this.dialogStatus = 'viewAuthInfo'
this.MFAInfo.systemUser = val.row
if (!this.needMFAVerify) {
this.showMFADialog = true
this.MFAConfirmed = true
this.$axios.get(`/api/v1/assets/system-users/${this.MFAInfo.systemUser.id}/auth-info/`).then(res => {
this.MFAConfirmed = true
this.MFAInfo.username = res.username
if (res.protocol === 'k8s') {
this.MFAInfo.password = res.token
} else {
this.MFAInfo.password = res.password
}
})
} else {
this.showMFADialog = true
}
}.bind(this)
}
]
}
},
tableAttrs: {
stripe: false, // 斑马纹表格
border: true, // 表格边框
fit: true, // 宽度自适应,
tooltipEffect: 'dark',
rowClassName({ row, rowIndex }) {
return 'row-background-color'
}
}
}
},
headerActions: {
hasLeftActions: false,
hasImport: false,
handleExport({ selectedRows }) {
vm.selectedRows = selectedRows
vm.dialogStatus = 'export'
if (!vm.needMFAVerify) {
vm.showMFADialog = false
vm.showExportDialog = true
} else {
vm.showMFADialog = true
}
setTimeout(() => {
vm.accountsUrl = url
}, 100)
}
}
}
}
},
computed: {
...mapGetters([
'MFA_TTl',
'MFAVerifyAt',
'publicSettings'
]),
needMFAVerify() {
if (!this.publicSettings.SECURITY_VIEW_AUTH_NEED_MFA) {
return false
}
const ttl = this.publicSettings.SECURITY_MFA_VERIFY_TTL
const now = new Date()
return !(this.MFAVerifyAt && (now - this.MFAVerifyAt) < ttl * 1000)
},
exportOptions() {
return [
{
label: this.$t('common.imExport.ExportAll'),
value: 'all',
can: true
},
{
label: this.$t('common.imExport.ExportOnlySelectedItems'),
value: 'selected',
can: this.selectedRows.length > 0
}
]
},
exportTypeOptions() {
return [
{
label: 'CSV',
value: 'csv',
can: true
},
{
label: 'Excel',
value: 'xlsx',
can: true
}
]
}
},
methods: {
MFAConfirm() {
if (this.MFAInput.length !== 6) {
return this.$message.error(this.$t('common.MFAErrorMsg'))
}
this.$axios.post(
`/api/v1/authentication/otp/verify/`, {
code: this.MFAInput
}
).then(
res => {
this.$store.dispatch('users/setMFAVerify')
if (this.dialogStatus === 'export') {
this.showMFADialog = false
this.showExportDialog = true
} else {
this.$axios.get(`/api/v1/assets/system-users/${this.MFAInfo.systemUser.id}/auth-info/`).then(res => {
this.MFAConfirmed = true
this.MFAInfo.username = res.username
this.MFAInfo.password = res.password
})
}
}
)
},
downloadCsv(url) {
const a = document.createElement('a')
a.href = url
a.click()
window.URL.revokeObjectURL(url)
},
async performExport(selectRows, exportOption, q) {
const url = `/api/v1/applications/application-user-auth-infos/`
const query = Object.assign({}, q)
if (exportOption === 'selected') {
const resources = []
const data = selectRows
for (let index = 0; index < data.length; index++) {
resources.push(data[index].id)
}
const spm = await createSourceIdCache(resources)
query['spm'] = spm.spm
} else if (exportOption === 'filtered') {
// console.log(listTableRef)
// console.log(listTableRef.dataTable)
// delete query['limit']
// delete query['offset']
}
query['format'] = this.exportTypeOption
const queryStr =
(url.indexOf('?') > -1 ? '&' : '?') +
queryUtil.stringify(query, '=', '&')
return this.downloadCsv(url + queryStr)
},
async performExportConfirm() {
const listTableRef = this.$refs.RightTable.$refs.ListTable
const query = listTableRef.dataTable.getQuery()
delete query['limit']
delete query['offset']
return this.performExport(this.selectedRows, this.exportOption, query)
},
async handleExportConfirm() {
await this.performExportConfirm()
this.showExportDialog = false
},
handleExportCancel() {
this.showExportDialog = false
}
}
}
</script>
<style lang="scss" scoped>
.asset-table ::v-deep .row-clicked, .asset-user-table ::v-deep .row-background-color {
background-color: #f5f7fa;
}
.asset-table {
&:hover {
cursor: pointer;
.application-table ::v-deep .row-clicked, .application-user-table ::v-deep .row-background-color {
background-color: #f5f7fa;
}
& >>> .table-content {
margin-left: 21px;
}
& ::v-deep .el-table__row{
height: 40px;
& > td{
padding: 0;
.application-table {
&:hover {
cursor: pointer;
}
& ::v-deep .el-table__row{
height: 40px;
& > td{
padding: 0;
}
}
}
}
.mini-button{
width: 12px;
float: left;
margin-right: 10px;
text-align: center;
padding: 9px 0;
background-color: #1ab394;
border-color: #1ab394;
color: #FFFFFF;
border-radius: 5px;
line-height: 1.428;
cursor:pointer;
}
.noDataR{
width: 100%;
height: 40vh;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-direction: column;
.hintWrap{
color: #D4D6E6;
display: flex;
align-items: flex-start;
justify-content: center;
flex-direction: column;
.export-item {
width: 100%;
display: block;
padding: 10px 20px;
}
}
.asset-user-table{
padding-left: 20px;
}
.export-form >>> .el-form-item__label {
line-height: 2
}
.application-user-table{
padding-left:20px ;
}
.noDataR {
width: 100%;
height: 40vh;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-direction: column;
.hintWrap {
color: #D4D6E6;
display: flex;
align-items: flex-start;
justify-content: center;
flex-direction: column;
}
}
</style>

View File

@@ -1,20 +1,63 @@
<template>
<GenericTreeListPage ref="TreeTablePage" :tree-setting="treeSetting">
<template #table>
<AccountListTable ref="table" :url="accountsUrl" />
</template>
</GenericTreeListPage>
<Page>
<el-row>
<el-col v-show="iShowTree" :span="iShowTree?4:0">
<AutoDataZTree
ref="AUtoDataZTree"
:setting="treeSetting"
/>
</el-col>
<el-col :span="iShowTree?9:11">
<div class="mini">
<div style="display:block" class="mini-button" @click="iShowTree=!iShowTree">
<i v-show="iShowTree" class="fa fa-angle-left fa-x" />
<i v-show="!iShowTree" class="fa fa-angle-right fa-x" />
</div>
</div>
<GenericListTable
ref="LeftTable"
class="asset-table"
:header-actions="leftTable.headerActions"
:table-config="leftTable.tableConfig"
@row-click="leftTable.tableConfig.rowClick"
/>
</el-col>
<el-col :span="iShowTree?11:13">
<AssetUserTable
v-if="!isInit"
ref="RightTable"
class="asset-user-table"
:url="rightTable.url"
:search-exclude="rightTable.searchExclude"
:extra-query="rightTable.extraQuery"
:has-left-actions="rightTable.hasLeftActions"
:table-config="rightTable.tableConfig"
:has-clone="false"
:has-import="false"
/>
<div v-else class="noDataR">
<div class="hintWrap">
<div>
{{ $t('accounts.PleaseClickLeftAssetToViewAssetAccount') }}
</div>
</div>
</div>
</el-col>
</el-row>
</Page>
</template>
<script>
import GenericTreeListPage from '@/layout/components/GenericTreeListPage'
import AccountListTable from '@/components/AccountListTable'
import { setUrlParam } from '@/utils/common'
import Page from '@/layout/components/Page'
import GenericListTable from '@/layout/components/GenericListTable'
import AutoDataZTree from '@/components/AutoDataZTree/index'
import { AssetUserTable } from '@/components'
import { DetailFormatter } from '@/components/TableFormatters'
export default {
name: 'AssetAccountList',
components: {
GenericTreeListPage, AccountListTable
AutoDataZTree, GenericListTable, Page, AssetUserTable
},
data() {
const vm = this
@@ -22,30 +65,104 @@ export default {
isInit: true,
clickedRow: null,
iShowTree: true,
accountsUrl: '/api/v1/assets/accounts/',
treeSetting: {
showMenu: false,
showRefresh: true,
showRefresh: false,
showAssets: false,
url: '/api/v1/assets/accounts/',
treeUrl: '/api/v1/assets/nodes/children/tree/?assets=1',
url: '',
treeUrl: '/api/v1/assets/nodes/children/tree/',
callback: {
onSelected: function(event, treeNode) {
let url = '/api/v1/assets/accounts/'
if (treeNode.meta.type === 'node') {
const nodeId = treeNode.meta.data.id
url = setUrlParam(url, 'asset', '')
url = setUrlParam(url, 'node', nodeId)
} else if (treeNode.meta.type === 'asset') {
const assetId = treeNode.meta.data.id
url = setUrlParam(url, 'node', '')
url = setUrlParam(url, 'asset', assetId)
}
setTimeout(() => {
vm.accountsUrl = url
}, 100)
vm.leftTable.tableConfig.url = `/api/v1/assets/assets/?node_id=${treeNode.meta.node.id}`
vm.isInit = true
}
}
},
leftTable: {
tableConfig: {
url: '/api/v1/assets/assets/',
columns: [
'hostname', 'ip', 'protocols', 'platform', 'comment', 'org_name'
],
columnsShow: {
min: ['hostname', 'ip'],
default: ['hostname', 'ip', 'platform']
},
columnsMeta: {
hostname: {
formatter: DetailFormatter,
formatterArgs: {
route: 'AssetDetail',
routeQuery: {
activeTab: 'Detail'
}
},
showOverflowTooltip: true
},
ip: {
showOverflowTooltip: true
}
},
tableAttrs: {
stripe: false, // 斑马纹表格
border: true, // 表格边框
fit: true, // 宽度自适应,
tooltipEffect: 'dark',
rowClassName({ row, rowIndex }) {
if (row === vm.clickedRow) {
return 'row-clicked'
}
return ''
}
},
rowClick: function(row, column, event) {
vm.rightTable.url = `/api/v1/assets/asset-users/?asset_id=${row.id}`
vm.rightTable.extraQuery.asset_id = row.id
vm.clickedRow = row
vm.isInit = false
}
},
headerActions: {
hasLeftActions: false,
hasCreate: false,
hasExport: false,
hasImport: false,
hasBulkDelete: false,
hasColumnSetting: true,
hasRefresh: true,
hasBulkUpdate: false
}
},
rightTable: {
url: `/api/v1/assets/asset-users/?hostname=ShowFirstAssetRelated`,
extraQuery: {
latest: 1
},
tableConfig: {
columns: ['name', 'username', 'version', 'backend_display', 'date_created', 'org_name', 'actions'],
columnsShow: {
min: ['username', 'actions'],
default: ['name', 'username', 'version', 'backend_display', 'date_created', 'actions']
},
columnsMeta: {
name: {
formatter: null,
showOverflowTooltip: true,
sortable: false
}
},
tableAttrs: {
stripe: true, // 斑马纹表格
border: false, // 表格边框
fit: true, // 宽度自适应,
tooltipEffect: 'dark',
rowClassName({ row, rowIndex }) {
return 'row-background-color'
}
}
},
hasLeftActions: false,
searchExclude: ['hostname', 'id', 'ip']
}
}
}

View File

@@ -1,5 +1,5 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdatePage v-bind="$data" :clean-form-value="cleanFormValue" />
</template>
<script>
@@ -24,17 +24,13 @@ export default {
fields: [
[this.$t('common.Basic'), ['name']],
[this.$t('xpack.Asset'), ['username', 'assets', 'nodes']],
[this.$t('xpack.ChangeAuthPlan.PasswordStrategy'), ['is_password', 'password_strategy', 'password', 'password_rules']],
[this.$t('xpack.ChangeAuthPlan.SecretKeyStrategy'), ['is_ssh_key', 'ssh_key_strategy', 'private_key']],
[this.$t('xpack.ChangeAuthPlan.PasswordStrategy'), ['password_strategy', 'password', 'password_rules']],
[this.$t('xpack.Timer'), ['is_periodic', 'crontab', 'interval']],
[this.$t('common.Other'), ['comment']]
],
initial: {
password_strategy: 'custom',
ssh_key_strategy: 'add',
is_periodic: true,
is_password: true,
is_ssh_key: false,
password_rules: {
length: 30
},
@@ -54,7 +50,7 @@ export default {
},
password: {
hidden: (formValue) => {
return formValue.password_strategy !== 'custom' || formValue.is_password === false
return formValue.password_strategy !== 'custom'
},
rules: [
{ required: this.$route.meta.action === 'create', message: this.$t('common.fieldRequiredError'), trigger: 'blur' }
@@ -64,19 +60,6 @@ export default {
type: 'group',
items: this.generatePasswordRulesItemsFields()
},
private_key: {
el: {
type: 'textarea',
placeholder: '-----BEGIN OPENSSH PRIVATE KEY-----',
autosize: { minRows: 3 }
},
hidden: (formValue) => {
return formValue.is_ssh_key === false
},
rules: [
{ required: this.$route.meta.action === 'create', message: this.$t('common.fieldRequiredError'), trigger: 'blur' }
]
},
nodes: {
label: this.$t('xpack.Node'),
el: {
@@ -92,24 +75,6 @@ export default {
is_periodic: {
type: 'switch'
},
is_password: {
type: 'switch'
},
is_ssh_key: {
type: 'switch'
},
password_strategy: {
label: this.$t('xpack.ChangeAuthPlan.PasswordStrategy'),
hidden: (formValue) => {
return formValue.is_password === false
}
},
ssh_key_strategy: {
label: this.$t('xpack.ChangeAuthPlan.SecretKeyStrategy'),
hidden: (formValue) => {
return formValue.is_ssh_key === false
}
},
crontab: {
label: this.$t('xpack.RegularlyPerform'),
hidden: (formValue) => {
@@ -127,21 +92,21 @@ export default {
{ validator: validatorInterval }
]
}
},
cleanFormValue(data) {
if (data['password_strategy'] === 'custom') {
delete data['password_rules']
} else {
delete data['password']
}
if (data['interval'] === '') {
delete data['interval']
}
return data
}
}
},
methods: {
cleanFormValue(data) {
if (data['password_strategy'] === 'custom') {
delete data['password_rules']
} else {
delete data['password']
}
if (data['interval'] === '') {
delete data['interval']
}
return data
},
generatePasswordRulesItemsFields() {
const itemsFields = []
const items = [
@@ -150,7 +115,7 @@ export default {
items.forEach((item, index, array) => {
itemsFields.push({
id: item.id, prop: item.prop, el: {}, attrs: {}, type: 'input', label: item.label, rules: [Required],
hidden: (formValue) => { return ['random_one', 'random_all'].indexOf(formValue.password_strategy) === -1 || formValue.is_password === false }
hidden: (formValue) => { return ['random_one', 'random_all'].indexOf(formValue.password_strategy) === -1 }
})
})
return itemsFields

View File

@@ -23,7 +23,7 @@ export default {
url: `/api/v1/xpack/change-auth-plan/plan-execution/?plan_id=${this.object.id}`,
columns: [
'username', 'assets_amount', 'nodes_amount', 'result_summary', 'password_strategy_display',
'timedelta', 'trigger_display', 'date_start', 'actions'
'timedelta', 'date_start', 'actions'
],
columnsMeta: {
username: {

View File

@@ -5,7 +5,6 @@
<script>
import { GenericListPage } from '@/layout/components'
import { DetailFormatter } from '@/components/TableFormatters'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'ChangeAuthPlanList',
@@ -21,10 +20,6 @@ export default {
'name', 'username', 'assets_amount', 'nodes_amount', 'password_strategy_display',
'periodic_display', 'run_times', 'comment', 'org_name', 'actions'
],
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'username', 'password_strategy_display', 'periodic_display', 'run_times', 'actions']
},
columnsMeta: {
username: {
showOverflowTooltip: true
@@ -74,7 +69,7 @@ export default {
`/api/v1/xpack/change-auth-plan/plan-execution/`,
{ plan: row.id }
).then(res => {
openTaskPage(res['task'])
window.open(`/#/ops/celery/task/${res.task}/log/`, '_blank', 'toolbar=yes, width=900, height=600')
})
}.bind(this)
}

View File

@@ -1,75 +1,249 @@
<template>
<TreeTable :table-config="tableConfig" :tree-setting="treeSetting" :header-actions="headerActions" />
<div>
<el-row>
<el-col v-show="iShowTree" :span="iShowTree?4:0">
<AutoDataZTree
ref="AUtoDataZTree"
:setting="treeSetting"
/>
</el-col>
<el-col :span="iShowTree?9:11">
<div class="mini">
<div style="display:block" class="mini-button" @click="iShowTree=!iShowTree">
<i v-show="iShowTree" class="fa fa-angle-left fa-x" />
<i v-show="!iShowTree" class="fa fa-angle-right fa-x" />
</div>
</div>
<GenericListTable
ref="LeftTable"
class="asset-table"
:header-actions="leftTable.headerActions"
:table-config="leftTable.tableConfig"
@row-click="leftTable.tableConfig.rowClick"
/>
</el-col>
<el-col :span="iShowTree?11:13">
<GenericListTable
v-if="!isInit"
ref="RightTable"
class="asset-user-table"
:header-actions="rightTable.headerActions"
:table-config="rightTable.tableConfig"
/>
<div v-else class="noDataR">
<div class="hintWrap">
<div>{{ $t('accounts.PleaseClickLeftAssetToViewGatheredUser') }}</div>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import TreeTable from '@/components/TreeTable'
import GenericListTable from '@/layout/components/GenericListTable'
import AutoDataZTree from '@/components/AutoDataZTree/index'
import { ChoicesFormatter, DetailFormatter } from '@/components/TableFormatters'
export default {
name: 'AssetAccountList',
components: {
TreeTable
AutoDataZTree, GenericListTable
},
data() {
const vm = this
return {
isInit: true,
clickedRow: {},
iShowTree: true,
treeSetting: {
showMenu: false,
showRefresh: true,
showAssets: true,
url: '/api/v1/assets/gathered-users/',
nodeUrl: '/api/v1/assets/nodes/',
// ?assets=0不显示资产. =1显示资产
treeUrl: '/api/v1/assets/nodes/children/tree/?assets=1'
},
tableConfig: {
url: '/api/v1/assets/gathered-users/',
hasTree: true,
columns: [
'hostname', 'ip', 'username', 'date_last_login', 'present', 'ip_last_login', 'date_updated'
],
columnsMeta: {
hostname: {
showOverflowTooltip: true
},
ip: {
width: 120
},
username: {
showOverflowTooltip: true
},
present: {
width: 80
},
ip_last_login: {
width: 120
showRefresh: false,
showAssets: false,
url: '',
treeUrl: '/api/v1/assets/nodes/children/tree/',
callback: {
onSelected: function(event, treeNode) {
vm.leftTable.tableConfig.url = `/api/v1/assets/assets/?node_id=${treeNode.meta.node.id}`
vm.isInit = true
}
}
},
headerActions: {
hasCreate: false,
hasLeftActions: false,
hasImport: false,
searchConfig: {
exclude: ['asset'],
options: [
{
label: this.$t('assets.Hostname'),
value: 'asset__hostname'
leftTable: {
tableConfig: {
url: '/api/v1/assets/assets/',
columns: [
'hostname', 'ip', 'public_ip', 'admin_user_display',
'protocols', 'platform', 'connectivity',
'created_by', 'date_created', 'comment', 'org_name'
],
columnsShow: {
min: ['hostname', 'ip', 'platform'],
default: ['hostname', 'ip', 'connectivity', 'platform']
},
columnsMeta: {
hostname: {
formatter: DetailFormatter,
formatterArgs: {
route: 'AssetDetail',
routeQuery: {
activeTab: 'Detail'
}
},
showOverflowTooltip: true
},
{
label: 'IP',
value: 'asset__ip'
connectivity: {
label: this.$t('assets.Reachable'),
formatter: ChoicesFormatter,
formatterArgs: {
iconChoices: {
0: 'fa-times text-danger',
1: 'fa-check text-primary',
2: 'fa-circle text-warning'
},
typeChange: function(val) {
if (!val) {
return 2
}
return val.status
},
hasTips: true
},
width: '90px',
align: 'center'
}
]
},
tableAttrs: {
stripe: false, // 斑马纹表格
border: true, // 表格边框
fit: true, // 宽度自适应,
tooltipEffect: 'dark',
rowClassName({ row, rowIndex }) {
if (row === vm.clickedRow) {
return 'row-clicked'
}
return ''
}
},
rowClick: function(row, column, event) {
vm.rightTable.tableConfig.url = `/api/v1/assets/gathered-users/?asset_id=${row.id}`
vm.clickedRow = row
vm.isInit = false
}
},
headerActions: {
hasLeftActions: false,
hasCreate: false,
hasExport: false,
hasImport: false,
hasBulkDelete: false,
hasBulkUpdate: false
}
},
rightTable: {
tableConfig: {
url: `/api/v1/assets/gathered-users/?asset__hostname=ShowFirstAssetRelated`,
columns: [
'username', 'date_last_login', 'present', 'ip_last_login', 'date_updated', 'org_name'
],
columnsShow: {
min: ['username'],
default: [
'username', 'date_last_login', 'present', 'ip_last_login', 'date_updated'
]
},
columnsMeta: {
username: {
showOverflowTooltip: true
},
present: {
width: 80
},
ip_last_login: {
width: 120
}
},
tableAttrs: {
stripe: true, // 斑马纹表格
border: true, // 表格边框
fit: true, // 宽度自适应,
tooltipEffect: 'dark',
rowClassName({ row, rowIndex }) {
return 'row-background-color'
}
}
},
headerActions: {
hasLeftActions: false,
hasCreate: false,
hasExport: true,
hasImport: false,
hasBulkDelete: false,
hasBulkUpdate: false,
searchConfig: {
exclude: ['asset']
}
}
}
}
},
methods: {
onGatherUserTasks() {
this.$router.push({ name: 'GatherUserTaskList' })
}
}
}
</script>
<style>
<style lang="scss" scoped>
.asset-table ::v-deep .row-clicked, .asset-user-table ::v-deep .row-background-color {
background-color: #f5f7fa;
}
.asset-table {
&:hover {
cursor: pointer;
}
& >>> .table-content {
margin-left: 21px;
}
& ::v-deep .el-table__row{
height: 40px;
& > td{
padding: 0;
}
}
}
.mini-button{
width: 12px;
float: left;
margin-right: 10px;
text-align: center;
padding: 9px 0;
background-color: #1ab394;
border-color: #1ab394;
color: #FFFFFF;
border-radius: 5px;
line-height: 1.428;
cursor:pointer;
}
.noDataR{
width: 100%;
height: 40vh;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-direction: column;
.hintWrap{
color: #D4D6E6;
display: flex;
align-items: flex-start;
justify-content: center;
flex-direction: column;
}
}
.asset-user-table{
padding-left: 20px;
& ::v-deep .el-table__header-wrapper thead tr{
height: 40px;
& > th{
padding: 0;
}
}
}
</style>

View File

@@ -38,7 +38,7 @@ export default {
value: [],
ajax: {
transformOption: (item) => {
return { label: item['full_value'], value: item.id }
return { label: item.full_value, value: item.id }
},
url: '/api/v1/assets/nodes/'
}

View File

@@ -34,7 +34,7 @@ export default {
},
{
key: this.$t('xpack.Cloud.PeriodicPerform'),
value: this.object.is_periodic ? (this.$t('xpack.Cloud.True')) : (this.$t('xpack.Cloud.False'))
value: this.object.is_periodic ? (this.$t('xpack.GatherUser.True')) : (this.$t('xpack.GatherUser.False'))
},
{
key: this.$t('xpack.Cloud.Periodic'),

View File

@@ -5,7 +5,6 @@
<script>
import GenericListTable from '@/layout/components/GenericListTable'
import { DetailFormatter } from '@/components/TableFormatters'
import { openTaskPage } from '@/utils/jms'
export default {
components: {
@@ -66,7 +65,7 @@ export default {
`/api/v1/xpack/gathered-user/task-executions/`,
{ task: data.row.id }
).then(res => {
openTaskPage(res['task'])
window.open(`/#/ops/celery/task/${res.task}/log/`, '_blank', 'toolbar=yes, width=900, height=600')
}).catch(res => {
})
}

View File

@@ -1,5 +1,6 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" :perform-submit="performSubmit" :after-get-form-value="afterGetFormValue" />
</template>
<script>
@@ -59,40 +60,59 @@ export default {
}
}
},
url: '/api/v1/acls/login-asset-acls/',
afterGetFormValue(formValue) {
formValue.assets.ip_group = formValue.assets.ip_group.toString()
formValue.assets.hostname_group = formValue.assets.hostname_group.toString()
formValue.system_users.name_group = formValue.system_users.name_group.toString()
formValue.system_users.protocol_group = formValue.system_users.protocol_group.toString()
formValue.system_users.username_group = formValue.system_users.username_group.toString()
formValue.users.username_group = formValue.users.username_group.toString()
return formValue
},
cleanFormValue(value) {
if (!Array.isArray(value.assets.ip_group)) {
value.assets.ip_group = value.assets.ip_group ? value.assets.ip_group.split(',') : []
}
if (!Array.isArray(value.assets.hostname_group)) {
value.assets.hostname_group = value.assets.hostname_group ? value.assets.hostname_group.split(',') : []
}
if (!Array.isArray(value.system_users.protocol_group)) {
value.system_users.protocol_group = value.system_users.protocol_group ? value.system_users.protocol_group.split(',') : []
}
if (!Array.isArray(value.system_users.name_group)) {
value.system_users.name_group = value.system_users.name_group ? value.system_users.name_group.split(',') : []
}
if (!Array.isArray(value.system_users.username_group)) {
value.system_users.username_group = value.system_users.username_group ? value.system_users.username_group.split(',') : []
}
if (!Array.isArray(value.users.username_group)) {
value.users.username_group = value.users.username_group ? value.users.username_group.split(',') : []
}
return value
}
url: '/api/v1/acls/login-asset-acls/'
}
},
methods: {
getUrl() {
const params = this.$route.params
let url = `/api/v1/acls/login-asset-acls/`
if (params.id) {
url = `${url}${params.id}/`
} else {
url = `${url}`
}
return url
},
getMethod() {
const params = this.$route.params
if (params.id) {
return 'put'
} else {
return 'post'
}
},
afterGetFormValue(validValues) {
validValues.assets.ip_group = validValues.assets.ip_group.toString()
validValues.assets.hostname_group = validValues.assets.hostname_group.toString()
validValues.system_users.name_group = validValues.system_users.name_group.toString()
validValues.system_users.protocol_group = validValues.system_users.protocol_group.toString()
validValues.system_users.username_group = validValues.system_users.username_group.toString()
validValues.users.username_group = validValues.users.username_group.toString()
return validValues
},
performSubmit(validValues) {
if (!Array.isArray(validValues.assets.ip_group)) {
validValues.assets.ip_group = validValues.assets.ip_group ? validValues.assets.ip_group.split(',') : []
}
if (!Array.isArray(validValues.assets.hostname_group)) {
validValues.assets.hostname_group = validValues.assets.hostname_group ? validValues.assets.hostname_group.split(',') : []
}
if (!Array.isArray(validValues.system_users.protocol_group)) {
validValues.system_users.protocol_group = validValues.system_users.protocol_group ? validValues.system_users.protocol_group.split(',') : []
}
if (!Array.isArray(validValues.system_users.name_group)) {
validValues.system_users.name_group = validValues.system_users.name_group ? validValues.system_users.name_group.split(',') : []
}
if (!Array.isArray(validValues.system_users.username_group)) {
validValues.system_users.username_group = validValues.system_users.username_group ? validValues.system_users.username_group.split(',') : []
}
if (!Array.isArray(validValues.users.username_group)) {
validValues.users.username_group = validValues.users.username_group ? validValues.users.username_group.split(',') : []
}
const method = this.getMethod()
return this.$axios[method](`${this.getUrl()}`, validValues)
}
}
}
</script>

View File

@@ -13,17 +13,10 @@ export default {
return {
tableConfig: {
url: '/api/v1/acls/login-asset-acls/',
columns: [
'name', 'user_username_group', 'hostname_group', 'ip_group', 'name_group',
'protocol_group', 'systemuser_username_group', 'reviewers', 'priority',
'is_active', 'comment', 'actions'
],
columns: ['name', 'user_username_group', 'hostname_group', 'ip_group', 'name_group', 'protocol_group', 'systemuser_username_group', 'reviewers', 'priority', 'is_active', 'comment', 'actions'],
columnsShow: {
min: ['name', 'actions'],
default: [
'name', 'user_username_group', 'hostname_group', 'ip_group', 'reviewers',
'priority', 'is_active', 'comment', 'actions'
]
default: ['name', 'user_username_group', 'hostname_group', 'ip_group', 'reviewers', 'priority', 'is_active', 'comment', 'actions']
},
columnsMeta: {
user_username_group: {

View File

@@ -1,5 +1,11 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdatePage
v-bind="$data"
:perform-submit="performSubmit"
:after-get-form-value="afterGetFormValue"
:get-url="getUrl"
/>
</template>
<script>
@@ -37,54 +43,45 @@ export default {
}
}
},
getUrl() {
const query = this.$route.query
const params = this.$route.params
let url = `/api/v1/acls/login-acls/`
if (params.id) {
url = `${url}${params.id}/`
}
if (query.user) {
url = `${url}?user=${query.user}`
}
return url
},
url: `/api/v1/acls/login-acls/`,
updateSuccessNextRoute: { name: 'UserDetail', params: {
id: this.$route.query.user
}},
createSuccessNextRoute: { name: 'UserDetail', params: {
id: this.$route.query.user
}},
onPerformError(error, method, vm) {
this.$emit('submitError', error)
const response = error.response
const data = response.data
if (response.status === 400) {
for (const key of Object.keys(data)) {
let value = data[key]
if (key === 'ip_group') {
value = Object.values(data[key])
}
if (value instanceof Array) {
value = value.join(';')
}
this.$refs.form.setFieldError(key, value)
}
}
},
afterGetFormValue(validValues) {
validValues.ip_group = validValues.ip_group.toString()
return validValues
},
cleanFormValue(value) {
if (!Array.isArray(value.ip_group)) {
value.ip_group = value.ip_group ? value.ip_group.split(',') : []
}
return value
}
}}
}
},
methods: {
getMethod() {
const params = this.$route.params
if (params.id) {
return 'put'
} else {
return 'post'
}
},
getUrl() {
const params = this.$route.params
let url = this.url
if (params.id) {
url = `${url}${params.id}/?user=${this.$route.query.user}`
} else {
url = `${url}?user=${this.$route.query.user}`
}
return url
},
afterGetFormValue(validValues) {
validValues.ip_group = validValues.ip_group.toString()
return validValues
},
performSubmit(validValues) {
if (!Array.isArray(validValues.ip_group)) {
validValues.ip_group = validValues.ip_group ? validValues.ip_group.split(',') : []
}
const method = this.getMethod()
return this.$axios[method](`${this.getUrl()}`, validValues)
}
}
}
</script>

View File

@@ -46,20 +46,33 @@ export default {
getUrl() {
const params = this.$route.params
let url = `/api/v1/applications/applications/`
const method = this.getMethod()
if (params.id) {
url = `${url}${params.id}/`
}
return `${url}?type=${this.$route.query.type}`
return method === 'post' ? `${url}?type=${this.$route.query.type}` : `${url}?category=db`
},
cleanFormValue(value) {
value.category = 'db'
return value
performSubmit(validValues) {
const params = this.$route.params
const baseUrl = `/api/v1/applications/applications/`
const url = (params.id) ? `${baseUrl}${params.id}/` : baseUrl
const method = this.getMethod()
validValues.category = 'db'
return this.$axios[method](`${url}?type=${validValues.type}`, validValues)
}
}
},
computed: {
initial() {
return this.$route.query
},
getMethod() {
const params = this.$route.params
if (params.id) {
return 'put'
} else {
return 'post'
}
}
}
}

View File

@@ -1,7 +1,7 @@
<template>
<GenericDetailPage :object.sync="app" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
<GenericDetailPage :object.sync="DatabaseApp" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
<keep-alive>
<component :is="config.activeMenu" :object="app" />
<component :is="config.activeMenu" :object="DatabaseApp" />
</keep-alive>
</GenericDetailPage>
</template>
@@ -18,7 +18,7 @@ export default {
},
data() {
return {
app: {
DatabaseApp: {
name: '', get_type_display: '', host: '', port: '', database: '', date_created: '', created_by: '', comment: '', attrs: ''
},
config: {
@@ -31,10 +31,7 @@ export default {
],
actions: {
detailApiUrl: `/api/v1/applications/applications/${this.$route.params.id}/`,
deleteApiUrl: `/api/v1/applications/applications/${this.$route.params.id}/`,
updateCallback: (item) => {
this.$router.push({ name: 'DatabaseAppUpdate', params: { id: this.app.id }, query: { type: this.app.type }})
}
deleteApiUrl: `/api/v1/applications/applications/${this.$route.params.id}/`
}
}
}

View File

@@ -45,9 +45,6 @@ export default {
onClone: ({ row }) => {
vm.$router.push({ name: 'DatabaseAppCreate', query: { type: row.type, clone_from: row.id }})
},
onUpdate: ({ row }) => {
vm.$router.push({ name: 'DatabaseAppUpdate', params: { id: row.id }, query: { type: row.type }})
},
performDelete: function({ row, col, cellValue, reload }) {
this.$axios.delete(
`/api/v1/applications/applications/${row.id}/`
@@ -66,7 +63,7 @@ export default {
hasCreate: false,
hasExport: false,
hasImport: false,
hasBulkDelete: true,
hasMoreActions: false,
createRoute: 'DatabaseAppCreate',
moreCreates: {
callback: (item) => {

View File

@@ -50,13 +50,25 @@ export default {
}
return `${url}?type=k8s`
},
cleanFormValue(value) {
value.category = 'cloud'
return value
performSubmit(validValues) {
const params = this.$route.params
const baseUrl = `/api/v1/applications/applications/`
const url = (params.id) ? `${baseUrl}${params.id}/` : baseUrl
const method = this.getMethod()
validValues.category = 'cloud'
return this.$axios[method](`${url}?type=${validValues.type}`, validValues)
}
}
},
computed: {
getMethod() {
const params = this.$route.params
if (params.id) {
return 'put'
} else {
return 'post'
}
}
}
}
</script>

View File

@@ -11,6 +11,7 @@ import DetailCard from '@/components/DetailCard'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'DatabaseAppDetail',
components: {
DetailCard
},

View File

@@ -1,7 +1,7 @@
<template>
<GenericDetailPage :object.sync="app" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
<GenericDetailPage :object.sync="KubernetesApp" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
<keep-alive>
<component :is="config.activeMenu" :object="app" />
<component :is="config.activeMenu" :object="KubernetesApp" />
</keep-alive>
</GenericDetailPage>
</template>
@@ -18,7 +18,7 @@ export default {
},
data() {
return {
app: {
KubernetesApp: {
name: '', type_display: '', cluster: '', date_created: '', created_by: '', comment: '', attrs: ''
},
config: {
@@ -31,10 +31,7 @@ export default {
],
actions: {
detailApiUrl: `/api/v1/applications/applications/${this.$route.params.id}/`,
deleteApiUrl: `/api/v1/applications/applications/${this.$route.params.id}/`,
updateCallback: (item) => {
this.$router.push({ name: 'KubernetesAppUpdate', params: { id: this.app.id }, query: { type: this.app.type }})
}
deleteApiUrl: `/api/v1/applications/applications/${this.$route.params.id}/`
}
}
}

View File

@@ -53,9 +53,9 @@ export default {
}
},
headerActions: {
hasMoreActions: false,
hasExport: false,
hasImport: false,
hasBulkDelete: true,
createRoute: 'KubernetesAppCreate'
}
}

View File

@@ -59,13 +59,26 @@ export default {
}
return `${url}?type=${this.$route.query.type}`
},
cleanFormValue(value) {
value.category = 'remote_app'
return value
performSubmit(validValues) {
this.$log.debug('Validated data: ', validValues)
const params = this.$route.params
const baseUrl = `/api/v1/applications/applications/`
const url = (params.id) ? `${baseUrl}${params.id}/` : baseUrl
const method = this.getMethod()
validValues.category = 'remote_app'
return this.$axios[method](`${url}?type=${validValues.type}`, validValues)
}
}
},
computed: {
getMethod() {
const params = this.$route.params
if (params.id) {
return 'put'
} else {
return 'post'
}
}
}
}

View File

@@ -12,6 +12,7 @@ import DetailCard from '@/components/DetailCard'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'RemoteAppDetail',
components: {
DetailCard
},

View File

@@ -1,7 +1,7 @@
<template>
<GenericDetailPage :object.sync="app" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
<GenericDetailPage :object.sync="RemoteApp" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
<keep-alive>
<component :is="config.activeMenu" :object="app" />
<component :is="config.activeMenu" :object="RemoteApp" />
</keep-alive>
</GenericDetailPage>
</template>
@@ -17,8 +17,9 @@ export default {
TabPage
},
data() {
const vm = this
return {
app: {
RemoteApp: {
name: '', asset: '', get_type_display: '', path: '', date_created: '', created_by: '', comment: '', attrs: ''
},
config: {
@@ -32,8 +33,8 @@ export default {
actions: {
detailApiUrl: `/api/v1/applications/applications/${this.$route.params.id}/`,
deleteApiUrl: `/api/v1/applications/applications/${this.$route.params.id}/`,
updateCallback: (item) => {
this.$router.push({ name: 'RemoteAppUpdate', params: { id: this.app.id }, query: { type: this.app.type }})
updateCallback: function(item) {
vm.$router.push({ name: 'RemoteAppUpdate', params: { id: vm.RemoteApp.id }, query: { type: vm.RemoteApp.type }})
}
}
}

View File

@@ -62,7 +62,8 @@ export default {
},
headerActions: {
hasCreate: false,
hasBulkDelete: true,
hasMoreActions: false,
hasBulkDelete: false,
hasExport: false,
hasImport: false,
// createRoute: 'RemoteAppCreate',

View File

@@ -0,0 +1,65 @@
<template>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import { UploadKey } from '@/components'
export default {
name: 'AdminUserCreateUpdate',
components: {
GenericCreateUpdatePage
},
data() {
return {
initial: {
},
fields: [
[this.$t('common.Basic'), ['name', 'username', 'update_password', 'password', 'private_key']],
[this.$t('common.Other'), ['comment']]
],
fieldsMeta: {
name: {
el: {
placeholder: this.$t('common.Name')
}
},
username: {
el: {
placeholder: this.$t('common.Username')
}
},
update_password: {
label: this.$t('users.UpdatePassword'),
type: 'checkbox',
hidden: (formValue) => {
if (formValue.update_password) {
return true
}
return !this.$route.params.id
}
},
password: {
helpText: this.$t('common.passwordOrPassphrase'),
hidden: (formValue) => {
if (!this.$route.params.id) {
return false
}
return !formValue.update_password
}
},
private_key: {
component: UploadKey
}
},
url: '/api/v1/assets/admin-users/'
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,60 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="16">
<AssetUserTable :url="assetUserUrl" :has-import="false" :has-clone="false" />
</el-col>
</el-row>
</div>
</template>
<script>
import { AssetUserTable } from '@/components'
export default {
name: 'Detail',
components: {
AssetUserTable
},
props: {
object: {
type: Object,
default: () => {}
}
},
data() {
return {
assetUserUrl: `/api/v1/assets/asset-users/?prefer_id=${this.object.id}&prefer=admin_user&latest=1`,
quickActions: [
{
title: this.$t('assets.TestAssetsConnective'),
attrs: {
type: 'primary',
label: this.$t('assets.Test')
},
callbacks: {
click: function() {
this.$axios.get(
`/api/v1/assets/admin-users/${this.object.id}/connective/`
).then(res => {
window.open(`/#/ops/celery/task/${res.task}/log/`, '', 'width=900,height=600')
}
)
}.bind(this)
}
}
]
}
},
computed: {
},
mounted() {
},
methods: {
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -0,0 +1,126 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="16">
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
</el-col>
<el-col :span="8">
<QuickActions type="primary" :actions="quickActions" />
<RelationCard ref="RelationCard" type="info" style="margin-top: 15px" v-bind="nodeRelationConfig" />
</el-col>
</el-row>
</div>
</template>
<script>
import QuickActions from '@/components/QuickActions/index'
import ListTable from '@/components/ListTable'
import RelationCard from '@/components/RelationCard'
import { ChoicesFormatter } from '@/components/TableFormatters'
export default {
name: 'AssetList',
components: {
QuickActions,
ListTable,
RelationCard
},
props: {
object: {
type: Object,
default: () => {}
}
},
data() {
return {
tableConfig: {
url: `/api/v1/assets/assets/?admin_user__id=${this.object.id}`,
columns: [
'hostname', 'ip', 'admin_user_display', 'connectivity'
],
columnsMeta: {
admin_user_display: {
label: this.$t('assets.AdminUser')
},
connectivity: {
label: this.$t('assets.Reachable'),
formatter: ChoicesFormatter,
formatterArgs: {
iconChoices: {
0: 'fa-times text-danger',
1: 'fa-check text-primary',
2: 'fa-circle text-warning'
},
typeChange: function(val) {
return val.status
},
hasTips: true
}
}
}
},
headerActions: {
hasLeftActions: false,
hasBulkDelete: false,
hasImport: false,
hasExport: true,
hasCreate: false,
hasSearch: true,
hasMoreActions: false
},
quickActions: [
{
title: this.$t('assets.TestAssetsConnective'),
attrs: {
type: 'primary',
label: this.$t('assets.Test')
},
callbacks: {
click: function() {
this.$axios.get(
`/api/v1/assets/admin-users/${this.object.id}/connective/`
).then(res => {
window.open(`/#/ops/celery/task/${res.task}/log/`, '', 'width=900,height=600')
}
)
}.bind(this)
}
}
],
nodeRelationConfig: {
icon: 'fa-info',
title: this.$t('assets.ReplaceNodeAssetsAdminUser'),
objectsAjax: {
url: '/api/v1/assets/nodes/',
transformOption: (item) => {
return { label: item.full_value, value: item.id }
}
},
performAdd: (items) => {
const data = []
const relationUrl = `/api/v1/assets/admin-users/${this.object.id}/nodes/`
items.map(v => {
data.push(v.value)
})
return this.$axios.patch(relationUrl, { nodes: data }).then(res => {
this.$message.success(this.$t('common.updateSuccessMsg'))
}).catch(err => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + err))
})
},
onAddSuccess: () => {
this.$refs.RelationCard.$refs.select2.clearSelected()
this.$refs.ListTable.reloadTable()
}
}
}
},
methods: {
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -0,0 +1,60 @@
<template>
<el-row :gutter="20">
<el-col :span="14">
<DetailCard :items="detailCardItems" />
</el-col>
</el-row>
</template>
<script>
import DetailCard from '@/components/DetailCard'
import { toSafeLocalDateStr } from '@/utils/common'
export default {
name: 'Detail',
components: {
DetailCard
},
props: {
object: {
type: Object,
default: () => {}
}
},
data() {
return {
}
},
computed: {
detailCardItems() {
return [
{
key: this.$t('assets.Name'),
value: this.object.name
},
{
key: this.$t('assets.Username'),
value: this.object.username
},
{
key: this.$t('assets.sshKeyFingerprint'),
value: this.object.ssh_key_fingerprint
},
{
key: this.$t('assets.date_joined'),
value: toSafeLocalDateStr(this.object.date_created)
},
{
key: this.$t('assets.CreatedBy'),
value: this.object.created_by
}
]
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -0,0 +1,50 @@
<template>
<GenericDetailPage :object.sync="TaskDetail" :active-menu.sync="config.activeMenu" v-bind="config" v-on="$listeners">
<keep-alive>
<component :is="config.activeMenu" :object="TaskDetail" />
</keep-alive>
</GenericDetailPage>
</template>
<script>
import { GenericDetailPage, TabPage } from '@/layout/components'
import Detail from './Detail.vue'
import AccountList from './AccountList.vue'
import AssetList from './AssetList.vue'
export default {
components: {
GenericDetailPage,
TabPage,
Detail,
AssetList,
AccountList
},
data() {
return {
TaskDetail: {},
config: {
activeMenu: 'Detail',
submenu: [
{
title: this.$t('assets.AdminUserDetail'),
name: 'Detail'
},
{
title: this.$t('assets.AssetList'),
name: 'AssetList'
},
{
title: this.$t('assets.AccountList'),
name: 'AccountList'
}
],
hasRightSide: true
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,45 @@
<template>
<GenericListPage :table-config="tableConfig" :header-actions="headerActions" :help-message="helpMessage" />
</template>
<script>
import { GenericListPage } from '@/layout/components'
export default {
components: {
GenericListPage
},
data() {
return {
tableConfig: {
url: '/api/v1/assets/admin-users/',
columns: [
'name', 'username', 'assets_amount',
'created_by', 'date_created', 'date_updated', 'comment', 'org_name', 'actions'
],
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'username', 'assets_amount', 'comment', 'actions']
},
columnsMeta: {
username: {
showOverflowTooltip: true
},
assets_amount: {
width: '80px'
}
}
},
updateRoute: 'AdminUserUpdate',
headerActions: {
createRoute: 'AdminUserCreate'
},
helpMessage: this.$t('assets.AdminUserListHelpMessage')
}
}
}
</script>
<style>
</style>

View File

@@ -1,5 +1,5 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
</template>
<script>
@@ -63,13 +63,13 @@ export default {
el: {
multiple: false,
ajax: {
url: '/api/v1/assets/system-users/?type=admin',
url: '/api/v1/assets/admin-users/',
transformOption: (item) => {
const username = item.username || '*'
return { label: item.name + '(' + username + ')', value: item.id }
return { label: `${item.name}(${item.username})`, value: item.id }
}
}
}
},
rules: [rules.RequiredChange]
},
nodes: {
rules: [rules.RequiredChange],
@@ -97,8 +97,7 @@ export default {
type: 'switch'
}
},
url: '/api/v1/assets/assets/',
createSuccessNextRoute: { name: 'AssetDetail' }
url: '/api/v1/assets/assets/'
}
}
}

View File

@@ -2,7 +2,7 @@
<div>
<el-row :gutter="24">
<el-col :span="16">
<AccountListTable ref="ListTable" :url="assetUserUrl" :has-import="false" :has-clone="false" />
<AssetUserTable ref="ListTable" :url="assetUserUrl" :has-import="false" :has-clone="false" />
</el-col>
<el-col :span="8">
<QuickActions type="primary" :actions="quickActions" />
@@ -13,13 +13,12 @@
<script>
import QuickActions from '@/components/QuickActions'
import { AccountListTable } from '@/components'
import { openTaskPage } from '@/utils/jms'
import { AssetUserTable } from '@/components'
export default {
name: 'Detail',
components: {
AccountListTable,
AssetUserTable,
QuickActions
},
props: {
@@ -30,7 +29,7 @@ export default {
},
data() {
return {
assetUserUrl: `/api/v1/assets/accounts/?asset=${this.object.id}`,
assetUserUrl: `/api/v1/assets/asset-users/?asset_id=${this.object.id}&latest=1`,
quickActions: [
{
title: this.$t('assets.TestAssetsConnective'),
@@ -41,10 +40,10 @@ export default {
callbacks: {
click: function() {
this.$axios.post(
`/api/v1/assets/accounts/tasks/?asset=${this.object.id}`,
`/api/v1/assets/asset-users/tasks/?asset_id=${this.object.id}&latest=1`,
{ action: 'test' }
).then(res => {
openTaskPage(res['task'])
window.open(`/#/ops/celery/task/${res.task}/log/`, '', 'width=900,height=600')
}
)
}.bind(this)

View File

@@ -17,7 +17,6 @@ import RelationCard from '@/components/RelationCard'
import QuickActions from '@/components/QuickActions'
import LabelCard from './components/LabelCard'
import { toSafeLocalDateStr } from '@/utils/common'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'Detail',
@@ -68,7 +67,7 @@ export default {
`/api/v1/assets/assets/${this.object.id}/tasks/`,
{ action: 'refresh' }
).then(res => {
openTaskPage(res['task'])
window.open(`/#/ops/celery/task/${res.task}/log/`, '', 'width=900,height=600')
}
)
}.bind(this)
@@ -86,12 +85,30 @@ export default {
`/api/v1/assets/assets/${this.object.id}/tasks/`,
{ action: 'test' }
).then(res => {
openTaskPage(res['task'])
window.open(`/#/ops/celery/task/${res.task}/log/`, '', 'width=900,height=600')
}
)
}.bind(this)
}
}
// {
// title: this.$t('assets.PushSystemUserNow'),
// attrs: {
// type: 'primary',
// label: this.$t('assets.Push')
// },
// callbacks: {
// click: function() {
// this.$axios.post(
// `api/v1/assets/system-users/${this.object.id}/tasks/`,
// { action: 'push' }
// ).then(res => {
// window.open(`/ops/celery/task/${res.task}/log/`, '', 'width=900,height=600')
// }
// )
// }.bind(this)
// }
// }
],
nodeRelationConfig: {
icon: 'fa-info',

View File

@@ -1,217 +0,0 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="16">
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
</el-col>
<el-col :span="8">
<QuickActions type="primary" :actions="quickActions" />
<RelationCard ref="systemUserRelation" style="margin-top: 15px" v-bind="systemUserRelationConfig" />
</el-col>
</el-row>
</div>
</template>
<script>
import QuickActions from '@/components/QuickActions/index'
import RelationCard from '@/components/RelationCard'
import ListTable from '@/components/ListTable'
import { DetailFormatter } from '@/components/TableFormatters'
import { connectivityMeta } from '@/components/AccountListTable/const'
import { openTaskPage } from '@/utils/jms'
export default {
name: 'SystemUserList',
components: {
QuickActions,
RelationCard,
ListTable
},
props: {
object: {
type: Object,
default: () => {}
}
},
data() {
const vm = this
return {
tableConfig: {
url: `/api/v1/assets/system-users-assets-relations/?asset=${this.object.id}`,
columns: ['systemuser_display', 'connectivity', 'actions'],
columnsMeta: {
systemuser_display: {
label: this.$t('assets.SystemUser'),
showOverflowTooltip: true,
formatter: DetailFormatter,
formatterArgs: {
getRoute({ row }) {
return {
name: 'SystemUserDetail',
params: { id: row.systemuser }
}
}
}
},
connectivity: connectivityMeta,
actions: {
formatterArgs: {
hasUpdate: false, // can set function(row, value)
hasDelete: false, // can set function(row, value)
hasClone: false,
moreActionsTitle: this.$t('common.More'),
extraActions: [
{
name: 'Test',
title: this.$t('common.Test'),
type: 'primary',
callback: ({ row }) => {
const theUrl = `/api/v1/assets/system-users/${row.systemuser}/tasks/`
const data = { action: 'test', assets: [this.object.id] }
this.$axios.post(theUrl, data).then(resp => {
openTaskPage(resp['task'])
})
}
},
{
name: 'Push',
title: this.$t('common.Push'),
type: 'primary',
callback: ({ row }) => {
const theUrl = `/api/v1/assets/system-users/${row.systemuser}/tasks/`
const data = { action: 'push', assets: [this.object.id] }
this.$axios.post(theUrl, data).then(resp => {
openTaskPage(resp['task'])
})
}
},
{
name: 'Delete',
title: this.$t('common.Delete'),
type: 'danger',
can: !this.$store.getters.currentOrgIsRoot,
callback: (val) => {
this.$axios.delete(`/api/v1/assets/system-users-assets-relations/${val.row.id}/`).then(() => {
this.$message.success(this.$t('common.deleteSuccessMsg'))
this.$refs.ListTable.reloadTable()
})
}
}
]
}
}
}
},
headerActions: {
hasBulkDelete: false,
hasImport: false,
hasCreate: false,
extraMoreActions: [
{
title: this.$t('common.TestSelectedSystemUsersConnective'),
name: 'TestSelected',
can({ selectedRows }) {
return selectedRows.length > 0
},
callback: this.bulkTestCallback.bind(this)
},
{
title: this.$t('common.PushSelectedSystemUsersToAsset'),
name: 'PushSelected',
can({ selectedRows }) {
return selectedRows.length > 0
},
callback: this.bulkPushCallback.bind(this)
}
]
},
quickActions: [
{
title: this.$t('assets.TestAllSystemUsersConnective'),
attrs: {
type: 'primary',
label: this.$t('common.Test')
},
callbacks: {
click: function() {
const theUrl = `/api/v1/assets/assets/${this.object.id}/tasks/`
const data = { action: 'test_system_user' }
this.$axios.post(theUrl, data).then(resp => {
openTaskPage(resp['task'])
})
}.bind(this)
}
},
{
title: this.$t('assets.PushAllSystemUsersToAsset'),
attrs: {
type: 'primary',
label: this.$t('common.Push')
},
callbacks: {
click: function({ row }) {
const theUrl = `/api/v1/assets/assets/${this.object.id}/tasks/`
const data = { action: 'push_system_user' }
this.$axios.post(theUrl, data).then(resp => {
openTaskPage(resp['task'])
})
}.bind(this)
}
}
],
systemUserRelationConfig: {
icon: 'fa-link',
type: 'info',
title: this.$t('assets.AssociateSystemUsers'),
objectsAjax: {
url: `/api/v1/assets/system-users/`
},
performAdd: (items, that) => {
const relationUrl = `/api/v1/assets/system-users-assets-relations/`
const data = items.map((i) => {
return {
'asset': this.object.id,
'systemuser': i.value
}
})
if (data.length === 0) {
return this.$message.error(this.$tc('assets.UnselectedSystemUsers'))
}
return this.$axios.post(relationUrl, data)
},
onAddSuccess: (items, that) => {
vm.$message.success(this.$t('common.updateSuccessMsg'))
vm.$refs.ListTable.reloadTable()
vm.$refs.systemUserRelation.$refs.select2.clearSelected()
}
}
}
},
methods: {
bulkPushCallback({ selectedRows }) {
const theUrl = `/api/v1/assets/assets/${this.object.id}/tasks/`
const systemUsers = selectedRows.map((v) => {
return v.systemuser
})
const data = { action: 'push_system_user', system_users: systemUsers }
this.$axios.post(theUrl, data).then(resp => {
openTaskPage(resp['task'])
})
},
bulkTestCallback({ selectedRows }) {
const theUrl = `/api/v1/assets/assets/${this.object.id}/tasks/`
const systemUsers = selectedRows.map((v) => {
return v.systemuser
})
const data = { action: 'test_system_user', system_users: systemUsers }
this.$axios.post(theUrl, data).then(resp => {
openTaskPage(resp['task'])
})
}
}
}
</script>
<style lang='less' scoped>
</style>

View File

@@ -9,17 +9,14 @@
<script>
import { GenericDetailPage, TabPage } from '@/layout/components'
import Detail from './Detail.vue'
import Account from './Account.vue'
import SystemUserList from './SystemUser.vue'
import AssetUserList from './AssetUserList.vue'
export default {
name: 'AssetListDetail',
components: {
GenericDetailPage,
TabPage,
Detail,
Account,
SystemUserList
AssetUserList
},
data() {
return {
@@ -32,12 +29,8 @@ export default {
name: 'Detail'
},
{
title: this.$t('assets.SystemUser'),
name: 'SystemUserList'
},
{
title: this.$t('assets.AccountList'),
name: 'Account'
title: this.$t('assets.AssetUserList'),
name: 'AssetUserList'
}
],
hasRightSide: true,

View File

@@ -67,7 +67,7 @@
<script>
import GenericTreeListPage from '@/layout/components/GenericTreeListPage/index'
import { DetailFormatter, ActionsFormatter } from '@/components/TableFormatters'
import { DetailFormatter, ActionsFormatter, ChoicesFormatter } from '@/components/TableFormatters'
import $ from '@/utils/jquery-vendor'
import Dialog from '@/components/Dialog'
import TreeTable from '@/components/TreeTable'
@@ -75,8 +75,6 @@ import { GenericUpdateFormDialog } from '@/layout/components'
import rules from '@/components/DataForm/rules'
import Protocols from '@/views/assets/Asset/components/Protocols/index'
import { mapGetters } from 'vuex'
import { connectivityMeta } from '@/components/AccountListTable/const'
import { openTaskPage } from '@/utils/jms'
export default {
components: {
@@ -103,16 +101,18 @@ export default {
hasTree: true,
columns: [
'hostname', 'ip', 'public_ip', 'admin_user_display',
'protocols', 'platform', 'hardware_info', 'model',
'protocols',
'platform', 'hardware_info', 'model',
'cpu_model', 'cpu_cores', 'cpu_count', 'cpu_vcpus',
'disk_info', 'disk_total', 'memory', 'os', 'os_arch',
'os_version', 'number', 'vendor', 'sn',
'disk_info', 'disk_total', 'memory',
'os', 'os_arch', 'os_version',
'number', 'vendor', 'sn',
'connectivity',
'created_by', 'date_created', 'comment', 'org_name', 'actions'
],
columnsShow: {
min: ['hostname', 'ip', 'actions'],
default: ['hostname', 'ip', 'platform', 'protocols', 'hardware_info', 'connectivity', 'actions']
default: ['hostname', 'ip', 'hardware_info', 'connectivity', 'actions']
},
columnsMeta: {
hostname: {
@@ -120,11 +120,7 @@ export default {
formatterArgs: {
route: 'AssetDetail'
},
showOverflowTooltip: true,
sortable: true
},
platform: {
sortable: true
showOverflowTooltip: true
},
protocols: {
formatter: function(row) {
@@ -147,7 +143,26 @@ export default {
comment: {
showOverflowTooltip: true
},
connectivity: connectivityMeta,
connectivity: {
label: this.$t('assets.Reachable'),
formatter: ChoicesFormatter,
formatterArgs: {
iconChoices: {
0: 'fa-times text-danger',
1: 'fa-check text-primary',
2: 'fa-circle text-warning'
},
typeChange: function(val) {
if (!val) {
return 2
}
return val.status
},
hasTips: true
},
width: '90px',
align: 'center'
},
actions: {
formatter: ActionsFormatter,
formatterArgs: {
@@ -177,7 +192,6 @@ export default {
query: this.$route.query
}
},
createInNewPage: true,
searchConfig: {
options: [
{ label: this.$t('assets.Label'), value: 'label' }
@@ -420,10 +434,10 @@ export default {
return
}
this.$axios.post(
`/api/v1/assets/nodes/${currentNode.meta.data.id}/tasks/`,
`/api/v1/assets/nodes/${currentNode.meta.node.id}/tasks/`,
{ 'action': 'refresh' }
).then((res) => {
openTaskPage(res['task'])
window.open(`/core/ops/celery/task/${res.task}/log/`, '_blank', 'toolbar=yes, width=900, height=600')
}).catch(error => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
})
@@ -435,10 +449,10 @@ export default {
return
}
this.$axios.post(
`/api/v1/assets/nodes/${currentNode.meta.data.id}/tasks/`,
`/api/v1/assets/nodes/${currentNode.meta.node.id}/tasks/`,
{ 'action': 'test' }
).then((res) => {
openTaskPage(res['task'])
window.open(`/core/ops/celery/task/${res.task}/log/`, '_blank', 'toolbar=yes, width=900, height=600')
}).catch(error => {
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
})
@@ -451,7 +465,7 @@ export default {
}
this.$cookie.set('show_current_asset', '1', 1)
this.decorateRMenu()
const url = `${this.treeSetting.url}?node_id=${currentNode.meta.data.id}&show_current_asset=1`
const url = `${this.treeSetting.url}?node_id=${currentNode.meta.node.id}&show_current_asset=1`
this.$refs.TreeList.$refs.TreeTable.handleUrlChange(url)
},
rMenuShowAssetAllChildrenNode: function() {
@@ -462,7 +476,7 @@ export default {
}
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`
const url = `${this.treeSetting.url}?node_id=${currentNode.meta.node.id}&show_current_asset=0`
this.$refs.TreeList.$refs.TreeTable.handleUrlChange(url)
},
rMenuShowNodeInfo: function() {
@@ -472,7 +486,7 @@ export default {
return
}
this.$axios.get(
`/api/v1/assets/nodes/${currentNode.meta.data.id}/`
`/api/v1/assets/nodes/${currentNode.meta.node.id}/`
).then(res => {
this.nodeInfoDialogSetting.dialogVisible = true
this.nodeInfoDialogSetting.items = [
@@ -489,7 +503,7 @@ export default {
this.$axios.post(
`/api/v1/assets/nodes/check_assets_amount_task/`
).then(res => {
openTaskPage(res['task'])
window.open(`/#/ops/celery/task/${res.task}/log/`, '', 'width=900,height=600')
}).catch(error => {
this.$message.error(this.$t('common.getErrorMsg' + ' ' + error))
})
@@ -512,9 +526,9 @@ export default {
if (!currentNode || assetsSelected.length === 0) {
return
}
let url = `/api/v1/assets/nodes/${currentNode.meta.data.id}/assets/add/`
let url = `/api/v1/assets/nodes/${currentNode.meta.node.id}/assets/add/`
if (this.assetTreeTableDialogSetting.action === 'move') {
url = `/api/v1/assets/nodes/${currentNode.meta.data.id}/assets/replace/`
url = `/api/v1/assets/nodes/${currentNode.meta.node.id}/assets/replace/`
}
this.$axios.put(
url, { assets: assetsSelected }

View File

@@ -33,8 +33,7 @@ export default {
objectsAjax: {
url: '/api/v1/assets/system-users/',
transformOption: (item) => {
const username = item.username || '*'
return { label: item.name + '(' + username + ')', value: item.id }
return { label: item.name + '(' + item.username + ')', value: item.id }
}
// processResults: (data) => {
// let results = data.results

View File

@@ -1,5 +1,12 @@
<template>
<GenericCreateUpdatePage v-bind="$data" />
<GenericCreateUpdatePage
:fields="fields"
:has-detail-in-msg="false"
:initial="initial"
:fields-meta="fieldsMeta"
:url="url"
:get-next-route="getNextRoute"
/>
</template>
<script>
@@ -78,30 +85,23 @@ export default {
params: {
}
},
url: `/api/v1/assets/gateways/`,
hasDetailInMsg: false,
getNextRoute(res, method) {
const domain = res.domain
const route = {
name: 'DomainDetail',
params: {
id: domain
},
query: {
activeTab: 'GatewayList'
}
}
return route
},
cleanFormValue(values) {
if (this.$route.params.id && !values.update_password) {
delete values['password']
}
return values
}
url: `/api/v1/assets/gateways/`
}
},
methods: {
getNextRoute(res, method) {
const domain = res.domain
const route = {
name: 'DomainDetail',
params: {
id: domain
},
query: {
activeTab: 'GatewayList'
}
}
return route
}
}
}
</script>

View File

@@ -1,19 +1,13 @@
<template>
<GenericCreateUpdatePage
:fields="fields"
:initial="initial"
:fields-meta="fieldsMeta"
:url="url"
v-bind="$attrs"
/>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import getFields from '../fields'
import getFields from './fields'
export default {
name: 'CommonUserDatabase',
name: 'SystemUserCreateUpdate',
components: { GenericCreateUpdatePage },
data() {
const fields = getFields.bind(this)()

View File

@@ -1,8 +1,10 @@
import { Required } from '@/components/DataForm/rules'
import i18n from '@/i18n/i18n'
import { Select2, UploadKey } from '@/components'
function getFields() {
const login_mode = {
helpText: i18n.t('assets.LoginModeHelpMessage'),
on: {
input: ([value], updateForm) => {
if (value === 'manual') {
@@ -24,14 +26,12 @@ function getFields() {
},
rules: [Object.assign({}, Required)],
hidden: (form) => {
if (['vnc'].includes(form.protocol)) {
this.fieldsMeta.username.rules[0].required = false
} else if (form.login_mode === 'manual') {
this.fieldsMeta.username.rules[0].required = false
} else if (form.username_same_with_user) {
if (['mysql', 'postgresql', 'mariadb', 'oracle'].includes(form.protocol)) {
this.fieldsMeta.username.rules[0].required = true
} else if (['vnc'].includes(form.protocol)) {
this.fieldsMeta.username.rules[0].required = false
} else {
this.fieldsMeta.username.rules[0].required = true
this.fieldsMeta.username.rules[0].required = !(form.login_mode === 'manual' || form.username_same_with_user)
}
if (form.username_same_with_user) {
this.fieldsMeta.username.el.disabled = true
@@ -54,7 +54,6 @@ function getFields() {
const username_same_with_user = {
type: 'switch',
label: this.$t('assets.DynamicUsername'),
helpText: this.$t('assets.UsernameHelpMessage'),
el: {
disabled: false
@@ -78,10 +77,6 @@ function getFields() {
if (form.protocol === 'k8s') {
return true
}
if (form.type === 'admin') {
form.auto_generate_key = false
return true
}
if (form.login_mode === 'manual') {
this.fieldsMeta.auto_generate_key.el.disabled = true
@@ -119,7 +114,7 @@ function getFields() {
disabled: false
},
hidden: form => {
if (form.login_mode === 'manual' || form.type === 'admin') {
if (form.login_mode === 'manual') {
this.fieldsMeta.auto_push.el.disabled = true
} else {
this.fieldsMeta.auto_push.el.disabled = false
@@ -167,9 +162,6 @@ function getFields() {
helpText: this.$t('assets.GroupsHelpMessage')
}
const type = {
}
return {
login_mode: login_mode,
username: username,
@@ -181,8 +173,7 @@ function getFields() {
auto_push: auto_push,
update_password: update_password,
password: password,
system_groups: system_groups,
type: type
system_groups: system_groups
}
}

View File

@@ -4,7 +4,6 @@
:initial="initial"
:fields-meta="fieldsMeta"
:url="url"
v-bind="$attrs"
/>
</template>
@@ -13,7 +12,7 @@ import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage
import { Required } from '@/components/DataForm/rules'
export default {
name: 'CommonUserSSH',
name: 'SystemUserCreateUpdate',
components: { GenericCreateUpdatePage },
data() {
return {
@@ -21,9 +20,9 @@ export default {
protocol: this.$route.query.protocol
},
fields: [
[this.$t('common.Basic'), ['name', 'protocol']],
[this.$t('common.Basic'), ['name', 'priority', 'protocol']],
[this.$t('common.Auth'), ['token']],
[this.$t('common.Other'), ['priority', 'comment']]
[this.$t('common.Other'), ['comment']]
],
fieldsMeta: {
token: {

View File

@@ -1,19 +1,13 @@
<template>
<GenericCreateUpdatePage
:fields="fields"
:initial="initial"
:fields-meta="fieldsMeta"
:url="url"
v-bind="$attrs"
/>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import getFields from '../fields'
import getFields from './fields'
export default {
name: 'CommonUserRDP',
name: 'SystemUserCreateUpdate',
components: { GenericCreateUpdatePage },
data() {
const fields = getFields.bind(this)()
@@ -24,14 +18,15 @@ export default {
username_same_with_user: false,
auto_generate_key: false,
auto_push: false,
sftp_root: 'tmp',
sudo: '/bin/whoami',
shell: '/bin/bash'
},
fields: [
[this.$t('common.Basic'), ['name', 'protocol', 'username', 'username_same_with_user']],
[this.$t('common.Auth'), ['login_mode', 'update_password', 'password', 'ad_domain']],
[this.$t('common.Basic'), ['name', 'login_mode', 'username', 'username_same_with_user', 'priority', 'protocol']],
[this.$t('assets.AutoPush'), ['auto_push', 'system_groups']],
[this.$t('common.Other'), ['priority', 'comment']]
[this.$t('common.Auth'), ['update_password', 'password', 'ad_domain']],
[this.$t('common.Other'), ['comment']]
],
fieldsMeta: {
login_mode: fields.login_mode,

View File

@@ -1,20 +1,14 @@
<template>
<GenericCreateUpdatePage
:fields="fields"
:initial="initial"
:fields-meta="fieldsMeta"
:url="url"
v-bind="$attrs"
/>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import { Required } from '@/components/DataForm/rules'
import getFields from '../fields'
import getFields from './fields'
export default {
name: 'CommonUserSSH',
name: 'SystemUserCreateUpdate',
components: { GenericCreateUpdatePage },
data() {
const fields = getFields.bind(this)()
@@ -30,11 +24,11 @@ export default {
shell: '/bin/bash'
},
fields: [
[this.$t('common.Basic'), ['name', 'protocol', 'username', 'username_same_with_user']],
[this.$t('common.Auth'), ['login_mode', 'auto_generate_key', 'update_password', 'password', 'private_key']],
[this.$t('common.Basic'), ['name', 'login_mode', 'username', 'username_same_with_user', 'priority', 'protocol']],
[this.$t('assets.AutoPush'), ['auto_push', 'sudo', 'shell', 'home', 'system_groups']],
[this.$t('common.Auth'), ['auto_generate_key', 'update_password', 'password', 'private_key']],
[this.$t('common.Command filter'), ['cmd_filters']],
[this.$t('common.Other'), ['priority', 'sftp_root', 'comment']]
[this.$t('common.Other'), ['sftp_root', 'comment']]
],
fieldsMeta: {
login_mode: fields.login_mode,

View File

@@ -1,19 +1,15 @@
<template>
<GenericCreateUpdatePage
:fields="fields"
:initial="initial"
:fields-meta="fieldsMeta"
:url="url"
v-bind="$attrs"
/>
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
</template>
<script>
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
import getFields from '../fields'
import getFields from '@/views/assets/SystemUser/SystemUserCreate/fields'
// const asciiProtocols = ['ssh', 'telnet', 'mysql']
export default {
name: 'CommonUserVNC',
name: 'SystemUserCreateUpdate',
components: { GenericCreateUpdatePage },
data() {
const fields = getFields.bind(this)()
@@ -24,9 +20,9 @@ export default {
username_same_with_user: false
},
fields: [
[this.$t('common.Basic'), ['name', 'protocol', 'username', 'username_same_with_user']],
[this.$t('common.Auth'), ['login_mode', 'update_password', 'password']],
[this.$t('common.Other'), ['priority', 'comment']]
[this.$t('common.Basic'), ['name', 'login_mode', 'username', 'username_same_with_user', 'priority', 'protocol']],
[this.$t('common.Auth'), ['update_password', 'password']],
[this.$t('common.Other'), ['comment']]
],
fieldsMeta: {
login_mode: fields.login_mode,

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