mirror of
https://github.com/jumpserver/lina.git
synced 2026-01-14 03:46:26 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9736d94762 | ||
|
|
d8566d2f9e | ||
|
|
d74da503c8 | ||
|
|
e1d8e4aea6 | ||
|
|
d21559599f | ||
|
|
f8cadb545f | ||
|
|
2bc4b53159 | ||
|
|
7f28cc0aad | ||
|
|
7e95e38d24 | ||
|
|
57bafc01e3 | ||
|
|
834033f2fd | ||
|
|
9a41ccbdd7 | ||
|
|
3793370c9c | ||
|
|
4486dc55a7 | ||
|
|
bdb63b865a | ||
|
|
2d17b48b86 | ||
|
|
67091d5a22 | ||
|
|
798c4ca64e | ||
|
|
eddd27e95d | ||
|
|
12ffa363c1 | ||
|
|
cd79246f0d | ||
|
|
94583e2156 | ||
|
|
ca602a8052 | ||
|
|
5bdc4e4e3a | ||
|
|
da1b73d3fd | ||
|
|
4bc4012520 | ||
|
|
b8f1cb7a8e | ||
|
|
ee6a3c6d68 | ||
|
|
be176ad408 | ||
|
|
73c17fccbe | ||
|
|
30c1284a41 | ||
|
|
191900381a | ||
|
|
91e04a8d18 | ||
|
|
1b223f0486 | ||
|
|
22eb78339e | ||
|
|
5831cb326c | ||
|
|
52a4c1824f | ||
|
|
c155e5a59b | ||
|
|
ff90e56763 | ||
|
|
14000317b9 |
@@ -1,5 +1,8 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
]
|
||||
}
|
||||
|
||||
10
nginx.conf
10
nginx.conf
@@ -1,6 +1,16 @@
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
gzip on;
|
||||
gzip_min_length 1k;
|
||||
gzip_buffers 4 16k;
|
||||
#gzip_http_version 1.0;
|
||||
gzip_comp_level 8;
|
||||
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
|
||||
gzip_vary off;
|
||||
gzip_static on;
|
||||
gzip_disable "MSIE [1-6].";
|
||||
|
||||
location /ui/ {
|
||||
try_files $uri / /ui/index.html;
|
||||
alias /opt/lina/;
|
||||
|
||||
@@ -18,13 +18,15 @@
|
||||
"vue-i18n-report": "vue-i18n-extract report -v './src/**/*.?(js|vue)' -l './src/i18n/langs/**/*.json'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
|
||||
"@ztree/ztree_v3": "3.5.44",
|
||||
"axios": "0.18.1",
|
||||
"axios": "0.21.1",
|
||||
"axios-retry": "^3.1.9",
|
||||
"deepmerge": "^4.2.2",
|
||||
"echarts": "^4.7.0",
|
||||
"element-ui": "2.13.2",
|
||||
"eslint-plugin-html": "^6.0.0",
|
||||
"install": "^0.13.0",
|
||||
"jquery": "^3.5.0",
|
||||
"js-cookie": "2.2.0",
|
||||
"less": "^3.10.3",
|
||||
@@ -43,6 +45,7 @@
|
||||
"lodash.values": "^4.3.0",
|
||||
"moment-parseformat": "^3.0.0",
|
||||
"normalize.css": "7.0.0",
|
||||
"npm": "^7.8.0",
|
||||
"nprogress": "0.2.0",
|
||||
"path-to-regexp": "2.4.0",
|
||||
"vue": "2.6.10",
|
||||
@@ -73,6 +76,7 @@
|
||||
"babel-eslint": "10.0.1",
|
||||
"babel-jest": "23.6.0",
|
||||
"chalk": "2.4.2",
|
||||
"compression-webpack-plugin": "^6.1.1",
|
||||
"connect": "3.6.6",
|
||||
"element-theme-chalk": "^2.13.1",
|
||||
"eslint": "^5.15.3",
|
||||
|
||||
@@ -1,51 +1,52 @@
|
||||
<template><div>
|
||||
<template>
|
||||
<div>
|
||||
<ListTable ref="ListTable" :table-config="tableConfig" :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">
|
||||
<div>
|
||||
<ListTable ref="ListTable" :table-config="tableConfig" :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="MFAInfo.hostname" disabled />
|
||||
<el-input v-model="dialogInfo.hostname" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="this.$t('assets.Username')">
|
||||
<el-input v-model="MFAInfo.username" disabled />
|
||||
<el-input v-model="dialogInfo.username" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="this.$t('assets.Password')">
|
||||
<el-input v-model="MFAInfo.password" type="password" show-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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -111,7 +112,7 @@ export default {
|
||||
username: '',
|
||||
hostname: '',
|
||||
password: '',
|
||||
key: ''
|
||||
private_key: ''
|
||||
},
|
||||
tableConfig: {
|
||||
url: this.url,
|
||||
@@ -316,7 +317,7 @@ export default {
|
||||
username: '',
|
||||
hostname: '',
|
||||
password: '',
|
||||
key: ''
|
||||
private_key: ''
|
||||
}
|
||||
this.showDialog = false
|
||||
this.$refs.ListTable.reloadTable()
|
||||
@@ -326,7 +327,7 @@ export default {
|
||||
// TODO 校验文件类型
|
||||
const reader = new FileReader()
|
||||
reader.onload = function() {
|
||||
vm.dialogInfo.key = this.result
|
||||
vm.dialogInfo.private_key = this.result
|
||||
}
|
||||
reader.readAsText(
|
||||
e.target.files[0]
|
||||
@@ -340,8 +341,8 @@ export default {
|
||||
if (this.dialogInfo.password !== '') {
|
||||
data.password = this.dialogInfo.password
|
||||
}
|
||||
if (this.dialogInfo.key !== '') {
|
||||
data.key = this.dialogInfo.key
|
||||
if (this.dialogInfo.private_key !== '') {
|
||||
data.private_key = this.dialogInfo.private_key
|
||||
}
|
||||
this.$axios.post(
|
||||
`/api/v1/assets/asset-users/`,
|
||||
@@ -356,7 +357,7 @@ export default {
|
||||
username: '',
|
||||
hostname: '',
|
||||
password: '',
|
||||
key: ''
|
||||
private_key: ''
|
||||
}
|
||||
this.showDialog = false
|
||||
this.$refs.ListTable.reloadTable()
|
||||
|
||||
@@ -2,6 +2,7 @@ import Vue from 'vue'
|
||||
import Select2 from '@/components/Select2'
|
||||
import NestedField from '@/components/AutoDataForm/components/NestedField'
|
||||
import rules from '@/components/DataForm/rules'
|
||||
import { assignIfNot } from '@/utils/common'
|
||||
|
||||
export class FormFieldGenerator {
|
||||
constructor() {
|
||||
@@ -109,19 +110,21 @@ export class FormFieldGenerator {
|
||||
return field
|
||||
}
|
||||
generateField(name, fieldsMeta, remoteFieldsMeta) {
|
||||
let field = { id: name, prop: name, el: {}, attrs: {}}
|
||||
let field = { id: name, prop: name, el: {}, attrs: {}, rules: [] }
|
||||
const remoteFieldMeta = remoteFieldsMeta[name] || {}
|
||||
Vue.$log.debug('FieldsMeta: ', fieldsMeta, name)
|
||||
const fieldMeta = fieldsMeta[name] || {}
|
||||
Vue.$log.debug('FieldMeta is: ', fieldMeta)
|
||||
field.label = remoteFieldMeta.label
|
||||
field.helpText = remoteFieldMeta.help_text
|
||||
field = this.generateFieldByType(remoteFieldMeta.type, field, fieldMeta, remoteFieldMeta)
|
||||
field = this.generateFieldByName(name, field)
|
||||
field = this.generateFieldByOther(field, fieldMeta, remoteFieldMeta)
|
||||
const el = Object.assign(field.el || {}, fieldMeta.el || {})
|
||||
field = Object.assign(field, fieldMeta || {}, { el: el })
|
||||
const el = assignIfNot(fieldMeta.el || {}, field.el)
|
||||
const rules = fieldMeta.rules || field.rules
|
||||
field = Object.assign(field, fieldMeta)
|
||||
field.el = el
|
||||
field.rules = rules
|
||||
_.set(field, 'attrs.error', '')
|
||||
Vue.$log.debug('Generate field: ', name, field)
|
||||
return field
|
||||
}
|
||||
generateFieldGroup(field, fieldsMeta, remoteFieldsMeta) {
|
||||
|
||||
@@ -78,6 +78,10 @@ export default {
|
||||
$('body').unbind('mousedown')
|
||||
},
|
||||
methods: {
|
||||
refreshTree: function() {
|
||||
const refreshIconRef = $('#tree-refresh')
|
||||
refreshIconRef.click()
|
||||
},
|
||||
editTreeNode: function() {
|
||||
this.hideRMenu()
|
||||
const currentNode = this.zTree.getSelectedNodes()[0]
|
||||
@@ -100,15 +104,19 @@ export default {
|
||||
if (this.setting.url.indexOf('?') !== -1) {
|
||||
combinator = '&'
|
||||
}
|
||||
let url = ''
|
||||
const query = Object.assign({}, this.$route.query)
|
||||
if (treeNode.meta.type === 'node') {
|
||||
this.currentNode = treeNode
|
||||
this.currentNodeId = treeNode.meta.node.id
|
||||
this.$route.query['node'] = this.currentNodeId
|
||||
this.$emit('urlChange', `${this.setting.url}${combinator}node_id=${treeNode.meta.node.id}&show_current_asset=${show_current_asset}`)
|
||||
query['node'] = this.currentNodeId
|
||||
url = `${this.setting.url}${combinator}node_id=${treeNode.meta.node.id}&show_current_asset=${show_current_asset}`
|
||||
} else if (treeNode.meta.type === 'asset') {
|
||||
this.$route.query['asset'] = treeNode.meta.asset.id
|
||||
this.$emit('urlChange', `${this.setting.url}${combinator}asset_id=${treeNode.meta.asset.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)
|
||||
},
|
||||
removeTreeNode: function() {
|
||||
this.hideRMenu()
|
||||
@@ -121,6 +129,7 @@ export default {
|
||||
).then(() => {
|
||||
this.$message.success(this.$t('common.deleteSuccessMsg'))
|
||||
this.zTree.removeNode(currentNode)
|
||||
this.refreshTree()
|
||||
}).catch(() => {
|
||||
// this.$message.error(this.$t('common.deleteErrorMsg') + ' ' + error)
|
||||
})
|
||||
@@ -141,7 +150,7 @@ export default {
|
||||
treeNode.name = treeNode.name + ' (' + assetsAmount + ')'
|
||||
this.zTree.updateNode(treeNode)
|
||||
this.$message.success(this.$t('common.updateSuccessMsg'))
|
||||
})
|
||||
}).finally(() => { this.refreshTree() })
|
||||
},
|
||||
onBodyMouseDown: function(event) {
|
||||
const rMenuID = this.$refs.dataztree.$refs.ztree.iRMenuID
|
||||
@@ -210,7 +219,7 @@ export default {
|
||||
this.$message.success(this.$t('common.updateSuccessMsg'))
|
||||
}).catch(error => {
|
||||
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
|
||||
})
|
||||
}).finally(() => this.refreshTree())
|
||||
},
|
||||
createTreeNode: function() {
|
||||
this.hideRMenu()
|
||||
@@ -242,7 +251,6 @@ export default {
|
||||
})
|
||||
},
|
||||
refresh: function() {
|
||||
|
||||
},
|
||||
getSelectedNodes: function() {
|
||||
return this.zTree.getSelectedNodes()
|
||||
|
||||
@@ -8,7 +8,14 @@ export const RequiredChange = {
|
||||
required: true, message: i18n.t('common.fieldRequiredError'), trigger: 'change'
|
||||
}
|
||||
|
||||
export const EmailCheck = {
|
||||
type: 'email',
|
||||
message: i18n.t('common.InputEmailAddress'),
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
|
||||
export default {
|
||||
Required,
|
||||
RequiredChange
|
||||
RequiredChange,
|
||||
EmailCheck
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ import getLocatedSlotKeys from './utils/extract-keys'
|
||||
import transformSearchImmediatelyItem from './utils/search-immediately-item'
|
||||
import isFalsey from './utils/is-falsey'
|
||||
import merge from 'deepmerge'
|
||||
const defaultFirstPage = 0
|
||||
const defaultFirstPage = 1
|
||||
const noPaginationDataPath = 'payload'
|
||||
|
||||
export default {
|
||||
@@ -723,6 +723,10 @@ export default {
|
||||
default(row, index) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
totalData: {
|
||||
type: Array,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -810,6 +814,13 @@ export default {
|
||||
},
|
||||
_searchForm() {
|
||||
return transformSearchImmediatelyItem(this.collapseForm, this)
|
||||
},
|
||||
lastPageNum() {
|
||||
// page
|
||||
const pageOffset = this.firstPage - defaultFirstPage
|
||||
const pageCount = Math.ceil(this.total / this.size)
|
||||
const lastPageNum = pageCount + pageOffset
|
||||
return lastPageNum
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -828,6 +839,13 @@ export default {
|
||||
* @property {array} rows - 已选中的行数据的数组
|
||||
*/
|
||||
this.$emit('selection-change', val)
|
||||
},
|
||||
totalData(val) {
|
||||
if (val) {
|
||||
this.page = defaultFirstPage
|
||||
this.total = val.length
|
||||
this.getList()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -877,12 +895,54 @@ export default {
|
||||
}
|
||||
return query
|
||||
},
|
||||
getPageData() {
|
||||
return this.data
|
||||
},
|
||||
async gotoNextPage() {
|
||||
if (!this.hasNextPage()) {
|
||||
return false
|
||||
}
|
||||
this.page += 1
|
||||
await this.getList({ loading: true })
|
||||
},
|
||||
hasNextPage() {
|
||||
return this.page < this.lastPageNum
|
||||
},
|
||||
getList({ loading = true } = {}) {
|
||||
const { url } = this
|
||||
if (url) {
|
||||
return this.getListFromRemote({ loading: loading })
|
||||
}
|
||||
if (this.totalData) {
|
||||
return this.getListFromStaticData({ loading: true })
|
||||
}
|
||||
// this.$log.debug("last page is: ", this.lastPageNum)
|
||||
},
|
||||
getListFromStaticData({ loading = true } = {}) {
|
||||
if (loading) {
|
||||
this.loading = true
|
||||
}
|
||||
if (!this.hasPagination) {
|
||||
this.data = this.totalData
|
||||
this.loading = false
|
||||
return this.data
|
||||
}
|
||||
// page
|
||||
const pageOffset = this.firstPage - defaultFirstPage
|
||||
const page = this.page === 0 ? 1 : this.page
|
||||
const start = (page + pageOffset - 1) * this.size
|
||||
const end = (page + pageOffset) * this.size
|
||||
this.$log.debug(`page: ${page}, size: ${this.size}, start: ${start}, end: ${end}`)
|
||||
this.data = this.totalData.slice(start, end)
|
||||
this.loading = false
|
||||
return this.data
|
||||
},
|
||||
/**
|
||||
* 手动刷新列表数据,选项的默认值为: { loading: true }
|
||||
* @public
|
||||
* @param {object} options 方法选项
|
||||
*/
|
||||
getList({ loading = true } = {}) {
|
||||
getListFromRemote({ loading = true } = {}) {
|
||||
const { url } = this
|
||||
if (!url) {
|
||||
return
|
||||
|
||||
@@ -100,6 +100,9 @@ export default {
|
||||
iListeners() {
|
||||
return Object.assign({}, this.$listeners, this.tableConfig.listeners)
|
||||
},
|
||||
dataTable() {
|
||||
return this.$refs.table
|
||||
},
|
||||
...mapGetters({
|
||||
'globalTableConfig': 'tableConfig'
|
||||
})
|
||||
|
||||
@@ -55,7 +55,6 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -74,4 +73,8 @@ export default {
|
||||
/*padding-top: 10px;*/
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
padding-right: 50px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,64 +1,74 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:title="$t('common.Import')"
|
||||
:title="importTitle"
|
||||
:visible.sync="showImportDialog"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:loading-status="loadStatus"
|
||||
@confirm="handleImportConfirm"
|
||||
@cancel="handleImportCancel()"
|
||||
width="80%"
|
||||
class="importDialog"
|
||||
:confirm-title="confirmTitle"
|
||||
:show-cancel="false"
|
||||
:show-confirm="false"
|
||||
@close="handleImportCancel"
|
||||
>
|
||||
<el-form label-position="left" style="padding-left: 50px">
|
||||
<el-form-item :label="$t('common.fileType' )" :label-width="'100px'">
|
||||
<el-radio-group v-model="importTypeOption">
|
||||
<el-radio v-for="option of importTypeOptions" :key="option.value" class="export-item" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form v-if="!showTable" label-position="left" style="padding-left: 50px">
|
||||
<el-form-item :label="$t('common.Import' )" :label-width="'100px'">
|
||||
<el-radio v-model="importOption" class="export-item" label="1">{{ this.$t('common.Create') }}</el-radio>
|
||||
<el-radio v-model="importOption" class="export-item" label="2">{{ this.$t('common.Update') }}</el-radio>
|
||||
<el-radio v-model="importOption" class="export-item" label="create">{{ this.$t('common.Create') }}</el-radio>
|
||||
<el-radio v-model="importOption" class="export-item" label="update">{{ this.$t('common.Update') }}</el-radio>
|
||||
<div style="line-height: 1.5">
|
||||
<span v-if="importOption==='1'" class="el-upload__tip">
|
||||
{{ this.$t('common.imExport.downloadImportTemplateMsg') }}
|
||||
<el-link type="success" :underline="false" :href="downloadImportTempUrl">{{ this.$t('common.Download') }}</el-link>
|
||||
</span>
|
||||
<span v-else class="el-upload__tip">
|
||||
{{ this.$t('common.imExport.downloadUpdateTemplateMsg') }}
|
||||
<el-link type="success" :underline="false" @click="downloadUpdateTempUrl">{{ this.$t('common.Download') }}</el-link>
|
||||
<span class="el-upload__tip">
|
||||
{{ downloadTemplateTitle }}
|
||||
<el-link type="success" :underline="false" style="padding-left: 10px" @click="downloadTemplateFile('csv')"> CSV </el-link>
|
||||
<el-link type="success" :underline="false" style="padding-left: 10px" @click="downloadTemplateFile('xlsx')"> XLSX </el-link>
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('common.Upload' )" :label-width="'100px'">
|
||||
<el-form-item :label="$t('common.Upload' )" :label-width="'100px'" class="file-uploader">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
drag
|
||||
action="string"
|
||||
list-type="text/csv"
|
||||
:http-request="handleImport"
|
||||
:limit="1"
|
||||
:auto-upload="false"
|
||||
:on-change="onFileChange"
|
||||
:before-upload="beforeUpload"
|
||||
accept=".csv,.xlsx"
|
||||
>
|
||||
<el-button size="mini" type="default">{{ this.$t('common.SelectFile') }}</el-button>
|
||||
<!-- <div slot="tip" :class="uploadHelpTextClass" style="line-height: 1.5">{{ this.$t('common.imExport.onlyCSVFilesTips') }}</div>-->
|
||||
<i class="el-icon-upload" />
|
||||
<div class="el-upload__text">{{ $t('common.imExport.dragUploadFileInfo') }}</div>
|
||||
<div slot="tip" class="el-upload__tip">
|
||||
<span :class="{'hasError': hasFileFormatOrSizeError }">{{ $t('common.imExport.uploadCsvLth10MHelpText') }}</span>
|
||||
<div v-if="renderError" class="hasError">{{ renderError }}</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div v-if="errorMsg" class="error-msg error-results">
|
||||
<ul v-if="typeof errorMsg === 'object'">
|
||||
<li v-for="(item, index) in errorMsg" :key="item + '-' + index"> {{ item }}</li>
|
||||
</ul>
|
||||
<span v-else>{{ errorMsg }}</span>
|
||||
<div v-else class="importTableZone">
|
||||
<ImportTable
|
||||
ref="importTable"
|
||||
:json-data="jsonData"
|
||||
:import-option="importOption"
|
||||
:url="url"
|
||||
@cancel="cancelUpload"
|
||||
@finish="closeDialog"
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
import ImportTable from '@/components/ListTable/TableAction/ImportTable'
|
||||
import { getErrorResponseMsg } from '@/utils/common'
|
||||
import { createSourceIdCache } from '@/api/common'
|
||||
|
||||
export default {
|
||||
name: 'ImportDialog',
|
||||
components: {
|
||||
Dialog
|
||||
Dialog,
|
||||
ImportTable
|
||||
},
|
||||
props: {
|
||||
selectedRows: {
|
||||
@@ -73,45 +83,49 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
showImportDialog: false,
|
||||
importOption: '1',
|
||||
isCsv: true,
|
||||
importOption: 'create',
|
||||
errorMsg: '',
|
||||
loadStatus: false,
|
||||
importTypeOption: 'csv'
|
||||
importTypeOption: 'csv',
|
||||
importTypeIsCsv: true,
|
||||
showTable: false,
|
||||
renderError: '',
|
||||
hasFileFormatOrSizeError: false,
|
||||
jsonData: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasSelected() {
|
||||
return this.selectedRows.length > 0
|
||||
},
|
||||
importTypeOptions() {
|
||||
return [
|
||||
{
|
||||
label: 'CSV',
|
||||
value: 'csv',
|
||||
can: true
|
||||
},
|
||||
{
|
||||
label: 'Excel',
|
||||
value: 'xlsx',
|
||||
can: true
|
||||
}
|
||||
]
|
||||
},
|
||||
upLoadUrl() {
|
||||
return this.url
|
||||
},
|
||||
downloadImportTempUrl() {
|
||||
const format = this.importTypeOption === 'csv' ? 'format=csv&template=import&limit=1' : 'format=xlsx&template=import&limit=1'
|
||||
const url = (this.url.indexOf('?') === -1) ? `${this.url}?${format}` : `${this.url}&${format}`
|
||||
return url
|
||||
},
|
||||
uploadHelpTextClass() {
|
||||
const cls = ['el-upload__tip']
|
||||
if (!this.isCsv) {
|
||||
cls.push('error-msg')
|
||||
}
|
||||
return cls
|
||||
},
|
||||
downloadTemplateTitle() {
|
||||
if (this.importOption === 'create') {
|
||||
return this.$t('common.imExport.downloadImportTemplateMsg')
|
||||
} else {
|
||||
return this.$t('common.imExport.downloadUpdateTemplateMsg')
|
||||
}
|
||||
},
|
||||
importTitle() {
|
||||
if (this.importOption === 'create') {
|
||||
return this.$t('common.Import') + this.$t('common.Create')
|
||||
} else {
|
||||
return this.$t('common.Import') + this.$t('common.Update')
|
||||
}
|
||||
},
|
||||
confirmTitle() {
|
||||
return '导入'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
importOption(val) {
|
||||
this.showTable = false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -120,56 +134,69 @@ export default {
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
performUpdate(item) {
|
||||
this.$axios.put(
|
||||
this.upLoadUrl,
|
||||
item.file,
|
||||
{ headers: { 'Content-Type': this.importTypeOption === 'csv' ? 'text/csv' : 'text/xlsx' }, disableFlashErrorMsg: true }
|
||||
).then((data) => {
|
||||
const msg = this.$t('common.imExport.updateSuccessMsg', { count: data.length })
|
||||
this.onSuccess(msg)
|
||||
closeDialog() {
|
||||
this.showImportDialog = false
|
||||
},
|
||||
cancelUpload() {
|
||||
this.showTable = false
|
||||
this.renderError = ''
|
||||
this.jsonData = {}
|
||||
},
|
||||
onFileChange(file, fileList) {
|
||||
fileList.splice(0, fileList.length)
|
||||
if (file.status !== 'ready') {
|
||||
return
|
||||
}
|
||||
// const isCsv = file.raw.type = 'text/csv'
|
||||
if (!this.beforeUpload(file)) {
|
||||
return
|
||||
}
|
||||
const isCsv = file.name.indexOf('csv') > -1
|
||||
const url = new URL(this.url, 'http://localhost')
|
||||
url.pathname += 'render-to-json/'
|
||||
const renderToJsonUrl = url.toString().replace('http://localhost', '')
|
||||
this.$axios.post(
|
||||
renderToJsonUrl,
|
||||
file.raw,
|
||||
{ headers: { 'Content-Type': isCsv ? 'text/csv' : 'text/xlsx' }, disableFlashErrorMsg: true }
|
||||
).then(data => {
|
||||
this.jsonData = data
|
||||
this.showTable = true
|
||||
}).catch(error => {
|
||||
this.catchError(error)
|
||||
fileList.splice(0, fileList.length)
|
||||
this.renderError = getErrorResponseMsg(error)
|
||||
}).finally(() => {
|
||||
this.loadStatus = false
|
||||
})
|
||||
},
|
||||
performCreate(item) {
|
||||
this.$axios.post(
|
||||
this.upLoadUrl,
|
||||
item.file,
|
||||
{ headers: { 'Content-Type': this.importTypeOption === 'csv' ? 'text/csv' : 'text/xlsx' }, disableFlashErrorMsg: true }
|
||||
).then((data) => {
|
||||
const msg = this.$t('common.imExport.createSuccessMsg', { count: data.length })
|
||||
this.onSuccess(msg)
|
||||
}).catch(error => {
|
||||
this.catchError(error)
|
||||
}).finally(() => {
|
||||
this.loadStatus = false
|
||||
})
|
||||
beforeUpload(file) {
|
||||
const isLt30M = file.size / 1024 / 1024 < 30
|
||||
if (!isLt30M) {
|
||||
this.hasFileFormatOrSizeError = true
|
||||
}
|
||||
return isLt30M
|
||||
},
|
||||
async downloadTemplateFile(tp) {
|
||||
const downloadUrl = await this.getDownloadTemplateUrl(tp)
|
||||
window.open(downloadUrl)
|
||||
},
|
||||
async getDownloadTemplateUrl(tp) {
|
||||
const template = this.importOption === 'create' ? 'import' : 'update'
|
||||
let query = `format=${tp}&template=${template}`
|
||||
if (this.importOption === 'update' && this.selectedRows.length > 0) {
|
||||
const resources = []
|
||||
for (const item of this.selectedRows) {
|
||||
resources.push(item.id)
|
||||
}
|
||||
const resp = await createSourceIdCache(resources)
|
||||
query += `&spm=${resp.spm}`
|
||||
} else {
|
||||
query += '&limit=1'
|
||||
}
|
||||
return this.url.indexOf('?') === -1 ? `${this.url}?${query}` : `${this.url}&${query}`
|
||||
},
|
||||
catchError(error) {
|
||||
this.$refs.upload.clearFiles()
|
||||
if (error.response && error.response.status === 400) {
|
||||
const errorData = error.response.data
|
||||
const totalErrorMsg = []
|
||||
errorData.forEach((value, index) => {
|
||||
if (typeof value === 'string') {
|
||||
totalErrorMsg.push(`line ${index}. ${value}`)
|
||||
} else {
|
||||
const errorMsg = [`line ${index}. `]
|
||||
for (const [k, v] of Object.entries(value)) {
|
||||
if (v) {
|
||||
errorMsg.push(`${k}: ${v}`)
|
||||
}
|
||||
}
|
||||
if (errorMsg.length > 1) {
|
||||
totalErrorMsg.push(errorMsg.join(' '))
|
||||
}
|
||||
}
|
||||
})
|
||||
this.errorMsg = totalErrorMsg
|
||||
}
|
||||
console.log(error)
|
||||
},
|
||||
onSuccess(msg) {
|
||||
this.errorMsg = ''
|
||||
@@ -181,40 +208,14 @@ export default {
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
},
|
||||
handleImport(item) {
|
||||
this.loadStatus = true
|
||||
if (this.importOption === '1') {
|
||||
this.performCreate(item)
|
||||
} else {
|
||||
this.performUpdate(item)
|
||||
}
|
||||
},
|
||||
async downloadUpdateTempUrl() {
|
||||
var resources = []
|
||||
const data = this.selectedRows
|
||||
for (let index = 0; index < data.length; index++) {
|
||||
resources.push(data[index].id)
|
||||
}
|
||||
const spm = await createSourceIdCache(resources)
|
||||
const baseUrl = (process.env.VUE_APP_ENV === 'production') ? (`${this.url}`) : (`${process.env.VUE_APP_BASE_API}${this.url}`)
|
||||
const format = this.importTypeOption === 'csv' ? '?format=csv&template=update&spm=' : '?format=xlsx&template=update&spm='
|
||||
const url = `${baseUrl}${format}` + spm.spm
|
||||
return this.downloadCsv(url)
|
||||
},
|
||||
async handleImportConfirm() {
|
||||
this.$refs.upload.submit()
|
||||
this.$refs['importTable'].performUpload()
|
||||
},
|
||||
handleImportCancel() {
|
||||
this.showImportDialog = false
|
||||
},
|
||||
beforeUpload(file) {
|
||||
this.isCsv = this.importTypeOption === 'csv' ? _.endsWith(file.name, 'csv') : _.endsWith(file.name, 'xlsx')
|
||||
if (!this.isCsv) {
|
||||
this.$message.error(
|
||||
this.$t('common.NeedSpecifiedFile')
|
||||
)
|
||||
}
|
||||
return this.isCsv
|
||||
this.showTable = false
|
||||
this.renderError = ''
|
||||
this.jsonData = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,4 +232,49 @@ export default {
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
.importDialog >>> .el-form-item.file-uploader {
|
||||
padding-right: 150px;
|
||||
}
|
||||
|
||||
.file-uploader >>> .el-upload {
|
||||
width: 100%;
|
||||
//padding-right: 150px;
|
||||
}
|
||||
|
||||
.file-uploader >>> .el-upload-dragger {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.importTableZone {
|
||||
padding: 0 20px;
|
||||
|
||||
.importTable {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.tableFilter {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.importTable >>> .el-dialog__body {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.export-item {
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
.export-item:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.hasError {
|
||||
color: $--color-danger;
|
||||
}
|
||||
|
||||
.el-upload__tip {
|
||||
line-height: 1.5;
|
||||
padding-top: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
426
src/components/ListTable/TableAction/ImportTable.vue
Normal file
426
src/components/ListTable/TableAction/ImportTable.vue
Normal file
@@ -0,0 +1,426 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<div class="tableFilter">
|
||||
<el-radio-group v-model="importStatusFilter" size="small">
|
||||
<el-radio-button label="all">{{ $t('common.Total') }}</el-radio-button>
|
||||
<el-radio-button label="ok">{{ $t('common.Success') }}</el-radio-button>
|
||||
<el-radio-button label="error">{{ $t('common.Failed') }}</el-radio-button>
|
||||
<el-radio-button label="pending">{{ $t('common.Pending') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8" style="text-align: center">
|
||||
<span class="summary-item summary-total"> {{ $t('common.Total') }}: {{ totalCount }}</span>
|
||||
<span class="summary-item summary-success"> {{ $t('common.Success') }}: {{ successCount }}</span>
|
||||
<span class="summary-item summary-failed"> {{ $t('common.Failed') }}: {{ failedCount }}</span>
|
||||
<span class="summary-item summary-pending"> {{ $t('common.Pending') }}: {{ pendingCount }}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="row">
|
||||
<el-progress :percentage="processedPercent" />
|
||||
</div>
|
||||
<DataTable v-if="tableGenDone" id="importTable" ref="dataTable" :config="tableConfig" class="importTable" />
|
||||
<div class="row" style="padding-top: 20px">
|
||||
<div style="float: right">
|
||||
<el-button size="small" @click="performCancel">{{ $t('common.Cancel') }}</el-button>
|
||||
<el-button size="small" type="primary" @click="performImportAction">{{ importActionTitle }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DataTable from '@/components/DataTable'
|
||||
import { sleep } from '@/utils/common'
|
||||
import { EditableInputFormatter, StatusFormatter } from '@/components/ListTable/formatters'
|
||||
export default {
|
||||
name: 'ImportTable',
|
||||
components: {
|
||||
DataTable
|
||||
},
|
||||
props: {
|
||||
jsonData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
importOption: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columns: [],
|
||||
importStatusFilter: 'all',
|
||||
iTotalData: [],
|
||||
tableConfig: {
|
||||
hasSelection: false,
|
||||
// hasPagination: false,
|
||||
columns: [],
|
||||
totalData: [],
|
||||
paginationSize: 10,
|
||||
paginationSizes: [10],
|
||||
tableAttrs: {
|
||||
stripe: true, // 斑马纹表格
|
||||
border: true, // 表格边框
|
||||
fit: true, // 宽度自适应,
|
||||
tooltipEffect: 'dark'
|
||||
}
|
||||
},
|
||||
tableGenDone: false,
|
||||
importTaskStatus: 'pending', // pending, started, stopped, done
|
||||
importTaskResult: '', // success, hasError
|
||||
hasImport: false,
|
||||
hasContinueButton: false,
|
||||
importActions: {
|
||||
import: this.$t('common.Import'),
|
||||
continue: this.$t('common.Continue'),
|
||||
stop: this.$t('common.Stop'),
|
||||
finished: this.$t('common.Finished')
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tableColumnNameMapper() {
|
||||
const mapper = {}
|
||||
for (const column of this.tableConfig.columns) {
|
||||
mapper[column['prop']] = column['label']
|
||||
}
|
||||
return mapper
|
||||
},
|
||||
importAction() {
|
||||
switch (this.importTaskStatus) {
|
||||
case 'pending':
|
||||
return 'import'
|
||||
case 'started':
|
||||
return 'stop'
|
||||
}
|
||||
if (this.totalCount === this.successCount) {
|
||||
return 'finished'
|
||||
} else {
|
||||
return 'continue'
|
||||
}
|
||||
},
|
||||
importActionTitle() {
|
||||
return this.importActions[this.importAction]
|
||||
},
|
||||
successData() {
|
||||
return this.iTotalData.filter((item) => {
|
||||
return item['@status'] === 'ok'
|
||||
})
|
||||
},
|
||||
failedData() {
|
||||
return this.iTotalData.filter((item) => {
|
||||
return typeof item['@status'] === 'object' && item['@status'].name === 'error'
|
||||
})
|
||||
},
|
||||
pendingData() {
|
||||
return this.iTotalData.filter((item) => {
|
||||
return item['@status'] === 'pending'
|
||||
})
|
||||
},
|
||||
totalCount() {
|
||||
return this.iTotalData.length
|
||||
},
|
||||
successCount() {
|
||||
return this.successData.length
|
||||
},
|
||||
failedCount() {
|
||||
return this.failedData.length
|
||||
},
|
||||
pendingCount() {
|
||||
return this.pendingData.length
|
||||
},
|
||||
processedCount() {
|
||||
return this.totalCount - this.pendingCount
|
||||
},
|
||||
processedPercent() {
|
||||
if (this.totalCount === 0) {
|
||||
return 0
|
||||
}
|
||||
return Math.round(this.processedCount / this.totalCount * 100)
|
||||
},
|
||||
elDataTable() {
|
||||
return this.$refs['dataTable'].dataTable
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
importStatusFilter(val) {
|
||||
if (val === 'all') {
|
||||
this.tableConfig.totalData = this.iTotalData
|
||||
} else if (val === 'error') {
|
||||
this.tableConfig.totalData = this.failedData
|
||||
} else {
|
||||
this.tableConfig.totalData = this.iTotalData.filter((item) => {
|
||||
return item['@status'] === val
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.generateTable()
|
||||
},
|
||||
methods: {
|
||||
generateTableColumns(tableTitles, tableData) {
|
||||
const vm = this
|
||||
const columns = [{
|
||||
prop: '@status',
|
||||
label: vm.$t('common.Status'),
|
||||
width: '80px',
|
||||
align: 'center',
|
||||
formatter: StatusFormatter,
|
||||
formatterArgs: {
|
||||
iconChoices: {
|
||||
ok: 'fa-check text-primary',
|
||||
error: 'fa-times text-danger',
|
||||
pending: 'fa-clock-o'
|
||||
},
|
||||
getChoicesKey(val) {
|
||||
if (val === 'ok' || val === 'pending') {
|
||||
return val
|
||||
}
|
||||
return 'error'
|
||||
},
|
||||
getTip(val) {
|
||||
if (val === 'ok') {
|
||||
return vm.$t('common.Success')
|
||||
} else if (val === 'pending') {
|
||||
return vm.$t('common.Pending')
|
||||
} else if (val && val.name === 'error') {
|
||||
return val.error
|
||||
}
|
||||
return ''
|
||||
},
|
||||
hasTips: true
|
||||
}
|
||||
}]
|
||||
for (const item of tableTitles) {
|
||||
const dataItemLens = tableData.map(d => {
|
||||
const prop = item[1]
|
||||
const itemColData = d[prop]
|
||||
if (!d) {
|
||||
return 0
|
||||
}
|
||||
if (!itemColData || !itemColData.length) {
|
||||
return 0
|
||||
}
|
||||
return itemColData.length
|
||||
})
|
||||
let colMaxWidth = Math.max(...dataItemLens) * 10
|
||||
if (colMaxWidth === 0) {
|
||||
continue
|
||||
}
|
||||
colMaxWidth = Math.min(180, colMaxWidth)
|
||||
colMaxWidth = Math.max(colMaxWidth, 100)
|
||||
columns.push({
|
||||
prop: item[1],
|
||||
label: item[0],
|
||||
minWidth: colMaxWidth + 'px',
|
||||
showOverflowTooltip: true,
|
||||
formatter: EditableInputFormatter,
|
||||
formatterArgs: {
|
||||
onEnter: ({ row, col, oldValue, newValue }) => {
|
||||
const prop = col.prop
|
||||
row['@status'] = 'pending'
|
||||
this.$log.debug(`Set value ${oldValue} => ${newValue}`)
|
||||
this.$set(row, prop, newValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return columns
|
||||
},
|
||||
generateTableData(tableTitles, tableData) {
|
||||
const totalData = []
|
||||
tableData.forEach(item => {
|
||||
this.$set(item, '@status', 'pending')
|
||||
totalData.push(item)
|
||||
})
|
||||
return totalData
|
||||
},
|
||||
generateTable() {
|
||||
const tableTitles = this.jsonData['title']
|
||||
const tableData = this.jsonData['data']
|
||||
const columns = this.generateTableColumns(tableTitles, tableData)
|
||||
const totalData = this.generateTableData(tableTitles, tableData)
|
||||
this.tableConfig.columns = columns
|
||||
this.tableGenDone = true
|
||||
setTimeout(() => {
|
||||
this.iTotalData = totalData
|
||||
this.tableConfig.totalData = totalData
|
||||
}, 200)
|
||||
},
|
||||
beautifyErrorData(errorData) {
|
||||
if (typeof errorData === 'string') {
|
||||
return errorData
|
||||
} else if (Array.isArray(errorData)) {
|
||||
return errorData
|
||||
} else if (typeof errorData !== 'object') {
|
||||
return errorData
|
||||
}
|
||||
const data = []
|
||||
// eslint-disable-next-line prefer-const
|
||||
for (let [key, value] of Object.entries(errorData)) {
|
||||
if (typeof value === 'object') {
|
||||
value = this.beautifyErrorData(value)
|
||||
}
|
||||
let label = this.tableColumnNameMapper[key]
|
||||
if (!label) {
|
||||
label = key
|
||||
}
|
||||
data.push(`${label}: ${value}`)
|
||||
}
|
||||
return data
|
||||
},
|
||||
performCancel() {
|
||||
this.performStop()
|
||||
this.$emit('cancel')
|
||||
},
|
||||
performFinish() {
|
||||
this.performStop()
|
||||
this.$emit('finish')
|
||||
},
|
||||
taskIsStopped() {
|
||||
return this.importTaskStatus === 'stopped'
|
||||
},
|
||||
performImportAction() {
|
||||
switch (this.importAction) {
|
||||
case 'continue':
|
||||
return this.performContinue()
|
||||
case 'import':
|
||||
return this.performUpload()
|
||||
case 'stop':
|
||||
return this.performStop()
|
||||
case 'finished':
|
||||
return this.performFinish()
|
||||
}
|
||||
},
|
||||
performContinue() {
|
||||
if (this.importTaskStatus === 'done') {
|
||||
for (const item of this.failedData) {
|
||||
item['@status'] = 'pending'
|
||||
}
|
||||
this.tableConfig.totalData = this.pendingData
|
||||
}
|
||||
this.importTaskStatus = 'started'
|
||||
setTimeout(() => {
|
||||
this.performUpload()
|
||||
}, 100)
|
||||
},
|
||||
performStop() {
|
||||
this.importTaskStatus = 'stopped'
|
||||
},
|
||||
async performUploadCurrentPageData() {
|
||||
const currentData = this.elDataTable.getPageData()
|
||||
for (const item of currentData) {
|
||||
if (item['@status'] !== 'pending') {
|
||||
continue
|
||||
}
|
||||
if (this.taskIsStopped()) {
|
||||
return
|
||||
}
|
||||
await this.performUploadObject(item)
|
||||
await sleep(100)
|
||||
}
|
||||
},
|
||||
async performUpload() {
|
||||
this.importTaskStatus = 'started'
|
||||
this.importStatusFilter = 'pending'
|
||||
while (!this.taskIsStopped()) {
|
||||
await this.performUploadCurrentPageData()
|
||||
const hasNextPage = this.elDataTable.hasNextPage()
|
||||
if (hasNextPage && !this.taskIsStopped()) {
|
||||
await this.elDataTable.gotoNextPage()
|
||||
await sleep(100)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (this.pendingCount === 0) {
|
||||
this.importTaskStatus = 'done'
|
||||
}
|
||||
if (this.failedCount > 0) {
|
||||
this.$message.error(this.$t('common.imExport.hasImportErrorItemMsg') + '')
|
||||
}
|
||||
},
|
||||
async performUpdateObject(item) {
|
||||
const updateUrl = `${this.url}${item.id}/`
|
||||
return this.$axios.put(
|
||||
updateUrl,
|
||||
item,
|
||||
{ disableFlashErrorMsg: true }
|
||||
)
|
||||
},
|
||||
async performUploadObject(item) {
|
||||
let handler = this.performCreateObject
|
||||
if (this.importOption === 'update') {
|
||||
handler = this.performUpdateObject
|
||||
}
|
||||
try {
|
||||
await handler.bind(this)(item)
|
||||
item['@status'] = 'ok'
|
||||
} catch (error) {
|
||||
const errorData = error?.response?.data
|
||||
const _error = this.beautifyErrorData(errorData)
|
||||
item['@status'] = {
|
||||
name: 'error',
|
||||
error: _error
|
||||
}
|
||||
}
|
||||
},
|
||||
async performCreateObject(item) {
|
||||
return this.$axios.post(
|
||||
this.url,
|
||||
item,
|
||||
{ disableFlashErrorMsg: true }
|
||||
)
|
||||
},
|
||||
keepElementInViewport() {
|
||||
const tableRef = document.getElementById('importTable')
|
||||
const pendingRef = tableRef?.getElementsByClassName('pendingStatus')[0]
|
||||
if (!pendingRef) {
|
||||
return
|
||||
}
|
||||
const parentTdRef = pendingRef.parentElement.parentElement.parentElement.parentElement
|
||||
const rect = parentTdRef.getBoundingClientRect()
|
||||
let windowInnerHeight = window.innerHeight || document.documentElement.clientHeight
|
||||
windowInnerHeight = windowInnerHeight * 0.97 - 150
|
||||
const inViewport = (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= windowInnerHeight
|
||||
)
|
||||
if (!inViewport) {
|
||||
parentTdRef.scrollIntoView({ behavior: 'auto', block: 'start', inline: 'start' })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "~@/styles/element-variables.scss";
|
||||
.summary-item {
|
||||
padding: 0 10px
|
||||
}
|
||||
|
||||
.summary-success {
|
||||
color: $--color-primary;
|
||||
}
|
||||
|
||||
.summary-failed {
|
||||
color: $--color-danger;
|
||||
}
|
||||
|
||||
.importTable >>> .cell {
|
||||
min-height: 20px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div style="width: 100%;min-height: 20px" @click.stop="editCell">
|
||||
<el-input
|
||||
v-if="inEditMode"
|
||||
v-model="value"
|
||||
size="mini"
|
||||
class="editInput"
|
||||
@keyup.enter.native="onInputEnter"
|
||||
@blur="onInputEnter"
|
||||
/>
|
||||
<template v-else>
|
||||
<span>{{ cellValue }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseFormatter from './base'
|
||||
|
||||
export default {
|
||||
name: 'EditableInputFormatter',
|
||||
components: {
|
||||
},
|
||||
extends: BaseFormatter,
|
||||
props: {
|
||||
formatterArgsDefault: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
trigger: 'click',
|
||||
onEnter: ({ row, col, oldValue, newValue }) => {
|
||||
const prop = col.prop
|
||||
this.$log.debug(`Set value ${oldValue} => ${newValue}`)
|
||||
this.$set(row, prop, newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const valueIsString = typeof this.cellValue === 'string'
|
||||
const jsonValue = this.cellValue ? JSON.stringify(this.cellValue) : ''
|
||||
return {
|
||||
inEditMode: false,
|
||||
value: valueIsString ? this.cellValue || '' : jsonValue,
|
||||
valueIsString: valueIsString,
|
||||
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
editCell() {
|
||||
this.inEditMode = true
|
||||
},
|
||||
onInputEnter() {
|
||||
let validValue = this.value
|
||||
try {
|
||||
validValue = JSON.parse(validValue)
|
||||
} catch (e) {
|
||||
// pass
|
||||
}
|
||||
this.formatterArgs.onEnter({
|
||||
row: this.row, col: this.col,
|
||||
oldValue: this.cellValue,
|
||||
newValue: validValue
|
||||
})
|
||||
this.inEditMode = false
|
||||
},
|
||||
cancelEdit() {
|
||||
this.inEditMode = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.editInput >>> .el-input__inner {
|
||||
padding: 2px;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.editInput {
|
||||
padding: -6px;
|
||||
}
|
||||
</style>
|
||||
68
src/components/ListTable/formatters/StatusFormatter.vue
Normal file
68
src/components/ListTable/formatters/StatusFormatter.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-tooltip v-if="formatterArgs.hasTips" placement="bottom" effect="dark">
|
||||
<div slot="content">
|
||||
<template v-if="tipsIsArray">
|
||||
<div v-for="tip of tips" :key="tip">
|
||||
<span>{{ tip }}</span>
|
||||
<br>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>
|
||||
{{ tips }}
|
||||
</span>
|
||||
</div>
|
||||
<i :class="'fa ' + iconClass" />
|
||||
</el-tooltip>
|
||||
<i v-else :class="'fa ' + iconClass" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseFormatter from './base'
|
||||
export default {
|
||||
name: 'StatusFormatter',
|
||||
extends: BaseFormatter,
|
||||
props: {
|
||||
formatterArgsDefault: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
iconChoices: {
|
||||
true: 'fa-check text-primary',
|
||||
false: 'fa-times text-danger'
|
||||
},
|
||||
getChoicesKey(val) {
|
||||
return !!val
|
||||
},
|
||||
getTip(val, col) {
|
||||
},
|
||||
hasTips: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconClass() {
|
||||
const key = this.formatterArgs.getChoicesKey(this.cellValue)
|
||||
return this.formatterArgs.iconChoices[key] + ' ' + key + 'Status'
|
||||
},
|
||||
tips() {
|
||||
const vm = this
|
||||
return this.formatterArgs.getTip(this.cellValue, vm)
|
||||
},
|
||||
tipsIsArray() {
|
||||
return Array.isArray(this.tips)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -10,6 +10,8 @@ import SystemUserFormatter from './GrantedSystemUsersShowFormatter'
|
||||
import ShowKeyFormatter from '@/components/ListTable/formatters/ShowKeyFormatter'
|
||||
import DialogDetailFormatter from './DialogDetailFormatter'
|
||||
import LoadingActionsFormatter from './LoadingActionsFormatter'
|
||||
import EditableInputFormatter from './EditableInputFormatter'
|
||||
import StatusFormatter from './StatusFormatter'
|
||||
|
||||
export default {
|
||||
DetailFormatter,
|
||||
@@ -23,7 +25,9 @@ export default {
|
||||
ShowKeyFormatter,
|
||||
DialogDetailFormatter,
|
||||
LoadingActionsFormatter,
|
||||
ArrayFormatter
|
||||
ArrayFormatter,
|
||||
EditableInputFormatter,
|
||||
StatusFormatter
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -38,5 +42,7 @@ export {
|
||||
ShowKeyFormatter,
|
||||
DialogDetailFormatter,
|
||||
LoadingActionsFormatter,
|
||||
ArrayFormatter
|
||||
ArrayFormatter,
|
||||
EditableInputFormatter,
|
||||
StatusFormatter
|
||||
}
|
||||
|
||||
@@ -45,17 +45,6 @@ export default {
|
||||
dataTable() {
|
||||
return this.$refs.dataTable.$refs.dataTable
|
||||
},
|
||||
// hasCreateAction() {
|
||||
// const hasLeftAction = this.headerActions.hasLeftActions
|
||||
// if (hasLeftAction === false) {
|
||||
// return false
|
||||
// }
|
||||
// const hasCreate = this.headerActions.hasCreate
|
||||
// if (hasCreate === false) {
|
||||
// return false
|
||||
// }
|
||||
// return true
|
||||
// },
|
||||
iTableConfig() {
|
||||
const config = deepmerge(this.tableConfig, { extraQuery: this.extraQuery })
|
||||
this.$log.debug('Header actions', this.headerActions)
|
||||
|
||||
@@ -72,7 +72,6 @@ export default {
|
||||
watch: {
|
||||
treeConfig: {
|
||||
handler(val) {
|
||||
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
"TestAssetsConnective": "测试资产可连接性",
|
||||
"TestConnection": "测试连接",
|
||||
"Type": "类型",
|
||||
"UnselectedAssets": "未选择资产",
|
||||
"UnselectedAssets": "未选择资产或所选择的资产不支持SSH协议连接",
|
||||
"UnselectedNodes": "未选择节点",
|
||||
"UpdateAssetUserToken": "更新资产用户认证信息",
|
||||
"Username": "用户名",
|
||||
@@ -258,6 +258,10 @@
|
||||
"EnterForSearch": "按回车进行搜索",
|
||||
"Export": "导出",
|
||||
"Import": "导入",
|
||||
"ContinueImport": "继续导入",
|
||||
"Continue": "继续",
|
||||
"Stop": "停止",
|
||||
"Finished": "完成",
|
||||
"Refresh": "刷新",
|
||||
"Info": "提示",
|
||||
"MFAConfirm": "MFA 认证",
|
||||
@@ -320,16 +324,25 @@
|
||||
"fieldRequiredError": "这个字段是必填项",
|
||||
"getErrorMsg": "获取失败",
|
||||
"MFAErrorMsg": "MFA错误,请检查",
|
||||
"Total": "总共",
|
||||
"Success": "成功",
|
||||
"Failed": "失败",
|
||||
"Pending": "等待",
|
||||
"Status": "状态",
|
||||
"InputEmailAddress": "请输入正确的邮箱地址",
|
||||
"imExport": {
|
||||
"ExportAll": "导出所有",
|
||||
"ExportOnlyFiltered": "仅导出搜索结果",
|
||||
"ExportOnlySelectedItems": "仅导出选择项",
|
||||
"ExportRange": "导出范围",
|
||||
"createSuccessMsg": "导入创建成功,总共:{count}",
|
||||
"downloadImportTemplateMsg": "下载导入模板",
|
||||
"downloadImportTemplateMsg": "下载创建模板",
|
||||
"downloadUpdateTemplateMsg": "下载更新模板",
|
||||
"onlyCSVFilesTips": "仅支持csv文件导入",
|
||||
"updateSuccessMsg": "导入更新成功,总共:{count}"
|
||||
"updateSuccessMsg": "导入更新成功,总共:{count}",
|
||||
"uploadCsvLth10MHelpText": "只能上传 csv/xlsx, 且不超过 10M",
|
||||
"dragUploadFileInfo": "将文件拖到此处,或点击上传",
|
||||
"hasImportErrorItemMsg": "存在导入失败项,点击左侧 x 查看失败原因,点击表格编辑后,可以继续导入失败项"
|
||||
},
|
||||
"fileType": "文件类型",
|
||||
"isValid": "有效",
|
||||
@@ -554,6 +567,7 @@
|
||||
"Acl": "访问控制",
|
||||
"UserAclList": "用户登录",
|
||||
"UserAclCreate": "创建用户登录规则",
|
||||
"UserAclLists": "用户登录规则",
|
||||
"UserAclUpdate": "更新用户登录规则",
|
||||
"UserAclDetail": "用户登录规则详情",
|
||||
"AssetAclList": "登录资产",
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
"TestAssetsConnective": "Test assets connective",
|
||||
"TestConnection": "Test connection",
|
||||
"Type": "Type",
|
||||
"UnselectedAssets": "Unselected assets",
|
||||
"UnselectedAssets": "No asset selected or the selected asset does not support SSH protocol connection",
|
||||
"UnselectedNodes": "Unselected nodes",
|
||||
"UpdateAssetUserToken": "Update asset user auth",
|
||||
"Username": "Username",
|
||||
@@ -257,6 +257,10 @@
|
||||
"EnterForSearch": "Press enter to search",
|
||||
"Export": "Export",
|
||||
"Import": "Import",
|
||||
"ContinueImport": "ContinueImport",
|
||||
"Continue": "Continue",
|
||||
"Stop": "Stop",
|
||||
"Finished": "Finished",
|
||||
"Refresh": "Refresh",
|
||||
"Info": "Info",
|
||||
"MFAConfirm": "MFA Confirm",
|
||||
@@ -277,11 +281,13 @@
|
||||
"Reset": "Reset",
|
||||
"Search": "Search",
|
||||
"MFAErrorMsg": "MFA Error,please check",
|
||||
"InputEmailAddress": "Please enter your email address",
|
||||
"Select": "Select",
|
||||
"SelectFile": "Select file",
|
||||
"Show": "Show",
|
||||
"Submit": "Submit",
|
||||
"Test": "Test",
|
||||
"SaveAndAddAnother":"Save and add another",
|
||||
"TestSuccessMsg": "Test Success",
|
||||
"To": "To",
|
||||
"Update": "Update",
|
||||
@@ -318,6 +324,11 @@
|
||||
"fieldRequiredError": "This field is required",
|
||||
"getErrorMsg": "Get failed",
|
||||
"fileType": "File type",
|
||||
"Status": "Status",
|
||||
"Total": "Total",
|
||||
"Success": "Success",
|
||||
"Failed": "Failed",
|
||||
"Pending": "Pending",
|
||||
"imExport": {
|
||||
"ExportAll": "Export all",
|
||||
"ExportOnlyFiltered": "Export only filtered",
|
||||
@@ -327,7 +338,10 @@
|
||||
"downloadImportTemplateMsg": "Download import template",
|
||||
"downloadUpdateTemplateMsg": "Download update template",
|
||||
"onlyCSVFilesTips": "Only csv supported",
|
||||
"updateSuccessMsg": "Update success, total: {count}"
|
||||
"updateSuccessMsg": "Update success, total: {count}",
|
||||
"dragUploadFileInfo": "Drag file here or click to upload",
|
||||
"uploadCsvLth10MHelpText": "csv/xlsx files with a size less than 10M",
|
||||
"hasImportErrorItemMsg": "There is an error item, click the x icon to view the details, and continue to import after editing"
|
||||
},
|
||||
"isValid": "Is valid",
|
||||
"nav": {
|
||||
@@ -552,6 +566,7 @@
|
||||
"UserAclList": "User acl list",
|
||||
"UserAclCreate": "User acl create",
|
||||
"UserAclUpdate": "User acl update",
|
||||
"UserAclLists": "User acl lists",
|
||||
"UserAclDetail": "User acl detail",
|
||||
"AssetAclList": "Asset acl list",
|
||||
"AssetAclCreate": "Asset acl create",
|
||||
@@ -587,6 +602,10 @@
|
||||
"RemoteAppPermissionCreate": "Remote apps permission create",
|
||||
"RemoteAppPermissionDetail": "Remote apps permissions detail",
|
||||
"RemoteAppPermissionUpdate": "Remote app permission update",
|
||||
"ApplicationDetail": "Application detail",
|
||||
"ApplicationPermissionCreate": "Application permission create",
|
||||
"ApplicationPermissionDetail": "Application permission detail",
|
||||
"ApplicationPermissionUpdate": "Application permission update",
|
||||
"RemoteAppUpdate": "Remote app update",
|
||||
"ReplayStorageUpdate": "Replay storage update",
|
||||
"SessionDetail": "Sessions detail",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<PageHeading>
|
||||
<PageHeading class="disabled-when-print">
|
||||
<slot name="title">{{ iTitle }}</slot>
|
||||
<template #rightSide>
|
||||
<slot name="headingRightSide" />
|
||||
@@ -42,5 +42,15 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@media print {
|
||||
.disabled-when-print{
|
||||
display: none;
|
||||
}
|
||||
.enabled-when-print{
|
||||
display: inherit !important;
|
||||
}
|
||||
.print-margin{
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div :class="classObj" class="app-wrapper">
|
||||
<div v-if="device==='mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||
<NavBar class="sidebar-container" />
|
||||
<NavBar class="sidebar-container disabled-when-print" />
|
||||
<div :class="{hasTagsView:needTagsView}" class="main-container">
|
||||
<div :class="{'fixed-header':fixedHeader}">
|
||||
<div :class="{'fixed-header':fixedHeader}" class="disabled-when-print">
|
||||
<NavHeader />
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
<app-main />
|
||||
<Footer />
|
||||
<Footer class="disabled-when-print" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -98,4 +98,28 @@ export default {
|
||||
.mobile .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
@media print {
|
||||
.disabled-when-print{
|
||||
display: none;
|
||||
width: 100%;
|
||||
}
|
||||
.enabled-when-print{
|
||||
display: inherit !important;
|
||||
}
|
||||
.print-margin{
|
||||
margin-top: 10px;
|
||||
}
|
||||
.drawer-bg{
|
||||
display: none;
|
||||
}
|
||||
.main-container{
|
||||
margin-left: 0px !important;
|
||||
}
|
||||
//.fixed-header{
|
||||
// width: 100% !important;
|
||||
//}
|
||||
//.hideSidebar .fixed-header{
|
||||
// width: 100% !important;
|
||||
//}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const _ = require('lodash')
|
||||
|
||||
function getTimeUnits(u) {
|
||||
const units = {
|
||||
'd': '天',
|
||||
@@ -187,8 +189,28 @@ export function getDayFuture(days, now) {
|
||||
return new Date(now.getTime() + 3600 * 1000 * 24 * days)
|
||||
}
|
||||
|
||||
export function getErrorResponseMsg(error) {
|
||||
let msg = ''
|
||||
const data = error.response && error.response.data || {}
|
||||
if (data && (data.error || data.msg || data.detail)) {
|
||||
msg = data.error || data.msg || data.detail
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
export function sleep(time) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time))
|
||||
}
|
||||
|
||||
function customizer(objValue, srcValue) {
|
||||
return _.isUndefined(objValue) ? srcValue : objValue
|
||||
}
|
||||
|
||||
export const assignIfNot = _.partialRight(_.assignInWith, customizer)
|
||||
|
||||
const scheme = document.location.protocol
|
||||
const port = document.location.port ? ':' + document.location.port : ''
|
||||
const BASE_URL = scheme + '//' + document.location.hostname + port
|
||||
|
||||
export { BASE_URL }
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import axios from 'axios'
|
||||
import i18n from '@/i18n/i18n'
|
||||
import { getTokenFromCookie } from '@/utils/auth'
|
||||
import { getErrorResponseMsg } from '@/utils/common'
|
||||
import { refreshSessionIdAge } from '@/api/users'
|
||||
import { Message, MessageBox } from 'element-ui'
|
||||
import store from '@/store'
|
||||
@@ -84,11 +85,8 @@ function ifBadRequest({ response, error }) {
|
||||
|
||||
export function flashErrorMsg({ response, error }) {
|
||||
if (!response.config.disableFlashErrorMsg) {
|
||||
let msg = error.message
|
||||
const data = response.data
|
||||
if (data && (data.error || data.msg || data.detail)) {
|
||||
msg = data.error || data.msg || data.detail
|
||||
}
|
||||
const responseErrorMsg = getErrorResponseMsg(error)
|
||||
const msg = responseErrorMsg || error.message
|
||||
Message({
|
||||
message: msg,
|
||||
type: 'error',
|
||||
|
||||
@@ -122,6 +122,11 @@ export default {
|
||||
},
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
protocols: {
|
||||
formatter: function(row) {
|
||||
return <span> {row.protocols.toString()} </span>
|
||||
}
|
||||
},
|
||||
ip: {
|
||||
sortable: 'custom',
|
||||
width: '140px'
|
||||
|
||||
@@ -85,7 +85,17 @@ export default {
|
||||
}
|
||||
}.bind(this)
|
||||
}
|
||||
]
|
||||
],
|
||||
onClone: function({ row, col }) {
|
||||
const cloneRoute = {
|
||||
name: 'GatewayCreate',
|
||||
query: {
|
||||
domain: this.object.id,
|
||||
clone_from: row.id
|
||||
}
|
||||
}
|
||||
this.$router.push(cloneRoute)
|
||||
}.bind(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" :perform-submit="performSubmit.bind(this)" />
|
||||
<GenericCreateUpdatePage :fields="fields" :initial="initial" :fields-meta="fieldsMeta" :url="url" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -17,65 +17,48 @@ export default {
|
||||
charset: 'utf8'
|
||||
},
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['name', 'base', 'charset', 'security', 'console', 'comment']]
|
||||
[this.$t('common.Basic'), ['name', 'base', 'charset', 'meta', 'comment']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
security: {
|
||||
type: 'select',
|
||||
label: 'RDP security',
|
||||
options: [{
|
||||
label: 'RDP',
|
||||
value: 'rdp'
|
||||
meta: {
|
||||
fields: ['security', 'console'],
|
||||
fieldsMeta: {
|
||||
security: {
|
||||
prop: 'meta.security',
|
||||
type: 'select',
|
||||
label: 'RDP security',
|
||||
options: [{
|
||||
label: 'RDP',
|
||||
value: 'rdp'
|
||||
},
|
||||
{
|
||||
label: 'NLA',
|
||||
value: 'nla'
|
||||
},
|
||||
{
|
||||
label: 'TLS',
|
||||
value: 'tls'
|
||||
},
|
||||
{
|
||||
label: 'Any',
|
||||
value: 'any'
|
||||
}]
|
||||
},
|
||||
console: {
|
||||
type: 'select',
|
||||
label: 'RDP console',
|
||||
options: [{
|
||||
label: this.$t('common.Yes'),
|
||||
value: 'true'
|
||||
}, {
|
||||
label: this.$t('common.No'),
|
||||
value: 'false'
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'NLA',
|
||||
value: 'nla'
|
||||
},
|
||||
{
|
||||
label: 'TLS',
|
||||
value: 'tls'
|
||||
},
|
||||
{
|
||||
label: 'Any',
|
||||
value: 'any'
|
||||
}],
|
||||
hidden: form => form.base !== 'Windows'
|
||||
},
|
||||
console: {
|
||||
type: 'select',
|
||||
label: 'RDP console',
|
||||
options: [{
|
||||
label: '是',
|
||||
value: 'true'
|
||||
}, {
|
||||
label: '否',
|
||||
value: 'false'
|
||||
}],
|
||||
hidden: form => form.base !== 'Windows'
|
||||
}
|
||||
},
|
||||
performSubmit: function(formdata) {
|
||||
var postData = {}
|
||||
if (formdata.base === 'Windows') {
|
||||
postData.meta = {}
|
||||
postData.meta.security = formdata.security
|
||||
postData.meta.console = (formdata.console === 'true')
|
||||
}
|
||||
postData.name = formdata.name
|
||||
postData.base = formdata.base
|
||||
postData.charset = formdata.charset
|
||||
postData.comment = formdata.comment || ''
|
||||
|
||||
const params = this.$route.params
|
||||
if (params.id) {
|
||||
return this.$axios.put(
|
||||
`${this.url}${params.id}/`, postData
|
||||
)
|
||||
} else {
|
||||
return this.$axios.post(
|
||||
this.url, postData
|
||||
)
|
||||
}
|
||||
},
|
||||
url: '/api/v1/assets/platforms/'
|
||||
}
|
||||
|
||||
@@ -4,141 +4,34 @@
|
||||
|
||||
<script>
|
||||
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
|
||||
import UploadKey from '@/components/UploadKey'
|
||||
import { Required } from '@/components/DataForm/rules'
|
||||
import getFields from './fields'
|
||||
|
||||
export default {
|
||||
name: 'SystemUserCreateUpdate',
|
||||
components: { GenericCreateUpdatePage },
|
||||
data() {
|
||||
const fields = getFields.bind(this)()
|
||||
return {
|
||||
initial: {
|
||||
login_mode: 'auto',
|
||||
protocol: this.$route.query.protocol,
|
||||
username_same_with_user: false,
|
||||
auto_generate_key: false,
|
||||
auto_push: false
|
||||
},
|
||||
fields: [
|
||||
[this.$t('common.Basic'), ['name', 'login_mode', 'username', 'username_same_with_user', 'priority', 'protocol']],
|
||||
[this.$t('common.Basic'), ['name', 'login_mode', 'username', 'priority', 'protocol']],
|
||||
[this.$t('common.Auth'), ['update_password', 'password']],
|
||||
[this.$t('common.Other'), ['comment']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
login_mode: {
|
||||
helpText: this.$t('assets.LoginModeHelpMessage'),
|
||||
hidden: (form) => {
|
||||
if (form.protocol === 'k8s') {
|
||||
return true
|
||||
}
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
if (value === 'manual') {
|
||||
updateForm({ auto_push: false })
|
||||
updateForm({ auto_generate_key: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
username: {
|
||||
el: {
|
||||
disabled: false
|
||||
},
|
||||
rules: [Required],
|
||||
hidden: (form) => {
|
||||
if (form.login_mode === 'auto') {
|
||||
this.fieldsMeta.username.rules = [Required]
|
||||
} else {
|
||||
this.fieldsMeta.username.rules[0].required = false
|
||||
}
|
||||
if (!form.username_same_with_user) {
|
||||
this.fieldsMeta.username.rules = [Required]
|
||||
} else {
|
||||
this.fieldsMeta.username.rules[0].required = false
|
||||
}
|
||||
if (['mysql', 'postgresql', 'mariadb', 'oracle'].indexOf(form.protocol) !== -1) {
|
||||
this.fieldsMeta.username.rules = [Required]
|
||||
this.fieldsMeta.username.rules[0].required = true
|
||||
}
|
||||
}
|
||||
},
|
||||
private_key: {
|
||||
component: UploadKey,
|
||||
hidden: (form) => {
|
||||
if (form.login_mode !== 'auto') {
|
||||
return true
|
||||
}
|
||||
if (form.protocol === 'k8s') {
|
||||
return true
|
||||
}
|
||||
return form.auto_generate_key === true
|
||||
}
|
||||
},
|
||||
username_same_with_user: {
|
||||
type: 'switch',
|
||||
helpText: this.$t('assets.UsernameHelpMessage'),
|
||||
hidden: (form) => {
|
||||
this.fieldsMeta.username.el.disabled = form.username_same_with_user
|
||||
return form.protocol === 'k8s'
|
||||
},
|
||||
el: {
|
||||
disabled: false
|
||||
}
|
||||
},
|
||||
protocol: {
|
||||
rules: [Required],
|
||||
el: {
|
||||
disabled: true,
|
||||
style: 'width:100%'
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
if (['ssh', 'rdp'].indexOf(value) === -1) {
|
||||
updateForm({ auto_push: false })
|
||||
updateForm({ auto_generate_key: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
update_password: {
|
||||
label: this.$t('users.UpdatePassword'),
|
||||
type: 'checkbox',
|
||||
hidden: (formValue) => {
|
||||
if (formValue.update_password || formValue.protocol === 'k8s') {
|
||||
return true
|
||||
}
|
||||
if (formValue.login_mode === 'manual') {
|
||||
return true
|
||||
}
|
||||
return !this.$route.params.id
|
||||
}
|
||||
},
|
||||
password: {
|
||||
helpText: this.$t('assets.PasswordHelpMessage'),
|
||||
hidden: form => {
|
||||
if (form.login_mode !== 'auto' || form.protocol === 'k8s' || form.auto_generate_key) {
|
||||
return true
|
||||
}
|
||||
if (!this.$route.params.id) {
|
||||
return false
|
||||
}
|
||||
return !form.update_password
|
||||
}
|
||||
}
|
||||
login_mode: fields.login_mode,
|
||||
username: fields.username,
|
||||
private_key: fields.private_key,
|
||||
protocol: fields.protocol,
|
||||
update_password: fields.update_password,
|
||||
password: fields.password
|
||||
},
|
||||
url: '/api/v1/assets/system-users/',
|
||||
authHiden: false
|
||||
}
|
||||
},
|
||||
method: {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
const params = this.$route.params
|
||||
const method = params.id ? 'update' : 'create'
|
||||
if (method === 'update') {
|
||||
this.fieldsMeta.username_same_with_user.el.disabled = true
|
||||
url: '/api/v1/assets/system-users/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
179
src/views/assets/SystemUser/SystemUserCreate/fields.js
Normal file
179
src/views/assets/SystemUser/SystemUserCreate/fields.js
Normal file
@@ -0,0 +1,179 @@
|
||||
import { Required } from '@/components/DataForm/rules'
|
||||
import UploadKey from '@/components/UploadKey'
|
||||
import i18n from '@/i18n/i18n'
|
||||
import { Select2 } from '@/components'
|
||||
|
||||
function getFields() {
|
||||
const login_mode = {
|
||||
helpText: i18n.t('assets.LoginModeHelpMessage'),
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
if (value === 'manual') {
|
||||
updateForm({ auto_push: false })
|
||||
updateForm({ auto_generate_key: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const username = {
|
||||
el: {
|
||||
disabled: false
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
updateForm({ home: `/home/${value}` })
|
||||
}
|
||||
},
|
||||
rules: [Object.assign({}, Required)],
|
||||
hidden: (form) => {
|
||||
if (form.login_mode === 'manual' || form.username_same_with_user) {
|
||||
this.fieldsMeta.username.rules[0].required = false
|
||||
} else {
|
||||
this.fieldsMeta.username.rules[0].required = true
|
||||
}
|
||||
|
||||
if (form.username_same_with_user) {
|
||||
this.fieldsMeta.username.el.disabled = true
|
||||
} else {
|
||||
this.fieldsMeta.username.el.disabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const private_key = {
|
||||
component: UploadKey,
|
||||
hidden: (form) => {
|
||||
if (form.login_mode !== 'auto') {
|
||||
return true
|
||||
}
|
||||
return form.auto_generate_key === true
|
||||
}
|
||||
}
|
||||
|
||||
const username_same_with_user = {
|
||||
type: 'switch',
|
||||
helpText: this.$t('assets.UsernameHelpMessage'),
|
||||
el: {
|
||||
disabled: false
|
||||
},
|
||||
hidden: form => {
|
||||
const params = this.$route.params
|
||||
const method = params.id ? 'update' : 'create'
|
||||
if (method === 'update') {
|
||||
this.fieldsMeta.username_same_with_user.el.disabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto_generate_key = {
|
||||
type: 'switch',
|
||||
label: this.$t('assets.AutoGenerateKey'),
|
||||
hidden: form => {
|
||||
if (JSON.stringify(this.$route.params) !== '{}') {
|
||||
return true
|
||||
}
|
||||
if (form.protocol === 'k8s') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (form.login_mode === 'manual') {
|
||||
this.fieldsMeta.auto_generate_key.el.disabled = true
|
||||
} else {
|
||||
this.fieldsMeta.auto_generate_key.el.disabled = false
|
||||
}
|
||||
},
|
||||
el: {
|
||||
disabled: false
|
||||
}
|
||||
}
|
||||
|
||||
const protocol = {
|
||||
rules: [Required],
|
||||
el: {
|
||||
style: 'width:100%',
|
||||
disabled: true
|
||||
}
|
||||
}
|
||||
|
||||
const cmd_filters = {
|
||||
component: Select2,
|
||||
el: {
|
||||
multiple: true,
|
||||
value: [],
|
||||
ajax: {
|
||||
url: '/api/v1/assets/cmd-filters/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto_push = {
|
||||
type: 'switch',
|
||||
el: {
|
||||
disabled: false
|
||||
},
|
||||
hidden: form => {
|
||||
if (form.login_mode === 'manual') {
|
||||
this.fieldsMeta.auto_push.el.disabled = true
|
||||
} else {
|
||||
this.fieldsMeta.auto_push.el.disabled = false
|
||||
}
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
if (!value) {
|
||||
updateForm({ auto_generate_key: value })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const update_password = {
|
||||
label: this.$t('users.UpdatePassword'),
|
||||
type: 'checkbox',
|
||||
hidden: (formValue) => {
|
||||
if (formValue.update_password) {
|
||||
return true
|
||||
}
|
||||
if (formValue.login_mode === 'manual') {
|
||||
return true
|
||||
}
|
||||
return !this.$route.params.id
|
||||
}
|
||||
}
|
||||
|
||||
const password = {
|
||||
helpText: this.$t('assets.PasswordHelpMessage'),
|
||||
hidden: form => {
|
||||
if (form.login_mode !== 'auto' || form.auto_generate_key) {
|
||||
return true
|
||||
}
|
||||
if (!this.$route.params.id) {
|
||||
return false
|
||||
}
|
||||
return !form.update_password
|
||||
}
|
||||
}
|
||||
|
||||
const system_groups = {
|
||||
label: this.$t('assets.LinuxUserAffiliateGroup'),
|
||||
hidden: (item) => !item.auto_push || item.username_same_with_user,
|
||||
helpText: this.$t('assets.GroupsHelpMessage')
|
||||
}
|
||||
|
||||
return {
|
||||
login_mode: login_mode,
|
||||
username: username,
|
||||
private_key: private_key,
|
||||
username_same_with_user: username_same_with_user,
|
||||
auto_generate_key: auto_generate_key,
|
||||
protocol: protocol,
|
||||
cmd_filters: cmd_filters,
|
||||
auto_push: auto_push,
|
||||
update_password: update_password,
|
||||
password: password,
|
||||
system_groups: system_groups
|
||||
}
|
||||
}
|
||||
|
||||
export default getFields
|
||||
@@ -26,10 +26,17 @@ export default {
|
||||
],
|
||||
fieldsMeta: {
|
||||
token: {
|
||||
rules: [Required],
|
||||
rules: [Object.assign({}, Required)],
|
||||
el: {
|
||||
type: 'textarea',
|
||||
autosize: { minRows: 3 }
|
||||
},
|
||||
hidden: form => {
|
||||
const params = this.$route.params
|
||||
const method = params.id ? 'update' : 'create'
|
||||
if (method === 'update') {
|
||||
this.fieldsMeta.token.rules[0].required = false
|
||||
}
|
||||
}
|
||||
},
|
||||
protocol: {
|
||||
@@ -40,18 +47,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
url: '/api/v1/assets/system-users/',
|
||||
authHiden: false
|
||||
}
|
||||
},
|
||||
method: {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
const params = this.$route.params
|
||||
const method = params.id ? 'update' : 'create'
|
||||
if (method === 'update') {
|
||||
this.fieldsMeta.token.rules[0].required = false
|
||||
url: '/api/v1/assets/system-users/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
|
||||
<script>
|
||||
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
|
||||
import { Required } from '@/components/DataForm/rules'
|
||||
import getFields from './fields'
|
||||
|
||||
export default {
|
||||
name: 'SystemUserCreateUpdate',
|
||||
components: { GenericCreateUpdatePage },
|
||||
data() {
|
||||
const fields = getFields.bind(this)()
|
||||
return {
|
||||
initial: {
|
||||
login_mode: 'auto',
|
||||
@@ -28,131 +29,23 @@ export default {
|
||||
[this.$t('common.Other'), ['comment']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
login_mode: {
|
||||
helpText: this.$t('assets.LoginModeHelpMessage'),
|
||||
hidden: (form) => {
|
||||
if (form.protocol === 'k8s') {
|
||||
return true
|
||||
}
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
if (value === 'manual') {
|
||||
updateForm({ auto_push: false })
|
||||
updateForm({ auto_generate_key: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
username: {
|
||||
el: {
|
||||
disabled: false
|
||||
},
|
||||
rules: [Required],
|
||||
hidden: (form) => {
|
||||
if (form.login_mode === 'auto') {
|
||||
this.fieldsMeta.username.rules = [Required]
|
||||
} else {
|
||||
this.fieldsMeta.username.rules[0].required = false
|
||||
}
|
||||
if (!form.username_same_with_user) {
|
||||
this.fieldsMeta.username.rules = [Required]
|
||||
} else {
|
||||
this.fieldsMeta.username.rules[0].required = false
|
||||
}
|
||||
}
|
||||
},
|
||||
username_same_with_user: {
|
||||
type: 'switch',
|
||||
helpText: this.$t('assets.UsernameHelpMessage'),
|
||||
hidden: (form) => {
|
||||
this.fieldsMeta.username.el.disabled = form.username_same_with_user
|
||||
return form.protocol === 'k8s'
|
||||
},
|
||||
el: {
|
||||
disabled: false
|
||||
}
|
||||
},
|
||||
auto_push: {
|
||||
type: 'switch',
|
||||
el: {
|
||||
disabled: false
|
||||
},
|
||||
hidden: form => {
|
||||
if (form.login_mode === 'manual') { this.fieldsMeta.auto_push.el.disabled = true }
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
if (!value) {
|
||||
updateForm({ auto_generate_key: value })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
protocol: {
|
||||
rules: [Required],
|
||||
el: {
|
||||
style: 'width:100%',
|
||||
disabled: true
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
if (['ssh', 'rdp'].indexOf(value) === -1) {
|
||||
updateForm({ auto_push: false })
|
||||
updateForm({ auto_generate_key: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
login_mode: fields.login_mode,
|
||||
username: fields.username,
|
||||
username_same_with_user: fields.username_same_with_user,
|
||||
auto_push: fields.auto_push,
|
||||
protocol: fields.protocol,
|
||||
ad_domain: {
|
||||
label: this.$t('assets.AdDomain'),
|
||||
hidden: (form) => ['rdp'].indexOf(form.protocol) === -1,
|
||||
helpText: this.$t('assets.AdDomainHelpText')
|
||||
},
|
||||
update_password: {
|
||||
label: this.$t('users.UpdatePassword'),
|
||||
type: 'checkbox',
|
||||
hidden: (formValue) => {
|
||||
if (formValue.update_password || formValue.protocol === 'k8s') {
|
||||
return true
|
||||
}
|
||||
if (formValue.login_mode === 'manual') {
|
||||
return true
|
||||
}
|
||||
return !this.$route.params.id
|
||||
}
|
||||
},
|
||||
password: {
|
||||
helpText: this.$t('assets.PasswordHelpMessage'),
|
||||
hidden: form => {
|
||||
if (form.login_mode !== 'auto' || form.protocol === 'k8s' || form.auto_generate_key) {
|
||||
return true
|
||||
}
|
||||
if (!this.$route.params.id) {
|
||||
return false
|
||||
}
|
||||
return !form.update_password
|
||||
}
|
||||
},
|
||||
system_groups: {
|
||||
label: this.$t('assets.LinuxUserAffiliateGroup'),
|
||||
hidden: (item) => ['ssh', 'rdp'].indexOf(item.protocol) === -1 || !item.auto_push || item.username_same_with_user,
|
||||
helpText: this.$t('assets.GroupsHelpMessage')
|
||||
}
|
||||
update_password: fields.update_password,
|
||||
password: fields.password,
|
||||
system_groups: fields.system_groups
|
||||
},
|
||||
url: '/api/v1/assets/system-users/',
|
||||
authHiden: false
|
||||
url: '/api/v1/assets/system-users/'
|
||||
}
|
||||
},
|
||||
method: {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
const params = this.$route.params
|
||||
const method = params.id ? 'update' : 'create'
|
||||
if (method === 'update') {
|
||||
this.fieldsMeta.username_same_with_user.el.disabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -4,17 +4,14 @@
|
||||
|
||||
<script>
|
||||
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
|
||||
import UploadKey from '@/components/UploadKey'
|
||||
import { Select2 } from '@/components'
|
||||
import { Required } from '@/components/DataForm/rules'
|
||||
|
||||
// const asciiProtocols = ['ssh', 'telnet', 'mysql']
|
||||
const graphProtocols = ['vnc', 'rdp', 'k8s']
|
||||
import getFields from './fields'
|
||||
|
||||
export default {
|
||||
name: 'SystemUserCreateUpdate',
|
||||
components: { GenericCreateUpdatePage },
|
||||
data() {
|
||||
const fields = getFields.bind(this)()
|
||||
return {
|
||||
initial: {
|
||||
login_mode: 'auto',
|
||||
@@ -29,185 +26,30 @@ export default {
|
||||
fields: [
|
||||
[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', 'token', 'ad_domain']],
|
||||
[this.$t('common.Auth'), ['auto_generate_key', 'update_password', 'password', 'private_key']],
|
||||
[this.$t('common.Command filter'), ['cmd_filters']],
|
||||
[this.$t('common.Other'), ['sftp_root', 'comment']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
login_mode: {
|
||||
helpText: this.$t('assets.LoginModeHelpMessage'),
|
||||
hidden: (form) => {
|
||||
if (form.protocol === 'k8s') {
|
||||
return true
|
||||
}
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
if (value === 'manual') {
|
||||
updateForm({ auto_push: false })
|
||||
updateForm({ auto_generate_key: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
username: {
|
||||
el: {
|
||||
disabled: false
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
updateForm({ home: `/home/${value}` })
|
||||
}
|
||||
},
|
||||
rules: [Required],
|
||||
hidden: (form) => {
|
||||
if (form.login_mode === 'auto') {
|
||||
this.fieldsMeta.username.rules = [Required]
|
||||
} else {
|
||||
this.fieldsMeta.username.rules[0].required = false
|
||||
}
|
||||
if (!form.username_same_with_user) {
|
||||
this.fieldsMeta.username.rules = [Required]
|
||||
} else {
|
||||
this.fieldsMeta.username.rules[0].required = false
|
||||
}
|
||||
if (['mysql', 'postgresql', 'mariadb', 'oracle'].indexOf(form.protocol) !== -1) {
|
||||
this.fieldsMeta.username.rules = [Required]
|
||||
this.fieldsMeta.username.rules[0].required = true
|
||||
}
|
||||
}
|
||||
},
|
||||
private_key: {
|
||||
component: UploadKey,
|
||||
hidden: (form) => {
|
||||
if (form.login_mode !== 'auto') {
|
||||
return true
|
||||
}
|
||||
if (form.protocol === 'k8s') {
|
||||
return true
|
||||
}
|
||||
return form.auto_generate_key === true
|
||||
}
|
||||
},
|
||||
username_same_with_user: {
|
||||
type: 'switch',
|
||||
helpText: this.$t('assets.UsernameHelpMessage'),
|
||||
hidden: (form) => {
|
||||
this.fieldsMeta.username.el.disabled = form.username_same_with_user
|
||||
return form.protocol === 'k8s'
|
||||
},
|
||||
el: {
|
||||
disabled: false
|
||||
}
|
||||
},
|
||||
auto_generate_key: {
|
||||
type: 'switch',
|
||||
label: this.$t('assets.AutoGenerateKey'),
|
||||
hidden: form => {
|
||||
this.fieldsMeta.auto_generate_key.el.disabled = ['ssh', 'rdp'].indexOf(form.protocol) === -1 || form.login_mode === 'manual'
|
||||
if (JSON.stringify(this.$route.params) !== '{}') {
|
||||
return true
|
||||
}
|
||||
if (form.protocol === 'k8s') {
|
||||
return true
|
||||
}
|
||||
},
|
||||
el: {
|
||||
disabled: false
|
||||
}
|
||||
},
|
||||
token: {
|
||||
rules: [Required],
|
||||
el: {
|
||||
type: 'textarea',
|
||||
autosize: { minRows: 3 }
|
||||
},
|
||||
hidden: form => {
|
||||
return form.protocol !== 'k8s'
|
||||
}
|
||||
},
|
||||
protocol: {
|
||||
rules: [Required],
|
||||
el: {
|
||||
style: 'width:100%',
|
||||
disabled: true
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
if (['ssh', 'rdp'].indexOf(value) === -1) {
|
||||
updateForm({ auto_push: false })
|
||||
updateForm({ auto_generate_key: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ad_domain: {
|
||||
label: this.$t('assets.AdDomain'),
|
||||
hidden: (form) => ['rdp'].indexOf(form.protocol) === -1,
|
||||
helpText: this.$t('assets.AdDomainHelpText')
|
||||
},
|
||||
cmd_filters: {
|
||||
component: Select2,
|
||||
hidden: (form) => graphProtocols.indexOf(form.protocol) !== -1,
|
||||
el: {
|
||||
multiple: true,
|
||||
value: [],
|
||||
ajax: {
|
||||
url: '/api/v1/assets/cmd-filters/'
|
||||
}
|
||||
}
|
||||
},
|
||||
auto_push: {
|
||||
type: 'switch',
|
||||
el: {
|
||||
disabled: false
|
||||
},
|
||||
hidden: form => {
|
||||
this.fieldsMeta.auto_push.el.disabled = ['ssh', 'rdp'].indexOf(form.protocol) === -1 || form.login_mode === 'manual'
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
if (!value) {
|
||||
updateForm({ auto_generate_key: value })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
login_mode: fields.login_mode,
|
||||
username: fields.username,
|
||||
private_key: fields.private_key,
|
||||
username_same_with_user: fields.username_same_with_user,
|
||||
auto_generate_key: fields.auto_generate_key,
|
||||
protocol: fields.protocol,
|
||||
cmd_filters: fields.cmd_filters,
|
||||
auto_push: fields.auto_push,
|
||||
sftp_root: {
|
||||
rules: [Required],
|
||||
helpText: this.$t('assets.SFTPHelpMessage'),
|
||||
hidden: (item) => item.protocol !== 'ssh'
|
||||
helpText: this.$t('assets.SFTPHelpMessage')
|
||||
},
|
||||
sudo: {
|
||||
rules: [Required],
|
||||
helpText: this.$t('assets.SudoHelpMessage'),
|
||||
hidden: (item) => item.protocol !== 'ssh' || !item.auto_push
|
||||
},
|
||||
update_password: {
|
||||
label: this.$t('users.UpdatePassword'),
|
||||
type: 'checkbox',
|
||||
hidden: (formValue) => {
|
||||
if (formValue.update_password || formValue.protocol === 'k8s') {
|
||||
return true
|
||||
}
|
||||
if (formValue.login_mode === 'manual') {
|
||||
return true
|
||||
}
|
||||
return !this.$route.params.id
|
||||
}
|
||||
},
|
||||
password: {
|
||||
helpText: this.$t('assets.PasswordHelpMessage'),
|
||||
hidden: form => {
|
||||
if (form.login_mode !== 'auto' || form.protocol === 'k8s' || form.auto_generate_key) {
|
||||
return true
|
||||
}
|
||||
if (!this.$route.params.id) {
|
||||
return false
|
||||
}
|
||||
return !form.update_password
|
||||
}
|
||||
},
|
||||
update_password: fields.update_password,
|
||||
password: fields.password,
|
||||
shell: {
|
||||
hidden: (item) => item.protocol !== 'ssh' || !item.auto_push,
|
||||
rules: [Required]
|
||||
@@ -217,25 +59,12 @@ export default {
|
||||
hidden: (item) => item.protocol !== 'ssh' || !item.auto_push || item.username_same_with_user,
|
||||
helpText: this.$t('assets.HomeHelpMessage')
|
||||
},
|
||||
system_groups: {
|
||||
label: this.$t('assets.LinuxUserAffiliateGroup'),
|
||||
hidden: (item) => ['ssh', 'rdp'].indexOf(item.protocol) === -1 || !item.auto_push || item.username_same_with_user,
|
||||
helpText: this.$t('assets.GroupsHelpMessage')
|
||||
}
|
||||
system_groups: fields.system_groups
|
||||
},
|
||||
url: '/api/v1/assets/system-users/',
|
||||
authHiden: false
|
||||
url: '/api/v1/assets/system-users/'
|
||||
}
|
||||
},
|
||||
method: {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
const params = this.$route.params
|
||||
const method = params.id ? 'update' : 'create'
|
||||
if (method === 'update') {
|
||||
this.fieldsMeta.username_same_with_user.el.disabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
import GenericCreateUpdatePage from '@/layout/components/GenericCreateUpdatePage'
|
||||
import { Required } from '@/components/DataForm/rules'
|
||||
import getFields from '@/views/assets/SystemUser/SystemUserCreate/fields'
|
||||
|
||||
// const asciiProtocols = ['ssh', 'telnet', 'mysql']
|
||||
|
||||
@@ -12,6 +12,7 @@ export default {
|
||||
name: 'SystemUserCreateUpdate',
|
||||
components: { GenericCreateUpdatePage },
|
||||
data() {
|
||||
const fields = getFields.bind(this)()
|
||||
return {
|
||||
initial: {
|
||||
login_mode: 'auto',
|
||||
@@ -24,90 +25,14 @@ export default {
|
||||
[this.$t('common.Other'), ['comment']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
login_mode: {
|
||||
helpText: this.$t('assets.LoginModeHelpMessage')
|
||||
},
|
||||
username: {
|
||||
el: {
|
||||
disabled: false
|
||||
},
|
||||
rules: [Required],
|
||||
hidden: (form) => {
|
||||
if (form.login_mode === 'auto') {
|
||||
this.fieldsMeta.username.rules = [Required]
|
||||
} else {
|
||||
this.fieldsMeta.username.rules[0].required = false
|
||||
}
|
||||
if (!form.username_same_with_user) {
|
||||
this.fieldsMeta.username.rules = [Required]
|
||||
} else {
|
||||
this.fieldsMeta.username.rules[0].required = false
|
||||
}
|
||||
}
|
||||
},
|
||||
username_same_with_user: {
|
||||
type: 'switch',
|
||||
helpText: this.$t('assets.UsernameHelpMessage'),
|
||||
hidden: (form) => {
|
||||
this.fieldsMeta.username.el.disabled = form.username_same_with_user
|
||||
},
|
||||
el: {
|
||||
disabled: false
|
||||
}
|
||||
},
|
||||
protocol: {
|
||||
rules: [Required],
|
||||
el: {
|
||||
disabled: true,
|
||||
style: 'width:100%'
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
if (['ssh', 'rdp'].indexOf(value) === -1) {
|
||||
updateForm({ auto_push: false })
|
||||
updateForm({ auto_generate_key: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
update_password: {
|
||||
label: this.$t('users.UpdatePassword'),
|
||||
type: 'checkbox',
|
||||
hidden: (formValue) => {
|
||||
if (formValue.update_password || formValue.protocol === 'k8s') {
|
||||
return true
|
||||
}
|
||||
if (formValue.login_mode === 'manual') {
|
||||
return true
|
||||
}
|
||||
return !this.$route.params.id
|
||||
}
|
||||
},
|
||||
password: {
|
||||
helpText: this.$t('assets.PasswordHelpMessage'),
|
||||
hidden: form => {
|
||||
if (form.login_mode !== 'auto' || form.protocol === 'k8s' || form.auto_generate_key) {
|
||||
return true
|
||||
}
|
||||
if (!this.$route.params.id) {
|
||||
return false
|
||||
}
|
||||
return !form.update_password
|
||||
}
|
||||
}
|
||||
login_mode: fields.login_mode,
|
||||
username: fields.username,
|
||||
username_same_with_user: fields.username_same_with_user,
|
||||
protocol: fields.protocol,
|
||||
update_password: fields.update_password,
|
||||
password: fields.password
|
||||
},
|
||||
url: '/api/v1/assets/system-users/',
|
||||
authHiden: false
|
||||
}
|
||||
},
|
||||
method: {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
const params = this.$route.params
|
||||
const method = params.id ? 'update' : 'create'
|
||||
if (method === 'update') {
|
||||
this.fieldsMeta.username_same_with_user.el.disabled = true
|
||||
url: '/api/v1/assets/system-users/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
<template>
|
||||
<div class="statistic-box">
|
||||
<h4>{{ $t('dashboard.ActiveUserAssetsRatioTitle') }}</h4>
|
||||
<el-row :gutter="10">
|
||||
<el-col :md="12" :sm="24">
|
||||
<el-row :gutter="2">
|
||||
<el-col :md="12" :sm="10">
|
||||
<echarts :options="userOption" :autoresize="true" />
|
||||
<div style="" class="print-display">
|
||||
<div class="circle-icon" style="background: #1ab394;" />
|
||||
<label>{{ $t('dashboard.ActiveUser') }}</label>
|
||||
<div class="circle-icon" style="background: #1C84C6;" />
|
||||
<label>{{ $t('dashboard.DisabledUser') }}</label>
|
||||
<div class="circle-icon" style="background: #9CC3DA;" />
|
||||
<label>{{ $t('dashboard.InActiveUser') }}</label>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :md="12" :sm="24">
|
||||
<el-col :md="12" :sm="10">
|
||||
<echarts :options="AssetOption" :autoresize="true" />
|
||||
<div style="" class="print-display">
|
||||
<div class="circle-icon" style="background: #1ab394;" />
|
||||
<label>{{ $t('dashboard.ActiveAsset') }}</label>
|
||||
<div class="circle-icon" style="background: #1C84C6;" />
|
||||
<label>{{ $t('dashboard.DisabledAsset') }}</label>
|
||||
<div class="circle-icon" style="background: #9CC3DA;" />
|
||||
<label>{{ $t('dashboard.InActiveAsset') }}</label>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
@@ -153,4 +169,23 @@ export default {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
}
|
||||
.print-display {
|
||||
display: none;
|
||||
}
|
||||
.circle-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
display:inline-block;
|
||||
}
|
||||
@media print {
|
||||
.el-col-24{
|
||||
width: 50% !important;
|
||||
}
|
||||
.print-display {
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<echarts :options="options" :autoresize="true" theme="light" />
|
||||
<div>
|
||||
<echarts ref="echarts" :options="options" :autoresize="true" theme="light" class="disabled-when-print" @finished="getDataUrl" />
|
||||
<img v-if="dataUrl" :src="dataUrl" class="enabled-when-print" style="display: none;width: 100%;">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -15,6 +18,7 @@ export default {
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
dataUrl: '',
|
||||
metricsData: {
|
||||
dates_metrics_date: [],
|
||||
dates_metrics_total_count_active_assets: [],
|
||||
@@ -106,6 +110,11 @@ export default {
|
||||
url = `${url}&monthly=1`
|
||||
}
|
||||
this.metricsData = await this.$axios.get(url)
|
||||
},
|
||||
getDataUrl() {
|
||||
this.dataUrl = this.$refs.echarts.getDataURL({
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,4 +125,15 @@ export default {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
@media print {
|
||||
.disabled-when-print{
|
||||
display: none;
|
||||
}
|
||||
.enabled-when-print{
|
||||
display: inherit !important;
|
||||
}
|
||||
.print-margin{
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="white-bg dashboard-header">
|
||||
<div class="white-bg dashboard-header print-margin">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<h2>{{ $t('dashboard.LoginOverview') }}</h2>
|
||||
@@ -65,5 +65,16 @@ export default {
|
||||
border: 1px solid #d2d2d2;
|
||||
-webkit-box-shadow: inset 0 3px 5px rgba(0,0,0,.125);
|
||||
}
|
||||
@media print {
|
||||
.disabled-when-print{
|
||||
display: none;
|
||||
}
|
||||
.enabled-when-print{
|
||||
display: inherit !important;
|
||||
}
|
||||
.print-margin{
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-row :gutter="10">
|
||||
<el-col v-for="item of summaryItems" :key="item.title" :md="6" :sm="12">
|
||||
<el-col v-for="item of summaryItems" :key="item.title" :md="6" :sm="12" :xs="12">
|
||||
<SummaryCard :title="item.title" :right-side-label="item.rightSideLabel" :body="item.body" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
type: 'primary'
|
||||
},
|
||||
body: {
|
||||
route: `/terminal/session`,
|
||||
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
|
||||
count: this.counter.total_count_online_users,
|
||||
comment: 'Online users'
|
||||
}
|
||||
@@ -68,7 +68,7 @@ export default {
|
||||
type: 'danger'
|
||||
},
|
||||
body: {
|
||||
route: `/terminal/session`,
|
||||
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
|
||||
count: this.counter.total_count_online_sessions,
|
||||
comment: 'Online sessions'
|
||||
}
|
||||
@@ -95,4 +95,10 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
.el-col-24{
|
||||
width: 50% !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<el-col :md="8" :sm="12">
|
||||
<TopUser />
|
||||
</el-col>
|
||||
<el-col :md="8" :sm="12">
|
||||
<el-col :md="8" :sm="12" class="print-margin-top">
|
||||
<TopAssets />
|
||||
</el-col>
|
||||
<el-col :md="8" :sm="12">
|
||||
<Latest10Sessions class="card-item" />
|
||||
<Latest10Sessions class="card-item print-margin-top" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -28,5 +28,9 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@media print {
|
||||
.print-margin-top{
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -108,7 +108,7 @@ export default {
|
||||
hasLeftActions: false,
|
||||
hasImport: false,
|
||||
hasDatePicker: true,
|
||||
canExportSelected: false,
|
||||
canExportSelected: true,
|
||||
datePicker: {
|
||||
dateStart: dateFrom,
|
||||
dateEnd: dateTo
|
||||
|
||||
@@ -41,6 +41,17 @@ export default {
|
||||
Title() {
|
||||
return this.$t('route.Sessions')
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const params = this.$route.params
|
||||
switch (params.activeMenu) {
|
||||
case 'OnlineList':
|
||||
this.config.activeMenu = 'OnlineList'
|
||||
break
|
||||
case 'OfflineList':
|
||||
this.config.activeMenu = 'OfflineList'
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -55,14 +55,14 @@ export default {
|
||||
key: this.$t('sessions.remoteAddr'),
|
||||
value: this.terminalData.remote_addr
|
||||
},
|
||||
{
|
||||
key: this.$t('sessions.sshPort'),
|
||||
value: this.terminalData.ssh_port
|
||||
},
|
||||
{
|
||||
key: this.$t('sessions.httpPort'),
|
||||
value: this.terminalData.http_port
|
||||
},
|
||||
// {
|
||||
// key: this.$t('sessions.sshPort'),
|
||||
// value: this.terminalData.ssh_port
|
||||
// },
|
||||
// {
|
||||
// key: this.$t('sessions.httpPort'),
|
||||
// value: this.terminalData.http_port
|
||||
// },
|
||||
{
|
||||
key: this.$t('sessions.dateCreated'),
|
||||
value: this.terminalData.date_created
|
||||
|
||||
@@ -94,8 +94,8 @@ export default {
|
||||
url: '/api/v1/terminal/terminals/',
|
||||
columns: [
|
||||
'name', 'remote_addr', 'session_online',
|
||||
'state.session_active_count', 'state.system_cpu_load_1',
|
||||
'state.system_disk_used_percent', 'state.system_memory_used_percent',
|
||||
'stat.cpu_load',
|
||||
'stat.disk_used', 'stat.memory_used',
|
||||
'status_display',
|
||||
'is_active', 'is_alive', 'actions'
|
||||
],
|
||||
@@ -103,19 +103,15 @@ export default {
|
||||
name: {
|
||||
sortable: 'custom'
|
||||
},
|
||||
'state.session_active_count': {
|
||||
label: this.$t('sessions.sessionActiveCount'),
|
||||
width: '120px'
|
||||
},
|
||||
'state.system_cpu_load_1': {
|
||||
'stat.cpu_load': {
|
||||
label: this.$t('sessions.systemCpuLoad'),
|
||||
width: '120px'
|
||||
},
|
||||
'state.system_disk_used_percent': {
|
||||
'stat.disk_used': {
|
||||
label: this.$t('sessions.systemDiskUsedPercent'),
|
||||
width: '120px'
|
||||
},
|
||||
'state.system_memory_used_percent': {
|
||||
'stat.memory_used': {
|
||||
label: this.$t('sessions.systemMemoryUsedPercent'),
|
||||
width: '120px'
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:create-success-next-route="successUrl"
|
||||
:get-method="getMethod"
|
||||
:has-detail-in-msg="false"
|
||||
:on-perform-success="onPerformSuccess"
|
||||
/>
|
||||
</IBox>
|
||||
</template>
|
||||
@@ -23,7 +24,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
fields: [
|
||||
[this.$t('common.BasicInfo'), ['SITE_URL', 'USER_GUIDE_URL', 'FORGOT_PASSWORD_URL']]
|
||||
[this.$t('common.BasicInfo'), ['SITE_URL', 'USER_GUIDE_URL', 'FORGOT_PASSWORD_URL', 'GLOBAL_ORG_DISPLAY_NAME']]
|
||||
],
|
||||
successUrl: { name: 'Settings', params: { activeMenu: 'Basic' }},
|
||||
url: '/api/v1/settings/setting/?category=basic'
|
||||
@@ -32,6 +33,9 @@ export default {
|
||||
methods: {
|
||||
getMethod() {
|
||||
return 'put'
|
||||
},
|
||||
onPerformSuccess() {
|
||||
setTimeout(() => window.location.reload(), 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
<GenericCreateUpdateForm
|
||||
:fields="fields"
|
||||
:url="url"
|
||||
:perform-submit="performSubmit"
|
||||
:get-method="getMethod"
|
||||
:fields-meta="fieldsMeta"
|
||||
:more-buttons="moreButtons"
|
||||
:has-detail-in-msg="false"
|
||||
/>
|
||||
@@ -14,6 +16,7 @@
|
||||
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm'
|
||||
import { testEmailSetting } from '@/api/settings'
|
||||
import { IBox } from '@/components'
|
||||
import rules from '@/components/DataForm/rules'
|
||||
|
||||
export default {
|
||||
name: 'Email',
|
||||
@@ -51,6 +54,24 @@ export default {
|
||||
]
|
||||
]
|
||||
],
|
||||
fieldsMeta: {
|
||||
EMAIL_HOST_USER: {
|
||||
rules: [
|
||||
rules.EmailCheck,
|
||||
rules.Required
|
||||
]
|
||||
},
|
||||
EMAIL_FROM: {
|
||||
rules: [
|
||||
rules.EmailCheck
|
||||
]
|
||||
},
|
||||
EMAIL_RECIPIENT: {
|
||||
rules: [
|
||||
rules.EmailCheck
|
||||
]
|
||||
}
|
||||
},
|
||||
url: '/api/v1/settings/setting/?category=email',
|
||||
moreButtons: [
|
||||
{
|
||||
@@ -72,7 +93,19 @@ export default {
|
||||
methods: {
|
||||
getMethod() {
|
||||
return 'put'
|
||||
},
|
||||
performSubmit(validValues) {
|
||||
Object.keys(validValues).forEach(
|
||||
function(key) {
|
||||
if (validValues[key] === null) {
|
||||
delete validValues[key]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return this.$axios['put'](`/api/v1/settings/setting/?category=email`, validValues)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,10 +38,16 @@ export default {
|
||||
url: '/api/v1/users/users/',
|
||||
fieldsMeta: {
|
||||
password_strategy: {
|
||||
hidden: () => {
|
||||
return this.$route.params.id
|
||||
hidden: (formValue) => {
|
||||
return this.$route.params.id || formValue.source !== 'local'
|
||||
}
|
||||
},
|
||||
email: {
|
||||
rules: [
|
||||
rules.EmailCheck,
|
||||
rules.Required
|
||||
]
|
||||
},
|
||||
update_password: {
|
||||
label: this.$t('users.UpdatePassword'),
|
||||
type: 'checkbox',
|
||||
@@ -58,7 +64,7 @@ export default {
|
||||
if (formValue.password_strategy) {
|
||||
return false
|
||||
}
|
||||
return !formValue.update_password
|
||||
return !formValue.update_password || formValue.source !== 'local'
|
||||
},
|
||||
el: {
|
||||
required: false
|
||||
@@ -71,12 +77,12 @@ export default {
|
||||
if (formValue.set_public_key) {
|
||||
return true
|
||||
}
|
||||
return this.$route.meta.action !== 'update'
|
||||
return this.$route.meta.action !== 'update' || formValue.source !== 'local'
|
||||
}
|
||||
},
|
||||
public_key: {
|
||||
hidden: (formValue) => {
|
||||
return !formValue.set_public_key
|
||||
return !formValue.set_public_key || formValue.source !== 'local'
|
||||
}
|
||||
},
|
||||
role: {
|
||||
|
||||
@@ -65,7 +65,7 @@ export default {
|
||||
name: 'UserApplicationPermissionRules'
|
||||
},
|
||||
{
|
||||
title: '用户登录规则',
|
||||
title: this.$t('route.UserAclLists'),
|
||||
name: 'UserAclList'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -164,7 +164,7 @@ export default {
|
||||
{
|
||||
name: 'updateSelected',
|
||||
title: this.$t('common.updateSelected'),
|
||||
can: ({ selectedRows }) => selectedRows.length > 0,
|
||||
can: ({ selectedRows }) => selectedRows.length > 0 && !vm.currentOrgIsRoot,
|
||||
callback: ({ selectedRows, reloadTable }) => {
|
||||
vm.updateSelectedDialogSetting.dialogSetting.dialogVisible = true
|
||||
vm.updateSelectedDialogSetting.selectedRows = selectedRows
|
||||
@@ -187,7 +187,7 @@ export default {
|
||||
fieldsMeta: {
|
||||
groups: {
|
||||
label: this.$t('users.UserGroups'),
|
||||
hidden: () => false,
|
||||
hidden: () => vm.currentOrgIsRoot,
|
||||
el: {
|
||||
multiple: true,
|
||||
ajax: {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<script type="text/jsx">
|
||||
import GenericListTable from '@/layout/components/GenericListTable'
|
||||
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun, aws_china, aws_international, huaweicloud, qcloud, azure, azure_international, vmware } from '../const'
|
||||
import { ACCOUNT_PROVIDER_ATTRS_MAP, aliyun, aws_china, aws_international, huaweicloud, qcloud, azure, azure_international, vmware, nutanix } from '../const'
|
||||
import { BooleanFormatter, DetailFormatter } from '@/components/ListTable/formatters'
|
||||
|
||||
export default {
|
||||
@@ -109,6 +109,10 @@ export default {
|
||||
{
|
||||
name: vmware,
|
||||
title: ACCOUNT_PROVIDER_ATTRS_MAP[vmware].title
|
||||
},
|
||||
{
|
||||
name: nutanix,
|
||||
title: ACCOUNT_PROVIDER_ATTRS_MAP[nutanix].title
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ export default {
|
||||
// 更新获取链接
|
||||
if (params.id) {
|
||||
const form = await this.$refs.createUpdatePage.$refs.createUpdateForm.getFormValue()
|
||||
this.fieldsMeta.regions.el.ajax.url = `/api/v1/xpack/cloud/regions/?account_id=${form.account}`
|
||||
this.fieldsMeta.regions.el.ajax.url = form.account ? `/api/v1/xpack/cloud/regions/?account_id=${form.account}` : `/api/v1/xpack/cloud/regions/`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export const qcloud = 'qcloud'
|
||||
export const azure = 'azure'
|
||||
export const azure_international = 'azure_international'
|
||||
export const vmware = 'vmware'
|
||||
export const nutanix = 'nutanix'
|
||||
|
||||
export const ACCOUNT_PROVIDER_ATTRS_MAP = {
|
||||
[aliyun]: {
|
||||
@@ -49,5 +50,10 @@ export const ACCOUNT_PROVIDER_ATTRS_MAP = {
|
||||
name: vmware,
|
||||
title: 'VMware',
|
||||
attrs: ['host', 'port', 'username', 'password']
|
||||
},
|
||||
[nutanix]: {
|
||||
name: nutanix,
|
||||
title: 'Nutanix',
|
||||
attrs: ['access_key_id', 'access_key_secret', 'api_endpoint']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="font-size: 24px;font-weight: 300">
|
||||
<span v-if="type === 'omnidb'">{{ `OmniDB ( ${serviceData.total} )` }}</span>
|
||||
<span v-else-if="type === 'guacamole'">{{ `Guacamole ( ${serviceData.total} )` }}</span>
|
||||
<span v-else>{{ `KoKo ( ${serviceData.total} )` }}</span>
|
||||
<span>{{ componentName }} ( {{ componentMetric.total }} )</span>
|
||||
</div>
|
||||
<el-card class="box-card" shadow="never">
|
||||
<el-row :gutter="10">
|
||||
@@ -14,40 +12,40 @@
|
||||
<div
|
||||
class="progress-bar progress-bar-success"
|
||||
role="progressbar"
|
||||
:style="{'width':toPercent(serviceData.normal) }"
|
||||
:style="{'width':toPercent(componentMetric.normal) }"
|
||||
/>
|
||||
<div
|
||||
class="progress-bar progress-bar-warning"
|
||||
role="progressbar"
|
||||
:style="{'width':toPercent(serviceData.high) }"
|
||||
:style="{'width':toPercent(componentMetric.high) }"
|
||||
/>
|
||||
<div
|
||||
class="progress-bar progress-bar-danger"
|
||||
role="progressbar"
|
||||
:style="{'width':toPercent(serviceData.critical) }"
|
||||
:style="{'width':toPercent(componentMetric.critical) }"
|
||||
/>
|
||||
<div
|
||||
class="progress-bar progress-bar-offline"
|
||||
role="progressbar"
|
||||
:style="{'width':toPercent(serviceData.offline) }"
|
||||
:style="{'width':toPercent(componentMetric.offline) }"
|
||||
/>
|
||||
</div>
|
||||
<div style="display: flex;justify-content: space-around;font-size: 14px;">
|
||||
<span>
|
||||
<i class="el-icon-circle-check" style="color: #13CE66;" />
|
||||
{{ $t('xpack.NormalLoad') }}: {{ serviceData.normal }}
|
||||
{{ $t('xpack.NormalLoad') }}: {{ componentMetric.normal }}
|
||||
</span>
|
||||
<span>
|
||||
<i class="el-icon-bell" style="color: #E6A23C;" />
|
||||
{{ $t('xpack.HighLoad') }}: {{ serviceData.high }}
|
||||
{{ $t('xpack.HighLoad') }}: {{ componentMetric.high }}
|
||||
</span>
|
||||
<span>
|
||||
<i class="el-icon-message-solid" style="color: #FF4949;" />
|
||||
{{ $t('xpack.CriticalLoad') }}: {{ serviceData.critical }}
|
||||
{{ $t('xpack.CriticalLoad') }}: {{ componentMetric.critical }}
|
||||
</span>
|
||||
<span>
|
||||
<i class="el-icon-circle-close" style="color: #bfbaba;" />
|
||||
{{ $t('xpack.Offline') }}: {{ serviceData.offline }}
|
||||
{{ $t('xpack.Offline') }}: {{ componentMetric.offline }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,7 +54,7 @@
|
||||
<div style="height: 100%;width: 100%;padding-top: 8px;">
|
||||
<h2 style="text-align: center;font-weight: 350">{{ $t('dashboard.OnlineSessions') }}</h2>
|
||||
<div style="text-align: center;font-size: 30px;">
|
||||
{{ serviceData.session_active }}
|
||||
{{ componentMetric.session_active }}
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
@@ -70,7 +68,6 @@
|
||||
export default {
|
||||
name: 'MonitorCard',
|
||||
components: {
|
||||
|
||||
},
|
||||
props: {
|
||||
// koko/guacamole/omnidb/core
|
||||
@@ -78,29 +75,25 @@ export default {
|
||||
type: String,
|
||||
default: 'koko',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
baseUrl: `/api/v1/terminal/components/metrics/?type=`,
|
||||
serviceData: {
|
||||
|
||||
}
|
||||
},
|
||||
componentMetric: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.getServiceData()
|
||||
componentName() {
|
||||
const nameMapper = {
|
||||
koko: 'KoKo',
|
||||
guacamole: 'Guacamole',
|
||||
omnidb: 'OmniDB'
|
||||
}
|
||||
return nameMapper[this.componentMetric.type]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getServiceData() {
|
||||
const url = `${this.baseUrl}${this.type}`
|
||||
this.serviceData = await this.$axios.get(url)
|
||||
},
|
||||
toPercent(num) {
|
||||
return (Math.round(num / this.serviceData.total * 10000) / 100.00 + '%')// 小数点后两位百分比
|
||||
return (Math.round(num / this.componentMetric.total * 10000) / 100.00 + '%')// 小数点后两位百分比
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
<template>
|
||||
<Page>
|
||||
<el-row :gutter="40">
|
||||
<el-col :lg="12" :md="24">
|
||||
<MonitorCard type="koko" class="monitorCard" />
|
||||
</el-col>
|
||||
<el-col :lg="12" :md="24">
|
||||
<MonitorCard type="guacamole" class="monitorCard" />
|
||||
</el-col>
|
||||
<el-col :lg="12" :md="24">
|
||||
<MonitorCard type="omnidb" class="monitorCard" />
|
||||
<el-row v-if="loaded" :gutter="40">
|
||||
<el-col v-for="metric of metricsData" :key="metric.type" :lg="12" :md="24">
|
||||
<MonitorCard :type="metric.type" :component-metric="metric" class="monitorCard" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</Page>
|
||||
</template>lg
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Page from '@/layout/components/Page/index'
|
||||
@@ -26,9 +20,24 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
metricsData: [],
|
||||
loaded: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
mounted() {
|
||||
this.getMetricsData()
|
||||
},
|
||||
methods: {
|
||||
async getMetricsData() {
|
||||
const url = '/api/v1/terminal/components/metrics/'
|
||||
this.$axios.get(url).then((data) => {
|
||||
this.metricsData = data
|
||||
}).finally(() => {
|
||||
this.loaded = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
'use strict'
|
||||
const path = require('path')
|
||||
const defaultSettings = require('./src/settings.js')
|
||||
const CompressionWebpackPlugin = require('compression-webpack-plugin')
|
||||
const productionGzipExtensions = /\.(js|css|json|txt|ico|svg)(\?.*)?$/i
|
||||
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, dir)
|
||||
@@ -82,7 +85,15 @@ module.exports = {
|
||||
alias: {
|
||||
'@': resolve('src')
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new CompressionWebpackPlugin({
|
||||
algorithm: 'gzip',
|
||||
test: productionGzipExtensions, // 处理所有匹配此 {RegExp} 的资源
|
||||
threshold: 10240, // 只处理比这个值大的资源。按字节计算(楼主设置10K以上进行压缩)
|
||||
minRatio: 0.8 // 只有压缩率比这个值小的资源才会被处理
|
||||
})
|
||||
]
|
||||
},
|
||||
chainWebpack(config) {
|
||||
// it can improve the speed of the first screen, it is recommended to turn on preload
|
||||
|
||||
Reference in New Issue
Block a user