mirror of
https://github.com/jumpserver/lina.git
synced 2026-01-14 03:46:26 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2732050e61 | ||
|
|
4e904add32 | ||
|
|
b85ed8405e | ||
|
|
e827deef35 | ||
|
|
3c2c2ca302 | ||
|
|
453ca578b5 | ||
|
|
aa622ff812 | ||
|
|
171ceeef3a | ||
|
|
754e861294 | ||
|
|
e7b37d43f2 | ||
|
|
fd749ef211 | ||
|
|
5667c39bcb | ||
|
|
982ce90a9a | ||
|
|
86ce758adb |
@@ -4,17 +4,11 @@ root = true
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_size = 2
|
||||
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
32
.github/workflows/jms-build-test.yml
vendored
32
.github/workflows/jms-build-test.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: "Run Build Test"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- pr@*
|
||||
- repr@*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
|
||||
- uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
tags: jumpserver/lina:test
|
||||
file: Dockerfile
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- uses: LouisBrunner/checks-action@v1.5.0
|
||||
if: always()
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: Check Build
|
||||
conclusion: ${{ job.status }}
|
||||
5
.github/workflows/release-drafter.yml
vendored
5
.github/workflows/release-drafter.yml
vendored
@@ -31,9 +31,6 @@ jobs:
|
||||
config-name: release-config.yml
|
||||
version: ${{ steps.get_version.outputs.TAG }}
|
||||
tag: ${{ steps.get_version.outputs.TAG }}
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.16'
|
||||
|
||||
build-and-release:
|
||||
needs: create-realese
|
||||
@@ -42,7 +39,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build it and upload
|
||||
uses: jumpserver/action-build-upload-assets@node10
|
||||
uses: jumpserver/action-build-upload-assets@node14.16
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,4 +16,3 @@ tests/**/coverage/
|
||||
*.njsproj
|
||||
*.sln
|
||||
.env.development
|
||||
.python-version
|
||||
|
||||
10
Dockerfile
10
Dockerfile
@@ -1,22 +1,24 @@
|
||||
FROM node:14.16 as stage-build
|
||||
ARG TARGETARCH
|
||||
ARG NPM_REGISTRY="https://registry.npmmirror.com"
|
||||
ENV NPM_REGISTY=$NPM_REGISTRY
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
RUN set -ex \
|
||||
&& npm config set registry ${NPM_REGISTRY} \
|
||||
&& yarn config set registry ${NPM_REGISTRY}
|
||||
&& yarn config set registry ${NPM_REGISTRY} \
|
||||
&& yarn config set cache-folder /root/.cache/yarn/lina
|
||||
|
||||
ADD package.json yarn.lock /data
|
||||
RUN --mount=type=cache,target=/usr/local/share/.cache/yarn,sharing=locked,id=lina \
|
||||
RUN --mount=type=cache,target=/root/.cache/yarn \
|
||||
yarn install
|
||||
|
||||
ARG VERSION
|
||||
ENV VERSION=$VERSION
|
||||
ADD . /data
|
||||
RUN --mount=type=cache,target=/usr/local/share/.cache/yarn,sharing=locked,id=lina \
|
||||
sed -i "s@version-dev@${VERSION}@g" src/layout/components/NavHeader/About.vue \
|
||||
RUN --mount=type=cache,target=/root/.cache/yarn \
|
||||
sed -i "s@Version <strong>.*</strong>@Version <strong>${VERSION}</strong>@g" src/layout/components/Footer/index.vue \
|
||||
&& yarn build
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
2
GITSHA
2
GITSHA
@@ -1 +1 @@
|
||||
955dabb02f5258167a547ca4502b6585fd0900ce
|
||||
4e904add32bb32432d3453465266b2316632bc24
|
||||
|
||||
10
package.json
10
package.json
@@ -12,16 +12,13 @@
|
||||
"build:stage": "vue-cli-service build --mode staging",
|
||||
"preview": "node build/index.js --preview",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"fix": "eslint --ext .js,.vue --fix src",
|
||||
"test:unit": "jest --clearCache && vue-cli-service test:unit",
|
||||
"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",
|
||||
"diff-i18n": "python ./src/i18n/langs/i18n-util.py diff en ja",
|
||||
"apply-i18n": "python ./src/i18n/langs/i18n-util.py apply en ja"
|
||||
"vue-i18n-report-add-miss": "vue-i18n-extract report -v './src/**/*.?(js|vue)' -l './src/i18n/langs/**/*.json' -a"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
|
||||
@@ -61,7 +58,7 @@
|
||||
"nprogress": "0.2.0",
|
||||
"path-to-regexp": "2.4.0",
|
||||
"vue": "2.6.10",
|
||||
"vue-codemirror": "4.0.6",
|
||||
"vue-codemirror-lite": "^1.0.4",
|
||||
"vue-cookie": "^1.1.4",
|
||||
"vue-echarts": "^5.0.0-beta.0",
|
||||
"vue-i18n": "^8.15.5",
|
||||
@@ -73,7 +70,6 @@
|
||||
"vue-select": "^3.9.5",
|
||||
"vuejs-logger": "^1.5.4",
|
||||
"vuex": "3.1.0",
|
||||
"xss": "^1.0.14",
|
||||
"xterm": "^4.5.0",
|
||||
"xterm-addon-fit": "^0.3.0",
|
||||
"zxcvbn": "^4.4.2"
|
||||
@@ -97,7 +93,7 @@
|
||||
"eslint": "^5.15.3",
|
||||
"eslint-plugin-vue": "5.2.2",
|
||||
"eslint-plugin-vue-i18n": "^0.3.0",
|
||||
"github-markdown-css": "^5.1.0",
|
||||
"github-markdown-css": "^5.2.0",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"husky": "^4.2.3",
|
||||
"less-loader": "^5.0.0",
|
||||
|
||||
@@ -17,15 +17,9 @@
|
||||
</noscript>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
if (location.pathname === '/') {
|
||||
location.pathname = '/ui/'
|
||||
}
|
||||
const pathname = window.location.pathname
|
||||
if (pathname.startsWith('/core')) {
|
||||
return
|
||||
}
|
||||
if(pathname.indexOf('/ui') === -1) {
|
||||
window.location.href = window.location.origin + '/ui/#' + pathname
|
||||
const baseUrl = "/ui/"
|
||||
if (location.pathname === '/' && baseUrl !== '/') {
|
||||
location.pathname = baseUrl
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -194,16 +194,17 @@ td .el-button.el-button--mini {
|
||||
line-height: 34px;
|
||||
}
|
||||
|
||||
.option-group .el-select-dropdown__item.hover, .option-group .el-select-dropdown__item.selected {
|
||||
.el-select-dropdown.is-multiple .el-select-dropdown__item.selected {
|
||||
color: #606266;
|
||||
background-color: #ddd;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.hover {
|
||||
background-color: primary;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.option-group:has(.hover) .el-select-dropdown__item.selected {
|
||||
background-color: light-2;
|
||||
}
|
||||
|
||||
|
||||
.el-select-dropdown__item.is-disabled:hover{
|
||||
color:#c0c4cc;
|
||||
}
|
||||
@@ -214,7 +215,7 @@ td .el-button.el-button--mini {
|
||||
|
||||
.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover {
|
||||
color: white;
|
||||
background-color: light-4;
|
||||
background-color: primary;
|
||||
}
|
||||
|
||||
.el-tag.el-tag--info {
|
||||
@@ -330,12 +331,13 @@ td .el-button.el-button--mini {
|
||||
}
|
||||
|
||||
.el-table .cell,
|
||||
.el-table td:first-child .cell,
|
||||
.el-table th:first-child .cell {
|
||||
.el-table--border td:first-child .cell,
|
||||
.el-table--border th:first-child .cell {
|
||||
padding-left: 10px;
|
||||
padding-right: 14px;
|
||||
}
|
||||
|
||||
|
||||
.el-tag--default.el-tag--dark {
|
||||
background-color: #d1dade;
|
||||
color: #5e5e5e;
|
||||
@@ -451,48 +453,11 @@ td .el-button.el-button--mini {
|
||||
|
||||
.el-select-dropdown__item.selected {
|
||||
font-weight: 400;
|
||||
color: white;
|
||||
background-color: primary;
|
||||
color: #606266;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.el-input-group__prepend div.el-select .el-input__inner,
|
||||
.el-input-group__prepend div.el-select .el-input__inner:hover {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.el-input-group__append, .el-input-group__prepend {
|
||||
color: primary
|
||||
}
|
||||
|
||||
.el-input.is-disabled .el-input__inner {
|
||||
color: $--color-text-primary;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.el-step__description.is-finish {
|
||||
color: #676a6c;
|
||||
}
|
||||
|
||||
.el-alert {
|
||||
border: solid 1px #e7eaec;
|
||||
}
|
||||
|
||||
.el-alert.el-alert--success.is-light {
|
||||
border-color: var(--color-success-light);
|
||||
}
|
||||
|
||||
.el-alert.el-alert--primary.is-light {
|
||||
border-color: var(--color-primary-light);
|
||||
}
|
||||
|
||||
.el-alert.el-alert--info.is-light {
|
||||
border-color: var(--color-info-light);
|
||||
}
|
||||
|
||||
.el-alert.el-alert--warning.is-light {
|
||||
border-color: var(--color-warning-light);
|
||||
}
|
||||
|
||||
.el-alert.el-alert--error.is-light {
|
||||
border-color: var(--color-danger-light);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
11
src/App.vue
11
src/App.vue
@@ -1,19 +1,12 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view v-if="isRouterAlive" />
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
computed: {
|
||||
...mapState({
|
||||
isRouterAlive: state => state.common.isRouterAlive
|
||||
})
|
||||
}
|
||||
name: 'App'
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -40,10 +40,3 @@ export function getCommandFilterList(data) {
|
||||
})
|
||||
}
|
||||
|
||||
export function getCategoryTypes() {
|
||||
return request({
|
||||
url: '/api/v1/assets/categories/',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function createSourceIdCache(ids) {
|
||||
ids = ids.map(item => {
|
||||
if (typeof item === 'object' && item.id) {
|
||||
return item.id
|
||||
} else {
|
||||
return item
|
||||
}
|
||||
})
|
||||
return request({
|
||||
url: '/api/v1/common/resources/cache/',
|
||||
method: 'post',
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getTaskDetail(id) {
|
||||
return request({
|
||||
url: `/api/v1/ops/tasks/${id}/`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getAdhocDetail(id) {
|
||||
return request({
|
||||
url: `/api/v1/ops/adhoc/${id}/`,
|
||||
@@ -13,34 +20,3 @@ export function getHistoryExecutionDetail(id) {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getTaskDetail(id) {
|
||||
return request({
|
||||
url: `/api/v1/ops/job-execution/task-detail/${id}/`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getJob(id) {
|
||||
return request({
|
||||
url: `/api/v1/ops/jobs/${id}/`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function uploadPlaybook(form) {
|
||||
return request({
|
||||
url: '/api/v1/ops/playbooks/',
|
||||
method: 'post',
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
data: form
|
||||
})
|
||||
}
|
||||
|
||||
export function renameFile(playbookId, node) {
|
||||
return request({
|
||||
url: `/api/v1/ops/playbook/${playbookId}/file/`,
|
||||
method: 'patch',
|
||||
data: node
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getAssetPermissionDetail(id) {
|
||||
return request({
|
||||
url: `/api/v1/perms/asset-permissions/${id}/`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getRemoteAppPermissionDetail(id) {
|
||||
return request({
|
||||
url: `/api/v1/perms/remote-app-permissions/${id}/`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getDatabaseAppPermissionDetail(id) {
|
||||
return request({
|
||||
url: `/api/v1/perms/database-app-permissions/${id}/`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getUserAssetGrantedSystemUsers(userId, assetId) {
|
||||
return request({
|
||||
url: `/api/v1/perms/users/${userId}/assets/${assetId}/system-users/?cache_policy=1`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getMyAssetGrantedSystemUsers(userId, assetId) {
|
||||
return request({
|
||||
url: `/api/v1/perms/users/assets/${assetId}/system-users/?cache_policy=1`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getUserGroupAssetGrantedSystemUsers(gId, assetId) {
|
||||
return request({
|
||||
url: `/api/v1/perms/user-groups/${gId}/assets/${assetId}/system-users/?cache_policy=1`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
BIN
src/assets/img/admin.png
Normal file
BIN
src/assets/img/admin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,306 +0,0 @@
|
||||
<template>
|
||||
<AutoDataForm
|
||||
v-if="!loading"
|
||||
ref="AutoDataForm"
|
||||
v-bind="$data"
|
||||
@submit="confirm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AutoDataForm from '@/components/AutoDataForm'
|
||||
import { UpdateToken, UploadSecret } from '@/components/FormFields'
|
||||
import Select2 from '@/components/FormFields/Select2'
|
||||
import AssetSelect from '@/components/AssetSelect'
|
||||
import { encryptPassword } from '@/utils/crypto'
|
||||
import { Required, RequiredChange } from '@/components/DataForm/rules'
|
||||
import AutomationParamsForm from '@/views/assets/Platform/AutomationParamsSetting.vue'
|
||||
|
||||
export default {
|
||||
name: 'AccountCreateForm',
|
||||
components: {
|
||||
AutoDataForm
|
||||
},
|
||||
props: {
|
||||
asset: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
platform: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
account: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 默认组件密码加密
|
||||
encryptPassword: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
addTemplate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
usernameChanged: false,
|
||||
defaultPrivilegedAccounts: ['root', 'administrator'],
|
||||
iPlatform: {
|
||||
automation: {},
|
||||
su_enabled: false,
|
||||
protocols: [
|
||||
{
|
||||
name: 'ssh',
|
||||
secret_types: ['password', 'ssh_key', 'token', 'access_key']
|
||||
}
|
||||
]
|
||||
},
|
||||
url: '/api/v1/accounts/accounts/',
|
||||
form: Object.assign({ 'on_invalid': 'error' }, this.account || {}),
|
||||
encryptedFields: ['secret'],
|
||||
fields: [
|
||||
[this.$t('assets.Asset'), ['assets']],
|
||||
[this.$t('accounts.AccountTemplate'), ['template']],
|
||||
[this.$t('common.Basic'), ['name', 'username', 'privileged', 'su_from']],
|
||||
[this.$t('assets.Secret'), [
|
||||
'secret_type', 'secret', 'ssh_key',
|
||||
'token', 'access_key', 'passphrase'
|
||||
]],
|
||||
[this.$t('common.Other'), ['push_now', 'params', 'on_invalid', 'is_active', 'comment']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
assets: {
|
||||
rules: [Required],
|
||||
component: AssetSelect,
|
||||
label: this.$t('assets.Asset'),
|
||||
el: {
|
||||
multiple: false
|
||||
},
|
||||
hidden: () => {
|
||||
return this.platform || this.asset
|
||||
}
|
||||
},
|
||||
template: {
|
||||
component: Select2,
|
||||
rules: [Required],
|
||||
el: {
|
||||
multiple: false,
|
||||
ajax: {
|
||||
url: '/api/v1/accounts/account-templates/',
|
||||
transformOption: (item) => {
|
||||
return { label: item.name, value: item.id }
|
||||
}
|
||||
}
|
||||
},
|
||||
hidden: () => {
|
||||
return this.platform || this.asset || !this.addTemplate
|
||||
}
|
||||
},
|
||||
on_invalid: {
|
||||
rules: [Required],
|
||||
label: this.$t('accounts.AccountPolicy'),
|
||||
helpText: this.$t('accounts.BulkCreateStrategy'),
|
||||
hidden: () => {
|
||||
return this.platform || this.asset
|
||||
}
|
||||
},
|
||||
name: {
|
||||
rules: [RequiredChange],
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
if (!this.usernameChanged) {
|
||||
if (!this.account?.name) {
|
||||
updateForm({ username: value })
|
||||
}
|
||||
const maybePrivileged = this.defaultPrivilegedAccounts.includes(value)
|
||||
if (maybePrivileged) {
|
||||
updateForm({ privileged: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
hidden: () => {
|
||||
return this.addTemplate
|
||||
}
|
||||
},
|
||||
username: {
|
||||
el: {
|
||||
disabled: !!this.account?.name
|
||||
},
|
||||
on: {
|
||||
input: ([value], updateForm) => {
|
||||
this.usernameChanged = true
|
||||
},
|
||||
change: ([value], updateForm) => {
|
||||
const maybePrivileged = this.defaultPrivilegedAccounts.includes(value)
|
||||
if (maybePrivileged) {
|
||||
updateForm({ privileged: true })
|
||||
}
|
||||
}
|
||||
},
|
||||
hidden: () => {
|
||||
return this.addTemplate
|
||||
}
|
||||
},
|
||||
privileged: {
|
||||
hidden: () => {
|
||||
return this.addTemplate
|
||||
}
|
||||
},
|
||||
su_from: {
|
||||
component: Select2,
|
||||
hidden: (formValue) => {
|
||||
return !this.asset?.id || !this.iPlatform.su_enabled
|
||||
},
|
||||
el: {
|
||||
multiple: false,
|
||||
clearable: true,
|
||||
ajax: {
|
||||
url: `/api/v1/accounts/accounts/su-from-accounts/?account=${this.account?.id || ''}&asset=${this.asset?.id || ''}`,
|
||||
transformOption: (item) => {
|
||||
return { label: `${item.name}(${item.username})`, value: item.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
secret: {
|
||||
label: this.$t('assets.Password'),
|
||||
component: UpdateToken,
|
||||
hidden: (formValue) => formValue.secret_type !== 'password' || this.addTemplate
|
||||
},
|
||||
ssh_key: {
|
||||
label: this.$t('assets.PrivateKey'),
|
||||
component: UploadSecret,
|
||||
hidden: (formValue) => formValue.secret_type !== 'ssh_key' || this.addTemplate
|
||||
},
|
||||
passphrase: {
|
||||
label: this.$t('assets.Passphrase'),
|
||||
component: UpdateToken,
|
||||
hidden: (formValue) => formValue.secret_type !== 'ssh_key' || this.addTemplate
|
||||
},
|
||||
token: {
|
||||
label: this.$t('assets.Token'),
|
||||
component: UploadSecret,
|
||||
hidden: (formValue) => formValue.secret_type !== 'token' || this.addTemplate
|
||||
},
|
||||
access_key: {
|
||||
id: 'access_key',
|
||||
label: this.$t('assets.AccessKey'),
|
||||
component: UploadSecret,
|
||||
hidden: (formValue) => formValue.secret_type !== 'access_key' || this.addTemplate
|
||||
},
|
||||
secret_type: {
|
||||
type: 'radio-group',
|
||||
options: [],
|
||||
hidden: () => {
|
||||
return this.addTemplate
|
||||
}
|
||||
},
|
||||
push_now: {
|
||||
helpText: this.$t('accounts.AccountPush.WindowsPushHelpText'),
|
||||
hidden: () => {
|
||||
const automation = this.iPlatform.automation || {}
|
||||
return !automation.push_account_enabled ||
|
||||
!automation.ansible_enabled ||
|
||||
!this.$hasPerm('accounts.push_account') ||
|
||||
this.addTemplate
|
||||
}
|
||||
},
|
||||
params: {
|
||||
label: this.$t('assets.PushParams'),
|
||||
component: AutomationParamsForm,
|
||||
el: {
|
||||
method: this.asset?.auto_config?.push_account_method
|
||||
},
|
||||
hidden: (formValue) => {
|
||||
const automation = this.iPlatform.automation || {}
|
||||
return !formValue.push_now ||
|
||||
!automation.push_account_enabled ||
|
||||
!automation.ansible_enabled ||
|
||||
!this.$hasPerm('accounts.push_account') ||
|
||||
this.addTemplate
|
||||
}
|
||||
},
|
||||
comment: {
|
||||
hidden: () => {
|
||||
return this.addTemplate
|
||||
}
|
||||
}
|
||||
},
|
||||
hasSaveContinue: false
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
try {
|
||||
await this.getPlatform()
|
||||
this.setSecretTypeOptions()
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getPlatform() {
|
||||
if (this.platform) {
|
||||
this.iPlatform = this.platform
|
||||
}
|
||||
if (!this.asset || !this.asset.platform) {
|
||||
return
|
||||
}
|
||||
const platformId = this.asset.platform.id
|
||||
this.iPlatform = await this.$axios.get(`/api/v1/assets/platforms/${platformId}/`)
|
||||
},
|
||||
setSecretTypeOptions() {
|
||||
const choices = [
|
||||
{
|
||||
label: this.$t('assets.Password'),
|
||||
value: 'password'
|
||||
},
|
||||
{
|
||||
label: this.$t('assets.SSHKey'),
|
||||
value: 'ssh_key'
|
||||
},
|
||||
{
|
||||
label: this.$t('assets.Token'),
|
||||
value: 'token'
|
||||
},
|
||||
{
|
||||
label: this.$t('assets.AccessKey'),
|
||||
value: 'access_key'
|
||||
}
|
||||
]
|
||||
const secretTypes = []
|
||||
this.iPlatform.protocols?.forEach(p => {
|
||||
secretTypes.push(...p['secret_types'])
|
||||
})
|
||||
if (!this.form?.secret_type) {
|
||||
this.form.secret_type = secretTypes[0]
|
||||
}
|
||||
this.fieldsMeta.secret_type.options = choices.filter(item => {
|
||||
return secretTypes.indexOf(item.value) > -1
|
||||
})
|
||||
},
|
||||
confirm(form) {
|
||||
const secretType = form.secret_type || ''
|
||||
if (secretType !== 'password') {
|
||||
form.secret = form[secretType]
|
||||
}
|
||||
form.secret = this.encryptPassword ? encryptPassword(form.secret) : form.secret
|
||||
if (!form.secret) {
|
||||
delete form['secret']
|
||||
}
|
||||
if (this.account?.name) {
|
||||
this.$emit('edit', form)
|
||||
} else {
|
||||
this.$emit('add', form)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
156
src/components/AccountHistoryTable/index.vue
Normal file
156
src/components/AccountHistoryTable/index.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div>
|
||||
<GenericListPage :table-config="tableConfig" :header-actions="headerActions" :help-message="title" />
|
||||
<ShowSecretInfo v-if="showViewSecretDialog" :visible.sync="showViewSecretDialog" :account="account" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ActionsFormatter, DetailFormatter, DisplayFormatter } from '@/components/TableFormatters'
|
||||
import ShowSecretInfo from '../AccountListTable/ShowSecretInfo'
|
||||
import { GenericListPage } from '@/layout/components'
|
||||
|
||||
export default {
|
||||
name: 'AccountHistoryTable',
|
||||
components: {
|
||||
GenericListPage,
|
||||
ShowSecretInfo
|
||||
},
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
exportUrl: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.url.replace('/assets/accounts-history/', '/assets/account-history-secrets/')
|
||||
}
|
||||
},
|
||||
hasLeftActions: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
otherActions: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
hasClone: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
showViewSecretDialog: false,
|
||||
account: {},
|
||||
tableConfig: {
|
||||
url: this.url,
|
||||
permissions: {
|
||||
app: 'assets',
|
||||
resource: 'authbook'
|
||||
},
|
||||
columns: [
|
||||
'hostname', 'ip', 'username', 'version',
|
||||
'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: {
|
||||
can: this.$hasPerm('assets.view_asset'),
|
||||
getRoute({ row }) {
|
||||
return {
|
||||
name: 'AssetDetail',
|
||||
params: { id: row.asset }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ip: {
|
||||
width: '120px'
|
||||
},
|
||||
username: {
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
systemuser: {
|
||||
formatter: DisplayFormatter
|
||||
},
|
||||
version: {
|
||||
width: '70px'
|
||||
},
|
||||
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'),
|
||||
can: this.$hasPerm('assets.view_assethistoryaccountsecret'),
|
||||
type: 'primary',
|
||||
callback: ({ row }) => {
|
||||
vm.account = row
|
||||
vm.showViewSecretDialog = true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: this.hasLeftActions,
|
||||
hasMoreActions: false,
|
||||
hasCreate: false,
|
||||
hasImport: false,
|
||||
hasExport: this.$hasPerm('assets.view_assethistoryaccountsecret'),
|
||||
exportOptions: {
|
||||
url: this.exportUrl,
|
||||
mfaVerifyRequired: true
|
||||
},
|
||||
searchConfig: {
|
||||
exclude: ['systemuser', 'asset']
|
||||
},
|
||||
hasSearch: true
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.$t('accounts.AccountHistableHelpMessage')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
url(iNew) {
|
||||
this.$set(this.tableConfig, 'url', iNew)
|
||||
this.$set(this.headerActions.exportOptions, 'url', iNew.replace('/accounts-history/', '/account-history-secrets/'))
|
||||
}
|
||||
},
|
||||
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: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='less' scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,178 +0,0 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-if="iVisible"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
:show-cancel="false"
|
||||
:show-confirm="false"
|
||||
:title="title"
|
||||
:visible.sync="iVisible"
|
||||
v-bind="$attrs"
|
||||
width="70%"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<AccountCreateUpdateForm
|
||||
v-if="!loading"
|
||||
ref="form"
|
||||
:account="account"
|
||||
:asset="asset"
|
||||
:add-template="addTemplate"
|
||||
@add="addAccount"
|
||||
@edit="editAccount"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
import AccountCreateUpdateForm from '@/components/AccountCreateUpdateForm'
|
||||
|
||||
export default {
|
||||
name: 'CreateAccountDialog',
|
||||
components: {
|
||||
Dialog,
|
||||
AccountCreateUpdateForm
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
addTemplate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
asset: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
account: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return this.$t('assets.AddAccount')
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
platform: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
protocols() {
|
||||
return this.asset ? this.asset.protocol : []
|
||||
},
|
||||
iVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addAccount(form) {
|
||||
const formValue = Object.assign({}, form)
|
||||
let data, url, iVisible
|
||||
if (this.asset) {
|
||||
data = {
|
||||
asset: this.asset.id,
|
||||
...formValue
|
||||
}
|
||||
iVisible = false
|
||||
url = `/api/v1/accounts/accounts/`
|
||||
} else {
|
||||
iVisible = true
|
||||
data = formValue
|
||||
url = `/api/v1/accounts/accounts/bulk/`
|
||||
if (data.assets.length === 0) {
|
||||
this.$message.error(this.$tc('assets.PleaseSelectAsset'))
|
||||
return
|
||||
}
|
||||
}
|
||||
this.$axios.post(url, data, {
|
||||
disableFlashErrorMsg: iVisible
|
||||
}).then((data) => {
|
||||
this.handleResult(data, null)
|
||||
this.iVisible = iVisible
|
||||
if (!iVisible) {
|
||||
this.$emit('add', true)
|
||||
}
|
||||
}).catch(error => {
|
||||
this.iVisible = iVisible
|
||||
this.handleResult(null, error)
|
||||
})
|
||||
},
|
||||
editAccount(form) {
|
||||
const data = { ...form }
|
||||
this.$axios.patch(`/api/v1/accounts/accounts/${this.account.id}/`, data).then(() => {
|
||||
this.iVisible = false
|
||||
this.$emit('add', true)
|
||||
this.$message.success(this.$tc('common.updateSuccessMsg'))
|
||||
}).catch(error => this.setFieldError(error))
|
||||
},
|
||||
handleResult(resp, error) {
|
||||
let bulkCreate = !this.asset
|
||||
if (error && !Array.isArray(error?.response?.data)) {
|
||||
bulkCreate = false
|
||||
}
|
||||
if (resp && !Array.isArray(resp)) {
|
||||
bulkCreate = false
|
||||
}
|
||||
if (!bulkCreate) {
|
||||
if (!error) {
|
||||
this.$message.success(this.$tc('common.createSuccessMsg'))
|
||||
} else {
|
||||
this.setFieldError(error)
|
||||
}
|
||||
} else {
|
||||
let result
|
||||
if (error) {
|
||||
result = error.response.data
|
||||
} else {
|
||||
result = resp
|
||||
}
|
||||
this.$emit('bulk-create-done', result)
|
||||
}
|
||||
},
|
||||
setFieldError(error) {
|
||||
const response = error.response
|
||||
const data = response.data
|
||||
const refsAutoDataForm = this.$refs.form.$refs.AutoDataForm
|
||||
if (response.status === 400) {
|
||||
for (const key of Object.keys(data)) {
|
||||
let err = ''
|
||||
let current = key
|
||||
let errorTips = data[current]
|
||||
if (errorTips instanceof Array) {
|
||||
errorTips = _.filter(errorTips, (item) => Object.keys(item).length > 0)
|
||||
for (const i of errorTips) {
|
||||
if (i instanceof Object) {
|
||||
err += i?.port?.join(',')
|
||||
} else {
|
||||
err += errorTips
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = errorTips
|
||||
}
|
||||
if (current === 'secret') {
|
||||
current = refsAutoDataForm.form.secret_type?.value || key
|
||||
}
|
||||
refsAutoDataForm.setFieldError(current, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,418 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<ListTable ref="ListTable" :header-actions="headerActions" :table-config="tableConfig" />
|
||||
<ViewSecret
|
||||
v-if="showViewSecretDialog"
|
||||
:account="account"
|
||||
:url="secretUrl"
|
||||
:visible.sync="showViewSecretDialog"
|
||||
/>
|
||||
<UpdateSecretInfo
|
||||
v-if="showUpdateSecretDialog"
|
||||
:account="account"
|
||||
:visible.sync="showUpdateSecretDialog"
|
||||
@updateAuthDone="onUpdateAuthDone"
|
||||
/>
|
||||
<AccountCreateUpdate
|
||||
v-if="showAddDialog"
|
||||
:account="account"
|
||||
:asset="iAsset"
|
||||
:title="accountCreateUpdateTitle"
|
||||
:visible.sync="showAddDialog"
|
||||
@add="addAccountSuccess"
|
||||
@bulk-create-done="showBulkCreateResult($event)"
|
||||
/>
|
||||
<AccountCreateUpdate
|
||||
v-if="showAddTemplateDialog"
|
||||
:account="account"
|
||||
:asset="iAsset"
|
||||
:add-template="true"
|
||||
:title="accountCreateUpdateTitle"
|
||||
:visible.sync="showAddTemplateDialog"
|
||||
@add="addAccountSuccess"
|
||||
@bulk-create-done="showBulkCreateResult($event)"
|
||||
/>
|
||||
<ResultDialog
|
||||
v-if="showResultDialog"
|
||||
:result="createAccountResults"
|
||||
:visible.sync="showResultDialog"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListTable from '@/components/ListTable/index'
|
||||
import { ActionsFormatter } from '@/components/TableFormatters'
|
||||
import ViewSecret from './ViewSecret'
|
||||
import UpdateSecretInfo from './UpdateSecretInfo'
|
||||
import AccountCreateUpdate from './AccountCreateUpdate'
|
||||
import { connectivityMeta } from './const'
|
||||
import { openTaskPage } from '@/utils/jms'
|
||||
import ResultDialog from './BulkCreateResultDialog.vue'
|
||||
|
||||
export default {
|
||||
name: 'AccountListTable',
|
||||
components: {
|
||||
ResultDialog,
|
||||
ListTable,
|
||||
UpdateSecretInfo,
|
||||
ViewSecret,
|
||||
AccountCreateUpdate
|
||||
},
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
exportUrl: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.url.replace('/accounts/accounts/', '/accounts/account-secrets/')
|
||||
}
|
||||
},
|
||||
hasLeftActions: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
otherActions: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
hasClone: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
asset: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
hasExport: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
hasImport: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
columnsMeta: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
headerExtraActions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
showViewSecretDialog: false,
|
||||
showUpdateSecretDialog: false,
|
||||
showResultDialog: false,
|
||||
showAddDialog: false,
|
||||
showAddTemplateDialog: false,
|
||||
createAccountResults: [],
|
||||
accountCreateUpdateTitle: this.$t('assets.AddAccount'),
|
||||
iAsset: this.asset,
|
||||
account: {},
|
||||
secretUrl: '',
|
||||
tableConfig: {
|
||||
url: this.url,
|
||||
permissions: {
|
||||
app: 'assets',
|
||||
resource: 'account'
|
||||
},
|
||||
extraQuery: {
|
||||
order: '-date_updated'
|
||||
},
|
||||
columnsExclude: ['spec_info'],
|
||||
columns: [
|
||||
'name', 'username', 'asset', 'privileged',
|
||||
'secret_type', 'source', 'actions'
|
||||
],
|
||||
columnsMeta: {
|
||||
name: {
|
||||
formatter: function(row) {
|
||||
const to = {
|
||||
name: 'AssetAccountDetail',
|
||||
params: { id: row.id }
|
||||
}
|
||||
if (vm.$hasPerm('accounts.view_account')) {
|
||||
return <router-link to={to}>{row.name}</router-link>
|
||||
} else {
|
||||
return <span>{row.name}</span>
|
||||
}
|
||||
}
|
||||
},
|
||||
asset: {
|
||||
label: this.$t('assets.Asset'),
|
||||
formatter: function(row) {
|
||||
const to = {
|
||||
name: 'AssetDetail',
|
||||
params: { id: row.asset.id }
|
||||
}
|
||||
if (vm.$hasPerm('assets.view_asset')) {
|
||||
return <router-link to={to}>{row.asset.name}</router-link>
|
||||
} else {
|
||||
return <span>{row.asset.name}</span>
|
||||
}
|
||||
}
|
||||
},
|
||||
secret_type: {
|
||||
width: '100px',
|
||||
formatter: function(row) {
|
||||
return row.secret_type.label
|
||||
}
|
||||
},
|
||||
source: {
|
||||
formatter: function(row) {
|
||||
return row.source.label
|
||||
}
|
||||
},
|
||||
has_secret: {
|
||||
width: '100px',
|
||||
formatterArgs: {
|
||||
showFalse: false
|
||||
}
|
||||
},
|
||||
privileged: {
|
||||
label: this.$t('assets.Privileged'),
|
||||
width: '120px',
|
||||
formatterArgs: {
|
||||
showText: false,
|
||||
showFalse: false
|
||||
}
|
||||
},
|
||||
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'),
|
||||
can: this.$hasPerm('accounts.view_accountsecret'),
|
||||
type: 'primary',
|
||||
callback: ({ row }) => {
|
||||
// debugger
|
||||
vm.secretUrl = `/api/v1/accounts/account-secrets/${row.id}/`
|
||||
vm.account = row
|
||||
vm.showViewSecretDialog = false
|
||||
setTimeout(() => {
|
||||
vm.showViewSecretDialog = true
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ClearSecret',
|
||||
title: this.$t('common.ClearSecret'),
|
||||
can: this.$hasPerm('accounts.change_account'),
|
||||
type: 'primary',
|
||||
callback: ({ row }) => {
|
||||
this.$axios.patch(
|
||||
`/api/v1/accounts/accounts/clear-secret/`,
|
||||
{ account_ids: [row.id] }
|
||||
).then(() => {
|
||||
this.$message.success(this.$tc('common.ClearSuccessMsg'))
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
title: this.$t('common.Delete'),
|
||||
can: this.$hasPerm('accounts.delete_account'),
|
||||
type: 'primary',
|
||||
callback: ({ row }) => {
|
||||
this.$axios.delete(`/api/v1/accounts/accounts/${row.id}/`).then(() => {
|
||||
this.$message.success(this.$tc('common.deleteSuccessMsg'))
|
||||
this.$refs.ListTable.reloadTable()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test',
|
||||
title: this.$t('common.Test'),
|
||||
can: ({ row }) =>
|
||||
!this.$store.getters.currentOrgIsRoot &&
|
||||
this.$hasPerm('accounts.change_account') &&
|
||||
row.asset['auto_config'].ansible_enabled &&
|
||||
row.asset['auto_config'].ping_enabled,
|
||||
callback: ({ row }) => {
|
||||
this.$axios.post(
|
||||
`/api/v1/accounts/accounts/tasks/`,
|
||||
{ action: 'verify', accounts: [row.id] }
|
||||
).then(res => {
|
||||
openTaskPage(res['task'])
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
title: this.$t('common.Update'),
|
||||
can: this.$hasPerm('accounts.change_account') && !this.$store.getters.currentOrgIsRoot,
|
||||
callback: ({ row }) => {
|
||||
const data = {
|
||||
...this.asset,
|
||||
...row.asset
|
||||
}
|
||||
vm.account = row
|
||||
vm.iAsset = data
|
||||
vm.showAddDialog = false
|
||||
vm.accountCreateUpdateTitle = this.$t('assets.UpdateAccount')
|
||||
setTimeout(() => {
|
||||
vm.showAddDialog = true
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
...this.columnsMeta
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: this.hasLeftActions,
|
||||
hasMoreActions: true,
|
||||
hasCreate: false,
|
||||
hasImport: this.hasImport,
|
||||
hasExport: this.hasExport && this.$hasPerm('accounts.view_accountsecret'),
|
||||
handleImportClick: ({ selectedRows }) => {
|
||||
this.$eventBus.$emit('showImportDialog', {
|
||||
selectedRows,
|
||||
url: '/api/v1/accounts/accounts/',
|
||||
name: this?.name
|
||||
})
|
||||
},
|
||||
exportOptions: {
|
||||
url: this.exportUrl,
|
||||
mfaVerifyRequired: true
|
||||
},
|
||||
importOptions: {
|
||||
canImportCreate: this.$hasPerm('accounts.add_account'),
|
||||
canImportUpdate: this.$hasPerm('accounts.change_account')
|
||||
},
|
||||
extraActions: [
|
||||
{
|
||||
name: 'add',
|
||||
title: this.$t('common.Add'),
|
||||
type: 'primary',
|
||||
can: () => {
|
||||
return vm.$hasPerm('accounts.add_account') && !this.$store.getters.currentOrgIsRoot
|
||||
},
|
||||
callback: async() => {
|
||||
await this.getAssetDetail()
|
||||
setTimeout(() => {
|
||||
vm.iAsset = this.asset
|
||||
vm.account = {}
|
||||
vm.accountCreateUpdateTitle = this.$t('assets.AddAccount')
|
||||
vm.showAddDialog = true
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'add-template',
|
||||
title: this.$t('common.TemplateAdd'),
|
||||
has: !(this.platform || this.asset),
|
||||
can: () => {
|
||||
return vm.$hasPerm('accounts.add_account') && !this.$store.getters.currentOrgIsRoot
|
||||
},
|
||||
callback: async() => {
|
||||
await this.getAssetDetail()
|
||||
setTimeout(() => {
|
||||
vm.iAsset = this.asset
|
||||
vm.account = {}
|
||||
vm.accountCreateUpdateTitle = this.$t('assets.AddAccount')
|
||||
vm.showAddTemplateDialog = true
|
||||
})
|
||||
}
|
||||
},
|
||||
...this.headerExtraActions
|
||||
],
|
||||
extraMoreActions: [
|
||||
{
|
||||
name: 'ClearSecrets',
|
||||
title: this.$t('common.ClearSecret'),
|
||||
type: 'primary',
|
||||
fa: 'clean',
|
||||
can: ({ selectedRows }) => {
|
||||
return selectedRows.length > 0 && vm.$hasPerm('accounts.change_account')
|
||||
},
|
||||
callback: function({ selectedRows }) {
|
||||
const ids = selectedRows.map(v => { return v.id })
|
||||
this.$axios.patch(
|
||||
'/api/v1/accounts/accounts/clear-secret/',
|
||||
{ account_ids: ids }).then(() => {
|
||||
this.$message.success(this.$tc('common.ClearSuccessMsg'))
|
||||
}).catch(err => {
|
||||
this.$message.error(this.$tc('common.bulkClearErrorMsg' + ' ' + err))
|
||||
})
|
||||
}.bind(this)
|
||||
}
|
||||
],
|
||||
canBulkDelete: vm.$hasPerm('accounts.delete_account'),
|
||||
searchConfig: {
|
||||
getUrlQuery: false,
|
||||
exclude: ['asset']
|
||||
},
|
||||
hasSearch: true
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
url(iNew) {
|
||||
this.$set(this.tableConfig, 'url', iNew)
|
||||
this.$set(this.headerActions.exportOptions, 'url', iNew.replace(/(.*)accounts/, '$1account-secrets'))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.columns.length > 0) {
|
||||
this.tableConfig.columns = this.columns
|
||||
}
|
||||
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)
|
||||
},
|
||||
addAccountSuccess() {
|
||||
this.$refs.ListTable.reloadTable()
|
||||
},
|
||||
async getAssetDetail() {
|
||||
const { query: { asset }} = this.$route
|
||||
if (asset) {
|
||||
this.iAsset = await this.$axios.get(`/api/v1/assets/assets/${asset}/`)
|
||||
}
|
||||
},
|
||||
refresh() {
|
||||
this.$refs.ListTable.reloadTable()
|
||||
},
|
||||
showBulkCreateResult(results) {
|
||||
this.showResultDialog = false
|
||||
this.createAccountResults = results
|
||||
setTimeout(() => {
|
||||
this.showResultDialog = true
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.cell a {
|
||||
color: var(--color-info);
|
||||
}
|
||||
</style>
|
||||
@@ -1,120 +0,0 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:show-cancel="false"
|
||||
:title="title"
|
||||
v-bind="$attrs"
|
||||
@confirm="closeDialog"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<el-alert style="margin-bottom: 10px" type="success">
|
||||
<span v-for="item of summary" :key="item.key"><b>{{ item.label }}</b>: {{ item.value }} </span>
|
||||
</el-alert>
|
||||
<DataTable :config="config" />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog/index.vue'
|
||||
import DataTable from '@/components/DataTable/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'ResultDialog',
|
||||
components: {
|
||||
DataTable,
|
||||
Dialog
|
||||
},
|
||||
props: {
|
||||
result: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const errorProp = this.$t('common.Error')
|
||||
const stateMap = {
|
||||
'created': this.$tc('common.Created'),
|
||||
'updated': this.$tc('common.Updated'),
|
||||
'skipped': this.$tc('common.Skipped')
|
||||
}
|
||||
const stateClsMap = {
|
||||
'created': 'color-primary',
|
||||
'updated': 'color-success',
|
||||
'skipped': 'color-default'
|
||||
}
|
||||
return {
|
||||
title: this.$t('accounts.AddAccountResult'),
|
||||
config: {
|
||||
columns: [
|
||||
{
|
||||
prop: 'asset',
|
||||
label: this.$t('assets.Asset')
|
||||
},
|
||||
{
|
||||
prop: 'state',
|
||||
label: this.$t('common.Status'),
|
||||
width: '200px',
|
||||
formatter: (row) => {
|
||||
if (row.error) {
|
||||
return <span class='color-error'>{ errorProp }: { row.error }</span>
|
||||
} else if (row.state) {
|
||||
const colorCls = stateClsMap[row.state]
|
||||
const state = stateMap[row.state]
|
||||
return <span class={ colorCls }>{ state }</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
totalData: this.result
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
summary() {
|
||||
const labels = {
|
||||
total: this.$tc('common.Total'),
|
||||
created: this.$tc('common.Created'),
|
||||
updated: this.$tc('common.Updated'),
|
||||
skipped: this.$tc('common.Skipped'),
|
||||
error: this.$tc('common.Error')
|
||||
}
|
||||
const grouped = _.groupBy(this.result, 'state')
|
||||
const groupedLength = _.mapValues(grouped, 'length')
|
||||
groupedLength['total'] = this.result.length
|
||||
return _.map(groupedLength, (value, key) => {
|
||||
return {
|
||||
label: labels[key],
|
||||
value: value,
|
||||
key: key
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeDialog() {
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.color-error {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.color-primary {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.color-success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.color-default {
|
||||
}
|
||||
|
||||
::v-deep .el-data-table .el-table .el-table__row > td > div > span {
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<GenericListTableDialog :visible.sync="iVisible" v-bind="config" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { GenericListTableDialog } from '@/layout/components'
|
||||
import { ShowKeyCopyFormatter } from '@/components/TableFormatters'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericListTableDialog
|
||||
},
|
||||
props: {
|
||||
account: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
config: {
|
||||
title: this.$t('accounts.HistoryPassword'),
|
||||
visible: false,
|
||||
width: '60%',
|
||||
tableConfig: {
|
||||
id: 'history_date',
|
||||
url: `/api/v1/accounts/account-secrets/${this.account.id}/histories/`,
|
||||
columns: ['secret', 'secret_type', 'version', 'history_date'],
|
||||
columnsMeta: {
|
||||
secret: {
|
||||
label: this.$t('assets.Password'),
|
||||
formatter: ShowKeyCopyFormatter,
|
||||
formatterArgs: {
|
||||
hasDownload: false,
|
||||
name: this.account.name
|
||||
}
|
||||
},
|
||||
history_date: {
|
||||
label: this.$t('accounts.HistoryDate')
|
||||
},
|
||||
secret_type: {
|
||||
width: '200px'
|
||||
},
|
||||
version: {
|
||||
width: '100px'
|
||||
},
|
||||
actions: {
|
||||
has: false
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
hasSearch: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
83
src/components/AccountListTable/ShowSecretInfo.vue
Normal file
83
src/components/AccountListTable/ShowSecretInfo.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div>
|
||||
<UserConfirmDialog
|
||||
:url="url"
|
||||
@UserConfirmDone="getAuthInfo"
|
||||
@UserConfirmCancel="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('users.SSHKey')">
|
||||
<el-input v-model="authInfo['private_key']" class="item-textarea" type="textarea" show-password />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
import UserConfirmDialog from '@/components/UserConfirmDialog'
|
||||
export default {
|
||||
name: 'ShowSecretInfo',
|
||||
components: {
|
||||
Dialog,
|
||||
UserConfirmDialog
|
||||
},
|
||||
props: {
|
||||
account: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogTitle: this.$t('common.ViewSecret'),
|
||||
authInfo: {},
|
||||
showAuthInfo: false,
|
||||
url: `/api/v1/assets/account-secrets/${this.account.id}/`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getAuthInfo() {
|
||||
this.$axios.get(this.url, { disableFlashErrorMsg: true }).then(resp => {
|
||||
this.authInfo = resp
|
||||
this.showAuthInfo = true
|
||||
})
|
||||
},
|
||||
exit() {
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.item-textarea >>> .el-textarea__inner {
|
||||
height: 110px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,27 +1,27 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:destroy-on-close="true"
|
||||
:title="$tc('assets.UpdateAssetUserToken')"
|
||||
:visible.sync="visible"
|
||||
width="50"
|
||||
@cancel="handleCancel()"
|
||||
:title="this.$t('assets.UpdateAssetUserToken')"
|
||||
:visible.sync="visible"
|
||||
:destroy-on-close="true"
|
||||
@confirm="handleConfirm()"
|
||||
@cancel="handleCancel()"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<el-form label-position="right" label-width="90px">
|
||||
<el-form-item :label="$tc('assets.Name')">
|
||||
<el-input v-model="account['asset_name']" readonly />
|
||||
<el-form-item :label="this.$t('assets.Hostname')">
|
||||
<el-input v-model="account.hostname" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$tc('assets.Username')">
|
||||
<el-form-item :label="this.$t('assets.Username')">
|
||||
<el-input v-model="account['username']" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$tc('assets.Password')">
|
||||
<el-form-item :label="this.$t('assets.Password')">
|
||||
<UpdateToken v-model="authInfo.password" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$tc('assets.SSHSecretKey')">
|
||||
<el-form-item :label="this.$t('assets.SSHSecretKey')">
|
||||
<UploadKey @input="getFile" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$tc('assets.Passphrase')">
|
||||
<el-form-item :label="this.$t('assets.Passphrase')">
|
||||
<UpdateToken v-model="authInfo.passphrase" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -32,7 +32,6 @@
|
||||
import Dialog from '@/components/Dialog'
|
||||
import { UpdateToken, UploadKey } from '@/components/FormFields'
|
||||
import { encryptPassword } from '@/utils/crypto'
|
||||
|
||||
export default {
|
||||
name: 'UpdateSecretInfo',
|
||||
components: {
|
||||
@@ -52,7 +51,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
secretInfo: {
|
||||
authInfo: {
|
||||
password: '',
|
||||
private_key: '',
|
||||
passphrase: ''
|
||||
@@ -62,15 +61,15 @@ export default {
|
||||
methods: {
|
||||
handleConfirm() {
|
||||
const data = {}
|
||||
if (this.secretInfo.password !== '') {
|
||||
data.password = encryptPassword(this.secretInfo.password)
|
||||
if (this.authInfo.password !== '') {
|
||||
data.password = encryptPassword(this.authInfo.password)
|
||||
}
|
||||
if (this.secretInfo.private_key !== '') {
|
||||
data.private_key = encryptPassword(this.secretInfo.private_key)
|
||||
if (this.secretInfo.passphrase) data.passphrase = this.secretInfo.passphrase
|
||||
if (this.authInfo.private_key !== '') {
|
||||
data.private_key = encryptPassword(this.authInfo.private_key)
|
||||
if (this.authInfo.passphrase) data.passphrase = this.authInfo.passphrase
|
||||
}
|
||||
this.$axios.patch(
|
||||
`/api/v1/accounts/accounts/${this.account.id}/`,
|
||||
`/api/v1/assets/accounts/${this.account.id}/`,
|
||||
data,
|
||||
{ disableFlashErrorMsg: true }
|
||||
).then(res => {
|
||||
@@ -88,7 +87,7 @@ export default {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
getFile(file) {
|
||||
this.secretInfo.private_key = file
|
||||
this.authInfo.private_key = file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="mfaDialogVisible">
|
||||
<UserConfirmDialog
|
||||
:url="url"
|
||||
@UserConfirmCancel="exit"
|
||||
@UserConfirmDone="getAuthInfo"
|
||||
/>
|
||||
</div>
|
||||
<Dialog
|
||||
:destroy-on-close="true"
|
||||
:show-cancel="false"
|
||||
:title="title"
|
||||
:visible.sync="showSecret"
|
||||
:width="'50'"
|
||||
v-bind="$attrs"
|
||||
@confirm="accountConfirmHandle"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<el-form :model="secretInfo" class="password-form" label-position="right" label-width="100px">
|
||||
<el-form-item :label="$tc('assets.Name')">
|
||||
<span>{{ account['name'] }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$tc('assets.Username')">
|
||||
<span>{{ account['username'] }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="secretTypeLabel">
|
||||
<ShowKeyCopyFormatter
|
||||
:cell-value="secretInfo.secret"
|
||||
:col="{ formatterArgs: {
|
||||
name: account['name'],
|
||||
}}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="secretType === 'ssh_key'" :label="$tc('assets.sshKeyFingerprint')">
|
||||
<span>{{ sshKeyFingerprint }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$tc('common.DateCreated')">
|
||||
<span>{{ account['date_created'] | date }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$tc('common.DateUpdated')">
|
||||
<span>{{ account['date_updated'] | date }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="showPasswordRecord" v-perms="'accounts.view_accountsecret'" :label="$tc('accounts.PasswordRecord')">
|
||||
<el-link
|
||||
:underline="false"
|
||||
type="success"
|
||||
@click="showHistoryDialog"
|
||||
>
|
||||
<span style="padding-right: 30px">
|
||||
{{ versions }}
|
||||
</span>
|
||||
</el-link>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</Dialog>
|
||||
<PasswordHistoryDialog
|
||||
v-if="showPasswordHistoryDialog"
|
||||
:account="account"
|
||||
:visible.sync="showPasswordHistoryDialog"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
import PasswordHistoryDialog from './PasswordHistoryDialog'
|
||||
import UserConfirmDialog from '@/components/UserConfirmDialog'
|
||||
import { ShowKeyCopyFormatter } from '@/components/TableFormatters'
|
||||
|
||||
export default {
|
||||
name: 'ShowSecretInfo',
|
||||
components: {
|
||||
Dialog,
|
||||
PasswordHistoryDialog,
|
||||
UserConfirmDialog,
|
||||
ShowKeyCopyFormatter
|
||||
},
|
||||
props: {
|
||||
account: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return this.$tc('assets.AccountDetail')
|
||||
}
|
||||
},
|
||||
showPasswordRecord: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
secretInfo: {},
|
||||
versions: '-',
|
||||
showSecret: false,
|
||||
mfaDialogVisible: true,
|
||||
sshKeyFingerprint: '-',
|
||||
historyCount: 0,
|
||||
showPasswordHistoryDialog: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
secretTypeLabel() {
|
||||
return this.account['secret_type'].label || 'Password'
|
||||
},
|
||||
secretType() {
|
||||
return this.account['secret_type'].value
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.showPasswordRecord) {
|
||||
const url = `/api/v1/accounts/account-secrets/${this.account.id}/histories/?limit=1`
|
||||
this.$axios.get(url, { disableFlashErrorMsg: true }).then(resp => {
|
||||
this.versions = resp.count
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
accountConfirmHandle() {
|
||||
this.showSecret = false
|
||||
this.mfaDialogVisible = false
|
||||
},
|
||||
getAuthInfo() {
|
||||
this.$axios.get(this.url, { disableFlashErrorMsg: true }).then(resp => {
|
||||
this.secretInfo = resp
|
||||
this.sshKeyFingerprint = resp?.spec_info?.ssh_key_fingerprint || '-'
|
||||
this.showSecret = true
|
||||
})
|
||||
},
|
||||
exit() {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
showHistoryDialog() {
|
||||
this.showPasswordHistoryDialog = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.item-textarea >>> .el-textarea__inner {
|
||||
height: 110px;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
padding: 5px 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
>>> .el-form-item__label {
|
||||
padding-right: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
>>> .el-form-item__content {
|
||||
line-height: 30px;
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
margin-bottom: 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.title {
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,26 +1,36 @@
|
||||
import { toSafeLocalDateStr } from '@/utils/common'
|
||||
import ChoicesFormatter from '@/components/TableFormatters/ChoicesFormatter'
|
||||
import i18n from '@/i18n/i18n'
|
||||
import { ChoicesFormatter } from '@/components/TableFormatters'
|
||||
|
||||
export const connectivityMeta = {
|
||||
label: i18n.t('assets.Connectivity'),
|
||||
label: i18n.t('assets.Reachable'),
|
||||
formatter: ChoicesFormatter,
|
||||
formatterArgs: {
|
||||
faChoices: {
|
||||
'-': '',
|
||||
ok: 'fa-check-circle',
|
||||
err: 'fa-times-circle'
|
||||
iconChoices: {
|
||||
ok: 'fa-check',
|
||||
failed: 'fa-times',
|
||||
unknown: 'fa-circle-o'
|
||||
},
|
||||
classChoices: {
|
||||
ok: 'text-primary',
|
||||
err: 'text-danger'
|
||||
failed: 'text-danger',
|
||||
unknown: 'text-warning'
|
||||
},
|
||||
getText({ cellValue }) {
|
||||
if (cellValue?.value === '-') {
|
||||
return '-'
|
||||
} else {
|
||||
return cellValue?.label
|
||||
hasTips: true,
|
||||
getTips: ({ row, cellValue }) => {
|
||||
const mapper = {
|
||||
'ok': i18n.t('assets.Reachable'),
|
||||
'failed': i18n.t('assets.Unreachable'),
|
||||
'unknown': i18n.t('assets.Unknown')
|
||||
}
|
||||
let tips = mapper[cellValue]
|
||||
if (row['date_verified']) {
|
||||
const datetime = toSafeLocalDateStr(row['date_verified'])
|
||||
tips += '<br> ' + datetime
|
||||
}
|
||||
return tips
|
||||
}
|
||||
},
|
||||
width: '100px'
|
||||
width: '90px',
|
||||
align: 'center'
|
||||
}
|
||||
|
||||
213
src/components/AccountListTable/index.vue
Normal file
213
src/components/AccountListTable/index.vue
Normal file
@@ -0,0 +1,213 @@
|
||||
<template>
|
||||
<div>
|
||||
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
|
||||
<ShowSecretInfo v-if="showViewSecretDialog" :visible.sync="showViewSecretDialog" :account="account" />
|
||||
<UpdateSecretInfo v-if="showUpdateSecretDialog" :visible.sync="showUpdateSecretDialog" :account="account" @updateAuthDone="onUpdateAuthDone" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListTable from '@/components/ListTable/index'
|
||||
import { ActionsFormatter, DetailFormatter, DisplayFormatter } from '@/components/TableFormatters'
|
||||
import ShowSecretInfo from './ShowSecretInfo'
|
||||
import UpdateSecretInfo from './UpdateSecretInfo'
|
||||
import { connectivityMeta } from './const'
|
||||
import { openTaskPage } from '@/utils/jms'
|
||||
// import i18n from '@/i18n/i18n'
|
||||
|
||||
export default {
|
||||
name: 'AccountListTable',
|
||||
components: {
|
||||
ListTable,
|
||||
UpdateSecretInfo,
|
||||
ShowSecretInfo
|
||||
},
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
exportUrl: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.url.replace('/assets/accounts/', '/assets/account-secrets/')
|
||||
}
|
||||
},
|
||||
hasLeftActions: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
otherActions: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
hasClone: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
showViewSecretDialog: false,
|
||||
showUpdateSecretDialog: false,
|
||||
account: {},
|
||||
tableConfig: {
|
||||
url: this.url,
|
||||
permissions: {
|
||||
app: 'assets',
|
||||
resource: 'authbook'
|
||||
},
|
||||
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: {
|
||||
can: this.$hasPerm('assets.view_asset'),
|
||||
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'),
|
||||
can: this.$hasPerm('assets.view_assetaccountsecret'),
|
||||
type: 'primary',
|
||||
callback: ({ row }) => {
|
||||
vm.account = row
|
||||
vm.showViewSecretDialog = false
|
||||
setTimeout(() => {
|
||||
vm.showViewSecretDialog = true
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
title: this.$t('common.Delete'),
|
||||
can: this.$hasPerm('assets.delete_authbook'),
|
||||
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'),
|
||||
can: this.$hasPerm('assets.test_authbook'),
|
||||
callback: ({ row }) => {
|
||||
this.$axios.post(
|
||||
`/api/v1/assets/accounts/${row.id}/verify/`,
|
||||
{ action: 'test' }
|
||||
).then(res => {
|
||||
openTaskPage(res['task'])
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
title: this.$t('common.Update'),
|
||||
can: this.$hasPerm('assets.change_assetaccountsecret') && !this.$store.getters.currentOrgIsRoot,
|
||||
callback: ({ row }) => {
|
||||
vm.account = row
|
||||
vm.showUpdateSecretDialog = false
|
||||
setTimeout(() => {
|
||||
vm.showUpdateSecretDialog = true
|
||||
})
|
||||
}
|
||||
}
|
||||
// {
|
||||
// name: 'History',
|
||||
// title: i18n.t('common.History'),
|
||||
// can: this.$hasPerm('assets.view_assethistoryaccount') && !this.$store.getters.currentOrgIsRoot,
|
||||
// callback: ({ row }) => {
|
||||
// this.$router.push({
|
||||
// name: 'AssetAccountHistoryList',
|
||||
// query: { id: row.id }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: this.hasLeftActions,
|
||||
hasMoreActions: true,
|
||||
hasCreate: false,
|
||||
hasImport: false,
|
||||
hasExport: this.$hasPerm('assets.view_assetaccountsecret'),
|
||||
exportOptions: {
|
||||
url: this.exportUrl,
|
||||
mfaVerifyRequired: true
|
||||
},
|
||||
searchConfig: {
|
||||
exclude: ['systemuser', 'asset']
|
||||
},
|
||||
hasSearch: true
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
url(iNew) {
|
||||
this.$set(this.tableConfig, 'url', iNew)
|
||||
this.$set(this.headerActions.exportOptions, 'url', iNew.replace('/accounts/', '/account-secrets/'))
|
||||
}
|
||||
},
|
||||
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>
|
||||
@@ -18,16 +18,16 @@ export default {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
moreActionBtn: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
moreActionsTitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.$t('common.MoreActions')
|
||||
}
|
||||
},
|
||||
moreActionsType: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
moreActionsPlacement: {
|
||||
type: String,
|
||||
default: 'bottom'
|
||||
@@ -43,21 +43,14 @@ export default {
|
||||
return actions
|
||||
},
|
||||
iMoreAction() {
|
||||
const defaultBtn = {
|
||||
return {
|
||||
name: 'moreActions',
|
||||
title: this.$t('common.MoreActions'),
|
||||
type: 'primary',
|
||||
plain: true
|
||||
}
|
||||
const btn = {
|
||||
...defaultBtn,
|
||||
...this.moreActionBtn,
|
||||
title: this.iMoreActionsTitle,
|
||||
dropdown: this.moreActions || []
|
||||
}
|
||||
if (this.moreActionsTitle) {
|
||||
btn.title = this.moreActionsTitle
|
||||
}
|
||||
return btn
|
||||
},
|
||||
iMoreActionsTitle() {
|
||||
return this.moreActionsTitle || this.$t('common.MoreActions')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
>
|
||||
<span class="announcement-main">{{ announcement.content }}</span>
|
||||
<span v-if="announcement.link">
|
||||
<el-link :href="announcement.link" target="_blank" type="info" class="link-more">
|
||||
<el-link :href="announcement.link" target="_blank" class="link-more">
|
||||
{{ $t('common.ViewMore') }}
|
||||
</el-link>
|
||||
<i class="fa fa-external-link icon" />
|
||||
<i class="fa fa-external-link" />
|
||||
</span>
|
||||
</el-alert>
|
||||
</template>
|
||||
@@ -67,7 +67,5 @@ export default {
|
||||
margin-left: 10px;
|
||||
border-bottom: solid 1px;
|
||||
}
|
||||
.icon {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
78
src/components/AppAccountListTable/ShowSecretInfo.vue
Normal file
78
src/components/AppAccountListTable/ShowSecretInfo.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<UserConfirmDialog
|
||||
:url="url"
|
||||
@UserConfirmDone="getAuthInfo"
|
||||
@UserConfirmCancel="exit"
|
||||
/>
|
||||
<Dialog
|
||||
:title="dialogTitle"
|
||||
:show-confirm="false"
|
||||
:show-cancel="false"
|
||||
:destroy-on-close="true"
|
||||
:width="'50'"
|
||||
:visible.sync="showAuthInfo"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<div>
|
||||
<el-form label-position="right" label-width="80px" :model="authInfo">
|
||||
<el-form-item :label="this.$t('applications.appName')">
|
||||
<el-input v-model="account['app_display']" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="this.$t('assets.Username')">
|
||||
<el-input v-model="account['username']" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="this.$t('assets.Password')">
|
||||
<el-input v-model="authInfo.password" type="password" show-password />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
import UserConfirmDialog from '@/components/UserConfirmDialog'
|
||||
export default {
|
||||
name: 'ShowSecretInfo',
|
||||
components: {
|
||||
Dialog,
|
||||
UserConfirmDialog
|
||||
},
|
||||
props: {
|
||||
account: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogTitle: this.$t('common.ViewSecret'),
|
||||
authInfo: {},
|
||||
showAuthInfo: false,
|
||||
url: `/api/v1/applications/account-secrets/${this.account.id}/`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getAuthInfo() {
|
||||
this.$axios.get(this.url, { disableFlashErrorMsg: true }).then(resp => {
|
||||
this.authInfo = resp
|
||||
this.showAuthInfo = true
|
||||
})
|
||||
},
|
||||
exit() {
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
31
src/components/AppAccountListTable/const.js
Normal file
31
src/components/AppAccountListTable/const.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { ChoicesFormatter } from '@/components/TableFormatters'
|
||||
import { toSafeLocalDateStr } from '@/utils/common'
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
export const connectivityMeta = {
|
||||
label: i18n.t('assets.Reachable'),
|
||||
formatter: ChoicesFormatter,
|
||||
formatterArgs: {
|
||||
iconChoices: {
|
||||
ok: 'fa-check text-primary',
|
||||
failed: 'fa-times text-danger',
|
||||
unknown: 'fa-circle text-warning'
|
||||
},
|
||||
hasTips: true,
|
||||
getTips: ({ row, cellValue }) => {
|
||||
const mapper = {
|
||||
'ok': i18n.t('assets.Reachable'),
|
||||
'failed': i18n.t('assets.Unreachable'),
|
||||
'unknown': i18n.t('assets.Unknown')
|
||||
}
|
||||
let tips = mapper[cellValue]
|
||||
if (row['date_verified']) {
|
||||
const datetime = toSafeLocalDateStr(row['date_verified'])
|
||||
tips += '<br> ' + datetime
|
||||
}
|
||||
return tips
|
||||
}
|
||||
},
|
||||
width: '90px',
|
||||
align: 'center'
|
||||
}
|
||||
173
src/components/AppAccountListTable/index.vue
Normal file
173
src/components/AppAccountListTable/index.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<div>
|
||||
<ListTable ref="ListTable" :table-config="tableConfig" :header-actions="headerActions" />
|
||||
<ShowSecretInfo v-if="showViewSecretDialog" :visible.sync="showViewSecretDialog" :account="account" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListTable from '@/components/ListTable/index'
|
||||
import { ActionsFormatter, DetailFormatter } from '@/components/TableFormatters'
|
||||
import ShowSecretInfo from './ShowSecretInfo'
|
||||
|
||||
export default {
|
||||
name: 'Detail',
|
||||
components: {
|
||||
ListTable,
|
||||
ShowSecretInfo
|
||||
},
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
exportUrl: {
|
||||
type: String,
|
||||
default() {
|
||||
return this.url.replace('/applications/accounts/', '/applications/account-secrets/')
|
||||
}
|
||||
},
|
||||
hasLeftActions: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
otherActions: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
hasClone: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
systemUserDisabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showViewSecretDialog: false,
|
||||
showUpdateSecretDialog: false,
|
||||
account: {},
|
||||
tableConfig: {
|
||||
url: this.url,
|
||||
columns: [
|
||||
'app_display', 'username', 'category_display',
|
||||
'type_display', 'systemuser', 'actions'
|
||||
],
|
||||
columnsMeta: {
|
||||
app_display: {
|
||||
showOverflowTooltip: true,
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
getRoute({ row }) {
|
||||
switch (row['category']) {
|
||||
case 'remote_app':
|
||||
return {
|
||||
name: 'RemoteAppDetail',
|
||||
params: { id: row.app }
|
||||
}
|
||||
case 'db':
|
||||
return {
|
||||
name: 'DatabaseAppDetail',
|
||||
params: { id: row.app }
|
||||
}
|
||||
default:
|
||||
return {
|
||||
name: 'KubernetesAppDetail',
|
||||
params: { id: row.app }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
username: {
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
systemuser: {
|
||||
showOverflowTooltip: true,
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
can: this.systemUserDisabled && this.$hasPerm('assets.view_systemuser'),
|
||||
getTitle({ row }) {
|
||||
return row.systemuser_display
|
||||
},
|
||||
getRoute({ row }) {
|
||||
return {
|
||||
name: 'SystemUserDetail',
|
||||
params: { id: row.systemuser }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
formatter: ActionsFormatter,
|
||||
formatterArgs: {
|
||||
hasUpdate: false, // can set function(row, value)
|
||||
hasDelete: false, // can set function(row, value)
|
||||
hasClone: this.hasClone,
|
||||
moreActionsTitle: this.$t('common.More'),
|
||||
extraActions: [
|
||||
{
|
||||
name: 'View',
|
||||
title: this.$t('common.View'),
|
||||
type: 'primary',
|
||||
can: this.$hasPerm('applications.view_applicationaccountsecret'),
|
||||
callback: function({ row }) {
|
||||
this.account = row
|
||||
this.showViewSecretDialog = true
|
||||
}.bind(this)
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
title: this.$t('common.Update'),
|
||||
can: !this.$store.getters.currentOrgIsRoot,
|
||||
callback: function({ row }) {
|
||||
this.$message.success(this.$tc('applications.updateAccountMsg'))
|
||||
}.bind(this)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: this.hasLeftActions,
|
||||
hasMoreActions: false,
|
||||
hasImport: false,
|
||||
hasExport: this.$hasPerm('applications.view_applicationaccountsecret'),
|
||||
exportOptions: {
|
||||
url: this.exportUrl,
|
||||
mfaVerifyRequired: true
|
||||
},
|
||||
searchConfig: {
|
||||
exclude: ['systemuser', 'app']
|
||||
},
|
||||
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,192 +0,0 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:title="$tc('assets.Assets')"
|
||||
custom-class="asset-select-dialog"
|
||||
top="1vh"
|
||||
v-bind="$attrs"
|
||||
width="80vw"
|
||||
@cancel="handleCancel"
|
||||
@close="handleClose"
|
||||
@confirm="handleConfirm"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<AssetTreeTable
|
||||
ref="ListPage"
|
||||
:header-actions="headerActions"
|
||||
:node-url="baseNodeUrl"
|
||||
:table-config="tableConfig"
|
||||
:tree-url="`${baseNodeUrl}children/tree/`"
|
||||
:url="baseUrl"
|
||||
class="tree-table"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AssetTreeTable from '@/components/AssetTreeTable'
|
||||
import Dialog from '@/components/Dialog'
|
||||
|
||||
export default {
|
||||
componentName: 'AssetSelectDialog',
|
||||
components: { AssetTreeTable, Dialog },
|
||||
props: {
|
||||
baseUrl: {
|
||||
type: String,
|
||||
default: '/api/v1/assets/assets/'
|
||||
},
|
||||
baseNodeUrl: {
|
||||
type: String,
|
||||
default: '/api/v1/assets/nodes/'
|
||||
},
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
canSelect: {
|
||||
type: Function,
|
||||
default(row, index) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: [Boolean, Function],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
dialogVisible: false,
|
||||
rowSelected: _.cloneDeep(this.value) || [],
|
||||
rowsAdd: [],
|
||||
tableConfig: {
|
||||
url: this.baseUrl,
|
||||
hasTree: true,
|
||||
canSelect: this.canSelect,
|
||||
columns: [
|
||||
{
|
||||
prop: 'name',
|
||||
label: this.$t('assets.Name'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
prop: 'address',
|
||||
label: this.$t('assets.ipDomain'),
|
||||
sortable: 'custom'
|
||||
},
|
||||
{
|
||||
prop: 'platform',
|
||||
label: this.$t('assets.Platform'),
|
||||
sortable: true,
|
||||
formatter: function(row) {
|
||||
return row.platform.name
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'protocols',
|
||||
formatter: function(row) {
|
||||
const data = row.protocols.map(p => {
|
||||
return <el-tag size='mini'>{p.name}/{p.port} </el-tag>
|
||||
})
|
||||
return <span> {data} </span>
|
||||
},
|
||||
label: this.$t('assets.Protocols')
|
||||
},
|
||||
{
|
||||
prop: 'actions',
|
||||
has: false
|
||||
}
|
||||
],
|
||||
listeners: {
|
||||
'toggle-row-selection': (isSelected, row) => {
|
||||
if (isSelected) {
|
||||
vm.addRowToSelect(row)
|
||||
} else {
|
||||
vm.removeRowFromSelect(row)
|
||||
}
|
||||
}
|
||||
},
|
||||
theRowDefaultIsSelected: (row) => {
|
||||
return this.value.indexOf(row.id) > -1
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
hasRightActions: false,
|
||||
searchConfig: {
|
||||
getUrlQuery: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.$eventBus.$emit('treeComponentKey')
|
||||
},
|
||||
handleConfirm() {
|
||||
this.$emit('confirm', this.rowSelected, this.rowsAdd)
|
||||
if (this.rowSelected.length > 0) {
|
||||
this.handleClose()
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
this.$emit('cancel')
|
||||
this.handleClose()
|
||||
},
|
||||
addRowToSelect(row) {
|
||||
const selectValueIndex = this.rowSelected.indexOf(row.id)
|
||||
if (selectValueIndex === -1) {
|
||||
this.rowSelected.push(row.id)
|
||||
this.rowsAdd.push(row)
|
||||
}
|
||||
},
|
||||
removeRowFromSelect(row) {
|
||||
const selectValueIndex = this.rowSelected.indexOf(row.id)
|
||||
if (selectValueIndex > -1) {
|
||||
this.rowSelected.splice(selectValueIndex, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page ::v-deep .page-heading {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-dialog__wrapper ::v-deep .el-dialog__body {
|
||||
padding: 0 0 0 3px;
|
||||
|
||||
.tree-table {
|
||||
.search {
|
||||
}
|
||||
|
||||
.left {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.right {
|
||||
height: calc(100vh - 200px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.mini {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.transition-box {
|
||||
padding: 10px 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page ::v-deep .treebox .ztree {
|
||||
|
||||
}
|
||||
|
||||
.asset-select-dialog ::v-deep .el-icon-circle-check {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,96 +1,151 @@
|
||||
<template>
|
||||
<div class="asset-select-formatter">
|
||||
<div class="asset-select-dialog">
|
||||
<Select2
|
||||
ref="select2"
|
||||
v-model="select2Config.value"
|
||||
v-bind="select2Config"
|
||||
@input="onInputChange"
|
||||
v-on="$listeners"
|
||||
@focus.stop="handleFocus"
|
||||
/>
|
||||
<AssetSelectDialog
|
||||
v-if="dialogVisible"
|
||||
ref="dialog"
|
||||
:base-node-url="baseNodeUrl"
|
||||
:base-url="baseUrl"
|
||||
:tree-url-query="treeUrlQuery"
|
||||
:value="value"
|
||||
:visible.sync="dialogVisible"
|
||||
v-bind="$attrs"
|
||||
@cancel="handleCancel"
|
||||
@confirm="handleConfirm"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
<Dialog
|
||||
v-if="dialogVisible"
|
||||
:title="this.$t('assets.Assets')"
|
||||
:visible.sync="dialogVisible"
|
||||
custom-class="asset-select-dialog"
|
||||
width="80vw"
|
||||
top="1vh"
|
||||
@confirm="handleConfirm"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<TreeTable
|
||||
ref="ListPage"
|
||||
:tree-setting="treeSetting"
|
||||
:table-config="tableConfig"
|
||||
:header-actions="headerActions"
|
||||
/>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TreeTable from '@/components/TreeTable'
|
||||
import { DetailFormatter } from '@/components/TableFormatters'
|
||||
import Select2 from '@/components/FormFields/Select2'
|
||||
import AssetSelectDialog from './dialog.vue'
|
||||
import { b } from 'css-color-function/lib/adjusters'
|
||||
import Dialog from '@/components/Dialog'
|
||||
|
||||
export default {
|
||||
componentName: 'AssetSelect',
|
||||
components: { AssetSelectDialog, Select2 },
|
||||
components: { TreeTable, Select2, Dialog },
|
||||
props: {
|
||||
baseUrl: {
|
||||
type: String,
|
||||
default: '/api/v1/assets/assets/'
|
||||
},
|
||||
baseNodeUrl: {
|
||||
type: String,
|
||||
default: '/api/v1/assets/nodes/'
|
||||
},
|
||||
treeUrlQuery: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
canSelect: {
|
||||
type: Function,
|
||||
default(row, index) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: [Boolean, Function],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const iValue = []
|
||||
for (const item of this.value) {
|
||||
if (typeof item === 'object') {
|
||||
iValue.push(item.id)
|
||||
} else {
|
||||
iValue.push(item)
|
||||
const select2Config = {
|
||||
value: this.value,
|
||||
multiple: true,
|
||||
clearable: true,
|
||||
ajax: {
|
||||
url: '/api/v1/assets/assets/?fields_size=mini',
|
||||
transformOption: (item) => {
|
||||
return { label: item.hostname + '(' + item.ip + ')', value: item.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
const vm = this
|
||||
return {
|
||||
dialogVisible: false,
|
||||
initialValue: _.cloneDeep(iValue),
|
||||
select2Config: {
|
||||
value: iValue,
|
||||
multiple: true,
|
||||
clearable: true,
|
||||
ajax: {
|
||||
url: this.baseUrl,
|
||||
transformOption: (item) => {
|
||||
return { label: item.name + '(' + item.address + ')', value: item.id }
|
||||
initialValue: _.cloneDeep(this.value),
|
||||
rowSelected: [],
|
||||
initSelection: null,
|
||||
treeSetting: {
|
||||
showMenu: false,
|
||||
showRefresh: true,
|
||||
showAssets: false,
|
||||
showSearch: true,
|
||||
customTreeHeader: true,
|
||||
url: '/api/v1/assets/assets/?fields_size=mini',
|
||||
nodeUrl: '/api/v1/assets/nodes/',
|
||||
// ?assets=0不显示资产. =1显示资产
|
||||
treeUrl: '/api/v1/assets/nodes/children/tree/?assets=0'
|
||||
},
|
||||
select2Config: select2Config,
|
||||
dialogSelect2Config: select2Config,
|
||||
tableConfig: {
|
||||
url: '/api/v1/assets/assets/?fields_size=mini',
|
||||
hasTree: true,
|
||||
canSelect: this.canSelect,
|
||||
columns: [
|
||||
{
|
||||
prop: 'hostname',
|
||||
label: this.$t('assets.Hostname'),
|
||||
sortable: true,
|
||||
showOverflowTooltip: true,
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
route: 'AssetDetail'
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'ip',
|
||||
label: this.$t('assets.ipDomain'),
|
||||
sortable: 'custom'
|
||||
},
|
||||
{
|
||||
prop: 'platform',
|
||||
label: this.$t('assets.Platform'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
prop: 'protocols',
|
||||
formatter: function(row) {
|
||||
return <span> {row.protocols.toString()} </span>
|
||||
},
|
||||
label: this.$t('assets.Protocols')
|
||||
}
|
||||
],
|
||||
listeners: {
|
||||
'toggle-row-selection': (isSelected, row) => {
|
||||
if (isSelected) {
|
||||
vm.addRowToSelect(row)
|
||||
} else {
|
||||
vm.removeRowFromSelect(row)
|
||||
}
|
||||
}
|
||||
},
|
||||
theRowDefaultIsSelected: (row) => {
|
||||
return this.value.indexOf(row.id) > -1
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
hasRightActions: false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
b,
|
||||
handleFocus() {
|
||||
this.$refs.select2.selectRef.blur()
|
||||
this.dialogVisible = true
|
||||
},
|
||||
handleConfirm(valueSelected, rowsAdd) {
|
||||
if (valueSelected === undefined) {
|
||||
return
|
||||
}
|
||||
this.$refs.select2.iValue = valueSelected
|
||||
this.addRowsToSelect(rowsAdd)
|
||||
this.onInputChange(valueSelected)
|
||||
handleConfirm() {
|
||||
this.dialogVisible = false
|
||||
},
|
||||
handleCancel() {
|
||||
this.$refs.select2.iValue = this.initialValue
|
||||
this.dialogVisible = false
|
||||
},
|
||||
onInputChange(val) {
|
||||
@@ -101,54 +156,53 @@ export default {
|
||||
// 如果select2的options中没有,那么可能无法显示正常的值
|
||||
if (selectOptionsHas === undefined) {
|
||||
const option = {
|
||||
label: `${row.name}(${row.address})`,
|
||||
label: `${row.hostname}(${row.ip})`,
|
||||
value: row.id
|
||||
}
|
||||
options.push(option)
|
||||
}
|
||||
},
|
||||
addRowsToSelect(rows) {
|
||||
addRowToSelect(row) {
|
||||
const outSelectOptions = this.$refs.select2.options
|
||||
for (const row of rows) {
|
||||
this.addToSelect(outSelectOptions, row)
|
||||
this.addToSelect(outSelectOptions, row)
|
||||
|
||||
const selectValue = this.$refs.select2.iValue
|
||||
const selectValueIndex = selectValue.indexOf(row.id)
|
||||
if (selectValueIndex === -1) {
|
||||
selectValue.push(row.id)
|
||||
}
|
||||
this.onInputChange(selectValue)
|
||||
},
|
||||
removeRowFromSelect(row) {
|
||||
const selectValue = this.$refs.select2.iValue
|
||||
const selectValueIndex = selectValue.indexOf(row.id)
|
||||
if (selectValueIndex > -1) {
|
||||
selectValue.splice(selectValueIndex, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page ::v-deep .page-heading {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-dialog__wrapper ::v-deep .el-dialog__body {
|
||||
padding: 0 0 0 3px;
|
||||
|
||||
.tree-table {
|
||||
.left {
|
||||
padding: 5px;
|
||||
|
||||
.ztree {
|
||||
height: calc(100vh - 250px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.mini {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.transition-box {
|
||||
padding: 10px 5px;
|
||||
}
|
||||
<style lang='scss' scoped>
|
||||
.el-select{
|
||||
width: 100%;
|
||||
}
|
||||
.page ::v-deep .page-heading{
|
||||
display: none;
|
||||
}
|
||||
.el-dialog__wrapper ::v-deep .el-dialog__body{
|
||||
padding: 5px 10px;
|
||||
}
|
||||
.page ::v-deep .treebox {
|
||||
height: inherit !important;
|
||||
}
|
||||
.asset-select-dialog >>> .transition-box:first-child {
|
||||
background-color: #f3f3f3 ;
|
||||
}
|
||||
|
||||
.el-dialog__wrapper ::v-deep .el-dialog__body .wrapper-content {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.page ::v-deep .treebox {
|
||||
height: inherit !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
<template>
|
||||
<TreeTable
|
||||
ref="TreeList"
|
||||
component="TabTree"
|
||||
:table-config="tableConfig"
|
||||
:active-menu.sync="treeTableConfig.activeMenu"
|
||||
:tree-tab-config="treeTableConfig"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<template #table>
|
||||
<slot name="table" />
|
||||
</template>
|
||||
<div slot="rMenu" slot-scope="{data}">
|
||||
<slot name="rMenu" :data="data" />
|
||||
</div>
|
||||
</TreeTable>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TreeTable from '../TreeTable'
|
||||
import { setRouterQuery, setUrlParam } from '@/utils/common'
|
||||
import $ from '@/utils/jquery-vendor'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeTable
|
||||
},
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
default: '/api/v1/assets/assets/'
|
||||
},
|
||||
nodeUrl: {
|
||||
type: String,
|
||||
default: '/api/v1/assets/nodes/'
|
||||
},
|
||||
treeUrl: {
|
||||
type: String,
|
||||
default: '/api/v1/assets/nodes/children/tree/'
|
||||
},
|
||||
treeUrlQuery: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
treeSetting: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
tableConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
showAssets: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const showAssets = this.treeSetting?.showAssets || this.showAssets
|
||||
const treeUrlQuery = this.setTreeUrlQuery()
|
||||
const assetTreeUrl = `${this.treeUrl}?assets=${showAssets ? '1' : '0'}&${treeUrlQuery}`
|
||||
|
||||
return {
|
||||
treeTabConfig: {
|
||||
activeMenu: 'CustomTree',
|
||||
submenu: [
|
||||
{
|
||||
title: this.$t('assets.AssetTree'),
|
||||
name: 'CustomTree',
|
||||
treeSetting: {
|
||||
showAssets,
|
||||
showMenu: false,
|
||||
showRefresh: true,
|
||||
showCreate: true,
|
||||
showUpdate: true,
|
||||
showDelete: true,
|
||||
hasRightMenu: true,
|
||||
showSearch: true,
|
||||
url: this.url,
|
||||
nodeUrl: this.nodeUrl,
|
||||
treeUrl: assetTreeUrl,
|
||||
callback: {
|
||||
onSelected: (event, treeNode) => this.getAssetsUrl(treeNode)
|
||||
},
|
||||
...this.treeSetting
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$t('assets.BuiltinTree'),
|
||||
name: 'BuiltinTree',
|
||||
treeSetting: {
|
||||
showRefresh: true,
|
||||
showAssets: false,
|
||||
showSearch: false,
|
||||
customTreeHeaderName: this.$t('assets.BuiltinTree'),
|
||||
url: '/api/v1/assets/nodes/category/tree/',
|
||||
nodeUrl: this.treeSetting?.nodeUrl || this.nodeUrl,
|
||||
treeUrl: `/api/v1/assets/nodes/category/tree/?assets=${showAssets ? '1' : '0'}&count_resource=${this.treeSetting.countResource || 'asset'}`,
|
||||
callback: {
|
||||
onSelected: (event, treeNode) => this.getAssetsUrl(treeNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
treeTableConfig() {
|
||||
if (this.treeSetting.notShowBuiltinTree) {
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
this.treeTabConfig.submenu.splice(1, 1)
|
||||
}
|
||||
return this.treeTabConfig
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.decorateRMenu()
|
||||
const treeSetting = this.treeTabConfig.submenu[0].treeSetting
|
||||
treeSetting.hasRightMenu = !this.currentOrgIsRoot
|
||||
treeSetting.showCreate = this.$hasPerm('assets.add_node')
|
||||
treeSetting.showUpdate = this.$hasPerm('assets.change_node')
|
||||
treeSetting.showDelete = this.$hasPerm('assets.delete_node')
|
||||
},
|
||||
methods: {
|
||||
setTreeUrlQuery() {
|
||||
let str = ''
|
||||
for (const key in this.treeUrlQuery) {
|
||||
str += `${key}=${this.treeUrlQuery[key]}&`
|
||||
}
|
||||
str = str.substr(0, str.length - 1)
|
||||
|
||||
return str
|
||||
},
|
||||
decorateRMenu() {
|
||||
const show_current_asset = this.$cookie.get('show_current_asset') || '0'
|
||||
if (show_current_asset === '1') {
|
||||
$('#m_show_asset_all_children_node').css('color', '#606266')
|
||||
$('#m_show_asset_only_current_node').css('color', 'green')
|
||||
} else {
|
||||
$('#m_show_asset_all_children_node').css('color', 'green')
|
||||
$('#m_show_asset_only_current_node').css('color', '#606266')
|
||||
}
|
||||
},
|
||||
getAssetsUrl(treeNode) {
|
||||
let url = this.treeSetting?.url || this.url
|
||||
if (treeNode.meta.type === 'node') {
|
||||
const nodeId = treeNode.meta.data.id
|
||||
url = setUrlParam(url, 'node_id', nodeId)
|
||||
url = setUrlParam(url, 'asset_id', '')
|
||||
} else if (treeNode.meta.type === 'asset') {
|
||||
const assetId = treeNode.meta.data?.id || treeNode.id
|
||||
url = setUrlParam(url, 'node_id', '')
|
||||
url = setUrlParam(url, 'asset_id', assetId)
|
||||
} else if (treeNode.meta.type === 'category') {
|
||||
url = setUrlParam(url, 'category', treeNode.meta.category)
|
||||
} else if (treeNode.meta.type === 'type') {
|
||||
url = setUrlParam(url, 'category', treeNode.meta.category)
|
||||
url = setUrlParam(url, 'type', treeNode.meta._type)
|
||||
} else if (treeNode.meta.type === 'platform') {
|
||||
url = setUrlParam(url, 'platform', treeNode.id)
|
||||
}
|
||||
const query = this.setTreeUrlQuery()
|
||||
url = query ? `${url}&${query}` : url
|
||||
this.$set(this.tableConfig, 'url', url)
|
||||
setRouterQuery(this, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<DataForm
|
||||
:disabled="disabled"
|
||||
:fields="iFields"
|
||||
:form="value"
|
||||
style="margin-left: -26%;margin-right: -6%"
|
||||
@@ -29,10 +28,6 @@ export default {
|
||||
errors: {
|
||||
type: [Object, String],
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
<template>
|
||||
<DataForm
|
||||
v-if="!loading"
|
||||
ref="dataForm"
|
||||
:fields="totalFields"
|
||||
:form="iForm"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<span
|
||||
<DataForm ref="dataForm" v-loading="loading" :fields="totalFields" :form="iForm" v-bind="$attrs" v-on="$listeners">
|
||||
<FormGroupHeader
|
||||
v-for="(group, i) in groups"
|
||||
:key="'group-'+group.name"
|
||||
:slot="'id:'+group.name"
|
||||
>
|
||||
<FormGroupHeader
|
||||
v-if="!groupHidden(group, i)"
|
||||
:group="group"
|
||||
:index="i"
|
||||
:line="i !== 0 && !groupHidden(groups[i - 1], i - 1)"
|
||||
/>
|
||||
</span>
|
||||
:key="'group-'+group.name"
|
||||
:group="group"
|
||||
:index="i"
|
||||
:line="i !== 0"
|
||||
/>
|
||||
</DataForm>
|
||||
</template>
|
||||
|
||||
@@ -26,7 +15,6 @@
|
||||
import DataForm from '../DataForm'
|
||||
import FormGroupHeader from '@/components/FormGroupHeader'
|
||||
import { FormFieldGenerator } from '@/components/AutoDataForm/utils'
|
||||
|
||||
export default {
|
||||
name: 'AutoDataForm',
|
||||
components: {
|
||||
@@ -63,52 +51,32 @@ export default {
|
||||
totalFields: [],
|
||||
loading: true,
|
||||
groups: [],
|
||||
iForm: this.form,
|
||||
errors: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iForm() {
|
||||
const iForm = {}
|
||||
Object.entries(this.form).forEach(([key, value]) => {
|
||||
// 初始值是 choice 对象
|
||||
if (value && typeof value === 'object' && value.label && value.value !== undefined) {
|
||||
iForm[key] = value.value
|
||||
} else if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' &&
|
||||
value[0].label && value[0].value !== undefined) {
|
||||
iForm[key] = value.map(item => item.value)
|
||||
} else {
|
||||
iForm[key] = value
|
||||
}
|
||||
})
|
||||
return iForm
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.optionUrlMetaAndGenerateColumns()
|
||||
},
|
||||
methods: {
|
||||
async optionUrlMetaAndGenerateColumns() {
|
||||
let data = { actions: {}}
|
||||
if (this.url) {
|
||||
data = await this.$store.dispatch('common/getUrlMeta', { url: this.url })
|
||||
}
|
||||
this.remoteMeta = data.actions[this.method.toUpperCase()] || {}
|
||||
this.$emit('afterRemoteMeta', this.remoteMeta)
|
||||
this.generateColumns()
|
||||
this.$emit('afterGenerateColumns', this.totalFields)
|
||||
this.cleanFormValue()
|
||||
this.loading = false
|
||||
optionUrlMetaAndGenerateColumns() {
|
||||
this.$store.dispatch('common/getUrlMeta', { url: this.url }).then(data => {
|
||||
this.remoteMeta = 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.$emit)
|
||||
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) {
|
||||
if (!form) {
|
||||
form = {}
|
||||
}
|
||||
for (const [k, v] of Object.entries(remoteMeta)) {
|
||||
let valueSet = form[k]
|
||||
if (v.type === 'nested object' && v.children) {
|
||||
@@ -146,18 +114,6 @@ export default {
|
||||
} else {
|
||||
field.attrs.error = error
|
||||
}
|
||||
},
|
||||
groupHidden(group, i) {
|
||||
for (const field of group.fields) {
|
||||
let hidden = field.hidden
|
||||
if (typeof hidden === 'function') {
|
||||
hidden = hidden(this.iForm)
|
||||
}
|
||||
if (!hidden) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,26 @@
|
||||
import Vue from 'vue'
|
||||
import Select2 from '@/components/FormFields/Select2'
|
||||
import ObjectSelect2 from '@/components/FormFields/NestedObjectSelect2'
|
||||
import NestedField from '@/components/AutoDataForm/components/NestedField'
|
||||
import Switcher from '@/components/FormFields/Switcher'
|
||||
import Swicher from '@/components/FormFields/Swicher'
|
||||
import rules from '@/components/DataForm/rules'
|
||||
import BasicTree from '@/components/FormFields/BasicTree'
|
||||
import JsonEditor from '@/components/FormFields/JsonEditor'
|
||||
import { assignIfNot } from '@/utils/common'
|
||||
import ListField from '@/components/FormFields/ListField.vue'
|
||||
|
||||
export class FormFieldGenerator {
|
||||
constructor(emit) {
|
||||
this.$emite = emit
|
||||
constructor() {
|
||||
this.groups = []
|
||||
}
|
||||
|
||||
generateFieldByType(type, field, fieldMeta, fieldRemoteMeta) {
|
||||
switch (type) {
|
||||
case 'labeled_choice':
|
||||
case 'choice':
|
||||
// Value 处理事在 AutoDataForm 中处理的
|
||||
if (!fieldRemoteMeta['read_only']) {
|
||||
field.options = fieldRemoteMeta.choices
|
||||
}
|
||||
type = 'radio-group'
|
||||
if (!fieldRemoteMeta.read_only) {
|
||||
field.options = fieldRemoteMeta.choices.map(v => {
|
||||
return { label: v.display_name, value: v.value }
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'multiple choice':
|
||||
field.options = fieldRemoteMeta.choices
|
||||
type = 'checkbox-group'
|
||||
break
|
||||
case 'tree':
|
||||
field.el.tree = fieldRemoteMeta.tree
|
||||
field.component = BasicTree
|
||||
field.el.choices = fieldRemoteMeta['choices']
|
||||
break
|
||||
case 'datetime':
|
||||
type = 'date-picker'
|
||||
@@ -39,19 +28,12 @@ export class FormFieldGenerator {
|
||||
type: 'datetime'
|
||||
}
|
||||
break
|
||||
case 'json':
|
||||
type = 'json-editor'
|
||||
field.component = JsonEditor
|
||||
break
|
||||
case 'field':
|
||||
type = ''
|
||||
field.component = Select2
|
||||
if (fieldRemoteMeta.required) {
|
||||
field.el.clearable = false
|
||||
}
|
||||
if (fieldRemoteMeta.child && fieldRemoteMeta.child.type === 'nested object') {
|
||||
field.component = ObjectSelect2
|
||||
}
|
||||
break
|
||||
case 'string':
|
||||
type = 'input'
|
||||
@@ -65,50 +47,40 @@ export class FormFieldGenerator {
|
||||
break
|
||||
case 'boolean':
|
||||
type = ''
|
||||
field.component = Switcher
|
||||
break
|
||||
case 'list':
|
||||
type = 'input'
|
||||
field.component = ListField
|
||||
break
|
||||
case 'object_related_field':
|
||||
field.component = ObjectSelect2
|
||||
break
|
||||
case 'm2m_related_field':
|
||||
field.component = ObjectSelect2
|
||||
field.component = Swicher
|
||||
break
|
||||
case 'nested object':
|
||||
type = 'nestedField'
|
||||
field.component = NestedField
|
||||
field.label = ''
|
||||
field.labelWidth = 0
|
||||
field.el = { ...field.el, ...fieldMeta }
|
||||
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
|
||||
}
|
||||
// 上面重写了 type
|
||||
if (type === 'radio-group') {
|
||||
if (field.options.length > 4) {
|
||||
type = 'select'
|
||||
field.el.filterable = true
|
||||
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 = []
|
||||
let nestedFields = fieldMeta.fields || []
|
||||
const nestedFields = fieldMeta.fields || []
|
||||
const nestedFieldsMeta = fieldMeta.fieldsMeta || {}
|
||||
const nestedFieldsRemoteMeta = fieldRemoteMeta.children || {}
|
||||
if (nestedFields === '__all__') {
|
||||
nestedFields = Object.keys(nestedFieldsRemoteMeta)
|
||||
}
|
||||
for (const name of nestedFields) {
|
||||
const f = this.generateField(name, nestedFieldsMeta, nestedFieldsRemoteMeta)
|
||||
fields.push(f)
|
||||
@@ -116,7 +88,6 @@ export class FormFieldGenerator {
|
||||
Vue.$log.debug('NestFields: ', fields)
|
||||
return fields
|
||||
}
|
||||
|
||||
generateFieldByName(name, field) {
|
||||
switch (name) {
|
||||
case 'email':
|
||||
@@ -131,7 +102,6 @@ export class FormFieldGenerator {
|
||||
}
|
||||
return field
|
||||
}
|
||||
|
||||
generateFieldByOther(field, fieldMeta, fieldRemoteMeta) {
|
||||
const filedRules = field.rules || []
|
||||
if (fieldRemoteMeta.required) {
|
||||
@@ -141,20 +111,15 @@ export class FormFieldGenerator {
|
||||
filedRules.push(rules.RequiredChange)
|
||||
}
|
||||
}
|
||||
// 一些 field 有 choices 但不是 choiceField
|
||||
if (fieldRemoteMeta.choices && field.type.indexOf('choice') === -1) {
|
||||
field.el.choices = fieldRemoteMeta.choices
|
||||
}
|
||||
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.helpText = remoteFieldMeta.help_text
|
||||
field = this.generateFieldByType(remoteFieldMeta.type, field, fieldMeta, remoteFieldMeta)
|
||||
field = this.generateFieldByName(name, field)
|
||||
field = this.generateFieldByOther(field, fieldMeta, remoteFieldMeta)
|
||||
@@ -167,25 +132,18 @@ export class FormFieldGenerator {
|
||||
// Vue.$log.debug('Generate field: ', name, field)
|
||||
return field
|
||||
}
|
||||
|
||||
generateFieldGroup(field, fieldsMeta, remoteFieldsMeta) {
|
||||
const [groupTitle, fields] = field
|
||||
const _fields = this.generateFields(fields, fieldsMeta, remoteFieldsMeta)
|
||||
const group = {
|
||||
this.groups.push({
|
||||
id: groupTitle,
|
||||
title: groupTitle,
|
||||
fields: _fields,
|
||||
name: _fields[0]?.id
|
||||
}
|
||||
this.groups.push(group)
|
||||
return _fields
|
||||
name: fields[0],
|
||||
fields: fields
|
||||
})
|
||||
return this.generateFields(fields, fieldsMeta, remoteFieldsMeta)
|
||||
}
|
||||
|
||||
generateFields(_fields, fieldsMeta, remoteFieldsMeta) {
|
||||
let fields = []
|
||||
if (_fields === '__all__') {
|
||||
_fields = Object.keys(remoteFieldsMeta)
|
||||
}
|
||||
for (let field of _fields) {
|
||||
if (field instanceof Array) {
|
||||
const items = this.generateFieldGroup(field, fieldsMeta, remoteFieldsMeta)
|
||||
|
||||
@@ -33,8 +33,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
iOption() {
|
||||
const options = this.options.concat(this.internalOptions)
|
||||
return _.uniqWith(options, _.isEqual)
|
||||
return this.options.concat(this.internalOptions)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -68,16 +67,16 @@ export default {
|
||||
type: field.type,
|
||||
value: name
|
||||
}
|
||||
if (['choice', 'labeled_choice'].indexOf(field.type) > -1 && field.choices) {
|
||||
if (field.type === 'choice' && field.choices) {
|
||||
option.children = field.choices.map(item => {
|
||||
if (typeof (item.value) === 'boolean') {
|
||||
if (item.value) {
|
||||
return { label: item.label, value: 'True' }
|
||||
return { label: item.display_name, value: 'True' }
|
||||
} else {
|
||||
return { label: item.label, value: 'False' }
|
||||
return { label: item.display_name, value: 'False' }
|
||||
}
|
||||
}
|
||||
return { label: item.label, value: item.value }
|
||||
return { label: item.display_name, value: item.value }
|
||||
})
|
||||
}
|
||||
if (field.type === 'boolean') {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-if="showColumnSettingPopover"
|
||||
:cancel-title="$tc('common.RestoreDefault')"
|
||||
:destroy-on-close="true"
|
||||
:title="$tc('common.CustomCol')"
|
||||
:title="$t('common.CustomCol')"
|
||||
:visible.sync="showColumnSettingPopover"
|
||||
:destroy-on-close="true"
|
||||
:show-cancel="false"
|
||||
width="35%"
|
||||
top="10%"
|
||||
width="50%"
|
||||
@cancel="restoreDefault()"
|
||||
@confirm="handleColumnConfirm()"
|
||||
>
|
||||
<el-alert type="success">
|
||||
@@ -24,20 +23,24 @@
|
||||
style="margin-top:5px;"
|
||||
>
|
||||
<el-checkbox
|
||||
:disabled="item.prop==='actions' || minColumns.indexOf(item.prop)!==-1"
|
||||
: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: {
|
||||
@@ -79,10 +82,6 @@ export default {
|
||||
handleColumnConfirm() {
|
||||
this.showColumnSettingPopover = false
|
||||
this.$emit('columnsUpdate', { columns: this.iCurrentColumns, url: this.url })
|
||||
},
|
||||
restoreDefault() {
|
||||
this.showColumnSettingPopover = false
|
||||
this.$emit('columnsUpdate', { columns: null, url: this.url })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
/>
|
||||
<ColumnSettingPopover
|
||||
:current-columns="popoverColumns.currentCols"
|
||||
:min-columns="popoverColumns.minCols"
|
||||
:total-columns-list="popoverColumns.totalColumnsList"
|
||||
:min-columns="popoverColumns.minCols"
|
||||
:url="config.url"
|
||||
@columnsUpdate="handlePopoverColumnsChange"
|
||||
/>
|
||||
@@ -22,13 +22,15 @@
|
||||
<script type="text/jsx">
|
||||
import DataTable from '../DataTable'
|
||||
import {
|
||||
ActionsFormatter, ArrayFormatter, ChoicesFormatter, DateFormatter, DetailFormatter, DisplayFormatter,
|
||||
ObjectRelatedFormatter
|
||||
DateFormatter,
|
||||
DetailFormatter,
|
||||
DisplayFormatter,
|
||||
ActionsFormatter,
|
||||
ChoicesFormatter
|
||||
} from '@/components/TableFormatters'
|
||||
import i18n from '@/i18n/i18n'
|
||||
import { newURL, replaceAllUUID } from '@/utils/common'
|
||||
import ColumnSettingPopover from './components/ColumnSettingPopover'
|
||||
|
||||
import { newURL } from '@/utils/common'
|
||||
export default {
|
||||
name: 'AutoDataTable',
|
||||
components: {
|
||||
@@ -61,12 +63,13 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
computed: {
|
||||
},
|
||||
watch: {
|
||||
config: {
|
||||
handler: function(iNew, iOld) {
|
||||
handler(iNew) {
|
||||
this.optionUrlMetaAndGenCols()
|
||||
this.$log.debug('AutoDataTable Config change found: ')
|
||||
this.$log.debug('AutoDataTable Config change found')
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
@@ -76,12 +79,8 @@ 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`
|
||||
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] : {}
|
||||
@@ -118,22 +117,7 @@ export default {
|
||||
case 'is_valid':
|
||||
col.label = i18n.t('common.Validity')
|
||||
col.formatter = ChoicesFormatter
|
||||
col.formatterArgs = {
|
||||
textChoices: {
|
||||
true: i18n.t('common.Yes'),
|
||||
false: i18n.t('common.No')
|
||||
}
|
||||
}
|
||||
col.width = '80px'
|
||||
break
|
||||
case 'is_active':
|
||||
col.formatter = ChoicesFormatter
|
||||
col.formatterArgs = {
|
||||
textChoices: {
|
||||
true: i18n.t('common.Active'),
|
||||
false: i18n.t('common.Inactive')
|
||||
}
|
||||
}
|
||||
col.align = 'center'
|
||||
col.width = '80px'
|
||||
break
|
||||
case 'datetime':
|
||||
@@ -145,41 +129,21 @@ export default {
|
||||
}
|
||||
return col
|
||||
},
|
||||
generateColumnByType(type, col, meta) {
|
||||
generateColumnByType(type, col) {
|
||||
switch (type) {
|
||||
case 'choice':
|
||||
col.sortable = 'custom'
|
||||
col.formatter = DisplayFormatter
|
||||
break
|
||||
case 'labeled_choice':
|
||||
col.sortable = 'custom'
|
||||
col.formatter = ChoicesFormatter
|
||||
break
|
||||
case 'boolean':
|
||||
col.formatter = ChoicesFormatter
|
||||
col.align = 'center'
|
||||
col.width = '80px'
|
||||
break
|
||||
case 'datetime':
|
||||
col.formatter = DateFormatter
|
||||
col.width = '160px'
|
||||
break
|
||||
case 'object_related_field':
|
||||
col.formatter = ObjectRelatedFormatter
|
||||
break
|
||||
case 'm2m_related_field':
|
||||
col.formatter = ObjectRelatedFormatter
|
||||
break
|
||||
case 'list':
|
||||
col.formatter = ArrayFormatter
|
||||
break
|
||||
case 'json':
|
||||
case 'field':
|
||||
if (meta.child && meta.child.type === 'nested object') {
|
||||
col.formatter = ObjectRelatedFormatter
|
||||
}
|
||||
break
|
||||
}
|
||||
// this.$log.debug('Field: ', type, col.prop, col)
|
||||
return col
|
||||
},
|
||||
addHelpTipsIfNeed(col) {
|
||||
@@ -191,9 +155,9 @@ export default {
|
||||
return (
|
||||
<span>{column.label}
|
||||
<el-tooltip placement='bottom' effect='light' popperClass='help-tips'>
|
||||
<div slot='content' domPropsInnerHTML={helpTips}/>
|
||||
<div slot='content' domPropsInnerHTML={helpTips} />
|
||||
<el-button style='padding: 0'>
|
||||
<i class='fa fa-info-circle'/>
|
||||
<i class='fa fa-info-circle' />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
@@ -219,12 +183,12 @@ export default {
|
||||
col.filters = column.choices.map(item => {
|
||||
if (typeof (item.value) === 'boolean') {
|
||||
if (item.value) {
|
||||
return { text: item['label'], value: 'True' }
|
||||
return { text: item['display_name'], value: 'True' }
|
||||
} else {
|
||||
return { text: item['label'], value: 'False' }
|
||||
return { text: item['display_name'], value: 'False' }
|
||||
}
|
||||
}
|
||||
return { text: item['label'], value: item.value }
|
||||
return { text: item['display_name'], value: item.value }
|
||||
})
|
||||
col.sortable = false
|
||||
col['column-key'] = col.prop
|
||||
@@ -232,65 +196,22 @@ export default {
|
||||
}
|
||||
return col
|
||||
},
|
||||
addOrderingIfNeed(col) {
|
||||
if (col.prop) {
|
||||
const column = this.meta[col.prop] || {}
|
||||
if (column.order) {
|
||||
col.sortable = 'custom'
|
||||
col['column-key'] = col.prop
|
||||
}
|
||||
}
|
||||
return col
|
||||
},
|
||||
setDefaultFormatterIfNeed(col) {
|
||||
if (!col.formatter) {
|
||||
col.formatter = (row, column, cellValue) => {
|
||||
let value = cellValue
|
||||
let padding = '0'
|
||||
const excludes = [undefined, null, '']
|
||||
if (excludes.indexOf(value) !== -1) {
|
||||
padding = '6px'
|
||||
value = '-'
|
||||
}
|
||||
return <span style={{ marginLeft: padding }}>{value}</span>
|
||||
}
|
||||
}
|
||||
return col
|
||||
},
|
||||
|
||||
generateColumn(name) {
|
||||
const colMeta = this.meta[name] || {}
|
||||
const customMeta = this.config.columnsMeta ? this.config.columnsMeta[name] : {}
|
||||
let col = { prop: name, label: colMeta.label, showOverflowTooltip: true }
|
||||
let col = { prop: name, label: colMeta.label }
|
||||
|
||||
col = this.generateColumnByName(name, col)
|
||||
col = this.generateColumnByType(colMeta.type, col, colMeta)
|
||||
col = this.setDefaultFormatterIfNeed(col)
|
||||
col = this.generateColumnByType(colMeta.type, col)
|
||||
col = Object.assign(col, customMeta)
|
||||
col = this.addHelpTipsIfNeed(col)
|
||||
col = this.addFilterIfNeed(col)
|
||||
col = this.addOrderingIfNeed(col)
|
||||
return col
|
||||
},
|
||||
generateTotalColumns() {
|
||||
const config = _.cloneDeep(this.config)
|
||||
let columns = []
|
||||
const allColumnNames = Object.entries(this.meta)
|
||||
.filter(([name, meta]) => !meta['write_only'])
|
||||
.map(([name, meta]) => name)
|
||||
.concat(config.columnsExtra || [])
|
||||
|
||||
let configColumns = config.columns || allColumnNames
|
||||
const columnsExclude = config.columnsExclude || []
|
||||
configColumns = configColumns.filter(item => !columnsExclude.includes(item))
|
||||
|
||||
// 解决后端 API 返回字段中包含 actions 的问题;
|
||||
const hasColumnActions = configColumns.findIndex(item => item?.prop === 'actions') !== -1
|
||||
if (!hasColumnActions) {
|
||||
configColumns = [...configColumns.filter(i => i !== 'actions'), 'actions']
|
||||
}
|
||||
|
||||
for (let col of configColumns) {
|
||||
for (let col of config.columns) {
|
||||
if (typeof col === 'object') {
|
||||
columns.push(col)
|
||||
} else if (typeof col === 'string') {
|
||||
@@ -298,11 +219,7 @@ export default {
|
||||
columns.push(col)
|
||||
}
|
||||
}
|
||||
|
||||
columns = columns.filter(item => {
|
||||
if (item?.showFullContent) {
|
||||
item.className = 'show-full-content'
|
||||
}
|
||||
let has = item.has
|
||||
if (has === undefined) {
|
||||
has = true
|
||||
@@ -335,8 +252,7 @@ export default {
|
||||
const _tableConfig = localStorage.getItem('tableConfig')
|
||||
? JSON.parse(localStorage.getItem('tableConfig'))
|
||||
: {}
|
||||
let tableName = this.config.name || this.$route.name + '_' + newURL(this.iConfig.url).pathname
|
||||
tableName = replaceAllUUID(tableName)
|
||||
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) {
|
||||
@@ -377,17 +293,11 @@ export default {
|
||||
},
|
||||
handlePopoverColumnsChange({ columns, url }) {
|
||||
this.$log.debug('Columns change: ', columns)
|
||||
if (columns === null) {
|
||||
columns = this.cleanedColumnsShow.default
|
||||
}
|
||||
this.popoverColumns.currentCols = columns
|
||||
const _tableConfig = localStorage.getItem('tableConfig')
|
||||
? JSON.parse(localStorage.getItem('tableConfig'))
|
||||
: {}
|
||||
let tableName = this.config.name || this.$route.name + '_' + newURL(url).pathname
|
||||
// 替换url中的uuid,避免同一个类型接口生成多个key,localStorage中的数据无法共用
|
||||
tableName = replaceAllUUID(tableName)
|
||||
|
||||
const tableName = this.config.name || this.$route.name + '_' + newURL(url).pathname
|
||||
_tableConfig[tableName] = {
|
||||
'showColumns': columns
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ export default {
|
||||
showDelete: true,
|
||||
showUpdate: true,
|
||||
showSearch: false,
|
||||
// 自定义header
|
||||
customTreeHeader: false,
|
||||
customTreeHeaderName: this.$t('assets.AssetTree'),
|
||||
async: {
|
||||
enable: true,
|
||||
@@ -126,9 +128,9 @@ export default {
|
||||
query['asset'] = ''
|
||||
url = `${this.setting.url}${combinator}node_id=${objectId}&show_current_asset=${show_current_asset}`
|
||||
} else if (treeNode.meta.type === 'asset') {
|
||||
query['asset'] = treeNode.meta.data?.id || treeNode.id
|
||||
query['asset'] = treeNode.meta.data.id
|
||||
query['node'] = ''
|
||||
url = `${this.setting.url}${combinator}asset_id=${query.asset}&show_current_asset=${show_current_asset}`
|
||||
url = `${this.setting.url}${combinator}asset_id=${objectId}&show_current_asset=${show_current_asset}`
|
||||
}
|
||||
this.$router.push({ query })
|
||||
this.$emit('urlChange', url)
|
||||
@@ -142,20 +144,22 @@ export default {
|
||||
this.$axios.delete(
|
||||
`${this.treeSetting.nodeUrl}${currentNode.meta.data.id}/`
|
||||
).then(() => {
|
||||
this.$message.success(this.$tc('common.deleteSuccessMsg'))
|
||||
this.$message.success(this.$t('common.deleteSuccessMsg'))
|
||||
this.zTree.removeNode(currentNode)
|
||||
this.refreshTree()
|
||||
}).catch(() => {
|
||||
// this.$message.error(this.$tc('common.deleteErrorMsg') + ' ' + error)
|
||||
// this.$message.error(this.$t('common.deleteErrorMsg') + ' ' + error)
|
||||
})
|
||||
},
|
||||
onRename: function(event, treeId, treeNode, isCancel) {
|
||||
const currentNodeId = this.currentNodeId || treeNode.meta.data?.id || ''
|
||||
const url = `${this.treeSetting.nodeUrl}${currentNodeId}/`
|
||||
const url = `${this.treeSetting.nodeUrl}${this.currentNodeId}/`
|
||||
if (isCancel) {
|
||||
return
|
||||
}
|
||||
this.$axios.patch(url, { 'value': treeNode.name }).then(res => {
|
||||
this.$axios.patch(
|
||||
url,
|
||||
{ 'value': treeNode.name }
|
||||
).then(res => {
|
||||
let assetsAmount = treeNode.meta.data['assetsAmount']
|
||||
if (!assetsAmount) {
|
||||
assetsAmount = 0
|
||||
@@ -163,10 +167,8 @@ export default {
|
||||
treeNode.name = treeNode.name + ' (' + assetsAmount + ')'
|
||||
treeNode.meta.data = res
|
||||
this.zTree.updateNode(treeNode)
|
||||
this.$message.success(this.$tc('common.updateSuccessMsg'))
|
||||
}).finally(() => {
|
||||
this.refreshTree()
|
||||
})
|
||||
this.$message.success(this.$t('common.updateSuccessMsg'))
|
||||
}).finally(() => { this.refreshTree() })
|
||||
},
|
||||
onBodyMouseDown: function(event) {
|
||||
const rMenuID = this.$refs.dataztree.$refs.ztree.iRMenuID
|
||||
@@ -206,7 +208,6 @@ export default {
|
||||
this.showRMenu('root', event.clientX, event.clientY)
|
||||
} else if (treeNode && !treeNode.noR) {
|
||||
this.zTree.selectNode(treeNode)
|
||||
this.currentNodeId = treeNode.meta.data.id
|
||||
this.showRMenu('node', event.clientX, event.clientY)
|
||||
}
|
||||
},
|
||||
@@ -233,9 +234,9 @@ export default {
|
||||
nodes: treeNodesIds
|
||||
}
|
||||
).then((res) => {
|
||||
this.$message.success(this.$tc('common.updateSuccessMsg'))
|
||||
this.$message.success(this.$t('common.updateSuccessMsg'))
|
||||
}).catch(error => {
|
||||
this.$message.error(this.$tc('common.updateErrorMsg' + ' ' + error))
|
||||
this.$message.error(this.$t('common.updateErrorMsg' + ' ' + error))
|
||||
}).finally()
|
||||
},
|
||||
createTreeNode: function() {
|
||||
@@ -263,9 +264,9 @@ export default {
|
||||
const node = this.zTree.getNodeByParam('id', newNode.id, parentNode)
|
||||
this.currentNodeId = node.meta.data.id || newNode.id
|
||||
this.zTree.editName(node)
|
||||
this.$message.success(this.$tc('common.createSuccessMsg'))
|
||||
this.$message.success(this.$t('common.createSuccessMsg'))
|
||||
}).catch(error => {
|
||||
this.$message.error(this.$tc('common.createErrorMsg') + ' ' + error)
|
||||
this.$message.error(this.$t('common.createErrorMsg') + ' ' + error)
|
||||
})
|
||||
},
|
||||
refresh: function() {
|
||||
@@ -303,13 +304,11 @@ export default {
|
||||
text-decoration: none;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.rmenu:hover {
|
||||
.rmenu:hover{
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.data-z-tree >>> .fa {
|
||||
width: 10px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane v-if="shouldHide('min')" :label="$tc('common.CronTab.min')">
|
||||
<el-tab-pane v-if="shouldHide('min')" :label="this.$t('common.CronTab.min')">
|
||||
<CrontabMin
|
||||
ref="cronmin"
|
||||
:check="checkNumber"
|
||||
@@ -11,7 +11,7 @@
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="shouldHide('hour')" :label="$tc('common.CronTab.hour')">
|
||||
<el-tab-pane v-if="shouldHide('hour')" :label="this.$t('common.CronTab.hour')">
|
||||
<CrontabHour
|
||||
ref="cronhour"
|
||||
:check="checkNumber"
|
||||
@@ -20,7 +20,7 @@
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="shouldHide('day')" :label="$tc('common.CronTab.day')">
|
||||
<el-tab-pane v-if="shouldHide('day')" :label="this.$t('common.CronTab.day')">
|
||||
<CrontabDay
|
||||
ref="cronday"
|
||||
:check="checkNumber"
|
||||
@@ -29,7 +29,7 @@
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="shouldHide('month')" :label="$tc('common.CronTab.month')">
|
||||
<el-tab-pane v-if="shouldHide('month')" :label="this.$t('common.CronTab.month')">
|
||||
<CrontabMonth
|
||||
ref="cronmonth"
|
||||
:check="checkNumber"
|
||||
@@ -38,7 +38,7 @@
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="shouldHide('week')" :label="$tc('common.CronTab.week')">
|
||||
<el-tab-pane v-if="shouldHide('week')" :label="this.$t('common.CronTab.week')">
|
||||
<CrontabWeek
|
||||
ref="cronweek"
|
||||
:check="checkNumber"
|
||||
@@ -390,7 +390,6 @@ export default {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.popup-main {
|
||||
position: relative;
|
||||
margin: 10px auto 0;
|
||||
@@ -399,14 +398,12 @@ export default {
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
overflow: hidden;
|
||||
line-height: 34px;
|
||||
padding-top: 6px;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
.popup-result {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
@@ -416,7 +413,6 @@ export default {
|
||||
border: 1px solid #dcdfe6;
|
||||
box-shadow: 0 2px 4px 0 rgb(0 0 0 / 12%), 0 0 6px 0 rgb(0 0 0 / 4%);
|
||||
}
|
||||
|
||||
.popup-result .title {
|
||||
position: absolute;
|
||||
top: -17px;
|
||||
@@ -428,13 +424,11 @@ export default {
|
||||
line-height: 30px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.popup-result table {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.popup-result table span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -445,14 +439,12 @@ export default {
|
||||
overflow: hidden;
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.popup-result-scroll {
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
height: 10em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.el-form-item--mini.el-form-item,
|
||||
.el-form-item--small.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
|
||||
@@ -25,13 +25,7 @@
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="7">
|
||||
{{ this.$t('common.CronTab.appoint') }}
|
||||
<el-select
|
||||
v-model="checkboxList"
|
||||
clearable
|
||||
:placeholder="$tc('common.CronTab.manyChoose')"
|
||||
multiple
|
||||
style="width:100%"
|
||||
>
|
||||
<el-select v-model="checkboxList" clearable :placeholder="this.$t('common.CronTab.manyChoose')" multiple style="width:100%">
|
||||
<el-option v-for="item in 31" :key="item" :value="item">{{ item }}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
@@ -51,8 +45,7 @@ export default {
|
||||
},
|
||||
check: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -191,6 +184,6 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.el-form-item--small.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -25,13 +25,7 @@
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
{{ this.$t('common.CronTab.appoint') }}
|
||||
<el-select
|
||||
v-model="checkboxList"
|
||||
clearable
|
||||
:placeholder="$tc('common.CronTab.manyChoose')"
|
||||
multiple
|
||||
style="width:100%"
|
||||
>
|
||||
<el-select v-model="checkboxList" clearable :placeholder="this.$t('common.CronTab.manyChoose')" multiple style="width:100%">
|
||||
<el-option v-for="item in 24" :key="item" :value="item-1">{{ item-1 }}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
@@ -51,8 +45,7 @@ export default {
|
||||
},
|
||||
check: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -160,6 +153,6 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.el-form-item--small.el-form-item {
|
||||
margin-bottom: 10px
|
||||
}
|
||||
margin-bottom: 10px
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -25,14 +25,7 @@
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
{{ this.$t('common.CronTab.appoint') }}
|
||||
<el-select
|
||||
v-model="checkboxList"
|
||||
clearable
|
||||
:placeholder="$tc('common.CronTab.manyChoose')"
|
||||
multiple
|
||||
style="width:100%"
|
||||
size="small"
|
||||
>
|
||||
<el-select v-model="checkboxList" clearable :placeholder="this.$t('common.CronTab.manyChoose')" multiple style="width:100%" size="small">
|
||||
<el-option v-for="item in 60" :key="item" :value="item-1">{{ item-1 }}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
@@ -53,8 +46,7 @@ export default {
|
||||
},
|
||||
check: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -159,6 +151,6 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.el-form-item--small.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -25,13 +25,7 @@
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="4">
|
||||
{{ this.$t('common.CronTab.appoint') }}
|
||||
<el-select
|
||||
v-model="checkboxList"
|
||||
clearable
|
||||
:placeholder="$tc('common.CronTab.manyChoose')"
|
||||
multiple
|
||||
style="width:100%"
|
||||
>
|
||||
<el-select v-model="checkboxList" clearable :placeholder="this.$t('common.CronTab.manyChoose')" multiple style="width:100%">
|
||||
<el-option v-for="item in 12" :key="item" :value="item">{{ item }}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
@@ -51,8 +45,7 @@ export default {
|
||||
},
|
||||
check: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -165,6 +158,6 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.el-form-item--small.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<script>
|
||||
import parser from 'cron-parser'
|
||||
import { toSafeLocalDateStr } from '@/utils/common'
|
||||
import moment from 'moment'
|
||||
export default {
|
||||
name: 'CrontabResult',
|
||||
props: {
|
||||
@@ -46,10 +46,10 @@ export default {
|
||||
const rule = 0 + ' ' + this.$options.propsData.ex
|
||||
try {
|
||||
this.resultList = []
|
||||
const interval = parser.parseExpression(rule)
|
||||
var interval = parser.parseExpression(rule)
|
||||
for (let index = 0; index < 5; index++) {
|
||||
const cur = interval.next().toString()
|
||||
this.resultList.push(toSafeLocalDateStr(cur))
|
||||
this.resultList.push(moment(cur).format('YYYY-MM-DD HH:mm:ss'))
|
||||
}
|
||||
} catch (error) {
|
||||
this.isShow = false
|
||||
|
||||
@@ -18,13 +18,7 @@
|
||||
<el-form-item>
|
||||
<el-radio v-model="radioValue" :label="6">
|
||||
{{ this.$t('common.CronTab.appoint') }}
|
||||
<el-select
|
||||
v-model="checkboxList"
|
||||
clearable
|
||||
:placeholder="$tc('common.CronTab.manyChoose')"
|
||||
multiple
|
||||
style="width:100%"
|
||||
>
|
||||
<el-select v-model="checkboxList" clearable :placeholder="this.$t('common.CronTab.manyChoose')" multiple style="width:100%">
|
||||
<el-option v-for="(item,index) of weekList" :key="index" :value="index+1">{{ item }}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
@@ -45,8 +39,7 @@ export default {
|
||||
},
|
||||
check: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -180,6 +173,6 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.el-form-item--small.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="box">
|
||||
<el-input v-model="input" clearable @focus="showDialog" @clear="onClear" />
|
||||
</div>
|
||||
<el-dialog :title="$tc('common.CronTab.newCron')" :visible.sync="showCron" top="8vh" width="580px" append-to-body>
|
||||
<el-dialog :title="this.$t('common.CronTab.newCron')" :visible.sync="showCron" top="8vh" width="580px" append-to-body>
|
||||
<Crontab
|
||||
:expression="expression"
|
||||
@hide="showCron = false"
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
<script>
|
||||
import Crontab from './Crontab.vue'
|
||||
|
||||
export default {
|
||||
components: { Crontab },
|
||||
props: {
|
||||
@@ -52,5 +51,5 @@ export default {
|
||||
<style scoped>
|
||||
.el-dialog__body {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
placement="bottom-start"
|
||||
@command="handleDropdownCallback"
|
||||
>
|
||||
<el-button class="more-action" :size="size" v-bind="cleanButtonAction(action)">
|
||||
<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" style="overflow: auto;max-height: 60vh">
|
||||
@@ -28,10 +28,6 @@
|
||||
:command="[option, action]"
|
||||
v-bind="option"
|
||||
>
|
||||
<span v-if="option.fa">
|
||||
<i v-if="option.fa.startsWith('fa-')" :class="'fa ' + option.fa" />
|
||||
<svg-icon v-else :icon-class="option.fa" style="font-size: 14px; margin-right: 2px; margin-left: -2px;" />
|
||||
</span>
|
||||
{{ option.title }}
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
@@ -48,11 +44,7 @@
|
||||
>
|
||||
<el-tooltip :disabled="!action.tip" :content="action.tip" placement="top">
|
||||
<span>
|
||||
<span v-if="action.fa" style="vertical-align: initial;">
|
||||
<i v-if="action.fa.startsWith('fa-')" :class="'fa ' + action.fa" />
|
||||
<svg-icon v-else :icon-class="action.fa" style="font-size: 14px;" />
|
||||
</span>
|
||||
{{ action.title }}
|
||||
<i v-if="action.fa" :class="'fa ' + action.fa" />{{ action.title }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</el-button>
|
||||
@@ -161,7 +153,7 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style scoped>
|
||||
.layout {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -189,22 +181,4 @@ export default {
|
||||
.el-button-ungroup .action-item:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
::v-deep .more-batch-processing {
|
||||
&.el-dropdown-menu__item--divided {
|
||||
margin-top: 0;
|
||||
border-top: none;
|
||||
color: #909399;
|
||||
cursor: auto;
|
||||
font-size: 12px;
|
||||
line-height: 30px;
|
||||
border-bottom: 1px solid #E4E7ED;
|
||||
&:before {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
&.el-dropdown-menu__item:not(.is-disabled):hover {
|
||||
color: #909399;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -43,25 +43,16 @@
|
||||
<template v-for="opt in options">
|
||||
<el-option
|
||||
v-if="data.type === 'select'"
|
||||
:key="opt.label"
|
||||
:key="opt.value"
|
||||
v-bind="opt"
|
||||
/>
|
||||
<el-checkbox-button
|
||||
v-else-if="data.type === 'checkbox-group' && data.style === 'button'"
|
||||
:key="opt.value"
|
||||
v-bind="opt"
|
||||
:label="'value' in opt ? opt.value : opt.label"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</el-checkbox-button>
|
||||
|
||||
<!-- TODO: 支持 el-checkbox-button 变体 -->
|
||||
<el-checkbox
|
||||
v-else-if="data.type === 'checkbox-group' && data.style !== 'button'"
|
||||
:key="opt.value"
|
||||
v-else-if="data.type === 'checkbox-group'"
|
||||
:key="opt.label"
|
||||
v-bind="opt"
|
||||
:label="'value' in opt ? opt.value : opt.label"
|
||||
>
|
||||
{{ opt.label }}
|
||||
{{ opt.value }}
|
||||
</el-checkbox>
|
||||
<!-- WARNING: radio 用 label 属性来表示 value 的含义 -->
|
||||
<!-- FYI: radio 的 value 属性可以在没有 radio-group 时用来关联到同一个 v-model -->
|
||||
@@ -238,9 +229,7 @@ export default {
|
||||
.then(resp => {
|
||||
if (isOptionsCase) {
|
||||
let formRenderer = this.$parent
|
||||
while (formRenderer.$options._componentTag !== 'el-form-renderer') {
|
||||
formRenderer = formRenderer.$parent
|
||||
}
|
||||
while (formRenderer.$options._componentTag !== 'el-form-renderer') { formRenderer = formRenderer.$parent }
|
||||
formRenderer.setOptions(this.prop, resp)
|
||||
} else {
|
||||
this.propsInner = { [prop]: resp }
|
||||
|
||||
@@ -14,36 +14,10 @@
|
||||
<slot v-for="item in fields" :slot="`$id:${item.id}`" :name="`$id:${item.id}`" />
|
||||
|
||||
<el-form-item v-if="hasButtons" class="form-buttons">
|
||||
<el-button
|
||||
v-for="button in moreButtons"
|
||||
:key="button.title"
|
||||
:loading="button.loading"
|
||||
size="small"
|
||||
v-bind="button"
|
||||
@click="handleClick(button)"
|
||||
>
|
||||
{{ button.title }}
|
||||
</el-button>
|
||||
<el-button v-if="defaultButton && hasReset" size="small" @click="resetForm('form')">
|
||||
{{ $t('common.Reset') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="defaultButton && hasSaveContinue"
|
||||
size="small"
|
||||
@click="submitForm('form', true)"
|
||||
>
|
||||
{{ $t('common.SaveAndAddAnother') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="defaultButton"
|
||||
:disabled="!canSubmit"
|
||||
:loading="isSubmitting"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="submitForm('form')"
|
||||
>
|
||||
{{ $t('common.Submit') }}
|
||||
</el-button>
|
||||
<el-button v-for="button in moreButtons" :key="button.title" size="small" v-bind="button" :loading="button.loading" @click="handleClick(button)">{{ button.title }}</el-button>
|
||||
<el-button v-if="defaultButton && hasReset" size="small" @click="resetForm('form')">{{ $t('common.Reset') }}</el-button>
|
||||
<el-button v-if="defaultButton && hasSaveContinue" size="small" @click="submitForm('form', true)">{{ $t('common.SaveAndAddAnother') }}</el-button>
|
||||
<el-button v-if="defaultButton" size="small" :loading="isSubmitting" type="primary" @click="submitForm('form')">{{ $t('common.Submit') }}</el-button>
|
||||
</el-form-item>
|
||||
</ElFormRender>
|
||||
</template>
|
||||
@@ -51,7 +25,6 @@
|
||||
<script>
|
||||
import ElFormRender from './components/el-form-renderer'
|
||||
import { scrollToError } from '@/utils'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ElFormRender
|
||||
@@ -69,10 +42,6 @@ export default {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
canSubmit: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
hasSaveContinue: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@@ -101,9 +70,6 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
elForm() {
|
||||
return this.$refs.form
|
||||
},
|
||||
mobile() {
|
||||
return this.$store.state.app.device === 'mobile'
|
||||
},
|
||||
@@ -159,7 +125,7 @@ export default {
|
||||
}
|
||||
|
||||
.el-form ::v-deep .el-form-item__error {
|
||||
position: inherit;
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
.el-form ::v-deep .form-group-header {
|
||||
@@ -178,12 +144,10 @@ export default {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.el-form ::v-deep .help-block a {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.form-buttons {
|
||||
margin-top: 20px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,23 +14,9 @@ export const EmailCheck = {
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
|
||||
export const IpCheck = {
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
value = value?.trim()
|
||||
const urlRegExp = /^[\w://.?=&#-]+$/
|
||||
if (urlRegExp.test(value)) {
|
||||
callback()
|
||||
} else {
|
||||
callback(new Error(i18n.t('common.FormatError')))
|
||||
}
|
||||
},
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
|
||||
export const specialEmojiCheck = {
|
||||
validator: (rule, value, callback) => {
|
||||
value = value?.trim()
|
||||
value = value.trim()
|
||||
if (/[\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/.test(value)) {
|
||||
callback(new Error(i18n.t('common.NotSpecialEmoji')))
|
||||
} else {
|
||||
@@ -40,26 +26,11 @@ export const specialEmojiCheck = {
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
|
||||
// 只能输入字母、数字、下划线
|
||||
export const matchAlphanumericUnderscore = {
|
||||
validator: (rule, value, callback) => {
|
||||
value = value?.trim()
|
||||
if (!/^[a-zA-Z0-9_]+$/.test(value)) {
|
||||
callback(new Error(i18n.t('common.notAlphanumericUnderscore')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
|
||||
export default {
|
||||
IpCheck,
|
||||
Required,
|
||||
RequiredChange,
|
||||
EmailCheck,
|
||||
specialEmojiCheck,
|
||||
matchAlphanumericUnderscore
|
||||
specialEmojiCheck
|
||||
}
|
||||
|
||||
export const JsonRequired = {
|
||||
@@ -67,7 +38,7 @@ export const JsonRequired = {
|
||||
trigger: 'change',
|
||||
validator: (rule, value, callback) => {
|
||||
try {
|
||||
typeof value === 'string' ? JSON.parse(value) : value
|
||||
JSON.parse(value)
|
||||
callback()
|
||||
} catch (e) {
|
||||
callback(new Error(i18n.t('common.InvalidJson')))
|
||||
@@ -80,8 +51,8 @@ export const JsonRequiredUserNameMapped = {
|
||||
trigger: 'change',
|
||||
validator: (rule, value, callback) => {
|
||||
try {
|
||||
const v = typeof value === 'string' ? JSON.parse(value) : value
|
||||
const hasUserName = _.map(v, (value) => value)
|
||||
JSON.parse(value)
|
||||
const hasUserName = _.map(JSON.parse(value), (value) => value)
|
||||
if (!hasUserName.includes('username')) {
|
||||
callback(new Error(i18n.t('common.requiredHasUserNameMapped')))
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
<el-table
|
||||
ref="table"
|
||||
v-loading="loading"
|
||||
v-bind="tableAttrs"
|
||||
:data="data"
|
||||
:row-class-name="rowClassName"
|
||||
v-bind="tableAttrs"
|
||||
@select="selectStrategy.onSelect"
|
||||
v-on="$listeners"
|
||||
@selection-change="selectStrategy.onSelectionChange"
|
||||
@select="selectStrategy.onSelect"
|
||||
@select-all="selectStrategy.onSelectAll($event, canSelect)"
|
||||
@sort-change="onSortChange"
|
||||
>
|
||||
@@ -91,28 +91,28 @@
|
||||
|
||||
<!--非树-->
|
||||
<template v-else>
|
||||
<el-data-table-column v-if="hasSelection" :align="selectionAlign" :selectable="canSelect" type="selection" />
|
||||
<el-data-table-column v-if="hasSelection" type="selection" :align="selectionAlign" :selectable="canSelect" />
|
||||
<el-data-table-column
|
||||
v-for="col in columns"
|
||||
:key="col.prop"
|
||||
:filter-method="typeof col.filterMethod === 'function' ? col.filterMethod : null"
|
||||
:filter-multiple="false"
|
||||
:filters="col.filters || null"
|
||||
:formatter="typeof col.formatter === 'function' ? col.formatter : null"
|
||||
:filters="col.filters || null"
|
||||
:filter-multiple="false"
|
||||
:filter-method="typeof col.filterMethod === 'function' ? col.filterMethod : null"
|
||||
v-bind="{align: columnsAlign, ...col}"
|
||||
>
|
||||
<template v-if="col.formatter && typeof col.formatter !== 'function'" v-slot:default="{row, column, index}">
|
||||
<div
|
||||
:is="col.formatter"
|
||||
:key="row.id"
|
||||
:cell-value="row[col.prop]"
|
||||
:col="col"
|
||||
:table-data="data"
|
||||
:row="row"
|
||||
:column="column"
|
||||
:index="index"
|
||||
:reload="getList"
|
||||
:row="row"
|
||||
:table-data="data"
|
||||
:url="url"
|
||||
:reload="getList"
|
||||
:col="col"
|
||||
:cell-value="row[col.prop]"
|
||||
/>
|
||||
</template>
|
||||
</el-data-table-column>
|
||||
@@ -122,12 +122,13 @@
|
||||
|
||||
<el-pagination
|
||||
v-if="hasPagination"
|
||||
:background="paginationBackground"
|
||||
:current-page="page"
|
||||
:layout="paginationLayout"
|
||||
:page-size="size"
|
||||
:page-sizes="paginationSizes"
|
||||
:page-size="size"
|
||||
:total="total"
|
||||
:background="paginationBackground"
|
||||
style="text-align: right; padding: 10px 0;"
|
||||
:layout="paginationLayout"
|
||||
v-bind="extraPaginationAttrs"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
@@ -135,18 +136,18 @@
|
||||
|
||||
<the-dialog
|
||||
ref="dialog"
|
||||
:button-size="buttonSize"
|
||||
:dialog-attrs="dialogAttrs"
|
||||
:new-title="dialogNewTitle"
|
||||
:edit-title="dialogEditTitle"
|
||||
:view-title="dialogViewTitle"
|
||||
:form="form"
|
||||
:form-attrs="formAttrs"
|
||||
:new-title="dialogNewTitle"
|
||||
:view-title="dialogViewTitle"
|
||||
:dialog-attrs="dialogAttrs"
|
||||
:button-size="buttonSize"
|
||||
@confirm="onConfirm"
|
||||
>
|
||||
<template v-slot="scope">
|
||||
<!-- @slot 表单作用域插槽。当编辑、查看时传入row;新增时row=null -->
|
||||
<slot :row="scope.row" name="form" />
|
||||
<slot name="form" :row="scope.row" />
|
||||
</template>
|
||||
</the-dialog>
|
||||
</template>
|
||||
@@ -166,7 +167,6 @@ 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 noPaginationDataPath = 'payload'
|
||||
|
||||
@@ -268,8 +268,7 @@ export default {
|
||||
*/
|
||||
beforeSearch: {
|
||||
type: Function,
|
||||
default() {
|
||||
}
|
||||
default() {}
|
||||
},
|
||||
/**
|
||||
* 单选, 适用场景: 不可以批量删除
|
||||
@@ -361,36 +360,28 @@ export default {
|
||||
*/
|
||||
newText: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return this.$t('ops.Add')
|
||||
}
|
||||
default: '新增'
|
||||
},
|
||||
/**
|
||||
* 修改按钮文案
|
||||
*/
|
||||
editText: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return this.$t('ops.Modify')
|
||||
}
|
||||
default: '修改'
|
||||
},
|
||||
/**
|
||||
* 查看按钮文案
|
||||
*/
|
||||
viewText: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return this.$t('ops.View')
|
||||
}
|
||||
default: '查看'
|
||||
},
|
||||
/**
|
||||
* 删除按钮文案
|
||||
*/
|
||||
deleteText: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return this.$t('ops.Delete')
|
||||
}
|
||||
default: '删除'
|
||||
},
|
||||
/**
|
||||
* 删除提示语。接受要删除的数据(单个对象或数组);返回字符串
|
||||
@@ -400,7 +391,7 @@ export default {
|
||||
deleteMessage: {
|
||||
type: Function,
|
||||
default() {
|
||||
return this.$t('ops.Confirm') + this.deleteText + '?'
|
||||
return `确认${this.deleteText}吗?`
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -459,7 +450,7 @@ export default {
|
||||
onSuccess: {
|
||||
type: Function,
|
||||
default() {
|
||||
return this.$message.success(this.$t('ops.SuccessfulOperation'))
|
||||
return this.$message.success('操作成功')
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -716,8 +707,7 @@ export default {
|
||||
},
|
||||
extraPaginationAttrs: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
}
|
||||
default: () => {}
|
||||
},
|
||||
hasSelection: {
|
||||
type: Boolean,
|
||||
@@ -1047,7 +1037,6 @@ export default {
|
||||
} else {
|
||||
this.innerQuery = merge(this.innerQuery, attrs)
|
||||
}
|
||||
this.selected.splice(0, this.selected.length)
|
||||
return this.getList()
|
||||
},
|
||||
searchDate(attrs) {
|
||||
@@ -1167,7 +1156,7 @@ export default {
|
||||
* @param {object|object[]} - 要删除的数据对象或数组
|
||||
*/
|
||||
onDefaultDelete(data) {
|
||||
this.$confirm(this.deleteMessage(data), this.$t('common.Info'), {
|
||||
this.$confirm(this.deleteMessage(data), '提示', {
|
||||
type: 'warning',
|
||||
confirmButtonClass: 'el-button--danger',
|
||||
beforeClose: async(action, instance, done) => {
|
||||
@@ -1210,9 +1199,7 @@ export default {
|
||||
remain === 0 &&
|
||||
this.page === lastPage &&
|
||||
this.page > defaultFirstPage
|
||||
) {
|
||||
this.page--
|
||||
}
|
||||
) { this.page-- }
|
||||
},
|
||||
|
||||
// 树形table相关
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
.el-data-table ::v-deep .el-table td {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.el-data-table ::v-deep .el-table th {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.el-data-table ::v-deep .el-form-item {
|
||||
margin-bottom: 10px !important;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.el-data-table ::v-deep .el-pagination {
|
||||
text-align: right;
|
||||
padding: 15px 25px 20px 15px;
|
||||
|
||||
.el-pagination__total {
|
||||
float: left;
|
||||
.el-data-table ::v-deep .el-pagination{
|
||||
text-align: center !important;
|
||||
}
|
||||
.el-data-table ::v-deep .el-table td{
|
||||
padding: 4px 0;
|
||||
}
|
||||
.el-data-table ::v-deep .el-table th{
|
||||
padding: 4px 0;
|
||||
}
|
||||
.el-data-table ::v-deep .el-form-item{
|
||||
margin-bottom:10px !important ;
|
||||
margin-top:10px;
|
||||
}
|
||||
.el-data-table ::v-deep .el-pagination{
|
||||
padding:15px 0 !important ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ElDatableTable
|
||||
ref="table"
|
||||
class="el-data-table"
|
||||
class="el-table"
|
||||
v-bind="tableConfig"
|
||||
@update="onUpdate"
|
||||
v-on="iListeners"
|
||||
@@ -21,8 +21,7 @@ export default {
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
}
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -50,11 +49,7 @@ export default {
|
||||
stripe: false, // 斑马纹表格
|
||||
border: true, // 表格边框
|
||||
fit: true, // 宽度自适应,
|
||||
tooltipEffect: 'dark',
|
||||
rowClassName: ({ row }) => {
|
||||
const selected = this.dataTable.selected.find(item => item.id === row.id)
|
||||
return selected ? 'selected-row' : ''
|
||||
}
|
||||
tooltipEffect: 'dark'
|
||||
},
|
||||
extraButtons: userTableActions.extraButtons,
|
||||
onEdit: (row) => {
|
||||
@@ -70,7 +65,6 @@ export default {
|
||||
},
|
||||
pageCount: 5,
|
||||
paginationLayout: 'total, sizes, prev, pager, next',
|
||||
paginationSize: JSON.parse(localStorage.getItem('paginationSize')) || 15,
|
||||
paginationSizes: [15, 30, 50, 100],
|
||||
paginationBackground: true,
|
||||
transformQuery: query => {
|
||||
@@ -92,36 +86,40 @@ export default {
|
||||
}
|
||||
return query
|
||||
},
|
||||
theRowDefaultIsSelected: (row) => {
|
||||
return false
|
||||
}
|
||||
theRowDefaultIsSelected: (row) => { return false }
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iListeners() {
|
||||
const defaultListeners = {}
|
||||
return Object.assign(defaultListeners, this.$listeners, this.tableConfig?.listeners)
|
||||
},
|
||||
dataTable() {
|
||||
return this.$refs.table
|
||||
},
|
||||
tableConfig() {
|
||||
const tableDefaultConfig = this.defaultConfig
|
||||
tableDefaultConfig.paginationSize = _.get(this.globalTableConfig, 'paginationSize', 15)
|
||||
let tableAttrs = tableDefaultConfig.tableAttrs
|
||||
if (this.config.tableAttrs) {
|
||||
tableAttrs = Object.assign(tableAttrs, this.config.tableAttrs)
|
||||
}
|
||||
const config = Object.assign(tableDefaultConfig, this.config)
|
||||
config.tableAttrs = tableAttrs
|
||||
this.$log.debug('elTableConfig', config)
|
||||
return config
|
||||
},
|
||||
iListeners() {
|
||||
return Object.assign({}, this.$listeners, this.tableConfig.listeners)
|
||||
},
|
||||
dataTable() {
|
||||
return this.$refs.table
|
||||
},
|
||||
...mapGetters({
|
||||
'globalTableConfig': 'tableConfig'
|
||||
})
|
||||
},
|
||||
watch: {},
|
||||
watch: {
|
||||
config: {
|
||||
handler() {
|
||||
// this.getList()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.$refs.table.clearSelection()
|
||||
@@ -155,7 +153,6 @@ export default {
|
||||
}
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
localStorage.setItem('paginationSize', val)
|
||||
this.$store.commit('table/SET_TABLE_CONFIG',
|
||||
{
|
||||
key: 'paginationSize',
|
||||
@@ -167,58 +164,37 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-data-table > > > .el-table {
|
||||
.table {
|
||||
margin-top: 15px;
|
||||
}
|
||||
<style lang="less" scoped>
|
||||
|
||||
.el-table__row {
|
||||
&.selected-row {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
& > td {
|
||||
line-height: 1.5;
|
||||
padding: 6px 0;
|
||||
font-size: 13px;
|
||||
border-right: none;
|
||||
* {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.el-checkbox {
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
& > div > span {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__header > thead > tr > th {
|
||||
padding: 6px 0;
|
||||
background-color: #ffffff;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
border-right: none;
|
||||
.cell {
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
&:hover {
|
||||
border-right: 2px solid #EBEEF5;
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-table ::v-deep .el-table__row > td {
|
||||
line-height: 1.5;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.el-data-table >>> .el-table .el-table__header > thead > tr .is-sortable {
|
||||
padding: 5px 0;
|
||||
.cell {
|
||||
padding-top: 3px!important;
|
||||
}
|
||||
.el-table ::v-deep .el-table__row > td> div > span {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.el-table ::v-deep .el-table__header > thead > tr >th {
|
||||
padding: 8px 0;
|
||||
background-color: #F5F5F6;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.table{
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
//分页
|
||||
.el-pagination ::v-deep .el-pagination__total{
|
||||
float: left;
|
||||
}
|
||||
|
||||
.el-pagination ::v-deep .el-pagination__sizes{
|
||||
float: left;
|
||||
}
|
||||
//修改颜色
|
||||
// .el-button--text{
|
||||
// color: #409EFF;
|
||||
// }
|
||||
</style>
|
||||
|
||||
@@ -1,31 +1,47 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="treebox">
|
||||
<div>
|
||||
<el-input
|
||||
v-if="treeSetting.showSearch && showTreeSearch"
|
||||
v-model="treeSearchValue"
|
||||
:placeholder="$tc('common.Search')"
|
||||
class="fixed-tree-search"
|
||||
prefix-icon="fa fa-search"
|
||||
size="mini"
|
||||
@input="treeSearchHandle"
|
||||
>
|
||||
<span slot="suffix">
|
||||
<!-- <i class="fa fa-search" style="font-size: 14px; color: #676A6C;" /> -->
|
||||
<svg-icon
|
||||
:icon-class="'close'"
|
||||
class="icon"
|
||||
style="font-size: 14px;"
|
||||
@click="onClose"
|
||||
<div
|
||||
v-if="treeSetting.customTreeHeader"
|
||||
class="tree-header treebox"
|
||||
>
|
||||
<div class="content">
|
||||
<span class="title">
|
||||
{{ treeSetting.customTreeHeaderName }}
|
||||
</span>
|
||||
<span class="tree-banner-icon-zone">
|
||||
<a id="searchIcon" class="tree-search special">
|
||||
<i
|
||||
class="fa fa-search tree-banner-icon"
|
||||
@click.stop="treeSearch"
|
||||
/>
|
||||
</span>
|
||||
</el-input>
|
||||
<input
|
||||
id="searchInput"
|
||||
v-model="treeSearchValue"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
class="tree-input"
|
||||
>
|
||||
</a>
|
||||
<i
|
||||
class="fa fa-refresh tree-banner-icon"
|
||||
style="margin-right: 2px;"
|
||||
@click.stop="refresh"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<ul v-show="loading" class="ztree">
|
||||
{{ this.$t('common.tree.Loading') }}...
|
||||
</ul>
|
||||
<ul v-show="!loading" :id="iZTreeID" :key="iZTreeID" class="ztree" />
|
||||
<ul v-show="!loading" :id="iZTreeID" class="ztree" />
|
||||
<div v-if="treeSetting.treeUrl===''" class="tree-empty">
|
||||
{{ this.$t('common.tree.Empty') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="treebox">
|
||||
<ul v-show="loading" class="ztree">
|
||||
{{ this.$t('common.tree.Loading') }}...
|
||||
</ul>
|
||||
<ul v-show="!loading" :id="iZTreeID" class="ztree" />
|
||||
<div v-if="treeSetting.treeUrl===''" class="tree-empty">
|
||||
{{ this.$t('common.tree.Empty') }}
|
||||
<a id="tree-refresh"><i class="fa fa-refresh" /></a>
|
||||
@@ -46,17 +62,16 @@ import $ from '@/utils/jquery-vendor.js'
|
||||
import '@ztree/ztree_v3/js/jquery.ztree.all.min.js'
|
||||
import '@ztree/ztree_v3/js/jquery.ztree.exhide.min.js'
|
||||
import '@/styles/ztree.css'
|
||||
import '@/styles/ztree_icon.css'
|
||||
import axiosRetry from 'axios-retry'
|
||||
|
||||
const defaultObject = {
|
||||
type: Object,
|
||||
default: () => {
|
||||
}
|
||||
default: () => {}
|
||||
}
|
||||
export default {
|
||||
name: 'ZTree',
|
||||
components: {},
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
setting: defaultObject
|
||||
},
|
||||
@@ -68,7 +83,6 @@ export default {
|
||||
rMenu: '',
|
||||
init: false,
|
||||
loading: false,
|
||||
showTreeSearch: JSON.parse(localStorage.getItem('showTreeSearch')) || false,
|
||||
treeSearchValue: ''
|
||||
}
|
||||
},
|
||||
@@ -79,99 +93,88 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
window.refresh = this.refresh
|
||||
window.onSearch = this.onSearch
|
||||
window.treeSearch = this.treeSearch
|
||||
this.initTree()
|
||||
},
|
||||
beforeDestroy() {
|
||||
$.fn.zTree.destroy(this.iZTreeID)
|
||||
},
|
||||
methods: {
|
||||
async initTree(refresh = false) {
|
||||
initTree: function() {
|
||||
const vm = this
|
||||
let treeUrl
|
||||
this.loading = true
|
||||
if (refresh && this.treeSetting.treeUrl.indexOf('/perms/') !== -1 &&
|
||||
this.treeSetting.treeUrl.indexOf('rebuild_tree') === -1
|
||||
) {
|
||||
treeUrl = (this.treeSetting.treeUrl.indexOf('?') === -1)
|
||||
? `${this.treeSetting.treeUrl}?rebuild_tree=1`
|
||||
: `${this.treeSetting.treeUrl}&rebuild_tree=1`
|
||||
if (this.init) {
|
||||
this.loading = true
|
||||
}
|
||||
if (this.init && this.treeSetting.treeUrl.indexOf('/perms/') !== -1 && this.treeSetting.treeUrl.indexOf('rebuild_tree') === -1) {
|
||||
treeUrl = (this.treeSetting.treeUrl.indexOf('?') === -1) ? `${this.treeSetting.treeUrl}?rebuild_tree=1` : `${this.treeSetting.treeUrl}&rebuild_tree=1`
|
||||
} else {
|
||||
treeUrl = this.treeSetting.treeUrl
|
||||
}
|
||||
|
||||
if (refresh) {
|
||||
$.fn.zTree.destroy(this.iZTreeID)
|
||||
}
|
||||
|
||||
let res = await this.$axios.get(treeUrl, {
|
||||
this.$axios.get(treeUrl, {
|
||||
'axios-retry': {
|
||||
retries: 20,
|
||||
retryCondition: e => {
|
||||
return axiosRetry.isNetworkOrIdempotentRequestError(e) || e.response.status === 409
|
||||
},
|
||||
shouldResetTimeout: true,
|
||||
retryDelay: () => {
|
||||
return 5000
|
||||
}
|
||||
retryDelay: () => { return 5000 }
|
||||
}
|
||||
}).then(res => {
|
||||
if (!res) res = []
|
||||
if (res.length === 0) {
|
||||
res.push({
|
||||
name: this.$t('common.tree.Empty')
|
||||
})
|
||||
}
|
||||
this.treeSetting.treeUrl = treeUrl
|
||||
if (this.init) {
|
||||
vm.zTree.destroy()
|
||||
}
|
||||
})
|
||||
vm.loading = false
|
||||
if (!res) res = []
|
||||
if (res?.length === 0) {
|
||||
res?.push({
|
||||
name: this.$t('common.tree.Empty')
|
||||
})
|
||||
}
|
||||
this.treeSetting.treeUrl = treeUrl
|
||||
vm.zTree = $.fn.zTree.init($(`#${this.iZTreeID}`), this.treeSetting, res)
|
||||
const rootNode = this.zTree.getNodes()[0]
|
||||
this.rootNodeAddDom(rootNode)
|
||||
// 手动上报事件, Tree加载完成
|
||||
this.$emit('TreeInitFinish', this.zTree)
|
||||
|
||||
if (this.treeSetting.showMenu) {
|
||||
this.rMenu = $(`#${this.iRMenuID}`)
|
||||
}
|
||||
if (this.treeSetting?.otherMenu) {
|
||||
$('.menu-actions').append(this.otherMenu)
|
||||
}
|
||||
this.zTree = $.fn.zTree.init($(`#${this.iZTreeID}`), this.treeSetting, res)
|
||||
if (!this.treeSetting.customTreeHeader) {
|
||||
this.rootNodeAddDom(this.zTree)
|
||||
}
|
||||
// 手动上报事件, Tree加载完成
|
||||
this.$emit('TreeInitFinish', this.zTree)
|
||||
|
||||
if (this.treeSetting.showMenu) {
|
||||
this.rMenu = $(`#${this.iRMenuID}`)
|
||||
}
|
||||
if (this.treeSetting.otherMenu) {
|
||||
$('.menu-actions').append(this.otherMenu)
|
||||
}
|
||||
}).finally(_ => {
|
||||
vm.loading = false
|
||||
vm.init = true
|
||||
})
|
||||
},
|
||||
onSearch() {
|
||||
this.showTreeSearch = !this.showTreeSearch
|
||||
localStorage.setItem('showTreeSearch', JSON.stringify(this.showTreeSearch))
|
||||
},
|
||||
onClose() {
|
||||
this.refresh()
|
||||
this.onSearch()
|
||||
},
|
||||
rootNodeAddDom(rootNode) {
|
||||
rootNodeAddDom(ztree) {
|
||||
const { showSearch, showRefresh } = this.treeSetting
|
||||
const searchIcon = `
|
||||
<a class="tree-action-btn" id='search-btn' onclick="onSearch()">
|
||||
<i class='fa fa-search tree-banner-icon'></i>
|
||||
</a>`
|
||||
const refreshIcon = `
|
||||
<a id='tree-refresh' class="tree-action-btn" onclick='refresh()'>
|
||||
<i class='fa fa-refresh'></i>
|
||||
</a>`
|
||||
const searchIcon = `<a class="tree-search" id="searchIcon">
|
||||
<i class='fa fa-search tree-banner-icon' onclick="treeSearch()" /></i>
|
||||
<input type="text" autocomplete="off" id="searchInput" class="tree-input" />
|
||||
</a>`
|
||||
const refreshIcon = "<a id='tree-refresh' onclick='refresh()'><i class='fa fa-refresh'></i></a>"
|
||||
const treeActions = `${showSearch ? searchIcon : ''}${showRefresh ? refreshIcon : ''}`
|
||||
const icons = `
|
||||
<span style="float: right; margin-right: 10px">
|
||||
${treeActions}
|
||||
</span>`
|
||||
const icons = `<span class="">${treeActions}</span>`
|
||||
const rootNode = ztree.getNodes()[0]
|
||||
if (rootNode) {
|
||||
const $rootNodeRef = $('#' + rootNode.tId + '_a')
|
||||
$rootNodeRef.after(icons)
|
||||
}
|
||||
},
|
||||
async refresh() {
|
||||
refresh() {
|
||||
this.treeSearchValue = ''
|
||||
if (this.treeSetting?.callback?.refresh) {
|
||||
await this.treeSetting.callback.refresh()
|
||||
const result = this.treeSetting?.callback?.refresh()
|
||||
if (result && result.then) {
|
||||
result.finally(() => {
|
||||
this.initTree()
|
||||
})
|
||||
} else {
|
||||
this.initTree()
|
||||
}
|
||||
this.zTree.destroy()
|
||||
setTimeout(() => this.initTree(true), 200)
|
||||
},
|
||||
treeSearch() {
|
||||
const searchIcon = document.getElementById(`searchIcon`)
|
||||
@@ -187,15 +190,16 @@ export default {
|
||||
searchIcon.classList.toggle('active')
|
||||
}
|
||||
}
|
||||
searchInput.oninput = e => this.treeSearchHandle((e.target.value || ''))
|
||||
searchInput.oninput = _.debounce((e) => {
|
||||
e.stopPropagation()
|
||||
const value = e.target.value || ''
|
||||
if (this.treeSetting.async.enable) {
|
||||
this.filterAssetsServer(value)
|
||||
} else {
|
||||
this.filterTree(value)
|
||||
}
|
||||
}, 600)
|
||||
},
|
||||
treeSearchHandle: _.debounce(function(value) {
|
||||
if (this.treeSetting.async.enable) {
|
||||
this.filterAssetsServer(value)
|
||||
} else {
|
||||
this.filterTree(value)
|
||||
}
|
||||
}, 600),
|
||||
getCheckedNodes: function() {
|
||||
return this.zTree.getCheckedNodes(true)
|
||||
},
|
||||
@@ -319,10 +323,8 @@ export default {
|
||||
const newNode = { id: 'search', name: name, isParent: true, open: true, zAsync: true }
|
||||
searchNode = this.zTree.addNodes(null, newNode)[0]
|
||||
searchNode.zAsync = true
|
||||
this.rootNodeAddDom(searchNode)
|
||||
|
||||
const nodesGroupByOrg = this.groupBy(nodes, (node) => {
|
||||
return node.meta?.data?.org_name
|
||||
return node.meta.data.org_name
|
||||
})
|
||||
|
||||
for (const item of nodesGroupByOrg) {
|
||||
@@ -330,6 +332,7 @@ export default {
|
||||
}
|
||||
searchNode.open = true
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,13 +340,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-track:horizontal {
|
||||
background: #FFFFFF;
|
||||
border-radius: 10px;
|
||||
}
|
||||
div.rMenu {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
@@ -357,18 +353,15 @@ export default {
|
||||
list-style: none;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.dataTables_wrapper .dataTables_processing {
|
||||
opacity: .9;
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.rMenu li {
|
||||
div.rMenu li{
|
||||
margin: 6px 0;
|
||||
cursor: pointer;
|
||||
list-style: none outside none;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
border: medium none;
|
||||
min-width: 160px;
|
||||
@@ -386,15 +379,12 @@ export default {
|
||||
top: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.ztree ::v-deep .fa {
|
||||
font: normal normal normal 14px/1 FontAwesome !important;
|
||||
}
|
||||
|
||||
.dropdown a:hover {
|
||||
background-color: #f1f1f1
|
||||
}
|
||||
|
||||
.dropdown-menu > li > a {
|
||||
border-radius: 3px;
|
||||
color: inherit;
|
||||
@@ -407,50 +397,27 @@ export default {
|
||||
clear: both;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus {
|
||||
.dropdown-menu>li>a:hover, .dropdown-menu>li>a:focus {
|
||||
color: #262626;
|
||||
text-decoration: none;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.treebox {
|
||||
background-color: transparent;
|
||||
|
||||
>>> .ztree {
|
||||
overflow: auto;
|
||||
background-color: transparent;
|
||||
height: calc(100vh - 237px);
|
||||
|
||||
li {
|
||||
background-color: transparent !important;
|
||||
|
||||
.button {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
ul {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
height: 80vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
::v-deep #tree-refresh {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
::v-deep .tree-banner-icon-zone {
|
||||
position: absolute;
|
||||
right: 7px;
|
||||
height: 30px;
|
||||
overflow: hidden;
|
||||
|
||||
.fa {
|
||||
color: #838385 !important;;
|
||||
|
||||
color: #838385!important;;
|
||||
&:hover {
|
||||
color: #606266 !important;;
|
||||
color: #606266!important;;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,11 +432,9 @@ export default {
|
||||
vertical-align: sub;
|
||||
transition: .25s;
|
||||
overflow: hidden;
|
||||
|
||||
.fa {
|
||||
width: 13px !important;
|
||||
width: 13px!important;
|
||||
}
|
||||
|
||||
.fa-search {
|
||||
padding-top: 1px;
|
||||
}
|
||||
@@ -477,20 +442,23 @@ export default {
|
||||
|
||||
::v-deep .tree-search .tree-banner-icon {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
top: 1px;
|
||||
left: 6px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 12px;
|
||||
padding: 10px 6px;
|
||||
overflow: hidden;
|
||||
background-color: transparent !important;
|
||||
background-color: transparent!important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::v-deep .tree-search.active {
|
||||
::v-deep .tree-search.active {
|
||||
width: 160px;
|
||||
background-color: #ffffff !important;
|
||||
background-color: #ffffff!important;
|
||||
}
|
||||
|
||||
::v-deep .tree-search.active:hover {
|
||||
@@ -502,7 +470,7 @@ export default {
|
||||
left: 20px;
|
||||
width: 133px;
|
||||
height: 100%;
|
||||
background-color: #ffffff !important;
|
||||
background-color: #ffffff!important;
|
||||
color: #606266;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -510,14 +478,11 @@ export default {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.tree-header {
|
||||
position: relative;
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.content {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
@@ -528,67 +493,19 @@ export default {
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background-color: #D7D8DC;
|
||||
|
||||
.rotate {
|
||||
transition: all .1 .8s;
|
||||
transition: all .1.8s;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.fa-caret-down {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.special {
|
||||
top: 1px !important;
|
||||
top: 1px!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tree-empty {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.fixed-tree-search {
|
||||
margin-bottom: 10px;
|
||||
|
||||
& > > > .el-input__inner {
|
||||
border-radius: 4px;
|
||||
background: #fafafa;
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
& > > > .el-input__suffix {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
& > > > .el-input__prefix {
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
& > > > .el-input__suffix-inner {
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-refresh {
|
||||
border-radius: 4px;
|
||||
padding: 0 1px;
|
||||
z-index: 1;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
border-color: #d2d2d2;
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tree-action-btn {
|
||||
padding: 0 2px;
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script type="text/jsx">
|
||||
import { toSafeLocalDateStr } from '@/utils/common'
|
||||
|
||||
export default {
|
||||
name: 'ItemValue',
|
||||
props: {
|
||||
@@ -17,60 +15,27 @@ export default {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
displayValue() {
|
||||
if ([null, undefined, ''].includes(this.value)) {
|
||||
return '-'
|
||||
}
|
||||
if (typeof this.value === 'boolean') {
|
||||
return this.toChoicesDisplay(this.value)
|
||||
} else if (typeof this.value === 'object') {
|
||||
return this.value
|
||||
} else if (this.value instanceof Array) {
|
||||
return this.value.map(item => {
|
||||
if (typeof item === 'object') {
|
||||
return item.label || item.title
|
||||
} else {
|
||||
return item
|
||||
}
|
||||
}).join(', ')
|
||||
} else if (this.isDatetime(this.value)) {
|
||||
return toSafeLocalDateStr(this.value)
|
||||
} else {
|
||||
return this.value
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toChoicesDisplay(value) {
|
||||
if (!value) {
|
||||
return this.$t('common.No')
|
||||
}
|
||||
return this.$t('common.Yes')
|
||||
},
|
||||
isDatetime(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return false
|
||||
}
|
||||
if (value.split(' ').length !== 3) {
|
||||
return false
|
||||
}
|
||||
if (value.split(' ')[1].split(':').length !== 3) {
|
||||
return false
|
||||
}
|
||||
if (isNaN(value) && !isNaN(Date.parse(value))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
},
|
||||
render(h) {
|
||||
if (typeof this.formatter === 'function') {
|
||||
return this.formatter(this.item, this.value)
|
||||
}
|
||||
if (typeof this.value === 'boolean') {
|
||||
return (
|
||||
<span class='item-value'>{this.toChoicesDisplay(this.value)}</span>
|
||||
)
|
||||
}
|
||||
if (this.value instanceof Array) {
|
||||
const newArr = this.value || []
|
||||
return (
|
||||
<span>
|
||||
<span class='item-value'>
|
||||
{
|
||||
newArr.map((item, index) => <div key={index}>{item.key}:{item.value} </div>)
|
||||
}
|
||||
@@ -78,14 +43,15 @@ export default {
|
||||
)
|
||||
}
|
||||
return (
|
||||
<span>{this.displayValue}</span>
|
||||
<span class='item-value'>{this.value}</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
a {
|
||||
color: var(--color-success);
|
||||
.item-value {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
<template>
|
||||
<DetailCard v-if="!loading && hasObject" :items="items" v-bind="$attrs" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DetailCard from './index'
|
||||
import { copy, toSafeLocalDateStr } from '@/utils/common'
|
||||
|
||||
export default {
|
||||
name: 'AutoDetailCard',
|
||||
components: { DetailCard },
|
||||
props: {
|
||||
object: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
fields: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
excludes: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
showUndefine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
formatters: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
nested: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
items: [],
|
||||
loading: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iObject() {
|
||||
if (this.nested) {
|
||||
return this.object[this.nested] || {}
|
||||
} else {
|
||||
return this.object
|
||||
}
|
||||
},
|
||||
hasObject() {
|
||||
return Object.keys(this.iObject).length > 0
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.optionAndGenFields()
|
||||
this.loading = false
|
||||
},
|
||||
methods: {
|
||||
defaultFormatter(fields) {
|
||||
const formatter = {}
|
||||
for (const name of fields) {
|
||||
formatter[name] = function(item, val) {
|
||||
if (val === '-') {
|
||||
return <span>{'-'}</span>
|
||||
}
|
||||
return (<span style={{ cursor: 'pointer' }} onClick={() => copy(val)}>
|
||||
{val}
|
||||
</span>)
|
||||
}
|
||||
}
|
||||
return formatter
|
||||
},
|
||||
async optionAndGenFields() {
|
||||
const data = await this.$store.dispatch('common/getUrlMeta', { url: this.url })
|
||||
let remoteMeta = data.actions['GET'] || {}
|
||||
if (this.nested) {
|
||||
remoteMeta = remoteMeta[this.nested]?.children || {}
|
||||
}
|
||||
let fields = this.fields
|
||||
fields = fields || Object.keys(remoteMeta)
|
||||
const defaultExcludes = ['org_id']
|
||||
const excludes = (this.excludes || []).concat(defaultExcludes)
|
||||
fields = fields.filter(item => !excludes.includes(item))
|
||||
const defaultFormatter = this.defaultFormatter(fields)
|
||||
for (const name of fields) {
|
||||
if (typeof name === 'object') {
|
||||
this.items.push(name)
|
||||
continue
|
||||
}
|
||||
const fieldMeta = remoteMeta[name]
|
||||
if (!fieldMeta) {
|
||||
continue
|
||||
}
|
||||
if (fieldMeta['write_only']) {
|
||||
continue
|
||||
}
|
||||
|
||||
let value = this.iObject[name]
|
||||
const label = fieldMeta.label
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
if (typeof value[0] === 'object') {
|
||||
value.forEach(item => {
|
||||
const fieldName = `${name}.${item.name}`
|
||||
if (excludes.includes(fieldName)) {
|
||||
return
|
||||
}
|
||||
this.items.push({
|
||||
key: item.label,
|
||||
value: item.value
|
||||
})
|
||||
})
|
||||
} else if (typeof value[0] === 'string') {
|
||||
value.forEach((item, index) => {
|
||||
let data = {}
|
||||
if (index === 0) {
|
||||
data = {
|
||||
key: label,
|
||||
value: value[index]
|
||||
}
|
||||
} else {
|
||||
data = {
|
||||
value: value[index]
|
||||
}
|
||||
}
|
||||
this.items.push(data)
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (value === null || value === '') {
|
||||
value = '-'
|
||||
} else if (fieldMeta.type === 'datetime') {
|
||||
value = toSafeLocalDateStr(value)
|
||||
} else if (fieldMeta.type === 'labeled_choice') {
|
||||
value = value?.['label']
|
||||
} else if (fieldMeta.type === 'related_field' || fieldMeta.type === 'nested object') {
|
||||
value = value?.['name']
|
||||
} else if (fieldMeta.type === 'm2m_related_field') {
|
||||
value = value?.map(item => item['name']).join(', ')
|
||||
} else if (fieldMeta.type === 'boolean') {
|
||||
value = value ? this.$t('common.Yes') : this.$t('common.No')
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
if (this.showUndefine) {
|
||||
value = '-'
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const item = {
|
||||
key: label,
|
||||
value: value,
|
||||
formatter: this.formatters[name] || defaultFormatter[name]
|
||||
}
|
||||
this.items.push(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,11 +1,22 @@
|
||||
<template>
|
||||
<IBox :title="title" :fa="fa">
|
||||
<el-form class="content" label-position="left" label-width="25%">
|
||||
<el-form-item v-for="item in items" :key="item.key" :label="item.key">
|
||||
<ItemValue class="item-value" :value="item.value" v-bind="item" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<slot />
|
||||
<IBox :title="title" fa="fa-info-circle">
|
||||
<div class="content">
|
||||
<el-row v-if="this.$route.params.id" :gutter="10" class="item">
|
||||
<el-col :span="6"><div :style="{ 'text-align': align }" class="item-label"><label>ID: </label></div></el-col>
|
||||
<el-col :span="18"><div class="item-text">{{ this.$route.params.id }}</div></el-col>
|
||||
</el-row>
|
||||
<el-row v-for="item in items" :key="'card-' + item.key" :gutter="10" class="item">
|
||||
<el-col :span="6">
|
||||
<div :style="{ 'text-align': align }" class="item-label"><label>{{ item.key }}: </label></div>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<div class="item-text">
|
||||
<ItemValue :value="item.value" v-bind="item" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<slot />
|
||||
</div>
|
||||
</IBox>
|
||||
</template>
|
||||
|
||||
@@ -23,10 +34,6 @@ export default {
|
||||
return this.$t('common.BasicInfo')
|
||||
}
|
||||
},
|
||||
fa: {
|
||||
type: String,
|
||||
default: 'fa-info-circle'
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
@@ -39,42 +46,7 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-card__body {
|
||||
padding: 20px 40px;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
border-bottom: 1px dashed #EBEEF5;
|
||||
padding: 1px 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
}
|
||||
|
||||
>>> .el-form-item__label {
|
||||
padding-right: 8%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
>>> .el-form-item__content {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
>>> .el-tag--mini {
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.item-value span {
|
||||
word-break: break-word;
|
||||
}
|
||||
<style scoped>
|
||||
.content {
|
||||
font-size: 13px;
|
||||
line-height: 2.5;
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-if="detailVisible"
|
||||
:show-cancel="false"
|
||||
:show-confirm="false"
|
||||
:title="title"
|
||||
:visible.sync="detailVisible"
|
||||
>
|
||||
<div>
|
||||
<div v-if="isEmpty()" style="text-align: center">
|
||||
{{ this.$tc('common.NoContent') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-table
|
||||
:data="diff"
|
||||
class="diffTable"
|
||||
>
|
||||
<el-table-column
|
||||
:label="$tc('audits.ChangeField')"
|
||||
:prop="fieldName"
|
||||
show-overflow-tooltip
|
||||
width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
:label="$tc('audits.BeforeChange')"
|
||||
:prop="leftKeyName"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
:label="$tc('audits.AfterChange')"
|
||||
:prop="rightKeyName"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog/index'
|
||||
|
||||
export default {
|
||||
name: 'DiffDetail',
|
||||
components: {
|
||||
Dialog
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
fieldName: {
|
||||
type: String,
|
||||
default: () => 'field'
|
||||
},
|
||||
leftKeyName: {
|
||||
type: String,
|
||||
default: () => 'before'
|
||||
},
|
||||
rightKeyName: {
|
||||
type: String,
|
||||
default: () => 'after'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
diff: [],
|
||||
detailVisible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isEmpty() {
|
||||
const content = this.diff
|
||||
return !content || JSON.stringify(content) === '{}'
|
||||
},
|
||||
show(data) {
|
||||
this.diff = data
|
||||
this.detailVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.el-tag {
|
||||
width: 100%;
|
||||
white-space: normal;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.el-table::before {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.diffTable {
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
|
||||
& >>> td {
|
||||
padding: 5px 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,21 +1,19 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:append-to-body="true"
|
||||
:modal-append-to-body="true"
|
||||
:title="title"
|
||||
:top="top"
|
||||
:width="iWidth"
|
||||
class="dialog"
|
||||
v-bind="$attrs"
|
||||
:append-to-body="false"
|
||||
:modal-append-to-body="false"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<slot />
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<slot name="footer">
|
||||
<el-button v-if="showCancel" @click="onCancel">{{ cancelTitle }}</el-button>
|
||||
<el-button v-if="showConfirm" :loading="loadingStatus" type="primary" @click="onConfirm">
|
||||
{{ confirmTitle }}
|
||||
</el-button>
|
||||
<el-button v-if="showCancel" size="small" @click="onCancel">{{ cancelTitle }}</el-button>
|
||||
<el-button v-if="showConfirm" type="primary" size="small" :loading="loadingStatus" @click="onConfirm">{{ confirmTitle }}</el-button>
|
||||
</slot>
|
||||
</div>
|
||||
</el-dialog>
|
||||
@@ -60,14 +58,11 @@ export default {
|
||||
default() {
|
||||
return this.$t('common.Confirm')
|
||||
}
|
||||
},
|
||||
maxWidth: {
|
||||
type: String,
|
||||
default: '1200px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iWidth() {
|
||||
@@ -86,34 +81,12 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dialog >>> .el-dialog {
|
||||
border-radius: 0.3em;
|
||||
max-width: 1500px;
|
||||
|
||||
&__header {
|
||||
box-sizing: border-box;
|
||||
padding: 15px 22px;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 20px 30px;
|
||||
|
||||
&:has(.el-table) {
|
||||
background: #f3f3f4;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
border-top: 1px solid #dee2e6;
|
||||
padding: 16px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.dialog >>> .el-dialog__header {
|
||||
/*padding-top: 10px;*/
|
||||
}
|
||||
|
||||
.dialog-footer >>> button.el-button {
|
||||
font-size: 13px;
|
||||
padding: 10px 20px;
|
||||
.dialog-footer {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<el-tree
|
||||
:data="iTree"
|
||||
show-checkbox
|
||||
node-key="value"
|
||||
:default-expand-all="true"
|
||||
:default-expanded-keys="iValue"
|
||||
:default-checked-keys="iValue"
|
||||
:props="defaultProps"
|
||||
@check="handleCheckChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
tree: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultProps: {
|
||||
children: 'children',
|
||||
label: 'label'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iValue() {
|
||||
return this.value.map(item => {
|
||||
if (item.value) {
|
||||
return item.value
|
||||
}
|
||||
return item
|
||||
})
|
||||
},
|
||||
iTree() {
|
||||
if (!this.readonly) {
|
||||
return this.tree
|
||||
} else {
|
||||
return this.setTreeReadonly(this.tree)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCheckChange(node, { checkedNodes }) {
|
||||
const checkedKeys = checkedNodes
|
||||
.filter(item => !item.children)
|
||||
.map(node => node.value)
|
||||
this.$emit('input', checkedKeys)
|
||||
},
|
||||
setTreeReadonly(tree) {
|
||||
return tree.map(item => {
|
||||
item.disabled = true
|
||||
if (item.children) {
|
||||
item.children = this.setTreeReadonly(item.children)
|
||||
}
|
||||
return item
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,282 +0,0 @@
|
||||
<template>
|
||||
<div class="code-editor" style="font-size: 12px">
|
||||
<div class="toolbar">
|
||||
<div
|
||||
v-for="(item,index) in toolbar.left"
|
||||
:key="index"
|
||||
style="display: inline-block; margin: 0 2px"
|
||||
>
|
||||
<el-tooltip :content="item.tip" :disabled="!item.tip" placement="top">
|
||||
<el-button
|
||||
v-if="item.type ==='button'"
|
||||
:disabled="item.disabled"
|
||||
:type="item.el&&item.el.type"
|
||||
size="mini"
|
||||
@click="item.callback()"
|
||||
>
|
||||
<i :class="item.icon" style="margin-right: 4px;" />{{ item.name }}
|
||||
</el-button>
|
||||
|
||||
<el-autocomplete
|
||||
v-if="item.type === 'input' && item.el.autoComplete"
|
||||
v-model="item.value"
|
||||
:fetch-suggestions="item.el.query"
|
||||
class="inline-input"
|
||||
size="mini"
|
||||
@select="item.callback(item.value)"
|
||||
@change="item.callback(item.value)"
|
||||
/>
|
||||
|
||||
<div v-if="item.type==='select' && item.el && item.el.create" class="select-content">
|
||||
<span class="filter-label">
|
||||
{{ item.name }}:
|
||||
</span>
|
||||
<el-select
|
||||
v-if="item.type==='select' && item.el && item.el.create"
|
||||
:key="index"
|
||||
v-model="item.value"
|
||||
:allow-create="item.el.create || false"
|
||||
:filterable="item.el.create || false"
|
||||
:multiple="item.el.multiple"
|
||||
:placeholder="item.name"
|
||||
class="autoWidth-select"
|
||||
default-first-option
|
||||
size="mini"
|
||||
@change="item.callback(item.value)"
|
||||
>
|
||||
<template slot="prefix">
|
||||
{{ item.label + ':' + item.value }}
|
||||
</template>
|
||||
<el-option
|
||||
v-for="(option,id) in item.options"
|
||||
:key="id"
|
||||
:label="option.label"
|
||||
:title="option.value"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<el-dropdown
|
||||
v-if="item.type==='select' && (!item.el || !item.el.create) "
|
||||
trigger="click"
|
||||
@command="(command) => {
|
||||
item.value= command
|
||||
item.callback(command)
|
||||
}"
|
||||
>
|
||||
<el-button size="mini" type="default">
|
||||
<b>{{ item.name }}:</b> {{ getLabel(item.value, item.options) }} <i
|
||||
class="el-icon-arrow-down el-icon--right"
|
||||
/>
|
||||
</el-button>
|
||||
<el-dropdown-menu v-slot="dropdown">
|
||||
<el-dropdown-item v-for="(option,i) in item.options" :key="i" :command="option.value">
|
||||
{{ option.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
<el-switch
|
||||
v-if="item.type === 'switch'"
|
||||
v-model="item.value"
|
||||
:active-text="item.name"
|
||||
:disabled="item.disabled"
|
||||
@change="item.callback( item.value)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="right-side" style="float: right">
|
||||
<div
|
||||
v-for="(item,index) in toolbar.right"
|
||||
:key="index"
|
||||
style="display: inline-block"
|
||||
>
|
||||
<el-tooltip :content="item.tip">
|
||||
<el-button
|
||||
v-if="item.type ==='button'"
|
||||
:disabled="item.disabled"
|
||||
size="mini"
|
||||
style="background-color: transparent"
|
||||
type="default"
|
||||
@click="item.callback()"
|
||||
>
|
||||
<i v-if="item.icon.startsWith('fa')" :class="'fa ' + item.icon" />
|
||||
<svg-icon v-else :icon-class="item.icon" style="font-size: 14px;" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<codemirror ref="myCm" v-model="iValue" :options="iOptions" class="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { codemirror } from 'vue-codemirror'
|
||||
|
||||
import 'codemirror/mode/shell/shell'
|
||||
import 'codemirror/mode/powershell/powershell'
|
||||
import 'codemirror/mode/python/python'
|
||||
import 'codemirror/mode/yaml/yaml'
|
||||
import 'codemirror/mode/ruby/ruby' // theme css
|
||||
import 'codemirror/theme/base16-light.css'
|
||||
import 'codemirror/theme/idea.css'
|
||||
import 'codemirror/theme/mbo.css'
|
||||
import 'codemirror/theme/duotone-light.css'
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
codemirror
|
||||
},
|
||||
props: {
|
||||
toolbar: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
},
|
||||
value: {
|
||||
type: [String, Object],
|
||||
default: () => ''
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
iValue: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:value', val)
|
||||
this.$emit('change', val)
|
||||
}
|
||||
},
|
||||
iOptions() {
|
||||
const defaultOptions = {
|
||||
tabSize: 4,
|
||||
mode: 'shell',
|
||||
lineNumbers: true,
|
||||
theme: 'idea',
|
||||
placeholder: 'Code goes here...',
|
||||
autofocus: true
|
||||
}
|
||||
return Object.assign(defaultOptions, this.options)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getLabel(value, items) {
|
||||
for (const item of items) {
|
||||
if (item.value === value) {
|
||||
return item.label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.editor {
|
||||
border: solid 1px #f3f3f3;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
line-height: 29px;
|
||||
vertical-align: bottom;
|
||||
display: inline-block;
|
||||
padding: 3px 3px 3px 0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
> > > .CodeMirror pre.CodeMirror-line,
|
||||
> > > .CodeMirror-linenumber.CodeMirror-gutter-elt {
|
||||
line-height: 18px !important;
|
||||
}
|
||||
|
||||
.runas-input {
|
||||
height: 28px;
|
||||
|
||||
> > > {
|
||||
.el-select {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-side {
|
||||
.el-button {
|
||||
border: none;
|
||||
padding: 2px;
|
||||
font-size: 14px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
color: #888;
|
||||
background-color: transparent;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.autoWidth-select {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.autoWidth-select > > > .el-input__prefix {
|
||||
position: relative;
|
||||
left: 0px;
|
||||
box-sizing: border-box;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.autoWidth-select > > > input {
|
||||
position: absolute;
|
||||
padding-left: 0px;
|
||||
border: none;
|
||||
color: #606266;
|
||||
background-color: #e6e6e6;
|
||||
font-size: 12px;
|
||||
font-weight: 470;
|
||||
line-height: 27px;
|
||||
}
|
||||
|
||||
> > > .el-select {
|
||||
top: -1px;
|
||||
|
||||
.el-input .el-select__caret {
|
||||
color: #7a7c7f;
|
||||
}
|
||||
}
|
||||
|
||||
> > > .el-button.el-button--default {
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.select-content {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
padding-left: 15px;
|
||||
font-size: 0;
|
||||
border: 1px solid #DCDFE6;
|
||||
border-radius: 4px;
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
</style>
|
||||
@@ -2,9 +2,9 @@
|
||||
<el-date-picker
|
||||
v-model="value"
|
||||
type="datetimerange"
|
||||
:range-separator="$tc('common.To')"
|
||||
:start-placeholder="$tc('common.DateStart')"
|
||||
:end-placeholder="$tc('common.DateEnd')"
|
||||
:range-separator="this.$t('common.To')"
|
||||
:start-placeholder="this.$t('common.DateStart')"
|
||||
:end-placeholder="this.$t('common.DateEnd')"
|
||||
size="small"
|
||||
:clearable="false"
|
||||
class="datepicker"
|
||||
@@ -92,26 +92,22 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.datepicker {
|
||||
.datepicker{
|
||||
width: 233px;
|
||||
|
||||
& >>> .el-range__icon {
|
||||
&>>> .el-range__icon {
|
||||
margin-top: 2px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
& >>> .el-range-input {
|
||||
&>>> .el-range-input {
|
||||
width: 49%;
|
||||
}
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
.el-input__inner{
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 3px;
|
||||
height: 32x;
|
||||
}
|
||||
|
||||
.el-date-editor ::v-deep .el-range-separator {
|
||||
.el-date-editor ::v-deep .el-range-separator{
|
||||
line-height: 28px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(command, index) in value" :key="index" :prop="'value.' + index + '.value'" class="command-item">
|
||||
<el-input v-model="value[index]" size="mini">
|
||||
<template slot="prepend"> {{ inputTitle + ' ' + (index + 1) }}</template>
|
||||
</el-input>
|
||||
<div class="input-button">
|
||||
<el-button
|
||||
:disabled="deleteDisabled()"
|
||||
icon="el-icon-minus"
|
||||
size="mini"
|
||||
style="flex-shrink: 0;"
|
||||
type="danger"
|
||||
@click="handleDelete(command)"
|
||||
/>
|
||||
<el-button
|
||||
v-if="index === value.length - 1"
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
style="flex-shrink: 0;"
|
||||
type="primary"
|
||||
@click="handleAdd()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => ['']
|
||||
},
|
||||
inputTitle: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
handleDelete(command) {
|
||||
const index = this.value.indexOf(command)
|
||||
if (index !== -1) {
|
||||
this.value.splice(index, 1)
|
||||
}
|
||||
},
|
||||
handleAdd() {
|
||||
this.value.push('')
|
||||
},
|
||||
deleteDisabled() {
|
||||
return this.value.length <= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-input {
|
||||
width: 85%;
|
||||
}
|
||||
.command-item {
|
||||
display: flex;
|
||||
margin: 5px 0;
|
||||
}
|
||||
.input-button {
|
||||
margin-top: 2px;
|
||||
display: flex;
|
||||
margin-left: 20px
|
||||
}
|
||||
.input-button ::v-deep .el-button.el-button--mini {
|
||||
height: 25px;
|
||||
padding: 5px;
|
||||
}
|
||||
.el-input-group__append .el-button {
|
||||
font-size: 14px;
|
||||
color: #1a1a1a;
|
||||
padding: 9px 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -2,8 +2,8 @@
|
||||
<div class="json-editor">
|
||||
<JsonEditor
|
||||
v-model="resultInfo"
|
||||
:mode="'code'"
|
||||
:show-btns="false"
|
||||
:mode="'code'"
|
||||
@json-change="onJsonChange"
|
||||
@json-save="onJsonSave"
|
||||
@has-error="onError"
|
||||
@@ -18,8 +18,8 @@ export default {
|
||||
components: { JsonEditor },
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Object, Array],
|
||||
default: () => ({})
|
||||
type: String,
|
||||
default: () => ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.resultInfo = typeof this.value === 'string' ? JSON.parse(this.value) : this.value
|
||||
this.resultInfo = JSON.parse(this.value)
|
||||
},
|
||||
methods: {
|
||||
// 数据改变
|
||||
@@ -38,14 +38,14 @@ export default {
|
||||
},
|
||||
// 保存
|
||||
onJsonSave(value) {
|
||||
this.resultInfo = typeof value === 'string' ? JSON.parse(value) : value
|
||||
this.resultInfo = value
|
||||
this.hasJsonFlag = true
|
||||
setTimeout(() => {
|
||||
this.$emit('change', this.resultInfo)
|
||||
this.$emit('change', JSON.stringify(this.resultInfo))
|
||||
}, 500)
|
||||
},
|
||||
onError: _.debounce(function(value) {
|
||||
this.$message.error(this.$tc('common.FormatError'))
|
||||
this.$message.error(this.$t('common.FormatError'))
|
||||
}, 1100)
|
||||
}
|
||||
}
|
||||
@@ -53,25 +53,20 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "~@/styles/variables.scss";
|
||||
|
||||
.json-editor {
|
||||
& > > > .jsoneditor {
|
||||
&>>> .jsoneditor {
|
||||
border: 1px solid #e5e6e7;
|
||||
}
|
||||
|
||||
& > > > .jsoneditor-compact {
|
||||
&>>> .jsoneditor-compact {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& > > > .jsoneditor-modes {
|
||||
&>>> .jsoneditor-modes {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& > > > .jsoneditor-poweredBy {
|
||||
&>>> .jsoneditor-poweredBy {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& > > > .jsoneditor-menu {
|
||||
&>>> .jsoneditor-menu {
|
||||
background: var(--color-primary);
|
||||
border-bottom: 1px solid var(--color-primary);
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(item, index) in value || []" :key="index" class="value-item">
|
||||
<el-input :value="item" class="input-z" @input="updateValue($event, index)" />
|
||||
<div class="input-button">
|
||||
<el-button
|
||||
:disabled="disableDelete(item)"
|
||||
icon="el-icon-minus"
|
||||
size="mini"
|
||||
style="flex-shrink: 0;"
|
||||
type="danger"
|
||||
@click="handleDelete(index)"
|
||||
/>
|
||||
<el-button
|
||||
:disabled="disableAdd(item, index)"
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
style="flex-shrink: 0;"
|
||||
type="primary"
|
||||
@click="handleAdd(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ListField',
|
||||
props: {
|
||||
value: {
|
||||
type: [Array, String],
|
||||
default: () => ([])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
mounted() {
|
||||
const value = this.value
|
||||
if (!value || !Array.isArray(value) || value.length === 0) {
|
||||
this.$emit('input', [''])
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValue(v, index) {
|
||||
const value = this.value
|
||||
value[index] = v
|
||||
this.$emit('input', value)
|
||||
},
|
||||
disableDelete() {
|
||||
return false
|
||||
},
|
||||
disableAdd() {
|
||||
return false
|
||||
},
|
||||
handleAdd(index) {
|
||||
const value = this.value
|
||||
value.splice(index + 1, 0, '')
|
||||
this.$emit('input', value)
|
||||
},
|
||||
handleDelete(index) {
|
||||
const value = this.value
|
||||
value.splice(index, 1)
|
||||
this.$emit('input', value)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.input-z {
|
||||
flex-shrink: 1;
|
||||
width: calc(100% - 80px) !important;
|
||||
}
|
||||
|
||||
.value-item {
|
||||
display: flex;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.input-button {
|
||||
display: flex;
|
||||
margin-left: 20px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.input-button ::v-deep .el-button.el-button--mini {
|
||||
height: 25px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,105 +0,0 @@
|
||||
<template>
|
||||
<Select2
|
||||
v-model="iValue"
|
||||
:multiple="multiple"
|
||||
v-bind="attrsWithoutValue"
|
||||
@change="onChange"
|
||||
@change-options="onChangeOptions"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Select2 from './Select2'
|
||||
|
||||
export default {
|
||||
name: 'NestedObjectSelect2',
|
||||
components: {
|
||||
Select2
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [Array, String, Number, Boolean, Object],
|
||||
default: () => ([])
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 自定义label字段的name
|
||||
customLabelKeyName: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
attrsWithoutValue() {
|
||||
const attrs = Object.assign({}, this.$attrs)
|
||||
delete attrs.value
|
||||
return attrs
|
||||
},
|
||||
iValue: {
|
||||
set(val) {
|
||||
const value = this.valuesToObjects(val)
|
||||
this.$log.debug('set iValue', value)
|
||||
this.$emit('input', value)
|
||||
},
|
||||
get() {
|
||||
const value = this.objectsToValues(this.value)
|
||||
return value
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(val) {
|
||||
val = this.valuesToObjects(val)
|
||||
this.$log.debug('onChange .... ', val)
|
||||
this.$emit('change', val)
|
||||
},
|
||||
onChangeOptions(val) {
|
||||
val = this.valuesToObjects(val)
|
||||
this.$log.debug('onChangeOptions', val)
|
||||
this.$emit('changeOptions', val)
|
||||
},
|
||||
valuesToObjects(values) {
|
||||
let value = values
|
||||
if (!this.multiple && !Array.isArray(value)) {
|
||||
value = [value]
|
||||
}
|
||||
value = value.map(v => {
|
||||
// uuid v4
|
||||
const uuid = /^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i
|
||||
return typeof v === 'object' ? v
|
||||
: this.$attrs?.allowCreate && !uuid.test(v) ? { [this.customLabelKeyName]: v } : { pk: v }
|
||||
})
|
||||
if (!this.multiple) {
|
||||
value = value[0]
|
||||
}
|
||||
return value
|
||||
},
|
||||
objectsToValues(objects) {
|
||||
let val = objects
|
||||
if (!this.multiple) {
|
||||
val = [val]
|
||||
}
|
||||
val = val.map((v) => {
|
||||
if (v && typeof v === 'object') {
|
||||
return v.pk || v.id || (this.$attrs?.allowCreate ? (v?.[this.customLabelKeyName] + ':' + v?.value) : '')
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
})
|
||||
if (!this.multiple) {
|
||||
val = val[0]
|
||||
}
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-input v-model="rawValue.phone" :placeholder="$tc('users.inputPhone')" @input="OnInputChange">
|
||||
<el-select
|
||||
slot="prepend"
|
||||
:value="rawValue.code"
|
||||
:placeholder="$tc('common.Select')"
|
||||
style="width: 90px;"
|
||||
@change="OnChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="country in countries"
|
||||
:key="country.value"
|
||||
:label="country.value"
|
||||
:value="country.value"
|
||||
style="width: 200px;"
|
||||
>
|
||||
<span style="float: left">{{ country.name }}</span>
|
||||
<span style="float: right; font-size: 13px">{{ country.value }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'PhoneInput',
|
||||
props: {
|
||||
value: {
|
||||
type: [Object, String],
|
||||
default: () => ({ 'code': '', 'phone': '' })
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rawValue: {},
|
||||
countries: [
|
||||
{ name: 'China(中国)', value: '+86' },
|
||||
{ name: 'HongKong(中国香港)', value: '+852' },
|
||||
{ name: 'Macao(中国澳门)', value: '+853' },
|
||||
{ name: 'Taiwan(中国台湾)', value: '+886' },
|
||||
{ name: 'America(America)', value: '+1' },
|
||||
{ name: 'Russia(Россия)', value: '+7' },
|
||||
{ name: 'France(français)', value: '+33' },
|
||||
{ name: 'Britain(Britain)', value: '+44' },
|
||||
{ name: 'Germany(Deutschland)', value: '+49' },
|
||||
{ name: 'Japan(日本)', value: '+81' },
|
||||
{ name: 'Korea(한국)', value: '+82' },
|
||||
{ name: 'India(भारत)', value: '+91' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fullPhone() {
|
||||
if (!this.rawValue.phone) {
|
||||
return ''
|
||||
}
|
||||
return `${this.rawValue.code} ${this.rawValue.phone}`
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.rawValue = this.value || { code: '+86', phone: '' }
|
||||
this.$emit('input', this.fullPhone)
|
||||
},
|
||||
methods: {
|
||||
OnChange(countryCode) {
|
||||
this.rawValue.code = countryCode
|
||||
this.OnInputChange()
|
||||
},
|
||||
OnInputChange() {
|
||||
this.$emit('input', this.fullPhone)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -1,213 +0,0 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
:show-cancel="false"
|
||||
:show-confirm="false"
|
||||
:title="$tc('assets.PlatformProtocolConfig') + ':' + item.name"
|
||||
class="setting-dialog"
|
||||
v-bind="$attrs"
|
||||
width="70%"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<el-alert v-if="disabled" type="success">
|
||||
{{ $t('assets.InheritPlatformConfig') }}
|
||||
<el-link :href="platformDetail" class="link-more" target="_blank">
|
||||
{{ $t('common.View') }}
|
||||
</el-link>
|
||||
<i class="fa fa-external-link" />
|
||||
</el-alert>
|
||||
<AutoDataForm
|
||||
:disabled="disabled"
|
||||
:form="form"
|
||||
class="data-form"
|
||||
v-bind="config"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { AutoDataForm, Dialog } from '@/components'
|
||||
|
||||
export default {
|
||||
name: 'ProtocolSetting',
|
||||
components: {
|
||||
Dialog,
|
||||
AutoDataForm
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
baseAttrs: ['primary', 'required', 'default'], // 基础属性, 放到 setting 中处理了,处理完成后,还得返回回去
|
||||
defaultSetting: {
|
||||
sftp_enabled: true,
|
||||
sftp_home: '/tmp',
|
||||
username_selector: '#username',
|
||||
password_selector: '#password',
|
||||
submit_selector: '.btn-submit',
|
||||
security: 'any',
|
||||
console: false
|
||||
},
|
||||
loading: true,
|
||||
form: {},
|
||||
platformDetail: '#/console/assets/platforms/' + this.$route.query.platform,
|
||||
config: {
|
||||
hasSaveContinue: false,
|
||||
hasButtons: !this.disabled,
|
||||
url: '',
|
||||
fields: [
|
||||
[this.$t('common.Basic'), [
|
||||
{
|
||||
id: 'primary',
|
||||
label: this.$t('assets.Primary'),
|
||||
type: 'switch',
|
||||
helpText: this.$t('assets.PrimaryProtocol'),
|
||||
on: {
|
||||
change: ([val], updateForm) => {
|
||||
const relatedFields = vm.config['fields'][0][1]
|
||||
.filter(item => this.baseAttrs.includes(item.id))
|
||||
.filter(item => item.id !== 'primary')
|
||||
if (val) {
|
||||
const relatedValue = relatedFields.reduce((acc, cur) => {
|
||||
acc[cur.id] = true
|
||||
return acc
|
||||
}, {})
|
||||
updateForm(relatedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'required',
|
||||
label: this.$t('assets.Required'),
|
||||
type: 'switch',
|
||||
helpText: this.$t('assets.RequiredProtocol'),
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
id: 'default',
|
||||
label: this.$t('assets.Default'),
|
||||
type: 'switch',
|
||||
helpText: this.$t('assets.DefaultProtocol'),
|
||||
disabled: false
|
||||
}
|
||||
]],
|
||||
[this.$t('assets.LoginConfig'), [
|
||||
{
|
||||
id: 'console',
|
||||
label: 'Console',
|
||||
type: 'switch',
|
||||
hidden: () => this.item.name !== 'rdp'
|
||||
},
|
||||
{
|
||||
id: 'security',
|
||||
label: 'Security',
|
||||
hidden: () => this.item.name !== 'rdp',
|
||||
type: 'radio-group',
|
||||
options: [
|
||||
{ label: 'Any', value: 'any' },
|
||||
{ label: 'RDP', value: 'rdp' },
|
||||
{ label: 'NLA', value: 'nla' },
|
||||
{ label: 'TLS', value: 'tls' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'use_ssl',
|
||||
label: this.$t('assets.UseSSL'),
|
||||
type: 'switch',
|
||||
hidden: () => this.item.name !== 'winrm'
|
||||
},
|
||||
{
|
||||
id: 'sftp_enabled',
|
||||
label: this.$t('common.Enable') + ' SFTP',
|
||||
type: 'switch',
|
||||
hidden: () => this.item.name !== 'ssh'
|
||||
},
|
||||
{
|
||||
id: 'sftp_home',
|
||||
label: 'SFTP home',
|
||||
type: 'input',
|
||||
helpText: this.$t('assets.SFTPHelpMessage'),
|
||||
hidden: (form) => this.item.name !== 'ssh' || !form['sftp_enabled']
|
||||
},
|
||||
{
|
||||
id: 'username_selector',
|
||||
label: this.$t('assets.UserNameSelector'),
|
||||
type: 'input',
|
||||
hidden: (form) => this.item.name !== 'http'
|
||||
},
|
||||
{
|
||||
id: 'password_selector',
|
||||
label: this.$t('assets.PasswordSelector'),
|
||||
type: 'input',
|
||||
hidden: (form) => this.item.name !== 'http'
|
||||
},
|
||||
{
|
||||
id: 'submit_selector',
|
||||
label: this.$t('assets.SubmitSelector'),
|
||||
type: 'input',
|
||||
hidden: (form) => this.item.name !== 'http'
|
||||
},
|
||||
{
|
||||
id: 'auth_username',
|
||||
label: this.$t('assets.AuthUsername'),
|
||||
type: 'switch',
|
||||
hidden: (form) => this.item.name !== 'redis'
|
||||
}
|
||||
]]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.form = this.item.setting
|
||||
if (!this.form || !this.item) {
|
||||
return
|
||||
}
|
||||
for (const i of this.baseAttrs) {
|
||||
this.form[i] = !!this.item[i]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit(form) {
|
||||
for (const i of this.baseAttrs) {
|
||||
if (form.hasOwnProperty(i)) {
|
||||
this.item[i] = form[i]
|
||||
}
|
||||
}
|
||||
this.item.setting = form
|
||||
this.$emit('update:visible', false)
|
||||
this.$emit('confirm', this.item)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.data-form > > > .el-form-item.form-buttons {
|
||||
padding-top: 10px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.setting-dialog > > > .el-dialog__body {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.link-more {
|
||||
font-size: 10px;
|
||||
margin-left: 10px;
|
||||
border-bottom: solid 1px;
|
||||
color: inherit;
|
||||
}
|
||||
</style>
|
||||
@@ -1,313 +0,0 @@
|
||||
<template>
|
||||
<div v-if="!loading" :class="showSetting ? 'show-setting' : 'hide-setting'">
|
||||
<div v-for="(item, index) in items" :key="item.name" class="protocol-item">
|
||||
<el-input
|
||||
v-model="item.port"
|
||||
:class="readonly ? '' : 'input-with-select'"
|
||||
:placeholder="portPlaceholder"
|
||||
:readonly="readonly"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<el-select
|
||||
slot="prepend"
|
||||
:disabled="disableSelect(item)"
|
||||
:value="item.display_name ? item.display_name : item.name"
|
||||
class="prepend"
|
||||
@change="handleProtocolChange($event, item)"
|
||||
>
|
||||
<el-option
|
||||
v-for="p of remainProtocols"
|
||||
:key="p.name"
|
||||
:label="p.name"
|
||||
:value="p.name"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button
|
||||
v-if="showSetting(item)"
|
||||
slot="append"
|
||||
icon="el-icon-setting"
|
||||
@click="onSettingClick(item)"
|
||||
/>
|
||||
</el-input>
|
||||
<div v-if="!readonly" class="input-button">
|
||||
<el-button
|
||||
:disabled="disableDelete(item)"
|
||||
icon="el-icon-minus"
|
||||
size="mini"
|
||||
style="flex-shrink: 0;"
|
||||
type="danger"
|
||||
@click="handleDelete(index)"
|
||||
/>
|
||||
<el-button
|
||||
v-if="index === items.length - 1"
|
||||
:disabled="disableAdd(item, index)"
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
style="flex-shrink: 0;"
|
||||
type="primary"
|
||||
@click="handleAdd(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="items.length === 0"
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
style="flex-shrink: 0;"
|
||||
type="primary"
|
||||
@click="handleAdd(0)"
|
||||
/>
|
||||
<ProtocolSettingDialog
|
||||
v-if="showDialog"
|
||||
:disabled="settingReadonly || readonly"
|
||||
:item="settingItem"
|
||||
:visible.sync="showDialog"
|
||||
@confirm="handleSettingConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProtocolSettingDialog from './ProtocolSettingDialog'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProtocolSettingDialog
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Array],
|
||||
default: () => []
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
choices: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
settingReadonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showSetting: {
|
||||
type: Function,
|
||||
default: (item) => true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
items: [],
|
||||
settingItem: {},
|
||||
showDialog: false,
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedProtocolNames() {
|
||||
return this.items.map(item => item.name)
|
||||
},
|
||||
remainProtocols() {
|
||||
return this.choices.filter(proto => {
|
||||
return this.selectedProtocolNames.indexOf(proto.name) === -1
|
||||
})
|
||||
},
|
||||
portPlaceholder() {
|
||||
if (this.settingReadonly) {
|
||||
return this.$t('applications.port')
|
||||
} else {
|
||||
return this.$t('assets.DefaultPort')
|
||||
}
|
||||
},
|
||||
iChoices() {
|
||||
return this.choices.map(item => {
|
||||
delete item?.id
|
||||
return item
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
choices: {
|
||||
handler(value) {
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
this.setDefaultItems(value)
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
items: {
|
||||
handler(value) {
|
||||
this.$emit('input', value)
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setDefaultItems(this.iChoices)
|
||||
this.$log.debug('Choices: ', this.choices)
|
||||
this.$log.debug('Value: ', this.value)
|
||||
this.$log.debug('Items: ', this.items)
|
||||
},
|
||||
methods: {
|
||||
handleSettingConfirm() {
|
||||
if (this.settingItem.primary) {
|
||||
const others = this.items
|
||||
.filter(item => item.name !== this.settingItem.name)
|
||||
.map(item => {
|
||||
item.primary = false
|
||||
return item
|
||||
})
|
||||
this.items = [this.settingItem, ...others]
|
||||
}
|
||||
if (this.settingItem.name === 'winrm') {
|
||||
if (this.settingItem.setting?.use_ssl) {
|
||||
this.settingItem.port = 5986
|
||||
} else {
|
||||
this.settingItem.port = 5985
|
||||
}
|
||||
}
|
||||
},
|
||||
handleDelete(index) {
|
||||
this.items = this.items.filter((value, i) => i !== index)
|
||||
},
|
||||
isRequired(item) {
|
||||
const full = this.iChoices.find(choice => {
|
||||
return choice.name === item.name
|
||||
})
|
||||
return full?.primary || full?.required
|
||||
},
|
||||
disableSelect(item) {
|
||||
return this.isRequired(item)
|
||||
},
|
||||
disableDelete(item) {
|
||||
if (this.items.length === 1) {
|
||||
return true
|
||||
}
|
||||
// 代表是设置平台
|
||||
if (!this.settingReadonly) {
|
||||
return false
|
||||
}
|
||||
return this.isRequired(item)
|
||||
},
|
||||
disableAdd(item) {
|
||||
return this.remainProtocols.length === 0 || !item.port
|
||||
},
|
||||
handleAdd(index) {
|
||||
this.items.push({ ...this.remainProtocols[0] })
|
||||
},
|
||||
handleProtocolChange(evt, item) {
|
||||
const selected = this.choices.find(item => item.name === evt)
|
||||
item.name = selected.name
|
||||
item.port = selected.port
|
||||
},
|
||||
setPrimaryIfNeed(items) {
|
||||
// 如果没有设置主协议,设置第一个为主协议
|
||||
if (!this.settingReadonly) {
|
||||
const primaryProtocols = items.filter(item => item.primary)
|
||||
if (primaryProtocols.length === 0) {
|
||||
items[0].primary = true
|
||||
items[0].default = true
|
||||
items[0].required = true
|
||||
} else if (primaryProtocols.length > 1) {
|
||||
primaryProtocols.slice(1, primaryProtocols.length).forEach(item => {
|
||||
item.primary = false
|
||||
})
|
||||
}
|
||||
}
|
||||
return items
|
||||
},
|
||||
setDefaultItems(choices) {
|
||||
let items = []
|
||||
const requiredItems = choices.filter(item => (item.required || item.primary))
|
||||
|
||||
if (this.value instanceof Array && this.value.length > 0) {
|
||||
const protocols = []
|
||||
this.value.forEach(item => {
|
||||
// 有默认值的情况下,设置为只读或者有id、有setting是平台
|
||||
if (!this.settingReadonly || (item?.id && item?.setting)) {
|
||||
protocols.push(item)
|
||||
} else {
|
||||
// 获取资产协议配置
|
||||
const assetDefaultItems = this.getAssetDefaultItems(item, choices)
|
||||
protocols.push(...assetDefaultItems)
|
||||
}
|
||||
})
|
||||
const notFound = requiredItems.filter(item => !protocols.find(p => p.name === item.name))
|
||||
protocols.push(...notFound)
|
||||
const allProtocolNames = protocols.map(item => item.name)
|
||||
items = protocols.filter(item => allProtocolNames.indexOf(item.name) !== -1)
|
||||
} else {
|
||||
const defaults = choices.filter(item => (item.required || item.primary || item.default))
|
||||
if (defaults.length === 0) {
|
||||
defaults.push(choices[0])
|
||||
}
|
||||
items = defaults
|
||||
}
|
||||
items = this.setPrimaryIfNeed(items)
|
||||
this.items = items
|
||||
},
|
||||
getAssetDefaultItems(item, choices) {
|
||||
const protocols = []
|
||||
const protocol = choices.find(i => i.name === item.name) || {}
|
||||
protocols.push({ ...protocol, ...item })
|
||||
return protocols
|
||||
},
|
||||
onSettingClick(item) {
|
||||
this.settingItem = item
|
||||
this.showDialog = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.el-select .el-input {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.el-select {
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.input-with-select {
|
||||
flex-shrink: 1;
|
||||
width: calc(100% - 80px) !important;
|
||||
}
|
||||
|
||||
.input-with-select .el-input-group__prepend {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.el-select ::v-deep .el-input__inner {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.protocol-item {
|
||||
display: flex;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.input-button {
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
margin-left: 20px
|
||||
}
|
||||
|
||||
.input-button ::v-deep .el-button.el-button--mini {
|
||||
height: 25px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.el-input-group__append .el-button {
|
||||
font-size: 14px;
|
||||
color: #1a1a1a;
|
||||
padding: 9px 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -4,26 +4,26 @@
|
||||
v-model="iValue"
|
||||
v-loading="!initialized"
|
||||
v-loadmore="loadMore"
|
||||
:clearable="clearable"
|
||||
:disabled="selectDisabled"
|
||||
:multiple="multiple"
|
||||
:options="iOptions"
|
||||
:remote="remote"
|
||||
:remote-method="filterOptions"
|
||||
class="select2"
|
||||
:multiple="multiple"
|
||||
:clearable="clearable"
|
||||
filterable
|
||||
popper-append-to-body
|
||||
class="select2"
|
||||
:disabled="selectDisabled"
|
||||
v-bind="$attrs"
|
||||
@change="onChange"
|
||||
v-on="$listeners"
|
||||
@visible-change="onVisibleChange"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in iOptions"
|
||||
:key="item.value"
|
||||
:disabled="checkDisabled(item)"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="checkDisabled(item)"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -78,7 +78,7 @@ export default {
|
||||
},
|
||||
// 初始化值,也就是选中的值
|
||||
value: {
|
||||
type: [Array, String, Number, Boolean, Object],
|
||||
type: [Array, String, Number, Boolean],
|
||||
default() {
|
||||
return this.multiple ? [] : ''
|
||||
}
|
||||
@@ -137,13 +137,7 @@ export default {
|
||||
if (noValue && !this.initialized) {
|
||||
return
|
||||
}
|
||||
if (val && val.constructor === Object && val.value) {
|
||||
this.$emit('input', val.value)
|
||||
} else if (val && val.constructor === Object && val.id) {
|
||||
this.$emit('input', val.id)
|
||||
} else {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
this.$emit('input', val)
|
||||
},
|
||||
get() {
|
||||
return this.value
|
||||
@@ -167,10 +161,6 @@ export default {
|
||||
return { label: item.name, value: item.id }
|
||||
}
|
||||
const transformOption = this.ajax.transformOption || defaultTransformOption
|
||||
const defaultFilterOption = (item) => {
|
||||
return item
|
||||
}
|
||||
const filterOption = this.ajax.filterOption || defaultFilterOption
|
||||
const defaultProcessResults = (data) => {
|
||||
let results = []
|
||||
let more = false
|
||||
@@ -184,7 +174,7 @@ export default {
|
||||
total = data.count
|
||||
}
|
||||
results = results.map(transformOption)
|
||||
results = results.filter(filterOption)
|
||||
results = results.filter(Boolean)
|
||||
return { results: results, pagination: more, total: total }
|
||||
}
|
||||
const defaultAjax = {
|
||||
@@ -199,28 +189,31 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// url(newValue, oldValue) {
|
||||
// this.$log.debug('Select url changed: ', oldValue, ' => ', newValue)
|
||||
// this.iAjax.url = newValue
|
||||
// this.refresh()
|
||||
// },
|
||||
iAjax(newValue, oldValue) {
|
||||
this.$log.debug('Select url changed: ', oldValue, ' => ', newValue)
|
||||
this.refresh()
|
||||
},
|
||||
value: {
|
||||
handler(newValue, oldValue) {
|
||||
},
|
||||
deep: true
|
||||
value(iNew) {
|
||||
this.iValue = iNew
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
// this.$log.debug('Select2 url is: ', this.iAjax.url)
|
||||
if (!this.initialized) {
|
||||
await this.initialSelect()
|
||||
setTimeout(() => {
|
||||
this.$log.debug('Value is : ', this.value)
|
||||
this.iValue = this.value
|
||||
this.initialized = true
|
||||
})
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
// 因为elform存在问题,这个来清楚验证
|
||||
const elFormItem = this.$refs.select?.elFormItem
|
||||
const elFormItem = this.$refs.select.elFormItem
|
||||
if (elFormItem && elFormItem.clearValidate) {
|
||||
elFormItem.clearValidate()
|
||||
}
|
||||
@@ -326,11 +319,13 @@ export default {
|
||||
addOption(option) {
|
||||
this.iOptions.push(option)
|
||||
},
|
||||
getOptionsByValues(values) {
|
||||
return this.iOptions.filter((v) => {
|
||||
return values.indexOf(v.value) !== -1
|
||||
})
|
||||
},
|
||||
getSelectedOptions() {
|
||||
let values = this.iValue
|
||||
if (!Array.isArray(values)) {
|
||||
values = [values]
|
||||
}
|
||||
const values = this.iValue
|
||||
return this.iOptions.filter((v) => {
|
||||
return values.indexOf(v.value) !== -1
|
||||
})
|
||||
@@ -343,9 +338,9 @@ export default {
|
||||
},
|
||||
onChange(values) {
|
||||
const options = this.getSelectedOptions()
|
||||
this.$log.debug('Current select options: ', options, 'Val: ', this.value)
|
||||
this.$log.debug('Current select options: ', options)
|
||||
this.$emit('changeOptions', options)
|
||||
// this.$emit('change', options) // 事件重复
|
||||
this.$emit('change', options)
|
||||
},
|
||||
onVisibleChange(visible) {
|
||||
if (!visible && this.params.search) {
|
||||
@@ -359,12 +354,11 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.select2 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.select2 >>> .el-tag.el-tag--info {
|
||||
height: auto;
|
||||
white-space: normal;
|
||||
}
|
||||
.select2 {
|
||||
width: 100%;
|
||||
}
|
||||
.select2 >>> .el-tag.el-tag--info {
|
||||
height: auto;
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Switcher', // 不能叫 Switch,否则会和 js 关键字冲突
|
||||
name: 'Switcher',
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
@@ -1,130 +0,0 @@
|
||||
<template>
|
||||
<div class="filter-field">
|
||||
<el-tag
|
||||
v-for="(v, k) in filterTags"
|
||||
:key="k"
|
||||
:disable-transitions="true"
|
||||
:type="tagType(v)"
|
||||
closable
|
||||
size="small"
|
||||
@click="handleTagClick(v, k)"
|
||||
@close="handleTagClose(v)"
|
||||
>
|
||||
{{ v }}
|
||||
</el-tag>
|
||||
<component
|
||||
:is="component"
|
||||
ref="SearchInput"
|
||||
v-model.trim="filterValue"
|
||||
:fetch-suggestions="autocomplete"
|
||||
:placeholder="this.$t('common.EnterToContinue')"
|
||||
class="search-input"
|
||||
@blur="focus = false"
|
||||
@change="handleConfirm"
|
||||
@focus="focus = true"
|
||||
@select="handleSelect"
|
||||
@keyup.enter.native="handleConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
tagType: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
return 'info'
|
||||
}
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: () => i18n.t('perms.Input')
|
||||
},
|
||||
autocomplete: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
filterTags: this.value,
|
||||
focus: false,
|
||||
filterValue: '',
|
||||
component: this.autocomplete ? 'el-autocomplete' : 'el-input'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.filterTags = val
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTagClose(tag) {
|
||||
this.filterTags.splice(this.filterTags.indexOf(tag), 1)
|
||||
this.$emit('change', this.filterTags)
|
||||
},
|
||||
handleSelect(item) {
|
||||
this.filterValue = item.value
|
||||
this.handleConfirm()
|
||||
},
|
||||
handleConfirm() {
|
||||
if (this.filterValue === '') return
|
||||
if (!this.filterTags.includes(this.filterValue)) {
|
||||
this.filterTags.push(this.filterValue)
|
||||
this.filterValue = ''
|
||||
this.$emit('change', this.filterTags)
|
||||
}
|
||||
},
|
||||
handleTagClick(v, k) {
|
||||
if (this.filterValue.length !== 0) {
|
||||
this.handleConfirm()
|
||||
}
|
||||
this.$delete(this.filterTags, k)
|
||||
this.filterValue = v
|
||||
this.$refs.SearchInput.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-tag + .el-tag {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.filter-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 2px;
|
||||
border: 1px solid #dcdee2;
|
||||
border-radius: 1px;
|
||||
background-color: #fff;
|
||||
line-height: 32px;
|
||||
|
||||
&:hover {
|
||||
border-color: #C0C4CC;
|
||||
}
|
||||
}
|
||||
|
||||
.search-input >>> .el-input__inner {
|
||||
max-width: 100%;
|
||||
border: none;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.el-input >>> .el-input__inner {
|
||||
border: none !important;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.filter-field >>> .el-input__inner {
|
||||
height: 32px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
{{ value? trueText : falseText }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Boolean],
|
||||
default: () => false
|
||||
},
|
||||
trueText: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return this.$t('common.Yes')
|
||||
}
|
||||
},
|
||||
falseText: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return this.$t('common.No')
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,92 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-button
|
||||
v-show="!iShowSelect"
|
||||
:disabled="disabled"
|
||||
class="button-text"
|
||||
type="text"
|
||||
@click="iShowSelect=true"
|
||||
>
|
||||
{{ iLabel }}
|
||||
<svg-icon class-name="icon" icon-class="switch" />
|
||||
</el-button>
|
||||
<Select2
|
||||
v-show="iShowSelect"
|
||||
ref="select2"
|
||||
v-model="iValue"
|
||||
:disabled="disabled"
|
||||
v-bind="$attrs"
|
||||
@change="onSelectChange"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Select2 from './Select2'
|
||||
import { hasUUID } from '@/utils/common'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Select2
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
showSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
iShowSelect: this.showSelect,
|
||||
iLabel: this.label || '-'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iValue: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const { path } = this.$route
|
||||
if (hasUUID(path) && this.value) {
|
||||
this.iShowSelect = false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSelectChange(val) {
|
||||
const options = this.$refs.select2.options.filter(item => item.value === val)
|
||||
const label = options.length > 0 ? options[0].label : ''
|
||||
this.iShowSelect = false
|
||||
this.iLabel = val ? label : '-'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.button-text {
|
||||
color: #676a6c;
|
||||
padding: 5px!important;
|
||||
}
|
||||
.icon {
|
||||
color: #676a6c!important;
|
||||
}
|
||||
</style>
|
||||
@@ -32,10 +32,6 @@ export default {
|
||||
return this.$t('common.Update')
|
||||
}
|
||||
},
|
||||
showInput: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
@@ -43,13 +39,13 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: this.showInput,
|
||||
isShow: false,
|
||||
curValue: this.value
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.$route.path.indexOf('/update') !== -1) {
|
||||
this.isShow = false
|
||||
if (this.$route.path.indexOf('/create') !== -1) {
|
||||
this.isShow = true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<el-input
|
||||
v-model="iValue"
|
||||
type="textarea"
|
||||
:rows="rows"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<el-upload
|
||||
ref="upload"
|
||||
class="upload-secret"
|
||||
:action="''"
|
||||
:accept="accept"
|
||||
:auto-upload="false"
|
||||
:limit="limit"
|
||||
v-bind="$attrs"
|
||||
:on-change="handleChange"
|
||||
:on-remove="handleRemove"
|
||||
:file-list="fileList"
|
||||
>
|
||||
<el-button size="mini" type="primary">
|
||||
{{ btnText }}
|
||||
</el-button>
|
||||
<div v-if="tip" slot="tip" class="el-upload__tip">
|
||||
{{ tip }}
|
||||
</div>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
btnText: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return this.$t('common.SelectFile')
|
||||
}
|
||||
},
|
||||
rows: {
|
||||
type: Number,
|
||||
default: () => 4
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: () => 2
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
tip: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fileName: '',
|
||||
fileList: [],
|
||||
iValue: this.value
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
iValue(val) {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange(file, fileList) {
|
||||
const vm = this
|
||||
const newFileList = fileList.slice(-1)
|
||||
this.fileList = newFileList
|
||||
const reader = new FileReader()
|
||||
reader.onload = function(res) {
|
||||
const result = res.target.result
|
||||
vm.iValue = result
|
||||
vm.$emit('input', vm.iValue)
|
||||
}
|
||||
reader.readAsText(file.raw)
|
||||
},
|
||||
|
||||
handleRemove() {
|
||||
this.iValue = ''
|
||||
this.fileList = []
|
||||
this.$emit('input', this.iValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.upload-secret {
|
||||
display: flex;
|
||||
&>>> .el-list-enter-active,
|
||||
&>>> .el-list-leave-active {
|
||||
transition: none;
|
||||
}
|
||||
&>>> .el-list-enter,
|
||||
&>>> .el-list-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
&>>> .el-upload-list {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,57 +1,42 @@
|
||||
import Link from './Link'
|
||||
import Text from './Text'
|
||||
import Select2 from './Select2'
|
||||
import TagInput from './TagInput'
|
||||
import Switcher from './Switcher'
|
||||
import UploadKey from './UploadKey'
|
||||
import JsonEditor from './JsonEditor'
|
||||
import PhoneInput from './PhoneInput'
|
||||
import UploadField from './UploadField'
|
||||
import UpdateToken from './UpdateToken'
|
||||
import UserPassword from './UserPassword'
|
||||
import DynamicInput from './DynamicInput'
|
||||
import PasswordInput from './PasswordInput'
|
||||
import UploadSecret from './UploadSecret'
|
||||
import WeekCronSelect from './WeekCronSelect'
|
||||
import NestedObjectSelect2 from './NestedObjectSelect2'
|
||||
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'
|
||||
import UpdateToken from './UpdateToken'
|
||||
import JsonEditor from './JsonEditor'
|
||||
import Text from './Text'
|
||||
|
||||
export default {
|
||||
Text,
|
||||
DatetimeRangePicker,
|
||||
Link,
|
||||
Switcher,
|
||||
PasswordInput,
|
||||
Select2,
|
||||
TagInput,
|
||||
Swicher,
|
||||
UploadKey,
|
||||
JsonEditor,
|
||||
UpdateToken,
|
||||
PhoneInput,
|
||||
UploadField,
|
||||
UserPassword,
|
||||
DynamicInput,
|
||||
PasswordInput,
|
||||
UploadSecret,
|
||||
WeekCronSelect,
|
||||
NestedObjectSelect2,
|
||||
DatetimeRangePicker
|
||||
UpdateToken,
|
||||
JsonEditor,
|
||||
Text
|
||||
}
|
||||
|
||||
export {
|
||||
Text,
|
||||
DatetimeRangePicker,
|
||||
Link,
|
||||
Switcher,
|
||||
PasswordInput,
|
||||
Select2,
|
||||
TagInput,
|
||||
Swicher,
|
||||
UploadKey,
|
||||
JsonEditor,
|
||||
UpdateToken,
|
||||
PhoneInput,
|
||||
UploadField,
|
||||
UserPassword,
|
||||
DynamicInput,
|
||||
PasswordInput,
|
||||
UploadSecret,
|
||||
WeekCronSelect,
|
||||
NestedObjectSelect2,
|
||||
DatetimeRangePicker
|
||||
UpdateToken,
|
||||
JsonEditor,
|
||||
Text
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="form-group-header">
|
||||
<div v-if="line" class="hr-line-dashed" />
|
||||
<h3>{{ group['title'] }} </h3>
|
||||
<h3>{{ group.title }}</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
<template>
|
||||
<Dialog
|
||||
v-if="iVisible"
|
||||
:destroy-on-close="true"
|
||||
:show-cancel="false"
|
||||
:show-confirm="false"
|
||||
:title="$tc('assets.TestGatewayTestConnection')"
|
||||
:visible.sync="iVisible"
|
||||
top="35vh"
|
||||
width="40%"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="4" :sm="24">
|
||||
<div style="line-height: 34px">{{ $t('assets.SSHPort') }}</div>
|
||||
</el-col>
|
||||
<el-col :md="14" :sm="24">
|
||||
<el-input v-model="port" />
|
||||
<span class="help-tips help-block">{{ $t('assets.TestGatewayHelpMessage') }}</span>
|
||||
</el-col>
|
||||
<el-col :md="4" :sm="24">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="mini"
|
||||
style="line-height:20px "
|
||||
type="primary"
|
||||
@click="dialogConfirm"
|
||||
>
|
||||
{{ this.$t('common.Confirm') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
import { openTaskPage } from '@/utils/jms'
|
||||
|
||||
export default {
|
||||
name: 'GatewayDialog',
|
||||
components: {
|
||||
Dialog
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
port: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
cell: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
iVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dialogConfirm() {
|
||||
if (isNaN(this.port)) {
|
||||
return this.$message.error(this.$tc('common.TestPortErrorMsg'))
|
||||
}
|
||||
this.$axios.post(
|
||||
`/api/v1/assets/gateways/${this.cell}/test-connective/`,
|
||||
{ port: this.port }
|
||||
)
|
||||
.then((res) => {
|
||||
openTaskPage(res['task'])
|
||||
}).finally(() => {
|
||||
this.iVisible = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<TreeTable :header-actions="headerActions" :table-config="tableConfig" :tree-setting="treeSetting" />
|
||||
<TreeTable :table-config="tableConfig" :header-actions="headerActions" :tree-setting="treeSetting" />
|
||||
</template>
|
||||
|
||||
<script type="text/jsx">
|
||||
import { DetailFormatter, SystemUserFormatter } from '@/components/TableFormatters'
|
||||
import TreeTable from '../TreeTable'
|
||||
import { DetailFormatter } from '@/components/TableFormatters'
|
||||
|
||||
export default {
|
||||
name: 'GrantedAssets',
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
getShowUrl: {
|
||||
type: Function,
|
||||
default({ row, col }) {
|
||||
return this.tableUrl.replace('/assets/', `/assets/${row.id}/accounts/`)
|
||||
return this.tableUrl.replace('/assets/', `/assets/${row.id}/system-users/?cache_policy=1`)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -57,21 +57,34 @@ export default {
|
||||
tableConfig: {
|
||||
url: this.tableUrl,
|
||||
hasTree: true,
|
||||
columnsExclude: ['spec_info'],
|
||||
columnShow: {
|
||||
min: ['name', 'address', 'accounts']
|
||||
},
|
||||
columnsMeta: {
|
||||
name: {
|
||||
columns: [
|
||||
{
|
||||
prop: 'hostname',
|
||||
label: this.$t('assets.Hostname'),
|
||||
formatter: DetailFormatter,
|
||||
sortable: true,
|
||||
formatterArgs: {
|
||||
route: 'AssetDetail'
|
||||
}
|
||||
},
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
actions: {
|
||||
has: false
|
||||
{
|
||||
prop: 'ip',
|
||||
label: this.$t('assets.IP'),
|
||||
width: '140px',
|
||||
sortable: 'custom'
|
||||
},
|
||||
{
|
||||
prop: 'systemUsers',
|
||||
label: this.$t('assets.SystemUsers'),
|
||||
align: 'center',
|
||||
formatter: SystemUserFormatter,
|
||||
formatterArgs: {
|
||||
getUrl: this.getShowUrl.bind(this)
|
||||
},
|
||||
showOverflowTooltip: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div style="padding: 0 20px;" @click="toggleClick">
|
||||
<svg-icon icon-class="arrow-to-left" class="hamburger" style="color: #ffffff;" :class="{'is-active':isActive}" />
|
||||
<div style="padding: 0 15px;" @click="toggleClick">
|
||||
<svg-icon icon-class="hamburger" class="hamburger" :class="{'is-active':isActive}" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -26,7 +26,7 @@ export default {
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
font-size: 16px;
|
||||
font-size: 20px;
|
||||
color: $menuText;
|
||||
}
|
||||
.hamburger.is-active {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<template #header>
|
||||
<slot name="header">
|
||||
<div v-if="title" slot="header" class="clearfix ibox-title">
|
||||
<i v-if="fa" :class="'fa ' + fa" /> <h5>{{ title }}</h5>
|
||||
<i v-if="fa" :class="'fa ' + fa" /> {{ title }}
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
@@ -41,6 +41,7 @@ export default {
|
||||
/*height: 100%;*/
|
||||
clear: both;
|
||||
padding: 0;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.ibox >>> .el-card__header {
|
||||
@@ -55,11 +56,11 @@ export default {
|
||||
|
||||
.ibox-title h5 {
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.ibox-tools a {
|
||||
@@ -82,7 +83,8 @@ export default {
|
||||
}
|
||||
|
||||
.ibox >>> .el-card__body {
|
||||
padding: 15px 30px 20px 30px;
|
||||
background-color: #ffffff;
|
||||
padding: 15px 20px 20px 20px;
|
||||
color: inherit;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,43 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="mfaDialogShow">
|
||||
<UserConfirmDialog
|
||||
:url="url"
|
||||
@AuthMFAError="handleAuthMFAError"
|
||||
@UserConfirmCancel="handleExportCancel"
|
||||
@UserConfirmDone="showExportDialog"
|
||||
/>
|
||||
</div>
|
||||
<UserConfirmDialog
|
||||
v-if="mfaDialogShow"
|
||||
:url="url"
|
||||
@UserConfirmDone="showExportDialog"
|
||||
@UserConfirmCancel="handleExportCancel"
|
||||
/>
|
||||
<Dialog
|
||||
v-if="exportDialogShow"
|
||||
:destroy-on-close="true"
|
||||
:title="$tc('common.Export')"
|
||||
:title="$t('common.Export')"
|
||||
:visible.sync="exportDialogShow"
|
||||
width="70%"
|
||||
@cancel="handleExportCancel()"
|
||||
:destroy-on-close="true"
|
||||
@confirm="handleExportConfirm()"
|
||||
@cancel="handleExportCancel()"
|
||||
>
|
||||
<el-form label-position="left" style="padding-left: 20px">
|
||||
<el-form-item :label="$tc('common.fileType' )" :label-width="'100px'">
|
||||
<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"
|
||||
:disabled="!option.can"
|
||||
:label="option.value"
|
||||
style="padding: 10px 20px;"
|
||||
>{{ option.label }}</el-radio>
|
||||
<el-radio v-for="option of exportTypeOptions" :key="option.value" style="padding: 10px 20px;" :label="option.value" :disabled="!option.can">{{ option.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$tc('common.imExport.ExportRange')" :label-width="'100px'" class="export-form">
|
||||
<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"
|
||||
:disabled="!option.can"
|
||||
:label="option.value"
|
||||
class="export-item"
|
||||
>{{ option.label }}</el-radio>
|
||||
<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>
|
||||
@@ -68,8 +53,7 @@ export default {
|
||||
},
|
||||
beforeExport: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
default: () => {}
|
||||
},
|
||||
mfaVerifyRequired: {
|
||||
type: Boolean,
|
||||
@@ -159,7 +143,8 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('showExportDialog', ({ selectedRows, url, name }) => {
|
||||
if (url === this.url || url.indexOf(this.url) > -1) {
|
||||
// Todo: 没有时间了,只能先这么处理了
|
||||
if (url === this.url || url.indexOf(this.url) > -1 || url.indexOf('account') > -1) {
|
||||
this.showExportDialog()
|
||||
}
|
||||
})
|
||||
@@ -197,8 +182,8 @@ export default {
|
||||
}
|
||||
query['format'] = exportTypeOption
|
||||
const queryStr =
|
||||
(url.indexOf('?') > -1 ? '&' : '?') +
|
||||
queryUtil.stringify(query, '=', '&')
|
||||
(url.indexOf('?') > -1 ? '&' : '?') +
|
||||
queryUtil.stringify(query, '=', '&')
|
||||
return this.downloadCsv(url + queryStr)
|
||||
},
|
||||
async handleExport() {
|
||||
@@ -220,9 +205,6 @@ export default {
|
||||
vm.exportDialogShow = false
|
||||
vm.mfaDialogShow = false
|
||||
}, 100)
|
||||
},
|
||||
handleAuthMFAError() {
|
||||
this.mfaDialogShow = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
:loading-status="loadStatus"
|
||||
:show-cancel="false"
|
||||
:show-confirm="false"
|
||||
:title="importTitle"
|
||||
:visible.sync="showImportDialog"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:loading-status="loadStatus"
|
||||
width="80%"
|
||||
class="importDialog"
|
||||
width="70%"
|
||||
:show-cancel="false"
|
||||
:show-confirm="false"
|
||||
@close="handleImportCancel"
|
||||
>
|
||||
<el-form v-if="!showTable" label-position="left" style="padding-left: 20px">
|
||||
<el-form-item :label="$tc('common.Import' )" :label-width="'100px'">
|
||||
<el-form v-if="!showTable" label-position="left" style="padding-left: 50px">
|
||||
<el-form-item :label="$t('common.Import' )" :label-width="'100px'">
|
||||
<el-radio v-if="canImportCreate" v-model="importOption" class="export-item" label="create">
|
||||
{{ this.$t('common.Create') }}
|
||||
</el-radio>
|
||||
@@ -22,31 +22,27 @@
|
||||
<div style="line-height: 1.5">
|
||||
<span class="el-upload__tip">
|
||||
{{ downloadTemplateTitle }}
|
||||
<el-link type="success" @click="downloadTemplateFile('csv')"> CSV </el-link>
|
||||
<el-link type="success" @click="downloadTemplateFile('xlsx')"> XLSX </el-link>
|
||||
<el-link type="success" :underline="false" style="padding-left: 10px" @click="downloadTemplateFile('csv')"> CSV </el-link>
|
||||
<el-link type="success" :underline="false" style="padding-left: 10px" @click="downloadTemplateFile('xlsx')"> XLSX </el-link>
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$tc('common.Upload' )" :label-width="'100px'" class="file-uploader">
|
||||
<el-form-item :label="$t('common.Upload' )" :label-width="'100px'" class="file-uploader">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
:auto-upload="false"
|
||||
:before-upload="beforeUpload"
|
||||
:limit="1"
|
||||
:on-change="onFileChange"
|
||||
accept=".csv,.xlsx"
|
||||
action="string"
|
||||
drag
|
||||
action="string"
|
||||
list-type="text/csv"
|
||||
: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 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>
|
||||
<span :class="{'hasError': hasFileFormatOrSizeError }">{{ $t('common.imExport.uploadCsvLth10MHelpText') }}</span>
|
||||
<div v-if="renderError" class="hasError">{{ renderError }}</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
@@ -55,8 +51,8 @@
|
||||
<div v-else class="importTableZone">
|
||||
<ImportTable
|
||||
ref="importTable"
|
||||
:import-option="importOption"
|
||||
:json-data="jsonData"
|
||||
:import-option="importOption"
|
||||
:url="url"
|
||||
@cancel="cancelUpload"
|
||||
@finish="closeDialog"
|
||||
@@ -91,7 +87,7 @@ export default {
|
||||
default: false
|
||||
},
|
||||
canImportUpdate: {
|
||||
type: [Boolean, Function],
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
@@ -172,10 +168,7 @@ export default {
|
||||
this.$axios.post(
|
||||
renderToJsonUrl,
|
||||
file.raw,
|
||||
{
|
||||
headers: { 'Content-Type': isCsv ? 'text/csv' : 'text/xlsx' },
|
||||
disableFlashErrorMsg: true
|
||||
}
|
||||
{ headers: { 'Content-Type': isCsv ? 'text/csv' : 'text/xlsx' }, disableFlashErrorMsg: true }
|
||||
).then(data => {
|
||||
this.jsonData = data
|
||||
this.showTable = true
|
||||
@@ -212,8 +205,9 @@ export default {
|
||||
}
|
||||
return this.url.indexOf('?') === -1 ? `${this.url}?${query}` : `${this.url}&${query}`
|
||||
},
|
||||
catchError(err) {
|
||||
console.log(err)
|
||||
// eslint-disable-next-line handle-callback-err
|
||||
catchError(error) {
|
||||
// debug(error)
|
||||
},
|
||||
onSuccess(msg) {
|
||||
this.errorMsg = ''
|
||||
@@ -226,14 +220,13 @@ export default {
|
||||
window.URL.revokeObjectURL(url)
|
||||
},
|
||||
async handleImportConfirm() {
|
||||
await this.$refs['importTable'].performUpload()
|
||||
this.$refs['importTable'].performUpload()
|
||||
},
|
||||
handleImportCancel() {
|
||||
this.showImportDialog = false
|
||||
this.showTable = false
|
||||
this.renderError = ''
|
||||
this.jsonData = {}
|
||||
this.$emit('importDialogClose')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,6 +243,10 @@ export default {
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
.importDialog >>> .el-form-item.file-uploader {
|
||||
padding-right: 150px;
|
||||
}
|
||||
|
||||
.file-uploader >>> .el-upload {
|
||||
width: 100%;
|
||||
//padding-right: 150px;
|
||||
@@ -290,9 +287,5 @@ export default {
|
||||
.el-upload__tip {
|
||||
line-height: 1.5;
|
||||
padding-top: 0;
|
||||
|
||||
.el-link {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,10 +33,8 @@
|
||||
|
||||
<script>
|
||||
import DataTable from '@/components/DataTable'
|
||||
import { getUpdateObjURL, sleep } from '@/utils/common'
|
||||
import { sleep, getUpdateObjURL } from '@/utils/common'
|
||||
import { EditableInputFormatter, StatusFormatter } from '@/components/TableFormatters'
|
||||
import { encryptPassword } from '@/utils/crypto'
|
||||
|
||||
export default {
|
||||
name: 'ImportTable',
|
||||
components: {
|
||||
@@ -178,7 +176,7 @@ export default {
|
||||
align: 'center',
|
||||
formatter: StatusFormatter,
|
||||
formatterArgs: {
|
||||
faChoices: {
|
||||
iconChoices: {
|
||||
ok: 'fa-check text-primary',
|
||||
error: 'fa-times text-danger',
|
||||
pending: 'fa-clock-o'
|
||||
@@ -211,18 +209,11 @@ export default {
|
||||
const itemColData = d[prop]
|
||||
if (typeof itemColData === 'boolean') {
|
||||
return 5 // boolean is 5 characters long 并且 boolean.length 是 undefined
|
||||
} else if (typeof itemColData === 'number') {
|
||||
return itemColData.toString().length
|
||||
} else if (typeof itemColData === 'string') {
|
||||
return itemColData.length
|
||||
} else if (typeof itemColData === 'object') {
|
||||
if (!itemColData || itemColData.length === 0) {
|
||||
return 0
|
||||
} else {
|
||||
return JSON.stringify(itemColData).length
|
||||
}
|
||||
}
|
||||
return 0
|
||||
if (typeof itemColData !== 'number' && (!itemColData || !itemColData.length)) {
|
||||
return 0
|
||||
}
|
||||
return itemColData.length
|
||||
})
|
||||
let colMaxWidth = Math.max(...dataItemLens) * 10
|
||||
if (colMaxWidth === 0) {
|
||||
@@ -234,8 +225,8 @@ export default {
|
||||
prop: item[1],
|
||||
label: item[0],
|
||||
minWidth: colMaxWidth + 'px',
|
||||
formatter: EditableInputFormatter,
|
||||
showOverflowTooltip: true,
|
||||
formatter: EditableInputFormatter,
|
||||
formatterArgs: {
|
||||
onEnter: ({ row, col, oldValue, newValue }) => {
|
||||
const prop = col.prop
|
||||
@@ -252,12 +243,6 @@ export default {
|
||||
const totalData = []
|
||||
tableData.forEach(item => {
|
||||
this.$set(item, '@status', 'pending')
|
||||
const encryptFields = ['password', 'secret', 'private_key']
|
||||
for (const field of encryptFields) {
|
||||
if (item[field]) {
|
||||
item[field] = encryptPassword(item[field])
|
||||
}
|
||||
}
|
||||
totalData.push(item)
|
||||
})
|
||||
return totalData
|
||||
@@ -285,9 +270,7 @@ export default {
|
||||
const data = []
|
||||
// eslint-disable-next-line prefer-const
|
||||
for (let [key, value] of Object.entries(errorData)) {
|
||||
if (Array.isArray(value)) {
|
||||
value = JSON.stringify(value)
|
||||
} else if (typeof value === 'object') {
|
||||
if (typeof value === 'object') {
|
||||
value = this.beautifyErrorData(value)
|
||||
}
|
||||
let label = this.tableColumnNameMapper[key]
|
||||
@@ -366,7 +349,7 @@ export default {
|
||||
this.importTaskStatus = 'done'
|
||||
}
|
||||
if (this.failedCount > 0) {
|
||||
this.$message.error(this.$tc('common.imExport.hasImportErrorItemMsg') + '')
|
||||
this.$message.error(this.$t('common.imExport.hasImportErrorItemMsg') + '')
|
||||
}
|
||||
},
|
||||
async performUpdateObject(item) {
|
||||
@@ -441,7 +424,7 @@ export default {
|
||||
.importTable >>> .cell {
|
||||
min-height: 20px;
|
||||
height: 100%;
|
||||
max-height: 160px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<DataActions
|
||||
v-if="hasLeftActions"
|
||||
:actions="iActions"
|
||||
class="header-action"
|
||||
v-bind="$attrs"
|
||||
class="header-action"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -26,14 +26,10 @@ export default {
|
||||
canCreate: defaultTrue,
|
||||
createRoute: {
|
||||
type: [String, Object, Function],
|
||||
default() {
|
||||
default: function() {
|
||||
return this.$route.name?.replace('List', 'Create')
|
||||
}
|
||||
},
|
||||
onCreate: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
createInNewPage: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -42,11 +38,6 @@ export default {
|
||||
canBulkDelete: defaultTrue,
|
||||
hasBulkUpdate: defaultFalse,
|
||||
canBulkUpdate: defaultTrue,
|
||||
handleBulkUpdate: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
hasMoreActions: defaultTrue,
|
||||
tableUrl: {
|
||||
type: String,
|
||||
@@ -54,8 +45,7 @@ export default {
|
||||
},
|
||||
reloadTable: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
default: () => {}
|
||||
},
|
||||
performBulkDelete: {
|
||||
type: Function,
|
||||
@@ -87,30 +77,49 @@ export 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)
|
||||
}
|
||||
const vm = this
|
||||
return {
|
||||
defaultActions: defaultActions,
|
||||
defaultMoreActions: [
|
||||
{
|
||||
title: this.$t('common.BatchDelete'),
|
||||
title: this.$t('common.deleteSelected'),
|
||||
name: 'actionDeleteSelected',
|
||||
has: this.hasBulkDelete,
|
||||
icon: 'fa fa-trash-o',
|
||||
can({ selectedRows }) {
|
||||
// vm.$log.debug('Delete select rows length: ', selectedRows.length)
|
||||
return selectedRows.length > 0 && vm.canBulkDelete
|
||||
},
|
||||
callback: this.defaultBulkDeleteCallback
|
||||
},
|
||||
{
|
||||
title: this.$t('common.BatchUpdate'),
|
||||
title: this.$t('common.updateSelected'),
|
||||
name: 'actionUpdateSelected',
|
||||
has: this.hasBulkUpdate,
|
||||
icon: 'fa fa-refresh',
|
||||
can: function({ selectedRows }) {
|
||||
let canBulkUpdate = vm.canBulkUpdate
|
||||
if (typeof canBulkUpdate === 'function') {
|
||||
canBulkUpdate = canBulkUpdate({ selectedRows })
|
||||
}
|
||||
return selectedRows.length > 0 && canBulkUpdate
|
||||
can: ({ selectedRows }) => {
|
||||
return selectedRows.length > 0 && vm.canBulkUpdate
|
||||
},
|
||||
callback: this.handleBulkUpdate
|
||||
}
|
||||
@@ -118,32 +127,6 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
defaultActions() {
|
||||
const defaultActions = [
|
||||
{
|
||||
name: 'actionCreate',
|
||||
title: this.createTitle,
|
||||
type: 'primary',
|
||||
has: this.hasCreate && !this.moreCreates,
|
||||
can: this.canCreate,
|
||||
callback: this.onCreate || this.handleCreate
|
||||
}
|
||||
]
|
||||
if (this.moreCreates) {
|
||||
const defaultMoreCreate = {
|
||||
name: 'actionMoreCreate',
|
||||
title: this.createTitle,
|
||||
type: 'primary',
|
||||
has: true,
|
||||
can: this.canCreate,
|
||||
dropdown: [],
|
||||
callback: this.onCreate || this.handleCreate
|
||||
}
|
||||
const createCreateAction = Object.assign(defaultMoreCreate, this.moreCreates)
|
||||
defaultActions.push(createCreateAction)
|
||||
}
|
||||
return defaultActions
|
||||
},
|
||||
iActions() {
|
||||
return [...this.actions, this.moreAction]
|
||||
},
|
||||
@@ -158,19 +141,7 @@ export default {
|
||||
if (!this.hasMoreActions) {
|
||||
return
|
||||
}
|
||||
const invariantActions = [
|
||||
{
|
||||
name: 'batch',
|
||||
title: this.$t('common.BatchProcessing', { 'Number': this.selectedRows.length }),
|
||||
divided: true,
|
||||
has: function({ selectedRows }) {
|
||||
return selectedRows.length > 0
|
||||
},
|
||||
class: 'more-batch-processing',
|
||||
can: true
|
||||
}
|
||||
]
|
||||
let dropdown = _.uniqBy([...invariantActions, ...this.extraMoreActions, ...this.defaultMoreActions], 'name')
|
||||
let dropdown = _.uniqBy([...this.extraMoreActions, ...this.defaultMoreActions], 'name')
|
||||
dropdown = cleanActions(dropdown, true, {
|
||||
selectedRows: this.selectedRows,
|
||||
reloadTable: this.reloadTable
|
||||
@@ -206,7 +177,7 @@ export default {
|
||||
},
|
||||
defaultBulkDeleteCallback({ selectedRows, reloadTable }) {
|
||||
const msg = this.$t('common.deleteWarningMsg') + ' ' + selectedRows.length + ' ' + this.$t('common.rows') + ' ?'
|
||||
const title = this.$tc('common.Info')
|
||||
const title = this.$t('common.Info')
|
||||
const performDelete = this.performBulkDelete || this.defaultPerformBulkDelete
|
||||
this.$alert(msg, title, {
|
||||
type: 'warning',
|
||||
@@ -219,9 +190,9 @@ export default {
|
||||
await performDelete(selectedRows)
|
||||
done()
|
||||
reloadTable()
|
||||
this.$message.success(this.$tc('common.bulkDeleteSuccessMsg'))
|
||||
this.$message.success(this.$t('common.bulkDeleteSuccessMsg'))
|
||||
} catch (error) {
|
||||
this.$message.error(this.$tc('common.bulkDeleteErrorMsg') + error)
|
||||
this.$message.error(this.$t('common.bulkDeleteErrorMsg') + error)
|
||||
} finally {
|
||||
instance.confirmButtonLoading = false
|
||||
}
|
||||
@@ -237,6 +208,8 @@ export default {
|
||||
const data = await createSourceIdCache(ids)
|
||||
const url = (this.tableUrl.indexOf('?') === -1) ? `${this.tableUrl}?spm=` + data.spm : `${this.tableUrl}&spm=` + data.spm
|
||||
return this.$axios.delete(url)
|
||||
},
|
||||
handleBulkUpdate({ selectedRows }) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
:export-options="iExportOptions"
|
||||
:import-options="iImportOptions"
|
||||
v-bind="$attrs"
|
||||
@importDialogClose="onImportDialogClose"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -38,9 +37,7 @@ export default {
|
||||
handleExportClick: {
|
||||
type: Function,
|
||||
default: function({ selectedRows }) {
|
||||
const { exportOptions, tableUrl } = this
|
||||
const url = exportOptions?.url ? exportOptions.url : tableUrl
|
||||
this.$eventBus.$emit('showExportDialog', { selectedRows, url, name: this.name })
|
||||
this.$eventBus.$emit('showExportDialog', { selectedRows, url: this.tableUrl, name: this.name })
|
||||
}
|
||||
},
|
||||
hasImport: defaultTrue,
|
||||
@@ -85,17 +82,17 @@ export default {
|
||||
default: false
|
||||
},
|
||||
canBulkUpdate: {
|
||||
type: [Boolean, Function],
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultRightSideActions: [
|
||||
{ name: 'actionColumnSetting', fa: 'system-setting', tip: this.$t('common.CustomCol'), has: this.hasColumnSetting, callback: this.handleTableSettingClick.bind(this) },
|
||||
{ name: 'actionImport', fa: 'upload', tip: this.$t('common.Import'), has: this.hasImport, callback: this.handleImportClick.bind(this) },
|
||||
{ name: 'actionExport', fa: 'download', tip: this.$t('common.Export'), has: this.hasExport, callback: this.handleExportClick.bind(this) },
|
||||
{ name: 'actionRefresh', fa: 'refresh', tip: this.$t('common.Refresh'), has: this.hasRefresh, callback: this.handleRefreshClick.bind(this) }
|
||||
{ name: 'actionColumnSetting', fa: 'fa-cog', tip: this.$t('common.CustomCol'), has: this.hasColumnSetting, callback: this.handleTableSettingClick.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.handleExportClick.bind(this) },
|
||||
{ name: 'actionRefresh', fa: 'fa-refresh', tip: this.$t('common.Refresh'), has: this.hasRefresh, callback: this.handleRefreshClick.bind(this) }
|
||||
],
|
||||
dialogExportVisible: false
|
||||
}
|
||||
@@ -127,10 +124,6 @@ export default {
|
||||
methods: {
|
||||
handleTagSearch(val) {
|
||||
this.searchTable(val)
|
||||
},
|
||||
onImportDialogClose() {
|
||||
this.$emit('importDialogClose')
|
||||
this.reloadTable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +1,31 @@
|
||||
<template>
|
||||
<div :class="device" class="table-header clearfix">
|
||||
<div class="table-header clearfix" :class="device">
|
||||
<slot name="header">
|
||||
<LeftSide
|
||||
v-if="hasLeftActions"
|
||||
:selected-rows="selectedRows"
|
||||
:table-url="tableUrl"
|
||||
class="left-side"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
<RightSide
|
||||
v-if="hasRightActions"
|
||||
:selected-rows="selectedRows"
|
||||
:table-url="tableUrl"
|
||||
class="right-side"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
<div :class="searchClass" class="search">
|
||||
<AutoDataSearch
|
||||
v-if="hasSearch"
|
||||
class="right-side-item action-search"
|
||||
v-bind="iSearchTableConfig"
|
||||
@tagSearch="handleTagSearch"
|
||||
/>
|
||||
<DatetimeRangePicker
|
||||
v-if="hasDatePicker"
|
||||
class="datepicker"
|
||||
v-bind="datePicker"
|
||||
@dateChange="handleDateChange"
|
||||
/>
|
||||
<LeftSide v-if="hasLeftActions" class="left-side" :selected-rows="selectedRows" :table-url="tableUrl" v-bind="$attrs" v-on="$listeners" />
|
||||
<RightSide v-if="hasRightActions" class="right-side" :selected-rows="selectedRows" :table-url="tableUrl" v-bind="$attrs" v-on="$listeners" />
|
||||
<div class="search" :class="searchClass">
|
||||
<AutoDataSearch v-if="hasSearch" class="right-side-item action-search" v-bind="iSearchTableConfig" @tagSearch="handleTagSearch" />
|
||||
<DatetimeRangePicker v-if="hasDatePicker" v-bind="datePicker" class="datepicker" @dateChange="handleDateChange" />
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LeftSide from './LeftSide'
|
||||
import RightSide from './RightSide'
|
||||
import AutoDataSearch from '@/components/AutoDataSearch'
|
||||
import LeftSide from './LeftSide'
|
||||
import DatetimeRangePicker from '@/components/FormFields/DatetimeRangePicker'
|
||||
import { getDaysAgo, getDaysFuture } from '@/utils/common'
|
||||
import RightSide from './RightSide'
|
||||
|
||||
const defaultTrue = { type: Boolean, default: true }
|
||||
const defaultFalse = { type: Boolean, default: false }
|
||||
export default {
|
||||
name: 'TableAction',
|
||||
components: {
|
||||
LeftSide,
|
||||
RightSide,
|
||||
AutoDataSearch,
|
||||
DatetimeRangePicker
|
||||
LeftSide,
|
||||
DatetimeRangePicker,
|
||||
RightSide
|
||||
},
|
||||
props: {
|
||||
hasLeftActions: defaultTrue,
|
||||
@@ -59,10 +34,7 @@ export default {
|
||||
hasDatePicker: defaultFalse,
|
||||
datePicker: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
dateStart: getDaysAgo(7).toISOString(),
|
||||
dateEnd: getDaysFuture(1).toISOString()
|
||||
})
|
||||
default: () => ({})
|
||||
},
|
||||
searchConfig: {
|
||||
type: Object,
|
||||
@@ -74,13 +46,11 @@ export default {
|
||||
},
|
||||
datePick: {
|
||||
type: Function,
|
||||
default: (val) => {
|
||||
}
|
||||
default: (val) => {}
|
||||
},
|
||||
searchTable: {
|
||||
type: Function,
|
||||
default: (val) => {
|
||||
}
|
||||
default: (val) => {}
|
||||
},
|
||||
selectedRows: {
|
||||
type: Array,
|
||||
@@ -165,66 +135,53 @@ export default {
|
||||
display: flex;
|
||||
padding-left: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content:center;
|
||||
}
|
||||
|
||||
.table-action-right-side {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
justify-content:center;
|
||||
}
|
||||
|
||||
.export-item {
|
||||
display: block;
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
.datepicker {
|
||||
.datepicker{
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.left-side {
|
||||
float: left;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.right-side {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.mobile .search {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.mobile .search .datepicker {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.search.left {
|
||||
float: left;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.search.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.mobile .search.right {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.mobile .search.right .action-search {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mobile .right-side {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
@@ -1,35 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<TableAction
|
||||
v-if="hasActions"
|
||||
:date-pick="handleDateChange"
|
||||
:reload-table="reloadTable"
|
||||
:search-table="search"
|
||||
:selected-rows="selectedRows"
|
||||
:table-url="tableUrl"
|
||||
:search-table="search"
|
||||
:date-pick="handleDateChange"
|
||||
:selected-rows="selectedRows"
|
||||
:reload-table="reloadTable"
|
||||
v-bind="iHeaderActions"
|
||||
/>
|
||||
<IBox class="table-content">
|
||||
<AutoDataTable
|
||||
ref="dataTable"
|
||||
:config="iTableConfig"
|
||||
:filter-table="filter"
|
||||
v-on="$listeners"
|
||||
:config="iTableConfig"
|
||||
@selection-change="handleSelectionChange"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</IBox>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getResourceFromApiUrl } from '@/utils/jms'
|
||||
import deepmerge from 'deepmerge'
|
||||
import { mapGetters } from 'vuex'
|
||||
import AutoDataTable from '../AutoDataTable'
|
||||
import IBox from '../IBox'
|
||||
import TableAction from './TableAction'
|
||||
import Emitter from '@/mixins/emitter'
|
||||
import AutoDataTable from '../AutoDataTable'
|
||||
import { getDayEnd, getDaysAgo } from '@/utils/common'
|
||||
import { getResourceFromApiUrl } from '@/utils/jms'
|
||||
import deepmerge from 'deepmerge'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'ListTable',
|
||||
@@ -52,28 +50,10 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const order = this.$route?.params?.order
|
||||
let extraQuery = {
|
||||
...(order && { order })
|
||||
}
|
||||
if (this.headerActions.hasDatePicker) {
|
||||
extraQuery = {
|
||||
...extraQuery,
|
||||
date_from: getDaysAgo(7).toISOString(),
|
||||
date_to: getDayEnd().toISOString()
|
||||
}
|
||||
this.headerActions.datePicker = Object.assign({
|
||||
dateStart: extraQuery.date_from,
|
||||
dateEnd: extraQuery.date_to
|
||||
}, this.headerActions.datePicker)
|
||||
}
|
||||
if (this.$route.query.order) {
|
||||
extraQuery['order'] = this.$route.query.order
|
||||
}
|
||||
return {
|
||||
selectedRows: [],
|
||||
init: false,
|
||||
extraQuery: extraQuery
|
||||
extraQuery: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -99,9 +79,6 @@ export default {
|
||||
}
|
||||
return Object.assign(defaults, this.headerActions)
|
||||
},
|
||||
hasActions() {
|
||||
return this.iHeaderActions.has === undefined ? true : this.iHeaderActions.has
|
||||
},
|
||||
iTableConfig() {
|
||||
const config = deepmerge(this.tableConfig, {
|
||||
extraQuery: this.extraQuery
|
||||
@@ -130,7 +107,7 @@ export default {
|
||||
return config
|
||||
},
|
||||
tableUrl() {
|
||||
return this.tableConfig.url || ''
|
||||
return this.tableConfig.url
|
||||
},
|
||||
permissions() {
|
||||
// 获取 permissions,获取不到通过 url 解析
|
||||
@@ -217,32 +194,25 @@ export default {
|
||||
.table-content {
|
||||
margin-top: 10px;
|
||||
|
||||
& > > > .el-card__body {
|
||||
& >>> .el-card__body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
& > > > .el-table__header thead > tr > th {
|
||||
& >>> .el-table__header thead > tr > th {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
& > > > .el-table__row .cell {
|
||||
&>>> .el-table__row .cell {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
& > > > .el-table__expanded-cell pre {
|
||||
&>>> .el-table__expanded-cell pre {
|
||||
max-height: 500px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
& > > > .el-button-ungroup .el-dropdown > .more-action {
|
||||
height: 24.6px;
|
||||
}
|
||||
}
|
||||
|
||||
//修改颜色
|
||||
.el-button--text {
|
||||
color: #409EFF;
|
||||
}
|
||||
// .el-button--text{
|
||||
// color: #409EFF;
|
||||
// }
|
||||
</style>
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
<template>
|
||||
<div class="el-page">
|
||||
<el-pagination
|
||||
v-if="hasPagination"
|
||||
:current-page="page"
|
||||
:page-sizes="paginationSizes"
|
||||
:page-size="size"
|
||||
:total="total"
|
||||
:background="paginationBackground"
|
||||
:layout="paginationLayout"
|
||||
v-bind="extraPaginationAttrs"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const defaultFirstPage = 1
|
||||
|
||||
export default {
|
||||
name: 'Pagination',
|
||||
components: {},
|
||||
props: {
|
||||
hasPagination: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
firstPage: {
|
||||
type: Number,
|
||||
default: defaultFirstPage
|
||||
},
|
||||
pageSizeKey: {
|
||||
type: String,
|
||||
default: 'limit'
|
||||
},
|
||||
pageKey: {
|
||||
type: String,
|
||||
default: 'offset'
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
noPaginationSize: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
paginationSize: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
paginationSizes: {
|
||||
type: Array,
|
||||
default: () => [10, 20, 30, 40, 50, 100]
|
||||
},
|
||||
paginationBackground: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
paginationLayout: {
|
||||
type: String,
|
||||
default: 'total, sizes, prev, pager, next, jumper'
|
||||
},
|
||||
extraPaginationAttrs: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
transformQuery: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
size: this.paginationSize || this.paginationSizes[0]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSizeChange(val) {
|
||||
this.$emit('sizeChange', val)
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.$emit('currentSizeChange', val)
|
||||
},
|
||||
getPageQuery(currentPage, pageSize) {
|
||||
// 构造query对象
|
||||
let query = {}
|
||||
query[this.pageSizeKey] = this.hasPagination
|
||||
? pageSize
|
||||
: this.noPaginationSize
|
||||
|
||||
const offset = (currentPage - 1) * pageSize
|
||||
query[this.pageKey] = offset
|
||||
if (this.transformQuery) {
|
||||
query = this.transformQuery(query)
|
||||
}
|
||||
return query
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
>>> .el-pagination {
|
||||
text-align: right;
|
||||
}
|
||||
>>> .el-pagination__total {
|
||||
float: left;
|
||||
}
|
||||
</style>
|
||||
@@ -3,44 +3,23 @@
|
||||
<td>{{ action.title }}:</td>
|
||||
<td>
|
||||
<span>
|
||||
<component
|
||||
:is="iType"
|
||||
v-model="action.attrs.model"
|
||||
v-bind="action.attrs"
|
||||
v-on="callbacks"
|
||||
>
|
||||
{{ label }}
|
||||
</component>
|
||||
<component :is="iType" v-model="action.attrs.model" v-bind="action.attrs" v-on="callbacks">{{ label }}</component>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Switcher from '../FormFields/Switcher'
|
||||
import Select2 from '../FormFields/Select2'
|
||||
import UpdateSelect from '../FormFields/UpdateSelect'
|
||||
|
||||
class Action {
|
||||
constructor() {
|
||||
this.title = ''
|
||||
this.type = ''
|
||||
this.attrs = {}
|
||||
this.callbacks = ''
|
||||
}
|
||||
}
|
||||
|
||||
import Switcher from '../FormFields/Swicher'
|
||||
export default {
|
||||
name: 'ActionItem',
|
||||
components: {
|
||||
Switcher,
|
||||
Select2,
|
||||
UpdateSelect
|
||||
Switcher
|
||||
},
|
||||
props: {
|
||||
action: {
|
||||
type: [Action, Object],
|
||||
default: () => ({ title: '' })
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -51,14 +30,8 @@ export default {
|
||||
computed: {
|
||||
iType() {
|
||||
switch (this.action.type) {
|
||||
case 'switch':
|
||||
return 'Switcher'
|
||||
case 'switcher':
|
||||
return 'Switcher'
|
||||
case 'select2':
|
||||
return 'Select2'
|
||||
case 'updateSelect':
|
||||
return 'UpdateSelect'
|
||||
default:
|
||||
return 'el-button'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<IBox :type="type" :title="title" v-bind="$attrs">
|
||||
<IBox :fa="icon" :type="type" :title="title" v-bind="$attrs">
|
||||
<table style="width: 100%;table-layout:fixed;" class="CardTable">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
@@ -117,22 +117,6 @@ export default {
|
||||
that.$message.success(that.$t('common.RemoveSuccessMsg'))
|
||||
}
|
||||
},
|
||||
onDeleteFail: {
|
||||
type: Function,
|
||||
default(error, that) {
|
||||
let msg = ''
|
||||
const data = error.response.data
|
||||
for (const item of Object.keys(data)) {
|
||||
const value = data[item]
|
||||
if (value instanceof Array) {
|
||||
msg = value.join(',')
|
||||
} else {
|
||||
msg = value
|
||||
}
|
||||
}
|
||||
that.$message.error(msg)
|
||||
}
|
||||
},
|
||||
performAdd: {
|
||||
type: Function,
|
||||
default: (objects, that) => {}
|
||||
@@ -264,9 +248,7 @@ export default {
|
||||
removeObject(obj) {
|
||||
this.performDelete(obj, this).then(
|
||||
() => this.onDeleteSuccess(obj, this)
|
||||
).catch(error => {
|
||||
this.onDeleteFail(error, this)
|
||||
})
|
||||
)
|
||||
},
|
||||
addObjects() {
|
||||
const objects = this.$refs.select2.$refs.select.selected.map(item => ({ label: item.label, value: item.value }))
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="12" :sm="24">
|
||||
<IBox :title="title" class="block" v-bind="$attrs">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(activity, index) in activities"
|
||||
:key="index"
|
||||
:size="activity.size"
|
||||
:timestamp="activity.timestamp"
|
||||
:type="activity.type"
|
||||
placement="bottom"
|
||||
>
|
||||
{{ activity.content }}
|
||||
<el-link
|
||||
v-if="activity['detail_url']"
|
||||
type="primary"
|
||||
@click.native="onClick(activity)"
|
||||
>
|
||||
{{ $tc('common.Detail') }}
|
||||
</el-link>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</IBox>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<DiffDetail ref="DetailDialog" :title="$tc('route.OperateLog')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IBox from '@/components/IBox'
|
||||
import DiffDetail from '@/components/Dialog/DiffDetail'
|
||||
import { openTaskPage } from '@/utils/jms'
|
||||
|
||||
export default {
|
||||
name: 'ResourceActivity',
|
||||
components: {
|
||||
IBox,
|
||||
DiffDetail
|
||||
},
|
||||
props: {
|
||||
object: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activityUrl: `/api/v1/audits/activities/?resource_id=${this.object.id}`,
|
||||
title: `${this.$t('common.Activity')} - ${this.$t('common.Last30')}`,
|
||||
activities: [
|
||||
{
|
||||
content: this.$t('common.Now'),
|
||||
timestamp: this.$moment().format('YYYY-MM-DD HH:mm:ss'),
|
||||
type: 'primary'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getActivities()
|
||||
},
|
||||
methods: {
|
||||
getActivities() {
|
||||
this.$axios.get(this.activityUrl).then(res => {
|
||||
for (const i in res) {
|
||||
this.activities.push(res[i])
|
||||
}
|
||||
})
|
||||
},
|
||||
onClick(activity) {
|
||||
const type = activity['r_type']
|
||||
const taskUrl = activity['detail_url']
|
||||
if (type === 'O') {
|
||||
this.$axios.get(taskUrl).then(
|
||||
res => {
|
||||
this.$refs.DetailDialog.show(res.diff)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
openTaskPage('', 'celery', taskUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -59,7 +59,4 @@ export default {
|
||||
mask-size: cover!important;
|
||||
display: inline-block;
|
||||
}
|
||||
::v-deep path {
|
||||
fill: inherit!important;
|
||||
}
|
||||
</style>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user