mirror of
https://github.com/jumpserver/lina.git
synced 2026-01-29 21:28:52 +00:00
perf: update perm
This commit is contained in:
@@ -49,7 +49,7 @@
|
||||
|
||||
<script>
|
||||
import ListTable from '@/components/Table/ListTable/index.vue'
|
||||
import { ActionsFormatter } from '@/components/Table/TableFormatters'
|
||||
import { ActionsFormatter, PlatformFormatter } from '@/components/Table/TableFormatters'
|
||||
import ViewSecret from './ViewSecret.vue'
|
||||
import UpdateSecretInfo from './UpdateSecretInfo.vue'
|
||||
import AccountCreateUpdate from './AccountCreateUpdate.vue'
|
||||
@@ -89,7 +89,7 @@ export default {
|
||||
},
|
||||
hasClone: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: true
|
||||
},
|
||||
asset: {
|
||||
type: Object,
|
||||
@@ -119,7 +119,7 @@ export default {
|
||||
columnsDefault: {
|
||||
type: Array,
|
||||
default: () => ([
|
||||
'name', 'username', 'asset', 'date_updated'
|
||||
'name', 'username', 'secret', 'asset', 'platform', 'date_updated', 'connect'
|
||||
])
|
||||
},
|
||||
headerExtraActions: {
|
||||
@@ -153,6 +153,7 @@ export default {
|
||||
},
|
||||
extraQuery: this.extraQuery,
|
||||
columnsExclude: ['spec_info'],
|
||||
columnsAdd: ['secret', 'platform', 'connect'],
|
||||
columnsShow: {
|
||||
min: ['name', 'username', 'actions'],
|
||||
default: this.columnsDefault
|
||||
@@ -172,6 +173,28 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
secret: {
|
||||
formatter: () => {
|
||||
// Todo: 通用的 formatter, 点击后 10s 后还原为 *
|
||||
return (
|
||||
<span class='secret'>
|
||||
<i class='fa fa-clone'/> ******
|
||||
</span>
|
||||
)
|
||||
}
|
||||
},
|
||||
connect: {
|
||||
label: this.$t('Connect'),
|
||||
formatter: () => {
|
||||
return (
|
||||
<span class='connect'>
|
||||
<el-button type='primary' size='mini'>
|
||||
<i class='fa fa-terminal'/>
|
||||
</el-button>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
},
|
||||
asset: {
|
||||
formatter: function(row) {
|
||||
const to = {
|
||||
@@ -185,6 +208,13 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
platform: {
|
||||
label: this.$t('Platform'),
|
||||
formatter: PlatformFormatter,
|
||||
formatterArgs: {
|
||||
platformAttr: 'asset.platform'
|
||||
}
|
||||
},
|
||||
username: {
|
||||
width: '120px'
|
||||
},
|
||||
@@ -218,6 +248,7 @@ export default {
|
||||
hasUpdate: false, // can set function(row, value)
|
||||
hasDelete: false, // can set function(row, value)
|
||||
hasClone: this.hasClone,
|
||||
canClone: true,
|
||||
moreActionsTitle: this.$t('More'),
|
||||
extraActions: [
|
||||
{
|
||||
@@ -350,8 +381,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 => {
|
||||
|
||||
@@ -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 的问题;
|
||||
|
||||
@@ -135,7 +135,7 @@ export default {
|
||||
{
|
||||
name: 'clone',
|
||||
title: this.$t('Duplicate'),
|
||||
type: 'info',
|
||||
type: 'primary',
|
||||
has: colActions.hasClone,
|
||||
can: colActions.canClone,
|
||||
callback: colActions.onClone,
|
||||
|
||||
60
src/components/Table/TableFormatters/PlatformFormatter.vue
Normal file
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>
|
||||
@@ -23,8 +23,8 @@
|
||||
v-if="item.has"
|
||||
:key="index"
|
||||
:content="item.tooltip"
|
||||
:open-delay="500"
|
||||
effect="dark"
|
||||
open-delay="500"
|
||||
placement="top"
|
||||
>
|
||||
<i :class="[item.class, item.icon]" class="fa" @click="item.action()" />
|
||||
|
||||
@@ -18,6 +18,7 @@ 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'
|
||||
|
||||
export default {
|
||||
DetailFormatter,
|
||||
@@ -39,6 +40,7 @@ export default {
|
||||
TagChoicesFormatter,
|
||||
LabelsFormatter,
|
||||
SwitchFormatter,
|
||||
PlatformFormatter,
|
||||
AccountInfoFormatter
|
||||
}
|
||||
|
||||
@@ -62,5 +64,6 @@ export {
|
||||
TagChoicesFormatter,
|
||||
LabelsFormatter,
|
||||
SwitchFormatter,
|
||||
PlatformFormatter,
|
||||
AccountInfoFormatter
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -23,7 +23,7 @@ export default {
|
||||
children: [
|
||||
{
|
||||
path: '/pam/dashboard',
|
||||
component: () => import('@/views/dashboard/Audit/index'),
|
||||
component: () => import('@/views/dashboard/Pam/index'),
|
||||
name: 'PamDashboard',
|
||||
meta: {
|
||||
icon: 'dashboard',
|
||||
@@ -34,7 +34,7 @@ export default {
|
||||
{
|
||||
path: '/pam/accounts',
|
||||
name: 'PamAccounts',
|
||||
component: () => import('@/views/pam/Account/AccountList.vue'),
|
||||
component: () => import('@/views/pam/Account/index.vue'),
|
||||
meta: {
|
||||
title: i18n.t('Accounts'),
|
||||
icon: 'accounts',
|
||||
|
||||
@@ -188,6 +188,7 @@ const actions = {
|
||||
commit('SET_MFA_VERIFY')
|
||||
},
|
||||
changeToView({ commit }, viewName) {
|
||||
console.log('Change to view')
|
||||
const mapper = {
|
||||
console: state.consoleOrgs,
|
||||
audit: state.auditOrgs,
|
||||
|
||||
@@ -96,7 +96,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 +156,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
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<script>
|
||||
import { ListTable } from '@/components'
|
||||
import {
|
||||
ActionsFormatter, ArrayFormatter, ChoicesFormatter, DetailFormatter, ProtocolsFormatter
|
||||
ActionsFormatter, ArrayFormatter, ChoicesFormatter, DetailFormatter, ProtocolsFormatter, PlatformFormatter
|
||||
} from '@/components/Table/TableFormatters'
|
||||
import AssetBulkUpdateDialog from './AssetBulkUpdateDialog'
|
||||
import { connectivityMeta } from '@/components/Apps/AccountListTable/const'
|
||||
@@ -144,7 +144,8 @@ export default {
|
||||
sortable: true
|
||||
},
|
||||
platform: {
|
||||
sortable: true
|
||||
sortable: true,
|
||||
formatter: PlatformFormatter
|
||||
},
|
||||
protocols: {
|
||||
showFullContent: true,
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
shadow="hover"
|
||||
@click.native="createAsset(platform)"
|
||||
>
|
||||
<img :src="loadImage(platform)" alt="icon" class="asset-icon">
|
||||
<div class="icon-zone">
|
||||
<img :src="loadImage(platform)" alt="icon" class="asset-icon">
|
||||
</div>
|
||||
<span class="platform-name">{{ platform.name }}</span>
|
||||
</el-card>
|
||||
</el-tooltip>
|
||||
@@ -42,6 +44,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import Dialog from '@/components/Dialog'
|
||||
import { loadPlatformIcon } from '@/utils/jms'
|
||||
|
||||
export default {
|
||||
name: 'PlatformDialog',
|
||||
@@ -126,23 +129,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
loadImage(platform) {
|
||||
const platformMap = {
|
||||
'Huawei': 'huawei',
|
||||
'Cisco': 'cisco',
|
||||
'Gateway': 'gateway',
|
||||
'macOS': 'macos',
|
||||
'BSD': 'bsd',
|
||||
'Vmware-vSphere': 'vmware'
|
||||
}
|
||||
|
||||
const value = platformMap[platform.name] || platform.type.value
|
||||
|
||||
try {
|
||||
return require(`@/assets/img/icons/${value}.png`)
|
||||
} catch (error) {
|
||||
this.$log.debug(`Image not found: ${value}.png`)
|
||||
return require(`@/assets/img/icons/other.png`)
|
||||
}
|
||||
return loadPlatformIcon(platform.name, platform.type)
|
||||
},
|
||||
loadRecentPlatformIds() {
|
||||
const recentPlatformIds = JSON.parse(localStorage.getItem('RecentPlatforms')) || []
|
||||
@@ -235,8 +222,11 @@ export default {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.asset-icon {
|
||||
.icon-zone {
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
.asset-icon {
|
||||
height: 2em;
|
||||
vertical-align: -0.2em;
|
||||
fill: currentColor;
|
||||
|
||||
123
src/views/dashboard/Pam/AccountSummary.vue
Normal file
123
src/views/dashboard/Pam/AccountSummary.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="box">
|
||||
<div style="margin-bottom: 12px;">
|
||||
<Title :config="config" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<el-row justify="space-between" type="flex">
|
||||
<el-col v-for="item of summaryItems" :key="item.title" :md="8" :sm="12" :xs="12">
|
||||
<SummaryCard :body="item.body" :title="item.title" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Title from '../components/Title.vue'
|
||||
import SummaryCard from '../components/SummaryCard'
|
||||
|
||||
export default {
|
||||
components: { Title, SummaryCard },
|
||||
data() {
|
||||
return {
|
||||
config: {
|
||||
title: this.$t('账号汇总'),
|
||||
tip: this.$t('RealTimeData')
|
||||
},
|
||||
counter: {
|
||||
total_count_online_sessions: '.',
|
||||
total_count_online_users: '.',
|
||||
total_count_today_failed_sessions: '.'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
summaryItems() {
|
||||
return [
|
||||
{
|
||||
title: this.$t('特权账号'),
|
||||
body: {
|
||||
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
|
||||
count: 4932,
|
||||
disabled: !this.$hasPerm('terminal.view_session')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$t('普通账号'),
|
||||
body: {
|
||||
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
|
||||
count: 2323,
|
||||
disabled: !this.$hasPerm('terminal.view_session')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$t('未托管账号'),
|
||||
body: {
|
||||
count: 1233,
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$t('不可用账号'),
|
||||
body: {
|
||||
count: 123,
|
||||
disabled: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.counter = await this.getResourcesCount()
|
||||
},
|
||||
methods: {
|
||||
async getResourcesCount() {
|
||||
return this.$axios.get(
|
||||
'/api/v1/index/',
|
||||
{
|
||||
params: {
|
||||
total_count_online_sessions: 1,
|
||||
total_count_online_users: 1,
|
||||
total_count_today_failed_sessions: 1
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box {
|
||||
padding: 20px;
|
||||
background: #FFFFFF;
|
||||
|
||||
.content {
|
||||
.el-col {
|
||||
padding-left: 16px;
|
||||
border-left: 1px solid #EFF0F1;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: #646A73;
|
||||
}
|
||||
|
||||
.num {
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
60
src/views/dashboard/Pam/AssetProportionSummary.vue
Normal file
60
src/views/dashboard/Pam/AssetProportionSummary.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="box">
|
||||
<div class="head">
|
||||
<Title :config="titleConfig" />
|
||||
</div>
|
||||
<ProgressChart v-if="config.data.length > 0" v-bind="config" />
|
||||
<div v-else class="no-data">{{ $tc('NoData') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Title from '../components/Title.vue'
|
||||
import ProgressChart from '../components/ProgressChart.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Title,
|
||||
ProgressChart
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
titleConfig: {
|
||||
title: this.$t('ProportionOfAssetTypes'),
|
||||
tip: this.$t('ProportionOfAssetTypes')
|
||||
},
|
||||
config: {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getChartData()
|
||||
},
|
||||
methods: {
|
||||
async getChartData() {
|
||||
const url = '/api/v1/index/?total_count_type_to_assets_amount=1'
|
||||
const data = await this.$axios.get(url)
|
||||
this.$set(this.config, 'data', data.total_count_type_to_assets_amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box {
|
||||
margin-top: 16px;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
.head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
.no-data {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
104
src/views/dashboard/Pam/DataSummary.vue
Normal file
104
src/views/dashboard/Pam/DataSummary.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :lg="24" :sm="12">
|
||||
<SummaryChart :config="userConfig" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SummaryChart from './SummaryChart.vue'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SummaryChart
|
||||
},
|
||||
data() {
|
||||
const documentStyle = document.documentElement.style
|
||||
const themeColor = documentStyle.getPropertyValue('--color-primary')
|
||||
|
||||
return {
|
||||
userConfig: {
|
||||
title: this.$t('账号数据'),
|
||||
tip: this.$t('UserData'),
|
||||
subTitle: this.$t('账号总数'),
|
||||
icon: 'users',
|
||||
subIcon: 'broken-line',
|
||||
color: '#FFD260',
|
||||
chartTitle: this.$t('LoginUserToday'),
|
||||
data: [],
|
||||
route: { name: 'UserList' },
|
||||
total: 0,
|
||||
active: 0,
|
||||
weekAdd: 0
|
||||
},
|
||||
assetConfig: {
|
||||
title: this.$t('AssetData'),
|
||||
tip: this.$t('AssetData'),
|
||||
subTitle: this.$t('AssetsTotal'),
|
||||
icon: 'assets',
|
||||
subIcon: 'broken-line',
|
||||
color: themeColor,
|
||||
chartTitle: this.$t('LoginAssetToday'),
|
||||
data: [],
|
||||
route: { name: 'AssetList' }
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
async init() {
|
||||
const data = await this.$axios.get(`/api/v1/index/?total_count_users=1
|
||||
&total_count_users_this_week=1
|
||||
&total_count_login_users=1
|
||||
&total_count_assets=1
|
||||
&total_count_assets_this_week=1
|
||||
&total_count_today_active_assets=1
|
||||
`)
|
||||
|
||||
const loginUserCountDecimal = data.total_count_login_users ? new Decimal(data.total_count_login_users) : new Decimal(0)
|
||||
const userCountDecimal = data.total_count_users ? new Decimal(data.total_count_users) : new Decimal(0)
|
||||
|
||||
let userActive = loginUserCountDecimal.dividedBy(userCountDecimal).times(100)
|
||||
userActive = isNaN(userActive) ? 0 : userActive
|
||||
userActive = userActive.toFixed(2)
|
||||
const userTotal = userActive === 100 ? 0 : 100 - userActive
|
||||
const users = [
|
||||
{ name: this.$t('ActiveUser'), value: userActive.toString() },
|
||||
{ name: this.$t('InActiveUser'), value: userTotal.toString() }
|
||||
]
|
||||
this.$set(this.userConfig, 'data', users)
|
||||
this.userConfig.total = data.total_count_users
|
||||
this.userConfig.active = data.total_count_login_users
|
||||
this.userConfig.weekAdd = data.total_count_users_this_week
|
||||
|
||||
const ActiveAssetCountDecimal = data.total_count_today_active_assets ? new Decimal(data.total_count_today_active_assets) : new Decimal(0)
|
||||
const AssetCountDecimal = data.total_count_assets ? new Decimal(data.total_count_assets) : new Decimal(0)
|
||||
|
||||
let assetActive = ActiveAssetCountDecimal.dividedBy(AssetCountDecimal).times(100)
|
||||
assetActive = isNaN(assetActive) ? 0 : assetActive
|
||||
assetActive = assetActive.toFixed(2)
|
||||
const assetTotal = assetActive === 100 ? 0 : 100 - assetActive
|
||||
const assets = [
|
||||
{ name: this.$t('ActiveAsset'), value: assetActive.toString() },
|
||||
{ name: this.$t('InActiveAsset'), value: assetTotal.toString() }
|
||||
]
|
||||
this.$set(this.assetConfig, 'data', assets)
|
||||
this.$set(this.assetConfig, 'total', data.total_count_assets)
|
||||
this.$set(this.assetConfig, 'active', data.total_count_today_active_assets)
|
||||
this.$set(this.assetConfig, 'weekAdd', data.total_count_assets_this_week)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.left, .right {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
64
src/views/dashboard/Pam/RankSummary.vue
Normal file
64
src/views/dashboard/Pam/RankSummary.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :lg="12" :sm="24">
|
||||
<RankTable :config="userConfig" />
|
||||
</el-col>
|
||||
<el-col :lg="12" :sm="24">
|
||||
<RankTable :config="assetConfig" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RankTable from '../components/RankTable.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RankTable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userConfig: {
|
||||
title: this.$t('LoginUserRanking'),
|
||||
url: '/api/v1/index/?dates_login_times_top10_users=1',
|
||||
tip: this.$t('LoginUserRanking'),
|
||||
data: 'dates_login_times_top10_users',
|
||||
columns: [
|
||||
{
|
||||
prop: 'user',
|
||||
label: this.$t('Username')
|
||||
},
|
||||
{
|
||||
prop: 'total',
|
||||
label: this.$t('LoginCount'),
|
||||
width: '120px'
|
||||
}
|
||||
]
|
||||
},
|
||||
assetConfig: {
|
||||
title: this.$t('ActiveAssetRanking'),
|
||||
url: '/api/v1/index/?dates_login_times_top10_assets=1',
|
||||
tip: this.$t('ActiveAssetRanking'),
|
||||
data: 'dates_login_times_top10_assets',
|
||||
columns: [
|
||||
{
|
||||
prop: 'asset',
|
||||
label: this.$t('AssetName')
|
||||
},
|
||||
{
|
||||
prop: 'total',
|
||||
label: this.$t('NumberOfVisits'),
|
||||
width: '140px'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
123
src/views/dashboard/Pam/RiskSummary.vue
Normal file
123
src/views/dashboard/Pam/RiskSummary.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="box">
|
||||
<div style="margin-bottom: 12px;">
|
||||
<Title :config="config" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<el-row justify="space-between" type="flex">
|
||||
<el-col v-for="item of summaryItems" :key="item.title" :md="8" :sm="12" :xs="12">
|
||||
<SummaryCard :body="item.body" :title="item.title" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Title from '../components/Title.vue'
|
||||
import SummaryCard from '../components/SummaryCard'
|
||||
|
||||
export default {
|
||||
components: { Title, SummaryCard },
|
||||
data() {
|
||||
return {
|
||||
config: {
|
||||
title: this.$t('风险账号'),
|
||||
tip: this.$t('RealTimeData')
|
||||
},
|
||||
counter: {
|
||||
total_count_online_sessions: '.',
|
||||
total_count_online_users: '.',
|
||||
total_count_today_failed_sessions: '.'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
summaryItems() {
|
||||
return [
|
||||
{
|
||||
title: this.$t('幽灵账号'),
|
||||
body: {
|
||||
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
|
||||
count: 23,
|
||||
disabled: !this.$hasPerm('terminal.view_session')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$t('僵尸账号'),
|
||||
body: {
|
||||
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
|
||||
count: 293,
|
||||
disabled: !this.$hasPerm('terminal.view_session')
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$t('弱密码'),
|
||||
body: {
|
||||
count: 203,
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$t('长时未改密'),
|
||||
body: {
|
||||
count: 1010,
|
||||
disabled: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.counter = await this.getResourcesCount()
|
||||
},
|
||||
methods: {
|
||||
async getResourcesCount() {
|
||||
return this.$axios.get(
|
||||
'/api/v1/index/',
|
||||
{
|
||||
params: {
|
||||
total_count_online_sessions: 1,
|
||||
total_count_online_users: 1,
|
||||
total_count_today_failed_sessions: 1
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box {
|
||||
padding: 20px;
|
||||
background: #FFFFFF;
|
||||
|
||||
.content {
|
||||
.el-col {
|
||||
padding-left: 16px;
|
||||
border-left: 1px solid #EFF0F1;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: #646A73;
|
||||
}
|
||||
|
||||
.num {
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
124
src/views/dashboard/Pam/SummaryChart.vue
Normal file
124
src/views/dashboard/Pam/SummaryChart.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="title">
|
||||
<Title :config="config" />
|
||||
</div>
|
||||
<div class="sub">{{ config.subTitle }}</div>
|
||||
<slot class="custom">
|
||||
<div>
|
||||
<template v-if="config.route">
|
||||
<router-link :to="config.route">
|
||||
<div class="num"> 1000 </div>
|
||||
</router-link>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="num">{{ config.total }}</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="add">
|
||||
<span class="add-num">
|
||||
{{ $tc('WeekAdd') }}:
|
||||
<span :class="{'increase': config.weekAdd > 0}" style="font-size: 14px;">
|
||||
{{ config.weekAdd }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="add-icon">
|
||||
<svg-icon v-if="config.icon" :icon-class="config.icon" class="font" />
|
||||
</span>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="ring" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Title from '../components/Title.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Title
|
||||
},
|
||||
props: {
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background-color: #FFF;
|
||||
|
||||
.card-content {
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #EFF0F1;
|
||||
|
||||
.title,
|
||||
.num {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.num {
|
||||
font-weight: 500;
|
||||
font-size: 32px;
|
||||
line-height: 40px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.sub,
|
||||
.add {
|
||||
color: var(--color-icon-primary);
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.add {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.custom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-weight: 500;
|
||||
font-size: 32px;
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
|
||||
.ring {
|
||||
padding: 26px 0 10px;
|
||||
|
||||
& ::v-deep .echarts {
|
||||
width: 100% !important;
|
||||
height: 278px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.font {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.increase {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
74
src/views/dashboard/Pam/UserAssetActivity.vue
Normal file
74
src/views/dashboard/Pam/UserAssetActivity.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="box">
|
||||
<div class="head">
|
||||
<Title :config="config" />
|
||||
</div>
|
||||
<LineChart v-if="loading" v-bind="lineChartConfig" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Title from '../components/Title.vue'
|
||||
import LineChart from '../components/LineChart.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Title,
|
||||
LineChart
|
||||
},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
config: {
|
||||
title: this.$t('UserAssetActivity'),
|
||||
tip: this.$t('UserAssetActivity')
|
||||
},
|
||||
lineChartConfig: {
|
||||
datesMetrics: [],
|
||||
primaryData: [1],
|
||||
primaryName: this.$t('ActiveUsers'),
|
||||
secondaryData: [1],
|
||||
secondaryName: this.$t('LoginAssets')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
try {
|
||||
this.getMetricData()
|
||||
} finally {
|
||||
this.loading = true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getMetricData() {
|
||||
const url = '/api/v1/index/?dates_metrics=1&days=7'
|
||||
const data = await this.$axios.get(url)
|
||||
const activeUsers = data?.dates_metrics_total_count_active_users
|
||||
const activeAssets = data?.dates_metrics_total_count_active_assets
|
||||
this.lineChartConfig.datesMetrics = data.dates_metrics_date
|
||||
if (activeUsers.length > 0) {
|
||||
this.lineChartConfig.primaryData = activeUsers
|
||||
}
|
||||
if (activeAssets.length > 0) {
|
||||
this.lineChartConfig.secondaryData = activeAssets
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box {
|
||||
margin-top: 16px;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
53
src/views/dashboard/Pam/index.vue
Normal file
53
src/views/dashboard/Pam/index.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<Page>
|
||||
<div v-if="this.$hasPerm('rbac.view_console')">
|
||||
<Announcement />
|
||||
<el-row :gutter="16">
|
||||
<el-col :lg="12" :sm="24">
|
||||
<AccountSummary />
|
||||
<RiskSummary />
|
||||
<UserAssetActivity />
|
||||
</el-col>
|
||||
<el-col :lg="12" :sm="24">
|
||||
<DataSummary />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<AssetProportionSummary />
|
||||
<RankSummary />
|
||||
</div>
|
||||
<Page403 v-else />
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Announcement } from '@/components'
|
||||
import { Page } from '@/layout/components'
|
||||
import Page403 from '@/views/403'
|
||||
import AccountSummary from './AccountSummary.vue'
|
||||
import RiskSummary from './RiskSummary.vue'
|
||||
import UserAssetActivity from './UserAssetActivity.vue'
|
||||
import DataSummary from './DataSummary'
|
||||
import AssetProportionSummary from './AssetProportionSummary'
|
||||
import RankSummary from './RankSummary'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
Page,
|
||||
Announcement,
|
||||
DataSummary,
|
||||
AssetProportionSummary,
|
||||
RiskSummary,
|
||||
RankSummary,
|
||||
AccountSummary,
|
||||
UserAssetActivity,
|
||||
Page403
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -1,42 +1,59 @@
|
||||
<template>
|
||||
<TabPage
|
||||
v-if="!loading"
|
||||
:active-menu.sync="activeMenu"
|
||||
:submenu="tab.submenu"
|
||||
@tab-click="changeMoreCreates"
|
||||
>
|
||||
<keep-alive>
|
||||
<AccountListTable ref="table" v-bind="tableConfig" />
|
||||
</keep-alive>
|
||||
</TabPage>
|
||||
<div>
|
||||
<div>
|
||||
<!-- Todo: 未来移动到通用 table 中 -->
|
||||
<div class="expanded-filter-zone">
|
||||
<div class="item-zone">
|
||||
<h5>过滤</h5>
|
||||
<div>
|
||||
<span class="item">全部账号</span>
|
||||
<span class="item">拥有的</span>
|
||||
<span class="item">收藏夹</span>
|
||||
<span class="item">最近访问</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-zone">
|
||||
<h5>风险账号</h5>
|
||||
<div>
|
||||
<span class="item">过期的密码</span>
|
||||
<span class="item">冲突的密码</span>
|
||||
<span class="item">违法策略</span>
|
||||
<span class="item">禁用的资源</span>
|
||||
<span class="item">回收站</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-zone">
|
||||
<h5>账号类型</h5>
|
||||
<div>
|
||||
<span class="item">全部</span>
|
||||
<span class="item">主机</span>
|
||||
<span class="item">数据库</span>
|
||||
<span class="item">网络设备</span>
|
||||
<span class="item">云服务</span>
|
||||
<span class="item">其他</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="expand-zone">
|
||||
<div class="expand-bar">
|
||||
<i class="fa fa-angle-double-up" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AccountListTable ref="table" v-bind="tableConfig" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AccountListTable from '@/components/Apps/AccountListTable/AccountList.vue'
|
||||
import { TabPage } from '@/layout/components'
|
||||
|
||||
export default {
|
||||
name: 'AssetAccountList',
|
||||
components: {
|
||||
TabPage,
|
||||
AccountListTable
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isInit: true,
|
||||
clickedRow: null,
|
||||
iShowTree: true,
|
||||
loading: true,
|
||||
activeMenu: 'host',
|
||||
tab: {
|
||||
submenu: [
|
||||
{
|
||||
name: 'all',
|
||||
title: this.$t('All'),
|
||||
icon: 'fa-bars'
|
||||
}
|
||||
]
|
||||
},
|
||||
tableConfig: {
|
||||
url: '/api/v1/accounts/accounts/',
|
||||
hasLeftActions: true,
|
||||
@@ -44,45 +61,9 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeMenu(val) {
|
||||
let url = '/api/v1/accounts/accounts/'
|
||||
if (val !== 'all') {
|
||||
url += '?category=' + val
|
||||
}
|
||||
this.tableConfig.url = url
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
try {
|
||||
await this.setCategoriesTab()
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeMoreCreates() {
|
||||
},
|
||||
async setCategoriesTab() {
|
||||
const categoryIcon = {
|
||||
host: 'fa-inbox',
|
||||
device: 'fa-microchip',
|
||||
database: 'fa-database',
|
||||
cloud: 'fa-cloud',
|
||||
web: 'fa-globe',
|
||||
gpt: 'fa-comment',
|
||||
custom: 'fa-cube'
|
||||
}
|
||||
const state = await this.$store.dispatch('assets/getAssetCategories')
|
||||
for (const item of state.assetCategories) {
|
||||
this.tab.submenu.push({
|
||||
name: item.value,
|
||||
title: item.label,
|
||||
icon: categoryIcon[item.value]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -130,4 +111,51 @@ export default {
|
||||
.asset-user-table {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.expand-bar {
|
||||
text-align: center;
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
//transform: rotateY(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.expanded-filter-zone {
|
||||
display: flex;
|
||||
|
||||
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: 10px;
|
||||
border-radius: 5px;
|
||||
background-color: #f5f7fa;
|
||||
color: #303133;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none outside none;
|
||||
margin-block-start: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
24
src/views/pam/Account/AssetList.vue
Normal file
24
src/views/pam/Account/AssetList.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<BaseList :table-config="tableConfig" v-bind="config" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseList from '@/views/assets/Asset/AssetList/components/BaseList'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseList
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
config: {
|
||||
url: '/api/v1/assets/assets/',
|
||||
category: 'all'
|
||||
},
|
||||
tableConfig: {
|
||||
columnsExclude: ['date_verified']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
49
src/views/pam/Account/index.vue
Normal file
49
src/views/pam/Account/index.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<TabPage
|
||||
:active-menu.sync="activeMenu"
|
||||
:submenu="tab.submenu"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { TabPage } from '@/layout/components'
|
||||
|
||||
export default {
|
||||
name: 'AssetAccountList',
|
||||
components: {
|
||||
TabPage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isInit: true,
|
||||
clickedRow: null,
|
||||
iShowTree: true,
|
||||
loading: true,
|
||||
activeMenu: 'host',
|
||||
tab: {
|
||||
submenu: [
|
||||
{
|
||||
name: 'account',
|
||||
title: this.$t('Account'),
|
||||
icon: 'fa-key',
|
||||
component: () => import('@/views/pam/Account/AccountList.vue')
|
||||
},
|
||||
{
|
||||
name: 'asset',
|
||||
title: this.$t('Asset'),
|
||||
icon: 'fa-inbox',
|
||||
component: () => import('@/views/pam/Account/AssetList.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
async mounted() {
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
Reference in New Issue
Block a user