Compare commits
81 Commits
pr@dev@fix
...
pam_new_dr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dfca604c2 | ||
|
|
b4abcd4c90 | ||
|
|
d62c87b858 | ||
|
|
743187b4b3 | ||
|
|
5c1f24af6a | ||
|
|
9477bfa2c1 | ||
|
|
1ba58f476f | ||
|
|
02600d7a1b | ||
|
|
2126c92e07 | ||
|
|
af774a8835 | ||
|
|
b93fad1f91 | ||
|
|
ef43be0cb7 | ||
|
|
8b6eea0267 | ||
|
|
d0da22738f | ||
|
|
18eda16851 | ||
|
|
610e9b9efa | ||
|
|
a0c7d60719 | ||
|
|
60ba0d8f02 | ||
|
|
b9afb05f1b | ||
|
|
fcf9ea2b79 | ||
|
|
430b1117c9 | ||
|
|
a65023c8f7 | ||
|
|
d6de85ffdd | ||
|
|
5f11d8b54f | ||
|
|
c7ce602d4c | ||
|
|
d0988da277 | ||
|
|
7f13ef35a7 | ||
|
|
44348de4ab | ||
|
|
be82fe1bde | ||
|
|
2e472dad93 | ||
|
|
bbfb237f23 | ||
|
|
390224613a | ||
|
|
7e0c677ad3 | ||
|
|
9936f2b806 | ||
|
|
bfde8bd28b | ||
|
|
4b309d950c | ||
|
|
688f06ebac | ||
|
|
6fb3b552eb | ||
|
|
f27e7bdad4 | ||
|
|
2ee139c92b | ||
|
|
7a6c156aaa | ||
|
|
11d40b4be1 | ||
|
|
b772580f99 | ||
|
|
fafdb6be5b | ||
|
|
2f984530e3 | ||
|
|
68b39bbc3d | ||
|
|
d21dc57305 | ||
|
|
c58d826898 | ||
|
|
7e80361635 | ||
|
|
b25d2016a6 | ||
|
|
636630fe57 | ||
|
|
e18726efc2 | ||
|
|
823c26aa5e | ||
|
|
c0c9d56408 | ||
|
|
ff4adde897 | ||
|
|
45ae7cab21 | ||
|
|
429f5aed90 | ||
|
|
d8999ffc06 | ||
|
|
fd745f0a26 | ||
|
|
322d12f27f | ||
|
|
a1bc8ac5bc | ||
|
|
9271cb2e1a | ||
|
|
0e7c682f72 | ||
|
|
c0b4029917 | ||
|
|
b1acb62889 | ||
|
|
26fd9b1813 | ||
|
|
4fabdfdc5f | ||
|
|
6e894c31a1 | ||
|
|
d8a6fd96ce | ||
|
|
7c5c5f966d | ||
|
|
8b25fd198e | ||
|
|
762fa4c17e | ||
|
|
73cc319e7b | ||
|
|
d90aba37cf | ||
|
|
d775ffa501 | ||
|
|
e8cf8347e9 | ||
|
|
7ff1da71d4 | ||
|
|
a23a0d0197 | ||
|
|
1477712c78 | ||
|
|
77a0100add | ||
|
|
833e44024f |
@@ -120,7 +120,7 @@
|
||||
"serve-static": "^1.13.2",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"svg-sprite-loader": "4.1.3",
|
||||
"svgo": "1.2.2",
|
||||
"svgo": "1.2.4",
|
||||
"vue-i18n-extract": "^1.1.1",
|
||||
"vue-template-compiler": "2.6.10"
|
||||
},
|
||||
|
||||
@@ -2,37 +2,70 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Cache-control" content="no-cache">
|
||||
<meta http-equiv="Cache" content="no-cache">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
|
||||
<meta content="0" http-equiv="Expires">
|
||||
<meta content="no-cache" http-equiv="Pragma">
|
||||
<meta content="no-cache" http-equiv="Cache-control">
|
||||
<meta content="no-cache" http-equiv="Cache">
|
||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||
<title><%= webpackConfig.name %></title>
|
||||
<link rel="stylesheet" href="<%= BASE_URL %>theme/element-ui.css">
|
||||
<link href="<%= BASE_URL %>theme/element-ui.css" rel="stylesheet">
|
||||
<style>
|
||||
#loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 5px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #3498db;
|
||||
animation: spin 1s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</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
|
||||
}
|
||||
if (pathname.startsWith('/ui/#/chat')) {
|
||||
window.location.href = window.location.origin + pathname
|
||||
}
|
||||
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
|
||||
}
|
||||
if (pathname.startsWith('/ui/#/chat')) {
|
||||
window.location.href = window.location.origin + pathname
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div id="app"></div>
|
||||
<div id="app">
|
||||
</div>
|
||||
<div id="loading">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -163,7 +163,8 @@ export const accountFieldsMeta = (vm) => {
|
||||
!automation.ansible_enabled ||
|
||||
!vm.$hasPerm('accounts.push_account') ||
|
||||
(formValue.secret_type === 'ssh_key' && vm.iPlatform.type.value === 'windows') ||
|
||||
vm.addTemplate
|
||||
vm.addTemplate ||
|
||||
!formValue.secret_reset
|
||||
}
|
||||
},
|
||||
params: {
|
||||
|
||||
@@ -65,7 +65,8 @@ export default {
|
||||
[this.$t('Basic'), ['assets', 'name', 'username', 'privileged', 'su_from', 'su_from_username']],
|
||||
[this.$t('Secret'), [
|
||||
'secret_type', 'password', 'ssh_key', 'token',
|
||||
'access_key', 'passphrase', 'api_key'
|
||||
'access_key', 'passphrase', 'api_key',
|
||||
'secret_reset'
|
||||
]],
|
||||
[this.$t('Other'), ['push_now', 'params', 'on_invalid', 'is_active', 'comment']]
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<template v-if="iVisible">
|
||||
<Dialog
|
||||
v-if="iVisible"
|
||||
v-if="origin !== 'pam'"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
:show-cancel="false"
|
||||
@@ -21,15 +21,33 @@
|
||||
@edit="editAccount"
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
<Drawer
|
||||
v-else
|
||||
:title="title"
|
||||
@close-drawer="iVisible = false"
|
||||
>
|
||||
<AccountCreateUpdateForm
|
||||
v-if="!loading"
|
||||
ref="form"
|
||||
:account="account"
|
||||
:add-template="addTemplate"
|
||||
:asset="asset"
|
||||
@add="addAccount"
|
||||
@edit="editAccount"
|
||||
/>
|
||||
</Drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Drawer from '@/components/Drawer/index.vue'
|
||||
import Dialog from '@/components/Dialog/index.vue'
|
||||
import AccountCreateUpdateForm from '@/components/Apps/AccountCreateUpdateForm/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'CreateAccountDialog',
|
||||
components: {
|
||||
Drawer,
|
||||
Dialog,
|
||||
AccountCreateUpdateForm
|
||||
},
|
||||
@@ -55,6 +73,10 @@ export default {
|
||||
default: function() {
|
||||
return this.$t('AddAccount')
|
||||
}
|
||||
},
|
||||
origin: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -106,6 +128,8 @@ export default {
|
||||
}
|
||||
}).catch(error => {
|
||||
this.iVisible = true
|
||||
console.log(this.iVisible)
|
||||
console.log(this.origin)
|
||||
this.handleResult(null, error)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<ListTable ref="ListTable" :header-actions="headerActions" :table-config="tableConfig" />
|
||||
<ListTable
|
||||
ref="ListTable"
|
||||
:header-actions="headerActions"
|
||||
:quick-filters="quickFilters"
|
||||
:table-config="tableConfig"
|
||||
/>
|
||||
<ViewSecret
|
||||
v-if="showViewSecretDialog"
|
||||
:account="account"
|
||||
@@ -17,6 +22,7 @@
|
||||
v-if="showAddDialog"
|
||||
:account="account"
|
||||
:asset="iAsset"
|
||||
:origin="origin"
|
||||
:title="accountCreateUpdateTitle"
|
||||
:visible.sync="showAddDialog"
|
||||
@add="addAccountSuccess"
|
||||
@@ -27,6 +33,7 @@
|
||||
:account="account"
|
||||
:add-template="true"
|
||||
:asset="iAsset"
|
||||
:origin="origin"
|
||||
:title="accountCreateByTemplateTitle"
|
||||
:visible.sync="showAddTemplateDialog"
|
||||
@add="addAccountSuccess"
|
||||
@@ -49,7 +56,9 @@
|
||||
|
||||
<script>
|
||||
import ListTable from '@/components/Table/ListTable/index.vue'
|
||||
import { ActionsFormatter } from '@/components/Table/TableFormatters'
|
||||
import {
|
||||
ActionsFormatter, PlatformFormatter, SecretViewerFormatter
|
||||
} from '@/components/Table/TableFormatters'
|
||||
import ViewSecret from './ViewSecret.vue'
|
||||
import UpdateSecretInfo from './UpdateSecretInfo.vue'
|
||||
import AccountCreateUpdate from './AccountCreateUpdate.vue'
|
||||
@@ -89,7 +98,7 @@ export default {
|
||||
},
|
||||
hasClone: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: true
|
||||
},
|
||||
asset: {
|
||||
type: Object,
|
||||
@@ -119,7 +128,7 @@ export default {
|
||||
columnsDefault: {
|
||||
type: Array,
|
||||
default: () => ([
|
||||
'name', 'username', 'asset', 'date_updated'
|
||||
'name', 'username', 'secret', 'asset', 'platform', 'date_updated', 'connect'
|
||||
])
|
||||
},
|
||||
headerExtraActions: {
|
||||
@@ -129,6 +138,10 @@ export default {
|
||||
extraQuery: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
origin: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -145,6 +158,131 @@ export default {
|
||||
iAsset: this.asset,
|
||||
account: {},
|
||||
secretUrl: '',
|
||||
quickFilters: [
|
||||
{
|
||||
label: '最近(7天)',
|
||||
options: [
|
||||
{
|
||||
label: '最近发现',
|
||||
filter: {
|
||||
latest_discovery: '1'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '最近被登录',
|
||||
filter: {
|
||||
latest_accessed: '1'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '最近修改',
|
||||
filter: {
|
||||
latest_updated: '1'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '最近改密',
|
||||
filter: {
|
||||
latest_secret_changed: '1'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '最近改密失败',
|
||||
filter: {
|
||||
latest_secret_changed_failed: '1'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '风险账号',
|
||||
options: [
|
||||
{
|
||||
label: '僵尸账号',
|
||||
filter: {
|
||||
risk: 'long_time_no_login'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '幽灵账号',
|
||||
filter: {
|
||||
risk: 'new_found'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '弱密码',
|
||||
filter: {
|
||||
risk: 'week_password'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '空密码',
|
||||
filter: {
|
||||
has_secret: 'false'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '长时间未改密',
|
||||
filter: {
|
||||
long_time_no_change_secret: 'true'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '长时间未验证',
|
||||
filter: {
|
||||
long_time_no_verify: 'true'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '账号类型',
|
||||
options: [
|
||||
{
|
||||
label: '全部',
|
||||
filter: {
|
||||
category: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
label: ' 主机',
|
||||
filter: {
|
||||
category: 'host'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '数据库',
|
||||
filter: {
|
||||
category: 'database'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '云',
|
||||
filter: {
|
||||
category: 'cloud'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '网络设备',
|
||||
filter: {
|
||||
category: 'device'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Web',
|
||||
filter: {
|
||||
category: 'website'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '其他',
|
||||
filter: {
|
||||
category: 'custom'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
tableConfig: {
|
||||
url: this.url,
|
||||
permissions: {
|
||||
@@ -153,6 +291,7 @@ export default {
|
||||
},
|
||||
extraQuery: this.extraQuery,
|
||||
columnsExclude: ['spec_info'],
|
||||
columnsAdd: ['secret', 'platform', 'connect'],
|
||||
columnsShow: {
|
||||
min: ['name', 'username', 'actions'],
|
||||
default: this.columnsDefault
|
||||
@@ -160,7 +299,7 @@ export default {
|
||||
columnsMeta: {
|
||||
name: {
|
||||
width: '120px',
|
||||
formatter: function(row) {
|
||||
formatter: (row) => {
|
||||
const to = {
|
||||
name: 'AssetAccountDetail',
|
||||
params: { id: row.id }
|
||||
@@ -172,8 +311,29 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
secret: {
|
||||
formatter: SecretViewerFormatter,
|
||||
formatterArgs: {
|
||||
secretFrom: 'api',
|
||||
hasDownload: false,
|
||||
actionLeft: true
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
label: this.$t('Connect'),
|
||||
width: '80px',
|
||||
formatter: () => {
|
||||
return (
|
||||
<span className='connect'>
|
||||
<el-button type='primary' size='mini' plain>
|
||||
<i className='fa fa-desktop'/>
|
||||
</el-button>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
},
|
||||
asset: {
|
||||
formatter: function(row) {
|
||||
formatter: (row) => {
|
||||
const to = {
|
||||
name: 'AssetDetail',
|
||||
params: { id: row.asset.id }
|
||||
@@ -185,6 +345,13 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
platform: {
|
||||
label: this.$t('Platform'),
|
||||
formatter: PlatformFormatter,
|
||||
formatterArgs: {
|
||||
platformAttr: 'asset.platform'
|
||||
}
|
||||
},
|
||||
username: {
|
||||
width: '120px'
|
||||
},
|
||||
@@ -217,7 +384,8 @@ export default {
|
||||
formatterArgs: {
|
||||
hasUpdate: false, // can set function(row, value)
|
||||
hasDelete: false, // can set function(row, value)
|
||||
hasClone: this.hasClone,
|
||||
hasClone: false,
|
||||
canClone: true,
|
||||
moreActionsTitle: this.$t('More'),
|
||||
extraActions: [
|
||||
{
|
||||
@@ -255,7 +423,7 @@ export default {
|
||||
},
|
||||
{
|
||||
name: 'Test',
|
||||
title: this.$t('Test'),
|
||||
title: this.$t('验证密文'),
|
||||
can: ({ row }) =>
|
||||
!this.$store.getters.currentOrgIsRoot &&
|
||||
this.$hasPerm('accounts.verify_account') &&
|
||||
@@ -283,6 +451,26 @@ export default {
|
||||
this.$message.success(this.$tc('ClearSuccessMsg'))
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'SecretHistory',
|
||||
title: '密文历史'
|
||||
},
|
||||
{
|
||||
name: 'CopyToOther',
|
||||
title: '复制到其他资产',
|
||||
type: 'primary',
|
||||
divided: true
|
||||
},
|
||||
{
|
||||
name: 'MoveToOther',
|
||||
title: '移动到其他资产',
|
||||
type: 'primary'
|
||||
},
|
||||
{
|
||||
name: 'Clone',
|
||||
title: this.$t('Duplicate'),
|
||||
divided: true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -350,8 +538,8 @@ export default {
|
||||
icon: 'fa-link',
|
||||
can: ({ selectedRows }) => {
|
||||
return selectedRows.length > 0 &&
|
||||
['clickhouse', 'redis', 'website', 'chatgpt'].indexOf(selectedRows[0].asset.type.value) === -1 &&
|
||||
!this.$store.getters.currentOrgIsRoot
|
||||
['clickhouse', 'redis', 'website', 'chatgpt'].indexOf(selectedRows[0].asset.type.value) === -1 &&
|
||||
!this.$store.getters.currentOrgIsRoot
|
||||
},
|
||||
callback: function({ selectedRows }) {
|
||||
const ids = selectedRows.map(v => {
|
||||
@@ -511,4 +699,5 @@ export default {
|
||||
.cell a {
|
||||
color: var(--color-info);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<script>
|
||||
import { GenericListTableDialog } from '@/layout/components'
|
||||
import { ShowKeyCopyFormatter } from '@/components/Table/TableFormatters'
|
||||
import { SecretViewerFormatter } from '@/components/Table/TableFormatters'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
columnsMeta: {
|
||||
secret: {
|
||||
label: this.$t('Password'),
|
||||
formatter: ShowKeyCopyFormatter,
|
||||
formatter: SecretViewerFormatter,
|
||||
formatterArgs: {
|
||||
hasDownload: false,
|
||||
name: this.account.name
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<span>{{ account['username'] }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="secretTypeLabel">
|
||||
<ShowKeyCopyFormatter
|
||||
<SecretViewerFormatter
|
||||
:cell-value="secretInfo.secret"
|
||||
:col="{ formatterArgs: {
|
||||
name: account['name'],
|
||||
@@ -60,7 +60,7 @@
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog/index.vue'
|
||||
import PasswordHistoryDialog from './PasswordHistoryDialog.vue'
|
||||
import { ShowKeyCopyFormatter } from '@/components/Table/TableFormatters'
|
||||
import { SecretViewerFormatter } from '@/components/Table/TableFormatters'
|
||||
import { encryptPassword } from '@/utils/crypto'
|
||||
|
||||
export default {
|
||||
@@ -68,7 +68,7 @@ export default {
|
||||
components: {
|
||||
Dialog,
|
||||
PasswordHistoryDialog,
|
||||
ShowKeyCopyFormatter
|
||||
SecretViewerFormatter
|
||||
},
|
||||
props: {
|
||||
account: {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
>
|
||||
<el-form :model="secretInfo" class="password-form" label-position="right" label-width="100px">
|
||||
<el-form-item :label="$tc('OldSecret')">
|
||||
<ShowKeyCopyFormatter
|
||||
<SecretViewerFormatter
|
||||
:cell-value="secretInfo.old_secret"
|
||||
:col="{ formatterArgs: {
|
||||
name: 'old_secret'
|
||||
@@ -20,7 +20,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$tc('NewSecret')">
|
||||
<ShowKeyCopyFormatter
|
||||
<SecretViewerFormatter
|
||||
:cell-value="secretInfo.new_secret"
|
||||
:col="{ formatterArgs: {
|
||||
name: 'new_secret'
|
||||
@@ -34,13 +34,13 @@
|
||||
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog/index.vue'
|
||||
import { ShowKeyCopyFormatter } from '@/components/Table/TableFormatters'
|
||||
import { SecretViewerFormatter } from '@/components/Table/TableFormatters'
|
||||
|
||||
export default {
|
||||
name: 'RecordViewSecret',
|
||||
components: {
|
||||
Dialog,
|
||||
ShowKeyCopyFormatter
|
||||
SecretViewerFormatter
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="16" :sm="24">
|
||||
<el-col :md="16" :sm="24" class="resource-activity">
|
||||
<IBox :title="title" class="block" v-bind="$attrs">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<div slot="header" class="summary-header">
|
||||
<span class="header-title">{{ title }}</span>
|
||||
<div>
|
||||
<div class="summary-header">
|
||||
<span class="title">{{ title }}</span>
|
||||
</div>
|
||||
<slot>
|
||||
<h1 class="no-margins">
|
||||
<span v-if="body.disabled" class="disabled-link">{{ body.count }}</span>
|
||||
<router-link v-else :to="body.route">
|
||||
<span>{{ body.count }}</span>
|
||||
</router-link>
|
||||
</h1>
|
||||
<small>{{ body.comment }}</small>
|
||||
<h3 class="no-margins ">
|
||||
<span v-async="iCount" class="num" @click="handleClick">
|
||||
-
|
||||
</span>
|
||||
</h3>
|
||||
</slot>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'SummaryCard',
|
||||
props: {
|
||||
@@ -23,56 +22,86 @@ export default {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
rightSideLabel: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
body: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
count: {
|
||||
type: [Number, String, Promise],
|
||||
default: 0
|
||||
},
|
||||
route: {
|
||||
type: [String, Object],
|
||||
default: ''
|
||||
},
|
||||
callback: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
iCount() {
|
||||
const count = this.body.count || this.count
|
||||
return count
|
||||
},
|
||||
iRoute() {
|
||||
return this.body.route || this.route
|
||||
},
|
||||
iDisabled() {
|
||||
return this.body.disabled === undefined ? this.disabled : this.body.disabled
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
if (this.iDisabled) {
|
||||
return
|
||||
}
|
||||
if (this.iRoute) {
|
||||
this.$router.push(this.iRoute)
|
||||
return
|
||||
}
|
||||
this.callback.bind(this)()
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pull-right {
|
||||
float: right !important;
|
||||
}
|
||||
.summary-header {
|
||||
//color: var(--color-icon-primary);
|
||||
|
||||
.header-title {
|
||||
font-size: 14px;
|
||||
margin: 0 0 7px;
|
||||
.title {
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
.right-side ::v-deep .el-tag {
|
||||
font-weight: 600;
|
||||
padding: 3px 8px;
|
||||
text-shadow: none;
|
||||
line-height: 1;
|
||||
}
|
||||
.no-margins {
|
||||
margin: 0 !important;
|
||||
|
||||
h1 {
|
||||
font-size: 30px;
|
||||
font-weight: 100;
|
||||
}
|
||||
.num {
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
line-height: 40px;
|
||||
color: var(--color-text-primary);
|
||||
cursor: pointer;
|
||||
|
||||
.el-card__body {
|
||||
background-color: #ffffff;
|
||||
color: inherit;
|
||||
padding: 15px 20px 20px 20px !important;
|
||||
border-color: #e7eaec;
|
||||
border-image: none;
|
||||
border-style: solid solid none;
|
||||
border-width: 1px 0;
|
||||
}
|
||||
|
||||
.no-margins {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.disabled-link {
|
||||
color: #428bca;
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
trigger="click"
|
||||
@command="handleDropdownCallback"
|
||||
>
|
||||
<el-button :size="size" class="more-action" v-bind="cleanButtonAction(action)">
|
||||
<span v-if="action.icon && !action.icon.startsWith('el-')" class="pre-icon">
|
||||
<i v-if="action.icon.startsWith('fa')" :class="'fa fa-fw ' + action.icon" />
|
||||
<svg-icon v-else :icon-class="action.icon" />
|
||||
<el-button :class="action.name" :size="size" class="more-action" v-bind="{...cleanButtonAction(action), icon: ''}">
|
||||
<span v-if="action.icon" class="pre-icon">
|
||||
<Icon :icon="action.icon" />
|
||||
</span>
|
||||
<span v-if="action.title">
|
||||
{{ action.title }}<i class="el-icon-arrow-down el-icon--right" />
|
||||
@@ -29,7 +28,13 @@
|
||||
>
|
||||
{{ option.group }}
|
||||
</div>
|
||||
<el-tooltip :key="option.name" :content="option.tip" :disabled="!option.tip" :open-delay="500" placement="top">
|
||||
<el-tooltip
|
||||
:key="option.name"
|
||||
:content="option.tip"
|
||||
:disabled="!option.tip"
|
||||
:open-delay="500"
|
||||
placement="top"
|
||||
>
|
||||
<el-dropdown-item
|
||||
:key="option.name"
|
||||
:command="[option, action]"
|
||||
@@ -38,8 +43,7 @@
|
||||
v-bind="{...option, icon: ''}"
|
||||
>
|
||||
<span v-if="option.icon" class="pre-icon">
|
||||
<i v-if="option.icon.startsWith('fa')" :class="'fa fa-fw ' + option.icon" />
|
||||
<svg-icon v-else :icon-class="option.icon" />
|
||||
<Icon :icon="option.icon" />
|
||||
</span>
|
||||
{{ option.title }}
|
||||
</el-dropdown-item>
|
||||
@@ -51,16 +55,16 @@
|
||||
<el-button
|
||||
v-else
|
||||
:key="action.name"
|
||||
:class="action.name"
|
||||
:size="size"
|
||||
class="action-item"
|
||||
v-bind="{...cleanButtonAction(action), icon: action.icon && action.icon.startsWith('el-') ? action.icon : ''}"
|
||||
v-bind="{...cleanButtonAction(action), icon: ''}"
|
||||
@click="handleClick(action)"
|
||||
>
|
||||
<el-tooltip :content="action.tip" :disabled="!action.tip" :open-delay="500" placement="top">
|
||||
<span>
|
||||
<span v-if="action.icon && !action.icon.startsWith('el-')" style="vertical-align: initial">
|
||||
<i v-if="action.icon.startsWith('fa')" :class="'fa ' + action.icon" />
|
||||
<svg-icon v-else :icon-class="action.icon" />
|
||||
<span v-if="action.icon" style="vertical-align: initial">
|
||||
<Icon :icon="action.icon" />
|
||||
</span>
|
||||
{{ action.title }}
|
||||
</span>
|
||||
@@ -72,9 +76,13 @@
|
||||
|
||||
<script>
|
||||
import { toSentenceCase } from '@/utils/common'
|
||||
import Icon from '@/components/Widgets/Icon/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'DataActions',
|
||||
components: {
|
||||
Icon
|
||||
},
|
||||
props: {
|
||||
grouped: {
|
||||
type: Boolean,
|
||||
@@ -252,7 +260,10 @@ $color-drop-menu-border: #e4e7ed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 6px;
|
||||
color: $btn-text-color;
|
||||
|
||||
&:not(.is-plain) {
|
||||
color: $btn-text-color;
|
||||
}
|
||||
|
||||
* {
|
||||
vertical-align: baseline !important;
|
||||
|
||||
62
src/components/Dialog/ProcessingDialog.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:visible="iVisible"
|
||||
height="300"
|
||||
title="Processing"
|
||||
width="300"
|
||||
>
|
||||
<div id="load">
|
||||
<div class="spinner" />
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dialog from './index.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProcessingDialog',
|
||||
components: { Dialog },
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
iVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.spinner {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 5px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top-color: #3498db;
|
||||
animation: spin 1s infinite linear;
|
||||
}
|
||||
|
||||
#load {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
96
src/components/Dialog/Secret.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:show-cancel="false"
|
||||
:title="title"
|
||||
:visible.sync="visible"
|
||||
:close-on-click-modal="false"
|
||||
width="700px"
|
||||
@close="onClose"
|
||||
@confirm="visible = false"
|
||||
>
|
||||
<el-alert type="warning" :closable="false">
|
||||
{{ warningText }}
|
||||
<div class="secret">
|
||||
<div class="row">
|
||||
<span class="col">ID:</span>
|
||||
<span class="value">{{ keyInfo.id }}</span>
|
||||
<i class="el-icon-copy-document copy-icon" @click="handleCopy(keyInfo.id)" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="col">Secret:</span>
|
||||
<span class="value">{{ keyInfo.secret }}</span>
|
||||
<i class="el-icon-copy-document copy-icon" @click="handleCopy(keyInfo.secret)" />
|
||||
</div>
|
||||
</div>
|
||||
</el-alert>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from '@/i18n/i18n'
|
||||
import { copy } from '@/utils/common'
|
||||
import Dialog from '@/components/Dialog/index'
|
||||
|
||||
export default {
|
||||
name: 'Secret',
|
||||
components: {
|
||||
Dialog
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: () => i18n.t('CreateAccessKey')
|
||||
},
|
||||
warningText: {
|
||||
type: String,
|
||||
default: () => i18n.t('ApiKeyWarning')
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyInfo: { id: '', secret: '' },
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
show(data) {
|
||||
this.keyInfo = data
|
||||
this.visible = true
|
||||
},
|
||||
onClose() {
|
||||
this.$emit('close')
|
||||
},
|
||||
handleCopy(value) {
|
||||
copy(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.secret {
|
||||
color: #2b2f3a;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.col {
|
||||
width: 100px;
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.copy-icon {
|
||||
margin-left: 5px;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
107
src/components/Drawer/index.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
ref="drawer"
|
||||
size="61.8%"
|
||||
direction="rtl"
|
||||
custom-class="drawer"
|
||||
:title="title"
|
||||
:before-close="handleClose"
|
||||
:visible.sync="dialog"
|
||||
:wrapper-closable="true"
|
||||
:append-to-body="true"
|
||||
>
|
||||
<div class="drawer__content">
|
||||
<slot name="default" />
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return this.$t('AddAccount')
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: true,
|
||||
loading: false,
|
||||
formLabelWidth: '80px'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose(done) {
|
||||
this.$emit('close-drawer')
|
||||
done()
|
||||
},
|
||||
cancelForm() {
|
||||
this.loading = false
|
||||
this.dialog = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang='scss'>
|
||||
::v-deep .drawer {
|
||||
min-width: 565px;
|
||||
|
||||
.el-drawer__header {
|
||||
margin-bottom: 20px;
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.el-drawer__body {
|
||||
|
||||
.drawer__content {
|
||||
height: 100%;
|
||||
|
||||
.el-form {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
margin: unset !important;
|
||||
padding-right: 50px;
|
||||
height: 100%;
|
||||
|
||||
.el-form-item {
|
||||
margin: 5px 0 5px 0;
|
||||
}
|
||||
|
||||
.form-buttons {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
padding: 0 10px 0 10px;
|
||||
|
||||
.el-form-item__content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin: unset !important;
|
||||
justify-content: center;
|
||||
|
||||
.el-button {
|
||||
width: 50%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,6 @@
|
||||
import Vue from 'vue'
|
||||
import ObjectSelect2 from '@/components/Form/FormFields/NestedObjectSelect2.vue'
|
||||
import NestedField from '@/components/Form/AutoDataForm/components/NestedField.vue'
|
||||
import Switcher from '@/components/Form/FormFields/Switcher.vue'
|
||||
import rules from '@/components/Form/DataForm/rules'
|
||||
import BasicTree from '@/components/Form/FormFields/BasicTree.vue'
|
||||
import JsonEditor from '@/components/Form/FormFields/JsonEditor.vue'
|
||||
@@ -65,8 +64,9 @@ export class FormFieldGenerator {
|
||||
}
|
||||
break
|
||||
case 'boolean':
|
||||
type = ''
|
||||
field.component = Switcher
|
||||
type = 'checkbox'
|
||||
// field.component = Switcher
|
||||
// field.type = 'checkbox'
|
||||
break
|
||||
case 'list':
|
||||
type = 'input'
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
<el-tooltip v-if="opt.tip" :content="opt.tip" :open-delay="500" placement="top">
|
||||
<i class="el-icon-warning-outline" />
|
||||
</el-tooltip>
|
||||
<span v-if="data.helpText">{{ data.helpText }}</span>
|
||||
</el-checkbox>
|
||||
<!-- WARNING: radio 用 label 属性来表示 value 的含义 -->
|
||||
<!-- FYI: radio 的 value 属性可以在没有 radio-group 时用来关联到同一个 v-model -->
|
||||
@@ -87,7 +88,7 @@
|
||||
</el-radio>
|
||||
</template>
|
||||
</custom-component>
|
||||
<div v-if="data.helpText" class="help-block">
|
||||
<div v-if="data.helpText" :class="data.type" class="help-block">
|
||||
<el-alert
|
||||
v-if="data.helpText.startsWith('!')"
|
||||
:closable="false"
|
||||
@@ -315,6 +316,10 @@ export default {
|
||||
::v-deep .el-alert__icon {
|
||||
font-size: 16px
|
||||
}
|
||||
|
||||
&.checkbox {
|
||||
//display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.help-tip-icon {
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
v-bind="iAttrs"
|
||||
@input="handleInput"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
>
|
||||
hello
|
||||
</Password>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -54,22 +54,22 @@ export default {
|
||||
{
|
||||
id: 'uppercase',
|
||||
label: this.$t('Uppercase'),
|
||||
type: 'switch'
|
||||
type: 'checkbox'
|
||||
},
|
||||
{
|
||||
id: 'lowercase',
|
||||
label: this.$t('Lowercase'),
|
||||
type: 'switch'
|
||||
type: 'checkbox'
|
||||
},
|
||||
{
|
||||
id: 'digit',
|
||||
label: this.$t('Digit'),
|
||||
type: 'switch'
|
||||
type: 'checkbox'
|
||||
},
|
||||
{
|
||||
id: 'symbol',
|
||||
label: this.$t('SpecialSymbol'),
|
||||
type: 'switch'
|
||||
type: 'checkbox'
|
||||
},
|
||||
{
|
||||
id: 'exclude_symbols',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="update-token">
|
||||
<el-button v-show="!isShow" icon="el-icon-edit" type="text" @click="isShow=true">
|
||||
{{ text }}
|
||||
</el-button>
|
||||
@@ -8,14 +8,19 @@
|
||||
v-model.trim="curValue"
|
||||
:placeholder="placeholder"
|
||||
:type="type"
|
||||
autocomplete="new-password"
|
||||
class="password-input"
|
||||
show-password
|
||||
@change="onChange"
|
||||
/>
|
||||
<el-button size="small" type="text" @click="randomPassword">
|
||||
<i class="fa fa-retweet" />
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { randomString } from '@/utils/string'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
@@ -55,7 +60,24 @@ export default {
|
||||
methods: {
|
||||
onChange(e) {
|
||||
this.$emit('input', this.curValue)
|
||||
},
|
||||
randomPassword() {
|
||||
this.curValue = randomString(24, true)
|
||||
this.$emit('input', this.curValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='scss' scoped>
|
||||
.password-input {
|
||||
width: calc(100% - 50px);
|
||||
}
|
||||
|
||||
.update-token {
|
||||
i {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div v-if="tip !== ''" class="help-block">{{ tip }}</div>
|
||||
<input v-model="value" hidden type="text" v-on="$listeners">
|
||||
<div>
|
||||
<img :class="showBG ? 'show-bg' : ''" :src="preview" v-bind="$attrs">
|
||||
<img v-if="preview" :class="showBG ? 'show-bg' : ''" :src="preview" v-bind="$attrs" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -348,6 +348,8 @@ export default {
|
||||
|
||||
let configColumns = config.columns || allColumnNames
|
||||
const columnsExclude = config.columnsExclude || []
|
||||
const columnsAdd = config.columnsAdd || []
|
||||
configColumns = configColumns.concat(columnsAdd)
|
||||
configColumns = configColumns.filter(item => !columnsExclude.includes(item))
|
||||
|
||||
// 解决后端 API 返回字段中包含 actions 的问题;
|
||||
|
||||
175
src/components/Table/CardTable/DataCardTable/CardPanel.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div class="account-panel">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="21">
|
||||
<div class="title">
|
||||
<span>{{ object.name }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col v-if="iActions.length !== 0" :span="3" @click.native="handleClick($event)">
|
||||
<el-dropdown>
|
||||
<el-link :underline="false" type="primary">
|
||||
<i class="el-icon-more el-icon--right" style="color: var(--color-text-primary)" />
|
||||
</el-link>
|
||||
<el-dropdown-menu default="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="action in iActions"
|
||||
:key="action.name"
|
||||
:disabled="action.disabled"
|
||||
@click.native="action.callback(object)"
|
||||
>
|
||||
<i v-if="action.icon" :class="action.icon" /> {{ action.name }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" class="panel-content">
|
||||
<el-col :span="6" class="panel-image">
|
||||
<el-image :src="imageUrl" fit="contain" />
|
||||
</el-col>
|
||||
<el-col :span="18" class="panel-info">
|
||||
<InfoPanel
|
||||
v-for="(obj, index) in getInfos(object)"
|
||||
:key="index"
|
||||
:content="obj.content"
|
||||
:title="obj.title"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InfoPanel from './InfoPanel'
|
||||
|
||||
export default {
|
||||
name: 'CardPanel',
|
||||
components: {
|
||||
InfoPanel
|
||||
},
|
||||
props: {
|
||||
tableConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
object: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
actions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
infos: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
getImage: {
|
||||
type: Function,
|
||||
default: (obj) => ''
|
||||
},
|
||||
getInfos: {
|
||||
type: Function,
|
||||
default: (obj) => []
|
||||
},
|
||||
handleUpdate: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultActions: [
|
||||
{
|
||||
id: 'update',
|
||||
name: this.$tc('Update'),
|
||||
icon: 'el-icon-edit',
|
||||
callback: this.handleUpdate,
|
||||
disabled: this.isDisabled('change')
|
||||
},
|
||||
{
|
||||
id: 'delete',
|
||||
name: this.$tc('Delete'),
|
||||
icon: 'el-icon-delete',
|
||||
callback: this.handleDelete,
|
||||
disabled: this.isDisabled('delete')
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
imageUrl() {
|
||||
return this.getImage(this.object)
|
||||
},
|
||||
iActions() {
|
||||
const mergedActions = new Map()
|
||||
this.defaultActions.forEach(a => {
|
||||
mergedActions.set(a.id, { ...a })
|
||||
})
|
||||
this.actions.forEach(a => {
|
||||
mergedActions.set(a.id, { ...a })
|
||||
})
|
||||
return Array.from(mergedActions.values())
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isDisabled(action) {
|
||||
const app = this.tableConfig.permissions?.app
|
||||
const resource = this.tableConfig.permissions?.resource
|
||||
return !this.$hasPerm(`${app}.${action}_${resource}`)
|
||||
},
|
||||
handleClick(event) {
|
||||
event.stopPropagation()
|
||||
},
|
||||
handleDelete() {
|
||||
const url = this.tableConfig.url
|
||||
this.$confirm(this.$tc('DeleteConfirmMessage'), this.$tc('Delete'), {
|
||||
confirmButtonText: this.$tc('Confirm'),
|
||||
cancelButtonText: this.$tc('Cancel'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$axios.delete(`${url}${this.object.id}/`).then(() => {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: this.$tc('DeleteSuccess')
|
||||
})
|
||||
this.$emit('refresh')
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.account-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
//height: 100%;
|
||||
cursor: pointer;
|
||||
|
||||
.title {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
display: flex;
|
||||
height: 100px;
|
||||
padding: 10px 0;
|
||||
|
||||
.panel-image {
|
||||
margin: auto 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-divider--horizontal {
|
||||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<div class="panel-item">
|
||||
<span class="item-label">{{ title }} </span>
|
||||
<el-link :underline="false" class="item-value">
|
||||
<span class="content">{{ content }}</span>
|
||||
</el-link>
|
||||
<span :title="content" class="text-info">{{ content || '' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -38,14 +36,18 @@ export default {
|
||||
|
||||
.panel-item {
|
||||
text-align: left;
|
||||
padding: 5px 0;
|
||||
padding: 3px 0;
|
||||
line-height: 20px;
|
||||
@include textOverflow;
|
||||
|
||||
.item-label {
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
.item-label::after {
|
||||
content: ':';
|
||||
margin-left: 1px;
|
||||
}
|
||||
.text-info {
|
||||
@include textOverflow;
|
||||
}
|
||||
}
|
||||
|
||||
44
src/components/Table/CardTable/DataCardTable/index.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<CardTable
|
||||
ref="table"
|
||||
:columns="3"
|
||||
:table-config="tableConfig"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template v-slot:default="slotProps">
|
||||
<CardPanel :object="slotProps.item" :table-config="tableConfig" v-bind="subComponentProps" />
|
||||
</template>
|
||||
</CardTable>
|
||||
</template>
|
||||
|
||||
<script type="text/jsx">
|
||||
import CardTable from '@/components/Table/CardTable/index.vue'
|
||||
import CardPanel from './CardPanel.vue'
|
||||
|
||||
export default {
|
||||
name: 'SmallCard',
|
||||
components: {
|
||||
CardPanel,
|
||||
CardTable
|
||||
},
|
||||
props: {
|
||||
tableConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
subComponentProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reloadTable() {
|
||||
this.$refs.table.reloadTable()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -10,7 +10,7 @@
|
||||
<IBox v-if="totalData.length === 0">
|
||||
<el-empty :description="$t('NoData')" :image-size="200" class="no-data" style="padding: 20px" />
|
||||
</IBox>
|
||||
<el-col v-for="(d, index) in totalData" :key="index" :lg="8" :md="12" :sm="24" style="min-width: 335px;">
|
||||
<el-col v-for="(d, index) in totalData" :key="index" :lg="8" :md="12" :sm="24" class="el-col">
|
||||
<el-card
|
||||
:body-style="{ 'text-align': 'center', 'padding': '15px' }"
|
||||
:class="{'is-disabled': isDisabled(d)}"
|
||||
@@ -19,8 +19,7 @@
|
||||
@click.native="onView(d)"
|
||||
>
|
||||
<keep-alive>
|
||||
<component :is="subComponent" v-if="subComponent" :object="d" @refresh="getList" />
|
||||
<slot v-else :index="index" :item="d">
|
||||
<slot :index="index" :item="d">
|
||||
<span v-if="d.edition === 'enterprise'" class="enterprise">
|
||||
{{ $t('Enterprise') }}
|
||||
</span>
|
||||
@@ -85,6 +84,10 @@ export default {
|
||||
},
|
||||
props: {
|
||||
// 定义 table 的配置
|
||||
columns: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
tableConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
@@ -100,6 +103,10 @@ export default {
|
||||
subComponent: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
subComponentProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -371,6 +378,10 @@ export default {
|
||||
border-top: 1px solid #e7eaec;
|
||||
}
|
||||
|
||||
.el-col {
|
||||
min-width: 330px;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,25 +1,37 @@
|
||||
<template>
|
||||
<DataActions
|
||||
v-if="hasLeftActions && iActions.length > 0"
|
||||
:actions="iActions"
|
||||
class="header-action"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
<div>
|
||||
<DataActions
|
||||
v-if="hasLeftActions && iActions.length > 0"
|
||||
:actions="iActions"
|
||||
class="header-action"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
|
||||
<Drawer v-if="showDrawer" @close-drawer="handleCloseDrawer">
|
||||
<component :is="dynamicTemplateComponent" />
|
||||
</Drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from '@/i18n/i18n'
|
||||
import DataActions from '@/components/DataActions/index.vue'
|
||||
import { createSourceIdCache } from '@/api/common'
|
||||
import { cleanActions } from './utils'
|
||||
import { createSourceIdCache } from '@/api/common'
|
||||
import { getErrorResponseMsg } from '@/utils/common'
|
||||
|
||||
import i18n from '@/i18n/i18n'
|
||||
import Drawer from '@/components/Drawer/index.vue'
|
||||
import DataActions from '@/components/DataActions/index.vue'
|
||||
import AccountTemplateCreate from '@/views/accounts/AccountTemplate/AccountTemplateCreateUpdate.vue'
|
||||
|
||||
const defaultTrue = { type: [Boolean, Function, String], default: true }
|
||||
const defaultFalse = { type: [Boolean, Function, String], default: false }
|
||||
|
||||
export default {
|
||||
name: 'LeftSide',
|
||||
components: {
|
||||
DataActions
|
||||
Drawer,
|
||||
DataActions,
|
||||
AccountTemplateCreate
|
||||
},
|
||||
props: {
|
||||
hasLeftActions: defaultTrue,
|
||||
@@ -76,7 +88,7 @@ export default {
|
||||
},
|
||||
moreActionsTitle: {
|
||||
type: String,
|
||||
default: null
|
||||
default: ''
|
||||
},
|
||||
moreCreates: {
|
||||
type: Object,
|
||||
@@ -90,6 +102,8 @@ export default {
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
showDrawer: false,
|
||||
dynamicTemplateComponent: null,
|
||||
defaultMoreActions: [
|
||||
{
|
||||
title: this.$t('DeleteSelected'),
|
||||
@@ -194,6 +208,7 @@ export default {
|
||||
methods: {
|
||||
handleCreate() {
|
||||
let route
|
||||
|
||||
if (typeof this.createRoute === 'string') {
|
||||
route = { name: this.createRoute }
|
||||
route.name = this.createRoute
|
||||
@@ -202,14 +217,27 @@ export default {
|
||||
} else if (typeof this.createRoute === 'object') {
|
||||
route = this.createRoute
|
||||
}
|
||||
|
||||
this.$log.debug('handle create')
|
||||
|
||||
if (this.createInNewPage) {
|
||||
const { href } = this.$router.resolve(route)
|
||||
window.open(href, '_blank')
|
||||
} else {
|
||||
if (route.isPam) {
|
||||
this.showDrawer = true
|
||||
this.dynamicTemplateComponent = route.name
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.$router.push(route)
|
||||
}
|
||||
},
|
||||
handleCloseDrawer() {
|
||||
this.showDrawer = false
|
||||
this.dynamicTemplateComponent = null
|
||||
},
|
||||
defaultBulkDeleteCallback({ selectedRows, reloadTable }) {
|
||||
const msg = this.$t('DeleteWarningMsg') + ' ' + selectedRows.length + ' ' + this.$t('Rows') + ' ?'
|
||||
const title = this.$tc('Info')
|
||||
@@ -248,3 +276,17 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep .ibox {
|
||||
height: 100% !important;
|
||||
margin: unset !important;
|
||||
border: unset !important;
|
||||
|
||||
.el-card__body {
|
||||
padding-top: unset !important;
|
||||
padding-bottom: unset !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
292
src/components/Table/ListTable/TableAction/QuickFilter.vue
Normal file
@@ -0,0 +1,292 @@
|
||||
<template>
|
||||
<div v-show="isExpand">
|
||||
<div v-if="filters || summary" :class="isExpand ? 'expand': 'shrink' " class="quick-filter">
|
||||
<div v-show="isExpand" class="quick-filter-wrap">
|
||||
<div v-if="filters" class="quick-filter-zone">
|
||||
<div v-for="category in iFilters" :key="category.label" class="item-zone">
|
||||
<div>
|
||||
<h5>{{ category.label }}</h5>
|
||||
<div class="filter-options">
|
||||
<span
|
||||
v-for="option in category.options"
|
||||
:key="option.label"
|
||||
:class="option.active ? 'active' : ''"
|
||||
class="item"
|
||||
@click="handleFilterClick(option)"
|
||||
>
|
||||
{{ option.label }}
|
||||
<span v-if="option.hasCount">
|
||||
(<span v-async="getCount(option)">-</span>)
|
||||
</span>
|
||||
<!-- <i class="el-icon-circle-check" />-->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="summary" class="summary-zone">
|
||||
<span v-for="item of iSummary" :key="item.title" class="summary-block">
|
||||
<SummaryCard
|
||||
:class="item.active ? 'active' : ''"
|
||||
:count="getCount(item)"
|
||||
:title="item.title"
|
||||
@click="handleFilterClick(item)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="expand-bar-wrap">
|
||||
<div class="expand-bar" @click="toggle">
|
||||
<i :class="isExpand ? 'expand': 'shrink' " class="fa fa-angle-double-up" />
|
||||
<span v-show="!isExpand"> 展开过滤器 </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SummaryCard from '@/components/Cards/SummaryCard/index.vue'
|
||||
import { setUrlParam } from '@/utils/common'
|
||||
|
||||
export default {
|
||||
name: 'QuickFilter',
|
||||
components: { SummaryCard },
|
||||
props: {
|
||||
filters: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
summary: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
expand: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
tableUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
iFilters: this.cleanFilters(),
|
||||
iSummary: this.cleanSummary(),
|
||||
filtered: {},
|
||||
activeFilters: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isExpand: {
|
||||
set(val) {
|
||||
this.$emit('update:expand', val)
|
||||
},
|
||||
get() {
|
||||
return this.expand
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
async getCount(item) {
|
||||
if (item.count) {
|
||||
return item.count
|
||||
}
|
||||
if (!item.filter) {
|
||||
return '-'
|
||||
}
|
||||
let url = this.tableUrl
|
||||
for (const [k, v] of Object.entries({ ...item.filter, limit: 1 })) {
|
||||
url = setUrlParam(url, k, v)
|
||||
}
|
||||
const res = await this.$axios.get(url, { raw: 1 })
|
||||
item.count = res.data.count
|
||||
console.log('............get count: ', item.count)
|
||||
return item.count
|
||||
},
|
||||
cleanSummary() {
|
||||
if (!this.summary) {
|
||||
return []
|
||||
}
|
||||
return this.summary.map(item => {
|
||||
return {
|
||||
category: 'summary',
|
||||
label: item.title,
|
||||
...item,
|
||||
filter: item.filter || {},
|
||||
active: false
|
||||
}
|
||||
})
|
||||
},
|
||||
cleanFilters() {
|
||||
if (!this.filters) {
|
||||
return []
|
||||
}
|
||||
return this.filters.map(category => {
|
||||
return {
|
||||
...category,
|
||||
options: category.options.map(option => {
|
||||
return {
|
||||
category: category.label,
|
||||
...option,
|
||||
active: false,
|
||||
filter: option.filter || {}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
toggle() {
|
||||
this.isExpand = !this.isExpand
|
||||
},
|
||||
handleFilterClick(option) {
|
||||
if (!option.active) {
|
||||
this.activeFilters = this.activeFilters.filter(item => {
|
||||
const conflict = Object.keys(item.filter).some(key => {
|
||||
return Object.keys(option.filter).includes(key)
|
||||
})
|
||||
if (conflict) {
|
||||
item.active = false
|
||||
}
|
||||
return !conflict
|
||||
})
|
||||
this.activeFilters.push(option)
|
||||
} else {
|
||||
this.activeFilters = this.activeFilters.filter(item => {
|
||||
return item.label !== option.label && item.category !== option.category
|
||||
})
|
||||
}
|
||||
option.active = !option.active
|
||||
this.activeFilters.forEach(item => {
|
||||
this.filtered = { ...item.filter }
|
||||
})
|
||||
this.$emit('filter', this.filtered)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang='scss' scoped>
|
||||
.quick-filter {
|
||||
background: white;
|
||||
padding: 10px 10px 10px 20px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
place-content: stretch flex-end;
|
||||
justify-content: center;
|
||||
align-content: stretch;
|
||||
box-shadow: 0 1px 1px 0 rgba(54, 58, 80, .32);
|
||||
|
||||
&.shrink {
|
||||
background: inherit;
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.quick-filter-wrap {
|
||||
display: inline-block;
|
||||
width: calc(100% - 70px);
|
||||
|
||||
.summary-zone {
|
||||
padding-top: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.summary-block {
|
||||
.active {
|
||||
::v-deep .no-margins .num {
|
||||
color: var(--color-primary);
|
||||
|
||||
&::after {
|
||||
content: "\e720";
|
||||
font-family: element-icons !important;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.quick-filter-zone {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap; /* 允许 item-zone 换行 */
|
||||
gap: 10px;
|
||||
|
||||
h5 {
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
margin-bottom: .5rem;
|
||||
line-height: 1.2;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.item-zone {
|
||||
margin-right: 30px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
color: #303133;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
i {
|
||||
visibility: hidden;
|
||||
margin-left: -3px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--color-primary);
|
||||
font-weight: 500;
|
||||
|
||||
i {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none outside none;
|
||||
margin-block-start: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.expand-bar-wrap {
|
||||
margin: auto 0;
|
||||
min-width: 60px;
|
||||
|
||||
.expand-bar {
|
||||
float: right;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
|
||||
i {
|
||||
padding: 5px;
|
||||
|
||||
&.shrink {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -98,11 +98,23 @@ export default {
|
||||
canBulkUpdate: {
|
||||
type: [Boolean, Function, String],
|
||||
default: false
|
||||
},
|
||||
hasQuickFilter: defaultTrue,
|
||||
quickFilterExpand: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultRightSideActions: [
|
||||
{
|
||||
name: 'actionFilter',
|
||||
icon: 'filter',
|
||||
tip: this.$t('Filter'),
|
||||
has: this.hasQuickFilter,
|
||||
callback: this.handleFilterClick.bind(this)
|
||||
},
|
||||
{
|
||||
name: 'actionSetting',
|
||||
icon: 'system-setting',
|
||||
@@ -159,6 +171,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleFilterClick() {
|
||||
this.$emit('update:quick-filter-expand', !this.quickFilterExpand)
|
||||
},
|
||||
handleTagSearch(val) {
|
||||
this.searchTable(val)
|
||||
},
|
||||
@@ -185,7 +200,6 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-left: 10px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="device" class="table-header clearfix">
|
||||
<div :class="device" class="table-header clearfix container">
|
||||
<slot name="header">
|
||||
<LeftSide
|
||||
v-if="hasLeftActions"
|
||||
@@ -10,6 +10,7 @@
|
||||
v-on="$listeners"
|
||||
@init-actions-done="handleActionsDone"
|
||||
/>
|
||||
|
||||
<RightSide
|
||||
v-if="hasRightActions"
|
||||
:selected-rows="selectedRows"
|
||||
@@ -18,6 +19,7 @@
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
|
||||
<div :class="searchClass" class="search">
|
||||
<LabelSearch
|
||||
v-if="hasLabelSearch"
|
||||
@@ -158,7 +160,7 @@ $headerHeight: 30px;
|
||||
.table-header {
|
||||
.left-side {
|
||||
display: block;
|
||||
float: left;
|
||||
//float: left;
|
||||
|
||||
::v-deep .action-item.el-dropdown > .el-button {
|
||||
height: 100%;
|
||||
@@ -166,13 +168,14 @@ $headerHeight: 30px;
|
||||
}
|
||||
|
||||
.right-side {
|
||||
float: right;
|
||||
//float: right;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
|
||||
.right-side-item.action-search {
|
||||
border: 1px solid var(--color-border);
|
||||
@@ -181,65 +184,60 @@ $headerHeight: 30px;
|
||||
}
|
||||
|
||||
.search.left {
|
||||
float: left;
|
||||
padding: 0 !important;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.search.right {
|
||||
float: right;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 0;
|
||||
|
||||
&.mobile {
|
||||
justify-content: flex-start;
|
||||
|
||||
.left-side {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.search {
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.left-side {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.search {
|
||||
order: 2;
|
||||
flex-grow: 1; /* This allows it to grow and fill available space */
|
||||
}
|
||||
|
||||
.right-side {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
/* When .left-side is not present, adjust the layout */
|
||||
.container:not(:has(.left-side)) .search {
|
||||
margin-right: auto; /* Pushes .search to the left */
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.export-item {
|
||||
display: block;
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
.mobile .search {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.mobile .search .datepicker {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.mobile .search.right {
|
||||
clear: both;
|
||||
float: none;
|
||||
padding-top: 10px;
|
||||
|
||||
.label-search {
|
||||
margin-right: 0;
|
||||
|
||||
::v-deep .el-button.label-button {
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
::v-deep .label-cascader {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile .search.right .action-search {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.mobile .right-side {
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
@media (max-width: 481px) {
|
||||
.mobile .right-side {
|
||||
float: left;
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
.mobile .left-side {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<QuickFilter
|
||||
:expand.sync="filterExpand"
|
||||
:filters="quickFilters"
|
||||
:summary="quickSummary"
|
||||
:table-url="tableUrl"
|
||||
@filter="filter"
|
||||
/>
|
||||
<TableAction
|
||||
v-if="hasActions"
|
||||
:class="{'filter-expand': filterExpand}"
|
||||
:date-pick="handleDateChange"
|
||||
:has-quick-filter="iHasQuickFilter"
|
||||
:quick-filter-expand.sync="filterExpand"
|
||||
:reload-table="reloadTable"
|
||||
:search-table="search"
|
||||
:selected-rows="selectedRows"
|
||||
@@ -31,11 +41,13 @@ import IBox from '../../IBox/index.vue'
|
||||
import TableAction from './TableAction/index.vue'
|
||||
import Emitter from '@/mixins/emitter'
|
||||
import AutoDataTable from '../AutoDataTable/index.vue'
|
||||
import QuickFilter from './TableAction/QuickFilter.vue'
|
||||
import { getDayEnd, getDaysAgo } from '@/utils/time'
|
||||
|
||||
export default {
|
||||
name: 'ListTable',
|
||||
components: {
|
||||
QuickFilter,
|
||||
AutoDataTable,
|
||||
TableAction,
|
||||
IBox
|
||||
@@ -51,6 +63,14 @@ export default {
|
||||
headerActions: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
quickFilters: {
|
||||
type: Array,
|
||||
default: () => null
|
||||
},
|
||||
quickSummary: {
|
||||
type: Array,
|
||||
default: () => null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -79,13 +99,18 @@ export default {
|
||||
isDeactivated: false,
|
||||
extraQuery: extraQuery,
|
||||
actionInit: this.headerActions.has === false,
|
||||
initQuery: {}
|
||||
initQuery: {},
|
||||
filterExpand: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['currentOrgIsRoot']),
|
||||
iHasQuickFilter() {
|
||||
const has = this.quickFilters && this.quickFilters.length > 0
|
||||
return !!has
|
||||
},
|
||||
dataTable() {
|
||||
return this.$refs.dataTable.$refs.dataTable
|
||||
return this.$refs.dataTable?.$refs.dataTable
|
||||
},
|
||||
iHeaderActions() {
|
||||
// 如果路由中锁定了 root 组织,就不在检查 root 组织下是否可以创建等
|
||||
@@ -208,6 +233,28 @@ export default {
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
handleFilterExpandChanged(expand) {
|
||||
this.filterExpand = expand
|
||||
},
|
||||
handleQuickFilter(option) {
|
||||
if (option.route) {
|
||||
this.$router.push(option.route)
|
||||
return
|
||||
}
|
||||
if (option.filter) {
|
||||
const filter = { ...option.filter }
|
||||
if (option.active) {
|
||||
for (const key in filter) {
|
||||
filter[key] = ''
|
||||
}
|
||||
}
|
||||
this.filter(option.filter)
|
||||
return
|
||||
}
|
||||
if (option.callback) {
|
||||
option.callback(option.active)
|
||||
}
|
||||
},
|
||||
handleActionInitialDone() {
|
||||
setTimeout(() => {
|
||||
this.actionInit = true
|
||||
@@ -282,6 +329,11 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.filter-expand {
|
||||
&::v-deep button.actionFilter {
|
||||
background-color: rgb(0, 0, 0, 0.08) !important;
|
||||
}
|
||||
}
|
||||
.table-content {
|
||||
margin-top: 10px;
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActionsGroup from '@/components/ActionsGroup/index.vue'
|
||||
import BaseFormatter from './base.vue'
|
||||
import ActionsGroup from '@/components/ActionsGroup/index.vue'
|
||||
|
||||
const defaultPerformDelete = function({ row, col }) {
|
||||
const id = row.id
|
||||
@@ -23,7 +23,9 @@ const defaultPerformDelete = function({ row, col }) {
|
||||
|
||||
const defaultUpdateCallback = function({ row, col }) {
|
||||
const id = row.id
|
||||
|
||||
let route = { params: { id: id }}
|
||||
|
||||
const updateRoute = this.colActions.updateRoute
|
||||
|
||||
if (typeof updateRoute === 'object') {
|
||||
@@ -33,6 +35,7 @@ const defaultUpdateCallback = function({ row, col }) {
|
||||
} else {
|
||||
route.name = updateRoute
|
||||
}
|
||||
|
||||
this.$router.push(route)
|
||||
}
|
||||
|
||||
@@ -106,7 +109,7 @@ export default {
|
||||
onUpdate: defaultUpdateCallback,
|
||||
onDelete: defaultDeleteCallback,
|
||||
onClone: defaultCloneCallback,
|
||||
extraActions: [] // format see defaultActions
|
||||
extraActions: []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +138,7 @@ export default {
|
||||
{
|
||||
name: 'clone',
|
||||
title: this.$t('Duplicate'),
|
||||
type: 'info',
|
||||
type: 'primary',
|
||||
has: colActions.hasClone,
|
||||
can: colActions.canClone,
|
||||
callback: colActions.onClone,
|
||||
@@ -146,8 +149,8 @@ export default {
|
||||
colActions: colActions,
|
||||
defaultActions: defaultActions,
|
||||
extraActions: colActions.extraActions,
|
||||
moreActionsTitle: ''
|
||||
// moreActionsTitle: colActions.moreActionsTitle || null
|
||||
moreActionsTitle: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
56
src/components/Table/TableFormatters/CopyableFormatter.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<script>
|
||||
import BaseFormatter from './base.vue'
|
||||
import { copy } from '@/utils/common'
|
||||
export default {
|
||||
name: 'CopyableFormatter',
|
||||
extends: BaseFormatter,
|
||||
props: {
|
||||
formatterArgsDefault: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
shadow: false,
|
||||
getText: ({ cellValue }) => cellValue
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iCellValue() {
|
||||
if (this.formatterArgs.shadow) {
|
||||
return '*'.repeat(6)
|
||||
} else {
|
||||
return this.cellValue
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async copy() {
|
||||
const text = await this.formatterArgs.getText({ cellValue: this.cellValue, row: this.row })
|
||||
copy(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="copyable">
|
||||
{{ iCellValue }} <i class="el-icon-copy-document copy" @click="copy()" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.copy {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -6,7 +6,7 @@
|
||||
:disabled="disabled"
|
||||
:type="col.type || 'info'"
|
||||
class="detail"
|
||||
@click="goDetail"
|
||||
@click="handleClick"
|
||||
>
|
||||
<slot>
|
||||
{{ iTitle }}
|
||||
@@ -30,6 +30,7 @@ export default {
|
||||
getRoute: null,
|
||||
routeQuery: null,
|
||||
can: true,
|
||||
onClick: null,
|
||||
openInNewPage: false,
|
||||
removeColorOnClick: false,
|
||||
getTitle({ col, row, cellValue }) {
|
||||
@@ -46,6 +47,9 @@ export default {
|
||||
const formatterArgs = Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
|
||||
return {
|
||||
linkClicked: false,
|
||||
showTableDetailDrawer: false,
|
||||
drawerTitle: '',
|
||||
currentTemplate: null,
|
||||
formatterArgs: formatterArgs
|
||||
}
|
||||
},
|
||||
@@ -73,6 +77,17 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
if (this.formatterArgs.onClick) {
|
||||
this.formatterArgs.onClick({
|
||||
col: this.col,
|
||||
row: this.row,
|
||||
cellValue: this.cellValue
|
||||
})
|
||||
} else {
|
||||
this.goDetail()
|
||||
}
|
||||
},
|
||||
getDetailRoute() {
|
||||
// const defaultRoute = this.$route.name.replace('List', 'Detail')
|
||||
let route = this.formatterArgs.route
|
||||
@@ -107,12 +122,12 @@ export default {
|
||||
const detailRoute = this.getDetailRoute()
|
||||
|
||||
if (this.formatterArgs.openInNewPage) {
|
||||
this.linkClicked = this.formatterArgs.removeColorOnClick
|
||||
const { href } = this.$router.resolve(detailRoute)
|
||||
window.open(href, '_blank')
|
||||
} else {
|
||||
this.$router.push(detailRoute)
|
||||
this.linkClicked = this.formatterArgs.removeColorOnClick
|
||||
return window.open(href, '_blank')
|
||||
}
|
||||
|
||||
this.$router.push(detailRoute)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,4 +157,8 @@ export default {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
::v-deep .go-back {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<span class="conform-td">
|
||||
<span v-if="iValue === statusMap.pending">
|
||||
<el-dropdown trigger="click" @command="handleRisk">
|
||||
<el-button class="confirm action" size="mini">
|
||||
<i class="fa fa-check" />
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item v-for="item of iActions" :key="item.name" :command="item.name">
|
||||
{{ item.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<el-tooltip :content="$tc('Ignore')" :open-delay="400">
|
||||
<el-button class="ignore action" size="mini">
|
||||
<svg-icon icon-class="ignore" @click="handleRisk('ignore')" />
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-tooltip v-else :content="iLabel" :open-delay="400" class="platform-status">
|
||||
<span v-if="iValue === statusMap.confirmed ">
|
||||
<i class="fa fa-check color-primary" />
|
||||
</span>
|
||||
<span v-else>
|
||||
<svg-icon icon-class="ignore" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<ProcessingDialog :visible="processing" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseFormatter from './base.vue'
|
||||
import ProcessingDialog from '@/components/Dialog/ProcessingDialog.vue'
|
||||
|
||||
const statusMap = {
|
||||
pending: '0',
|
||||
confirmed: '1',
|
||||
ignored: '2'
|
||||
}
|
||||
export default {
|
||||
name: 'ConfirmOrIgnoreFormatter',
|
||||
components: { ProcessingDialog },
|
||||
extends: BaseFormatter,
|
||||
props: {
|
||||
formatterArgsDefault: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
confirm: ({ row, cellValue }) => {
|
||||
},
|
||||
ignore: ({ row, cellValue }) => {
|
||||
},
|
||||
remove: ({ row, cellValue }) => {
|
||||
},
|
||||
confirmIcon: 'fa fa-check'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs),
|
||||
processing: false,
|
||||
statusMap: statusMap
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iValue() {
|
||||
if (this.cellValueIsLabelChoice()) {
|
||||
return this.cellValue.value
|
||||
} else {
|
||||
return this.cellValue
|
||||
}
|
||||
},
|
||||
iLabel() {
|
||||
if (this.cellValueIsLabelChoice()) {
|
||||
return this.cellValue.label
|
||||
} else {
|
||||
return this.cellValue
|
||||
}
|
||||
},
|
||||
iActions() {
|
||||
return this.getActions()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRemove() {
|
||||
this.formatterArgs.remove({ row: this.row, cellValue: this.cellValue })
|
||||
},
|
||||
handleRisk(cmd) {
|
||||
const data = {
|
||||
asset: this.row.asset.id,
|
||||
username: this.row.username,
|
||||
action: cmd,
|
||||
risk: ''
|
||||
}
|
||||
this.processing = true
|
||||
this.$axios.post(`/api/v1/accounts/account-risks/handle/`, data).then(() => {
|
||||
if (cmd === 'add_account') {
|
||||
this.row.present = true
|
||||
}
|
||||
if (cmd === 'ignore') {
|
||||
this.row.status = { 'value': statusMap.ignored }
|
||||
}
|
||||
this.row.status = { 'value': statusMap.confirmed }
|
||||
}).finally(() => {
|
||||
setTimeout(() => {
|
||||
this.processing = false
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
getActions() {
|
||||
const actions = [
|
||||
// {
|
||||
// name: 'disable_account',
|
||||
// label: this.$t('Disable remote account'),
|
||||
// has: this.row.remote_present
|
||||
// },
|
||||
{
|
||||
name: 'delete_remote',
|
||||
label: this.$t('Delete remote account'),
|
||||
has: this.row.remote_present
|
||||
},
|
||||
{
|
||||
name: 'add_account',
|
||||
label: this.$t('Add account'),
|
||||
has: !this.row.present
|
||||
},
|
||||
{
|
||||
name: 'add_account_after_change_password',
|
||||
label: this.$t('Add account after changing password'),
|
||||
has: !this.row.present
|
||||
}
|
||||
]
|
||||
return actions.filter(action => {
|
||||
return action.has
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
||||
.action.el-button--mini {
|
||||
cursor: pointer;
|
||||
padding: 1px 4px;
|
||||
|
||||
&.confirm {
|
||||
::v-deep i {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&.remove {
|
||||
::v-deep i {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
}
|
||||
|
||||
&.ignore {
|
||||
::v-deep svg.svg-icon {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action.ignore {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
</style>
|
||||
60
src/components/Table/TableFormatters/PlatformFormatter.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<span class="platform-td">
|
||||
<span class="icon-zone">
|
||||
<img :src="icon" alt="icon" class="asset-icon">
|
||||
</span>
|
||||
<span class="platform-name">{{ value.name }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseFormatter from './base.vue'
|
||||
import { loadPlatformIcon } from '@/utils/jms'
|
||||
|
||||
export default {
|
||||
name: 'PlatformFormatter',
|
||||
extends: BaseFormatter,
|
||||
props: {
|
||||
formatterArgsDefault: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
platformAttr: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
icon() {
|
||||
return loadPlatformIcon(this.value.name, this.value.type)
|
||||
},
|
||||
value() {
|
||||
if (!this.formatterArgs.platformAttr) {
|
||||
return this.cellValue
|
||||
} else {
|
||||
return _.get(this.row, this.formatterArgs.platformAttr)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.icon-zone {
|
||||
display: inline-block;
|
||||
width: 1.5em;
|
||||
}
|
||||
|
||||
.asset-icon {
|
||||
height: 1.5em;
|
||||
vertical-align: -0.2em;
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,24 @@
|
||||
<template>
|
||||
<div class="content">
|
||||
<span v-if="realValue" :class="formatterArgs.actionLeft ? 'left' : 'right'" class="action">
|
||||
<template v-for="(item, index) in iActions">
|
||||
<el-tooltip
|
||||
v-if="item.has"
|
||||
:key="index"
|
||||
:content="item.tooltip"
|
||||
:open-delay="500"
|
||||
effect="dark"
|
||||
placement="top"
|
||||
>
|
||||
<i :class="[item.class, item.icon]" class="fa" @click="item.action()" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</span>
|
||||
<el-tooltip
|
||||
v-if="!isEdit"
|
||||
:content="currentValue"
|
||||
:disabled="!isShow"
|
||||
:open-delay="500"
|
||||
placement="top"
|
||||
>
|
||||
<pre class="text" style="cursor: pointer">{{ currentValue }}</pre>
|
||||
@@ -16,21 +32,6 @@
|
||||
size="small"
|
||||
@blur="onEditBlur"
|
||||
/>
|
||||
|
||||
<span v-if="realValue" class="action">
|
||||
<template v-for="(item, index) in iActions">
|
||||
<el-tooltip
|
||||
v-if="item.has"
|
||||
:key="index"
|
||||
:content="item.tooltip"
|
||||
effect="dark"
|
||||
open-delay="500"
|
||||
placement="top"
|
||||
>
|
||||
<i :class="[item.class, item.icon]" class="fa" @click="item.action()" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -39,7 +40,7 @@ import { copy, downloadText } from '@/utils/common'
|
||||
import BaseFormatter from '@/components/Table/TableFormatters/base.vue'
|
||||
|
||||
export default {
|
||||
name: 'ShowKeyCopyFormatter',
|
||||
name: 'SecretViewerFormatter',
|
||||
extends: BaseFormatter,
|
||||
props: {
|
||||
formatterArgsDefault: {
|
||||
@@ -51,7 +52,9 @@ export default {
|
||||
hasDownload: true,
|
||||
hasCopy: true,
|
||||
hasEdit: true,
|
||||
defaultShow: false
|
||||
defaultShow: false,
|
||||
secretFrom: 'cellValue', // fromCellValue or api,
|
||||
actionLeft: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +64,8 @@ export default {
|
||||
isEdit: false,
|
||||
realValue: this.cellValue,
|
||||
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs || {}),
|
||||
isShow: false
|
||||
isShow: false,
|
||||
getIt: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -107,6 +111,9 @@ export default {
|
||||
tooltip: this.$t('Copy')
|
||||
}
|
||||
]
|
||||
if (this.formatterArgs.actionLeft) {
|
||||
actions.reverse()
|
||||
}
|
||||
return actions
|
||||
},
|
||||
currentValue() {
|
||||
@@ -117,20 +124,45 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
cellValue: {
|
||||
handler: function(val) {
|
||||
this.realValue = val
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.isShow = this.formatterArgs.defaultShow
|
||||
if (this.formatterArgs.secretFrom !== 'cellValue') {
|
||||
this.realValue = '--'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onShow() {
|
||||
this.isShow = !this.isShow
|
||||
async getAccountSecret() {
|
||||
if (this.formatterArgs.secretFrom === 'cellValue' || this.getIt) {
|
||||
return
|
||||
}
|
||||
const res = await this.$axios.get(`/api/v1/accounts/account-secrets/${this.row.id}/`)
|
||||
this.realValue = res.secret
|
||||
},
|
||||
onCopy() {
|
||||
async onShow() {
|
||||
await this.getAccountSecret()
|
||||
this.isShow = !this.isShow
|
||||
setTimeout(() => {
|
||||
this.isShow = false
|
||||
}, 10000)
|
||||
},
|
||||
async onCopy() {
|
||||
await this.getAccountSecret()
|
||||
copy(this.realValue)
|
||||
},
|
||||
onDownload() {
|
||||
async onDownload() {
|
||||
await this.getAccountSecret()
|
||||
downloadText(this.realValue, this.name + '.txt')
|
||||
},
|
||||
onEdit() {
|
||||
async onEdit() {
|
||||
await this.getAccountSecret()
|
||||
this.isEdit = !this.isEdit
|
||||
if (this.isEdit) {
|
||||
this.$nextTick(() => {
|
||||
@@ -146,7 +178,7 @@ export default {
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
display: flex;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
//white-space: nowrap;
|
||||
@@ -155,6 +187,7 @@ export default {
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
@@ -163,13 +196,17 @@ export default {
|
||||
}
|
||||
|
||||
.action {
|
||||
float: right;
|
||||
font-size: 15px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
margin-left: 5px;
|
||||
margin-left: 1px;
|
||||
display: inline;
|
||||
|
||||
&.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.fa {
|
||||
margin-right: 10px;
|
||||
margin-right: 5px;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
@@ -47,6 +47,11 @@ export default {
|
||||
return {
|
||||
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cellValueIsLabelChoice() {
|
||||
return typeof this.cellValue === 'object' && this.cellValue['value'] !== undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -6,7 +6,7 @@ import ActionsFormatter from './ActionsFormatter.vue'
|
||||
import DeleteActionFormatter from './DeleteActionFormatter.vue'
|
||||
import DateFormatter from './DateFormatter.vue'
|
||||
import AccountShowFormatter from './GrantedAccountShowFormatter.vue'
|
||||
import ShowKeyCopyFormatter from './ShowKeyCopyFormatter.vue'
|
||||
import SecretViewerFormatter from './SecretViewerFormatter.vue'
|
||||
import DialogDetailFormatter from './DialogDetailFormatter.vue'
|
||||
import EditableInputFormatter from './EditableInputFormatter.vue'
|
||||
import StatusFormatter from './StatusFormatter.vue'
|
||||
@@ -18,6 +18,8 @@ import ProtocolsFormatter from './ProtocolsFormatter.vue'
|
||||
import TagChoicesFormatter from './TagChoicesFormatter.vue'
|
||||
import SwitchFormatter from './SwitchFormatter.vue'
|
||||
import AccountInfoFormatter from './AccountInfoFormatter.vue'
|
||||
import PlatformFormatter from './PlatformFormatter.vue'
|
||||
import DiscoverConfirmFormatter from './DiscoverConfirmFormatter.vue'
|
||||
|
||||
export default {
|
||||
DetailFormatter,
|
||||
@@ -27,7 +29,7 @@ export default {
|
||||
DeleteActionFormatter,
|
||||
DateFormatter,
|
||||
AccountShowFormatter,
|
||||
ShowKeyCopyFormatter,
|
||||
SecretViewerFormatter,
|
||||
DialogDetailFormatter,
|
||||
ArrayFormatter,
|
||||
EditableInputFormatter,
|
||||
@@ -39,7 +41,9 @@ export default {
|
||||
TagChoicesFormatter,
|
||||
LabelsFormatter,
|
||||
SwitchFormatter,
|
||||
AccountInfoFormatter
|
||||
PlatformFormatter,
|
||||
AccountInfoFormatter,
|
||||
DiscoverConfirmFormatter
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -50,7 +54,7 @@ export {
|
||||
DeleteActionFormatter,
|
||||
DateFormatter,
|
||||
AccountShowFormatter,
|
||||
ShowKeyCopyFormatter,
|
||||
SecretViewerFormatter,
|
||||
DialogDetailFormatter,
|
||||
ArrayFormatter,
|
||||
EditableInputFormatter,
|
||||
@@ -62,5 +66,7 @@ export {
|
||||
TagChoicesFormatter,
|
||||
LabelsFormatter,
|
||||
SwitchFormatter,
|
||||
PlatformFormatter,
|
||||
DiscoverConfirmFormatter,
|
||||
AccountInfoFormatter
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
:key="componentKey"
|
||||
ref="ListTable"
|
||||
:header-actions="headerActions"
|
||||
:quick-filters="quickFilters"
|
||||
:quick-summary="quickSummary"
|
||||
:table-config="iTableConfig"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
@@ -243,7 +245,7 @@ $origin-color: #ffffff;
|
||||
text-align: center;
|
||||
padding: 5px 0;
|
||||
border: 1px solid #DCDFE6;
|
||||
background-color: #fff;
|
||||
background-color: #f3f3f3;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
height: 30px;
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
<template>
|
||||
<DataZTree ref="dataztree" :setting="treeSetting" class="data-z-tree" v-on="$listeners">
|
||||
<slot v-if="treeSetting.hasRightMenu" slot="rMenu">
|
||||
<li v-if="treeSetting.showCreate" id="m_create" class="rmenu" tabindex="-1" @click="createTreeNode">
|
||||
<i class="fa fa-plus-square-o" /> {{ this.$t('CreateNode') }}
|
||||
</li>
|
||||
<li v-if="treeSetting.showUpdate" id="m_edit" class="rmenu" tabindex="-1" @click="editTreeNode">
|
||||
<i class="fa fa-pencil-square-o" /> {{ this.$t('RenameNode') }}
|
||||
</li>
|
||||
<li v-if="treeSetting.showDelete" id="m_del" class="rmenu" tabindex="-1" @click="removeTreeNode">
|
||||
<i class="fa fa-minus-square" /> {{ this.$t('DeleteNode') }}
|
||||
</li>
|
||||
<slot slot="rMenu">
|
||||
<div v-if="menu && menu.length > 0">
|
||||
<span v-for="item in menu" :key="item.id">
|
||||
<li
|
||||
v-if="hasMenuItem(item)"
|
||||
:id="item.id"
|
||||
:key="item.id"
|
||||
:class="{ 'disabled': checkDisabled(item) }"
|
||||
class="rmenu"
|
||||
tabindex="-1"
|
||||
@click="onMenuItemClick(item)"
|
||||
>
|
||||
<Icon :icon="item.icon" class="icon" /> {{ item.name }}
|
||||
</li>
|
||||
<li v-if="item.divided" class="divider" />
|
||||
</span>
|
||||
</div>
|
||||
<slot name="rMenu" />
|
||||
</slot>
|
||||
</DataZTree>
|
||||
@@ -17,13 +24,15 @@
|
||||
|
||||
<script>
|
||||
import DataZTree from '../DataZTree/index.vue'
|
||||
import Icon from '@/components/Widgets/Icon'
|
||||
import $ from '@/utils/jquery-vendor'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'AutoDataZTree',
|
||||
components: {
|
||||
DataZTree
|
||||
DataZTree,
|
||||
Icon
|
||||
},
|
||||
props: {
|
||||
setting: {
|
||||
@@ -34,7 +43,32 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultMenu: [
|
||||
{
|
||||
id: 'm_create',
|
||||
name: this.$t('CreateNode'),
|
||||
icon: 'fa-plus-square-o',
|
||||
callback: this.createTreeNode,
|
||||
has: () => this.setting.showCreate
|
||||
},
|
||||
{
|
||||
id: 'm_edit',
|
||||
name: this.$t('RenameNode'),
|
||||
icon: 'fa-pencil-square-o',
|
||||
callback: this.editTreeNode,
|
||||
has: () => this.setting.showUpdate
|
||||
},
|
||||
{
|
||||
id: 'm_del',
|
||||
name: this.$t('DeleteNode'),
|
||||
icon: 'fa-minus-square',
|
||||
callback: this.removeTreeNode,
|
||||
has: () => this.setting.showDelete
|
||||
}
|
||||
],
|
||||
defaultSetting: {
|
||||
showDefaultMenu: true,
|
||||
showMenu: false,
|
||||
showCreate: true,
|
||||
showDelete: true,
|
||||
showUpdate: true,
|
||||
@@ -80,12 +114,49 @@ export default {
|
||||
},
|
||||
rMenu() {
|
||||
return this.$refs.dataztree.rMenu
|
||||
},
|
||||
menu() {
|
||||
let menu = []
|
||||
if (this.setting.showDefaultMenu) {
|
||||
menu = menu.concat(this.defaultMenu)
|
||||
}
|
||||
if (this.setting.menu && this.setting.menu.length > 0) {
|
||||
menu = menu.concat(this.setting.menu)
|
||||
}
|
||||
return menu
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
$('body').unbind('mousedown')
|
||||
},
|
||||
methods: {
|
||||
checkDisabled(item) {
|
||||
let disabled = item.disabled
|
||||
if (typeof disabled === 'function') {
|
||||
disabled = disabled(this.currentNode)
|
||||
}
|
||||
if (typeof disabled === 'undefined') {
|
||||
disabled = false
|
||||
}
|
||||
return disabled
|
||||
},
|
||||
hasMenu(node) {
|
||||
return false
|
||||
},
|
||||
hasMenuItem(item) {
|
||||
let has = item.has
|
||||
if (typeof has === 'function') {
|
||||
has = has(this.currentNode)
|
||||
}
|
||||
if (typeof has === 'undefined') {
|
||||
has = true
|
||||
}
|
||||
return has
|
||||
},
|
||||
onMenuItemClick(item) {
|
||||
item.callback(this.currentNode)
|
||||
this.hideRMenu()
|
||||
},
|
||||
onAsyncSuccess(event, treeId, treeNode, msg) {
|
||||
const nodes = JSON.parse(msg)
|
||||
nodes.forEach((node) => {
|
||||
@@ -115,7 +186,7 @@ export default {
|
||||
if (this.rMenu) this.rMenu.css({ 'visibility': 'hidden' })
|
||||
$('body').unbind('mousedown', this.onBodyMouseDown)
|
||||
},
|
||||
// Request URL: http://localhost/api/v1/assets/assets/?node_id=d8212328-538d-41a6-bcfd-1e8cc7e3aed4&show_current_asset=null&draw=2&limit=15&offset=0&_=1587022917769
|
||||
// Request URL: http://localhost/api/v1/assets/assets/?node_id=ID&show_current_asset=null&draw=2&limit=15&offset=0&_=1587022917769
|
||||
onSelected: function(event, treeNode) {
|
||||
const show_current_asset = this.$cookie.get('show_current_asset') || '0'
|
||||
if (!this.setting.url) {
|
||||
@@ -191,6 +262,8 @@ export default {
|
||||
const offset = $(`#${zTreeID}`).offset()
|
||||
const scrollTop = document.querySelector('.treebox')?.scrollTop
|
||||
x -= offset.left
|
||||
x = x < 0 ? 0 : x
|
||||
|
||||
// Tmp
|
||||
y -= (offset.top + scrollTop) / 3 - 10
|
||||
x += document.body.scrollLeft
|
||||
@@ -199,15 +272,22 @@ export default {
|
||||
if (y + $(`#${rMenuID} ul`).height() >= window.innerHeight) {
|
||||
y -= $(`#${rMenuID} ul`).height()
|
||||
}
|
||||
y = y < 0 ? 0 : y
|
||||
|
||||
this.rMenu.css({ 'top': y + 'px', 'left': x + 'px', 'visibility': 'visible' })
|
||||
$(`#${rMenuID} ul`).show()
|
||||
$('body').bind('mousedown', this.onBodyMouseDown)
|
||||
},
|
||||
onRightClick: function(event, treeId, treeNode) {
|
||||
if (!this.setting.showMenu) {
|
||||
let showMenu = this.setting.showMenu
|
||||
if (typeof showMenu === 'function') {
|
||||
showMenu = showMenu(treeNode)
|
||||
}
|
||||
if (!showMenu) {
|
||||
return
|
||||
}
|
||||
this.currentNode = treeNode
|
||||
this.currentNodeId = treeNode.meta.data.id
|
||||
// 屏蔽收藏资产
|
||||
if (treeNode?.id === '-12') {
|
||||
return
|
||||
@@ -321,9 +401,14 @@ export default {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 15px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.data-z-tree {
|
||||
::v-deep {
|
||||
.fa {
|
||||
.icon {
|
||||
width: 10px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<a id="tree-refresh"><i class="fa fa-refresh" /></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :id="iRMenuID" class="rMenu">
|
||||
<ul class="dropdown-menu menu-actions">
|
||||
<slot name="rMenu" />
|
||||
@@ -49,8 +50,7 @@ import axiosRetry from 'axios-retry'
|
||||
|
||||
const defaultObject = {
|
||||
type: Object,
|
||||
default: () => {
|
||||
}
|
||||
default: () => ({})
|
||||
}
|
||||
export default {
|
||||
name: 'ZTree',
|
||||
@@ -90,6 +90,12 @@ export default {
|
||||
window.removeEventListener('resize', this.updateTreeHeight)
|
||||
},
|
||||
methods: {
|
||||
onMenuClick(menu) {
|
||||
if (menu.disabled) {
|
||||
return
|
||||
}
|
||||
menu.callback()
|
||||
},
|
||||
updateTreeHeight: _.debounce(function() {
|
||||
const tree = document.getElementById(this.iZTreeID)
|
||||
if (!tree) {
|
||||
@@ -106,8 +112,8 @@ export default {
|
||||
}
|
||||
}
|
||||
// 使用 table 的高度
|
||||
const ztreeRect = tree.getBoundingClientRect()
|
||||
tree.style.height = `calc(100vh - ${ztreeRect.top}px - 30px - 25px)`
|
||||
const zTreeRect = tree.getBoundingClientRect()
|
||||
tree.style.height = `calc(100vh - ${zTreeRect.top}px - 30px - 25px)`
|
||||
}, 100),
|
||||
async initTree(refresh = false) {
|
||||
const vm = this
|
||||
@@ -156,9 +162,6 @@ export default {
|
||||
if (this.treeSetting.showMenu) {
|
||||
this.rMenu = $(`#${this.iRMenuID}`)
|
||||
}
|
||||
if (this.treeSetting?.otherMenu) {
|
||||
$('.menu-actions').append(this.otherMenu)
|
||||
}
|
||||
},
|
||||
onSearch() {
|
||||
this.showTreeSearch = !this.showTreeSearch
|
||||
@@ -435,6 +438,24 @@ div.rMenu li {
|
||||
list-style: none outside none;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
font-size: 12px;
|
||||
padding: 0 16px;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #606266;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
i {
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
border: medium none;
|
||||
min-width: 160px;
|
||||
@@ -451,7 +472,8 @@ div.rMenu li {
|
||||
text-shadow: none;
|
||||
top: 100%;
|
||||
z-index: 1000;
|
||||
height: 300px;
|
||||
max-height: 320px;
|
||||
min-height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
26
src/components/Widgets/Loading/index.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<span v-if="loading" v-loading="loading" class="loading" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Index',
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.loading {
|
||||
margin-top: 20px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,7 @@
|
||||
import i18n from '@/i18n/i18n'
|
||||
import CronTab from '@/components/Form/FormFields/CronTab/index.vue'
|
||||
import InputWithUnit from '@/components/Form/FormFields/InputWithUnit.vue'
|
||||
import store from '@/store'
|
||||
|
||||
export const strMatchValues = ['exact', 'not', 'in', 'contains', 'startswith', 'endswith', 'regex']
|
||||
export const typeMatchMapper = {
|
||||
@@ -26,3 +29,49 @@ export const attrMatchOptions = [
|
||||
{ label: i18n.t('GreatEqualThan'), value: 'gte' },
|
||||
{ label: i18n.t('LessEqualThan'), value: 'lte' }
|
||||
]
|
||||
|
||||
export const crontab = {
|
||||
type: 'cronTab',
|
||||
component: CronTab,
|
||||
label: i18n.t('Crontab'),
|
||||
hidden: (formValue) => {
|
||||
return formValue.is_periodic === false
|
||||
},
|
||||
helpText: i18n.t('CrontabHelpText'),
|
||||
helpTip: i18n.t('CrontabHelpTip')
|
||||
}
|
||||
|
||||
const validatorInterval = (rule, value, callback) => {
|
||||
if (parseInt(value) < 1) {
|
||||
return callback(new Error(i18n.t('EnsureThisValueIsGreaterThanOrEqualTo1')))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
export const interval = {
|
||||
label: i18n.t('Interval'),
|
||||
hidden: (formValue) => {
|
||||
return formValue.is_periodic === false
|
||||
},
|
||||
component: InputWithUnit,
|
||||
el: {
|
||||
unit: 'hour'
|
||||
},
|
||||
rules: [
|
||||
{ validator: validatorInterval }
|
||||
]
|
||||
}
|
||||
|
||||
export const is_periodic = {
|
||||
type: 'checkbox',
|
||||
hidden: (formValue) => {
|
||||
return !store.getters.hasValidLicense
|
||||
}
|
||||
}
|
||||
|
||||
export const periodicMeta = {
|
||||
is_periodic,
|
||||
interval,
|
||||
crontab
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export { default as AccountListTable } from './Apps/AccountListTable/AccountList
|
||||
export { default as AssetRelationCard } from './Apps/AssetRelationCard'
|
||||
export { default as UserConfirmDialog } from './Apps/UserConfirmDialog'
|
||||
export { default as Announcement } from './Widgets/Announcement'
|
||||
export { default as CronTab } from './Form/CronTab'
|
||||
export { default as CronTab } from './Form/FormFields/CronTab'
|
||||
export { default as Pagination } from './Table/Pagination'
|
||||
export { default as Tooltip } from './Widgets/Tooltip'
|
||||
export { default as ResourceActivity } from './Apps/ResourceActivity'
|
||||
|
||||
23
src/directive/async.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
Vue.directive('async', {
|
||||
async bind(el, binding) {
|
||||
const { value: asyncFn, arg } = binding
|
||||
if (typeof asyncFn === 'function') {
|
||||
const result = await asyncFn(arg)
|
||||
el.innerText = result
|
||||
}
|
||||
},
|
||||
async update(el, binding) {
|
||||
const { value: asyncFn, arg } = binding
|
||||
if (typeof asyncFn === 'function') {
|
||||
const result = await asyncFn(arg)
|
||||
el.innerText = result
|
||||
} else if (typeof asyncFn === 'object' && asyncFn.then) {
|
||||
const result = await asyncFn
|
||||
el.innerText = result
|
||||
} else {
|
||||
el.innerText = asyncFn
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1 +1,2 @@
|
||||
import './permission'
|
||||
import './async'
|
||||
|
||||
@@ -5,3 +5,12 @@ Vue.filter('date', function(value) {
|
||||
return toSafeLocalDateStr(value)
|
||||
})
|
||||
|
||||
Vue.filter('async', async(asyncFn, args) => {
|
||||
if (typeof asyncFn === 'function') {
|
||||
return await asyncFn(args)
|
||||
} else if (typeof asyncFn === 'object' && asyncFn.then) {
|
||||
return await asyncFn
|
||||
} else {
|
||||
return asyncFn
|
||||
}
|
||||
})
|
||||
|
||||
@@ -14,6 +14,7 @@ router.beforeEach(async(to, from, next) => {
|
||||
// start progress bar
|
||||
// NProgress.start()
|
||||
try {
|
||||
await store.dispatch('common/cleanDrawerActionMeta')
|
||||
await startup({ to, from, next })
|
||||
next()
|
||||
} catch (e) {
|
||||
|
||||
10
src/icons/svg/activity.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="138px" height="134px" viewBox="0 0 138 134" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>activity</title>
|
||||
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="activity" fill="#3E3A39" fill-rule="nonzero">
|
||||
<path d="M125.454545,123.012 C125.454545,123.30102 125.310273,123.469176 125.435727,123.484941 L12.7900909,123.553255 C12.7399091,123.521725 12.5454545,123.332549 12.5454545,123.012 L12.5454545,44.6614118 L125.454545,44.6614118 L125.454545,123.012 Z M12.5454545,18.865098 C12.5454545,18.5813333 12.6709091,18.4131765 12.5642727,18.386902 L37.6363636,18.386902 L37.6363636,21.0196078 C37.6363636,23.9218101 40.4447593,26.2745098 43.9090909,26.2745098 C47.3734225,26.2745098 50.1818182,23.9218101 50.1818182,21.0196078 L50.1818182,18.3921569 L87.8181818,18.3921569 L87.8181818,21.0196078 C87.8181818,23.9218101 90.6265775,26.2745098 94.0909091,26.2745098 C97.5552407,26.2745098 100.363636,23.9218101 100.363636,21.0196078 L100.363636,18.3921569 L125.260091,18.3921569 C125.385308,18.5275639 125.454545,18.6938135 125.454545,18.865098 L125.454545,34.1568627 L12.5454545,34.1568627 L12.5454545,18.865098 Z M125.423182,7.88235294 L100.363636,7.88235294 L100.363636,5.25490196 C100.363636,2.35269975 97.5552407,0 94.0909091,0 C90.6265775,0 87.8181818,2.35269975 87.8181818,5.25490196 L87.8181818,7.88235294 L50.1818182,7.88235294 L50.1818182,5.25490196 C50.1818182,2.35269975 47.3734225,0 43.9090909,0 C40.4447593,0 37.6363636,2.35269975 37.6363636,5.25490196 L37.6363636,7.88235294 L12.489,7.88235294 C5.60154545,7.88235294 0,12.811451 0,18.865098 L0,123.017255 C0,129.076157 5.60781818,134 12.4952727,134 L125.504727,134 C132.392182,134 138,129.076157 138,123.017255 L138,18.865098 C138,12.8061961 132.360818,7.87709804 125.423182,7.87709804 L125.423182,7.88235294 Z" id="形状"></path>
|
||||
<path d="M44.3,76.5714286 L94.7,76.5714286 C98.1793939,76.5714286 101,74.2049337 101,71.2857143 C101,68.3664949 98.1793939,66 94.7,66 L44.3,66 C40.8206061,66 38,68.3664949 38,71.2857143 C38,74.2049337 40.8206061,76.5714286 44.3,76.5714286 M44.3,103 L94.7,103 C98.1793939,103 101,100.633505 101,97.7142857 C101,94.7950663 98.1793939,92.4285714 94.7,92.4285714 L44.3,92.4285714 C40.8206061,92.4285714 38,94.7950663 38,97.7142857 C38,100.633505 40.8206061,103 44.3,103" id="形状"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
7
src/icons/svg/backup.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg t="1730795663037" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="53312" width="200" height="200">
|
||||
<path d="M81.4592 150.4768c-44.544 0-80.6912 35.0208-80.6912 78.2848v712.704c0 43.2128 36.1472 78.2848 80.6912 78.2848h686.6432c44.5952 0 80.7424-35.072 80.7424-78.336v-71.8848h90.3168c44.544 0 80.6912-35.0208 80.6912-78.2848V78.5408c0-43.2128-36.1472-78.2848-80.6912-78.2848H252.416c-44.544 0-80.6912 35.072-80.6912 78.336v71.8848H81.4592zM848.896 800.3584V228.7616c0-43.264-36.1472-78.336-80.7424-78.336H243.0976V69.2736h710.144v731.136h-104.448zM72.0896 941.4656V219.648h705.4336v731.136H72.0896v-9.3184z"
|
||||
fill="#333333" p-id="53313"></path>
|
||||
<path d="M215.1936 716.8a35.1232 35.1232 0 0 0-35.6352 34.6112c0 19.1488 15.9232 34.6112 35.6352 34.6112h203.3664a35.1232 35.1232 0 0 0 35.6864-34.6112 35.1232 35.1232 0 0 0-35.6864-34.56H215.1936z m0-186.9824a35.1232 35.1232 0 0 0-35.6352 34.56c0 19.0976 15.9232 34.6112 35.6352 34.6112h389.4272a35.1232 35.1232 0 0 0 35.6352-34.6112 35.1232 35.1232 0 0 0-35.6352-34.56H215.1936z m0-180.224a35.1232 35.1232 0 0 0-35.6352 34.56c0 19.1488 15.9232 34.6112 35.6352 34.6112h389.4272a35.1232 35.1232 0 0 0 35.6352-34.6112 35.1232 35.1232 0 0 0-35.6352-34.56H215.1936z"
|
||||
fill="#333333" p-id="53314"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,8 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="40px" height="44px" viewBox="0 0 40 44" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M30.2706254,38.229976 C30.2706254,39.3606857 29.7535516,39.817019 28.4764414,39.817019 L5.16450885,39.817019 C3.87961144,39.817019 3.36253758,39.3606857 3.36253758,38.229976 L3.36253758,25.2564058 C3.36253758,24.1256961 3.88116889,23.6678054 5.16450885,23.6678054 L26.9080879,23.6678054 L26.9080879,20.3052678 L3.6179596,20.3052678 C1.39859136,20.3052678 0,21.7038592 0,23.9232274 L0,39.5600395 C0,41.7794077 1.39859136,43.1779991 3.6179596,43.1779991 L30.0229907,43.1779991 C32.2330142,43.1779991 33.6316056,41.7794077 33.6316056,39.5600395 L33.6316056,29.0488 L30.269068,29.0488 L30.269068,38.229976 L30.2706254,38.229976 Z"
|
||||
id="路径"></path>
|
||||
<path d="M15.4763873,27.703162 C15.4755615,27.5244652 15.54618,27.3528466 15.6725391,27.2264875 C15.7988982,27.1001284 15.9705168,27.0295099 16.1492136,27.0303357 L17.4948516,27.0303357 C17.8639676,27.0303357 18.1676706,27.3324885 18.1676706,27.703162 L18.1676706,35.7816624 C18.1668142,36.1528948 17.866084,36.453625 17.4948516,36.4544887 L16.1492136,36.4544887 C15.9705168,36.4553145 15.7988982,36.384696 15.6725391,36.2583369 C15.54618,36.1319778 15.4755615,35.9603592 15.4763873,35.7816624 L15.4763873,27.703162 L15.4763873,27.703162 Z M39.5047548,15.4429045 C39.1907428,15.1267586 38.7635683,14.9489692 38.3179768,14.9489692 C37.8723853,14.9489692 37.4452109,15.1267586 37.1311989,15.4429045 L22.3821341,30.1919693 C21.9578609,30.6170771 21.7926087,31.2362399 21.9486262,31.8162251 C22.1046438,32.3962103 22.5582284,32.8489044 23.1385191,33.0037818 C23.7188098,33.1586592 24.3376467,32.9921905 24.7619198,32.5670827 L39.5047548,17.8242477 C39.8217544,17.5091076 40,17.0805683 40,16.6335761 C40,16.1865839 39.8217544,15.7580447 39.5047548,15.4429045 L39.5047548,15.4429045 Z M10.0876127,20.3068253 L10.0876127,10.6973501 C10.0876127,6.72273414 13.107511,3.48790758 16.8204751,3.48790758 C20.5334393,3.48790758 23.5455503,6.72273414 23.5455503,10.6973501 L23.5455503,16.2730261 L26.9080879,16.2730261 L26.9080879,10.2176551 C26.9532494,6.58243177 25.0397574,3.20385387 21.8988728,1.37308023 C18.7579882,-0.45769341 14.8751748,-0.45769341 11.7342902,1.37308023 C8.59340562,3.20385387 6.67991361,6.58243177 6.72507515,10.2176551 L6.72507515,23.6678054 L26.9080879,23.6678054 L26.9080879,20.3052678 L10.0876127,20.3052678 L10.0876127,20.3068253 Z"
|
||||
id="形状"></path>
|
||||
</svg>
|
||||
<svg width="162px" height="176px" viewBox="0 0 162 176" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组</title>
|
||||
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="编组" fill="#2C2C2C" fill-rule="nonzero">
|
||||
<path d="M145.273438,109.375 C142.304688,109.375 139.882812,111.796875 139.882812,114.765625 L139.882812,164.316406 L10.78125,164.316406 L10.78125,87.3046875 L91.1132813,87.3046875 C94.0820313,87.3046875 96.5039063,84.8828125 96.5039063,81.9140625 C96.5039063,78.9453125 94.0820313,76.5234375 91.1132813,76.5234375 L31.25,76.5234375 L31.25,50.4296875 C31.25,28.5742188 49.0234375,10.8007813 70.8984375,10.8007813 L78.90625,10.8007813 C100.761719,10.8007813 118.535156,28.5742188 118.535156,50.4296875 L118.535156,54.609375 L118.554688,54.609375 C118.730469,57.421875 121.074219,59.6484375 123.925781,59.6484375 C126.894531,59.6484375 129.316406,57.2460938 129.316406,54.2578125 C129.316406,54.1210938 129.316406,53.984375 129.296875,53.8476563 L129.296875,50.4101563 C129.296875,22.6757813 106.621094,0 78.8867188,0 L70.8789063,0 C43.1445313,0 20.46875,22.6757813 20.46875,50.4101563 L20.46875,76.5039063 L5.390625,76.5039063 C2.421875,76.5039063 0,78.9257812 0,81.8945313 L0,170 C0,172.96875 2.421875,175.390625 5.390625,175.390625 C6.03515625,175.390625 6.66015625,175.273438 7.24609375,175.058594 L143.417969,175.058594 C144.003906,175.273438 144.609375,175.390625 145.273438,175.390625 C148.242188,175.390625 150.664063,172.96875 150.664063,170 L150.664063,114.746094 C150.644531,111.796875 148.222656,109.375 145.273438,109.375 Z" id="路径"></path>
|
||||
<path d="M159.648438,56.9335938 C157.558594,54.84375 154.121094,54.84375 152.03125,56.9335938 L76.8945312,132.050781 C74.8046875,134.140625 74.8046875,137.578125 76.8945312,139.667969 C78.984375,141.757813 82.421875,141.757813 84.5117188,139.667969 L159.628906,64.5507813 C161.738281,62.4414063 161.738281,59.0234375 159.648438,56.9335938 Z" id="路径"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.1 KiB |
10
src/icons/svg/discovery.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="175px" height="172px" viewBox="0 0 175 172" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组</title>
|
||||
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="编组" fill="#000000" fill-rule="nonzero">
|
||||
<path d="M173.785375,165.454861 L123.361962,115.009064 C137.448412,98.2738101 143.521494,75.2018529 137.651474,52.4540759 C128.018006,15.1217873 89.8109099,-7.40014283 52.4770224,2.22233315 C34.3936829,6.89116772 19.2091781,18.3220175 9.72181048,34.4071119 C0.234442898,50.4958037 -2.41955769,69.3134455 2.24727825,87.3967849 C6.91431399,105.480124 18.3433652,120.666428 34.432057,130.153995 C45.3350648,136.583636 57.4928218,139.875204 69.8298572,139.875204 C75.6914829,139.875204 81.5950804,139.12991 87.4219298,137.628528 C99.4151984,134.532427 109.877505,128.483928 118.240035,120.466763 L168.495562,170.744875 C169.224268,171.475579 170.183418,171.840932 171.140568,171.840932 C172.097719,171.840932 173.054871,171.475579 173.785575,170.744875 C175.246584,169.283466 175.246584,166.916271 173.785375,165.454861 Z M85.551198,130.380442 C69.402147,134.552413 52.5955419,132.177823 38.2312808,123.705967 C23.865021,115.234111 13.6597404,101.677103 9.49156634,85.5262533 C5.32319251,69.3790011 7.69418489,52.5743947 16.1660409,38.2099337 C24.6396958,23.8418753 38.1985029,13.6347956 54.3475538,9.47021928 C59.5606195,8.12213334 64.7902738,7.47916928 69.9393825,7.47916928 C97.7275421,7.47916928 123.148307,26.2018753 130.407185,54.3242083 C135.890068,75.5734013 129.699865,97.1395794 115.85805,112.213559 C115.752321,112.298502 115.649591,112.388841 115.551457,112.486774 C115.398161,112.64007 115.264852,112.804958 115.143734,112.976043 C107.36301,121.170886 97.2760486,127.355893 85.551198,130.380442 Z" id="形状"></path>
|
||||
<path d="M91.6046937,27.1300456 C86.1485929,23.9152253 79.7609238,23.0164347 73.6272824,24.5947646 C71.6270386,25.1136128 70.4234546,27.1520308 70.940304,29.1538736 C71.4553548,31.1559162 73.492174,32.3505062 75.4976142,31.8426505 C84.1721329,29.6031689 93.0403202,34.8382195 95.2798019,43.5073419 C95.7145072,45.1951976 97.2342767,46.3130396 98.9001472,46.3130396 C99.2089378,46.3130396 99.5229249,46.2764644 99.8371118,46.1925212 C101.837156,45.6774705 103.04094,43.6388527 102.52409,41.6370099 C100.940364,35.5031687 97.0625933,30.3484636 91.6046937,27.1300456 Z" id="路径"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
5
src/icons/svg/ignore.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg t="1730113499097" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1649"
|
||||
width="200" height="200">
|
||||
<path d="M512 0C229.234286 0 0 229.234286 0 512s229.234286 512 512 512 512-229.234286 512-512S794.765714 0 512 0zM109.714286 512a400.514286 400.514286 0 0 1 81.68-243.017143l563.622857 563.622857A400.514286 400.514286 0 0 1 512 914.285714C289.828571 914.285714 109.714286 734.171429 109.714286 512z m722.891428 243.017143L268.982857 191.394286A400.514286 400.514286 0 0 1 512 109.714286c222.171429 0 402.285714 180.114286 402.285714 402.285714a400.514286 400.514286 0 0 1-81.68 243.017143z"
|
||||
p-id="1650"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 684 B |
8
src/icons/svg/pam.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="40px" height="48px" viewBox="0 0 40 48" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M36.0001406,20.0002344 L36.0001406,16.000375 C36.0001406,7.17752804 28.8226126,0 20.0002344,0 C11.1778562,0 4.00032813,7.17752804 4.00032813,15.9999062 L4.00032813,19.9997656 L0,19.9997656 L0,48.0001875 L40,48.0001875 L40,20.0002344 L36.0001406,20.0002344 Z M8.0001875,15.9999062 C8.0001875,9.3830054 13.3833335,3.99985937 20.0002344,3.99985937 C26.6171352,3.99985937 32.0002813,9.3830054 32.0002813,15.9999062 L32.0002813,19.9997656 L8.0001875,19.9997656 L8.0001875,15.9999062 Z M36.0006094,44.0003281 L4.00032813,44.0003281 L4.00032813,24.0000938 L36.0006094,24.0000938 L36.0006094,44.0003281 Z"
|
||||
id="形状"></path>
|
||||
<path d="M20.0002344,40 C22.2094618,40 24.0000938,38.2088993 24.0000938,36.0001406 C24.0000938,34.5231036 23.1905593,33.2476299 21.9999297,32.5548147 L21.9999297,27.9999531 L18.0000703,27.9999531 L18.0000703,32.5548147 C16.8094407,33.2476299 15.9999062,34.5231036 15.9999062,36.0001406 C16.000375,38.209368 17.791007,40 20.0002344,40 L20.0002344,40 Z"
|
||||
id="路径"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
9
src/icons/svg/scan.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="184px" height="184px" viewBox="0 0 184 184" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组</title>
|
||||
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="编组" fill="#333333" fill-rule="nonzero">
|
||||
<path d="M91.6666666,183.341458 C41.0405646,183.341458 -6.9388939e-15,142.300894 -6.9388939e-15,91.6747917 C0.0535397665,70.505775 7.41237071,50.0040719 20.8333332,33.6331251 C23.6218122,30.2086156 27.7168568,28.1082053 32.125,27.8414583 C36.6727552,27.6410729 41.0978101,29.347664 44.3333332,32.5497917 L97.5416666,85.7997917 C100.373139,89.1061274 100.182768,94.0347428 97.1046929,97.112818 C94.0266177,100.190893 89.0980023,100.381265 85.7916666,97.5497917 L33.0833332,44.8831251 C22.4640203,58.1678232 16.6752268,74.6673541 16.6666666,91.6747917 C16.634797,112.309451 25.1059247,132.045685 40.0857285,146.237078 C55.0655324,160.428471 75.2305194,167.821188 95.8333332,166.674792 C133.947312,164.34614 164.338015,133.955437 166.666667,95.8414583 C167.813062,75.2386445 160.420346,55.0736575 146.228953,40.0938537 C132.03756,25.1140498 112.301325,16.6429221 91.6666666,16.6747917 C85.0987008,16.6886637 78.5594988,17.5428207 72.2083332,19.2164583 C67.9277344,19.9697568 63.787032,17.3066153 62.6970126,13.0991407 C61.6069933,8.89166618 63.9337598,4.55301463 68.0416666,3.13312512 C96.4092432,-4.47902134 126.706595,1.9631523 149.522872,20.4586022 C172.33915,38.9540521 184.910974,67.2627144 183.333333,96.5914583 C180.529036,143.291305 143.283179,180.537161 96.5833332,183.341458 L91.6666666,183.341458 Z" id="路径"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
9
src/icons/svg/service.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="200px" height="200px" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>编组</title>
|
||||
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="编组" fill="#272636" fill-rule="nonzero">
|
||||
<path d="M196.888904,59.872974 L154.191511,102.570368 C150.259952,106.501926 143.890605,106.501926 139.959046,102.570368 L97.2672057,59.872974 C93.3356471,55.9469684 93.3356471,49.5720682 97.2672057,45.6460625 L139.959046,2.94866895 C143.890605,-0.982889648 150.259952,-0.982889648 154.191511,2.94866895 L196.888904,45.6460625 C200.820463,49.5720682 200.820463,55.9469684 196.888904,59.872974 Z M147.155798,10.7062783 L105.024815,52.8372609 L147.155798,94.9682436 L189.28678,52.8372609 L147.155798,10.7062783 Z M72.1896365,199.993059 L11.1060979,199.993059 C4.96997871,199.993059 0,195.02308 0,188.886961 L0,127.803422 C0,121.672856 4.96997871,116.697324 11.1060979,116.697324 L72.1896365,116.697324 C78.3202027,116.697324 83.2957346,121.672856 83.2957346,127.803422 L83.2957346,188.886961 C83.2957346,195.02308 78.3202027,199.993059 72.1896365,199.993059 Z M72.1896365,127.803422 L11.1060979,127.803422 L11.1060979,188.886961 L72.1896365,188.886961 L72.1896365,127.803422 Z M72.1896365,100.038177 L11.1060979,100.038177 C4.96997871,100.038177 0,95.0681984 0,88.9320793 L0,27.8485406 C0,21.7179744 4.96997871,16.7424426 11.1060979,16.7424426 L72.1896365,16.7424426 C78.3202027,16.7424426 83.2957346,21.7179744 83.2957346,27.8485406 L83.2957346,88.9320793 C83.2957346,95.0681984 78.3202027,100.038177 72.1896365,100.038177 Z M72.1896365,27.8485406 L11.1060979,27.8485406 L11.1060979,88.9320793 L72.1896365,88.9320793 L72.1896365,27.8485406 Z M111.060979,116.697324 L172.144518,116.697324 C178.275084,116.697324 183.250616,121.672856 183.250616,127.803422 L183.250616,188.886961 C183.250616,195.02308 178.275084,199.993059 172.144518,199.993059 L111.060979,199.993059 C104.92486,199.993059 99.9548814,195.02308 99.9548814,188.886961 L99.9548814,127.803422 C99.9548814,121.672856 104.92486,116.697324 111.060979,116.697324 Z M111.060979,188.886961 L172.144518,188.886961 L172.144518,127.803422 L111.060979,127.803422 L111.060979,188.886961 Z" id="形状"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -124,7 +124,7 @@ export default {
|
||||
}
|
||||
},
|
||||
cloneNameSuffix: {
|
||||
type: String,
|
||||
type: [String, Number],
|
||||
default: function() {
|
||||
return this.$t('Duplicate').toLowerCase()
|
||||
}
|
||||
@@ -133,8 +133,13 @@ export default {
|
||||
submitMethod: {
|
||||
type: [Function, String],
|
||||
default: function() {
|
||||
const params = this.$route.params
|
||||
if (params.id) {
|
||||
const cloneFrom = this.getCloneId()
|
||||
console.log('Clone from: ', cloneFrom)
|
||||
if (cloneFrom) {
|
||||
return 'post'
|
||||
}
|
||||
const objectId = this.getUpdateId()
|
||||
if (objectId) {
|
||||
return 'put'
|
||||
} else {
|
||||
return 'post'
|
||||
@@ -145,13 +150,13 @@ export default {
|
||||
getUrl: {
|
||||
type: Function,
|
||||
default: function() {
|
||||
const params = this.$route.params
|
||||
const objectId = this.getUpdateId()
|
||||
let url = this.url
|
||||
if (params.id) {
|
||||
url = getUpdateObjURL(url, params.id)
|
||||
if (objectId) {
|
||||
url = getUpdateObjURL(url, objectId)
|
||||
}
|
||||
|
||||
const clone_from = this.$route.query['clone_from']
|
||||
const clone_from = this.getCloneId()
|
||||
const query = clone_from ? `clone_from=${clone_from}` : ''
|
||||
if (query) {
|
||||
if (url.indexOf('?') === -1) {
|
||||
@@ -204,7 +209,6 @@ export default {
|
||||
type: Function,
|
||||
default(res, method, vm, addContinue) {
|
||||
const route = this.getNextRoute(res, method)
|
||||
|
||||
if (!(route.params && route.params.id)) {
|
||||
route['params'] = deepmerge(route['params'] || {}, { 'id': res.id })
|
||||
}
|
||||
@@ -213,10 +217,18 @@ export default {
|
||||
this.$emit('submitSuccess', res)
|
||||
|
||||
this.emitPerformSuccessMsg(method, res, addContinue)
|
||||
if (!addContinue) {
|
||||
if (addContinue) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!vm.drawer) {
|
||||
if (this.$router.currentRoute.name !== route?.name) {
|
||||
setTimeout(() => this.$router.push(route), 100)
|
||||
}
|
||||
} else {
|
||||
console.log('Reload table clsonse dr')
|
||||
this.$emit('close-drawer')
|
||||
this.$emit('reload-table')
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -268,7 +280,11 @@ export default {
|
||||
form: {},
|
||||
loading: true,
|
||||
isSubmitting: false,
|
||||
clone: false
|
||||
clone: false,
|
||||
drawer: false,
|
||||
action: '',
|
||||
actionId: '',
|
||||
row: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -297,19 +313,47 @@ export default {
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$log.debug('Object init is: ', this.object)
|
||||
const drawActionMeta = await this.$store.dispatch('common/getDrawerActionMeta')
|
||||
if (drawActionMeta) {
|
||||
this.drawer = true
|
||||
this.action = drawActionMeta.action
|
||||
this.row = drawActionMeta.row
|
||||
this.actionId = this.row?.id
|
||||
}
|
||||
this.$log.debug('Object init is: ', this.object, this.method)
|
||||
console.log('Action: ', this.action, this.actionId)
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
const values = await this.getFormValue()
|
||||
this.$log.debug('Final object is: ', values)
|
||||
const formValue = Object.assign(this.form, values)
|
||||
this.form = this.afterGetFormValue(formValue)
|
||||
console.log('Form: ', this.form)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getUpdateId() {
|
||||
if (this.actionId && this.action === 'update') {
|
||||
return this.actionId
|
||||
} else {
|
||||
return this.$route.params.id
|
||||
}
|
||||
},
|
||||
getAction() {
|
||||
return this.action
|
||||
},
|
||||
getCloneId() {
|
||||
if (this.actionId && this.action === 'clone') {
|
||||
return this.actionId
|
||||
} else {
|
||||
return this.$route.query['clone_from']
|
||||
}
|
||||
},
|
||||
isUpdateMethod() {
|
||||
console.log('This method: ', this.method)
|
||||
return ['put', 'patch'].indexOf(this.method.toLowerCase()) > -1
|
||||
},
|
||||
encryptFields(values) {
|
||||
@@ -352,27 +396,34 @@ export default {
|
||||
}, 200)
|
||||
})
|
||||
},
|
||||
async getUpdateForm() {
|
||||
},
|
||||
async getCloneForm(cloneFrom) {
|
||||
const [curUrl, query] = this.url.split('?')
|
||||
const url = `${curUrl}${cloneFrom}/${query ? ('?' + query) : ''}`
|
||||
const object = await this.getObjectDetail(url)
|
||||
let name = ''
|
||||
let attr = ''
|
||||
if (object['name']) {
|
||||
name = object['name']
|
||||
attr = 'name'
|
||||
} else if (object['hostname']) {
|
||||
name = object['hostname']
|
||||
attr = 'hostname'
|
||||
}
|
||||
object[attr] = name + '-' + this.cloneNameSuffix.toString()
|
||||
return object
|
||||
},
|
||||
async getFormValue() {
|
||||
const cloneFrom = this.$route.query['clone_from']
|
||||
if ((!this.isUpdateMethod() && !cloneFrom) || !this.needGetObjectDetail) {
|
||||
const cloneFrom = this.getCloneId()
|
||||
const objectId = this.getUpdateId()
|
||||
if ((!objectId && !cloneFrom) || !this.needGetObjectDetail) {
|
||||
return Object.assign(this.form, this.initial)
|
||||
}
|
||||
let object = this.object
|
||||
if (!object || Object.keys(object).length === 0) {
|
||||
if (cloneFrom) {
|
||||
const [curUrl, query] = this.url.split('?')
|
||||
const url = `${curUrl}${cloneFrom}/${query ? ('?' + query) : ''}`
|
||||
object = await this.getObjectDetail(url)
|
||||
let name = ''
|
||||
let attr = ''
|
||||
if (object['name']) {
|
||||
name = object['name']
|
||||
attr = 'name'
|
||||
} else if (object['hostname']) {
|
||||
name = object['hostname']
|
||||
attr = 'hostname'
|
||||
}
|
||||
object[attr] = name + '-' + this.cloneNameSuffix
|
||||
object = await this.getCloneForm(cloneFrom)
|
||||
} else {
|
||||
object = await this.getObjectDetail(this.iUrl)
|
||||
}
|
||||
@@ -393,7 +444,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ibox ::v-deep .el-card__body {
|
||||
padding-top: 30px;
|
||||
}
|
||||
.ibox ::v-deep .el-card__body {
|
||||
padding-top: 30px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Page v-bind="$attrs">
|
||||
<Page :class="{drawer: drawer}" v-bind="$attrs">
|
||||
<IBox>
|
||||
<GenericCreateUpdateForm ref="createUpdateForm" v-bind="$attrs" v-on="$listeners" />
|
||||
</IBox>
|
||||
@@ -14,6 +14,18 @@ export default {
|
||||
name: 'GenericCreateUpdatePage',
|
||||
components: {
|
||||
Page, IBox, GenericCreateUpdateForm
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
drawer: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('common/getDrawerActionMeta').then((res) => {
|
||||
if (res.action) {
|
||||
this.drawer = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -27,4 +39,8 @@ export default {
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer ::v-deep .page-heading {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -35,7 +35,6 @@ export default {
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
object: {
|
||||
@@ -98,37 +97,34 @@ export default {
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
const detailApiUrl = (function() {
|
||||
if (vm.url) {
|
||||
return `${vm.url}/${vm.$route.params.id}/`
|
||||
} else {
|
||||
return getApiPath(vm)
|
||||
}
|
||||
}())
|
||||
const defaultActions = {
|
||||
// Delete button
|
||||
canDelete: vm.$hasCurrentResAction('delete'),
|
||||
hasDelete: true,
|
||||
deleteCallback: function(item) {
|
||||
vm.defaultDelete(item)
|
||||
},
|
||||
deleteApiUrl: detailApiUrl,
|
||||
deleteSuccessRoute: this.$route.name.replace('Detail', 'List'),
|
||||
// Update button
|
||||
canUpdate: () => {
|
||||
return !vm.currentOrgIsRoot && vm.$hasCurrentResAction('change')
|
||||
},
|
||||
hasUpdate: true,
|
||||
updateCallback: function(item) {
|
||||
this.defaultUpdate(item)
|
||||
},
|
||||
updateRoute: this.$route.name.replace('Detail', 'Update')
|
||||
}
|
||||
return {
|
||||
detailApiUrl,
|
||||
defaultActions,
|
||||
loading: true,
|
||||
drawer: '',
|
||||
action: '',
|
||||
actionId: '',
|
||||
validActions: Object.assign(defaultActions, this.actions)
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['currentOrgIsRoot']),
|
||||
pageActions() {
|
||||
@@ -139,7 +135,7 @@ export default {
|
||||
icon: 'el-icon-edit-outline',
|
||||
size: 'small',
|
||||
can: this.validActions.canUpdate,
|
||||
has: this.validActions.hasUpdate,
|
||||
has: this.validActions.hasUpdate && !this.drawer,
|
||||
callback: this.validActions.updateCallback.bind(this)
|
||||
},
|
||||
{
|
||||
@@ -178,20 +174,53 @@ export default {
|
||||
return [...this.submenu, activity]
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
async created() {
|
||||
try {
|
||||
this.loading = true
|
||||
await this.checkDrawer()
|
||||
await this.getObject()
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async checkDrawer() {
|
||||
const drawActionMeta = await this.$store.dispatch('common/getDrawerActionMeta')
|
||||
if (drawActionMeta) {
|
||||
this.drawer = true
|
||||
this.row = drawActionMeta.row
|
||||
this.actionId = this.row?.id
|
||||
}
|
||||
},
|
||||
getDetailUrl() {
|
||||
const vm = this
|
||||
let objectId = ''
|
||||
if (this.actionId) {
|
||||
objectId = this.actionId
|
||||
} else {
|
||||
objectId = vm.$route.params.id
|
||||
}
|
||||
if (vm.url) {
|
||||
return `${vm.url}/${objectId}/`
|
||||
} else {
|
||||
return getApiPath(vm, objectId)
|
||||
}
|
||||
},
|
||||
afterDelete() {
|
||||
if (this.drawer) {
|
||||
this.$emit('close-drawer')
|
||||
this.$emit('detail-delete-success')
|
||||
this.$emit('reload-table')
|
||||
} else {
|
||||
this.$message.success(this.$tc('DeleteSuccessMsg'))
|
||||
this.$router.push({ name: this.validActions.deleteSuccessRoute })
|
||||
}
|
||||
},
|
||||
defaultDelete() {
|
||||
const msg = this.$t('DeleteWarningMsg') + ' ' + this.iTitle + ' ?'
|
||||
const title = this.$t('Info')
|
||||
const performDelete = () => {
|
||||
const url = this.validActions.deleteApiUrl
|
||||
const url = this.getDetailUrl()
|
||||
this.$log.debug('Start perform delete: ', url)
|
||||
return this.$axios.delete(url)
|
||||
}
|
||||
@@ -206,8 +235,7 @@ export default {
|
||||
try {
|
||||
await performDelete.bind(this)()
|
||||
done()
|
||||
this.$message.success(this.$tc('DeleteSuccessMsg'))
|
||||
this.$router.push({ name: this.validActions.deleteSuccessRoute })
|
||||
this.afterDelete()
|
||||
} catch (error) {
|
||||
const errorDetail = error?.response?.data?.detail || ''
|
||||
if (errorDetail) {
|
||||
@@ -238,13 +266,13 @@ export default {
|
||||
},
|
||||
getObject() {
|
||||
// 兼容之前的 detailApiUrl
|
||||
const url = this.validActions.detailApiUrl || this.detailApiUrl
|
||||
const url = this.getDetailUrl()
|
||||
return this.$axios.get(url, { disableFlashErrorMsg: true }).then(data => {
|
||||
this.$emit('update:object', data)
|
||||
this.$emit('getObjectDone', data)
|
||||
}).catch(error => {
|
||||
if (error.response && error.response.status === 404) {
|
||||
const msg = this.$t('ObjectNotFoundOrDeletedMsg')
|
||||
const msg = this.$tc('ObjectNotFoundOrDeletedMsg')
|
||||
this.$message.error(msg)
|
||||
} else {
|
||||
flashErrorMsg({ error, response: error.response })
|
||||
@@ -261,7 +289,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header-buttons {
|
||||
z-index: 999;
|
||||
}
|
||||
.header-buttons {
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
|
||||
86
src/layout/components/GenericListDrawerPage/Drawer.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
:size="drawerSize"
|
||||
:title="title"
|
||||
:visible.sync="iVisible"
|
||||
append-to-body
|
||||
class="form-drawer"
|
||||
destroy-on-close
|
||||
>
|
||||
<component
|
||||
:is="component"
|
||||
v-bind="props"
|
||||
@close="closeDrawer"
|
||||
v-on="$listeners"
|
||||
@close-drawer="iVisible=false"
|
||||
/>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
component: {
|
||||
type: [String, Function],
|
||||
required: true
|
||||
},
|
||||
props: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
},
|
||||
drawerSize() {
|
||||
const width = window.innerWidth
|
||||
if (width >= 768) return '800px'
|
||||
return '90%'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeDrawer() {
|
||||
this.iVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-drawer {
|
||||
/* 可自定义样式 */
|
||||
|
||||
::v-deep {
|
||||
.el-card.ibox {
|
||||
//border: none;
|
||||
}
|
||||
|
||||
.el-drawer__header {
|
||||
//border-bottom: 1px solid #EBEEF5;
|
||||
margin-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
183
src/layout/components/GenericListDrawerPage/index.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div>
|
||||
<Page v-bind="$attrs">
|
||||
<GenericListTable
|
||||
ref="ListTable"
|
||||
:header-actions="iHeaderActions"
|
||||
:table-config="iTableConfig"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
</Page>
|
||||
<Drawer
|
||||
v-if="drawerVisible"
|
||||
:action="action"
|
||||
:component="drawerComponent"
|
||||
:props="drawerProps"
|
||||
:title="drawerTitle"
|
||||
:visible.sync="drawerVisible"
|
||||
@reload-table="reloadTable"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Page from '@/layout/components/Page'
|
||||
import GenericListTable from '@/layout/components/GenericListTable'
|
||||
import Drawer from './Drawer'
|
||||
import { toSentenceCase } from '@/utils/common'
|
||||
|
||||
const drawerType = [String, Function]
|
||||
|
||||
export default {
|
||||
name: 'GenericListPage',
|
||||
components: {
|
||||
Page, GenericListTable, Drawer
|
||||
},
|
||||
props: {
|
||||
detailDrawer: {
|
||||
type: drawerType,
|
||||
default: ''
|
||||
},
|
||||
createDrawer: {
|
||||
type: drawerType,
|
||||
default: ''
|
||||
},
|
||||
updateDrawer: {
|
||||
type: drawerType,
|
||||
default: ''
|
||||
},
|
||||
tableConfig: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
headerActions: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
drawerVisible: false,
|
||||
drawerComponent: '',
|
||||
drawerProps: {},
|
||||
iHeaderActions: {},
|
||||
iTableConfig: {},
|
||||
action: '',
|
||||
iCreateDrawer: this.createDrawer,
|
||||
iUpdateDrawer: this.updateDrawer,
|
||||
iDetailDrawer: this.detailDrawer
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
drawerTitle() {
|
||||
let title = this.title || this.$route.meta.title
|
||||
if (!title) {
|
||||
title = this.$t('NoTitle')
|
||||
}
|
||||
title = toSentenceCase(this.action) + ' ' + title.toLowerCase()
|
||||
return title
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
drawerVisible(val) {
|
||||
if (!val) {
|
||||
this.$store.dispatch('common/cleanDrawerActionMeta')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!this.createDrawer) {
|
||||
this.iCreateDrawer = this.getDefaultDrawer('create')
|
||||
}
|
||||
if (!this.updateDrawer) {
|
||||
this.iUpdateDrawer = this.getDefaultDrawer('update')
|
||||
}
|
||||
if (!this.detailDrawer) {
|
||||
this.iDetailDrawer = this.getDefaultDrawer('detail')
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.iHeaderActions = {
|
||||
...this.headerActions,
|
||||
onCreate: this.onCreate
|
||||
}
|
||||
this.iTableConfig = {
|
||||
...this.tableConfig
|
||||
}
|
||||
_.set(this.iTableConfig, 'columnsMeta.actions.formatterArgs.onUpdate', this.onUpdate)
|
||||
_.set(this.iTableConfig, 'columnsMeta.actions.formatterArgs.onClone', this.onClone)
|
||||
_.set(this.iTableConfig, 'columnsMeta.name.formatterArgs.onClick', this.onDetail)
|
||||
},
|
||||
methods: {
|
||||
getDefaultDrawer(action) {
|
||||
const route = this.$route.name
|
||||
const actionRouteName = route.replace('List', toSentenceCase(action))
|
||||
return this.getRouteNameComponent(actionRouteName)
|
||||
},
|
||||
getRouteNameComponent(name) {
|
||||
const routes = this.$router.resolve({ name: name })
|
||||
if (!routes) {
|
||||
return
|
||||
}
|
||||
const matched = routes.resolved.matched.filter(item => item.name === name && item.components)
|
||||
if (matched.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (matched[0] && matched[0].components?.default) {
|
||||
return matched[0].components.default
|
||||
}
|
||||
},
|
||||
onCreate() {
|
||||
this.action = 'create'
|
||||
this.drawerComponent = this.iCreateDrawer
|
||||
this.$store.dispatch('common/setDrawerActionMeta', {
|
||||
action: 'create'
|
||||
}).then(() => {
|
||||
this.drawerVisible = true
|
||||
})
|
||||
},
|
||||
reloadTable() {
|
||||
this.$refs.ListTable.reloadTable()
|
||||
},
|
||||
onClone({ row, col }) {
|
||||
this.drawerComponent = this.iCreateDrawer
|
||||
this.action = 'clone'
|
||||
this.$store.dispatch('common/setDrawerActionMeta', {
|
||||
action: 'clone',
|
||||
row: row,
|
||||
col: col
|
||||
}).then(() => {
|
||||
this.drawerVisible = true
|
||||
})
|
||||
},
|
||||
onUpdate({ row, col }) {
|
||||
this.action = 'update'
|
||||
let updateDrawer = this.iUpdateDrawer
|
||||
if (!updateDrawer) {
|
||||
updateDrawer = this.iCreateDrawer
|
||||
}
|
||||
this.drawerComponent = updateDrawer
|
||||
this.$store.dispatch('common/setDrawerActionMeta', {
|
||||
action: 'update',
|
||||
row: row,
|
||||
col: col
|
||||
}).then(() => {
|
||||
this.drawerVisible = true
|
||||
})
|
||||
},
|
||||
onDetail({ row, cellValue }) {
|
||||
this.action = 'detail'
|
||||
this.drawerComponent = this.iDetailDrawer
|
||||
this.$store.dispatch('common/setDrawerActionMeta', {
|
||||
action: 'detail',
|
||||
row: row,
|
||||
cellValue: cellValue
|
||||
}).then(() => {
|
||||
this.drawerVisible = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -82,7 +82,7 @@ export default {
|
||||
]
|
||||
}
|
||||
const hasPerms = this.$hasPerm('orgs.view_organization | orgs.add_organization')
|
||||
const isConsole = this.currentViewRoute.name === 'console'
|
||||
const isConsole = ['console'].includes(this.currentViewRoute.name)
|
||||
return hasPerms && isConsole ? orgActions : {}
|
||||
},
|
||||
orgChoicesGroup() {
|
||||
|
||||
@@ -115,6 +115,7 @@ export default {
|
||||
this.tipHasRead = '1'
|
||||
this.iShowTip = false
|
||||
}
|
||||
console.log('Route to: ', routeName)
|
||||
this.$router.push(routeName)
|
||||
this.$emit('view-change', routeName)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<li class="header-item active-menu">
|
||||
<Help />
|
||||
</li>
|
||||
<li class="header-item">
|
||||
<li v-if="!isMobile" class="header-item language">
|
||||
<Language />
|
||||
</li>
|
||||
<li class="header-item header-profile">
|
||||
@@ -76,7 +76,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'sidebar', 'publicSettings', 'currentOrgRoles', 'currentViewRoute'
|
||||
'sidebar', 'publicSettings', 'currentOrgRoles', 'currentViewRoute', 'isMobile'
|
||||
]),
|
||||
ticketsEnabled() {
|
||||
return this.publicSettings['TICKETS_ENABLED'] &&
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<slot name="globalNotification">
|
||||
<SqlQueryTip v-if="debug" />
|
||||
<SqlQueryTip v-if="debug && !inDrawer" />
|
||||
<LicenseRelatedTip v-else />
|
||||
<PasswordExpireTip />
|
||||
</slot>
|
||||
@@ -22,6 +22,7 @@
|
||||
import LicenseRelatedTip from './LicenseRelatedTip'
|
||||
import PasswordExpireTip from './PasswordExpireTip'
|
||||
import SqlQueryTip from './SqlQueryTip'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'PageHeading',
|
||||
@@ -40,6 +41,9 @@ export default {
|
||||
return {
|
||||
debug: process.env.NODE_ENV === 'development'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['inDrawer'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<TagsView />
|
||||
<PageHeading v-if="iTitle || helpMessage" :help-msg="helpMessage" class="disabled-when-print">
|
||||
<el-button
|
||||
v-if="!inDrawer"
|
||||
:disabled="gobackDisabled"
|
||||
class="go-back"
|
||||
icon="el-icon-back"
|
||||
@@ -46,6 +47,7 @@ import PageContent from './PageContent'
|
||||
import UserConfirmDialog from '@/components/Apps/UserConfirmDialog/index.vue'
|
||||
import TagsView from '../TagsView/index.vue'
|
||||
import { toSentenceCase } from '@/utils/common'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Page',
|
||||
@@ -81,6 +83,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['inDrawer']),
|
||||
iTitle() {
|
||||
let title = this.title || this.$route.meta.title
|
||||
if (!title) {
|
||||
@@ -132,6 +135,7 @@ export default {
|
||||
|
||||
::v-deep > div {
|
||||
margin-bottom: 50px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,10 @@
|
||||
<el-tab-pane
|
||||
:key="item.name"
|
||||
:disabled="item.disabled"
|
||||
:label-content="item.labelContent"
|
||||
:name="item.name"
|
||||
>
|
||||
<span slot="label">
|
||||
<i v-if="item.icon" :class="item.icon" class="fa pre-icon " />
|
||||
<Icon v-if="item.icon" :icon="item.icon" class="pre-icon" />
|
||||
{{ toSentenceCase(item.title) }}
|
||||
<slot :tab="item.name" name="badge" />
|
||||
<el-tooltip
|
||||
@@ -60,12 +59,14 @@
|
||||
|
||||
<script>
|
||||
import Page from '../Page/'
|
||||
import Icon from '@/components/Widgets/Icon'
|
||||
import { toSentenceCase } from '@/utils/common'
|
||||
|
||||
export default {
|
||||
name: 'TabPage',
|
||||
components: {
|
||||
Page
|
||||
Page,
|
||||
Icon
|
||||
},
|
||||
props: {
|
||||
submenu: {
|
||||
@@ -193,14 +194,14 @@ export default {
|
||||
}
|
||||
|
||||
::v-deep .page-content {
|
||||
overflow-y: hidden;
|
||||
overflow-y: hidden !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tab-page-content {
|
||||
padding: 10px 30px 22px;
|
||||
overflow-y: auto;
|
||||
height: calc(100% - 50px);
|
||||
height: calc(100vh - 130px);
|
||||
|
||||
.el-alert {
|
||||
margin-top: 0;
|
||||
|
||||
@@ -92,6 +92,15 @@ new Vue({
|
||||
i18n,
|
||||
router,
|
||||
store,
|
||||
mounted() {
|
||||
// 移除加载页面
|
||||
const loadingElement = document.getElementById('loading')
|
||||
if (loadingElement) {
|
||||
setTimeout(() => {
|
||||
loadingElement.style.display = 'none'
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
render: h => h(App)
|
||||
})
|
||||
|
||||
|
||||
@@ -110,339 +110,14 @@ export default [
|
||||
action: 'update'
|
||||
},
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/accounts/AccountTemplate/AccountTemplateDetail/index.vue'),
|
||||
name: 'AccountTemplateDetail',
|
||||
meta: { title: i18n.t('AccountTemplate') },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'automations',
|
||||
component: empty,
|
||||
name: 'AccountAutomation',
|
||||
meta: {
|
||||
title: i18n.t('Automations'),
|
||||
name: 'Automations',
|
||||
icon: 'automation',
|
||||
permissions: []
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'account-push',
|
||||
name: 'AccountPush',
|
||||
component: empty,
|
||||
redirect: {
|
||||
name: 'AccountPushList'
|
||||
},
|
||||
meta: {
|
||||
app: 'accounts',
|
||||
name: 'BaseAccountPushList',
|
||||
resource: 'pushaccountautomation'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/AccountPush/index.vue'),
|
||||
name: 'AccountPushList',
|
||||
meta: {
|
||||
title: i18n.t('AccountPushList'),
|
||||
menuTitle: i18n.t('AccountPushList'),
|
||||
permissions: ['accounts.view_pushaccountautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/accounts/AccountPush/AccountPushCreateUpdate.vue'),
|
||||
name: 'AccountPushCreate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountPushCreate'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.add_pushaccountautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/accounts/AccountPush/AccountPushCreateUpdate.vue'),
|
||||
name: 'AccountPushUpdate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountPushUpdate'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.change_pushaccountautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/accounts/AccountPush/AccountPushDetail/index.vue'),
|
||||
name: 'AccountPushDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountPushList'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_pushaccountautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'executions',
|
||||
component: () => import('@/views/accounts/AccountPush/AccountPushExecutionList.vue'),
|
||||
name: 'AccountPushExecutionList',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ExecutionList'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_pushaccountexecution']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'executions/:id',
|
||||
component: () => import('@/views/accounts/AccountPush/AccountPushExecutionDetail/index.vue'),
|
||||
name: 'AccountPushExecutionDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ExecutionDetail'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_pushaccountexecution']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'account-change-secret',
|
||||
name: 'AccountChangeSecret',
|
||||
component: empty,
|
||||
redirect: {
|
||||
name: 'AccountChangeSecretList'
|
||||
},
|
||||
meta: {
|
||||
title: i18n.t('BaseAccountChangeSecret'),
|
||||
app: 'accounts',
|
||||
licenseRequired: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/index.vue'),
|
||||
name: 'AccountChangeSecretList',
|
||||
meta: {
|
||||
menuTitle: i18n.t('ChangeCredentials'),
|
||||
title: i18n.t('AccountChangeSecret'),
|
||||
permissions: ['accounts.view_changesecretautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/AccountChangeSecretCreateUpdate.vue'),
|
||||
name: 'AccountChangeSecretCreate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AssetChangeSecretCreate'),
|
||||
permissions: ['accounts.add_changesecretautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/AccountChangeSecretCreateUpdate.vue'),
|
||||
name: 'AccountChangeSecretUpdate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AssetChangeSecretUpdate'),
|
||||
permissions: ['accounts.change_changesecretautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/AccountChangeSecretDetail/index.vue'),
|
||||
name: 'AccountChangeSecretDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountChangeSecret'),
|
||||
permissions: ['accounts.view_changesecretautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'executions',
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/AccountChangeSecretDetail/AccountChangeSecretExecution/AccountChangeSecretExecutionList.vue'),
|
||||
name: 'AccountChangeSecretExecutionList',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ExecutionList'),
|
||||
permissions: ['accounts.view_changesecretexecution']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'executions/:id',
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/AccountChangeSecretDetail/AccountChangeSecretExecution/AccountChangeSecretExecutionDetail/index.vue'),
|
||||
name: 'AccountChangeSecretExecutionDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ExecutionDetail'),
|
||||
permissions: ['accounts.view_changesecretexecution']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'account-gather',
|
||||
component: empty,
|
||||
redirect: {
|
||||
name: 'AccountGatherList'
|
||||
},
|
||||
name: 'AccountGather',
|
||||
meta: {
|
||||
title: i18n.t('AccountGatherList'),
|
||||
app: 'accounts',
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
licenseRequired: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/AccountGather/index.vue'),
|
||||
name: 'AccountGatherList',
|
||||
meta: {
|
||||
menuTitle: i18n.t('GatherAccounts'),
|
||||
title: i18n.t('AccountGatherTaskList'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_gatheredaccount']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/accounts/AccountGather/TaskCreateUpdate'),
|
||||
name: 'AccountGatherTaskCreate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountGatherTaskCreate'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.add_gatheraccountsautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/accounts/AccountGather/TaskDetail/index'),
|
||||
name: 'AccountGatherTaskDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountGatherTaskDetail'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_gatheraccountsautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/accounts/AccountGather/TaskCreateUpdate'),
|
||||
name: 'AccountGatherTaskUpdate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountGatherTaskUpdate'),
|
||||
action: 'update',
|
||||
permissions: ['accounts.change_gatheraccountsautomation'],
|
||||
activeMenu: '/accounts/account-gather'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'executions',
|
||||
component: () => import('@/views/accounts/AccountGather/TaskDetail/TaskExecutionList.vue'),
|
||||
name: 'AccountGatherTaskExecutionList',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ExecutionList'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_gatheraccountsexecution']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'executions/:id',
|
||||
component: () => import('@/views/accounts/AccountGather/TaskDetail/AccountGatherExecutionDetail/index.vue'),
|
||||
name: 'AccountGatherExecutionDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ExecutionDetail'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_gatheraccountsexecution']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'account-backup',
|
||||
component: empty,
|
||||
name: 'AccountBackup',
|
||||
redirect: {
|
||||
name: 'AccountBackupList'
|
||||
},
|
||||
meta: {
|
||||
title: i18n.t('AccountBackup'),
|
||||
app: 'accounts',
|
||||
resource: 'accountbackupautomation',
|
||||
licenseRequired: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/AccountBackup/index.vue'),
|
||||
name: 'AccountBackupList',
|
||||
meta: {
|
||||
menuTitle: i18n.t('AccountBackup'),
|
||||
title: i18n.t('AccountBackupList'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_accountbackupautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/accounts/AccountBackup/AccountBackupCreateUpdate.vue'),
|
||||
name: 'AccountBackupCreate',
|
||||
meta: {
|
||||
title: i18n.t('AccountBackupCreate'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
action: 'create'
|
||||
},
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/accounts/AccountBackup/AccountBackupCreateUpdate.vue'),
|
||||
name: 'AccountBackupUpdate',
|
||||
meta: {
|
||||
title: i18n.t('AccountBackupUpdate'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
action: 'update'
|
||||
},
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/accounts/AccountBackup/AccountBackupDetail/index.vue'),
|
||||
name: 'AccountBackupDetail',
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
meta: {
|
||||
title: i18n.t('AccountBackupDetail'),
|
||||
activeMenu: '/console/accounts/automations'
|
||||
},
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'executions',
|
||||
component: () => import('@/views/accounts/AccountBackup/AccountBackupDetail/AccountBackupExecution/AccountBackupExecutionList.vue'),
|
||||
name: 'AccountBackupExecutionList',
|
||||
meta: { title: i18n.t('ExecutionDetail') },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'executions/:id',
|
||||
component: () => import('@/views/accounts/AccountBackup/AccountBackupDetail/AccountBackupExecution/AccountBackupExecutionDetail/index.vue'),
|
||||
name: 'AccountBackupExecutionDetail',
|
||||
meta: { title: i18n.t('ExecutionDetail') },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
}
|
||||
// {
|
||||
// path: ':id',
|
||||
// component: () => import('@/views/accounts/AccountTemplate/AccountTemplateDetail/Application.vue'),
|
||||
// name: 'AccountTemplateDetail',
|
||||
// meta: { title: i18n.t('AccountTemplate') },
|
||||
// hidden: true
|
||||
// }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -27,7 +27,7 @@ export default {
|
||||
children: [
|
||||
{
|
||||
path: '/console/dashboard',
|
||||
component: () => import('@/views/dashboard/Console/index'),
|
||||
component: () => import('@/views/dashboard/Console/index.vue'),
|
||||
name: 'AdminDashboard',
|
||||
meta: {
|
||||
icon: 'dashboard',
|
||||
|
||||
@@ -32,6 +32,7 @@ import workbenchViewRoutes from './workbench'
|
||||
import ticketsRoutes from './tickets'
|
||||
import settingsRoutes from './settings'
|
||||
import profileRoutes from './profile'
|
||||
import pamViewRoutes from './pam'
|
||||
import { getPropView } from '@/utils/jms'
|
||||
import store from '@/store'
|
||||
|
||||
@@ -92,6 +93,7 @@ export const viewRoutes = [
|
||||
consoleViewRoutes,
|
||||
auditViewRoutes,
|
||||
workbenchViewRoutes,
|
||||
pamViewRoutes,
|
||||
ticketsRoutes,
|
||||
settingsRoutes,
|
||||
profileRoutes
|
||||
|
||||
54
src/router/pam/activity.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import empty from '@/layout/empty.vue'
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
export default [
|
||||
|
||||
{
|
||||
path: 'sessions',
|
||||
name: 'AccountSession',
|
||||
component: empty,
|
||||
meta: {
|
||||
app: 'terminal',
|
||||
name: 'BaseAccountSession',
|
||||
icon: 'session',
|
||||
resource: 'session',
|
||||
permissions: []
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/sessions/SessionList/index.vue'),
|
||||
name: 'AccountSessionList',
|
||||
meta: {
|
||||
title: i18n.t('AccountSessions'),
|
||||
permissions: []
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'activity',
|
||||
name: 'AccountActivity',
|
||||
component: empty,
|
||||
redirect: {
|
||||
name: 'AccountPushList'
|
||||
},
|
||||
meta: {
|
||||
app: 'accounts',
|
||||
name: 'BaseAccountActivity',
|
||||
icon: 'activity',
|
||||
resource: 'operate-logs'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/AccountActivity/AccountActivityList.vue'),
|
||||
name: 'AccountActivityList',
|
||||
meta: {
|
||||
title: i18n.t('AccountActivity'),
|
||||
permissions: ['audits.view_operatelog']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
311
src/router/pam/automations.js
Normal file
@@ -0,0 +1,311 @@
|
||||
import empty from '@/layout/empty.vue'
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
export default [
|
||||
{
|
||||
path: 'account-discovery',
|
||||
component: empty,
|
||||
redirect: {
|
||||
name: 'AccountDiscoverList'
|
||||
},
|
||||
name: 'AccountDiscover',
|
||||
meta: {
|
||||
title: i18n.t('AccountDiscoverList'),
|
||||
app: 'accounts',
|
||||
icon: 'discovery'
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/AccountDiscover/index.vue'),
|
||||
name: 'AccountDiscoverList',
|
||||
meta: {
|
||||
title: i18n.t('DiscoverAccounts'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_gatheredaccount']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/accounts/AccountDiscover/TaskCreateUpdate'),
|
||||
name: 'AccountDiscoverTaskCreate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountDiscoverTaskCreate'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.add_gatheraccountsautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/accounts/AccountDiscover/TaskDetail/index'),
|
||||
name: 'AccountDiscoverTaskDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountDiscoverTaskDetail'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_gatheraccountsautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/accounts/AccountDiscover/TaskCreateUpdate'),
|
||||
name: 'AccountDiscoverTaskUpdate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountDiscoverTaskUpdate'),
|
||||
action: 'update',
|
||||
permissions: ['accounts.change_gatheraccountsautomation'],
|
||||
activeMenu: '/accounts/account-gather'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'executions',
|
||||
component: () => import('@/views/accounts/AccountDiscover/TaskExecutionList.vue'),
|
||||
name: 'AccountDiscoverTaskExecutionList',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ExecutionList'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_gatheraccountsexecution']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'executions/:id',
|
||||
component: () => import('@/views/accounts/AccountDiscover/ExecutionDetail/index.vue'),
|
||||
name: 'AccountDiscoverExecutionDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ExecutionDetail'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_gatheraccountsexecution']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'account-change-secret',
|
||||
name: 'AccountChangeSecret',
|
||||
component: empty,
|
||||
redirect: {
|
||||
name: 'AccountChangeSecretList'
|
||||
},
|
||||
meta: {
|
||||
title: i18n.t('BaseAccountChangeSecret'),
|
||||
icon: 'change-password',
|
||||
app: 'accounts'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/index.vue'),
|
||||
name: 'AccountChangeSecretList',
|
||||
meta: {
|
||||
menuTitle: i18n.t('ChangeSecret'),
|
||||
title: i18n.t('AccountChangeSecret'),
|
||||
permissions: ['accounts.view_changesecretautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/AccountChangeSecretCreateUpdate.vue'),
|
||||
name: 'AccountChangeSecretCreate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AssetChangeSecretCreate'),
|
||||
permissions: ['accounts.add_changesecretautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/AccountChangeSecretCreateUpdate.vue'),
|
||||
name: 'AccountChangeSecretUpdate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AssetChangeSecretUpdate'),
|
||||
permissions: ['accounts.change_changesecretautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/AccountChangeSecretDetail/index.vue'),
|
||||
name: 'AccountChangeSecretDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountChangeSecret'),
|
||||
permissions: ['accounts.view_changesecretautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'executions',
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/AccountChangeSecretDetail/AccountChangeSecretExecution/AccountChangeSecretExecutionList.vue'),
|
||||
name: 'AccountChangeSecretExecutionList',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ExecutionList'),
|
||||
permissions: ['accounts.view_changesecretexecution']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'executions/:id',
|
||||
component: () => import('@/views/accounts/AccountChangeSecret/AccountChangeSecretDetail/AccountChangeSecretExecution/AccountChangeSecretExecutionDetail/index.vue'),
|
||||
name: 'AccountChangeSecretExecutionDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ExecutionDetail'),
|
||||
permissions: ['accounts.view_changesecretexecution']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'account-push',
|
||||
name: 'AccountPush',
|
||||
component: empty,
|
||||
redirect: {
|
||||
name: 'AccountPushList'
|
||||
},
|
||||
meta: {
|
||||
app: 'accounts',
|
||||
name: 'BaseAccountPushList',
|
||||
resource: 'pushaccountautomation',
|
||||
icon: 'change-password'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/AccountPush/index.vue'),
|
||||
name: 'AccountPushList',
|
||||
meta: {
|
||||
title: i18n.t('AccountPushList'),
|
||||
menuTitle: i18n.t('AccountPushList'),
|
||||
permissions: ['accounts.view_pushaccountautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/accounts/AccountPush/AccountPushCreateUpdate.vue'),
|
||||
name: 'AccountPushCreate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountPushCreate'),
|
||||
permissions: ['accounts.add_pushaccountautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/accounts/AccountPush/AccountPushCreateUpdate.vue'),
|
||||
name: 'AccountPushUpdate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountPushUpdate'),
|
||||
permissions: ['accounts.change_pushaccountautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/accounts/AccountPush/AccountPushDetail/index.vue'),
|
||||
name: 'AccountPushDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountPushList'),
|
||||
permissions: ['accounts.view_pushaccountautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'executions',
|
||||
component: () => import('@/views/accounts/AccountPush/AccountPushExecutionList.vue'),
|
||||
name: 'AccountPushExecutionList',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ExecutionList'),
|
||||
permissions: ['accounts.view_pushaccountexecution']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'executions/:id',
|
||||
component: () => import('@/views/accounts/AccountPush/AccountPushExecutionDetail/index.vue'),
|
||||
name: 'AccountPushExecutionDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ExecutionDetail'),
|
||||
permissions: ['accounts.view_pushaccountexecution']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'account-backup',
|
||||
component: empty,
|
||||
name: 'AccountBackup',
|
||||
redirect: {
|
||||
name: 'AccountBackupList'
|
||||
},
|
||||
meta: {
|
||||
title: i18n.t('AccountBackup'),
|
||||
app: 'accounts',
|
||||
icon: 'backup',
|
||||
resource: 'accountbackupautomation'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/accounts/AccountBackup/index.vue'),
|
||||
name: 'AccountBackupList',
|
||||
meta: {
|
||||
menuTitle: i18n.t('AccountBackup'),
|
||||
title: i18n.t('AccountBackupList'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
permissions: ['accounts.view_accountbackupautomation']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/accounts/AccountBackup/AccountBackupCreateUpdate.vue'),
|
||||
name: 'AccountBackupCreate',
|
||||
meta: {
|
||||
title: i18n.t('AccountBackupCreate'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
action: 'create'
|
||||
},
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/accounts/AccountBackup/AccountBackupCreateUpdate.vue'),
|
||||
name: 'AccountBackupUpdate',
|
||||
meta: {
|
||||
title: i18n.t('AccountBackupUpdate'),
|
||||
// activeMenu: '/console/accounts/automations',
|
||||
action: 'update'
|
||||
},
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/accounts/AccountBackup/AccountBackupDetail/index.vue'),
|
||||
name: 'AccountBackupDetail',
|
||||
meta: {
|
||||
title: i18n.t('AccountBackupDetail'),
|
||||
activeMenu: '/console/accounts/automations'
|
||||
},
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'executions',
|
||||
component: () => import('@/views/accounts/AccountBackup/AccountBackupExecution/AccountBackupExecutionList.vue'),
|
||||
name: 'AccountBackupExecutionList',
|
||||
meta: { title: i18n.t('ExecutionDetail') },
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: 'executions/:id',
|
||||
component: () => import('@/views/accounts/AccountBackup/AccountBackupExecution/AccountBackupExecutionDetail/index.vue'),
|
||||
name: 'AccountBackupExecutionDetail',
|
||||
meta: { title: i18n.t('ExecutionDetail') },
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
89
src/router/pam/index.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import Layout from '@/layout'
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
import empty from '@/layout/empty'
|
||||
import automations from './automations'
|
||||
import integrations from './integrations'
|
||||
import security from './security'
|
||||
import activity from './activity'
|
||||
|
||||
export default {
|
||||
path: '/pam/',
|
||||
name: 'pam',
|
||||
component: Layout,
|
||||
redirect: '/pam/dashboard',
|
||||
meta: {
|
||||
title: i18n.t('PAM'),
|
||||
icon: 'pam',
|
||||
type: 'view',
|
||||
showNavSwitcher: true,
|
||||
permissions: [],
|
||||
view: 'pam'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/pam/dashboard',
|
||||
component: () => import('@/views/dashboard/Pam/index'),
|
||||
name: 'PamDashboard',
|
||||
meta: {
|
||||
icon: 'dashboard',
|
||||
title: i18n.t('Dashboard'),
|
||||
permissions: []
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/pam/accounts',
|
||||
name: 'PamAccounts',
|
||||
component: () => import('@/views/pam/Account/index.vue'),
|
||||
meta: {
|
||||
title: i18n.t('Accounts'),
|
||||
icon: 'accounts',
|
||||
permissions: []
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/pam/automations',
|
||||
name: 'AccountAutomation',
|
||||
component: empty,
|
||||
meta: {
|
||||
title: i18n.t('Automation'),
|
||||
icon: 'accounts',
|
||||
permissions: []
|
||||
},
|
||||
children: automations
|
||||
},
|
||||
{
|
||||
path: '/pam/security',
|
||||
name: 'AccountSecurity',
|
||||
component: empty,
|
||||
meta: {
|
||||
title: i18n.t('Security'),
|
||||
icon: 'accounts',
|
||||
permissions: []
|
||||
},
|
||||
children: security
|
||||
},
|
||||
{
|
||||
path: '/pam/integrations',
|
||||
name: 'Integrations',
|
||||
component: empty,
|
||||
meta: {
|
||||
title: i18n.t('Integration'),
|
||||
icon: 'accounts',
|
||||
permissions: []
|
||||
},
|
||||
children: integrations
|
||||
},
|
||||
{
|
||||
path: '/pam/activity',
|
||||
name: 'AccountActivity',
|
||||
component: empty,
|
||||
meta: {
|
||||
title: i18n.t('Activity'),
|
||||
icon: 'accounts',
|
||||
permissions: []
|
||||
},
|
||||
children: activity
|
||||
}
|
||||
]
|
||||
}
|
||||
60
src/router/pam/integrations.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import empty from '@/layout/empty.vue'
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
export default [
|
||||
{
|
||||
path: 'services',
|
||||
name: 'Services',
|
||||
component: empty,
|
||||
redirect: {
|
||||
name: 'IntegrationApplicationList'
|
||||
},
|
||||
meta: {
|
||||
app: 'accounts',
|
||||
name: 'Service',
|
||||
icon: 'service',
|
||||
resource: 'integrationapplicaion'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/pam/Integration/Application.vue'),
|
||||
name: 'IntegrationApplicationList',
|
||||
meta: {
|
||||
title: i18n.t('Applications'),
|
||||
permissions: ['accounts.view_integrationapplication']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/pam/Integration/ApplicationCreateUpdate.vue'),
|
||||
name: 'IntegrationApplicationCreate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('IntegrationApplicationCreate'),
|
||||
permissions: ['accounts.add_integrationapplication']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/pam/Integration/ApplicationCreateUpdate.vue'),
|
||||
name: 'IntegrationApplicationUpdate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('IntegrationApplicationUpdate'),
|
||||
permissions: ['accounts.change_integrationapplication']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: () => import('@/views/pam/Integration/ApplicationDetail/index.vue'),
|
||||
name: 'IntegrationApplicationDetail',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('ApplicationDetail'),
|
||||
permissions: ['accounts.view_integrationapplication']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
37
src/router/pam/security.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import empty from '@/layout/empty.vue'
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
export default [
|
||||
{
|
||||
path: 'check',
|
||||
name: 'AccountCheck',
|
||||
component: empty,
|
||||
meta: {
|
||||
app: 'accounts',
|
||||
name: 'BaseAccountRisk',
|
||||
icon: 'scan',
|
||||
resource: 'accountcheckautomation'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/pam/RiskDetect/index.vue'),
|
||||
name: 'AccountCheckList',
|
||||
meta: {
|
||||
title: i18n.t('RiskDetection'),
|
||||
permissions: ['accounts.view_accountrisk']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/pam/RiskDetect/AccountCheckCreateUpdate.vue'),
|
||||
name: 'AccountCheckCreateUpdate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('AccountCheckCreate'),
|
||||
permissions: ['accounts.view_accountrisk']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -31,6 +31,7 @@ const getters = {
|
||||
hasValidLicense: state => state.settings.hasValidLicense,
|
||||
isSystemAdmin: state => state.users.profile.system_roles.some(i => (i?.id === '00000000-0000-0000-0000-000000000001')),
|
||||
sqlQueryCounter: state => state.common.sqlQueryCounter,
|
||||
showSqlQueryCounter: state => state.common.showSqlQueryCounter
|
||||
showSqlQueryCounter: state => state.common.showSqlQueryCounter,
|
||||
inDrawer: state => state.common.inDrawer
|
||||
}
|
||||
export default getters
|
||||
|
||||
@@ -7,7 +7,9 @@ const getDefaultState = () => {
|
||||
isRouterAlive: true,
|
||||
sqlQueryCounter: [],
|
||||
showSqlQueryCounter: true,
|
||||
confirmDialogVisible: false
|
||||
confirmDialogVisible: false,
|
||||
drawerActionMeta: {},
|
||||
inDrawer: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +87,17 @@ const actions = {
|
||||
},
|
||||
showSqlQueryCounter({ commit, state }, show) {
|
||||
state.showSqlQueryCounter = show
|
||||
},
|
||||
setDrawerActionMeta({ commit, state }, meta) {
|
||||
state.drawerActionMeta = meta
|
||||
state.inDrawer = true
|
||||
},
|
||||
getDrawerActionMeta({ commit, state }) {
|
||||
return state.drawerActionMeta
|
||||
},
|
||||
cleanDrawerActionMeta({ commit, state }) {
|
||||
state.drawerActionMeta = {}
|
||||
state.inDrawer = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -254,6 +254,10 @@ input[type=file] {
|
||||
line-height: 1.5;
|
||||
border-right: none;
|
||||
color: var(--color-text-primary);
|
||||
|
||||
a {
|
||||
color: #1c84c6
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
@@ -275,6 +279,8 @@ input[type=file] {
|
||||
.el-table__column-filter-trigger {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
td {
|
||||
@@ -541,8 +547,23 @@ li.rmenu i.fa {
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.el-checkbox__input {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.el-checkbox__label {
|
||||
padding-left: 5px !important;
|
||||
}
|
||||
|
||||
.el-checkbox__inner {
|
||||
border-radius: 1px !important;
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
|
||||
&::after {
|
||||
left: 5px !important;
|
||||
height: 8px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-alert.el-alert--info.is-light {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { message } from '@/utils/message'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
export function getApiPath(that) {
|
||||
export function getApiPath(that, objectId) {
|
||||
let pagePath = that.$route.path
|
||||
const pagePathArray = pagePath.split('/')
|
||||
if (pagePathArray.indexOf('orgs') !== -1) {
|
||||
@@ -11,6 +11,9 @@ export function getApiPath(that) {
|
||||
} else if (pagePathArray.indexOf('gathered-user') !== -1 || pagePathArray.indexOf('change-auth-plan') !== -1) {
|
||||
pagePathArray[pagePathArray.indexOf('accounts')] = 'xpack'
|
||||
}
|
||||
if (pagePathArray.indexOf(objectId) === -1) {
|
||||
pagePathArray.push(objectId)
|
||||
}
|
||||
if (pagePathArray.indexOf('tickets') !== -1) {
|
||||
// ticket ...
|
||||
pagePath = pagePathArray.slice(1, pagePathArray.length).join('/')
|
||||
@@ -310,4 +313,5 @@ export function toSentenceCase(string) {
|
||||
}).join(' ')
|
||||
return s[0].toUpperCase() + s.slice(1)
|
||||
}
|
||||
|
||||
export { BASE_URL }
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
import { constantRoutes } from '@/router'
|
||||
import store from '@/store'
|
||||
import { openWindow } from './common'
|
||||
|
||||
let openedTaskWindow = null // 保存已打开的窗口对象
|
||||
|
||||
function openOrReuseWindow(url, windowName = 'task', windowFeatures = '', iWidth = 900, iHeight = 600) {
|
||||
const iTop = (window.screen.height - 30 - iHeight) / 2
|
||||
const iLeft = (window.screen.width - 10 - iWidth) / 2
|
||||
|
||||
// 检查窗口是否已经打开
|
||||
if (openedTaskWindow && !openedTaskWindow.closed) {
|
||||
openedTaskWindow.location.href = url // 如果窗口未关闭,更新其地址
|
||||
openedTaskWindow.focus() // 将窗口置于前台
|
||||
} else {
|
||||
// 如果窗口未打开或已关闭,创建新窗口
|
||||
openedTaskWindow = window.open(url, windowName, 'height=' + iHeight + ',width=' + iWidth + ',top=' + iTop + ',left=' + iLeft)
|
||||
}
|
||||
}
|
||||
|
||||
export function openTaskPage(taskId, taskType, taskUrl) {
|
||||
taskType = taskType || 'celery'
|
||||
if (!taskUrl) {
|
||||
taskUrl = `/core/ops/${taskType}/task/${taskId}/log/?type=${taskType}`
|
||||
}
|
||||
openWindow(taskUrl)
|
||||
openOrReuseWindow(taskUrl)
|
||||
}
|
||||
|
||||
export function checkPermission(permsRequired, permsAll) {
|
||||
@@ -96,7 +111,8 @@ export function getPermedViews() {
|
||||
['audit', store.getters.auditOrgs.length > 0],
|
||||
['workbench', true],
|
||||
['tickets', hasPermission('tickets.view_ticket')],
|
||||
['settings', hasPermission('settings.view_setting')]
|
||||
['settings', hasPermission('settings.view_setting')],
|
||||
['pam', store.getters.consoleOrgs.length > 0]
|
||||
]
|
||||
return viewShowMapper.filter(i => i[1]).map(i => i[0])
|
||||
}
|
||||
@@ -155,3 +171,22 @@ export function IsSupportPauseSessionType(terminalType) {
|
||||
const supportedType = ['koko', 'lion', 'chen', 'kael']
|
||||
return supportedType.includes(terminalType)
|
||||
}
|
||||
|
||||
export function loadPlatformIcon(name, type) {
|
||||
const platformMap = {
|
||||
'Huawei': 'huawei',
|
||||
'Cisco': 'cisco',
|
||||
'Gateway': 'gateway',
|
||||
'macOS': 'macos',
|
||||
'BSD': 'bsd',
|
||||
'Vmware-vSphere': 'vmware'
|
||||
}
|
||||
|
||||
const value = platformMap[name] || type
|
||||
|
||||
try {
|
||||
return require(`@/assets/img/icons/${value}.png`)
|
||||
} catch (error) {
|
||||
return require(`@/assets/img/icons/other.png`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ export async function checkUserFirstLogin({ to, from, next }) {
|
||||
export async function changeCurrentViewIfNeed({ to, from, next }) {
|
||||
let viewName = to.path.split('/')[1]
|
||||
// 这几个是需要检测的, 切换视图组织时,避免 404, 这里不能加 settings, 因为 默认没有返回 setting 组织(System) 的管理权限
|
||||
if (['console', 'audit', 'workbench', 'tickets', ''].indexOf(viewName) === -1) {
|
||||
if (['console', 'audit', 'workbench', 'tickets', 'pam', ''].indexOf(viewName) === -1) {
|
||||
Vue.$log.debug('Current view no need check', viewName)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
export function randomString(length) {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
let result = ''
|
||||
const charactersLength = characters.length
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength))
|
||||
export function randomString(length, includeSymbols = false) {
|
||||
const upperCase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
const lowerCase = 'abcdefghijklmnopqrstuvwxyz'
|
||||
const numbers = '0123456789'
|
||||
const symbols = '!@#$%^&*()-_=+[]{}|;:,.<>?'
|
||||
|
||||
// 根据是否包含特殊字符来决定字符集
|
||||
let allCharacters = upperCase + lowerCase + numbers
|
||||
if (includeSymbols) {
|
||||
allCharacters += symbols
|
||||
}
|
||||
return result
|
||||
|
||||
let result = ''
|
||||
|
||||
// 如果包含特殊字符,确保至少包含一个大写字母、一个小写字母、一个数字、一个符号
|
||||
if (includeSymbols) {
|
||||
result += upperCase.charAt(Math.floor(Math.random() * upperCase.length))
|
||||
result += lowerCase.charAt(Math.floor(Math.random() * lowerCase.length))
|
||||
result += numbers.charAt(Math.floor(Math.random() * numbers.length))
|
||||
result += symbols.charAt(Math.floor(Math.random() * symbols.length))
|
||||
}
|
||||
|
||||
const allCharactersLength = allCharacters.length
|
||||
|
||||
// 填充剩余的字符
|
||||
for (let i = result.length; i < length; i++) {
|
||||
result += allCharacters.charAt(Math.floor(Math.random() * allCharactersLength))
|
||||
}
|
||||
|
||||
// 随机打乱结果
|
||||
return result.split('').sort(() => 0.5 - Math.random()).join('')
|
||||
}
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="15" :sm="24">
|
||||
<AutoDetailCard :object="object" v-bind="detail" />
|
||||
</el-col>
|
||||
<el-col :md="9" :sm="24">
|
||||
<QuickActions :actions="quickActions" type="primary" />
|
||||
<ViewSecret
|
||||
v-if="showViewSecretDialog"
|
||||
:account="object"
|
||||
:url="secretUrl"
|
||||
:visible.sync="showViewSecretDialog"
|
||||
/>
|
||||
<AutomationParamsForm
|
||||
:has-button="false"
|
||||
:method="pushAccountMethod"
|
||||
:visible.sync="autoPushVisible"
|
||||
@canSetting="onCanSetting"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="15" :sm="24" class="auto-detail-card">
|
||||
<AutoDetailCard :object="object" v-bind="detail" />
|
||||
</el-col>
|
||||
<el-col :md="9" :sm="24" class="quick-actions">
|
||||
<QuickActions :actions="quickActions" type="primary" />
|
||||
<ViewSecret
|
||||
v-if="showViewSecretDialog"
|
||||
:account="object"
|
||||
:url="secretUrl"
|
||||
:visible.sync="showViewSecretDialog"
|
||||
/>
|
||||
<AutomationParamsForm
|
||||
:has-button="false"
|
||||
:method="pushAccountMethod"
|
||||
:visible.sync="autoPushVisible"
|
||||
@canSetting="onCanSetting"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-drawer
|
||||
size="50%"
|
||||
:with-header="false"
|
||||
:append-to-body="true"
|
||||
:visible.sync="pamDrawerShow"
|
||||
>
|
||||
<component :is="drawerRefName" />
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -28,12 +39,14 @@ import QuickActions from '@/components/QuickActions/index.vue'
|
||||
import ViewSecret from '@/components/Apps/AccountListTable/ViewSecret.vue'
|
||||
import { openTaskPage } from '@/utils/jms'
|
||||
import AutomationParamsForm from '@/views/assets/Platform/AutomationParamsSetting.vue'
|
||||
import AssetDetail from '@/views/assets/Asset/AssetDetail'
|
||||
|
||||
export default {
|
||||
name: 'Detail',
|
||||
components: {
|
||||
AutoDetailCard,
|
||||
QuickActions,
|
||||
AssetDetail,
|
||||
AutomationParamsForm,
|
||||
ViewSecret
|
||||
},
|
||||
@@ -47,6 +60,8 @@ export default {
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
pamDrawerShow: false,
|
||||
drawerRefName: null,
|
||||
needSetAutoPushParams: false,
|
||||
autoPushVisible: false,
|
||||
secretUrl: `/api/v1/accounts/account-secrets/${this.object.id}/`,
|
||||
@@ -213,6 +228,22 @@ export default {
|
||||
name: 'AssetDetail',
|
||||
params: { id: this.object.asset.id }
|
||||
}
|
||||
|
||||
console.log(this.$route)
|
||||
|
||||
if (this.$route.params.type === 'pam') {
|
||||
return (
|
||||
<span style={{ color: '#1c84c6', cursor: 'pointer' }} onClick={() => {
|
||||
this.pamDrawerShow = true
|
||||
this.$route.params.id = this.object.asset.id
|
||||
|
||||
this.drawerRefName = 'AssetDetail'
|
||||
}}>
|
||||
{value.name}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return <router-link to={route}>{value?.name}</router-link>
|
||||
},
|
||||
su_from: (item, value) => {
|
||||
|
||||
@@ -38,6 +38,7 @@ export default {
|
||||
AccountVisible: false,
|
||||
account: {},
|
||||
config: {
|
||||
url: '/api/v1/accounts/accounts',
|
||||
activeMenu: 'Detail',
|
||||
submenu: [
|
||||
{
|
||||
|
||||
84
src/views/accounts/AccountActivity/AccountActivityList.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div>
|
||||
<GenericListPage
|
||||
v-loading="loading"
|
||||
:header-actions="headerActions"
|
||||
:table-config="tableConfig"
|
||||
/>
|
||||
<DiffDetail ref="DetailDialog" :title="$tc('OperateLog')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericListPage from '@/layout/components/GenericListPage'
|
||||
import { ActionsFormatter } from '@/components/Table/TableFormatters'
|
||||
import DiffDetail from '@/components/Dialog/DiffDetail'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GenericListPage,
|
||||
DiffDetail
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
rowObj: {
|
||||
diff: ''
|
||||
},
|
||||
logDetailVisible: false,
|
||||
loading: false,
|
||||
tableConfig: {
|
||||
url: '/api/v1/audits/operate-logs/?resource_type=Account',
|
||||
columnsShow: {
|
||||
min: ['user', 'resource'],
|
||||
default: [
|
||||
'user', 'action_display', 'resource', 'remote_addr', 'datetime', 'action', 'actions'
|
||||
]
|
||||
},
|
||||
columnsMeta: {
|
||||
user: {
|
||||
'min-width': '120px'
|
||||
},
|
||||
actions: {
|
||||
formatter: ActionsFormatter,
|
||||
formatterArgs: {
|
||||
hasUpdate: false,
|
||||
hasDelete: false,
|
||||
hasClone: false,
|
||||
extraActions: [
|
||||
{
|
||||
name: 'View',
|
||||
title: this.$t('View'),
|
||||
type: 'primary',
|
||||
callback: ({ row }) => {
|
||||
vm.loading = true
|
||||
this.$axios.get(
|
||||
`/api/v1/audits/operate-logs/${row.id}/?type=action_detail`
|
||||
).then(res => {
|
||||
this.$refs.DetailDialog.show(res.diff)
|
||||
}).finally(() => {
|
||||
vm.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
headerActions: {
|
||||
hasLeftActions: false,
|
||||
hasImport: false,
|
||||
hasDatePicker: true,
|
||||
searchConfig: {
|
||||
exclude: ['resource_type']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -6,6 +6,7 @@
|
||||
import { GenericCreateUpdatePage } from '@/layout/components'
|
||||
import getChangeSecretFields from '@/views/accounts/AccountBackup/fields'
|
||||
import { encryptPassword } from '@/utils/crypto'
|
||||
import { periodicMeta } from '@/components/const'
|
||||
|
||||
export default {
|
||||
name: 'AccountBackupUpdate',
|
||||
@@ -40,9 +41,7 @@ export default {
|
||||
categories: []
|
||||
},
|
||||
fieldsMeta: {
|
||||
is_periodic: fields.is_periodic,
|
||||
crontab: fields.crontab,
|
||||
interval: fields.interval,
|
||||
...periodicMeta,
|
||||
is_password_divided_by_email: fields.is_password_divided_by_email,
|
||||
zip_encrypt_password: fields.zip_encrypt_password,
|
||||
is_password_divided_by_obj_storage: fields.is_password_divided_by_obj_storage,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<script>
|
||||
import { QuickActions } from '@/components'
|
||||
import AutoDetailCard from '@/components/Cards/DetailCard/auto'
|
||||
import AutoDetailCard from '@/components/Cards/DetailCard/auto.vue'
|
||||
import { openTaskPage } from '@/utils/jms'
|
||||
|
||||
export default {
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
],
|
||||
url: `/api/v1/accounts/account-backup-plans/${this.object.id}/`,
|
||||
detailFields: [
|
||||
'id', 'name',
|
||||
'id', 'name', 'backup_type',
|
||||
{
|
||||
key: this.$t('Crontab'),
|
||||
value: this.object.crontab,
|
||||
@@ -68,7 +68,6 @@ export default {
|
||||
return <span>{this.object.is_periodic ? val : '-'}</span>
|
||||
}
|
||||
},
|
||||
'date_created', 'date_updated', 'comment',
|
||||
{
|
||||
key: this.$t('Recipient') + ' A',
|
||||
value: this.object.recipients_part_one,
|
||||
@@ -100,7 +99,8 @@ export default {
|
||||
const recipientServerB = this.isEmail ? '-' : val.map(item => item.name).join(', ')
|
||||
return <span>{recipientServerB}</span>
|
||||
}
|
||||
}
|
||||
},
|
||||
'date_created', 'date_updated', 'is_active', 'comment'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
<script>
|
||||
import { GenericDetailPage } from '@/layout/components'
|
||||
import AccountBackupInfo from './AccountBackupInfo.vue'
|
||||
import AccountBackupExecutionList from './AccountBackupExecution/AccountBackupExecutionList.vue'
|
||||
import AccountBackupExecutionList
|
||||
from '@/views/accounts/AccountBackup/AccountBackupExecution/AccountBackupExecutionList.vue'
|
||||
|
||||
export default {
|
||||
name: 'AccountBackupDetail',
|
||||
components: {
|
||||
GenericDetailPage,
|
||||
AccountBackupInfo,
|
||||
@@ -38,7 +40,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AutoDetailCard from '@/components/Cards/DetailCard/auto'
|
||||
import AutoDetailCard from '@/components/Cards/DetailCard/auto.vue'
|
||||
|
||||
export default {
|
||||
name: 'AccountBackupExecutionInfo',
|
||||