perf: update perm

This commit is contained in:
ibuler
2024-10-11 17:59:00 +08:00
parent 1477712c78
commit a23a0d0197
25 changed files with 1031 additions and 97 deletions

View File

@@ -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 => {

View File

@@ -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 的问题;

View File

@@ -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,

View 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>

View File

@@ -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()" />

View File

@@ -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
}

View File

@@ -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() {

View File

@@ -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',

View File

@@ -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',

View File

@@ -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,

View File

@@ -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`)
}
}

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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;

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>

View 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>