mirror of
https://github.com/jumpserver/lina.git
synced 2026-01-14 11:55:34 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
607eec6c44 | ||
|
|
28daf41fb5 | ||
|
|
e45720af1b |
@@ -1,8 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
]
|
||||
}
|
||||
|
||||
10
nginx.conf
10
nginx.conf
@@ -1,16 +1,6 @@
|
||||
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/;
|
||||
|
||||
12
package.json
12
package.json
@@ -15,23 +15,18 @@
|
||||
"test:ci": "npm run lint && npm run test:unit",
|
||||
"svgo": "svgo -f src/icons/svg --config=src/icas/svgo.yml",
|
||||
"vue-i18n-extract": "vue-i18n-extract",
|
||||
"vue-i18n-report": "vue-i18n-extract report -v './src/**/*.?(js|vue)' -l './src/i18n/langs/**/*.json'",
|
||||
"vue-i18n-report-json": "vue-i18n-extract report -v './src/**/*.?(js|vue)' -l './src/i18n/langs/**/*.json' -o /tmp/abc.json",
|
||||
"vue-i18n-report-add-miss": "vue-i18n-extract report -v './src/**/*.?(js|vue)' -l './src/i18n/langs/**/*.json' -a"
|
||||
"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.21.1",
|
||||
"axios": "0.18.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",
|
||||
"krry-transfer": "^1.7.3",
|
||||
"less": "^3.10.3",
|
||||
"less-loader": "^5.0.0",
|
||||
"lodash": "^4.17.15",
|
||||
@@ -46,10 +41,8 @@
|
||||
"lodash.set": "^4.3.2",
|
||||
"lodash.topairs": "^4.3.0",
|
||||
"lodash.values": "^4.3.0",
|
||||
"moment": "^2.29.1",
|
||||
"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",
|
||||
@@ -80,7 +73,6 @@
|
||||
"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",
|
||||
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
@@ -8,6 +8,7 @@
|
||||
<meta http-equiv="Cache-control" content="no-cache">
|
||||
<meta http-equiv="Cache" content="no-cache">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= webpackConfig.name %></title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getOrgDetail(oid) {
|
||||
return request({
|
||||
url: `/api/v1/orgs/orgs/current/?oid=${oid}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getCurrentOrg() {
|
||||
return request({
|
||||
url: `/api/v1/orgs/orgs/current/`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -57,26 +57,6 @@ export function TestReplayStorage(id) {
|
||||
})
|
||||
}
|
||||
|
||||
function SetToDefaultStorage(url) {
|
||||
return request({
|
||||
url: url,
|
||||
method: 'patch',
|
||||
data: { 'is_default': true }
|
||||
})
|
||||
}
|
||||
|
||||
export function SetToDefaultCommandStorage(id) {
|
||||
return SetToDefaultStorage(
|
||||
`/api/v1/terminal/command-storages/${id}/`,
|
||||
)
|
||||
}
|
||||
|
||||
export function SetToDefaultReplayStorage(id) {
|
||||
return SetToDefaultStorage(
|
||||
`/api/v1/terminal/replay-storages/${id}/`,
|
||||
)
|
||||
}
|
||||
|
||||
export function getReplayStorage(id) {
|
||||
return request({
|
||||
url: `/api/v1/terminal/replay-storages/${id}/`,
|
||||
|
||||
BIN
src/assets/img/logo-text.png
Normal file
BIN
src/assets/img/logo-text.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
BIN
src/assets/img/logo.png
Normal file
BIN
src/assets/img/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -1,83 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<MFAVerifyDialog
|
||||
@MFAVerifyDone="getAuthInfo"
|
||||
@MFAVerifyCancel="exit"
|
||||
/>
|
||||
<Dialog
|
||||
:title="dialogTitle"
|
||||
:show-confirm="false"
|
||||
:show-cancel="false"
|
||||
:destroy-on-close="true"
|
||||
:width="'50'"
|
||||
:visible.sync="showAuthInfo"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<div>
|
||||
<el-form label-position="right" label-width="80px" :model="authInfo">
|
||||
<el-form-item :label="this.$t('assets.Hostname')">
|
||||
<el-input v-model="account.hostname" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="this.$t('assets.Username')">
|
||||
<el-input v-model="account['username']" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="this.$t('assets.Password')">
|
||||
<el-input v-model="authInfo.password" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item :label="this.$t('assets.SSHKey')">
|
||||
<el-input v-model="authInfo['private_key']" type="password" show-password />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
import MFAVerifyDialog from '@/components/MFAVerifyDialog'
|
||||
export default {
|
||||
name: 'ShowSecretInfo',
|
||||
components: {
|
||||
Dialog,
|
||||
MFAVerifyDialog
|
||||
},
|
||||
props: {
|
||||
account: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogTitle: this.$t('common.ViewSecret'),
|
||||
authInfo: {},
|
||||
showAuthInfo: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getAuthInfo()
|
||||
},
|
||||
methods: {
|
||||
getAuthInfo() {
|
||||
const url = `/api/v1/assets/account-secrets/${this.account.id}/`
|
||||
this.$axios.get(url, { disableFlashErrorMsg: true }).then(resp => {
|
||||
this.authInfo = resp
|
||||
this.showAuthInfo = true
|
||||
})
|
||||
},
|
||||
exit() {
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,91 +0,0 @@
|
||||
<template>
|
||||
<Dialog
|
||||
width="50"
|
||||
:title="this.$t('assets.UpdateAssetUserToken')"
|
||||
:destroy-on-close="true"
|
||||
v-bind="$attrs"
|
||||
@confirm="handleConfirm()"
|
||||
@cancel="handleCancel()"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<el-form label-position="right" label-width="80px">
|
||||
<el-form-item :label="this.$t('assets.Hostname')">
|
||||
<el-input v-model="account.hostname" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="this.$t('assets.Username')">
|
||||
<el-input v-model="account['username']" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="this.$t('assets.Password')">
|
||||
<el-input v-model="authInfo.password" type="password" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="this.$t('assets.SSHKey')">
|
||||
<input type="file" @change="onPrivateKeyLoaded">
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
export default {
|
||||
name: 'UpdateSecretInfo',
|
||||
components: {
|
||||
Dialog
|
||||
},
|
||||
props: {
|
||||
account: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
authInfo: {
|
||||
password: '',
|
||||
private_key: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleConfirm() {
|
||||
const data = {}
|
||||
if (this.authInfo.password !== '') {
|
||||
data.password = this.authInfo.password
|
||||
}
|
||||
if (this.authInfo.private_key !== '') {
|
||||
data.private_key = this.authInfo.private_key
|
||||
}
|
||||
this.$axios.patch(
|
||||
`/api/v1/assets/accounts/${this.account.id}/`,
|
||||
data
|
||||
).then(res => {
|
||||
this.authInfo = { password: '', private_key: '' }
|
||||
this.$message.success(this.$tc('common.updateSuccessMsg'))
|
||||
this.$emit('updateAuthDone', res)
|
||||
this.$emit('update:visible', false)
|
||||
}).catch(err => {
|
||||
this.$message.error(this.$tc('common.updateErrorMsg' + ' ' + err))
|
||||
this.$emit('update:visible', false)
|
||||
})
|
||||
},
|
||||
handleCancel() {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
onPrivateKeyLoaded(e) {
|
||||
const vm = this
|
||||
// TODO 校验文件类型
|
||||
const reader = new FileReader()
|
||||
reader.onload = function() {
|
||||
vm.authInfo.private_key = this.result
|
||||
}
|
||||
reader.readAsText(
|
||||
e.target.files[0]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,31 +0,0 @@
|
||||
import { ChoicesFormatter } from '@/components/TableFormatters'
|
||||
import { toSafeLocalDateStr } from '@/utils/common'
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
export const connectivityMeta = {
|
||||
label: i18n.t('assets.Reachable'),
|
||||
formatter: ChoicesFormatter,
|
||||
formatterArgs: {
|
||||
iconChoices: {
|
||||
ok: 'fa-check text-primary',
|
||||
failed: 'fa-times text-danger',
|
||||
unknown: 'fa-circle text-warning'
|
||||
},
|
||||
hasTips: true,
|
||||
getTips: ({ row, cellValue }) => {
|
||||
const mapper = {
|
||||
'ok': i18n.tc('assets.Reachable'),
|
||||
'failed': i18n.tc('assets.Unreachable'),
|
||||
'unknown': i18n.tc('assets.Unknown')
|
||||
}
|
||||
let tips = mapper[cellValue]
|
||||
if (row['date_verified']) {
|
||||
const datetime = toSafeLocalDateStr(row['date_verified'])
|
||||
tips += '<br> ' + datetime
|
||||
}
|
||||
return tips
|
||||
}
|
||||
},
|
||||
width: '90px',
|
||||
align: 'center'
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
|
||||
<ShowSecretInfo v-if="showViewSecretDialog" :visible.sync="showViewSecretDialog" :account="account" />
|
||||
<UpdateSecretInfo :visible.sync="showUpdateSecretDialog" :account="account" @updateAuthDone="onUpdateAuthDone" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListTable from '@/components/ListTable/index'
|
||||
import { ActionsFormatter, DetailFormatter, DisplayFormatter } from '@/components/TableFormatters'
|
||||
import ShowSecretInfo from './ShowSecretInfo'
|
||||
import UpdateSecretInfo from './UpdateSecretInfo'
|
||||
import { connectivityMeta } from './const'
|
||||
|
||||
export default {
|
||||
name: 'Detail',
|
||||
components: {
|
||||
ListTable,
|
||||
UpdateSecretInfo,
|
||||
ShowSecretInfo
|
||||
},
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
exportUrl: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.url.replace('/assets/accounts/', '/assets/account-secrets/')
|
||||
}
|
||||
},
|
||||
hasLeftActions: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
otherActions: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
hasClone: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showViewSecretDialog: false,
|
||||
showUpdateSecretDialog: false,
|
||||
account: {},
|
||||
tableConfig: {
|
||||
url: this.url,
|
||||
columns: [
|
||||
'hostname', 'ip', 'username', 'version', 'connectivity',
|
||||
'systemuser', 'date_created', 'date_updated', 'actions'
|
||||
],
|
||||
columnsShow: {
|
||||
min: ['username', 'actions'],
|
||||
default: ['hostname', 'ip', 'username', 'version', 'actions']
|
||||
},
|
||||
columnsMeta: {
|
||||
hostname: {
|
||||
prop: 'hostname',
|
||||
label: this.$t('assets.Hostname'),
|
||||
showOverflowTooltip: true,
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
getRoute({ row }) {
|
||||
return {
|
||||
name: 'AssetDetail',
|
||||
params: { id: row.asset }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ip: {
|
||||
width: '120px'
|
||||
},
|
||||
username: {
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
systemuser: {
|
||||
formatter: DisplayFormatter
|
||||
},
|
||||
version: {
|
||||
width: '70px'
|
||||
},
|
||||
connectivity: connectivityMeta,
|
||||
actions: {
|
||||
formatter: ActionsFormatter,
|
||||
formatterArgs: {
|
||||
hasUpdate: false, // can set function(row, value)
|
||||
hasDelete: false, // can set function(row, value)
|
||||
hasClone: this.hasClone,
|
||||
moreActionsTitle: this.$t('common.More'),
|
||||
extraActions: [
|
||||
{
|
||||
name: 'View',
|
||||
title: this.$t('common.View'),
|
||||
type: 'primary',
|
||||
callback: function({ row }) {
|
||||
this.account = row
|
||||
this.showViewSecretDialog = true
|
||||
}.bind(this)
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
title: this.$t('common.Delete'),
|
||||
type: 'primary',
|
||||
callback: ({ row }) => {
|
||||
this.$axios.delete(`/api/v1/assets/accounts/${row.id}/`).then(() => {
|
||||
this.$message.success(this.$tc('common.deleteSuccessMsg'))
|
||||
this.$refs.ListTable.reloadTable()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test',
|
||||
title: this.$t('common.Test'),
|
||||
callback: ({ row }) => {
|
||||
this.$axios.post(
|
||||
`/api/v1/assets/accounts/${row.id}/verify/`,
|
||||
{ action: 'test' }
|
||||
).then(res => {
|
||||
window.open(`/#/ops/celery/task/${res.task}/log/`, '', 'width=900,height=600')
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
title: this.$t('common.Update'),
|
||||
can: !this.$store.getters.currentOrgIsRoot,
|
||||
callback: function({ row }) {
|
||||
this.account = row
|
||||
this.showUpdateSecretDialog = true
|
||||
}.bind(this)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: this.hasLeftActions,
|
||||
hasMoreActions: false,
|
||||
hasImport: this.hasImport,
|
||||
hasExport: this.hasExport,
|
||||
exportOptions: {
|
||||
url: this.exportUrl,
|
||||
mfaVerifyRequired: true
|
||||
},
|
||||
hasSearch: true
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
url(iNew) {
|
||||
this.$set(this.tableConfig, 'url', iNew)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.otherActions) {
|
||||
const actionColumn = this.tableConfig.columns[this.tableConfig.columns.length - 1]
|
||||
for (const item of this.otherActions) {
|
||||
actionColumn.formatterArgs.extraActions.push(item)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onUpdateAuthDone(account) {
|
||||
Object.assign(this.account, account)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='less' scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,15 +1,40 @@
|
||||
<template>
|
||||
<DataActions :actions="iActions" v-bind="$attrs" />
|
||||
<div :class="grouped ? 'el-button-group' : ''">
|
||||
<el-button v-for="item in iActions" :key="item.name" :size="size" v-bind="item" @click="handleClick(item.name)">
|
||||
<el-tooltip v-if="item.tip" effect="dark" :content="item.tip" placement="top">
|
||||
<i v-if="item.fa" :class="'fa ' + item.fa" />{{ item.title }}
|
||||
</el-tooltip>
|
||||
<span v-else>
|
||||
<i v-if="item.fa" :class="'fa ' + item.fa" />{{ item.title }}
|
||||
</span>
|
||||
</el-button>
|
||||
<el-dropdown v-if="iMoreActions.length > 0" trigger="click" :placement="moreActionsPlacement" @command="handleClick">
|
||||
<el-button :size="size" :type="moreActionsType" class="btn-more-actions">
|
||||
{{ iMoreActionsTitle }}<i class="el-icon-arrow-down el-icon--right" />
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item v-for="item in iMoreActions" :key="item.name" :command="item.name" v-bind="item" @click="handleClick(item.name)">{{ item.title }} </el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DataActions from '@/components/DataActions'
|
||||
export default {
|
||||
name: 'ActionsGroup',
|
||||
components: {
|
||||
DataActions
|
||||
},
|
||||
props: {
|
||||
grouped: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'small'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
actions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
@@ -36,22 +61,79 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
iActions() {
|
||||
const actions = [...this.actions]
|
||||
if (this.iMoreAction && this.iMoreAction.dropdown.length > 0) {
|
||||
actions.push(this.iMoreAction)
|
||||
return this.cleanActions(this.actions)
|
||||
},
|
||||
iMoreActions() {
|
||||
return this.cleanActions(this.moreActions)
|
||||
},
|
||||
totalActions() {
|
||||
return [...this.actions, ...this.moreActions]
|
||||
},
|
||||
totalNamedActions() {
|
||||
const actions = {}
|
||||
for (const action of this.totalActions) {
|
||||
if (!action || !action.hasOwnProperty('name')) {
|
||||
continue
|
||||
}
|
||||
actions[action.name] = action
|
||||
}
|
||||
return actions
|
||||
},
|
||||
iMoreAction() {
|
||||
return {
|
||||
name: 'moreActions',
|
||||
title: this.iMoreActionsTitle,
|
||||
dropdown: this.moreActions || []
|
||||
}
|
||||
},
|
||||
iMoreActionsTitle() {
|
||||
return this.moreActionsTitle || this.$t('common.MoreActions')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick(item) {
|
||||
const action = this.totalNamedActions[item]
|
||||
if (action && action.callback) {
|
||||
action.callback(action)
|
||||
} else {
|
||||
this.$log.debug('No callback found')
|
||||
}
|
||||
this.$emit('actionClick', item)
|
||||
},
|
||||
checkItem(item, attr, defaults) {
|
||||
if (!item) {
|
||||
return true
|
||||
}
|
||||
let ok = item[attr]
|
||||
if (ok && typeof ok === 'function') {
|
||||
ok = ok(item)
|
||||
} else if (ok == null) {
|
||||
ok = defaults === undefined ? true : defaults
|
||||
}
|
||||
return ok
|
||||
},
|
||||
cleanActions(actions) {
|
||||
const cleanedActions = []
|
||||
const cloneActions = _.cloneDeep(actions)
|
||||
for (const v of cloneActions) {
|
||||
if (!v) {
|
||||
continue
|
||||
}
|
||||
const action = Object.assign({}, v)
|
||||
// 是否拥有这个action
|
||||
const has = this.checkItem(action, 'has')
|
||||
delete action['has']
|
||||
if (!has) {
|
||||
continue
|
||||
}
|
||||
// 是否有分割线
|
||||
const divided = this.checkItem(action, 'divided', false)
|
||||
delete action['divided']
|
||||
action.divided = divided
|
||||
|
||||
// 是否是disabled
|
||||
const can = this.checkItem(action, 'can')
|
||||
delete action['can']
|
||||
action.disabled = !can
|
||||
cleanedActions.push(action)
|
||||
// 删掉callback,避免前台看到
|
||||
delete action['callback']
|
||||
}
|
||||
return cleanedActions
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<table style="width: 100%">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<AssetSelect ref="assetSelect" :disabled="disabled" :can-select="canSelect" />
|
||||
<AssetSelect ref="assetSelect" :can-select="canSelect" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<el-button :type="type" size="small" :disabled="disabled" @click="addObjects">{{ $t('common.Add') }}</el-button>
|
||||
<el-button :type="type" size="small" @click="addObjects">{{ $t('common.Add') }}</el-button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -38,10 +38,6 @@ export default {
|
||||
type: String,
|
||||
default: 'primary'
|
||||
},
|
||||
disabled: {
|
||||
type: [Boolean, Function],
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: [Array, Number, String],
|
||||
default: () => []
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
|
||||
<script>
|
||||
import TreeTable from '@/components/TreeTable'
|
||||
import { DetailFormatter } from '@/components/TableFormatters'
|
||||
import Select2 from '@/components/FormFields/Select2'
|
||||
import { DetailFormatter } from '@/components/ListTable/formatters'
|
||||
import Select2 from '@/components/Select2'
|
||||
import Dialog from '@/components/Dialog'
|
||||
|
||||
export default {
|
||||
@@ -40,10 +40,6 @@ export default {
|
||||
default(row, index) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: [Boolean, Function],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
364
src/components/AssetUserTable/index.vue
Normal file
364
src/components/AssetUserTable/index.vue
Normal file
@@ -0,0 +1,364 @@
|
||||
<template><div>
|
||||
<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="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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import ListTable from '@/components/ListTable/index'
|
||||
import Dialog from '@/components/Dialog'
|
||||
import { ActionsFormatter, DateFormatter } from '@/components/ListTable/formatters'
|
||||
|
||||
export default {
|
||||
name: 'Detail',
|
||||
components: {
|
||||
ListTable,
|
||||
Dialog
|
||||
},
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
hasLeftActions: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
otherActions: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
handleExport: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
handleImport: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
hasImport: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
hasExport: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
MFAConfirmed: false,
|
||||
MFAInput: '',
|
||||
MFAInfo: {
|
||||
asset: '',
|
||||
username: '',
|
||||
hostname: '',
|
||||
password: ''
|
||||
},
|
||||
showDialog: false,
|
||||
showMFADialog: false,
|
||||
dialogInfo: {
|
||||
asset: '',
|
||||
username: '',
|
||||
hostname: '',
|
||||
password: '',
|
||||
key: ''
|
||||
},
|
||||
tableConfig: {
|
||||
url: this.url,
|
||||
columns: [
|
||||
{
|
||||
prop: 'hostname',
|
||||
label: this.$t('assets.Hostname'),
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'ip',
|
||||
label: this.$t('assets.ip'),
|
||||
width: '120px'
|
||||
},
|
||||
{
|
||||
prop: 'username',
|
||||
label: this.$t('assets.Username'),
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'version',
|
||||
label: this.$t('assets.Version'),
|
||||
width: '70px'
|
||||
},
|
||||
{
|
||||
prop: 'date_created',
|
||||
label: this.$t('assets.date_joined'),
|
||||
formatter: DateFormatter
|
||||
},
|
||||
{
|
||||
prop: 'id',
|
||||
label: this.$t('common.Action'),
|
||||
align: 'center',
|
||||
width: 150,
|
||||
formatter: ActionsFormatter,
|
||||
formatterArgs: {
|
||||
hasUpdate: false, // can set function(row, value)
|
||||
hasDelete: false, // can set function(row, value)
|
||||
moreActionsTitle: this.$t('common.More'),
|
||||
extraActions: [
|
||||
{
|
||||
name: 'View',
|
||||
title: this.$t('common.View'),
|
||||
type: 'primary',
|
||||
callback: function(val) {
|
||||
this.MFAInfo.asset = val.cellValue
|
||||
if (!this.needMFAVerify) {
|
||||
this.showMFADialog = true
|
||||
this.MFAConfirmed = true
|
||||
this.$axios.get(`/api/v1/assets/asset-user-auth-infos/${this.MFAInfo.asset}/`).then(res => {
|
||||
this.MFAConfirmed = true
|
||||
this.MFAInfo.hostname = res.hostname
|
||||
this.MFAInfo.password = res.password
|
||||
this.MFAInfo.username = res.username
|
||||
})
|
||||
} else {
|
||||
this.showMFADialog = true
|
||||
}
|
||||
}.bind(this)
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
title: this.$t('common.Delete'),
|
||||
type: 'primary',
|
||||
callback: (val) => {
|
||||
this.$axios.delete(`/api/v1/assets/asset-users/${val.cellValue}/`).then(() => {
|
||||
this.$message.success(this.$t('common.deleteSuccessMsg'))
|
||||
this.$refs.ListTable.reloadTable()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test',
|
||||
title: this.$t('common.Test'),
|
||||
callback: (val) => {
|
||||
this.$axios.post(
|
||||
`/api/v1/assets/asset-users/tasks/?id=${val.cellValue}`,
|
||||
{ action: 'test' }
|
||||
).then(res => {
|
||||
window.open(`/#/ops/celery/task/${res.task}/log/`, '', 'width=900,height=600')
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
title: this.$t('common.Update'),
|
||||
callback: function(val) {
|
||||
this.showDialog = true
|
||||
this.dialogInfo.asset = val.row.asset
|
||||
this.dialogInfo.hostname = val.row.hostname
|
||||
this.dialogInfo.username = val.row.username
|
||||
}.bind(this)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
extraQuery: {
|
||||
latest: 1
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: this.hasLeftActions,
|
||||
hasBulkDelete: false,
|
||||
hasImport: this.hasImport,
|
||||
hasExport: this.hasExport,
|
||||
hasSearch: true,
|
||||
searchConfig: {
|
||||
options: [
|
||||
{
|
||||
label: this.$t('assets.OnlyLatestVersion'),
|
||||
value: 'latest',
|
||||
children: [
|
||||
{
|
||||
label: this.$t('common.Yes'),
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: this.$t('common.No'),
|
||||
value: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'MFA_TTl',
|
||||
'MFAVerifyAt',
|
||||
'publicSettings'
|
||||
]),
|
||||
needMFAVerify() {
|
||||
if (!this.publicSettings.SECURITY_VIEW_AUTH_NEED_MFA) {
|
||||
return false
|
||||
}
|
||||
const ttl = this.publicSettings.SECURITY_MFA_VERIFY_TTL
|
||||
const now = new Date()
|
||||
return !(this.MFAVerifyAt && (now - this.MFAVerifyAt) < ttl * 1000)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
url(iNew) {
|
||||
this.$set(this.tableConfig, 'url', iNew)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.otherActions) {
|
||||
const actionColumn = this.tableConfig.columns[this.tableConfig.columns.length - 1]
|
||||
for (const item of this.otherActions) {
|
||||
actionColumn.formatterArgs.extraActions.push(item)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.handleExport) {
|
||||
this.headerActions.handleExport = this.handleExport
|
||||
}
|
||||
if (this.handleImport) {
|
||||
this.headerActions.handleImport = this.handleImport
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
MFAConfirm() {
|
||||
if (this.MFAInput.length !== 6) {
|
||||
return this.$message.error(this.$t('common.MFAErrorMsg'))
|
||||
}
|
||||
this.$axios.post(
|
||||
`/api/v1/authentication/otp/verify/`, {
|
||||
code: this.MFAInput
|
||||
}
|
||||
).then(
|
||||
res => {
|
||||
this.$store.dispatch('users/setMFAVerify')
|
||||
this.$axios.get(`/api/v1/assets/asset-user-auth-infos/${this.MFAInfo.asset}/`).then(res => {
|
||||
this.MFAConfirmed = true
|
||||
this.MFAInfo.hostname = res.hostname
|
||||
this.MFAInfo.password = res.password
|
||||
this.MFAInfo.username = res.username
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
handleMFAConfirm() {
|
||||
this.MFAInfo = {
|
||||
asset: '',
|
||||
username: '',
|
||||
hostname: '',
|
||||
password: ''
|
||||
}
|
||||
this.MFAInput = ''
|
||||
this.showMFADialog = false
|
||||
this.MFAConfirmed = false
|
||||
},
|
||||
handleCancel() {
|
||||
this.dialogInfo = {
|
||||
asset: '',
|
||||
username: '',
|
||||
hostname: '',
|
||||
password: '',
|
||||
key: ''
|
||||
}
|
||||
this.showDialog = false
|
||||
this.$refs.ListTable.reloadTable()
|
||||
},
|
||||
Onchange(e) {
|
||||
const vm = this
|
||||
// TODO 校验文件类型
|
||||
const reader = new FileReader()
|
||||
reader.onload = function() {
|
||||
vm.dialogInfo.key = this.result
|
||||
}
|
||||
reader.readAsText(
|
||||
e.target.files[0]
|
||||
)
|
||||
},
|
||||
handleConfirm() {
|
||||
const data = {
|
||||
asset: this.dialogInfo.asset,
|
||||
username: this.dialogInfo.username
|
||||
}
|
||||
if (this.dialogInfo.password !== '') {
|
||||
data.password = this.dialogInfo.password
|
||||
}
|
||||
if (this.dialogInfo.key !== '') {
|
||||
data.key = this.dialogInfo.key
|
||||
}
|
||||
this.$axios.post(
|
||||
`/api/v1/assets/asset-users/`,
|
||||
data
|
||||
).then(res => {
|
||||
this.$message.success(this.$t('common.updateSuccessMsg'))
|
||||
}).catch(err => {
|
||||
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + err))
|
||||
})
|
||||
this.dialogInfo = {
|
||||
asset: '',
|
||||
username: '',
|
||||
hostname: '',
|
||||
password: '',
|
||||
key: ''
|
||||
}
|
||||
this.showDialog = false
|
||||
this.$refs.ListTable.reloadTable()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='less' scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,81 +0,0 @@
|
||||
<template>
|
||||
<DataForm
|
||||
:fields="iFields"
|
||||
:form="value"
|
||||
style="margin-left: -26%;margin-right: -6%"
|
||||
v-bind="kwargs"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DataForm from '@/components/DataForm'
|
||||
|
||||
export default {
|
||||
name: 'NestedField',
|
||||
components: {
|
||||
DataForm
|
||||
},
|
||||
props: {
|
||||
fields: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
errors: {
|
||||
type: [Object, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
kwargs: {
|
||||
hasReset: false,
|
||||
hasSaveContinue: false,
|
||||
defaultButton: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iFields() {
|
||||
const fields = this.fields
|
||||
if (this.errors && typeof this.errors === 'object') {
|
||||
// eslint-disable-next-line prefer-const
|
||||
for (let [name, error] of Object.entries(this.errors)) {
|
||||
const field = fields.find((v) => v.prop === name)
|
||||
if (!field) {
|
||||
continue
|
||||
}
|
||||
this.$log.debug(`${name}: ${error}`)
|
||||
if (typeof error === 'object' && !Array.isArray(error)) {
|
||||
error = this.objectToString(error)
|
||||
}
|
||||
field.attrs.error = error.toString()
|
||||
}
|
||||
}
|
||||
this.$log.debug('Fields change: ', fields, this.errors)
|
||||
return fields
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
objectToString(obj) {
|
||||
let data = ''
|
||||
// eslint-disable-next-line prefer-const
|
||||
for (let [key, value] of Object.entries(obj)) {
|
||||
if (typeof value === 'object') {
|
||||
value = this.objectToString(value)
|
||||
}
|
||||
data += ` ${key}: ${value} `
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,13 +1,15 @@
|
||||
<template>
|
||||
<DataForm ref="dataForm" v-loading="loading" :fields="totalFields" :form="iForm" v-bind="$attrs" v-on="$listeners">
|
||||
<FormGroupHeader v-for="(group, i) in groups" :slot="'id:'+group.name" :key="'group-'+group.name" :title="group.title" :line="i !== 0" />
|
||||
<DataForm ref="dataForm" v-loading="loading" :fields="totalFields" v-bind="$attrs" v-on="$listeners">
|
||||
<FormGroupHeader v-for="(group, i) in groups" :slot="'id:'+group.name" :key="'group-'+group.name" :title="group.title" :line="i != 0" />
|
||||
</DataForm>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DataForm from '../DataForm'
|
||||
import FormGroupHeader from '@/components/FormGroupHeader'
|
||||
import { FormFieldGenerator } from '@/components/AutoDataForm/utils'
|
||||
// import { optionUrlMeta } from '@/api/common'
|
||||
import rules from '@/components/DataForm/rules'
|
||||
import Select2 from '@/components/Select2'
|
||||
export default {
|
||||
name: 'AutoDataForm',
|
||||
components: {
|
||||
@@ -29,10 +31,6 @@ export default {
|
||||
return []
|
||||
}
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
fieldsMeta: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
@@ -40,65 +38,171 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
remoteMeta: {},
|
||||
meta: {},
|
||||
totalFields: [],
|
||||
loading: true,
|
||||
groups: [],
|
||||
iForm: this.form
|
||||
groups: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.optionUrlMetaAndGenerateColumns()
|
||||
this.optionUrlMeta()
|
||||
},
|
||||
methods: {
|
||||
optionUrlMetaAndGenerateColumns() {
|
||||
optionUrlMeta() {
|
||||
this.$store.dispatch('common/getUrlMeta', { url: this.url }).then(data => {
|
||||
this.remoteMeta = data.actions[this.method.toUpperCase()] || {}
|
||||
this.meta = data.actions[this.method.toUpperCase()] || {}
|
||||
this.generateColumns()
|
||||
this.cleanFormValue()
|
||||
}).catch(err => {
|
||||
this.$log.error(err)
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
generateColumns() {
|
||||
const generator = new FormFieldGenerator()
|
||||
this.totalFields = generator.generateFields(this.fields, this.fieldsMeta, this.remoteMeta)
|
||||
this.groups = generator.groups
|
||||
this.$log.debug('Total fields: ', this.totalFields)
|
||||
},
|
||||
_cleanFormValue(form, remoteMeta) {
|
||||
for (const [k, v] of Object.entries(remoteMeta)) {
|
||||
if (v.default === undefined) {
|
||||
continue
|
||||
}
|
||||
const valueSet = form[k]
|
||||
if (valueSet !== undefined) {
|
||||
continue
|
||||
}
|
||||
if (v.type === 'nested object' && typeof valueSet === 'object') {
|
||||
this._cleanFormValue(valueSet, v.children)
|
||||
}
|
||||
form[k] = v.default
|
||||
generateFieldByType(type, field, fieldMeta) {
|
||||
switch (type) {
|
||||
case 'choice':
|
||||
type = 'radio-group'
|
||||
if (!fieldMeta.read_only) {
|
||||
field.options = fieldMeta.choices.map(v => {
|
||||
return { label: v.display_name, value: v.value }
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'datetime':
|
||||
type = 'date-picker'
|
||||
field.el = {
|
||||
type: 'datetime'
|
||||
}
|
||||
break
|
||||
case 'field':
|
||||
type = ''
|
||||
field.component = Select2
|
||||
if (fieldMeta.required) {
|
||||
field.el.clearable = false
|
||||
}
|
||||
break
|
||||
case 'string':
|
||||
type = 'input'
|
||||
if (!fieldMeta.max_length) {
|
||||
field.el.type = 'textarea'
|
||||
field.el.rows = 3
|
||||
}
|
||||
break
|
||||
default:
|
||||
type = 'input'
|
||||
break
|
||||
}
|
||||
if (type === 'radio-group') {
|
||||
if (!fieldMeta.read_only) {
|
||||
const options = fieldMeta.choices.map(v => {
|
||||
return { label: v.display_name, value: v.value }
|
||||
})
|
||||
if (options.length > 4) {
|
||||
type = 'select'
|
||||
field.el.filterable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
field.type = type
|
||||
return field
|
||||
},
|
||||
cleanFormValue() {
|
||||
this._cleanFormValue(this.iForm, this.remoteMeta)
|
||||
generateFieldByName(name, field) {
|
||||
switch (name) {
|
||||
case 'email':
|
||||
field.el.type = 'email'
|
||||
break
|
||||
case 'password':
|
||||
field.el.type = 'password'
|
||||
break
|
||||
case 'comment':
|
||||
field.el.type = 'textarea'
|
||||
break
|
||||
}
|
||||
return field
|
||||
},
|
||||
generateFieldByOther(field, fieldMeta) {
|
||||
const filedRules = field.rules || []
|
||||
if (fieldMeta.required) {
|
||||
if (field.type === 'input') {
|
||||
filedRules.push(rules.Required)
|
||||
} else {
|
||||
filedRules.push(rules.RequiredChange)
|
||||
}
|
||||
}
|
||||
field.rules = filedRules
|
||||
return field
|
||||
},
|
||||
generateField(name) {
|
||||
let field = { id: name, prop: name, el: {}, attrs: {}}
|
||||
// const fieldMeta = this.meta[name] || this.meta['attrs']['children'][name] || {}
|
||||
const fieldMeta = this.meta[name] || ((this.meta['attrs']) ? (this.meta['attrs']['children'][name]) : {})
|
||||
field.label = fieldMeta.label
|
||||
field = this.generateFieldByType(fieldMeta.type, field, fieldMeta)
|
||||
field = this.generateFieldByName(name, field)
|
||||
field = this.generateFieldByOther(field, fieldMeta)
|
||||
field = Object.assign(field, this.fieldsMeta[name] || {})
|
||||
_.set(field, 'attrs.error', '')
|
||||
return field
|
||||
},
|
||||
generateFieldGroup(data) {
|
||||
const [groupTitle, fields] = data
|
||||
this.groups.push({
|
||||
id: groupTitle,
|
||||
title: groupTitle,
|
||||
name: fields[0]
|
||||
})
|
||||
return this.generateFields(fields)
|
||||
},
|
||||
generateFieldAttrs(name) {
|
||||
const fields = []
|
||||
Object.keys(this.meta[name]['children']).forEach((key, i) => {
|
||||
const filed = this.generateField(key)
|
||||
fields.push(filed)
|
||||
})
|
||||
return fields
|
||||
},
|
||||
generateFields(data) {
|
||||
let fields = []
|
||||
for (let field of data) {
|
||||
if (field instanceof Array) {
|
||||
const items = this.generateFieldGroup(field)
|
||||
fields = [...fields, ...items]
|
||||
} else if (field === 'attrs') {
|
||||
const items = this.generateFieldAttrs(field)
|
||||
fields = [...fields, ...items]
|
||||
// 修改title插入ID
|
||||
this.groups[this.groups.length - 1].name = items[0].id
|
||||
} else if (typeof field === 'string') {
|
||||
field = this.generateField(field)
|
||||
fields.push(field)
|
||||
} else if (field instanceof Object) {
|
||||
this.errors[field.prop] = ''
|
||||
_.set(field, 'attrs.error', '')
|
||||
fields.push(field)
|
||||
}
|
||||
}
|
||||
return fields
|
||||
},
|
||||
generateColumns() {
|
||||
this.totalFields = this.generateFields(this.fields)
|
||||
this.$log.debug('Total fields: ', this.totalFields)
|
||||
},
|
||||
setFieldError(name, error) {
|
||||
const field = this.totalFields.find((v) => v.prop === name)
|
||||
if (!field) {
|
||||
return
|
||||
}
|
||||
if (field.attrs.error === error) {
|
||||
error += '.'
|
||||
}
|
||||
if (field.type === 'nestedField') {
|
||||
field.el.errors = error
|
||||
} else {
|
||||
field.attrs.error = error
|
||||
if (typeof error === 'object') {
|
||||
const str = error
|
||||
error = ''
|
||||
Object.keys(str).forEach(key => {
|
||||
error += `${parseInt(key) + 1}.${str[key][0]} `
|
||||
})
|
||||
}
|
||||
// if (field.attrs.error === error) {
|
||||
// error += '.'
|
||||
// }
|
||||
field.attrs.error = error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import Select2 from '@/components/FormFields/Select2'
|
||||
import NestedField from '@/components/AutoDataForm/components/NestedField'
|
||||
import rules from '@/components/DataForm/rules'
|
||||
import { assignIfNot } from '@/utils/common'
|
||||
|
||||
export class FormFieldGenerator {
|
||||
constructor() {
|
||||
this.groups = []
|
||||
}
|
||||
generateFieldByType(type, field, fieldMeta, fieldRemoteMeta) {
|
||||
switch (type) {
|
||||
case 'choice':
|
||||
type = 'radio-group'
|
||||
if (!fieldRemoteMeta.read_only) {
|
||||
field.options = fieldRemoteMeta.choices.map(v => {
|
||||
return { label: v.display_name, value: v.value }
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'datetime':
|
||||
type = 'date-picker'
|
||||
field.el = {
|
||||
type: 'datetime'
|
||||
}
|
||||
break
|
||||
case 'field':
|
||||
type = ''
|
||||
field.component = Select2
|
||||
if (fieldRemoteMeta.required) {
|
||||
field.el.clearable = false
|
||||
}
|
||||
break
|
||||
case 'string':
|
||||
type = 'input'
|
||||
if (!fieldRemoteMeta.max_length) {
|
||||
field.el.type = 'textarea'
|
||||
field.el.rows = 3
|
||||
}
|
||||
if (fieldRemoteMeta.write_only) {
|
||||
field.el.type = 'password'
|
||||
}
|
||||
break
|
||||
case 'boolean':
|
||||
type = 'checkbox'
|
||||
break
|
||||
case 'nested object':
|
||||
type = 'nestedField'
|
||||
field.component = NestedField
|
||||
field.label = ''
|
||||
field.labelWidth = 0
|
||||
field.el.fields = this.generateNestFields(field, fieldMeta, fieldRemoteMeta)
|
||||
field.el.errors = {}
|
||||
Vue.$log.debug('All fields in generate: ', field.el.allFields)
|
||||
break
|
||||
default:
|
||||
type = 'input'
|
||||
break
|
||||
}
|
||||
if (type === 'radio-group') {
|
||||
if (!fieldRemoteMeta.read_only) {
|
||||
const options = fieldRemoteMeta.choices.map(v => {
|
||||
return { label: v.display_name, value: v.value }
|
||||
})
|
||||
if (options.length > 4) {
|
||||
type = 'select'
|
||||
field.el.filterable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
field.type = type
|
||||
return field
|
||||
}
|
||||
generateNestFields(field, fieldMeta, fieldRemoteMeta) {
|
||||
const fields = []
|
||||
const nestedFields = fieldMeta.fields || []
|
||||
const nestedFieldsMeta = fieldMeta.fieldsMeta || {}
|
||||
const nestedFieldsRemoteMeta = fieldRemoteMeta.children || {}
|
||||
for (const name of nestedFields) {
|
||||
const f = this.generateField(name, nestedFieldsMeta, nestedFieldsRemoteMeta)
|
||||
fields.push(f)
|
||||
}
|
||||
Vue.$log.debug('NestFields: ', fields)
|
||||
return fields
|
||||
}
|
||||
generateFieldByName(name, field) {
|
||||
switch (name) {
|
||||
case 'email':
|
||||
field.el.type = 'email'
|
||||
break
|
||||
case 'password':
|
||||
field.el.type = 'password'
|
||||
break
|
||||
case 'comment':
|
||||
field.el.type = 'textarea'
|
||||
break
|
||||
}
|
||||
return field
|
||||
}
|
||||
generateFieldByOther(field, fieldMeta, fieldRemoteMeta) {
|
||||
const filedRules = field.rules || []
|
||||
if (fieldRemoteMeta.required) {
|
||||
if (field.type === 'input') {
|
||||
filedRules.push(rules.Required)
|
||||
} else {
|
||||
filedRules.push(rules.RequiredChange)
|
||||
}
|
||||
}
|
||||
field.rules = filedRules
|
||||
return field
|
||||
}
|
||||
generateField(name, fieldsMeta, remoteFieldsMeta) {
|
||||
let field = { id: name, prop: name, el: {}, attrs: {}, rules: [] }
|
||||
const remoteFieldMeta = remoteFieldsMeta[name] || {}
|
||||
const fieldMeta = fieldsMeta[name] || {}
|
||||
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 = 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) {
|
||||
const [groupTitle, fields] = field
|
||||
this.groups.push({
|
||||
id: groupTitle,
|
||||
title: groupTitle,
|
||||
name: fields[0]
|
||||
})
|
||||
return this.generateFields(fields, fieldsMeta, remoteFieldsMeta)
|
||||
}
|
||||
generateFields(_fields, fieldsMeta, remoteFieldsMeta) {
|
||||
let fields = []
|
||||
for (let field of _fields) {
|
||||
if (field instanceof Array) {
|
||||
const items = this.generateFieldGroup(field, fieldsMeta, remoteFieldsMeta)
|
||||
fields = [...fields, ...items]
|
||||
} else if (typeof field === 'string') {
|
||||
field = this.generateField(field, fieldsMeta, remoteFieldsMeta)
|
||||
fields.push(field)
|
||||
} else if (field instanceof Object) {
|
||||
this.errors[field.prop] = ''
|
||||
fields.push(field)
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TagSearch :options="iOption" v-bind="$attrs" v-on="$listeners" />
|
||||
<TagSearch :options="options" v-bind="$attrs" v-on="$listeners" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -23,22 +23,9 @@ export default {
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
internalOptions: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iOption() {
|
||||
return this.options.concat(this.internalOptions)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options() {
|
||||
// 空函数,方便子组件刷新
|
||||
},
|
||||
url() {
|
||||
this.genericOptions()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -49,7 +36,6 @@ export default {
|
||||
methods: {
|
||||
async genericOptions() {
|
||||
const vm = this // 透传This
|
||||
vm.internalOptions = [] // 重置
|
||||
const data = await this.optionUrlMeta()
|
||||
const meta = data.actions['GET'] || {}
|
||||
for (const [name, field] of Object.entries(meta)) {
|
||||
@@ -63,6 +49,7 @@ export default {
|
||||
label: field.label,
|
||||
type: field.type,
|
||||
value: name
|
||||
|
||||
}
|
||||
if (field.type === 'choice' && field.choices) {
|
||||
option.children = field.choices.map(item => {
|
||||
@@ -82,7 +69,7 @@ export default {
|
||||
{ label: this.$t('common.No'), value: false }
|
||||
]
|
||||
}
|
||||
vm.internalOptions.push(option)
|
||||
vm.options.push(option)
|
||||
}
|
||||
},
|
||||
optionUrlMeta() {
|
||||
@@ -94,4 +81,5 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang='less' scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-if="showColumnSettingPopover"
|
||||
:title="$t('common.CustomCol')"
|
||||
:visible.sync="showColumnSettingPopover"
|
||||
:destroy-on-close="true"
|
||||
:show-cancel="false"
|
||||
width="35%"
|
||||
top="10%"
|
||||
@confirm="handleColumnConfirm()"
|
||||
>
|
||||
<el-alert type="success">
|
||||
{{ this.$t('common.TableColSettingInfo') }}
|
||||
</el-alert>
|
||||
<el-checkbox-group
|
||||
v-model="iCurrentColumns"
|
||||
>
|
||||
<el-row>
|
||||
<el-col
|
||||
v-for="item in totalColumnsList"
|
||||
:key="item.prop"
|
||||
:span="8"
|
||||
style="margin-top:5px;"
|
||||
>
|
||||
<el-checkbox
|
||||
:label="item.prop"
|
||||
:disabled="
|
||||
item.prop==='id' ||
|
||||
item.prop==='actions' ||
|
||||
minColumns.indexOf(item.prop)!==-1
|
||||
"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</el-checkbox-group>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog/index'
|
||||
export default {
|
||||
name: 'ColumnSettingPopover',
|
||||
components: {
|
||||
Dialog
|
||||
},
|
||||
props: {
|
||||
totalColumnsList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
currentColumns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
minColumns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showColumnSettingPopover: false,
|
||||
iCurrentColumns: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('showColumnSettingPopover', ({ url }) => {
|
||||
if (url === this.url) {
|
||||
this.showColumnSettingPopover = true
|
||||
this.iCurrentColumns = this.currentColumns
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
handleColumnConfirm() {
|
||||
this.showColumnSettingPopover = false
|
||||
this.$emit('columnsUpdate', { columns: this.iCurrentColumns, url: this.url })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='less' scoped>
|
||||
</style>
|
||||
@@ -1,27 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<DataTable v-if="!loading" ref="dataTable" v-loading="loading" :config="iConfig" v-bind="$attrs" v-on="$listeners" @filter-change="filterChange" />
|
||||
<ColumnSettingPopover
|
||||
:current-columns="popoverColumns.currentCols"
|
||||
:total-columns-list="popoverColumns.totalColumnsList"
|
||||
:min-columns="popoverColumns.minCols"
|
||||
:url="config.url"
|
||||
@columnsUpdate="handlePopoverColumnsChange"
|
||||
/>
|
||||
</div>
|
||||
<DataTable v-if="!loading" ref="dataTable" v-loading="loading" :config="iConfig" v-bind="$attrs" v-on="$listeners" @filter-change="filterChange" />
|
||||
</template>
|
||||
|
||||
<script type="text/jsx">
|
||||
import DataTable from '../DataTable'
|
||||
import { DateFormatter, DetailFormatter, DisplayFormatter, BooleanFormatter, ActionsFormatter } from '@/components/TableFormatters'
|
||||
import { DateFormatter, DetailFormatter, DisplayFormatter, BooleanFormatter, ActionsFormatter } from '@/components/ListTable/formatters'
|
||||
import i18n from '@/i18n/i18n'
|
||||
import ColumnSettingPopover from './components/ColumnSettingPopover'
|
||||
import { newURL } from '@/utils/common'
|
||||
export default {
|
||||
name: 'AutoDataTable',
|
||||
components: {
|
||||
DataTable,
|
||||
ColumnSettingPopover
|
||||
DataTable
|
||||
},
|
||||
props: {
|
||||
config: {
|
||||
@@ -39,18 +27,8 @@ export default {
|
||||
method: 'get',
|
||||
autoConfig: {},
|
||||
iConfig: {},
|
||||
meta: {},
|
||||
cleanedColumnsShow: {},
|
||||
totalColumns: [],
|
||||
popoverColumns: {
|
||||
totalColumnsList: [],
|
||||
minCols: [],
|
||||
currentCols: []
|
||||
}
|
||||
meta: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
watch: {
|
||||
config: {
|
||||
@@ -66,18 +44,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async optionUrlMetaAndGenCols() {
|
||||
if (this.config.url === '') { return }
|
||||
const url = (this.config.url.indexOf('?') === -1) ? `${this.config.url}?draw=1&display=1` : `${this.config.url}&draw=1&display=1`
|
||||
this.$store.dispatch('common/getUrlMeta', { url: url }).then(data => {
|
||||
const method = this.method.toUpperCase()
|
||||
this.meta = data.actions && data.actions[method] ? data.actions[method] : {}
|
||||
this.generateTotalColumns()
|
||||
}).then(() => {
|
||||
// 根据当前列重新生成最终渲染表格
|
||||
this.filterShowColumns()
|
||||
}).then(() => {
|
||||
// 生成给子组件使用的TotalColList
|
||||
this.generatePopoverColumns()
|
||||
this.meta = data.actions[this.method.toUpperCase()] || {}
|
||||
this.generateColumns()
|
||||
}).catch((error) => {
|
||||
this.$log.error('Error occur: ', error)
|
||||
}).finally(() => {
|
||||
@@ -93,7 +63,7 @@ export default {
|
||||
break
|
||||
case 'actions':
|
||||
col = {
|
||||
prop: 'actions',
|
||||
prop: 'id',
|
||||
label: i18n.t('common.Actions'),
|
||||
align: 'center',
|
||||
width: '150px',
|
||||
@@ -170,12 +140,12 @@ export default {
|
||||
col.filters = column.choices.map(item => {
|
||||
if (typeof (item.value) === 'boolean') {
|
||||
if (item.value) {
|
||||
return { text: item['display_name'], value: 'True' }
|
||||
return { text: item.display_name, value: 'True' }
|
||||
} else {
|
||||
return { text: item['display_name'], value: 'False' }
|
||||
return { text: item.display_name, value: 'False' }
|
||||
}
|
||||
}
|
||||
return { text: item['display_name'], value: item.value }
|
||||
return { text: item.display_name, value: item.value }
|
||||
})
|
||||
col.sortable = false
|
||||
col['column-key'] = col.prop
|
||||
@@ -195,7 +165,7 @@ export default {
|
||||
col = this.addFilterIfNeed(col)
|
||||
return col
|
||||
},
|
||||
generateTotalColumns() {
|
||||
generateColumns() {
|
||||
const config = _.cloneDeep(this.config)
|
||||
const columns = []
|
||||
for (let col of config.columns) {
|
||||
@@ -206,80 +176,9 @@ export default {
|
||||
columns.push(col)
|
||||
}
|
||||
}
|
||||
// 第一次初始化时记录 totalColumns
|
||||
this.totalColumns = columns
|
||||
config.columns = columns
|
||||
this.iConfig = config
|
||||
},
|
||||
// 生成给子组件使用的TotalColList
|
||||
cleanColumnsShow() {
|
||||
const totalColumnsNames = this.totalColumns.map(obj => obj.prop)
|
||||
// 默认列
|
||||
let defaultColumnsNames = _.get(this.iConfig, 'columnsShow.default', [])
|
||||
if (defaultColumnsNames.length === 0) {
|
||||
defaultColumnsNames = totalColumnsNames
|
||||
}
|
||||
// Clean it
|
||||
defaultColumnsNames = totalColumnsNames.filter(n => defaultColumnsNames.indexOf(n) > -1)
|
||||
|
||||
// 最小列
|
||||
const minColumnsNames = _.get(this.iConfig, 'columnsShow.min', ['actions', 'id'])
|
||||
.filter(n => defaultColumnsNames.indexOf(n) > -1)
|
||||
|
||||
// 应该显示的列
|
||||
const _tableConfig = localStorage.getItem('tableConfig')
|
||||
? JSON.parse(localStorage.getItem('tableConfig'))
|
||||
: {}
|
||||
const tableName = this.config.name || this.$route.name + '_' + newURL(this.iConfig.url).pathname
|
||||
const configShowColumnsNames = _.get(_tableConfig[tableName], 'showColumns', null)
|
||||
let showColumnsNames = configShowColumnsNames || defaultColumnsNames
|
||||
if (showColumnsNames.length === 0) {
|
||||
showColumnsNames = totalColumnsNames
|
||||
}
|
||||
// 校对显示的列,是不是包含最小列
|
||||
minColumnsNames.forEach((v, i) => {
|
||||
if (showColumnsNames.indexOf(v) === -1) {
|
||||
showColumnsNames.push(v)
|
||||
}
|
||||
})
|
||||
// Clean it
|
||||
showColumnsNames = totalColumnsNames.filter(n => showColumnsNames.indexOf(n) > -1)
|
||||
|
||||
this.cleanedColumnsShow = {
|
||||
default: defaultColumnsNames,
|
||||
show: showColumnsNames,
|
||||
min: minColumnsNames,
|
||||
configShow: configShowColumnsNames
|
||||
}
|
||||
this.$log.debug('Cleaned columns show: ', this.cleanedColumnsShow)
|
||||
},
|
||||
filterShowColumns() {
|
||||
this.cleanColumnsShow()
|
||||
this.iConfig.columns = this.totalColumns.filter(obj => {
|
||||
return this.cleanedColumnsShow.show.indexOf(obj.prop) > -1
|
||||
})
|
||||
},
|
||||
generatePopoverColumns() {
|
||||
this.popoverColumns.totalColumnsList = this.totalColumns.map(obj => {
|
||||
return { prop: obj.prop, label: obj.label }
|
||||
})
|
||||
this.popoverColumns.currentCols = this.cleanedColumnsShow.show
|
||||
this.popoverColumns.minCols = this.cleanedColumnsShow.min
|
||||
this.$log.debug('Popover cols: ', this.popoverColumns)
|
||||
},
|
||||
handlePopoverColumnsChange({ columns, url }) {
|
||||
this.$log.debug('Columns change: ', columns)
|
||||
this.popoverColumns.currentCols = columns
|
||||
const _tableConfig = localStorage.getItem('tableConfig')
|
||||
? JSON.parse(localStorage.getItem('tableConfig'))
|
||||
: {}
|
||||
const tableName = this.config.name || this.$route.name + '_' + newURL(url).pathname
|
||||
_tableConfig[tableName] = {
|
||||
'showColumns': columns
|
||||
}
|
||||
localStorage.setItem('tableConfig', JSON.stringify(_tableConfig))
|
||||
this.filterShowColumns()
|
||||
},
|
||||
filterChange(filters) {
|
||||
const key = Object.keys(filters)[0]
|
||||
const attr = {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<DataZTree ref="dataztree" :setting="treeSetting" class="data-z-tree" v-on="$listeners">
|
||||
<slot v-if="treeSetting.hasRightMenu" slot="rMenu">
|
||||
<DataZTree ref="dataztree" :setting="treeSetting">
|
||||
<slot slot="rMenu">
|
||||
<li id="m_create" class="rmenu" tabindex="-1" @click="createTreeNode">
|
||||
<i class="fa fa-plus-square-o" /> {{ this.$t('tree.CreateNode') }}
|
||||
</li>
|
||||
@@ -55,8 +55,7 @@ export default {
|
||||
// beforeDrag
|
||||
// onDrag
|
||||
// beforeAsync: this.defaultCallback.bind(this, 'beforeAsync')
|
||||
},
|
||||
hasRightMenu: true
|
||||
}
|
||||
},
|
||||
currentNode: '',
|
||||
currentNodeId: ''
|
||||
@@ -64,7 +63,6 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
treeSetting() {
|
||||
this.$log.debug('Settings: ', this.setting)
|
||||
return _.merge(this.defaultSetting, this.setting)
|
||||
},
|
||||
zTree() {
|
||||
@@ -78,10 +76,6 @@ export default {
|
||||
$('body').unbind('mousedown')
|
||||
},
|
||||
methods: {
|
||||
refreshTree: function() {
|
||||
const refreshIconRef = $('#tree-refresh')
|
||||
refreshIconRef.click()
|
||||
},
|
||||
editTreeNode: function() {
|
||||
this.hideRMenu()
|
||||
const currentNode = this.zTree.getSelectedNodes()[0]
|
||||
@@ -104,19 +98,15 @@ 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
|
||||
query['node'] = this.currentNodeId
|
||||
url = `${this.setting.url}${combinator}node_id=${treeNode.meta.node.id}&show_current_asset=${show_current_asset}`
|
||||
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}`)
|
||||
} else if (treeNode.meta.type === '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.$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}`)
|
||||
}
|
||||
this.$router.push({ query })
|
||||
this.$emit('urlChange', url)
|
||||
},
|
||||
removeTreeNode: function() {
|
||||
this.hideRMenu()
|
||||
@@ -129,9 +119,8 @@ 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)
|
||||
// this.$message.error(this.$t('common.deleteErrorMsg' + ' ' + error))
|
||||
})
|
||||
},
|
||||
onRename: function(event, treeId, treeNode, isCancel) {
|
||||
@@ -150,7 +139,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
|
||||
@@ -219,7 +208,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()
|
||||
@@ -251,21 +240,16 @@ export default {
|
||||
})
|
||||
},
|
||||
refresh: function() {
|
||||
|
||||
},
|
||||
getSelectedNodes: function() {
|
||||
return this.zTree.getSelectedNodes()
|
||||
},
|
||||
getNodes: function() {
|
||||
return this.zTree.getNodes()
|
||||
},
|
||||
selectNode: function(node) {
|
||||
return this.zTree.selectNode(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang='less' scoped>
|
||||
.rmenu {
|
||||
font-size: 12px;
|
||||
padding: 0 16px;
|
||||
@@ -288,8 +272,4 @@ export default {
|
||||
.rmenu:hover{
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.data-z-tree >>> .fa {
|
||||
width: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
<template>
|
||||
<div :class="grouped ? 'el-button-group' : 'el-button-ungroup'">
|
||||
<template v-for="action in iActions">
|
||||
<el-dropdown
|
||||
v-if="action.dropdown"
|
||||
v-show="action.dropdown.length > 0"
|
||||
:key="action.name"
|
||||
class="action-item"
|
||||
trigger="click"
|
||||
placement="bottom-start"
|
||||
@command="handleDropdownCallback"
|
||||
>
|
||||
<el-button :size="size" v-bind="cleanButtonAction(action)">
|
||||
{{ action.title }}<i class="el-icon-arrow-down el-icon--right" />
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<template v-for="option in action.dropdown">
|
||||
<div v-if="option.group" :key="'group:'+option.name" class="dropdown-menu-title">
|
||||
{{ option.group }}
|
||||
</div>
|
||||
<el-dropdown-item
|
||||
:key="option.name"
|
||||
:command="[option, action]"
|
||||
v-bind="option"
|
||||
>
|
||||
{{ option.title }}
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
<el-button
|
||||
v-else
|
||||
:key="action.name"
|
||||
:size="size"
|
||||
v-bind="cleanButtonAction(action)"
|
||||
class="action-item"
|
||||
@click="handleClick(action)"
|
||||
>
|
||||
<el-tooltip :disabled="!action.tip" :content="action.tip" placement="top">
|
||||
<span>
|
||||
<i v-if="action.fa" :class="'fa ' + action.fa" />{{ action.title }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'DataActions',
|
||||
props: {
|
||||
grouped: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'small'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
actions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iActions() {
|
||||
return this.cleanActions(this.actions)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleDropdownCallback(command) {
|
||||
const [option, dropdown] = command
|
||||
const defaultCallback = () => this.$log.debug('No callback found: ', option, dropdown)
|
||||
let callback = option.callback
|
||||
if (!callback) {
|
||||
callback = dropdown.callback
|
||||
}
|
||||
if (!callback) {
|
||||
callback = defaultCallback
|
||||
}
|
||||
return callback(option)
|
||||
},
|
||||
handleClick(action) {
|
||||
if (action && action.callback) {
|
||||
action.callback(action)
|
||||
} else {
|
||||
this.$log.debug('No callback found')
|
||||
}
|
||||
this.$emit('actionClick', action)
|
||||
},
|
||||
checkItem(item, attr, defaults) {
|
||||
if (!item) {
|
||||
return true
|
||||
}
|
||||
let ok = item[attr]
|
||||
if (ok && typeof ok === 'function') {
|
||||
ok = ok(item)
|
||||
} else if (ok == null) {
|
||||
ok = defaults === undefined ? true : defaults
|
||||
}
|
||||
return ok
|
||||
},
|
||||
cleanButtonAction(action) {
|
||||
action = _.cloneDeep(action)
|
||||
delete action['dropdown']
|
||||
delete action['callback']
|
||||
delete action['name']
|
||||
delete action['can']
|
||||
return action
|
||||
},
|
||||
cleanActions(actions) {
|
||||
const cleanedActions = []
|
||||
const cloneActions = _.cloneDeep(actions)
|
||||
for (const v of cloneActions) {
|
||||
if (!v) {
|
||||
continue
|
||||
}
|
||||
const action = Object.assign({}, v)
|
||||
// 是否拥有这个action
|
||||
const has = this.checkItem(action, 'has')
|
||||
delete action['has']
|
||||
if (!has) {
|
||||
continue
|
||||
}
|
||||
// 是否有分割线
|
||||
action.divided = this.checkItem(action, 'divided', false)
|
||||
|
||||
// 是否是disabled
|
||||
const can = this.checkItem(action, 'can')
|
||||
action.disabled = !can
|
||||
|
||||
if (action.dropdown) {
|
||||
// const dropdown = this.cleanActions(action.dropdown)
|
||||
action.dropdown = this.cleanActions(action.dropdown)
|
||||
}
|
||||
cleanedActions.push(action)
|
||||
}
|
||||
return cleanedActions
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dropdown-menu-title {
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 30px;
|
||||
padding-left: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: solid 1px #e4e7ed;
|
||||
}
|
||||
|
||||
.dropdown-menu-title:first-child {
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.el-button-ungroup .action-item {
|
||||
margin-left: 4px
|
||||
}
|
||||
|
||||
.el-button-ungroup .action-item:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@
|
||||
:content="fields"
|
||||
:form="basicForm"
|
||||
label-position="right"
|
||||
label-width="20%"
|
||||
label-width="17%"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
// 初始值
|
||||
form: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
default: () => { return {} }
|
||||
},
|
||||
moreButtons: {
|
||||
type: Array,
|
||||
|
||||
@@ -8,27 +8,7 @@ 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,
|
||||
EmailCheck
|
||||
}
|
||||
|
||||
export const JsonRequired = {
|
||||
required: true,
|
||||
trigger: 'change',
|
||||
validator: (rule, value, callback) => {
|
||||
try {
|
||||
JSON.parse(value)
|
||||
callback()
|
||||
} catch (e) {
|
||||
callback(new Error(i18n.t('common.InvalidJson')))
|
||||
}
|
||||
}
|
||||
RequiredChange
|
||||
}
|
||||
|
||||
@@ -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 = 1
|
||||
const defaultFirstPage = 0
|
||||
const noPaginationDataPath = 'payload'
|
||||
|
||||
export default {
|
||||
@@ -426,6 +426,7 @@ export default {
|
||||
onEdit: {
|
||||
type: Function,
|
||||
default(row) {
|
||||
// console.log('On delete row')
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -722,10 +723,6 @@ export default {
|
||||
default(row, index) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
totalData: {
|
||||
type: Array,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -813,13 +810,6 @@ 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: {
|
||||
@@ -838,13 +828,6 @@ export default {
|
||||
* @property {array} rows - 已选中的行数据的数组
|
||||
*/
|
||||
this.$emit('selection-change', val)
|
||||
},
|
||||
totalData(val) {
|
||||
if (val) {
|
||||
this.page = defaultFirstPage
|
||||
this.total = val.length
|
||||
this.getList()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -861,9 +844,6 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.totalData) {
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getQuery() {
|
||||
@@ -897,58 +877,12 @@ 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
|
||||
if (this.isTree) {
|
||||
this.data = this.tree2Array(this.data, this.expandAll)
|
||||
}
|
||||
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
|
||||
this.data = this.tree2Array(this.data, this.expandAll)
|
||||
return this.data
|
||||
},
|
||||
/**
|
||||
* 手动刷新列表数据,选项的默认值为: { loading: true }
|
||||
* @public
|
||||
* @param {object} options 方法选项
|
||||
*/
|
||||
getListFromRemote({ loading = true } = {}) {
|
||||
getList({ loading = true } = {}) {
|
||||
const { url } = this
|
||||
if (!url) {
|
||||
return
|
||||
|
||||
@@ -100,9 +100,6 @@ export default {
|
||||
iListeners() {
|
||||
return Object.assign({}, this.$listeners, this.tableConfig.listeners)
|
||||
},
|
||||
dataTable() {
|
||||
return this.$refs.table
|
||||
},
|
||||
...mapGetters({
|
||||
'globalTableConfig': 'tableConfig'
|
||||
})
|
||||
|
||||
@@ -95,8 +95,6 @@ export default {
|
||||
vm.zTree.destroy()
|
||||
}
|
||||
this.zTree = $.fn.zTree.init($(`#${this.iZTreeID}`), this.treeSetting, res)
|
||||
// 手动上报事件, Tree加载完成
|
||||
this.$emit('TreeInitFinish', this.zTree)
|
||||
if (this.treeSetting.showRefresh) {
|
||||
this.rootNodeAddDom(
|
||||
this.zTree,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ZTree ref="ztree" :setting="treeSetting" v-on="$listeners">
|
||||
<ZTree ref="ztree" :setting="treeSetting">
|
||||
<!--Slot透传-->
|
||||
<div slot="rMenu" slot-scope="{data}">
|
||||
<slot name="rMenu" :data="data" />
|
||||
|
||||
@@ -55,6 +55,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -73,8 +74,4 @@ export default {
|
||||
/*padding-top: 10px;*/
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<el-link @click="onClick">
|
||||
{{ title }}
|
||||
</el-link>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Link',
|
||||
props: {
|
||||
href: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
window.open(this.href)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,250 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="hours-container">
|
||||
<div v-for="(item, index) in hours" :key="index" class="hours-item">
|
||||
<div class="hours-item-header">{{ compItem(item) }}</div>
|
||||
<div class="hours-item-value">
|
||||
<div
|
||||
:class="compClass(2 * item)"
|
||||
@click="handleClick(2 * item)"
|
||||
@mouseover="handleHover(2 * item)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tips">{{ tips }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
model: {
|
||||
prop: 'sendTimeList'
|
||||
},
|
||||
props: {
|
||||
sendTimeList: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hours: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23], // 选项
|
||||
selectStart: false, // 开始
|
||||
startIndex: '', // 开始下标
|
||||
timeRangeList: [], // 选择的时间段
|
||||
timeRangeListIndex: [], // 选中的下标
|
||||
tempRangeIndex: [], // 预选下标
|
||||
tips: '向右选中,向左取消选择'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
watch: {
|
||||
timeRangeList: function(value) {
|
||||
this.$emit('change', value)
|
||||
this.$parent.$emit('el.form.change')// 触发父组件的校验规则
|
||||
},
|
||||
sendTimeList: {
|
||||
handler() {
|
||||
this.transformedIndex()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.transformedIndex()
|
||||
},
|
||||
methods: {
|
||||
// 时间区间转换成下标区间
|
||||
transformedIndex() {
|
||||
this.timeRangeListIndex = []
|
||||
this.timeRangeList = this.sendTimeList
|
||||
this.timeRangeList.forEach(element => {
|
||||
const [startTime, endTime] = element.match(/\d+\:\d+/g)
|
||||
if (startTime && endTime) {
|
||||
const [startHour, startMin] = startTime.split(':')
|
||||
const [endHour, endMin] = endTime.split(':')
|
||||
if (startHour && startMin && endHour && endMin) {
|
||||
let startNum, endNum
|
||||
if (startMin === '00') {
|
||||
startNum = 2 * parseInt(startHour)
|
||||
} else {
|
||||
startNum = 2 * parseInt(startHour) + 1
|
||||
}
|
||||
if (endMin === '00') {
|
||||
endNum = 2 * parseInt(endHour) - 1
|
||||
} else {
|
||||
endNum = 2 * parseInt(endHour)
|
||||
}
|
||||
while (endNum >= startNum) {
|
||||
this.timeRangeListIndex.push(startNum)
|
||||
startNum++
|
||||
}
|
||||
} else {
|
||||
this.$message.error('时间段格式不正确')
|
||||
}
|
||||
} else {
|
||||
this.$message.error('没有拿到开始时间或结束时间或者时间段格式不对')
|
||||
}
|
||||
})
|
||||
this.tips = this.timeRangeList && this.timeRangeList.length > 0 ? this.timeRangeList : '向右选中,向左取消选择'
|
||||
},
|
||||
// 下标区间转换成时间区间
|
||||
transformedSection() {
|
||||
this.timeRangeList = []
|
||||
let startTime = ''; let endTime = ''; const len = this.hours.length
|
||||
for (let index = this.hours[0] * 2; index < 2 * (len + 1); index++) {
|
||||
if (this.timeRangeListIndex.indexOf(index) > -1) {
|
||||
if (startTime) { // 如果有开始时间,直接确定结束时间
|
||||
const endHour = Math.floor((index + 1) / 2)
|
||||
const endMin = (index + 1) % 2 === 0 ? '00' : '30'
|
||||
endTime = `${endHour < 10 ? '0' + endHour : endHour}:${endMin}`
|
||||
} else { // 没有开始时间,确定当前点为开始时间
|
||||
const startHour = Math.floor(index / 2)
|
||||
const startMin = index % 2 === 0 ? '00' : '30'
|
||||
startTime = `${startHour < 10 ? '0' + startHour : startHour}:${startMin}`
|
||||
}
|
||||
if (index === 2 * this.hours.length + 1) { // 如果是最后一格,直接结束
|
||||
endTime = `${Math.floor((index + 1) / 2)}:00`
|
||||
this.timeRangeList.push(`${startTime || '23:30'}-${endTime}`)
|
||||
startTime = ''
|
||||
endTime = ''
|
||||
}
|
||||
} else { // 若这个点不在选择区间,确定一个时间段
|
||||
if (startTime && endTime) {
|
||||
this.timeRangeList.push(`${startTime}-${endTime}`)
|
||||
startTime = ''
|
||||
endTime = ''
|
||||
} else if (startTime && !endTime) { // 这里可能只选半个小时
|
||||
const endHour = Math.floor(index / 2)
|
||||
const endMin = index % 2 === 0 ? '00' : '30'
|
||||
endTime = `${endHour < 10 ? '0' + endHour : endHour}:${endMin}`
|
||||
this.timeRangeList.push(`${startTime}-${endTime}`)
|
||||
startTime = ''
|
||||
endTime = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
this.tips = this.timeRangeList && this.timeRangeList.length > 0 ? this.timeRangeList : '向右选中,向左取消选择'
|
||||
},
|
||||
// 点击事件
|
||||
handleClick(index) {
|
||||
if (this.selectStart) {
|
||||
if (index === this.startIndex) { // 双击取反
|
||||
if (this.timeRangeListIndex.indexOf(index) > -1) {
|
||||
this.timeRangeListIndex.splice(this.timeRangeListIndex.indexOf(index), 1)
|
||||
} else {
|
||||
this.timeRangeListIndex.push(this.startIndex)
|
||||
}
|
||||
} else if (index > this.startIndex) { // 选取数据--向右添加,向左取消
|
||||
while (index >= this.startIndex) {
|
||||
this.timeRangeListIndex.push(this.startIndex)
|
||||
this.startIndex++
|
||||
}
|
||||
this.timeRangeListIndex = Array.from(new Set(this.timeRangeListIndex))
|
||||
} else { // 删除数据
|
||||
while (this.startIndex >= index) {
|
||||
if (this.timeRangeListIndex.indexOf(index) > -1) {
|
||||
this.timeRangeListIndex.splice(this.timeRangeListIndex.indexOf(index), 1)
|
||||
}
|
||||
index++
|
||||
}
|
||||
}
|
||||
this.startIndex = ''
|
||||
this.tempRangeIndex = []
|
||||
this.transformedSection()
|
||||
} else {
|
||||
this.startIndex = index
|
||||
}
|
||||
this.selectStart = !this.selectStart
|
||||
},
|
||||
// 预选区间
|
||||
handleHover(index) {
|
||||
if (this.selectStart) {
|
||||
this.tempRangeIndex = []
|
||||
if (index > this.startIndex) { // 选取数据--向右添加,向左取消
|
||||
while (index >= this.startIndex) {
|
||||
this.tempRangeIndex.push(index)
|
||||
index--
|
||||
}
|
||||
} else { // 删除数据
|
||||
while (this.startIndex >= index) {
|
||||
this.tempRangeIndex.push(index)
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 是否选中,计算className
|
||||
compClass(index) {
|
||||
if (index === this.startIndex) {
|
||||
return 'hours-item-left preSelected'
|
||||
}
|
||||
if (index >= this.startIndex) {
|
||||
if (this.tempRangeIndex.indexOf(index) > -1) {
|
||||
return 'hours-item-left preSelected'
|
||||
}
|
||||
} else {
|
||||
if (this.tempRangeIndex.indexOf(index) > -1) {
|
||||
return 'hours-item-left unSelected'
|
||||
}
|
||||
}
|
||||
return this.timeRangeListIndex.indexOf(index) > -1 ? 'hours-item-left selected' : 'hours-item-left'
|
||||
},
|
||||
compItem(item) { // 不足10前面补0
|
||||
return item < 10 ? `0${item}` : item
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.hours-container {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
.hours-item {
|
||||
width: 30px;
|
||||
height: 60px;
|
||||
border: 1px solid #c2d0f3;
|
||||
border-right: none;
|
||||
text-align: center;
|
||||
&:last-child {
|
||||
border-right: 1px solid #c2d0f3;
|
||||
}
|
||||
.hours-item-header {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
border-bottom: 1px solid #c2d0f3;
|
||||
}
|
||||
.hours-item-value {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
}
|
||||
.selected {
|
||||
background-color: #4e84fe;
|
||||
border-bottom: 1px solid #c2d0f3;
|
||||
}
|
||||
.preSelected {
|
||||
background-color: #8eaffc;
|
||||
border-bottom: 1px solid #c2d0f3;
|
||||
}
|
||||
.unSelected {
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #c2d0f3;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tips {
|
||||
width: 100%;
|
||||
line-height: 30px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,33 +0,0 @@
|
||||
import DatetimeRangePicker from './DatetimeRangePicker'
|
||||
import Link from './Link'
|
||||
import PasswordInput from './PasswordInput'
|
||||
import Select2 from './Select2'
|
||||
import Swicher from './Swicher'
|
||||
import UploadField from './UploadField'
|
||||
import UploadKey from './UploadKey'
|
||||
import UserPassword from './UserPassword'
|
||||
import WeekCronSelect from './WeekCronSelect'
|
||||
|
||||
export default {
|
||||
DatetimeRangePicker,
|
||||
Link,
|
||||
PasswordInput,
|
||||
Select2,
|
||||
Swicher,
|
||||
UploadKey,
|
||||
UploadField,
|
||||
UserPassword,
|
||||
WeekCronSelect
|
||||
}
|
||||
|
||||
export {
|
||||
DatetimeRangePicker,
|
||||
Link,
|
||||
PasswordInput,
|
||||
Select2,
|
||||
Swicher,
|
||||
UploadKey,
|
||||
UploadField,
|
||||
UserPassword,
|
||||
WeekCronSelect
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
</template>
|
||||
|
||||
<script type="text/jsx">
|
||||
import { DetailFormatter, SystemUserFormatter } from '@/components/TableFormatters'
|
||||
import { DetailFormatter, SystemUserFormatter } from '@/components/ListTable/formatters'
|
||||
import TreeTable from '../TreeTable'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,45 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<MFAVerifyDialog
|
||||
v-if="mfaDialogShow"
|
||||
@MFAVerifyDone="showExportDialog"
|
||||
@MFAVerifyCancel="handleExportCancel"
|
||||
/>
|
||||
<Dialog
|
||||
v-if="exportDialogShow"
|
||||
:title="$t('common.Export')"
|
||||
:visible.sync="exportDialogShow"
|
||||
:destroy-on-close="true"
|
||||
@confirm="handleExportConfirm()"
|
||||
@cancel="handleExportCancel()"
|
||||
>
|
||||
<el-form label-position="left" style="padding-left: 50px">
|
||||
<el-form-item :label="$t('common.fileType' )" :label-width="'100px'">
|
||||
<el-radio-group v-model="exportTypeOption">
|
||||
<el-radio v-for="option of exportTypeOptions" :key="option.value" style="padding: 10px 20px;" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item class="export-form" :label="this.$t('common.imExport.ExportRange')" :label-width="'100px'">
|
||||
<el-radio-group v-model="exportOption">
|
||||
<el-radio v-for="option of exportOptions" :key="option.value" class="export-item" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</Dialog>
|
||||
</div>
|
||||
<Dialog v-if="showExportDialog" :title="$t('common.Export')" :visible.sync="showExportDialog" :destroy-on-close="true" @confirm="handleExportConfirm()" @cancel="handleExportCancel()">
|
||||
<el-form label-position="left" style="padding-left: 50px">
|
||||
<el-form-item :label="$t('common.fileType' )" :label-width="'100px'">
|
||||
<el-radio-group v-model="exportTypeOption">
|
||||
<el-radio v-for="option of exportTypeOptions" :key="option.value" style="padding: 10px 20px;" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item class="export-form" :label="this.$t('common.imExport.ExportRange')" :label-width="'100px'">
|
||||
<el-radio-group v-model="exportOption">
|
||||
<el-radio v-for="option of exportOptions" :key="option.value" class="export-item" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
import MFAVerifyDialog from '@/components/MFAVerifyDialog'
|
||||
import { createSourceIdCache } from '@/api/common'
|
||||
import * as queryUtil from '@/components/DataTable/compenents/el-data-table/utils/query'
|
||||
|
||||
export default {
|
||||
name: 'ExportDialog',
|
||||
components: {
|
||||
Dialog,
|
||||
MFAVerifyDialog
|
||||
Dialog
|
||||
},
|
||||
props: {
|
||||
selectedRows: {
|
||||
@@ -48,15 +32,7 @@ export default {
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
beforeExport: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
mfaVerifyRequired: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: () => ''
|
||||
},
|
||||
performExport: {
|
||||
type: Function,
|
||||
@@ -79,12 +55,10 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
exportDialogShow: false,
|
||||
showExportDialog: false,
|
||||
exportOption: 'all',
|
||||
exportTypeOption: 'csv',
|
||||
meta: {},
|
||||
mfaVerified: false,
|
||||
mfaDialogShow: false
|
||||
meta: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -141,26 +115,11 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('showExportDialog', ({ selectedRows, url, name }) => {
|
||||
// Todo: 没有时间了,只能先这么处理了
|
||||
if (url === this.url || url.indexOf('account') > -1) {
|
||||
this.showExportDialog()
|
||||
}
|
||||
this.$eventBus.$on('showExportDialog', (row) => {
|
||||
this.showExportDialog = true
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
showExportDialog() {
|
||||
if (!this.mfaVerifyRequired) {
|
||||
this.exportDialogShow = true
|
||||
return
|
||||
}
|
||||
// 这是需要校验 MFA 的
|
||||
if (!this.mfaDialogShow) {
|
||||
this.mfaDialogShow = true
|
||||
} else {
|
||||
this.exportDialogShow = true
|
||||
}
|
||||
},
|
||||
downloadCsv(url) {
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
@@ -178,6 +137,11 @@ export default {
|
||||
}
|
||||
const spm = await createSourceIdCache(resources)
|
||||
query['spm'] = spm.spm
|
||||
} else if (exportOption === 'filtered') {
|
||||
// console.log(listTableRef)
|
||||
// console.log(listTableRef.dataTable)
|
||||
// delete query['limit']
|
||||
// delete query['offset']
|
||||
}
|
||||
query['format'] = this.exportTypeOption
|
||||
const queryStr =
|
||||
@@ -190,17 +154,14 @@ export default {
|
||||
const query = listTableRef.dataTable.getQuery()
|
||||
delete query['limit']
|
||||
delete query['offset']
|
||||
await this.beforeExport()
|
||||
return this.performExport(this.selectedRows, this.exportOption, query)
|
||||
},
|
||||
async handleExportConfirm() {
|
||||
await this.handleExport()
|
||||
this.exportDialogShow = false
|
||||
this.mfaDialogShow = false
|
||||
this.showExportDialog = false
|
||||
},
|
||||
handleExportCancel() {
|
||||
this.exportDialogShow = false
|
||||
this.mfaDialogShow = false
|
||||
this.showExportDialog = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<ExportDialog :selected-rows="selectedRows" v-bind="exportOptions" v-on="$listeners" />
|
||||
<ImportDialog :selected-rows="selectedRows" v-bind="importOptions" v-on="$listeners" />
|
||||
<ExportDialog :selected-rows="selectedRows" :url="url" v-bind="$attrs" v-on="$listeners" />
|
||||
<ImportDialog :selected-rows="selectedRows" :url="url" v-bind="$attrs" v-on="$listeners" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -10,7 +10,7 @@ import ExportDialog from './ExportDialog'
|
||||
import ImportDialog from './ImportDialog'
|
||||
|
||||
export default {
|
||||
name: 'ImExportDialog',
|
||||
name: 'DialogAction',
|
||||
components: {
|
||||
ExportDialog,
|
||||
ImportDialog
|
||||
@@ -20,13 +20,9 @@ export default {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
exportOptions: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
importOptions: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
url: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -1,73 +1,64 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:title="importTitle"
|
||||
:title="$t('common.Import')"
|
||||
:visible.sync="showImportDialog"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:loading-status="loadStatus"
|
||||
width="80%"
|
||||
class="importDialog"
|
||||
:show-cancel="false"
|
||||
:show-confirm="false"
|
||||
@close="handleImportCancel"
|
||||
@confirm="handleImportConfirm"
|
||||
@cancel="handleImportCancel()"
|
||||
>
|
||||
<el-form v-if="!showTable" label-position="left" style="padding-left: 50px">
|
||||
<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-item :label="$t('common.Import' )" :label-width="'100px'">
|
||||
<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>
|
||||
<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>
|
||||
<div style="line-height: 1.5">
|
||||
<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 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>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('common.Upload' )" :label-width="'100px'" class="file-uploader">
|
||||
<el-form-item :label="$t('common.Upload' )" :label-width="'100px'">
|
||||
<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"
|
||||
>
|
||||
<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-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>-->
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div v-else class="importTableZone">
|
||||
<ImportTable
|
||||
ref="importTable"
|
||||
:json-data="jsonData"
|
||||
:import-option="importOption"
|
||||
:url="url"
|
||||
@cancel="cancelUpload"
|
||||
@finish="closeDialog"
|
||||
/>
|
||||
<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>
|
||||
</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,
|
||||
ImportTable
|
||||
Dialog
|
||||
},
|
||||
props: {
|
||||
selectedRows: {
|
||||
@@ -82,119 +73,103 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
showImportDialog: false,
|
||||
importOption: 'create',
|
||||
importOption: '1',
|
||||
isCsv: true,
|
||||
errorMsg: '',
|
||||
loadStatus: false,
|
||||
importTypeOption: 'csv',
|
||||
importTypeIsCsv: true,
|
||||
showTable: false,
|
||||
renderError: '',
|
||||
hasFileFormatOrSizeError: false,
|
||||
jsonData: {}
|
||||
importTypeOption: 'csv'
|
||||
}
|
||||
},
|
||||
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')
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
importOption(val) {
|
||||
this.showTable = false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('showImportDialog', ({ url }) => {
|
||||
if (url === this.url) {
|
||||
this.showImportDialog = true
|
||||
}
|
||||
this.$eventBus.$on('showImportDialog', (row) => {
|
||||
this.showImportDialog = true
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
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
|
||||
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)
|
||||
}).catch(error => {
|
||||
fileList.splice(0, fileList.length)
|
||||
this.renderError = getErrorResponseMsg(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}`
|
||||
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
|
||||
})
|
||||
},
|
||||
catchError(error) {
|
||||
console.log(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
|
||||
}
|
||||
},
|
||||
onSuccess(msg) {
|
||||
this.errorMsg = ''
|
||||
@@ -206,14 +181,40 @@ 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['importTable'].performUpload()
|
||||
this.$refs.upload.submit()
|
||||
},
|
||||
handleImportCancel() {
|
||||
this.showImportDialog = false
|
||||
this.showTable = false
|
||||
this.renderError = ''
|
||||
this.jsonData = {}
|
||||
},
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,49 +231,4 @@ 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>
|
||||
|
||||
@@ -1,426 +0,0 @@
|
||||
<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, getUpdateObjURL } from '@/utils/common'
|
||||
import { EditableInputFormatter, StatusFormatter } from '@/components/TableFormatters'
|
||||
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 (typeof itemColData !== 'number' && (!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 = getUpdateObjURL(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>
|
||||
@@ -1,38 +1,30 @@
|
||||
<template>
|
||||
<DataActions
|
||||
v-if="hasLeftActions"
|
||||
:actions="iActions"
|
||||
v-bind="$attrs"
|
||||
class="header-action"
|
||||
/>
|
||||
<ActionsGroup v-if="hasLeftActions" :actions="actions" :more-actions="moreActions" :more-actions-title="moreActionsTitle" v-bind="$attrs" class="header-action" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from '@/i18n/i18n'
|
||||
import DataActions from '@/components/DataActions'
|
||||
import ActionsGroup from '@/components/ActionsGroup'
|
||||
import { createSourceIdCache } from '@/api/common'
|
||||
import { cleanActions } from './utils'
|
||||
|
||||
const defaultTrue = { type: [Boolean, Function], default: true }
|
||||
const defaultFalse = { type: [Boolean, Function], default: false }
|
||||
const defaultTrue = { type: Boolean, default: true }
|
||||
const defaultFalse = { type: Boolean, default: false }
|
||||
export default {
|
||||
name: 'LeftSide',
|
||||
components: {
|
||||
DataActions
|
||||
ActionsGroup
|
||||
},
|
||||
props: {
|
||||
hasLeftActions: defaultTrue,
|
||||
hasCreate: defaultTrue,
|
||||
canCreate: defaultTrue,
|
||||
hasBulkDelete: defaultTrue,
|
||||
hasBulkUpdate: defaultFalse,
|
||||
hasMoreActions: defaultTrue,
|
||||
hasLeftActions: defaultTrue,
|
||||
tableUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
createRoute: {
|
||||
type: [String, Object, Function],
|
||||
type: [String, Object],
|
||||
default: function() {
|
||||
return this.$route.name.replace('List', 'Create')
|
||||
}
|
||||
@@ -61,41 +53,23 @@ export default {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
moreCreates: {
|
||||
moreActionsButton: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
createTitle: {
|
||||
type: String,
|
||||
default: () => i18n.t('common.Create')
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const defaultActions = [
|
||||
{
|
||||
name: 'actionCreate',
|
||||
title: this.createTitle,
|
||||
type: 'primary',
|
||||
has: this.hasCreate && !this.moreCreates,
|
||||
can: this.canCreate,
|
||||
callback: this.handleCreate
|
||||
}
|
||||
]
|
||||
if (this.moreCreates) {
|
||||
const defaultMoreCreate = {
|
||||
name: 'actionMoreCreate',
|
||||
title: this.createTitle,
|
||||
type: 'primary',
|
||||
has: true,
|
||||
can: this.canCreate,
|
||||
dropdown: [],
|
||||
callback: this.handleCreate
|
||||
}
|
||||
const createCreateAction = Object.assign(defaultMoreCreate, this.moreCreates)
|
||||
defaultActions.push(createCreateAction)
|
||||
}
|
||||
return {
|
||||
defaultActions: defaultActions,
|
||||
defaultActions: [
|
||||
{
|
||||
name: 'actionCreate',
|
||||
title: this.$t('common.Create'),
|
||||
type: 'primary',
|
||||
has: this.hasCreate,
|
||||
can: true,
|
||||
callback: this.handleCreate
|
||||
}
|
||||
],
|
||||
defaultMoreActions: [
|
||||
{
|
||||
title: this.$t('common.deleteSelected'),
|
||||
@@ -118,9 +92,6 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iActions() {
|
||||
return [...this.actions, this.moreAction]
|
||||
},
|
||||
actions() {
|
||||
const actions = [...this.defaultActions, ...this.extraActions]
|
||||
return cleanActions(actions, true, {
|
||||
@@ -128,20 +99,12 @@ export default {
|
||||
reloadTable: this.reloadTable
|
||||
})
|
||||
},
|
||||
moreAction() {
|
||||
if (!this.hasMoreActions) {
|
||||
return
|
||||
}
|
||||
let dropdown = [...this.defaultMoreActions, ...this.extraMoreActions]
|
||||
dropdown = cleanActions(dropdown, true, {
|
||||
moreActions() {
|
||||
const actions = [...this.defaultMoreActions, ...this.extraMoreActions]
|
||||
return cleanActions(actions, true, {
|
||||
selectedRows: this.selectedRows,
|
||||
reloadTable: this.reloadTable
|
||||
})
|
||||
return {
|
||||
name: 'moreActions',
|
||||
title: this.moreActionsTitle || this.$t('common.MoreActions'),
|
||||
dropdown: dropdown
|
||||
}
|
||||
},
|
||||
hasSelectedRows() {
|
||||
return this.selectedRows.length > 0
|
||||
@@ -149,13 +112,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
handleCreate() {
|
||||
let route
|
||||
let route = {}
|
||||
if (typeof this.createRoute === 'string') {
|
||||
route = { name: this.createRoute }
|
||||
route.name = this.createRoute
|
||||
} else if (typeof this.createRoute === 'function') {
|
||||
route = this.createRoute()
|
||||
} else if (typeof this.createRoute === 'object') {
|
||||
} else {
|
||||
route = this.createRoute
|
||||
}
|
||||
this.$router.push(route)
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<ActionsGroup :is-fa="true" :actions="rightSideActions" class="right-side-actions right-side-item" />
|
||||
<ImExportDialog
|
||||
:selected-rows="selectedRows"
|
||||
:export-options="iExportOptions"
|
||||
:import-options="iImportOptions"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
<ImExportDialog :selected-rows="selectedRows" :url="tableUrl" v-bind="$attrs" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -14,7 +9,6 @@
|
||||
import ActionsGroup from '@/components/ActionsGroup'
|
||||
import ImExportDialog from './ImExportDialog'
|
||||
import { cleanActions } from './utils'
|
||||
import { assignIfNot } from '@/utils/common'
|
||||
|
||||
const defaultTrue = { type: Boolean, default: true }
|
||||
|
||||
@@ -30,32 +24,17 @@ export default {
|
||||
default: ''
|
||||
},
|
||||
hasExport: defaultTrue,
|
||||
exportOptions: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
handleExportClick: {
|
||||
handleExport: {
|
||||
type: Function,
|
||||
default: function({ selectedRows }) {
|
||||
this.$eventBus.$emit('showExportDialog', { selectedRows, url: this.tableUrl, name: this.name })
|
||||
this.$eventBus.$emit('showExportDialog', { selectedRows })
|
||||
}
|
||||
},
|
||||
hasImport: defaultTrue,
|
||||
importOptions: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
handleImportClick: {
|
||||
handleImport: {
|
||||
type: Function,
|
||||
default: function({ selectedRows }) {
|
||||
this.$eventBus.$emit('showImportDialog', { selectedRows, url: this.tableUrl, name: this.name })
|
||||
}
|
||||
},
|
||||
hasColumnSetting: defaultTrue,
|
||||
handleTableSettingClick: {
|
||||
type: Function,
|
||||
default: function({ selectedRows }) {
|
||||
this.$eventBus.$emit('showColumnSettingPopover', { url: this.tableUrl, row: selectedRows, name: this.name })
|
||||
this.$eventBus.$emit('showImportDialog', { selectedRows })
|
||||
}
|
||||
},
|
||||
hasRefresh: defaultTrue,
|
||||
@@ -75,12 +54,12 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
defaultRightSideActions: [
|
||||
{ name: 'actionColumnSetting', fa: 'fa-cog', tip: this.$t('common.CustomCol'), has: this.hasColumnSetting, callback: this.handleTableSettingClick.bind(this) },
|
||||
{ name: 'actionExport', fa: 'fa-download', tip: this.$t('common.Export'), has: this.hasExport, callback: this.handleExportClick.bind(this) },
|
||||
{ name: 'actionImport', fa: 'fa-upload', tip: this.$t('common.Import'), has: this.hasImport, callback: this.handleImportClick.bind(this) },
|
||||
{ name: 'actionExport', fa: 'fa-download', tip: this.$t('common.Export'), has: this.hasExport, callback: this.handleExport.bind(this) },
|
||||
{ name: 'actionImport', fa: 'fa-upload', tip: this.$t('common.Import'), has: this.hasImport, callback: this.handleImport.bind(this) },
|
||||
{ name: 'actionRefresh', fa: 'fa-refresh', tip: this.$t('common.Refresh'), has: this.hasRefresh, callback: this.handleRefresh }
|
||||
],
|
||||
dialogExportVisible: false
|
||||
dialogExportVisible: false,
|
||||
exportValue: 2
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -94,19 +73,18 @@ export default {
|
||||
},
|
||||
hasSelectedRows() {
|
||||
return this.selectedRows.length > 0
|
||||
},
|
||||
iImportOptions() {
|
||||
return assignIfNot(this.importOptions, { url: this.tableUrl })
|
||||
},
|
||||
iExportOptions() {
|
||||
const options = assignIfNot(this.exportOptions, { url: this.tableUrl })
|
||||
return options
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTagSearch(val) {
|
||||
this.searchTable(val)
|
||||
},
|
||||
// handleExport({ selectedRows }) {
|
||||
// this.$eventBus.$emit('showExportDialog', { selectedRows })
|
||||
// },
|
||||
// handleImport({ selectedRows }) {
|
||||
// this.$eventBus.$emit('showImportDialog', { selectedRows })
|
||||
// },
|
||||
handleRefresh() {
|
||||
this.reloadTable()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<script>
|
||||
import AutoDataSearch from '@/components/AutoDataSearch'
|
||||
import LeftSide from './LeftSide'
|
||||
import DatetimeRangePicker from '@/components/FormFields/DatetimeRangePicker'
|
||||
import DatetimeRangePicker from '@/components/DatetimeRangePicker'
|
||||
import RightSide from './RightSide'
|
||||
|
||||
const defaultTrue = { type: Boolean, default: true }
|
||||
|
||||
@@ -5,9 +5,7 @@ export function cleanActions(actions, canDefaults, { selectedRows, reloadTable }
|
||||
cloneActions.forEach((action) => {
|
||||
action.has = cleanBoolean(action, 'has', true, { selectedRows, reloadTable })
|
||||
action.can = cleanBoolean(action, 'can', true, { selectedRows, reloadTable })
|
||||
if (!action.dropdown) {
|
||||
action.callback = cleanCallback(action, { selectedRows, reloadTable })
|
||||
}
|
||||
action.callback = cleanCallback(action, { selectedRows, reloadTable })
|
||||
cleanedActions.push(action)
|
||||
})
|
||||
return cleanedActions
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
<template>
|
||||
<ActionsGroup v-loading="loadingStatus" :size="'mini'" :actions="actions" :more-actions="moreActions" :more-actions-title="moreActionsTitle" />
|
||||
<ActionsGroup :size="'mini'" :actions="actions" :more-actions="moreActions" :more-actions-title="moreActionsTitle" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActionsGroup from '@/components/ActionsGroup'
|
||||
import ActionsGroup from '@/components/ActionsGroup/index'
|
||||
import BaseFormatter from './base'
|
||||
|
||||
const defaultPerformDelete = function({ row, col }) {
|
||||
const id = row.id
|
||||
const url = new URL(this.url, location.origin)
|
||||
url.pathname += `${id}/`
|
||||
const deleteUrl = url.href
|
||||
return this.$axios.delete(deleteUrl)
|
||||
const url = `${this.url}${id}/`
|
||||
return this.$axios.delete(url)
|
||||
}
|
||||
|
||||
const defaultUpdateCallback = function({ row, col }) {
|
||||
const id = row.id
|
||||
let route = { params: { id: id }}
|
||||
@@ -84,15 +81,11 @@ export default {
|
||||
default: function() {
|
||||
return {
|
||||
hasUpdate: true, // can set function(row, value)
|
||||
canUpdate: () => {
|
||||
return !this.$store.getters.currentOrgIsRoot
|
||||
}, // can set function(row, value)
|
||||
canUpdate: true, // can set function(row, value)
|
||||
hasDelete: true, // can set function(row, value)
|
||||
canDelete: true,
|
||||
hasClone: true,
|
||||
canClone: () => {
|
||||
return !this.$store.getters.currentOrgIsRoot
|
||||
},
|
||||
hasClone: false,
|
||||
canClone: true,
|
||||
updateRoute: this.$route.name.replace('List', 'Update'),
|
||||
cloneRoute: this.$route.name.replace('List', 'Create'),
|
||||
performDelete: defaultPerformDelete,
|
||||
@@ -147,12 +140,10 @@ export default {
|
||||
let actions = [...this.defaultActions, ...this.extraActions]
|
||||
actions = _.cloneDeep(actions)
|
||||
actions = actions.map((v) => {
|
||||
v.has = this.cleanBoolean(v, 'has', true)
|
||||
v.can = this.cleanBoolean(v, 'can', true)
|
||||
v.callback = this.cleanCallback(v, 'callback')
|
||||
v.fa = this.cleanValue(v, 'fa')
|
||||
v.has = this.cleanBoolean(v, 'has')
|
||||
v.can = this.cleanBoolean(v, 'can')
|
||||
v.callback = this.cleanCallback(v)
|
||||
v.order = v.order || 100
|
||||
v.tip = this.cleanValue(v, 'tip')
|
||||
return v
|
||||
})
|
||||
actions = actions.filter((v) => v.has)
|
||||
@@ -170,21 +161,18 @@ export default {
|
||||
return []
|
||||
}
|
||||
return this.cleanedActions.slice(1, this.cleanedActions.length)
|
||||
},
|
||||
loadingStatus() {
|
||||
return this.col.formatterArgs.loading
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cleanBoolean(item, attr, defaults) {
|
||||
cleanBoolean(item, attr) {
|
||||
const ok = item[attr]
|
||||
if (typeof ok !== 'function') {
|
||||
return ok === undefined ? defaults : ok
|
||||
return ok === undefined ? true : ok
|
||||
}
|
||||
return this.cleanValue(item, attr)
|
||||
return ok(this.row, this.cellValue)
|
||||
},
|
||||
cleanCallback(item, attr) {
|
||||
const callback = item[attr]
|
||||
cleanCallback(item) {
|
||||
const callback = item.callback
|
||||
const attrs = {
|
||||
reload: this.reload,
|
||||
row: this.row,
|
||||
@@ -193,20 +181,6 @@ export default {
|
||||
tableData: this.tableData
|
||||
}
|
||||
return () => { return callback.bind(this)(attrs) }
|
||||
},
|
||||
cleanValue(item, attr) {
|
||||
const value = item[attr]
|
||||
if (!value || typeof value !== 'function') {
|
||||
return value
|
||||
}
|
||||
const attrs = {
|
||||
reload: this.reload,
|
||||
row: this.row,
|
||||
col: this.col,
|
||||
cellValue: this.cellValue,
|
||||
tableData: this.tableData
|
||||
}
|
||||
return value(attrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-tooltip v-if="formatterArgs.hasTips" placement="bottom" effect="dark">
|
||||
<div slot="content" v-html="tips" />
|
||||
<div slot="content">{{ tipStatus }}<br>{{ tipTime }}</div>
|
||||
<i :class="'fa ' + iconClass" />
|
||||
</el-tooltip>
|
||||
<i v-else :class="'fa ' + iconClass" />
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
<script>
|
||||
import BaseFormatter from './base'
|
||||
import { toSafeLocalDateStr } from '@/utils/common'
|
||||
export default {
|
||||
name: 'ChoicesFormatter',
|
||||
extends: BaseFormatter,
|
||||
@@ -22,12 +23,18 @@ export default {
|
||||
true: 'fa-check text-primary',
|
||||
false: 'fa-times text-danger'
|
||||
},
|
||||
getIconKey({ row, cellValue }) {
|
||||
return cellValue
|
||||
typeChange(val) {
|
||||
return !!val
|
||||
},
|
||||
hasTips: false,
|
||||
getTips: ({ row, cellValue }) => {
|
||||
return cellValue
|
||||
tipStatus(val, vm) {
|
||||
if (val.status === 0) {
|
||||
return vm.$t('assets.Unreachable')
|
||||
} else if (val.status === 1) {
|
||||
return vm.$t('assets.Reachable')
|
||||
} else if (val.status === 2) {
|
||||
return vm.$t('assets.Unknown')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,11 +47,15 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
iconClass() {
|
||||
const key = this.formatterArgs.getIconKey({ row: this.row, cellValue: this.cellValue })
|
||||
const key = this.formatterArgs.typeChange(this.cellValue)
|
||||
return this.formatterArgs.iconChoices[key]
|
||||
},
|
||||
tips() {
|
||||
return this.formatterArgs.getTips({ cellValue: this.cellValue, row: this.row })
|
||||
tipStatus() {
|
||||
const vm = this
|
||||
return this.formatterArgs.tipStatus(this.cellValue, vm)
|
||||
},
|
||||
tipTime() {
|
||||
return toSafeLocalDateStr(this.cellValue.datetime)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<ActionsGroup :size="'mini'" :actions="cleanedActions" :more-actions="cleanMoreActions" v-bind="$attrs" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActionsGroup from '@/components/ActionsGroup/index'
|
||||
import BaseFormatter from './base'
|
||||
|
||||
export default {
|
||||
name: 'CustomActionsFormatterVue',
|
||||
components: {
|
||||
ActionsGroup
|
||||
},
|
||||
extends: BaseFormatter,
|
||||
computed: {
|
||||
cleanedActions() {
|
||||
if (this.col.actions.actions instanceof Array) {
|
||||
const copy = _.cloneDeep(this.col.actions.actions)
|
||||
let actions = [...copy]
|
||||
actions = actions.map((v) => {
|
||||
v.has = this.checkBool(v, 'has')
|
||||
v.can = this.checkBool(v, 'can')
|
||||
v.callback = this.cleanCallback(v)
|
||||
return v
|
||||
})
|
||||
return actions
|
||||
}
|
||||
return []
|
||||
},
|
||||
cleanMoreActions() {
|
||||
if (this.col.actions.extraActions instanceof Array) {
|
||||
const copy = _.cloneDeep(this.col.actions.extraActions)
|
||||
let actions = [...copy]
|
||||
actions = actions.map((v) => {
|
||||
v.has = this.checkBool(v, 'has')
|
||||
v.can = this.checkBool(v, 'can')
|
||||
v.callback = this.cleanCallback(v)
|
||||
return v
|
||||
})
|
||||
return actions
|
||||
}
|
||||
return []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// console.log(this.col)
|
||||
},
|
||||
methods: {
|
||||
checkBool(item, attr, defaults) {
|
||||
if (!item) {
|
||||
return false
|
||||
}
|
||||
let ok = item[attr]
|
||||
if (ok && typeof ok === 'function') {
|
||||
ok = ok(this.row, this.cellValue)
|
||||
} else if (ok == null) {
|
||||
ok = defaults === undefined ? true : defaults
|
||||
}
|
||||
return ok
|
||||
},
|
||||
cleanCallback(item) {
|
||||
const callback = item.callback
|
||||
const attrs = {
|
||||
reload: this.reload,
|
||||
row: this.row,
|
||||
col: this.col,
|
||||
cellValue: this.cellValue,
|
||||
tableData: this.tableData
|
||||
}
|
||||
return () => { return callback.bind(this)(attrs) }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-button ref="deleteButton" size="mini" type="danger" :disabled="iDisabled" @click="onDelete(col, row, cellValue, reload)">
|
||||
<el-button ref="deleteButton" size="mini" type="danger" :disabled="canDelete" @click="onDelete(col, row, cellValue, reload)">
|
||||
<i class="fa fa-minus" />
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -11,19 +11,18 @@ export default {
|
||||
name: 'DeleteActionFormatter',
|
||||
extends: BaseFormatter,
|
||||
computed: {
|
||||
iDisabled() {
|
||||
// 禁用
|
||||
return (this.disabled() || this.$store.getters.currentOrgIsRoot)
|
||||
canDelete() {
|
||||
return this.iCanDelete()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
defaultOnDelete(col, row, cellValue, reload) {
|
||||
const url = col.deleteUrl + cellValue
|
||||
this.$axios.delete(url).then(res => {
|
||||
this.$message.success(this.$tc('common.deleteSuccessMsg'))
|
||||
this.$message.success(this.$t('common.deleteSuccessMsg'))
|
||||
reload()
|
||||
}).catch(error => {
|
||||
this.$message.error(this.$t('common.deleteErrorMsg') + ' ' + error)
|
||||
this.$message.error(this.$t('common.deleteErrorMsg' + ' ' + error))
|
||||
})
|
||||
},
|
||||
onDelete(col, row, cellValue, reload) {
|
||||
@@ -33,7 +32,7 @@ export default {
|
||||
this.defaultOnDelete(col, row, cellValue, reload)
|
||||
}
|
||||
},
|
||||
disabled() {
|
||||
iCanDelete() {
|
||||
if (this.col.objects === 'all') {
|
||||
return false
|
||||
}
|
||||
116
src/components/ListTable/formatters/LoadingActionsFormatter.vue
Normal file
116
src/components/ListTable/formatters/LoadingActionsFormatter.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<ActionsGroup v-loading="loadStatus" :size="'mini'" :actions="actions" :more-actions="moreActions" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActionsGroup from '@/components/ActionsGroup/index'
|
||||
import BaseFormatter from './base'
|
||||
|
||||
export default {
|
||||
name: 'LoadingActionsFormatter',
|
||||
components: { ActionsGroup },
|
||||
extends: BaseFormatter,
|
||||
props: {
|
||||
formatterArgsDefault: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {
|
||||
hasUpdate: true, // can set function(row, value)
|
||||
canUpdate: true, // can set function(row, value)
|
||||
hasDelete: true, // can set function(row, value)
|
||||
canDelete: true,
|
||||
updateRoute: this.$route.name.replace('List', 'Update'),
|
||||
extraActions: [] // format see defaultActions
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const colActions = Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
|
||||
const defaultActions = [
|
||||
{
|
||||
name: 'update',
|
||||
title: this.$t('common.Update'),
|
||||
type: 'primary',
|
||||
has: colActions.hasUpdate,
|
||||
can: colActions.canUpdate,
|
||||
callback: colActions.onUpdate
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
title: this.$t('common.Delete'),
|
||||
type: 'danger',
|
||||
has: colActions.hasDelete,
|
||||
can: colActions.canDelete,
|
||||
callback: colActions.onDelete
|
||||
}
|
||||
]
|
||||
return {
|
||||
colActions: colActions,
|
||||
defaultActions: defaultActions,
|
||||
extraActions: colActions.extraActions
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cleanedActions() {
|
||||
let actions = [...this.defaultActions, ...this.extraActions]
|
||||
actions = _.cloneDeep(actions)
|
||||
actions = actions.map((v) => {
|
||||
v.has = this.cleanBoolean(v, 'has')
|
||||
v.can = this.cleanBoolean(v, 'can')
|
||||
v.fa = this.cleanFa(v, 'fa')
|
||||
v.callback = this.cleanCallback(v)
|
||||
return v
|
||||
})
|
||||
actions = actions.filter((v) => v.has)
|
||||
return actions
|
||||
},
|
||||
actions() {
|
||||
if (this.cleanedActions.length <= 2) {
|
||||
return this.cleanedActions
|
||||
}
|
||||
return this.cleanedActions.slice(0, 1)
|
||||
},
|
||||
moreActions() {
|
||||
if (this.cleanedActions.length <= 2) {
|
||||
return []
|
||||
}
|
||||
return this.cleanedActions.slice(1, this.cleanedActions.length)
|
||||
},
|
||||
loadStatus() {
|
||||
return this.col.formatterArgs.loading
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cleanBoolean(item, attr) {
|
||||
const ok = item[attr]
|
||||
if (typeof ok !== 'function') {
|
||||
return ok === undefined ? true : ok
|
||||
}
|
||||
return ok(this.row, this.cellValue)
|
||||
},
|
||||
cleanCallback(item) {
|
||||
const callback = item.callback
|
||||
const attrs = {
|
||||
reload: this.reload,
|
||||
row: this.row,
|
||||
col: this.col,
|
||||
cellValue: this.cellValue,
|
||||
tableData: this.tableData
|
||||
}
|
||||
return () => { return callback.bind(this)(attrs) }
|
||||
},
|
||||
cleanFa(item, attr) {
|
||||
const ok = item[attr]
|
||||
if (typeof ok !== 'function') {
|
||||
return ok === undefined ? false : ok
|
||||
}
|
||||
return ok(this.row, this.cellValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,45 +1,39 @@
|
||||
import DetailFormatter from './DetailFormatter'
|
||||
import ArrayFormatter from './ArrayFormatter'
|
||||
import DisplayFormatter from './DisplayFormatter'
|
||||
import BooleanFormatter from './BooleanFormatter'
|
||||
import ChoicesFormatter from './ChoicesFormatter'
|
||||
import BooleanFormatter from './ChoicesFormatter'
|
||||
import ActionsFormatter from './ActionsFormatter'
|
||||
import CustomActionsFormatter from './CustomActionsFormatter'
|
||||
import DeleteActionFormatter from './DeleteActionFormatter'
|
||||
import DateFormatter from './DateFormatter'
|
||||
import SystemUserFormatter from './GrantedSystemUsersShowFormatter'
|
||||
import ShowKeyFormatter from '@/components/TableFormatters/ShowKeyFormatter'
|
||||
import ShowKeyFormatter from '@/components/ListTable/formatters/ShowKeyFormatter'
|
||||
import DialogDetailFormatter from './DialogDetailFormatter'
|
||||
import EditableInputFormatter from './EditableInputFormatter'
|
||||
import StatusFormatter from './StatusFormatter'
|
||||
import LoadingActionsFormatter from './LoadingActionsFormatter'
|
||||
|
||||
export default {
|
||||
DetailFormatter,
|
||||
DisplayFormatter,
|
||||
BooleanFormatter,
|
||||
ChoicesFormatter,
|
||||
ActionsFormatter,
|
||||
CustomActionsFormatter,
|
||||
DeleteActionFormatter,
|
||||
DateFormatter,
|
||||
SystemUserFormatter,
|
||||
ShowKeyFormatter,
|
||||
DialogDetailFormatter,
|
||||
ArrayFormatter,
|
||||
EditableInputFormatter,
|
||||
StatusFormatter
|
||||
LoadingActionsFormatter
|
||||
}
|
||||
|
||||
export {
|
||||
DetailFormatter,
|
||||
DisplayFormatter,
|
||||
BooleanFormatter,
|
||||
ChoicesFormatter,
|
||||
ActionsFormatter,
|
||||
CustomActionsFormatter,
|
||||
DeleteActionFormatter,
|
||||
DateFormatter,
|
||||
SystemUserFormatter,
|
||||
ShowKeyFormatter,
|
||||
DialogDetailFormatter,
|
||||
ArrayFormatter,
|
||||
EditableInputFormatter,
|
||||
StatusFormatter
|
||||
LoadingActionsFormatter
|
||||
}
|
||||
@@ -42,9 +42,21 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
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)
|
||||
@@ -74,11 +86,9 @@ export default {
|
||||
this.dataTable.getList()
|
||||
},
|
||||
search(attrs) {
|
||||
this.$emit('TagSearch', attrs)
|
||||
return this.dataTable.search(attrs, true)
|
||||
},
|
||||
filter(attrs) {
|
||||
this.$emit('TagFilter', attrs)
|
||||
this.$refs.dataTable.$refs.dataTable.search(attrs, true)
|
||||
},
|
||||
handleDateChange(attrs) {
|
||||
@@ -92,7 +102,6 @@ export default {
|
||||
date_from: attrs[0].toISOString(),
|
||||
date_to: attrs[1].toISOString()
|
||||
}
|
||||
this.$emit('TagDateChange', attrs)
|
||||
return this.dataTable.searchDate(query)
|
||||
},
|
||||
toggleRowSelection(row, isSelected) {
|
||||
@@ -113,6 +122,15 @@ export default {
|
||||
& >>> .el-table__header thead > tr > th {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/*& >>> .el-table--striped .el-table__body tr.el-table__row--striped td {*/
|
||||
/*background: white;*/
|
||||
/*}*/
|
||||
|
||||
/*& >>> .el-table th, .el-table tr {*/
|
||||
/*background-color: red;*/
|
||||
/*!*background-color: #FAFAFA;*!*/
|
||||
/*}*/
|
||||
}
|
||||
|
||||
//修改颜色
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:title="$t('common.MFAVerify')"
|
||||
:width="'50'"
|
||||
:show-confirm="false"
|
||||
:show-cancel="false"
|
||||
:visible.sync="visible"
|
||||
:destroy-on-close="true"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4">
|
||||
<div style="line-height: 34px;text-align: center">MFA</div>
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
<el-input v-model="MFAToken" />
|
||||
<span class="help-tips help-block">{{ $t('common.MFARequireForSecurity') }}</span>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-button size="mini" type="primary" style="line-height:20px " @click="verifyMFA">
|
||||
{{ this.$t('common.Confirm') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
|
||||
export default {
|
||||
name: 'MFAVerifyDialog',
|
||||
components: {
|
||||
Dialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
MFAToken: '',
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (!val) {
|
||||
this.$emit('MFAVerifyCancel', true)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$axios.get('/api/v1/authentication/otp/verify/', { disableFlashErrorMsg: true }).then(() => {
|
||||
this.$emit('MFAVerifyDone', true)
|
||||
}).catch(err => {
|
||||
this.$log.debug('Verify otp code error: ', err)
|
||||
this.visible = true
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
verifyMFA() {
|
||||
if (this.MFAToken.length !== 6) {
|
||||
return this.$message.error(this.$tc('common.MFAErrorMsg'))
|
||||
}
|
||||
this.$axios.post(
|
||||
`/api/v1/authentication/otp/verify/`, {
|
||||
code: this.MFAToken
|
||||
}
|
||||
).then(res => {
|
||||
this.$emit('MFAVerifyDone', true)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -10,7 +10,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Switcher from '../FormFields/Swicher'
|
||||
import Switcher from '../Swicher'
|
||||
export default {
|
||||
name: 'ActionItem',
|
||||
components: {
|
||||
|
||||
@@ -1,28 +1,22 @@
|
||||
<template>
|
||||
<IBox :fa="icon" :type="type" :title="title" v-bind="$attrs">
|
||||
<table style="width: 100%;table-layout:fixed;" class="CardTable">
|
||||
<table style="width: 100%">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<Select2 ref="select2" v-model="select2.value" :disabled="iDisabled" v-bind="select2" />
|
||||
<Select2 ref="select2" v-model="select2.value" v-bind="select2" />
|
||||
</td>
|
||||
</tr>
|
||||
<slot />
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<el-button :type="type" size="small" :loading="submitLoading" :disabled="iDisabled" @click="addObjects">
|
||||
{{ $t('common.Add') }}
|
||||
</el-button>
|
||||
<el-button :type="type" size="small" :loading="submitLoading" @click="addObjects">{{ $t('common.Add') }}</el-button>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-if="showHasObjects">
|
||||
<tr v-for="obj of iHasObjects" :key="obj.value" class="item">
|
||||
<td style="width: 100%;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;">
|
||||
<el-tooltip style="margin: 4px;" effect="dark" :content="obj.label" placement="left">
|
||||
<b>{{ obj.label }}</b>
|
||||
</el-tooltip>
|
||||
</td>
|
||||
<tr v-for="obj of iHasObjects" :key="obj.value" style="width: 100%" class="item">
|
||||
<td><b>{{ obj.label }}</b></td>
|
||||
<td>
|
||||
<el-button size="mini" :disabled="iDisabled" type="danger" style="float: right" @click="removeObject(obj)">
|
||||
<el-button size="mini" type="danger" style="float: right" @click="removeObject(obj)">
|
||||
<i class="fa fa-minus" />
|
||||
</el-button>
|
||||
</td>
|
||||
@@ -30,7 +24,7 @@
|
||||
</template>
|
||||
<tr v-if="params.hasMore && showHasMore" class="item">
|
||||
<td colspan="2">
|
||||
<el-button :type="type" :disabled="iDisabled" size="small" style="width: 100%" @click="loadMore">
|
||||
<el-button :type="type" size="small" style="width: 100%" @click="loadMore">
|
||||
<i class="fa fa-arrow-down" />
|
||||
{{ $t('common.More') }}
|
||||
</el-button>
|
||||
@@ -41,10 +35,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Select2 from '../FormFields/Select2'
|
||||
import Select2 from '../Select2'
|
||||
import IBox from '../IBox'
|
||||
import { createSourceIdCache } from '@/api/common'
|
||||
import { mapGetters } from 'vuex'
|
||||
export default {
|
||||
name: 'RelationCard',
|
||||
components: {
|
||||
@@ -90,10 +83,6 @@ export default {
|
||||
type: [Array, Number, String],
|
||||
default: () => []
|
||||
},
|
||||
disabled: {
|
||||
type: [Boolean, Function],
|
||||
default: null
|
||||
},
|
||||
showHasMore: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@@ -145,13 +134,11 @@ export default {
|
||||
ajax: this.objectsAjax,
|
||||
options: this.objects,
|
||||
value: this.value,
|
||||
disabled: this.disabled,
|
||||
disabledValues: []
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['currentOrgIsRoot']),
|
||||
iAjax() {
|
||||
return this.$refs.select2.iAjax
|
||||
},
|
||||
@@ -160,12 +147,6 @@ export default {
|
||||
},
|
||||
hasObjectLeftLength() {
|
||||
return this.totalHasObjectsLength - this.iHasObjects.length
|
||||
},
|
||||
iDisabled() {
|
||||
if (this.disabled !== null) {
|
||||
return this.disabled
|
||||
}
|
||||
return this.currentOrgIsRoot
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -4,16 +4,13 @@
|
||||
<span class="header-title">{{ title }}</span>
|
||||
<span class="pull-right right-side">
|
||||
<slot name="header-right">
|
||||
<el-tag :type="rightSideLabel.type || 'success'" effect="dark" size="mini">
|
||||
{{ rightSideLabel.title }}
|
||||
</el-tag>
|
||||
<el-tag :type="rightSideLabel.type || 'success'" effect="dark" size="mini">{{ rightSideLabel.title }}</el-tag>
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
<slot>
|
||||
<h1 class="no-margins">
|
||||
<span v-if="body.disabled" class="disabled-link">{{ body.count }}</span>
|
||||
<router-link v-else :to="body.route">
|
||||
<router-link :to="body.route">
|
||||
<span>{{ body.count }}</span>
|
||||
</router-link>
|
||||
</h1>
|
||||
@@ -77,8 +74,4 @@ export default {
|
||||
.no-margins {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.disabled-link {
|
||||
color: #428bca;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<span>{{ cellValue.toString() }}</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseFormatter from './base'
|
||||
export default {
|
||||
name: 'ArrayFormatter',
|
||||
extends: BaseFormatter
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<i :class="'fa ' + iconClass" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseFormatter from './base'
|
||||
export default {
|
||||
name: 'ChoicesFormatter',
|
||||
extends: BaseFormatter,
|
||||
props: {
|
||||
formatterArgsDefault: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
iconChoices: {
|
||||
true: 'fa-check text-primary',
|
||||
false: 'fa-times text-danger'
|
||||
},
|
||||
typeChange(val) {
|
||||
return !!val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconClass() {
|
||||
const key = this.formatterArgs.typeChange(this.cellValue)
|
||||
return this.formatterArgs.iconChoices[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,84 +0,0 @@
|
||||
<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>
|
||||
@@ -1,68 +0,0 @@
|
||||
<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>
|
||||
@@ -5,10 +5,8 @@
|
||||
<component
|
||||
:is="component"
|
||||
ref="AutoDataZTree"
|
||||
:key="componentTreeKey"
|
||||
:setting="treeSetting"
|
||||
class="auto-data-ztree"
|
||||
v-on="$listeners"
|
||||
@urlChange="handleUrlChange"
|
||||
>
|
||||
<div slot="rMenu" slot-scope="{data}">
|
||||
@@ -24,7 +22,7 @@
|
||||
</div>
|
||||
<div class="transition-box" style="width: calc(100% - 17px);">
|
||||
<slot name="table">
|
||||
<ListTable ref="ListTable" :key="componentKey" :table-config="iTableConfig" :header-actions="headerActions" v-on="$listeners" />
|
||||
<ListTable ref="ListTable" :key="componentKey" :table-config="iTableConfig" :header-actions="headerActions" />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,16 +63,10 @@ export default {
|
||||
return {
|
||||
iTableConfig: this.tableConfig,
|
||||
iShowTree: this.showTree,
|
||||
componentKey: 0,
|
||||
componentTreeKey: 0
|
||||
componentKey: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
treeConfig: {
|
||||
handler(val) {
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleUrlChange(_url) {
|
||||
@@ -85,20 +77,11 @@ export default {
|
||||
forceRerender() {
|
||||
this.componentKey += 1
|
||||
},
|
||||
forceRerenderTree() {
|
||||
this.componentTreeKey += 1
|
||||
},
|
||||
hideRMenu() {
|
||||
this.$refs.AutoDataZTree.hideRMenu()
|
||||
},
|
||||
getSelectedNodes: function() {
|
||||
return this.$refs.AutoDataZTree.getSelectedNodes()
|
||||
},
|
||||
getNodes: function() {
|
||||
return this.$refs.AutoDataZTree.getNodes()
|
||||
},
|
||||
selectNode: function(node) {
|
||||
return this.$refs.AutoDataZTree.selectNode(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
@@ -7,7 +7,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PasswordInput from './PasswordInput'
|
||||
import PasswordInput from '../PasswordInput'
|
||||
import { mapGetters } from 'vuex'
|
||||
import store from '@/store'
|
||||
import i18n from '@/i18n/i18n'
|
||||
@@ -12,16 +12,14 @@ export { default as FormGroupHeader } from './FormGroupHeader'
|
||||
export { default as Hamburger } from './Hamburger'
|
||||
export { default as ListTable } from './ListTable'
|
||||
export { default as RelationCard } from './RelationCard'
|
||||
export { default as Select2 } from './FormFields/Select2'
|
||||
export { default as UploadKey } from './FormFields/UploadKey.vue'
|
||||
export { default as Select2 } from './Select2'
|
||||
export { default as AssetSelect } from './AssetSelect'
|
||||
export { default as SvgIcon } from './SvgIcon'
|
||||
export { default as TreeTable } from './TreeTable'
|
||||
export { default as IBox } from './IBox'
|
||||
export { default as QuickActions } from './QuickActions'
|
||||
export { default as Switcher } from './FormFields/Swicher'
|
||||
export { default as Switcher } from './Swicher'
|
||||
export { default as SummaryCard } from './SummaryCard'
|
||||
export { default as UploadField } from './FormFields/UploadField'
|
||||
export { default as AccountListTable } from './AccountListTable/index'
|
||||
export { default as UploadField } from './UploadField'
|
||||
export { default as AssetUserTable } from './AssetUserTable'
|
||||
export { default as AssetRelationCard } from './AssetRelationCard'
|
||||
export { default as MFAVerifyDialog } from './MFAVerifyDialog'
|
||||
|
||||
@@ -1,37 +1,7 @@
|
||||
{
|
||||
"": "",
|
||||
"accounts": {
|
||||
"PleaseClickLeftAssetToViewAssetAccount": "资产账号列表,点击左侧资产进行查看",
|
||||
"PleaseClickLeftApplicationToViewApplicationAccount": "应用账号列表,点击左侧应用进行查看",
|
||||
"PleaseClickLeftAssetToViewGatheredUser": "收集用户列表,点击左侧资产进行查看"
|
||||
},
|
||||
"acl": {
|
||||
"name": "名称",
|
||||
"username": "用户名",
|
||||
"ip_group": "IP 组",
|
||||
"action": "动作",
|
||||
"priority": "优先级",
|
||||
"date_created": "创建时间",
|
||||
"created_by": "创建者",
|
||||
"asset": "资产信息",
|
||||
"users":"用户信息",
|
||||
"system_user": "系统用户",
|
||||
"username_group":"用户名",
|
||||
"hostname_group":"资产名",
|
||||
"asset_ip_group": "资产IP",
|
||||
"system_users_name_group": "系统用户名称",
|
||||
"system_users_protocol_group": "系统用户协议",
|
||||
"system_users_username_group": "系统用户名",
|
||||
"apply_login_asset": "申请登录资产",
|
||||
"apply_login_system_user": "申请登录系统用户",
|
||||
"apply_login_user": "申请登录用户",
|
||||
"RuleDetail": "规则详情"
|
||||
},
|
||||
"applications": {
|
||||
"": "",
|
||||
"RemoteApp": "远程应用",
|
||||
"Database": "数据库",
|
||||
"Cloud": "云",
|
||||
"applicationsType": {
|
||||
"chrome": "Chrome",
|
||||
"mysql_workbench": "MySQL Workbench",
|
||||
@@ -83,13 +53,11 @@
|
||||
"DBInfo": "数据库信息"
|
||||
},
|
||||
"assets": {
|
||||
"AssociateAssets": "关联资产",
|
||||
"AssociateNodes": "关联节点",
|
||||
"Action": "动作",
|
||||
"ActiveSelected": "激活所选",
|
||||
"AdminUser": "特权用户",
|
||||
"AdminUser": "管理用户",
|
||||
"AdminUserDetail": "管理用户详情",
|
||||
"AdminUserListHelpMessage": "<b>特权用户</b> 是资产已存在的, 并且拥有 高级权限 的系统用户, 如 root 或 拥有 `NOPASSWD: ALL` sudo 权限的用户。 JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等。",
|
||||
"AdminUserListHelpMessage": "管理用户是资产(被控服务器)上的 root,或拥有 NOPASSWD: ALL sudo 权限的用户, JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等。\n",
|
||||
"Asset": "资产",
|
||||
"HardwareInfo": "硬件信息",
|
||||
"AssetDetail": "资产详情",
|
||||
@@ -100,11 +68,11 @@
|
||||
"TestGatewayHelpMessage": "如果使用了nat端口映射,请设置为ssh真实监听的端口",
|
||||
"SshPort": "SSH 端口",
|
||||
"AssetNumber": "资产编号",
|
||||
"AssetUserList": "账号列表",
|
||||
"AssetUserList": "资产用户列表",
|
||||
"Assets": "资产",
|
||||
"Auth": "认证",
|
||||
"AccountList": "账号列表",
|
||||
"AutoGenerateKey": "自动生成",
|
||||
"AutoGenerateKey": "自动生成密钥",
|
||||
"AutoPush": "自动推送",
|
||||
"BasePlatform": "基础平台",
|
||||
"Basic": "基本",
|
||||
@@ -117,7 +85,6 @@
|
||||
"CommandFilterRules": "命令过滤器规则",
|
||||
"Comment": "备注",
|
||||
"Cpu": "CPU",
|
||||
"CommonUser": "普通用户",
|
||||
"CreatedBy": "创建者",
|
||||
"Database": "数据库",
|
||||
"DateJoined": "创建日期",
|
||||
@@ -159,7 +126,6 @@
|
||||
"PriorityHelpMessage": "1-100, 1最低优先级,100最高优先级。授权多个用户时,高优先级的系统用户将会作为默认登录用户",
|
||||
"Protocol": "协议",
|
||||
"Protocols": "协议组",
|
||||
"LoginOption": "登录选项",
|
||||
"PublicIp": "公网IP",
|
||||
"Push": "推送",
|
||||
"PushSystemUserNow": "推送系统用户",
|
||||
@@ -182,16 +148,15 @@
|
||||
"PasswordHelpMessage": "密码或密钥密码",
|
||||
"SystemUser": "系统用户",
|
||||
"SystemUserDetail": "系统用户详情",
|
||||
"SystemUserListHelpMessage": "<b>系统用户</b> 是JumpServer 登录资产时使用的账号,如 root `ssh root@host`,而不是使用该用户名登录资产(ssh admin@host)`;<br><b>特权用户</b> 是资产已存在的, 并且拥有 高级权限 的系统用户, JumpServer 使用该用户来 `推送系统用户`、`获取资产硬件信息` 等;</br><b>普通用户</b> 可以在资产上预先存在,也可以由 特权用户 来自动创建。",
|
||||
"DynamicUsername": "动态用户名",
|
||||
"SystemUserListHelpMessage": "系统用户是 JumpServer 跳转登录资产时使用的用户,可以理解为登录资产用户,如 web,sa,dba(`ssh web@some-host`),而不是使用某个用户的用户名跳转登录服务器(`ssh xiaoming@some-host`); 简单来说是用户使用自己的用户名登录 JumpServer,JumpServer 使用系统用户登录资产。 系统用户创建时,如果选择了自动推送,JumpServer 会使用 Ansible 自动推送系统用户到资产中,如果资产(交换机)不支持 Ansible,请手动填写账号密码。\n",
|
||||
"SystemUsers": "系统用户",
|
||||
"Test": "测试",
|
||||
"TestAssetsConnective": "测试资产可连接性",
|
||||
"TestConnection": "测试连接",
|
||||
"Type": "类型",
|
||||
"UnselectedAssets": "未选择资产或所选择的资产不支持SSH协议连接",
|
||||
"UnselectedAssets": "未选择资产",
|
||||
"UnselectedNodes": "未选择节点",
|
||||
"UpdateAssetUserToken": "更新账号认证信息",
|
||||
"UpdateAssetUserToken": "更新资产用户认证信息",
|
||||
"Username": "用户名",
|
||||
"UsernameHelpMessage": "用户名是动态的,登录资产时使用当前用户的用户名登录",
|
||||
"Value": "值",
|
||||
@@ -199,36 +164,26 @@
|
||||
"Version": "版本",
|
||||
"command_filter_list": "命令过滤器列表",
|
||||
"date_joined": "创建日期",
|
||||
"sshKeyFingerprint": "SSH 指纹",
|
||||
"ip": "IP",
|
||||
"sshkey": "sshkey",
|
||||
"SSHKey": "SSH 密钥",
|
||||
"GroupsHelpMessage": "请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)",
|
||||
"HomeHelpMessage": "默认家目录 /home/系统用户名: /home/username",
|
||||
"Home": "家目录",
|
||||
"LinuxUserAffiliateGroup": "用户附属组",
|
||||
"ipDomain": "IP(域名)",
|
||||
"HostProtocol": "主机协议",
|
||||
"DatabaseProtocol": "数据库协议",
|
||||
"OtherProtocol": "其它协议",
|
||||
"PasswordOrToken": "密码 / 令牌"
|
||||
"ipDomain": "IP(域名)"
|
||||
|
||||
},
|
||||
"audits": {
|
||||
"Hosts": "主机",
|
||||
"RunUser": "运行用户",
|
||||
"User": "用户",
|
||||
"Username": "用户名",
|
||||
"View": "查看",
|
||||
"SystemUserName": "系统用户名"
|
||||
"View": "查看"
|
||||
},
|
||||
"auth": {
|
||||
"LoginRequiredMsg": "账号已退出,请重新登录",
|
||||
"ReLogin": "重新登录"
|
||||
},
|
||||
"common": {
|
||||
"MFAVerify": "验证 MFA",
|
||||
"ViewSecret": "查看密码",
|
||||
"ConnectWebSocketError": "连接 WebSocket 失败",
|
||||
"Action": "动作",
|
||||
"RequestTickets": "申请工单",
|
||||
"Actions": "操作",
|
||||
@@ -243,8 +198,6 @@
|
||||
"UpdateAssetDetail": "配置更多信息",
|
||||
"AddSuccessMsg": "添加成功",
|
||||
"Auth": "认证",
|
||||
"bind": "绑定",
|
||||
"unbind": "解绑",
|
||||
"PushSelected":"推送所选",
|
||||
"BadRequestErrorMsg": "请求错误,请检查填写内容",
|
||||
"BadRoleErrorMsg": "请求错误,无该操作权限",
|
||||
@@ -276,16 +229,10 @@
|
||||
"EnterForSearch": "按回车进行搜索",
|
||||
"Export": "导出",
|
||||
"Import": "导入",
|
||||
"ContinueImport": "继续导入",
|
||||
"Continue": "继续",
|
||||
"Stop": "停止",
|
||||
"Finished": "完成",
|
||||
"Refresh": "刷新",
|
||||
"Info": "提示",
|
||||
"MFAConfirm": "MFA 认证",
|
||||
"MFARequireForSecurity": "为了安全请输入MFA",
|
||||
"PasswordConfirm": "密码认证",
|
||||
"PasswordRequireForSecurity": "为了安全请输入密码",
|
||||
"Members": "成员",
|
||||
"More": "更多",
|
||||
"Message": "消息",
|
||||
@@ -319,7 +266,6 @@
|
||||
"View": "查看",
|
||||
"Yes": "是",
|
||||
"action": "动作",
|
||||
"User": "用户",
|
||||
"activateSelected": "激活所选",
|
||||
"bulkDeleteErrorMsg": "批量删除失败: ",
|
||||
"bulkDeleteSuccessMsg": "批量删除成功",
|
||||
@@ -345,26 +291,16 @@
|
||||
"fieldRequiredError": "这个字段是必填项",
|
||||
"getErrorMsg": "获取失败",
|
||||
"MFAErrorMsg": "MFA错误,请检查",
|
||||
"Total": "总共",
|
||||
"Success": "成功",
|
||||
"Failed": "失败",
|
||||
"Pending": "等待",
|
||||
"Status": "状态",
|
||||
"InputEmailAddress": "请输入正确的邮箱地址",
|
||||
"Receivers": "接收人",
|
||||
"imExport": {
|
||||
"ExportAll": "导出所有",
|
||||
"ExportOnlyFiltered": "仅导出搜索结果",
|
||||
"ExportOnlySelectedItems": "仅导出选择项",
|
||||
"ExportRange": "导出范围",
|
||||
"createSuccessMsg": "导入创建成功,总共:{count}",
|
||||
"downloadImportTemplateMsg": "下载创建模板",
|
||||
"downloadImportTemplateMsg": "下载导入模板",
|
||||
"downloadUpdateTemplateMsg": "下载更新模板",
|
||||
"onlyCSVFilesTips": "仅支持csv文件导入",
|
||||
"updateSuccessMsg": "导入更新成功,总共:{count}",
|
||||
"uploadCsvLth10MHelpText": "只能上传 csv/xlsx, 且不超过 10M",
|
||||
"dragUploadFileInfo": "将文件拖到此处,或点击上传",
|
||||
"hasImportErrorItemMsg": "存在导入失败项,点击左侧 x 查看失败原因,点击表格编辑后,可以继续导入失败项"
|
||||
"updateSuccessMsg": "导入更新成功,总共:{count}"
|
||||
},
|
||||
"fileType": "文件类型",
|
||||
"isValid": "有效",
|
||||
@@ -402,8 +338,7 @@
|
||||
"SPECIAL_CHAR_REQUIRED": "须包含特殊字符",
|
||||
"MIN_LENGTH_ERROR": "密码最小长度 {0} 位"
|
||||
},
|
||||
"lastCannotBeDeleteMsg": "最后一项,不能被删除",
|
||||
"InvalidJson": "不是合法 JSON"
|
||||
"lastCannotBeDeleteMsg": "最后一项,不能被删除"
|
||||
},
|
||||
"dashboard": {
|
||||
"ActiveAsset": "近期被登录过",
|
||||
@@ -517,7 +452,6 @@
|
||||
"downloadFile": "下载文件",
|
||||
"hostName": "主机名",
|
||||
"isValid": "有效",
|
||||
"isEffective": "起作用的",
|
||||
"nodeCount": "节点数量",
|
||||
"refreshFail": "刷新失败",
|
||||
"refreshPermissionCache": "刷新授权缓存",
|
||||
@@ -540,11 +474,7 @@
|
||||
},
|
||||
"route": {
|
||||
"": "",
|
||||
"Accounts": "账号管理",
|
||||
"AssetAccount": "资产账号",
|
||||
"ApplicationAccount": "应用账号",
|
||||
"Ticket":"工单",
|
||||
"CommandConfirm": "命令复核",
|
||||
"AdminUserCreate": "创建管理用户",
|
||||
"AdminUserDetail": "管理用户详情",
|
||||
"AdminUserList": "管理用户",
|
||||
@@ -591,16 +521,7 @@
|
||||
"KubernetesAppPermissionDetail": "Kubernetes授权详情",
|
||||
"KubernetesAppPermissionUpdate": "更新Kubernetes授权规则",
|
||||
"KubernetesAppUpdate": "更新Kubernetes",
|
||||
"Acl": "访问控制",
|
||||
"UserAclList": "用户登录",
|
||||
"UserAclCreate": "创建用户登录规则",
|
||||
"UserAclLists": "用户登录规则",
|
||||
"UserAclUpdate": "更新用户登录规则",
|
||||
"UserAclDetail": "用户登录规则详情",
|
||||
"AssetAclList": "登录资产",
|
||||
"AssetAclCreate": "创建资产登录规则",
|
||||
"AssetAclUpdate": "更新资产登录规则",
|
||||
"AssetAclDetail": "资产登录规则详情",
|
||||
|
||||
"DomainCreate": "创建网域",
|
||||
"DomainDetail": "网域详情",
|
||||
"DomainList": "网域列表",
|
||||
@@ -646,7 +567,6 @@
|
||||
"SystemUserDetail": "系统用户详情",
|
||||
"SystemUserList": "系统用户",
|
||||
"SystemUserUpdate": "更新系统用户",
|
||||
"AssetUserList": "资产用户",
|
||||
"TaskDetail": "任务详情",
|
||||
"TaskList": "任务列表",
|
||||
"TaskMonitor": "任务监控",
|
||||
@@ -667,15 +587,9 @@
|
||||
"UserUpdate": "更新用户",
|
||||
"Users": "用户管理",
|
||||
"WebFTP": "文件管理",
|
||||
"WebTerminal": "Web终端",
|
||||
"Notifications": "通知",
|
||||
"SiteMessageList": "站内信"
|
||||
"WebTerminal": "Web终端"
|
||||
},
|
||||
"sessions": {
|
||||
"SetToDefaultStorage": "设置为默认存储",
|
||||
"SetToDefault": "设为默认",
|
||||
"SetSuccess": "设置成功",
|
||||
"SetFailed": "设置失败",
|
||||
"StorageConfiguration": "存储配置",
|
||||
"accountKey": "账户密钥",
|
||||
"accountName": "账户名称",
|
||||
@@ -710,7 +624,6 @@
|
||||
"name": "名称",
|
||||
"protocol": "协议",
|
||||
"region": "地域",
|
||||
"EsDisabled": "节点不可用, 请联系管理员",
|
||||
"sessionActiveCount": "在线会话数量",
|
||||
"systemCpuLoad": "CPU负载",
|
||||
"systemDiskUsedPercent": "硬盘使用率",
|
||||
@@ -736,11 +649,10 @@
|
||||
"common": "普通"
|
||||
},
|
||||
"Monitor": "监控",
|
||||
"XRDPNotSupport": "RDP 客户端会话, 暂不支持监控",
|
||||
"sessionMonitor": "监控",
|
||||
"TerminateTaskSendSuccessMsg": "终断任务已下发,请稍后刷新查看",
|
||||
"helpText": {
|
||||
"esUrl": "提示:如果有多台主机,请使用逗号 ( , ) 进行分割。(eg: http://www.jumpserver.a.com:3000,http://www.jumpserver.b.com:3000)",
|
||||
"esUrl": "提示:如果有多台主机,请使用逗号 ( , ) 进行分割。(eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com)",
|
||||
"esIndex": "es提供默认index:jumpserver",
|
||||
"esDocType": "es默认文档类型:command",
|
||||
"s3Endpoint": "S3 格式: http://s3.{REGION_NAME}.amazonaws.com<br>S3(China) 格式: http://s3.{REGION_NAME}.amazonaws.com.cn<br>如: http://s3.cn-north-1.amazonaws.com.cn",
|
||||
@@ -748,7 +660,6 @@
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"InsecureCommandNotifyToSubscription": "危险命令通知已升级到消息订阅中,支持更多通知方式",
|
||||
"ApiKeyList": "API Key 列表",
|
||||
"AssetCount": "资产数量",
|
||||
"Basic": "基本设置",
|
||||
@@ -767,8 +678,6 @@
|
||||
"PasswordCheckRule": "密码校验规则",
|
||||
"Security": "安全设置",
|
||||
"SecuritySetting": "安全设置",
|
||||
"SystemMessageSubscription": "系统消息订阅",
|
||||
"insecureCommandEmailUpdate": "点我设置",
|
||||
"SubscriptionID": "订阅授权ID",
|
||||
"Terminal": "终端设置",
|
||||
"all": "全部",
|
||||
@@ -799,11 +708,9 @@
|
||||
"emailTest": "测试连接",
|
||||
"emailUserSSL": "使用SSL",
|
||||
"emailUserTLS": "使用TLS",
|
||||
"InsecureCommandAlert": "危险命令告警",
|
||||
"SecurityInsecureCommand": "危险命令告警",
|
||||
"Insecure_Command_Alert": "危险命令告警",
|
||||
"SecurityInsecureCommandEmailReceiver": "告警接收邮件",
|
||||
"MailSend": "邮件发送",
|
||||
"LDAPServerInfo": "LDAP 服务器",
|
||||
"LDAPUser": "LDAP 用户",
|
||||
"helpText": {
|
||||
"ApiKeyList": "使用api key签名请求头,每个请求的头部是不一样的, 请查阅使用文档",
|
||||
"authLdapSearchFilter": "可能的选项是(cn或uid或sAMAccountName=%(user)s)",
|
||||
@@ -813,7 +720,7 @@
|
||||
"emailCustomUserCreatedHonorific": "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)",
|
||||
"emailCustomUserCreatedSignature": "提示: 邮件的署名 (例如: jumpserver)",
|
||||
"emailCustomUserCreatedSubject": "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)",
|
||||
"emailEmailFrom": "",
|
||||
"emailEmailFrom": "提示:发送邮件账号,默认使用SMTP账号作为发送账号",
|
||||
"emailHostPassword": "提示:一些邮件提供商需要输入的是Token",
|
||||
"emailRecipient": "提示:仅用来作为测试邮件收件人",
|
||||
"emailSubjectPrefix": "提示: 一些关键字可能会被邮件提供商拦截,如 跳板机、JumpServer",
|
||||
@@ -882,15 +789,12 @@
|
||||
"refreshLdapCache":"刷新Ldap缓存,请稍后",
|
||||
"LicenseExpired": "许可证已经过期",
|
||||
"LicenseWillBe": "许可证即将在 ",
|
||||
"Expire": " 过期",
|
||||
"WeCom": "企业微信",
|
||||
"DingTalk": "钉钉",
|
||||
"dingTalkTest": "测试",
|
||||
"weComTest": "测试",
|
||||
"Expire": " 过期"
|
||||
},
|
||||
"settings": {
|
||||
"setting": "设置"
|
||||
},
|
||||
"tickets": {
|
||||
"PermissionName": "授权规则名称",
|
||||
"Accept": "同意",
|
||||
"AssignedMe": "待我审批",
|
||||
"Assignee": "处理人",
|
||||
@@ -932,13 +836,7 @@
|
||||
"ips": "请输入逗号分割的IP地址组",
|
||||
"fuzzySearch": "支持模糊搜索",
|
||||
"application": "请输入逗号分割的应用名称组"
|
||||
},
|
||||
"ApplyRunUser": "申请运行的用户",
|
||||
"ApplyRunAsset": "申请运行的资产",
|
||||
"ApplyRunSystemUser": "申请运行的系统用户",
|
||||
"ApplyRunCommand": "申请运行的命令",
|
||||
"ApplyFromSession": "会话",
|
||||
"ApplyFromCMDFilterRule": "命令过滤规则"
|
||||
}
|
||||
},
|
||||
"tree": {
|
||||
"AddAssetToNode": "添加资产到节点",
|
||||
@@ -954,7 +852,6 @@
|
||||
"UpdateNodeAssetHardwareInfo": "更新节点资产硬件信息"
|
||||
},
|
||||
"users": {
|
||||
"UserName": "姓名",
|
||||
"Account": "账户",
|
||||
"Authentication": "认证",
|
||||
"Comment": "备注",
|
||||
@@ -975,8 +872,6 @@
|
||||
"Invite": "邀请",
|
||||
"InviteUserInOrg": "邀请用户加入此组织",
|
||||
"Guide": "向导",
|
||||
"setWeCom": "设置企业微信认证",
|
||||
"setDingTalk": "设置钉钉认证",
|
||||
"HelpText": {
|
||||
"MFAOfUserFirstLoginPersonalInformationImprovementPage": "启用多因子认证,使账号更加安全。<br/> 启用之后您将会在下次登录时进入多因子认证绑定流程;您也可以在(个人信息->快速修改->更改多因子设置)中直接绑定!",
|
||||
"MFAOfUserFirstLoginUserGuidePage": "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:设置复杂密码,并启用多因子认证)",
|
||||
@@ -1024,14 +919,7 @@
|
||||
"resetSSHKey": "重置SSH密钥",
|
||||
"resetSSHKeySuccessMsg": "发送邮件任务已提交, 用户稍后会收到重置密钥邮件",
|
||||
"resetSSHKeyWarningMsg": "你确定要发送重置用户的SSH Key的邮件吗?",
|
||||
"resetWechat": "解绑企业微信",
|
||||
"resetWechatLoginWarningMsg": "你确定要解绑用户的 企业微信 吗?",
|
||||
"resetWechatLoginSuccessMsg": "重置成功, 用户可以重新绑定企业微信了",
|
||||
"resetDingTalk": "解绑钉钉",
|
||||
"resetDingTalkLoginWarningMsg": "你确定要解绑用户的 钉钉 吗?",
|
||||
"resetDingTalkLoginSuccessMsg": "重置成功, 用户可以重新绑定钉钉了",
|
||||
"send": "发送",
|
||||
"unbind": "解绑",
|
||||
"unblock": "解锁",
|
||||
"unblockSuccessMsg": "解锁成功",
|
||||
"unblockUser": "解锁用户"
|
||||
@@ -1050,27 +938,12 @@
|
||||
"remoteAppPermissionRules": "远程应用授权规则"
|
||||
},
|
||||
"dateLastLogin": "最后登录日期",
|
||||
"needUpdatePasswordNextLogin": "下次登录须修改密码",
|
||||
"UpdatePassword": "更新密码",
|
||||
"SetPublicKey": "设置SSH公钥",
|
||||
"passwordExpired": "密码过期了",
|
||||
"passwordWillExpiredPrefixMsg": "密码即将在 ",
|
||||
"passwordWillExpiredSuffixMsg": "天 后过期,请尽快修改您的密码。"
|
||||
},
|
||||
"notifications": {
|
||||
"MessageType": "消息类型",
|
||||
"Receivers": "接收人",
|
||||
"Subscription": "消息订阅",
|
||||
"ChangeReceiver": "修改消息接收人",
|
||||
"Subject": "主题",
|
||||
"Message": "消息",
|
||||
"DeliveryTime": "发送时间",
|
||||
"HasRead": "是否已读",
|
||||
"Sender": "发送人",
|
||||
"MarkAsRead": "标记已读",
|
||||
"NoUnreadMsg": "暂无未读消息",
|
||||
"SiteMessage": "站内信"
|
||||
},
|
||||
"xpack": {
|
||||
"Admin": "管理员",
|
||||
"Asset": "资产",
|
||||
@@ -1120,13 +993,10 @@
|
||||
"Cloud": {
|
||||
"Aliyun": "阿里云",
|
||||
"Qcloud": "腾讯云",
|
||||
"QingyunPrivatecloud": "青云私有云",
|
||||
"HuaweiPrivatecloud": "华为私有云",
|
||||
"AWS_China": "AWS(中国)",
|
||||
"AWS_Int": "AWS(国际)",
|
||||
"HuaweiCloud": "华为云",
|
||||
"Azure":"Azure(中国)",
|
||||
"Azure_Int": "Azure(国际)",
|
||||
"HostnameStrategy": "用于生成资产主机名。例如:1. 实例名称 (instanceDemo);2. 实例名称和部分IP(后两位) (instanceDemo-250.1)",
|
||||
"IsAlwaysUpdate": "资产信息保持最新",
|
||||
"AccountCreate": "创建账户",
|
||||
@@ -1149,7 +1019,7 @@
|
||||
"Name": "名称",
|
||||
"Account":"账户",
|
||||
"Node": "节点",
|
||||
"AdminUser":"特权用户",
|
||||
"AdminUser":"管理用户",
|
||||
"Periodic":"执行周期",
|
||||
"PeriodicPerform":"定时执行",
|
||||
"RegularlyPerform": "定期执行",
|
||||
@@ -1179,9 +1049,7 @@
|
||||
"GatherUserList": "收集用户",
|
||||
"GatherUserTaskCreate": "创建任务",
|
||||
"GatherUserTaskList": "任务列表",
|
||||
"GatherUserTaskUpdate": "更新任务",
|
||||
"GatherUserTaskDetail": "任务详情",
|
||||
"GatherUserTaskExecutionList": "任务执行列表"
|
||||
"GatherUserTaskUpdate": "更新任务"
|
||||
},
|
||||
"Import": "导入",
|
||||
"ImportLicense": "导入许可证",
|
||||
@@ -1211,7 +1079,7 @@
|
||||
"users_amount": "用户数量",
|
||||
"groups_amount": "用户组数量",
|
||||
"assets_amount": "资产数量",
|
||||
"admin_users_amount": "特权用户数量",
|
||||
"admin_users_amount": "管理用户数量",
|
||||
"system_users_amount": "系统用户数量",
|
||||
"applications_amount": "应用数量",
|
||||
"asset_perms_amount": "资产授权数量",
|
||||
|
||||
@@ -1,36 +1,7 @@
|
||||
{
|
||||
"": "",
|
||||
"accounts": {
|
||||
"PleaseClickLeftAssetToViewAssetAccount": "Asset account list, please click on the assets on the left to view",
|
||||
"PleaseClickLeftApplicationToViewApplicationAccount": "Application account list, please click on the application on the left to view",
|
||||
"PleaseClickLeftAssetToViewGatheredUser": "Gathered user list, please click on the assets on the left to view"
|
||||
},
|
||||
"acl": {
|
||||
"name": "Name",
|
||||
"username": "Username",
|
||||
"ip_group": "IP group",
|
||||
"action": "Action",
|
||||
"priority": "Priority",
|
||||
"date_created": "Date created",
|
||||
"created_by": "Created by",
|
||||
"asset": "Asset",
|
||||
"system_user": "System user",
|
||||
"username_group":"Username group",
|
||||
"hostname_group":"Hostname group",
|
||||
"asset_ip_group": "Asset ip group",
|
||||
"system_users_name_group": "Systemusers name group",
|
||||
"system_users_protocol_group": "Systemusers protocol group",
|
||||
"system_users_username_group": "systemusers username group",
|
||||
"apply_login_asset": "Apply login asset",
|
||||
"apply_login_system_user": "Apply login system user",
|
||||
"apply_login_user": "Apply login user",
|
||||
"RuleDetail": "Rule detail"
|
||||
},
|
||||
"applications": {
|
||||
"": "",
|
||||
"RemoteApp": "Remote app",
|
||||
"Database": "Database",
|
||||
"Cloud": "Cloud",
|
||||
"applicationsType": {
|
||||
"chrome": "Chrome",
|
||||
"mysql_workbench": "MySQL Workbench",
|
||||
@@ -82,20 +53,16 @@
|
||||
"DBInfo": "Database Info"
|
||||
},
|
||||
"assets": {
|
||||
"AssociateAssets": "Associate assets",
|
||||
"AssociateNodes": "Associate nodes",
|
||||
"Action": "Action",
|
||||
"ActiveSelected": "Active selected",
|
||||
"AdminUser": "Admin user",
|
||||
"ReplaceNodeAssetsAdminUser":"Replace node assets admin user with this",
|
||||
"AdminUserDetail": "Admin user detail",
|
||||
"DynamicUsername": "Dynamic username",
|
||||
"AdminUserListHelpMessage": "Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, JumpServer users of the system using the user to `push system user`, `get assets hardware information`, etc.\n",
|
||||
"Asset": "Asset",
|
||||
"HardwareInfo": "Hardware info",
|
||||
"Hardware": "Hardware",
|
||||
"AccountList": "Account list",
|
||||
"LoginOption": "Login option",
|
||||
"AssetDetail": "Asset detail",
|
||||
"AssetList": "Asset list",
|
||||
"AssetListHelpMessage": "The left side is the asset tree, right click to create, delete, and change the tree node, authorization asset is also organized as a node, and the right side is the asset under that node\n",
|
||||
@@ -119,7 +86,6 @@
|
||||
"CommandFilterRules": "Command filter rules",
|
||||
"Comment": "Comment",
|
||||
"Cpu": "Cpu",
|
||||
"CommonUser": "Common user",
|
||||
"CreatedBy": "Created by",
|
||||
"Database": "Database",
|
||||
"DateJoined": "Date joined",
|
||||
@@ -178,17 +144,17 @@
|
||||
"Rules": "Rules",
|
||||
"SFTPHelpMessage": "SFTP root dir, tmp, home or custom",
|
||||
"SerialNumber": "Serial number",
|
||||
"SudoHelpMessage": "Use comma split multi command, ex: /bin/whoami, /bin/ifconfig",
|
||||
"SudoHelpMessage": "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig",
|
||||
"PasswordHelpMessage": "Password or private key password",
|
||||
"SystemUser": "System user",
|
||||
"SystemUserDetail": "System user detail",
|
||||
"SystemUserListHelpMessage": "<b>System user</b> is the account JumpServer used to log into the asset, such as using root `ssh root@host`, rather than the current user username(ssh admin@host)`;<br><b>Admin user</b> is the account that already exists on an asset, and have privileged permissions, JumpServer using this create common system user, and gather hardware Etc;</br><b>Common user</b> can pre-exist assets or created automatically by the admin user.",
|
||||
"SystemUserListHelpMessage": "System user is JumpServer jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); In simple terms, users log into JumpServer using their own username, and JumpServer uses system users to log into assets. When system users are created, if you choose auto push JumpServer to use Ansible push system users into the asset, if the asset (Switch) does not support ansible, please manually fill in the account password.\n",
|
||||
"SystemUsers": "System users",
|
||||
"Test": "Test",
|
||||
"TestAssetsConnective": "Test assets connective",
|
||||
"TestConnection": "Test connection",
|
||||
"Type": "Type",
|
||||
"UnselectedAssets": "No asset selected or the selected asset does not support SSH protocol connection",
|
||||
"UnselectedAssets": "Unselected assets",
|
||||
"UnselectedNodes": "Unselected nodes",
|
||||
"UpdateAssetUserToken": "Update asset user auth",
|
||||
"Username": "Username",
|
||||
@@ -198,25 +164,18 @@
|
||||
"Version": "Version",
|
||||
"command_filter_list": "Command filter list",
|
||||
"date_joined": "Date joined",
|
||||
"sshKeyFingerprint": "SSH fingerprint",
|
||||
"ip": "IP",
|
||||
"sshkey": "sshkey",
|
||||
"GroupsHelpMessage": "Please fill in user groups, separated by commas if there are multiple user groups(Please fill in the existing user groups)",
|
||||
"HomeHelpMessage": "Default home directory: /home/system username",
|
||||
"Home": "Home",
|
||||
"LinuxUserAffiliateGroup": "Linux user affiliate group",
|
||||
"ipDomain": "IP(Domain)",
|
||||
"HostProtocol": "Host Protocol",
|
||||
"DatabaseProtocol": "Database Protocol",
|
||||
"OtherProtocol": "Other Protocol",
|
||||
"PasswordOrToken": "Password / Token"
|
||||
"ipDomain": "IP(Domain)"
|
||||
},
|
||||
"audits": {
|
||||
"Hosts": "Host",
|
||||
"RunUser": "Run user",
|
||||
"User": "User",
|
||||
"Username": "Username",
|
||||
"SystemUserName": "System username",
|
||||
"View": "View"
|
||||
},
|
||||
"auth": {
|
||||
@@ -224,9 +183,6 @@
|
||||
"ReLogin": "Re-Login"
|
||||
},
|
||||
"common": {
|
||||
"MFAVerify": "Verify MFA",
|
||||
"ViewSecret": "View secret",
|
||||
"ConnectWebSocketError": "Connect Websocket failed",
|
||||
"Nothing": "Nothing",
|
||||
"Action": "Action",
|
||||
"CustomCol":"Custom table col",
|
||||
@@ -257,7 +213,7 @@
|
||||
"Confirm": "Confirm",
|
||||
"Create": "Create",
|
||||
"CreatedBy": "Created by",
|
||||
"CrontabHelpTips": "eg: Every Sunday 03:05 run <5 3 * * 0> <br>Tips:Using 5 digits linux crontab expressions<min hour day month week> (<a href='https://tool.lu/crontab/' target='_blank'>Online tools</a>) <br>Note:If both Regularly perform and Cycle perform are set, give priority to Regularly perform",
|
||||
"CrontabHelpTips": "eg: Every Sunday 03:05 run <5 3 * * 0> <br>Tips:Using 5 digits linux crontab expressions<min hour day month week> (<a href='https://tool.lu/crontab/' target='_blank'>Online tools</a>) <br>Note:If both Regularly perform and Cycle perform are set,give priority to Regularly perform",
|
||||
"DateEnd": "End date",
|
||||
"Resource": "Resource",
|
||||
"DateLast24Hours": "Last 24 hours",
|
||||
@@ -273,16 +229,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",
|
||||
"MFARequireForSecurity": "MFA required for security",
|
||||
"PasswordConfirm": "Password Confirm",
|
||||
"PasswordRequireForSecurity": "Password required for security",
|
||||
"Members": "Members",
|
||||
"More": "More",
|
||||
"Message": "Message",
|
||||
@@ -294,24 +244,19 @@
|
||||
"Other": "Other",
|
||||
"Others": "Others",
|
||||
"Push": "Push",
|
||||
"Receivers": "Receivers",
|
||||
"QuickUpdate": "Quick update",
|
||||
"RemoveSuccessMsg": "Remove success",
|
||||
"Reset": "Reset",
|
||||
"Search": "Search",
|
||||
"MFAErrorMsg": "MFA Error, please check",
|
||||
"InputEmailAddress": "Please enter your email address",
|
||||
"MFAErrorMsg": "MFA Error,please check",
|
||||
"Select": "Select",
|
||||
"SelectFile": "Select file",
|
||||
"Show": "Show",
|
||||
"Submit": "Submit",
|
||||
"Test": "Test",
|
||||
"SaveAndAddAnother":"Save and add another",
|
||||
"TestSuccessMsg": "Test Success",
|
||||
"To": "To",
|
||||
"Update": "Update",
|
||||
"bind": "Bind",
|
||||
"unbind": "Unbind",
|
||||
"Upload": "Upload",
|
||||
"Clone": "Clone",
|
||||
"Username": "Username",
|
||||
@@ -345,11 +290,6 @@
|
||||
"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",
|
||||
@@ -359,10 +299,7 @@
|
||||
"downloadImportTemplateMsg": "Download import template",
|
||||
"downloadUpdateTemplateMsg": "Download update template",
|
||||
"onlyCSVFilesTips": "Only csv supported",
|
||||
"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"
|
||||
"updateSuccessMsg": "Update success, total: {count}"
|
||||
},
|
||||
"isValid": "Is valid",
|
||||
"nav": {
|
||||
@@ -399,8 +336,7 @@
|
||||
"SPECIAL_CHAR_REQUIRED": "Special char required",
|
||||
"MIN_LENGTH_ERROR": "Password minimum length {}"
|
||||
},
|
||||
"lastCannotBeDeleteMsg": "The last one can't be delete",
|
||||
"InvalidJson": "Not a valid json format"
|
||||
"lastCannotBeDeleteMsg": "The last one can't be delete"
|
||||
},
|
||||
"dashboard": {
|
||||
"ActiveAsset": "Asset active",
|
||||
@@ -514,7 +450,6 @@
|
||||
"downloadFile": "Download file",
|
||||
"hostName": "Hostname",
|
||||
"isValid": "Validity",
|
||||
"isEffective": "Effective",
|
||||
"nodeCount": "Node count",
|
||||
"refreshFail": "Refresh fail",
|
||||
"refreshPermissionCache": "Refresh permission cache",
|
||||
@@ -537,33 +472,29 @@
|
||||
},
|
||||
"route": {
|
||||
"": "",
|
||||
"Accounts": "Accounts",
|
||||
"AssetAccount": "Asset account",
|
||||
"ApplicationAccount": "Application account",
|
||||
"Ticket": "Tickets",
|
||||
"CommandConfirm": "Command confirm",
|
||||
"AdminUserCreate": "Admin user create",
|
||||
"AdminUserDetail": "Admin user detail",
|
||||
"AdminUserList": "Admin Users",
|
||||
"AdminUserList": "Admin users",
|
||||
"AdminUserUpdate": "Admin user update",
|
||||
"Applications": "Applications",
|
||||
"AssetCreate": "Asset create",
|
||||
"AssetDetail": "Asset detail",
|
||||
"AssetList": "Assets",
|
||||
"AssetPermission": "Asset Permissions",
|
||||
"AssetPermission": "Asset permissions",
|
||||
"AssetPermissionCreate": "Asset permissions create",
|
||||
"AssetPermissionDetail": "Asset permissions detail",
|
||||
"AssetPermissionUpdate": "Asset permissions update",
|
||||
"AssetUpdate": "Asset update",
|
||||
"Assets": "Assets",
|
||||
"Audits": "Audits",
|
||||
"BatchCommand": "Batch Command",
|
||||
"BatchCommandLog": "Batch Command Log",
|
||||
"BatchCommand": "Batch command",
|
||||
"BatchCommandLog": "Batch command log",
|
||||
"CeleryTaskLog": "Celery task log",
|
||||
"CommandExecutions": "CommandExecutions ",
|
||||
"CommandFilterCreate": "Command filter create",
|
||||
"CommandFilterDetail": "Command filter detail",
|
||||
"CommandFilterList": "Command Filters",
|
||||
"CommandFilterList": "Command filters",
|
||||
"CommandFilterRulesCreate": "Command filter rules create",
|
||||
"CommandFilterRulesUpdate": "Command filter rules update",
|
||||
"CommandFilterUpdate": "Command filter update",
|
||||
@@ -572,7 +503,7 @@
|
||||
"CreateCommandStorage": "Create command storage",
|
||||
"CreateReplayStorage": "Create replay storage",
|
||||
"Dashboard": "Dashboard",
|
||||
"DatabaseApp": "Database Apps",
|
||||
"DatabaseApp": "Database apps",
|
||||
"DatabaseAppCreate": "Database app create",
|
||||
"DatabaseAppDetail": "Database app detail",
|
||||
"DatabaseAppPermission": "Databases permissions",
|
||||
@@ -580,7 +511,7 @@
|
||||
"DatabaseAppPermissionDetail": "Databases permissions detail",
|
||||
"DatabaseAppPermissionUpdate": "Databases permissions update",
|
||||
"DatabaseAppUpdate": "Database app update",
|
||||
"KubernetesApp": "Kubernetes Apps",
|
||||
"KubernetesApp": "Kubernetes apps",
|
||||
"KubernetesAppCreate": "Kubernetes app create",
|
||||
"KubernetesAppDetail": "Kubernetes app detail",
|
||||
"KubernetesAppPermission": "Kubernetes permissions",
|
||||
@@ -588,50 +519,36 @@
|
||||
"KubernetesAppPermissionDetail": "Kubernetes permissions detail",
|
||||
"KubernetesAppPermissionUpdate": "Kubernetes permissions update",
|
||||
"KubernetesAppUpdate": "Kubernetes app update",
|
||||
"Acl": "Access Control",
|
||||
"UserAclList": "User acl list",
|
||||
"UserAclCreate": "User acl create",
|
||||
"UserAclUpdate": "User acl update",
|
||||
"UserAclLists": "User acl lists",
|
||||
"UserAclDetail": "User acl detail",
|
||||
"AssetAclList": "Asset Acl",
|
||||
"AssetAclCreate": "Asset acl create",
|
||||
"AssetAclUpdate": "Asset acl update",
|
||||
"AssetAclDetail": "Asset acl detail",
|
||||
"DomainCreate": "Domain create",
|
||||
"DomainDetail": "Domain detail",
|
||||
"DomainList": "Domains",
|
||||
"DomainUpdate": "Domain update",
|
||||
"FileManager": "File Manager",
|
||||
"FtpLog": "FTP Logs",
|
||||
"FileManager": "File manager",
|
||||
"FtpLog": "FTP logs",
|
||||
"GatewayCreate": "Gateway create",
|
||||
"GatewayUpdate": "Gateway update",
|
||||
"JobCenter": "Jobcenter",
|
||||
"LabelCreate": "Label create",
|
||||
"LabelList": "Labels",
|
||||
"LabelUpdate": "Label update",
|
||||
"LoginLog": "Login Logs",
|
||||
"MyApps": "My Apps",
|
||||
"MyAssets": "My Assets",
|
||||
"OperateLog": "Operation Logs",
|
||||
"PasswordChangeLog": "Password Update Logs",
|
||||
"LoginLog": "Login logs",
|
||||
"MyApps": "My apps",
|
||||
"MyAssets": "My assets",
|
||||
"OperateLog": "Operation logs",
|
||||
"PasswordChangeLog": "Password update logs",
|
||||
"Perms": "Permissions",
|
||||
"PersonalInformationImprovement": "PersonalInformationImprovement",
|
||||
"PlatformCreate": "Platform create",
|
||||
"PlatformDetail": "Platform detail",
|
||||
"PlatformList": "Platforms",
|
||||
"PlatformUpdate": "Platform update",
|
||||
"RemoteApp": "Remote Apps",
|
||||
"RemoteApp": "Remote apps",
|
||||
"RemoteAppDetail": "Remote app detail",
|
||||
"RemoteAppPermission": "Remote apps permissions",
|
||||
"ApplicationPermission": "Application Permissions",
|
||||
"ApplicationPermission": "Application permissions",
|
||||
"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",
|
||||
@@ -641,11 +558,11 @@
|
||||
"Settings": "Settings",
|
||||
"SystemUserCreate": "System user create",
|
||||
"SystemUserDetail": "System user detail",
|
||||
"SystemUserList": "System Users",
|
||||
"SystemUserList": "System users",
|
||||
"SystemUserUpdate": "System user update",
|
||||
"TaskDetail": "Tasks detail",
|
||||
"TaskList": "Tasks",
|
||||
"TaskMonitor": "Task Monitor",
|
||||
"TaskMonitor": "Task monitor",
|
||||
"Terminal": "Terminal",
|
||||
"TicketDetail": "Ticket detail",
|
||||
"TicketCreate": "Ticket create",
|
||||
@@ -655,7 +572,7 @@
|
||||
"UserFirstLogin": "UserFirstLogin",
|
||||
"UserGroupCreate": "User group create",
|
||||
"UserGroupDetail": "User group detail",
|
||||
"UserGroupList": "User Groups",
|
||||
"UserGroupList": "User groups",
|
||||
"UserGroupUpdate": "User group update",
|
||||
"UserGuide": "UserGuide",
|
||||
"UserList": "Users",
|
||||
@@ -663,15 +580,9 @@
|
||||
"UserUpdate": "User update",
|
||||
"Users": "Users",
|
||||
"WebFTP": "WebFTP",
|
||||
"WebTerminal": "Web Terminal",
|
||||
"Notifications": "Notifications",
|
||||
"SiteMessageList": "Site message"
|
||||
"WebTerminal": "Web terminal"
|
||||
},
|
||||
"sessions": {
|
||||
"SetToDefaultStorage": "Set to default storage",
|
||||
"SetToDefault": "Set to default",
|
||||
"SetSuccess": "Set success",
|
||||
"SetFailed": "Set failed",
|
||||
"StorageConfiguration": "Storage configuration",
|
||||
"accountKey": "Account key",
|
||||
"accountName": "Account name",
|
||||
@@ -698,7 +609,6 @@
|
||||
"systemCpuLoad": "cpu load",
|
||||
"systemDiskUsedPercent": "disk used percent",
|
||||
"systemMemoryUsedPercent": "memory used percent",
|
||||
"EsDisabled": "Node is unavailable, please contact administrator",
|
||||
"go": "Go",
|
||||
"goto": "Goto",
|
||||
"hosts": "Hosts",
|
||||
@@ -732,11 +642,10 @@
|
||||
"common": "common"
|
||||
},
|
||||
"Monitor": "Monitor",
|
||||
"XRDPNotSupport": "RDP Client session not support now",
|
||||
"sessionMonitor": "Session Monitor",
|
||||
"TerminateTaskSendSuccessMsg": "Terminate task has been send, Please check later",
|
||||
"helpText": {
|
||||
"esUrl": "Tip: If you have multiple hosts, use comma (, ) to split (eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com)",
|
||||
"esUrl": "Tip: If you have multiple hosts, use comma (,) to split (eg: http://www.jumpserver.a.com,http://www.jumpserver.b.com)",
|
||||
"esIndex":"Es provides the default index: jumpserver",
|
||||
"esDocType": "Es provides the default document type: command",
|
||||
"s3Endpoint": "S3: http://s3.{REGION_NAME}.amazonaws.com<br>S3(China): http://s3.{REGION_NAME}.amazonaws.com.cn<br>Example: http://s3.cn-north-1.amazonaws.com.cn",
|
||||
@@ -744,7 +653,6 @@
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"InsecureCommandNotifyToSubscription": "Insecure command notification setting, change to system message subscription, support more notify method",
|
||||
"ApiKeyList": "Api key list",
|
||||
"AssetCount": "Asset count",
|
||||
"Basic": "Basic setting",
|
||||
@@ -764,8 +672,6 @@
|
||||
"Security": "Security setting",
|
||||
"SecuritySetting": "Security setting",
|
||||
"SubscriptionID": "Subscription ID",
|
||||
"SystemMessageSubscription": "System messages",
|
||||
"insecureCommandEmailUpdate": "Setting",
|
||||
"Terminal": "Terminal setting",
|
||||
"all": "All",
|
||||
"authLdap": "Enable LDAP auth",
|
||||
@@ -795,15 +701,11 @@
|
||||
"emailTest": "Test connection",
|
||||
"emailUserSSL": "Use SSL",
|
||||
"emailUserTLS": "Use TLS",
|
||||
"MailSend": "Mail send",
|
||||
"LDAPServerInfo": "LDAP Server",
|
||||
"LDAPUser": "LDAP User",
|
||||
"InsecureCommandAlert": "Insecure command alert",
|
||||
"helpText": {
|
||||
"ApiKeyList": "The API key is used to sign the request header. The header of each request is different. Please refer to the usage documentation",
|
||||
"authLdapSearchFilter": "Choice may be (cn|uid|sAMAccountName)=%(user)s)",
|
||||
"authLdapSearchOu": "Use | split User OUs",
|
||||
"authLdapUserAttrMap": "User attr map present how to map LDAP user attr to jumpserver, username, name, email is jumpserver attr",
|
||||
"authLdapUserAttrMap": "User attr map present how to map LDAP user attr to jumpserver, username,name,email is jumpserver attr",
|
||||
"emailCustomUserCreatedBody": "Tips:When creating a user, send the content of the email",
|
||||
"emailCustomUserCreatedHonorific": "Tips: When creating a user, send the honorific of the email (eg:Hello)",
|
||||
"emailCustomUserCreatedSignature": "Tips: Email signature (eg:jumpserver)",
|
||||
@@ -875,16 +777,12 @@
|
||||
"refreshLdapCache":"Refreshing Ldap cache ",
|
||||
"LicenseExpired": "License expired",
|
||||
"LicenseWillBe": "License will expire at ",
|
||||
"Expire": "",
|
||||
"WeCom": "WeCom",
|
||||
"DingTalk": "DingTalk",
|
||||
"dingTalkTest": "Test",
|
||||
"weComTest": "Test",
|
||||
"Expire": ""
|
||||
},
|
||||
"settings": {
|
||||
"setting": "Setting"
|
||||
},
|
||||
|
||||
"tickets": {
|
||||
"PermissionName": "Permission name",
|
||||
"Accept": "Accept",
|
||||
"AssignedMe": "Assigned me",
|
||||
"Assignee": "Assignee",
|
||||
@@ -926,13 +824,7 @@
|
||||
"ips": "Enter the IP address group, separated by commas",
|
||||
"fuzzySearch": "Support for fuzzy search",
|
||||
"application": "Enter the application group, separated by commas"
|
||||
},
|
||||
"ApplyRunUser": "Apply run user",
|
||||
"ApplyRunAsset": "Apply run asset",
|
||||
"ApplyRunSystemUser": "Apply run system user",
|
||||
"ApplyRunCommand": "Apply run command",
|
||||
"ApplyFromSession": "Session",
|
||||
"ApplyFromCMDFilterRule": "Command filter rule"
|
||||
}
|
||||
},
|
||||
"tree": {
|
||||
"AddAssetToNode": "Add asset to node",
|
||||
@@ -948,7 +840,6 @@
|
||||
"UpdateNodeAssetHardwareInfo": "Update node asset hardware information"
|
||||
},
|
||||
"users": {
|
||||
"UserName": "Name",
|
||||
"Account": "Account",
|
||||
"Existing":"Existing",
|
||||
"Authentication": "Account",
|
||||
@@ -958,8 +849,6 @@
|
||||
"DateJoined": "Date joined",
|
||||
"DateLastLogin": "Date last login",
|
||||
"DatePasswordLastUpdated": "Date password last updated",
|
||||
"setWeCom": "Set wecom login",
|
||||
"setDingTalk": "Set dingtalk login",
|
||||
"DatePasswordUpdated": "Date password updated",
|
||||
"DescribeOfGuide": "Welcome to JumpServer. Click here for more information",
|
||||
"Email": "Email",
|
||||
@@ -1019,11 +908,7 @@
|
||||
"resetSSHKey": "Reset SSH key",
|
||||
"resetSSHKeySuccessMsg": "An e-mail has been sent to the user`s mailbox",
|
||||
"resetSSHKeyWarningMsg": "This will reset the user public key and send a reset mail",
|
||||
"resetWechat": "Reset Wechat",
|
||||
"resetWechatLoginWarningMsg": "This will reset the user Wechat setting, user can reset it",
|
||||
"resetWechatLoginSuccessMsg": "Reset Wechat success",
|
||||
"send": "Send",
|
||||
"unbind": "Unbind",
|
||||
"unblock": "Unblock",
|
||||
"unblockSuccessMsg": "Account has unblocked",
|
||||
"unblockUser": "Unblock login"
|
||||
@@ -1041,28 +926,12 @@
|
||||
"ApplicationPermissionRules": "Application permission rules",
|
||||
"remoteAppPermissionRules": "Remote app permission rules"
|
||||
},
|
||||
"needUpdatePasswordNextLogin": "Update password next login",
|
||||
"UpdatePassword": "Update password",
|
||||
"SetPublicKey": "Set public key",
|
||||
"UpdatePassword": "",
|
||||
"UpdatePublicKey": "",
|
||||
"passwordExpired": "Password expired",
|
||||
"passwordWillExpiredPrefixMsg": "The password will expire in ",
|
||||
"passwordWillExpiredSuffixMsg": " days.Please change your password as soon as possible."
|
||||
},
|
||||
"notifications": {
|
||||
"MessageType": "Message Type",
|
||||
"Receivers": "Receivers",
|
||||
"Subscription": "Subscription",
|
||||
"ChangeReceiver": "Change Receivers",
|
||||
"Subject": "Subject",
|
||||
"Message": "Message",
|
||||
"DeliveryTime": "Delivery time",
|
||||
"HasRead": "Has read",
|
||||
"Sender": "Sender",
|
||||
"MarkAsRead": "Mark as read",
|
||||
"NoUnreadMsg": "No unread messages",
|
||||
"SiteMessage": "Site messages"
|
||||
},
|
||||
"xpack": {
|
||||
"Admin": "Admin",
|
||||
"Asset": "Asset",
|
||||
@@ -1074,7 +943,7 @@
|
||||
"Asset": "Asset",
|
||||
"AssetAmount": "Asset",
|
||||
"AssetAndNode": "Asset and Node",
|
||||
"ChangeAuthPlan": "Change Auth Plan",
|
||||
"ChangeAuthPlan": "Change auth plan",
|
||||
"ChangeAuthPlanCreate": "Create change auth plan",
|
||||
"ChangeAuthPlanUpdate": "Update change auth plan",
|
||||
"CyclePerform": "Cycle perform",
|
||||
@@ -1112,13 +981,10 @@
|
||||
"Cloud": {
|
||||
"Aliyun": "Ali Cloud",
|
||||
"Qcloud": "Tencent Cloud",
|
||||
"QingyunPrivatecloud": "Qingyun Private Cloud",
|
||||
"HuaweiPrivatecloud": "Huawei Private Cloud",
|
||||
"AWS_China": "AWS(China)",
|
||||
"AWS_Int": "AWS(International)",
|
||||
"HuaweiCloud": "Huawei Cloud",
|
||||
"Azure":"Azure(China)",
|
||||
"Azure_Int": "Azure(International)",
|
||||
"HostnameStrategy": "Used to produce the asset hostname. For example, 1. Instance name (instanceDemo);2. Instance name and Partial IP (instanceDemo-250.1)",
|
||||
"IsAlwaysUpdate": "Asset info is kept up-to-date",
|
||||
"AccountCreate": "Create account",
|
||||
@@ -1126,7 +992,7 @@
|
||||
"AccountUpdate": "Update account",
|
||||
"AccountDetail": "Account detail",
|
||||
"Cloud": "Cloud center",
|
||||
"CloudCenter": "Cloud Center",
|
||||
"CloudCenter": "Cloud center",
|
||||
"Provider": "Provider",
|
||||
"Validity": "Validity",
|
||||
"IsAlwaysUpdateHelpTips": "Whether the asset information, including Hostname, IP, Platform, and AdminUser, is updated synchronously each time a synchronization task is performed",
|
||||
@@ -1167,18 +1033,16 @@
|
||||
"Execute": "Execute",
|
||||
"Expired": "Expired",
|
||||
"GatherUser": {
|
||||
"GatherUser": "Gather User",
|
||||
"GatherUser": "Gather user",
|
||||
"GatherUserList": "Gather user",
|
||||
"GatherUserTaskCreate": "Create gather user task",
|
||||
"GatherUserTaskList": "Gather user task list",
|
||||
"GatherUserTaskUpdate": "Update gather user task",
|
||||
"GatherUserTaskDetail": "Gather user detail",
|
||||
"GatherUserTaskExecutionList": "Gather user task execution list"
|
||||
"GatherUserTaskUpdate": "Update gather user task"
|
||||
},
|
||||
"Import": "Import",
|
||||
"ImportLicense": "Import license",
|
||||
"ImportLicenseTip": "Please Import License",
|
||||
"InterfaceSettings": "Interface Setting",
|
||||
"InterfaceSettings": "Interface setting",
|
||||
"License": "License",
|
||||
"LicenseDetail": "License detail",
|
||||
"SystemMonitor": "System Monitor",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import AutoDataForm from '@/components/AutoDataForm'
|
||||
import { getUpdateObjURL } from '@/utils/common'
|
||||
import deepmerge from 'deepmerge'
|
||||
export default {
|
||||
name: 'GenericCreateUpdateForm',
|
||||
components: {
|
||||
@@ -37,10 +37,6 @@ export default {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
afterGetFormValue: {
|
||||
type: Function,
|
||||
default: (value) => value
|
||||
},
|
||||
// 提交前,清理form的值
|
||||
cleanFormValue: {
|
||||
type: Function,
|
||||
@@ -65,14 +61,13 @@ export default {
|
||||
return this.$t('common.createSuccessMsg')
|
||||
}
|
||||
},
|
||||
// 保存成功,继续添加的msg
|
||||
// 更新成功的msg
|
||||
saveSuccessContinueMsg: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return this.$t('common.saveSuccessContinueMsg')
|
||||
}
|
||||
},
|
||||
// 更新成功的msg
|
||||
updateSuccessMsg: {
|
||||
type: String,
|
||||
default: function() {
|
||||
@@ -98,9 +93,7 @@ export default {
|
||||
objectDetailRoute: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
const routeName = this.$route.name
|
||||
.replace('Update', 'Detail')
|
||||
.replace('Create', 'Detail')
|
||||
const routeName = this.$route.name.replace('Update', 'Detail').replace('Create', 'Detail')
|
||||
return { name: routeName }
|
||||
}
|
||||
},
|
||||
@@ -130,7 +123,7 @@ export default {
|
||||
const params = this.$route.params
|
||||
let url = this.url
|
||||
if (params.id) {
|
||||
url = getUpdateObjURL(url, params.id)
|
||||
url = `${url}${params.id}/`
|
||||
}
|
||||
return url
|
||||
}
|
||||
@@ -240,8 +233,7 @@ export default {
|
||||
try {
|
||||
const values = await this.getFormValue()
|
||||
this.$log.debug('Final object is: ', values)
|
||||
const formValue = Object.assign(this.form, values)
|
||||
this.form = this.afterGetFormValue(formValue)
|
||||
this.form = Object.assign(this.form, values)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
@@ -281,9 +273,11 @@ export default {
|
||||
}
|
||||
}
|
||||
if (object) {
|
||||
object = _.cloneDeep(object)
|
||||
if (object['attrs']) {
|
||||
object = deepmerge(object, object['attrs'])
|
||||
}
|
||||
this.$log.debug('Object is: ', object)
|
||||
this.$emit('update:object', object)
|
||||
this.$emit('getObjectDone', object)
|
||||
}
|
||||
return object
|
||||
},
|
||||
|
||||
@@ -19,7 +19,6 @@ import TabPage from '../TabPage'
|
||||
import { flashErrorMsg } from '@/utils/request'
|
||||
import { getApiPath } from '@/utils/common'
|
||||
import ActionsGroup from '@/components/ActionsGroup'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'GenericDetailPage',
|
||||
@@ -81,27 +80,22 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
const defaultActions = {
|
||||
canDelete: true,
|
||||
deleteCallback: function(item) { this.defaultDelete(item) },
|
||||
deleteApiUrl: getApiPath(this),
|
||||
deleteSuccessRoute: this.$route.name.replace('Detail', 'List'),
|
||||
canUpdate: () => {
|
||||
return !vm.currentOrgIsRoot
|
||||
},
|
||||
canUpdate: true,
|
||||
updateCallback: function(item) { this.defaultUpdate(item) },
|
||||
updateRoute: this.$route.name.replace('Detail', 'Update'),
|
||||
detailApiUrl: getApiPath(this)
|
||||
}
|
||||
return {
|
||||
defaultActions: defaultActions,
|
||||
loading: true,
|
||||
validActions: Object.assign(defaultActions, this.actions)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['currentOrgIsRoot']),
|
||||
pageActions() {
|
||||
return [
|
||||
{
|
||||
@@ -164,7 +158,7 @@ export default {
|
||||
this.$message.success(this.$t('common.deleteSuccessMsg'))
|
||||
this.$router.push({ name: this.validActions.deleteSuccessRoute })
|
||||
} catch (error) {
|
||||
this.$message.error(this.$t('common.deleteErrorMsg') + ' ' + error)
|
||||
this.$message.error(this.$t('common.deleteErrorMsg' + ' ' + error))
|
||||
} finally {
|
||||
instance.confirmButtonLoading = false
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<Page v-bind="$attrs">
|
||||
<GenericListTable ref="ListTable" v-bind="$attrs" />
|
||||
<ListTable ref="ListTable" v-bind="$attrs" />
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Page from '@/layout/components/Page'
|
||||
import GenericListTable from '@/layout/components/GenericListTable'
|
||||
import ListTable from '@/components/ListTable'
|
||||
export default {
|
||||
name: 'GenericListPage',
|
||||
components: {
|
||||
Page, GenericListTable
|
||||
Page, ListTable
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<ListTable ref="ListTable" v-bind="$attrs" v-on="$listeners" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListTable from '@/components/ListTable/index'
|
||||
import { mapGetters } from 'vuex'
|
||||
export default {
|
||||
name: 'GenericListTable',
|
||||
components: {
|
||||
ListTable
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['currentOrgIsRoot'])
|
||||
},
|
||||
created() {
|
||||
const headerActions = this.$attrs['header-actions'] || {}
|
||||
if (headerActions.canCreate === undefined && this.currentOrgIsRoot) {
|
||||
_.set(this.$attrs, 'header-actions.canCreate', false)
|
||||
}
|
||||
if (headerActions.hasImport === undefined && this.currentOrgIsRoot) {
|
||||
_.set(this.$attrs, 'header-actions.hasImport', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,10 +1,7 @@
|
||||
<template>
|
||||
<Page v-bind="$attrs">
|
||||
<TreeTable
|
||||
ref="TreeTable"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<Page>
|
||||
<el-alert v-if="helpMessage" type="success"> {{ helpMessage }} </el-alert>
|
||||
<TreeTable ref="TreeTable" :table-config="tableConfig" :header-actions="headerActions" :tree-setting="treeSetting">
|
||||
<template #table>
|
||||
<slot name="table" />
|
||||
</template>
|
||||
@@ -18,22 +15,16 @@
|
||||
<script>
|
||||
import Page from '@/layout/components/Page'
|
||||
import TreeTable from '@/components/TreeTable'
|
||||
import { mapGetters } from 'vuex'
|
||||
export default {
|
||||
name: 'GenericTreeListPage',
|
||||
components: {
|
||||
Page, TreeTable
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['currentOrgIsRoot'])
|
||||
},
|
||||
created() {
|
||||
const headerActions = this.$attrs['header-actions'] || {}
|
||||
if (headerActions.canCreate === undefined && this.currentOrgIsRoot) {
|
||||
_.set(this.$attrs, 'header-actions.canCreate', false)
|
||||
}
|
||||
if (headerActions.hasImport === undefined && this.currentOrgIsRoot) {
|
||||
_.set(this.$attrs, 'header-actions.hasImport', false)
|
||||
props: {
|
||||
...TreeTable.props,
|
||||
helpMessage: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -42,12 +33,6 @@ export default {
|
||||
},
|
||||
getSelectedNodes: function() {
|
||||
return this.$refs.TreeTable.getSelectedNodes()
|
||||
},
|
||||
getNodes: function() {
|
||||
return this.$refs.TreeTable.getNodes()
|
||||
},
|
||||
selectNode: function(node) {
|
||||
return this.$refs.TreeTable.selectNode(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
|
||||
<transition name="sidebarLogoFade">
|
||||
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
|
||||
<img :src="logoSrc" class="sidebar-logo">
|
||||
<img v-if="logo" :src="logo" class="sidebar-logo">
|
||||
<h1 v-else class="sidebar-title">{{ title }}</h1>
|
||||
</router-link>
|
||||
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
||||
<img :src="logoTextSrc" class="sidebar-logo-text">
|
||||
<img :src="logoSrc" class="sidebar-logo-text">
|
||||
<!-- <img v-else-if="logoText" :src="logoText" class="sidebar-logo-text">-->
|
||||
<!-- <h1 class="sidebar-title">{{ title }}</h1>-->
|
||||
</router-link>
|
||||
</transition>
|
||||
</div>
|
||||
@@ -23,6 +26,10 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: 'JumpServer',
|
||||
logoText: require('@/assets/img/logo-text.png'),
|
||||
logo: require('@/assets/img/logo.png'),
|
||||
xpackData: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -30,14 +37,16 @@ export default {
|
||||
'publicSettings'
|
||||
]),
|
||||
// eslint-disable-next-line vue/return-in-computed-property
|
||||
logoTextSrc() {
|
||||
return this.publicSettings.LOGO_URLS.logo_index
|
||||
},
|
||||
logoSrc() {
|
||||
return this.publicSettings.LOGO_URLS.logo_logout
|
||||
if (this.publicSettings.LOGO_URLS.logo_index !== '/static/img/logo_text.png') {
|
||||
return this.publicSettings.LOGO_URLS.logo_index
|
||||
} else {
|
||||
return this.logoText
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<el-option
|
||||
v-for="item in userAdminOrgList"
|
||||
:key="item.id"
|
||||
:selected="item.id === currentOrg.id"
|
||||
selected="item.id == currentOrg.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
@@ -48,8 +48,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
needShow() {
|
||||
const hasValidLicense = this.$store.getters.hasValidLicense
|
||||
return !this.isCollapse && this.inAdminPage && hasValidLicense
|
||||
return !this.isCollapse && this.userAdminOrgList.length > 1 && this.inAdminPage
|
||||
},
|
||||
changeOrg(orgId) {
|
||||
orgUtil.changeOrg(orgId)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
import ListTable from '@/components/ListTable'
|
||||
import { DateFormatter, ShowKeyFormatter } from '@/components/TableFormatters'
|
||||
import { DateFormatter, ShowKeyFormatter } from '@/components/ListTable/formatters'
|
||||
export default {
|
||||
name: 'ApiKey',
|
||||
components: {
|
||||
@@ -61,7 +61,7 @@ export default {
|
||||
this.$refs.ListTable.reloadTable()
|
||||
this.$message.success(this.$t('common.deleteSuccessMsg'))
|
||||
}).catch(error => {
|
||||
this.$message.error(this.$t('common.deleteErrorMsg') + ' ' + error)
|
||||
this.$message.error(this.$t('common.deleteErrorMsg' + ' ' + error))
|
||||
})
|
||||
}.bind(this),
|
||||
extraActions: [
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Language',
|
||||
data() {
|
||||
@@ -46,22 +47,11 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.changeLang()
|
||||
this.changeMomentLang()
|
||||
if (this.currentLang.code !== this.$i18n.locale) {
|
||||
this.changeLangTo(this.currentLang)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeLang() {
|
||||
if (this.currentLang.code !== this.$i18n.locale) {
|
||||
this.changeLangTo(this.currentLang)
|
||||
}
|
||||
},
|
||||
changeMomentLang() {
|
||||
if (this.currentLang.code.indexOf('en') > -1) {
|
||||
this.$moment.locale('en')
|
||||
} else {
|
||||
this.$moment.locale('zh-cn')
|
||||
}
|
||||
},
|
||||
changeLangTo(item) {
|
||||
this.$i18n.locale = item.code
|
||||
localStorage.setItem('lang', item.code)
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-badge :value="unreadMsgCount" :hidden="unreadMsgCount === 0" :max="99" size="mini" type="primary">
|
||||
<a style="color: #606266 !important; width: 30px" @click="toggleDrawer">
|
||||
<i class="el-icon-message" style="font-size: 18px" />
|
||||
</a>
|
||||
</el-badge>
|
||||
<el-drawer
|
||||
:visible.sync="show"
|
||||
:before-close="handleClose"
|
||||
:modal="false"
|
||||
:title="$t('notifications.SiteMessage')"
|
||||
custom-class="site-msg"
|
||||
size="25%"
|
||||
@open="getMessages"
|
||||
>
|
||||
<div v-if="unreadMsgCount !== 0" class="msg-list">
|
||||
<div
|
||||
v-for="msg of messages"
|
||||
:key="msg.id"
|
||||
class="msg-item"
|
||||
:class="msg.has_read ? 'msg-read' : 'msg-unread'"
|
||||
@mouseover="hoverMsgId = msg.id"
|
||||
@mouseleave="hoverMsgId = ''"
|
||||
@click="showMsgDetail(msg)"
|
||||
>
|
||||
<div class="msg-item-head">
|
||||
<span class="msg-item-head-type">
|
||||
<i :class="msg.has_read ? 'fa-envelope-open-o' : 'fa-envelope'" class="fa msg-icon" />
|
||||
{{ msg.subject }}
|
||||
</span>
|
||||
<span v-if="hoverMsgId !== msg.id || msg.has_read" class="msg-item-head-time">
|
||||
{{ formatDate(msg.date_created) }}
|
||||
</span>
|
||||
<div v-else class="msg-item-read-btn" @click.stop="markAsRead(msg)">
|
||||
<a>{{ $t('notifications.MarkAsRead') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg-item-txt">
|
||||
<span v-html="msg.message" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-msg">
|
||||
{{ $t('notifications.NoUnreadMsg') }}
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<Dialog
|
||||
v-if="msgDetailVisible"
|
||||
:visible.sync="msgDetailVisible"
|
||||
:title="''"
|
||||
:close-on-click-modal="false"
|
||||
:confirm-title="$t('notifications.MarkAsRead')"
|
||||
@confirm="markAsRead(currentMsg)"
|
||||
@cancel="cancelRead"
|
||||
>
|
||||
<div class="msg-detail">
|
||||
<div class="msg-detail-head">
|
||||
<h3>{{ currentMsg.subject }}</h3>
|
||||
<h5>
|
||||
<span class="msg-detail-time">{{ formatDate(currentMsg.date_created) }}</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="msg-detail-txt">
|
||||
<span v-html="currentMsg.message" />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { toSafeLocalDateStr } from '@/utils/common'
|
||||
import Dialog from '@/components/Dialog'
|
||||
|
||||
export default {
|
||||
name: 'SiteMessages',
|
||||
components: { Dialog },
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
messages: [],
|
||||
hoverMsgId: '',
|
||||
msgDetailVisible: false,
|
||||
currentMsg: null,
|
||||
unreadMsgCount: 0
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.enablePullMsgCount()
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.show = false
|
||||
},
|
||||
toggleDrawer() {
|
||||
this.show = !this.show
|
||||
},
|
||||
showMsgDetail(msg) {
|
||||
this.currentMsg = msg
|
||||
this.msgDetailVisible = true
|
||||
},
|
||||
getMessages() {
|
||||
const url = '/api/v1/notifications/site-message/?offset=0&limit=15&has_read=false'
|
||||
this.$axios.get(url).then(resp => {
|
||||
this.messages = [...resp.results]
|
||||
this.unreadMsgCount = resp.count
|
||||
})
|
||||
},
|
||||
formatDate(s) {
|
||||
if (!s) {
|
||||
return ''
|
||||
}
|
||||
const d = new Date(s)
|
||||
const now = new Date()
|
||||
if (now.getTime() - d.getTime() > (3600 * 24 * 7) * 1000) {
|
||||
return toSafeLocalDateStr(s)
|
||||
} else {
|
||||
return this.$moment(d).fromNow()
|
||||
}
|
||||
},
|
||||
markAsRead(msg) {
|
||||
const url = `/api/v1/notifications/site-message/mark-as-read/`
|
||||
this.$axios.patch(url, { ids: [msg.id] }).then(res => {
|
||||
this.msgDetailVisible = false
|
||||
this.getMessages()
|
||||
}).catch(err => {
|
||||
this.$message(err.detail)
|
||||
})
|
||||
},
|
||||
cancelRead() {
|
||||
this.msgDetailVisible = false
|
||||
},
|
||||
enablePullMsgCount() {
|
||||
const scheme = document.location.protocol === 'https:' ? 'wss' : 'ws'
|
||||
const port = document.location.port ? ':' + document.location.port : ''
|
||||
const url = '/ws/notifications/site-msg/'
|
||||
const wsURL = scheme + '://' + document.location.hostname + port + url
|
||||
|
||||
const ws = new WebSocket(wsURL)
|
||||
ws.onopen = (event) => {
|
||||
this.$log.debug('Websocket connected: ', event)
|
||||
}
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
this.$log.debug('Data: ', data)
|
||||
const unreadCount = data['unread_count']
|
||||
if (unreadCount !== undefined) {
|
||||
this.unreadMsgCount = unreadCount
|
||||
}
|
||||
} catch (e) {
|
||||
this.$log.debug('Recv site message error')
|
||||
}
|
||||
}
|
||||
ws.onerror = (error) => {
|
||||
this.$message.error(this.$t('common.ConnectWebSocketError'))
|
||||
this.$log.debug('site message ws error: ', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-badge ::v-deep .el-badge__content.is-fixed{
|
||||
top:10px;
|
||||
}
|
||||
|
||||
.msg-list {
|
||||
padding: 0 25px 20px;
|
||||
}
|
||||
|
||||
>>> .site-msg {
|
||||
.el-drawer__header {
|
||||
border-bottom: solid 1px rgb(231, 234, 239);
|
||||
margin-bottom: 0;
|
||||
padding-top: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.el-drawer__body {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-item {
|
||||
border-bottom: solid 1px rgb(231, 234, 239);
|
||||
padding: 15px 0 10px;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #ddd;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #f2f2f2;
|
||||
padding: 15px 20px 10px;
|
||||
margin: 0 -20px;
|
||||
border-bottom: 1px solid #fff;
|
||||
}
|
||||
|
||||
.msg-icon {
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
}
|
||||
|
||||
&.msg-unread {
|
||||
.msg-item-txt {
|
||||
font-weight: bolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.msg-item-head {
|
||||
line-height: 20px;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
&:after {
|
||||
clear: both;
|
||||
content: ".";
|
||||
display: block;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.msg-item-head-type {
|
||||
float: left;
|
||||
width: 240px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.msg-item-head-time {
|
||||
float: right;
|
||||
}
|
||||
.msg-item-read-btn {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-item-txt {
|
||||
overflow: hidden;
|
||||
color: #000;
|
||||
padding: 4px 0 0;
|
||||
line-height: 21px;
|
||||
max-height: 21px;
|
||||
display: -webkit-box;
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.msg-detail {
|
||||
padding-left: 20px;
|
||||
|
||||
.msg-detail-time {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.msg-detail-txt {
|
||||
margin-bottom: 20px;
|
||||
line-height: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.no-msg {
|
||||
padding-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
>>> :focus{ outline:0; }
|
||||
</style>
|
||||
@@ -3,26 +3,30 @@
|
||||
<div class="navbar-header">
|
||||
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
|
||||
</div>
|
||||
<ul class="navbar-right">
|
||||
<li class="header-item header-icon">
|
||||
<SiteMessages />
|
||||
</li>
|
||||
<li class="header-item" style="margin-left: 10px">
|
||||
<div class="navbar-right">
|
||||
<div class="header-item">
|
||||
<Help />
|
||||
</li>
|
||||
<li class="header-item">
|
||||
</div>
|
||||
<div class="header-item">
|
||||
<Language />
|
||||
</li>
|
||||
<li v-if="showTickets" class="header-item">
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
publicSettings.TICKETS_ENABLED
|
||||
&& publicSettings.XPACK_LICENSE_IS_VALID
|
||||
&& !isOrgAuditor
|
||||
"
|
||||
class="header-item"
|
||||
>
|
||||
<Tickets />
|
||||
</li>
|
||||
<li class="header-item">
|
||||
</div>
|
||||
<div class="header-item">
|
||||
<WebTerminal />
|
||||
</li>
|
||||
<li class="header-item header-profile">
|
||||
</div>
|
||||
<div class="header-item header-profile">
|
||||
<AccountDropdown />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -30,7 +34,6 @@
|
||||
import { mapGetters } from 'vuex'
|
||||
import Hamburger from '@/components/Hamburger'
|
||||
import AccountDropdown from './AccountDropdown'
|
||||
import SiteMessages from './SiteMessages'
|
||||
import Help from './Help'
|
||||
import Language from './Language'
|
||||
import WebTerminal from './WebTerminal'
|
||||
@@ -45,8 +48,7 @@ export default {
|
||||
Language,
|
||||
Help,
|
||||
Tickets,
|
||||
WebTerminal,
|
||||
SiteMessages
|
||||
WebTerminal
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -58,17 +60,13 @@ export default {
|
||||
]),
|
||||
isOrgAuditor() {
|
||||
return rolc.getRolesDisplay(this.currentOrgRoles).includes('OrgAuditor') || rolc.getRolesDisplay(this.currentOrgRoles).includes('Auditor')
|
||||
},
|
||||
showTickets() {
|
||||
return this.publicSettings.TICKETS_ENABLED &&
|
||||
this.publicSettings.XPACK_LICENSE_IS_VALID &&
|
||||
!this.isOrgAuditor
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch('app/toggleSideBar')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -93,19 +91,12 @@ export default {
|
||||
.navbar-right {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.header-item {
|
||||
line-height: 50px;
|
||||
display: inline-block;
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
&:hover {
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
}
|
||||
.header-item {
|
||||
line-height: 50px;
|
||||
display: inline-block;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.breadcrumb-container {
|
||||
@@ -117,9 +108,5 @@ export default {
|
||||
.el-header {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<PageHeading class="disabled-when-print">
|
||||
<PageHeading>
|
||||
<slot name="title">{{ iTitle }}</slot>
|
||||
<template #rightSide>
|
||||
<slot name="headingRightSide" />
|
||||
@@ -42,15 +42,5 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@media print {
|
||||
.disabled-when-print{
|
||||
display: none;
|
||||
}
|
||||
.enabled-when-print{
|
||||
display: inherit !important;
|
||||
}
|
||||
.print-margin{
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div>
|
||||
<el-tabs v-if="submenu.length > 0" slot="submenu" v-model="iActiveMenu" class="page-submenu" @tab-click="handleTabClick">
|
||||
<template v-for="item in submenu">
|
||||
<el-tab-pane :key="item.name" :label-content="item.labelContent" :name="item.name" :disabled="item.disabled">
|
||||
<el-tab-pane :key="item.name" :label-content="item.labelContent" :name="item.name">
|
||||
<span slot="label">
|
||||
{{ item.title }}
|
||||
<slot name="badge" :tab="item.name" />
|
||||
@@ -80,23 +80,22 @@ export default {
|
||||
},
|
||||
getPropActiveTab() {
|
||||
let activeTab = ''
|
||||
let tabObj = null
|
||||
|
||||
const preActiveTabs = [
|
||||
const activeTabs = [
|
||||
this.$route.query[ACTIVE_TAB_KEY],
|
||||
this.$cookie.get(ACTIVE_TAB_KEY),
|
||||
this.activeMenu
|
||||
]
|
||||
|
||||
for (const preTab of preActiveTabs) {
|
||||
for (const tabName in this.tabIndices) {
|
||||
if (preTab && tabName && preTab.toLowerCase() === tabName.toLowerCase()) {
|
||||
return tabName
|
||||
}
|
||||
for (activeTab of activeTabs) {
|
||||
tabObj = this.tabIndices[activeTab]
|
||||
if (tabObj !== undefined) {
|
||||
return activeTab
|
||||
}
|
||||
}
|
||||
|
||||
activeTab = this.submenu[0].name
|
||||
return activeTab
|
||||
return this.submenu[0].name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ export { default as GenericDetailPage } from './GenericDetailPage'
|
||||
export { default as TabPage } from './TabPage'
|
||||
export { default as Footer } from './Footer'
|
||||
export { default as GenericListPage } from './GenericListPage'
|
||||
export { default as GenericListTable } from './GenericListTable'
|
||||
export { default as GenericCreateUpdatePage } from './GenericCreateUpdatePage'
|
||||
export { default as GenericCreateUpdateForm } from './GenericCreateUpdateForm'
|
||||
export { default as GenericUpdateFormDialog } from './GenericUpdateFormDialog'
|
||||
|
||||
@@ -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 disabled-when-print" />
|
||||
<NavBar class="sidebar-container" />
|
||||
<div :class="{hasTagsView:needTagsView}" class="main-container">
|
||||
<div :class="{'fixed-header':fixedHeader}" class="disabled-when-print">
|
||||
<div :class="{'fixed-header':fixedHeader}">
|
||||
<NavHeader />
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
<app-main />
|
||||
<Footer class="disabled-when-print" />
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -98,28 +98,4 @@ 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>
|
||||
|
||||
@@ -40,13 +40,8 @@ Vue.config.productionTip = false
|
||||
import VueCookie from 'vue-cookie'
|
||||
Vue.use(VueCookie)
|
||||
window.$cookie = VueCookie
|
||||
|
||||
const moment = require('moment')
|
||||
require('moment/locale/zh-cn')
|
||||
Vue.use(require('vue-moment'), {
|
||||
moment
|
||||
})
|
||||
|
||||
import VueMoment from 'vue-moment'
|
||||
Vue.use(VueMoment)
|
||||
// logger
|
||||
import VueLogger from 'vuejs-logger'
|
||||
import loggerOptions from './utils/logger'
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
import i18n from '@/i18n/i18n'
|
||||
import empty from '@/layout/empty'
|
||||
export default [
|
||||
{
|
||||
path: 'asset-accounts',
|
||||
component: empty,
|
||||
meta: { title: i18n.t('route.AssetAccount') },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'AssetAccountList',
|
||||
component: () => import('@/views/accounts/AssetAccount/AssetAccountList'),
|
||||
meta: { title: i18n.t('route.AssetAccount') }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'application-accounts',
|
||||
component: empty,
|
||||
meta: { title: i18n.t('route.AssetAccount') },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'ApplicationAccountList',
|
||||
component: () => import('@/views/accounts/ApplicationAccount/ApplicationAccountList'),
|
||||
meta: { title: i18n.t('route.ApplicationAccount') }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'gathered-user',
|
||||
component: empty,
|
||||
redirect: '',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserList') },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/GatheredUser/index'),
|
||||
name: 'GatherUserListIndex',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUser'), activeMenu: '/accounts/gathered-user' }
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/GatheredUser/GatheredUserList'),
|
||||
name: 'GatherUserList',
|
||||
hidden: true,
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserList'), activeMenu: '/accounts/gathered-user' }
|
||||
},
|
||||
{
|
||||
path: 'tasks',
|
||||
component: () => import('@/views/accounts/GatheredUser/TaskList'),
|
||||
name: 'GatherUserTaskList',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskList'), activeMenu: '/accounts/gathered-user' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'tasks/:id',
|
||||
component: () => import('@/views/accounts/GatheredUser/TaskDetail/index'),
|
||||
name: 'GatherUserTaskDetail',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskDetail'), activeMenu: '/accounts/gathered-user' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'tasks/create',
|
||||
component: () => import('@/views/accounts/GatheredUser/TaskCreateUpdate'),
|
||||
name: 'GatherUserTaskCreate',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskCreate'), activeMenu: '/accounts/gathered-user' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'tasks/:id/update',
|
||||
component: () => import('@/views/accounts/GatheredUser/TaskCreateUpdate'),
|
||||
name: 'GatherUserTaskUpdate',
|
||||
meta: { title: i18n.t('xpack.GatherUser.GatherUserTaskUpdate'), action: 'update', activeMenu: '/accounts/gathered-user' },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'change-auth-plan',
|
||||
component: empty,
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan/plan' },
|
||||
children: [
|
||||
{
|
||||
path: 'plan',
|
||||
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanList.vue'),
|
||||
name: 'ChangeAuthPlanList',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan/plan' }
|
||||
},
|
||||
{
|
||||
path: 'plan/create',
|
||||
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanCreateUpdate.vue'),
|
||||
name: 'ChangeAuthPlanCreate',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlanCreate'), activeMenu: '/accounts/change-auth-plan/plan', action: 'create' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'plan/:id/update',
|
||||
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanCreateUpdate.vue'),
|
||||
name: 'ChangeAuthPlanUpdate',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlanUpdate'), activeMenu: '/accounts/change-auth-plan/plan', action: 'update' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'plan/:id',
|
||||
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanDetail/index.vue'),
|
||||
name: 'ChangeAuthPlanDetail',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ChangeAuthPlan'), activeMenu: '/accounts/change-auth-plan/plan' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'plan-execution/:id',
|
||||
component: () => import('@/views/accounts/ChangeAuthPlan/ChangeAuthPlanDetail/ChangeAuthPlanExecution/ChangeAuthPlanExecutionDetail/index.vue'),
|
||||
name: 'ChangeAuthPlanExecutionDetail',
|
||||
meta: { title: i18n.t('xpack.ChangeAuthPlan.ExecutionDetail'), activeMenu: '/accounts/change-auth-plan/plan' },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,42 +0,0 @@
|
||||
import i18n from '@/i18n/i18n'
|
||||
import empty from '@/layout/empty'
|
||||
export default [
|
||||
{
|
||||
path: 'asset-acl',
|
||||
component: empty,
|
||||
redirect: '',
|
||||
meta: {
|
||||
title: i18n.t('route.AssetAclList'),
|
||||
licenseRequired: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'AssetAclList',
|
||||
component: () => import('@/views/acl/AssetAcl/AssetAclList'),
|
||||
meta: { title: i18n.t('route.AssetAclList'), activeMenu: '/acl/asset-acl' }
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
name: 'AssetAclCreate',
|
||||
component: () => import('@/views/acl/AssetAcl/AssetAclCreateUpdate'),
|
||||
meta: { title: i18n.t('route.AssetAclCreate'), activeMenu: '/acl/asset-acl' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
name: 'AssetAclDetail',
|
||||
component: () => import('@/views/acl/AssetAcl/AssetAclDetail'),
|
||||
meta: { title: i18n.t('route.AssetAclDetail'), activeMenu: '/acl/asset-acl' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
name: 'AssetAclUpdate',
|
||||
component: () => import('@/views/acl/AssetAcl/AssetAclCreateUpdate'),
|
||||
meta: { title: i18n.t('route.AssetAclUpdate'), activeMenu: '/acl/asset-acl' },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -101,6 +101,41 @@ export default [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'admin-users',
|
||||
component: empty,
|
||||
redirect: '',
|
||||
meta: { title: i18n.t('route.AdminUserList') },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'AdminUserList',
|
||||
component: () => import('@/views/assets/AdminUser/AdminUserList'),
|
||||
meta: { title: i18n.t('route.AdminUserList'), activeMenu: '/assets/admin-users' }
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/assets/AdminUser/AdminUserCreateUpdate.vue'), // Parent router-view
|
||||
name: 'AdminUserCreate',
|
||||
meta: { title: i18n.t('route.AdminUserCreate'), activeMenu: '/assets/admin-users' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/assets/AdminUser/AdminUserCreateUpdate.vue'), // Parent router-view
|
||||
name: 'AdminUserUpdate',
|
||||
meta: { title: i18n.t('route.AdminUserUpdate'), activeMenu: '/assets/admin-users' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/assets/AdminUser/AdminUserDetail/index.vue'), // Parent router-view
|
||||
name: 'AdminUserDetail',
|
||||
meta: { title: i18n.t('route.AdminUserDetail'), activeMenu: '/assets/admin-users' },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'system-users',
|
||||
component: empty,
|
||||
@@ -110,20 +145,20 @@ export default [
|
||||
{
|
||||
path: '',
|
||||
name: 'SystemUserList',
|
||||
component: () => import('@/views/assets/SystemUser/SystemUserList/index.vue'),
|
||||
component: () => import('@/views/assets/SystemUser/SystemUserList.vue'),
|
||||
meta: { title: i18n.t('route.SystemUserList'), activeMenu: '/assets/system-users' }
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
name: 'SystemUserCreate',
|
||||
component: () => import('@/views/assets/SystemUser/SystemUserCreateUpdate/index.vue'),
|
||||
component: () => import('@/views/assets/SystemUser/SystemUserCreateUpdate.vue'),
|
||||
meta: { title: i18n.t('route.SystemUserCreate'), activeMenu: '/assets/system-users' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
name: 'SystemUserUpdate',
|
||||
component: () => import('@/views/assets/SystemUser/SystemUserCreateUpdate/index.vue'),
|
||||
component: () => import('@/views/assets/SystemUser/SystemUserCreateUpdate.vue'),
|
||||
meta: { title: i18n.t('route.SystemUserUpdate'), activeMenu: '/assets/system-users' },
|
||||
hidden: true
|
||||
},
|
||||
|
||||
@@ -36,8 +36,6 @@ import OpsRoutes from './ops'
|
||||
import TicketsRoutes from './tickets'
|
||||
import AuditsRoutes from './audits'
|
||||
import commonRoutes from './common'
|
||||
import aclRoutes from './acl'
|
||||
import AccountRoutes from './accounts'
|
||||
|
||||
/**
|
||||
* constantRoutes
|
||||
@@ -111,18 +109,6 @@ export const allRoleRoutes = [
|
||||
meta: { title: i18n.t('route.Applications'), icon: 'th' },
|
||||
children: ApplicationsRoute
|
||||
},
|
||||
{
|
||||
path: '/accounts',
|
||||
component: Layout,
|
||||
redirect: '/accounts/asset-accounts/',
|
||||
name: 'Accounts',
|
||||
meta: {
|
||||
licenseRequired: true,
|
||||
title: i18n.t('route.Accounts'),
|
||||
icon: 'address-book'
|
||||
},
|
||||
children: AccountRoutes
|
||||
},
|
||||
{
|
||||
path: '/perms/',
|
||||
component: Layout,
|
||||
@@ -131,17 +117,6 @@ export const allRoleRoutes = [
|
||||
meta: { title: i18n.t('route.Perms'), icon: 'edit' },
|
||||
children: PermsRoute
|
||||
},
|
||||
{
|
||||
path: '/acl/',
|
||||
component: Layout,
|
||||
redirect: '/perms/access-control-list/',
|
||||
name: 'Acl',
|
||||
meta: {
|
||||
licenseRequired: true,
|
||||
title: i18n.t('route.Acl'),
|
||||
icon: 'fort-awesome' },
|
||||
children: aclRoutes
|
||||
},
|
||||
{
|
||||
path: '/terminal/',
|
||||
component: Layout,
|
||||
|
||||
@@ -44,7 +44,7 @@ export default [
|
||||
// meta: { title: i18n.t('route.CeleryTaskLog') }
|
||||
// },
|
||||
{
|
||||
path: `${BASE_URL}/core/flower/?_=${Date.now()}`,
|
||||
path: `${BASE_URL}/core/flower?_=${Date.now()}`,
|
||||
name: 'TaskMonitor',
|
||||
// component: () => window.open(`/core/flower?_=${Date.now()}`),
|
||||
meta: { title: i18n.t('route.TaskMonitor'), permissions: [rolec.PERM_SUPER] }
|
||||
|
||||
@@ -28,13 +28,6 @@ export default [
|
||||
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets/tickets' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'tickets/login-asset-confirm/:id',
|
||||
name: 'loginAssetTicketDetail',
|
||||
component: () => import('@/views/tickets/LoginAssetConfirm/Detail/index'),
|
||||
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets/tickets' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'tickets/request-application-perm/create',
|
||||
name: 'RequestApplicationPermTicketCreateUpdate',
|
||||
@@ -48,12 +41,5 @@ export default [
|
||||
component: () => import('@/views/tickets/RequestApplicationPerm/Detail/index'),
|
||||
meta: { title: i18n.t('route.TicketDetail'), activeMenu: '/tickets/tickets' },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'tickets/command-confirm/:id',
|
||||
name: 'CommandConfirmDetail',
|
||||
component: () => import('@/views/tickets/CommandConfirm/Detail/index'),
|
||||
meta: { title: i18n.t('route.CommandConfirm'), activeMenu: '/tickets/tickets' },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user