perf: Change secret dashboard (#4473)

* perf: Change secret dashboard

* style: Modify layout

---------

Co-authored-by: feng <1304903146@qq.com>
Co-authored-by: zhaojisen <1301338853@qq.com>
This commit is contained in:
fit2bot
2024-12-02 10:33:46 +08:00
committed by GitHub
parent 7a6c156aaa
commit 2ee139c92b
9 changed files with 789 additions and 14 deletions

View File

@@ -1,13 +0,0 @@
<script>
export default {
name: 'AccountChangeDashboard'
}
</script>
<template>
<h1>账号改密汇总</h1>
</template>
<style scoped>
</style>

View File

@@ -18,7 +18,7 @@ export default {
{
title: this.$t('Overview'),
name: 'AccountChangeDashboard',
component: () => import('@/views/accounts/AccountChangeSecret/AccountChangeDashboard.vue')
component: () => import('@/views/dashboard/ChangeSecret')
},
{
title: this.$t('AccountChangeSecret'),

View File

@@ -0,0 +1,84 @@
<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 './LineChart.vue'
export default {
components: {
Title,
LineChart
},
props: {
days: {
type: [Number, String],
default: '7'
}
},
data() {
return {
loading: false,
config: {
title: '账号成功/失败情况',
tip: '账号成功/失败情况'
},
lineChartConfig: {
datesMetrics: [],
primaryData: [1],
primaryName: '成功',
secondaryData: [1],
secondaryName: '失败'
}
}
},
watch: {
days() {
this.getMetricData()
}
},
mounted() {
try {
this.getMetricData()
} finally {
this.loading = true
}
},
methods: {
async getMetricData() {
const url = `/api/v1/accounts/change-secret-dashboard/?daily_success_and_failure_metrics=1&days=${this.days}`
const data = await this.$axios.get(url)
const success = data?.dates_metrics_total_count_success
const failed = data?.dates_metrics_total_count_failed
this.lineChartConfig.datesMetrics = data?.dates_metrics_date
if (success.length > 0) {
this.lineChartConfig.primaryData = success
}
if (failed.length > 0) {
this.lineChartConfig.secondaryData = failed
}
}
}
}
</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,122 @@
<template>
<div>
<el-row :gutter="16">
<el-col :lg="12" :sm="24" class="margin-top-10">
<SummaryCountCard :config="logConfig" :items="LogItems" />
</el-col>
<el-col :lg="12" :sm="24" class="margin-top-10">
<SummaryCountCard :config="sessionConfig" :items="sessionItems" />
</el-col>
</el-row>
</div>
</template>
<script>
import SummaryCountCard from '../components/SummaryCountCard.vue'
export default {
components: { SummaryCountCard },
props: {
days: {
type: [Number, String],
default: '7'
}
},
data() {
return {
logConfig: {
title: '当前状态',
tip: '当前状态'
},
sessionConfig: {
title: '改密任务执行状态',
tip: '改密任务执行状态'
},
data: {
total_count_change_secrets: 0,
total_count_periodic_change_secrets: 0,
total_count_change_secret_assets: 0,
total_count_change_secret_executions: 0,
total_count_success_change_secret_executions: 0,
total_count_failed_change_secret_executions: 0
}
}
},
computed: {
LogItems() {
return [
{
title: '任务数',
body: {
route: { name: `LoginLogList` },
count: this.data.total_count_change_secrets
}
},
{
title: '定时任务数',
body: {
route: { name: `LoginLogList` },
count: this.data.total_count_periodic_change_secrets
}
},
{
title: '资产数',
body: {
route: { name: `OperateLogList` },
count: this.data.total_count_change_secret_assets
}
}
]
},
sessionItems() {
return [
{
title: '任务执行数',
body: {
route: { name: `SessionList`, params: { activeMenu: 'OnlineList' }},
count: this.data.total_count_change_secret_executions
}
},
{
title: '成功数',
body: {
route: { name: `SessionList`, params: { activeMenu: 'OfflineList' }},
count: this.data.total_count_success_change_secret_executions
}
},
{
title: '失败数',
body: {
route: { name: `FtpLog` },
count: this.data.total_count_failed_change_secret_executions
}
}
]
}
},
watch: {
days() {
this.getData()
}
},
mounted() {
this.getData()
},
methods: {
async getData() {
this.data = await this.$axios.get(`/api/v1/accounts/change-secret-dashboard/?days=${this.days}
&total_count_change_secrets=1
&total_count_periodic_change_secrets=1
&total_count_change_secret_assets=1
&total_count_change_secret_status=1
`)
}
}
}
</script>
<style scoped>
.margin-top-10 {
margin-top: 10px;
}
</style>

View File

@@ -0,0 +1,92 @@
<template>
<div>
<el-row :gutter="16">
<el-col :lg="24" :sm="12" class="margin-top-10">
<SummaryCountCard :config="logConfig" :items="LogItems" />
</el-col>
</el-row>
</div>
</template>
<script>
import SummaryCountCard from '../components/SummaryCountCard.vue'
export default {
components: { SummaryCountCard },
props: {
days: {
type: [Number, String],
default: '7'
}
},
data() {
return {
logConfig: {
title: '当前正在改密情况',
tip: '当前正在改密情况'
},
data: {
total_count_ongoing_change_secret: 0,
total_count_ongoing_change_secret_assets: 0,
total_count_ongoing_change_secret_accounts: 0,
total_count_online_sessions: 0,
total_count_history_sessions: 0,
total_count_ftp_logs: 0
}
}
},
computed: {
LogItems() {
return [
{
title: '任务执行数',
body: {
route: { name: `LoginLogList` },
count: this.data.total_count_ongoing_change_secret
}
},
{
title: '资产数',
body: {
route: { name: `LoginLogList` },
count: this.data.total_count_ongoing_change_secret_assets
}
},
{
title: '账号数',
body: {
route: { name: `OperateLogList` },
count: this.data.total_count_ongoing_change_secret_accounts
}
}
]
}
},
watch: {
days() {
this.getData()
}
},
mounted() {
this.getData()
},
methods: {
async getData() {
this.data = await this.$axios.get(`/api/v1/index/?days=${this.days}
&total_count_user_login_logs=1
&total_count_operate_logs=1
&total_count_change_password_logs=1
&total_count_online_sessions=1
&total_count_history_sessions=1
&total_count_ftp_logs=1
`)
}
}
}
</script>
<style scoped>
.margin-top-10 {
margin-top: 10px;
}
</style>

View File

@@ -0,0 +1,90 @@
<template>
<HomeCard :table-config="tableConfig" v-bind="cardConfig" />
</template>
<script>
import HomeCard from '@/views/workbench/myhome/components/HomeCard.vue'
export default {
components: {
HomeCard
},
data() {
const vm = this
return {
cardConfig: {
title: '改密失败账号'
},
tableConfig: {
url: '/api/v1/terminal/my-sessions/?limit=5',
columns: [
'id', 'asset', 'account', 'remote_addr', 'protocol'
],
columnsMeta: {
id: {
prop: 'id',
align: 'center',
formatter: function(row, column, cellValue, index) {
const label = index + 1
const route = { to: { name: 'SessionDetail', params: { id: row.id }}}
if (vm.$hasPerm('terminal.view_session')) {
return <router-link {...{ attrs: route }} >{label}</router-link>
} else {
return label
}
}
},
asset: {
'min-width': 200,
label: this.$t('Asset')
},
account: {
'min-width': 100
},
command_amount: {
align: 'center',
label: this.$t('Command')
},
remote_addr: {
width: 180,
label: this.$t('RemoteAddr')
},
protocol: {
width: 100,
label: this.$t('Protocol'),
el: {
disabled: false
},
sortable: false
},
actions: {
align: 'center',
formatterArgs: {
hasDelete: false,
hasClone: false,
hasUpdate: false,
extraActions: [
{
name: 'connect',
icon: 'fa-desktop',
plain: true,
type: 'primary',
can: ({ row }) => row.is_active,
callback: ({ row }) => {
window.open(`/luna/?login_to=${row.asset_id}&login_account=${row.account_id}`, '_blank')
}
}
]
}
}
},
hasSelection: false,
paginationSize: 10
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,260 @@
<template>
<div>
<echarts
ref="echarts"
:options="options"
:autoresize="true"
theme="light"
class="disabled-when-print"
@finished="getDataUrl"
/>
<img v-if="dataUrl" :src="dataUrl" class="enabled-when-print" style="display: none;width: 100%;">
</div>
</template>
<script>
// eslint-disable-next-line no-unused-vars
import * as echarts from 'echarts'
import { mix } from '@/utils/theme/color'
export default {
name: 'LoginMetric',
props: {
range: {
type: String,
default: 'weekly'
},
datesMetrics: {
type: Array,
default: () => []
},
primaryName: {
type: String,
default: ''
},
primaryData: {
type: Array,
default: () => []
},
secondaryName: {
type: String,
default: ''
},
secondaryData: {
type: Array,
default: () => []
}
},
data: function() {
return {
dataUrl: '',
metricsData: {
dates_metrics_date: [],
dates_metrics_total_count_failed: [],
dates_metrics_total_count_success: []
}
}
},
computed: {
mixColors() {
const documentStyle = document.documentElement.style
const primary = documentStyle.getPropertyValue('--color-primary')
const colorValue = primary.replace(/#/g, '')
const TwoLevelColor = mix(colorValue, 'ffffff', 38)
const ThreeLevelColor = mix(colorValue, 'ffffff', 20)
const shadowColor = mix(colorValue, 'ffffff', 1)
return {
primary,
TwoLevelColor,
ThreeLevelColor,
shadowColor
}
},
options() {
const { primary, TwoLevelColor, ThreeLevelColor, shadowColor } = this.mixColors
return {
title: {
show: false
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
left: 'auto',
icon: 'rect',
// 图例标记的图形宽度
itemWidth: 10,
itemHeight: 10
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
color: [primary, '#F3B44B'],
xAxis: [
{
type: 'category',
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#8F959E'
}
},
axisLabel: {
textStyle: {
// 坐标轴颜色
color: '#8F959E'
}
},
axisTick: {
show: false
},
data: this.datesMetrics
}
],
yAxis: [
{
type: 'value',
name: '',
axisLine: {
show: false,
lineStyle: {
color: '#fff'
}
},
axisLabel: {
textStyle: {
color: '#8F959E'
}
},
axisTick: {
show: false
},
// 坐标轴线样式
splitLine: {
show: true,
lineStyle: {
color: '#EFF0F1'
}
}
}
],
animationDuration: 500,
series: [
{
name: this.primaryName,
type: 'line',
smooth: true,
areaStyle: {
// 区域填充样式
normal: {
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[{
offset: 0,
color: primary
}, {
offset: 0.6,
color: TwoLevelColor
},
{
offset: 0.8,
color: ThreeLevelColor
}
],
false
),
shadowColor: shadowColor,
shadowBlur: 5
}
},
data: this.primaryData
},
{
name: this.secondaryName,
type: 'line',
smooth: true,
areaStyle: {
// 区域填充样式
normal: {
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[{
offset: 0,
color: 'rgba(249, 199, 79, 0.6)'
}, {
offset: 0.6,
color: 'rgba(249, 199, 79, 0.2)'
},
{
offset: 0.8,
color: 'rgba(249, 199, 79, 0.1)'
}
],
false
),
shadowColor: 'rgba(249, 199, 79, 0.1)',
shadowBlur: 6
}
},
data: this.secondaryData
}
]
}
}
},
watch: {
range() {
this.getMetricData()
}
},
mounted() {
this.getMetricData()
},
methods: {
getDataUrl() {
const instance = this.$refs.echarts.echartsInstance
if (instance) {
this.dataUrl = instance.getDataURL()
}
},
getMetricData() {
this.getDataUrl()
}
}
}
</script>
<style lang="scss" scoped>
.echarts {
width: 100%;
height: 272px;
}
@media print {
.disabled-when-print {
display: none;
}
.enabled-when-print {
display: inherit !important;
}
.print-margin {
margin-top: 10px;
}
}
</style>

View File

@@ -0,0 +1,60 @@
<template>
<div class="box">
<Title :config="config" style="margin-bottom: 16px;" />
<ColumnChart v-bind="columnChartConfig" />
</div>
</template>
<script>
import Title from '../components/Title.vue'
import ColumnChart from '../components/ColumnChart'
export default {
components: { Title, ColumnChart },
props: {
days: {
type: [Number, String],
default: '7'
}
},
data() {
return {
config: {
title: this.$t('SessionConnectTrend'),
tip: this.$t('SessionConnectTrend')
},
columnChartConfig: {
datesMetrics: [],
primaryData: [0],
primaryName: this.$t('LoginUserToday')
}
}
},
watch: {
days() {
this.getData()
}
},
mounted() {
this.getData()
},
methods: {
async getData() {
const data = await this.$axios.get(`/api/v1/index/?dates_metrics=1&days=${this.days}`)
const loginTotal = data?.dates_metrics_total_count_login
this.columnChartConfig.datesMetrics = data.dates_metrics_date
if (loginTotal.length > 0) {
this.columnChartConfig.primaryData = loginTotal
}
}
}
}
</script>
<style lang="scss" scoped>
.box {
margin-top: 16px;
padding: 20px;
background-color: #fff;
}
</style>

View File

@@ -0,0 +1,80 @@
<template>
<Page>
<div v-if="this.$hasPerm('accounts.view_changesecretautomation')">
<div>
<SwitchDate class="switch-date" @change="onChange" />
</div>
<el-row type="flex">
<el-col :span="18">
<CardSummary class="card-summary" :days="days" />
</el-col>
<el-col :span="6">
<DataSummary class="data-summary" :days="days" style="margin-left: 1rem" />
</el-col>
</el-row>
<el-row type="flex" :gutter="20">
<el-col :span="12" style="margin-top: 2rem">
<FailedAccountSummary />
</el-col>
<el-col :span="12" style="margin-top: 2rem">
<FailedAccountSummary />
</el-col>
</el-row>
<el-row type="flex" style="flex-direction: column">
<el-col :span="24">
<AccountSummary class="account-summary" :days="days" />
</el-col>
</el-row>
</div>
<Page403 v-else />
</Page>
</template>
<script>
import { Page } from '@/layout/components'
import SwitchDate from '../components/SwitchDate'
import FailedAccountSummary from './FailedAccountSummary.vue'
import DataSummary from './DataSummary.vue'
import CardSummary from './CardSummary.vue'
import AccountSummary from './AccountSummary.vue'
import Page403 from '@/views/403'
export default {
components: {
AccountSummary,
Page,
SwitchDate,
DataSummary,
FailedAccountSummary,
CardSummary,
Page403
},
data() {
return {
days: localStorage.getItem('dashboardDays') || '7'
}
},
methods: {
onChange(val) {
this.days = val
localStorage.setItem('dashboardDays', val)
}
}
}
</script>
<style lang="scss" scoped>
.page ::v-deep .page-heading {
display: none;
}
</style>