merge: with remote

This commit is contained in:
ibuler
2025-01-07 14:36:47 +08:00
11 changed files with 247 additions and 263 deletions

View File

@@ -161,8 +161,7 @@ export default {
iAsset: this.asset,
account: {},
secretUrl: '',
quickFilters: accountQuickFilters,
tabDeactivated: false,
quickFilters: accountQuickFilters(this),
tableConfig: {
url: this.url,
permissions: {

View File

@@ -112,7 +112,7 @@ export const accountOtherActions = (vm) => [
{
name: 'SecretHistory',
// 密文历史
title: vm.$t('SecretHistory'),
title: vm.$t('HistoryPassword'),
can: () => vm.$hasPerm('accounts.view_accountsecret'),
type: 'primary',
callback: ({ row }) => {
@@ -124,7 +124,7 @@ export const accountOtherActions = (vm) => [
},
{
name: 'CopyToOther',
title: vm.$t('CopyToOther'),
title: vm.$t('CopyToAsset'),
type: 'primary',
divided: true,
callback: ({ row }) => {
@@ -137,7 +137,7 @@ export const accountOtherActions = (vm) => [
},
{
name: 'MoveToOther',
title: vm.$t('MoveToOther'),
title: vm.$t('MoveToAsset'),
type: 'primary',
callback: ({ row }) => {
vm.accountCreateUpdateTitle = vm.$t('MoveToOther')
@@ -149,36 +149,36 @@ export const accountOtherActions = (vm) => [
}
]
export const accountQuickFilters = [
export const accountQuickFilters = (vm) => [
{
label: '最近(7天)',
label: vm.$t('Recent (7 days)'),
options: [
{
label: '最近发现',
label: vm.$t('RecentlyDiscovered'),
filter: {
latest_discovery: '1'
}
},
{
label: '最近被登录',
label: vm.$t('RecentlyLoggedIn'),
filter: {
latest_accessed: '1'
}
},
{
label: '最近修改',
label: vm.$t('RecentlyModified'),
filter: {
latest_updated: '1'
}
},
{
label: '最近改密',
label: vm.$t('RecentlyChangedPassword'),
filter: {
latest_secret_changed: '1'
}
},
{
label: '最近改密失败',
label: vm.$t('RecentPasswordChangeFailed'),
filter: {
latest_secret_changed_failed: '1'
}
@@ -186,40 +186,40 @@ export const accountQuickFilters = [
]
},
{
label: '风险账号',
label: vm.$t('RiskyAccount'),
options: [
{
label: '僵尸账号',
label: vm.$t('LongTimeNoLogin'),
filter: {
risk: 'long_time_no_login'
}
},
{
label: '幽灵账号',
label: vm.$t('UnmanagedAccount'),
filter: {
risk: 'new_found'
}
},
{
label: '弱密码',
label: vm.$t('WeakPassword'),
filter: {
risk: 'weak_password'
}
},
{
label: '空密码',
label: vm.$t('EmptyPassword'),
filter: {
has_secret: 'false'
}
},
{
label: '长时间未改密',
label: vm.$t('LongTimeNoChangeSecret'),
filter: {
long_time_no_change_secret: 'true'
}
},
{
label: '长时间未验证',
label: vm.$t('LongTimeNoVerify'),
filter: {
long_time_no_verify: 'true'
}
@@ -227,34 +227,34 @@ export const accountQuickFilters = [
]
},
{
label: '账号类型',
label: vm.$t('AccountType'),
options: [
{
label: '全部',
label: vm.$t('All'),
filter: {
category: ''
}
},
{
label: ' 主机',
label: vm.$t('Host'),
filter: {
category: 'host'
}
},
{
label: '数据库',
label: vm.$t('Database'),
filter: {
category: 'database'
}
},
{
label: '云',
label: vm.$t('Cloud'),
filter: {
category: 'cloud'
}
},
{
label: '网络设备',
label: vm.$t('Device'),
filter: {
category: 'device'
}
@@ -266,7 +266,7 @@ export const accountQuickFilters = [
}
},
{
label: '其他',
label: vm.$t('Other'),
filter: {
category: 'custom'
}

View File

@@ -0,0 +1,150 @@
<template>
<el-dropdown
size="small"
trigger="hover"
:show-timeout="500"
@command="handleCommand"
@visible-change="visibleChange"
>
<el-button
plain
size="mini"
type="primary"
@click="handlePamConnect"
>
<i :class="IButtonIcon" />
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="Title" disabled>
{{ ITitleText }}
</el-dropdown-item>
<el-dropdown-item divided />
<el-dropdown-item
v-for="protocol in protocols"
:key="protocol.id"
:command="protocol.name"
>
{{ protocol.name }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
import BaseFormatter from './base.vue'
export default {
name: 'AccountConnectFormatter',
extends: BaseFormatter,
props: {
buttonIcon: {
type: String,
default: 'fa fa-desktop'
},
titleText: {
type: String,
default: ''
},
url: {
type: String,
default: ''
},
connectUrlTemplate: {
type: String,
default: ''
}
},
data() {
return {
protocols: []
}
},
computed: {
IButtonIcon() {
return this.buttonIcon
},
ITitleText() {
return this.titleText || this.$t('OptionalProtocol')
}
},
methods: {
handleCommand(protocol) {
if (protocol === 'Title') return
this.$store.commit('table/SET_PROTOCOL_MAP_ITEM', {
key: this.row.id,
value: protocol
})
this.handleWindowOpen(this.row, protocol)
},
visibleChange(visible) {
if (visible) {
this.getProtocols(this.row.asset.id)
}
},
handleWindowOpen(row, protocol) {
const url = this.formatterArgs.connectUrlTemplate
.replace('{id}', row.id)
.replace('{username}', row.username)
.replace('{assetId}', row.asset.id)
.replace('{assetName}', row.asset.name)
.replace('{protocol}', protocol)
window.open(url, '_blank')
},
async handlePamConnect() {
const protocolMap = this.$store.getters.protocolMap
if (protocolMap.has(this.row.id)) {
const protocol = protocolMap.get(this.row.id)
this.handleWindowOpen(this.row, protocol)
} else {
try {
const url = this.formatterArgs.url.replace('{id}', this.row.asset.id)
const res = await this.$axios.get(url)
if (res && res.protocols.length > 0) {
const protocol = res.protocols[0]
this.$store.commit('table/SET_PROTOCOL_MAP_ITEM', {
key: this.row.id,
value: protocol.name
})
this.handleWindowOpen(this.row, protocol.name)
}
} catch (e) {
throw new Error(`Error getting protocols: ${e}`)
}
}
},
async getProtocols(assetId) {
try {
const url = this.formatterArgs.url.replace('{id}', assetId)
const res = await this.$axios.get(url)
if (res) this.protocols = res.protocols
} catch (e) {
throw new Error(`Error getting protocols: ${e}`)
}
}
}
}
</script>
<style scoped lang="scss">
.el-dropdown-menu__item.is-disabled {
font-weight: 500;
color: var(--el-text-color-secondary);
}
::v-deep .el-dropdown-menu__item {
transition: height 0.3s ease-in-out, padding 0.3s ease-in-out;
overflow: hidden;
}
::v-deep .el-dropdown-menu {
transition: min-height 0.3s ease-in-out;
}
</style>

View File

@@ -20,6 +20,7 @@ import SwitchFormatter from './SwitchFormatter.vue'
import AccountInfoFormatter from './AccountInfoFormatter.vue'
import PlatformFormatter from './PlatformFormatter.vue'
import DiscoverConfirmFormatter from './DiscoverConfirmFormatter.vue'
import AccountConnectFormatter from './AccountConnectFormatter.vue'
export default {
DetailFormatter,
@@ -43,7 +44,8 @@ export default {
SwitchFormatter,
PlatformFormatter,
AccountInfoFormatter,
DiscoverConfirmFormatter
DiscoverConfirmFormatter,
AccountConnectFormatter
}
export {
@@ -68,5 +70,6 @@ export {
SwitchFormatter,
PlatformFormatter,
DiscoverConfirmFormatter,
AccountInfoFormatter
AccountInfoFormatter,
AccountConnectFormatter
}

View File

@@ -75,20 +75,20 @@ export default {
},
quickSummary: [
{
title: '最近一周发现',
title: this.$t('DateLastWeek'),
hasCount: true,
filter: {
'days': '7'
}
},
{
title: '最近一月发现',
title: this.$t('DateLastMonth'),
filter: {
'days': '30'
}
},
{
title: '待确认',
title: this.$t('Pending'),
filter: {
status: '0'
}

View File

@@ -22,13 +22,14 @@ export default {
data() {
return {
config: {
title: this.$t('账号汇总'),
title: this.$t('AccountSummary'),
tip: this.$t('RealTimeData')
},
counter: {
total_count_online_sessions: '.',
total_count_online_users: '.',
total_count_today_failed_sessions: '.'
total_privileged_accounts: '',
total_ordinary_accounts: '',
total_unmanaged_accounts: '',
total_unavailable_accounts: ''
}
}
},
@@ -36,32 +37,31 @@ export default {
summaryItems() {
return [
{
title: this.$t('特权账号'),
title: this.$t('Privileged'),
body: {
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
count: 4932,
count: this.counter.total_privileged_accounts,
disabled: !this.$hasPerm('terminal.view_session')
}
},
{
title: this.$t('普通账号'),
title: this.$t('GeneralAccounts'),
body: {
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
count: 2323,
disabled: !this.$hasPerm('terminal.view_session')
count: this.counter.total_ordinary_accounts
}
},
{
title: this.$t('未托管账号'),
title: this.$t('UnmanagedAccount'),
body: {
count: 1233,
count: this.counter.total_unmanaged_accounts,
disabled: true
}
},
{
title: this.$t('不可用账号'),
title: this.$t('UnavailableAccount'),
body: {
count: 123,
count: this.counter.total_unavailable_accounts,
disabled: true
}
}
@@ -74,12 +74,13 @@ export default {
methods: {
async getResourcesCount() {
return this.$axios.get(
'/api/v1/index/',
'/api/v1/accounts/pam-dashboard/',
{
params: {
total_count_online_sessions: 1,
total_count_online_users: 1,
total_count_today_failed_sessions: 1
total_privileged_accounts: 1,
total_ordinary_accounts: 1,
total_unmanaged_accounts: 1,
total_unavailable_accounts: 1
}
}
)

View File

@@ -2,7 +2,7 @@
<div>
<el-row :gutter="16">
<el-col :lg="24" :sm="12">
<SummaryChart :config="userConfig" />
<SummaryChart :config="accountConfig" />
</el-col>
</el-row>
</div>
@@ -10,41 +10,22 @@
<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('账号数据'),
accountConfig: {
title: this.$t('AccountData'),
tip: this.$t('UserData'),
subTitle: this.$t('账号总数'),
subTitle: this.$t('AccountTotal'),
icon: 'users',
subIcon: 'broken-line',
color: '#FFD260',
chartTitle: this.$t('LoginUserToday'),
data: [],
route: { name: 'UserList' },
route: { name: 'PamAccounts' },
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' }
}
}
},
@@ -53,52 +34,22 @@ export default {
},
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 data = await this.$axios.get(
'/api/v1/accounts/pam-dashboard/',
{
params: {
total_accounts: 1,
total_week_add_accounts: 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)
this.accountConfig.total = data.total_accounts
this.accountConfig.weekAdd = data.total_week_add_accounts
}
}
}
</script>
<style scoped>
.left, .right {
display: inline-block;
}
</style>

View File

@@ -22,13 +22,13 @@ export default {
data() {
return {
config: {
title: this.$t('风险账号'),
title: this.$t('RiskyAccount'),
tip: this.$t('RealTimeData')
},
counter: {
total_count_online_sessions: '.',
total_count_online_users: '.',
total_count_today_failed_sessions: '.'
total_long_time_no_login_accounts: '.',
total_weak_password_accounts: '.',
total_long_time_change_password_accounts: '.'
}
}
},
@@ -36,32 +36,23 @@ export default {
summaryItems() {
return [
{
title: this.$t('幽灵账号'),
title: this.$t('LongTimeNoLogin'),
body: {
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
count: 23,
disabled: !this.$hasPerm('terminal.view_session')
count: this.counter.total_long_time_no_login_accounts
}
},
{
title: this.$t('僵尸账号'),
title: this.$t('WeakPassword'),
body: {
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
count: 293,
disabled: !this.$hasPerm('terminal.view_session')
}
},
{
title: this.$t('弱密码'),
body: {
count: 203,
count: this.counter.total_weak_password_accounts,
disabled: true
}
},
{
title: this.$t('长时未改密'),
title: this.$t('LongTimeNoChangeSecret'),
body: {
count: 1010,
count: this.counter.total_long_time_change_password_accounts,
disabled: true
}
}
@@ -74,12 +65,16 @@ export default {
methods: {
async getResourcesCount() {
return this.$axios.get(
'/api/v1/index/',
'/api/v1/accounts/pam-dashboard/',
{
params: {
total_count_online_sessions: 1,
total_count_online_users: 1,
total_count_today_failed_sessions: 1
total_privileged_accounts: 1,
total_ordinary_accounts: 1,
total_unmanaged_accounts: 1,
total_unavailable_accounts: 1,
total_long_time_no_login_accounts: 1,
total_weak_password_accounts: 1,
total_long_time_change_password_accounts: 1
}
}
)

View File

@@ -9,7 +9,7 @@
<div>
<template v-if="config.route">
<router-link :to="config.route">
<div class="num"> 1000 </div>
<div class="num"> {{ config.total }} </div>
</router-link>
</template>
<template v-else>

View File

@@ -3,9 +3,8 @@
</template>
<script>
import { mapGetters } from 'vuex'
import { DetailFormatter } from '@/components/Table/TableFormatters'
import AccountListTable from '@/components/Apps/AccountListTable/AccountList.vue'
import { DetailFormatter, AccountConnectFormatter } from '@/components/Table/TableFormatters'
export default {
name: 'AssetAccountList',
@@ -14,9 +13,6 @@ export default {
},
data() {
return {
drawerTitle: '',
currentProtocol: '',
perm_protocols: [],
tableConfig: {
url: '/api/v1/accounts/accounts/',
hasLeftActions: true,
@@ -38,127 +34,21 @@ export default {
connect: {
label: this.$t('Connect'),
width: '80px',
formatter: row => {
return (
<span class='connect'>
<el-dropdown
{...{
props: {
trigger: 'hover',
size: 'small',
showTimeout: 500
},
on: {
'visible-change': visible => {
if (visible) {
this.getPermdProtocols(row.asset.id)
}
},
'command': protocol => {
this.$store.commit('table/SET_PROTOCOL_MAP_ITEM', {
key: row.id,
value: protocol
})
this.handleWindowOpen(row, protocol)
}
}
}}
>
<el-button
plain
size='mini'
type='primary'
onClick={() => this.handlePamConnect(row)}
>
<i class='fa fa-desktop'/>
</el-button>
<el-dropdown-menu slot='dropdown'>
<el-dropdown-item command='Title' disabled>
可选协议
</el-dropdown-item>
<el-dropdown-item divided/>
{this.perm_protocols.map(protocol => {
return (
<el-dropdown-item command={protocol.name}>
{protocol.name}
</el-dropdown-item>
)
})}
</el-dropdown-menu>
</el-dropdown>
</span>
)
formatter: AccountConnectFormatter,
formatterArgs: {
buttonIcon: 'fa fa-desktop',
titleText: '可选协议',
url: '/api/v1/assets/assets/{id}',
connectUrlTemplate: '/luna/pam_connect/{id}/{username}/{assetId}/{assetName}/{protocol}'
}
}
}
}
}
},
computed: {
...mapGetters(['protocolMap'])
},
async mounted() {
mounted() {
},
methods: {
getAssetDetail(id) {
const detailUrl = `/api/v1/assets/assets/${id}`
return new Promise((resolve, reject) => {
this.$axios
.get(detailUrl)
.then(res => {
resolve(res)
})
.catch(err => {
reject(err)
})
})
},
handleWindowOpen(row, protocol) {
window.open(
`/luna/pam_connect/${row.id}/${row.username}/${row.asset.id}/${
row.asset.name
}/${protocol}`,
'_blank'
)
},
async handlePamConnect(row) {
const protocolMap = this.protocolMap
if (protocolMap.has(row.id)) {
const protocol = protocolMap.get(row.id)
this.handleWindowOpen(row, protocol)
} else {
try {
const res = await this.getAssetDetail(row.asset.id)
if (res) {
const protocol = res.protocols[0]
this.$store.commit('table/SET_PROTOCOL_MAP_ITEM', {
key: row.id,
value: protocol.name
})
this.handleWindowOpen(row, protocol.name)
}
} catch (e) {
console.log(e)
}
}
},
async getPermdProtocols(assetId) {
try {
const res = await this.getAssetDetail(assetId)
if (res) {
this.perm_protocols = res.protocols
}
} catch (e) {
console.log(e)
}
}
}
}
</script>
@@ -208,9 +98,4 @@ export default {
.asset-user-table {
padding-left: 20px;
}
.el-dropdown-menu__item.is-disabled {
font-weight: 500;
color: var(--el-text-color-secondary);
}
</style>

View File

@@ -60,19 +60,19 @@ export default {
},
quickSummary: [
{
title: '最近一周发现',
title: this.$t('DateLastWeek'),
filter: {
'days': '7'
}
},
{
title: '最近一月发现',
title: this.$t('DateLastMonth'),
filter: {
'days': '30'
}
},
{
title: '待处理',
title: this.$t('Pending'),
filter: {
status: '0'
}