CN-268 feat: panel重构

This commit is contained in:
chenjinsong
2022-01-16 23:16:00 +08:00
parent 53cba023e7
commit d86d18a2ee
68 changed files with 3852 additions and 185 deletions

View File

@@ -15,8 +15,8 @@ module.exports = {
eqeqeq: 0, // 关闭必须使用全等
'no-extend-native': 0,
'vue/no-parsing-error': 0, // 关闭此项避免在{{}}中使用>、<号导致报错的问题
'vue/no-use-v-if-with-v-for': 0, // vue2暂时关闭v-if和v-for写在一起的错误提示到vue3后要遵守
'no-useless-escape': 0,
'no-eval': 0
'no-eval': 0,
'no-trailing-spaces': 0
}
}

View File

@@ -51,8 +51,8 @@ https://www.lodashjs.com/
多个单词时,应该以高阶的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾
eg`SearchButtonClear.vue` 反例:`ClearSearchButton.vue`
- **文件夹**
使用小写,单词间使用连字符`-`连接
eg`right-box`
使用小写,单词间使用连字符`-`连接,或使用驼峰格式
eg`right-box``rightBox`
- **VUE组件文件**
@@ -99,4 +99,4 @@ eg
`el.style.border = 'xxx'`
- **布局**
避免使用float视情况使用position建议使用flex和grid
避免使用float视情况使用position建议使用flex和grid

View File

@@ -1,3 +1,5 @@
/* 不含主题变量的通用css */
[v-cloak] {
display: none !important;
}
@@ -14,3 +16,8 @@ body {
cursor: default !important;
overflow: hidden;
}
.temp-dom {
visibility: hidden;
font-size: 14px;
position: fixed;
}

View File

@@ -1,2 +1,12 @@
@import './rightBoxCommon';
@import './tableCommon';
@import 'right-box-common';
@import 'table-common';
/* 通用css */
.panel-chart__no-data {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
color: #999;
}

View File

@@ -728,6 +728,7 @@
.header__operation-btn {
margin-left: 12px;
cursor: pointer;
color: #999;
}
.ip-detail-as {
color: #999;

View File

@@ -1,20 +1,22 @@
@import './components/advancedSearch/advancedSearch';
@import 'components/advancedSearch/advanced-search';
@import './components/charts/panel';
@import './components/common/TimeRange/dateTimeRange';
@import './components/common/TimeRange/timeRefresh';
@import 'components/common/TimeRange/date-time-range';
@import 'components/common/TimeRange/time-refresh';
@import './components/common/pagination';
// @import './components/entities/entities';
@import './components/layout/layout';
@import './components/rightBox/settings/chartBox';
@import './components/setting/galaxyProxyDebug';
@import './components/table/settings/galaxyProxyTable';
@import 'components/rightBox/settings/chart-box';
@import 'components/setting/galaxy-proxy-debug';
@import 'components/table/settings/galaxy-proxy-table';
@import './components/table/common';
@import './views/charts/chart';
@import './views/entityExplorer/entityExplorer';
@import './views/entityExplorer/search/explorerSearch';
@import './views/entityExplorer/entityFilter';
@import './views/entityExplorer/entityDetail';
@import './views/entityExplorer/entityList/entityList';
@import 'views/entityExplorer/entity-explorer';
@import 'views/entityExplorer/search/explorer-search';
@import 'views/entityExplorer/entity-filter';
@import 'views/entityExplorer/entity-detail';
@import 'views/entityExplorer/entityList/entity-list';
@import './views/entityExplorer/entityList/card';
@import './views/entityExplorer/entityList/row';
@import './views/entityExplorer/entityList/detailOverview';
@import 'views/entityExplorer/entityList/detail-overview';
@import './views/charts/panel';
//@import '../chart';

View File

@@ -375,12 +375,11 @@
}
.chart__loading {
position: absolute;
height: calc(100% - 47px);
top: 47px;
height: 100%;
width: 100%;
background-color: #fefefe;
z-index: 1;
opacity: .9;
opacity: .8;
i {
position: absolute;
@@ -390,3 +389,12 @@
color: #aaa;
}
}
.map-back {
position: absolute;
right: 12px;
top: 15px;
color: $--color-primary;
cursor: pointer;
z-index: 2;
}

View File

@@ -0,0 +1,636 @@
.cn-panel2 {
position: relative;
.panel__time {
position: absolute;
right: 10px;
top: 10px;
z-index: 1;
display: flex;
&>div {
margin-left: 10px;
}
}
.chart-list {
&>.vue-grid-layout>.vue-grid-item {
&>.panel-chart {
border: 1px solid $--chart-box-border-color;
background-color: #FFFFFF;
box-shadow: 0 2px 4px 0 rgba(51,51,51,0.02);
height: 100%;
display: flex;
position: relative;
flex-direction: column;
&.panel-chart--title-chart {
border: none;
background-color: transparent;
box-shadow: none;
}
.chart-header {
display: flex;
justify-content:space-between;
align-items:center;
padding: 10px 20px 10px 18px;
height: 47px;
font-size: 16px;
color: $--color-text-primary;
transition: all 0.2s;
&.chart-header--float {
position: absolute;
width: 100%;
z-index: 100;
box-sizing: border-box;
height: 10px;
opacity: 0;
transition: all linear .2s;
&:hover {
height: 47px;
opacity: 1;
}
}
&.chart-header--title-chart {
font-size: 20px;
color: #333;
padding: 0;
}
/*&:hover {
background-color: $--chart-title-hover-background-color;
.chart-header__tools {
.chart-header__tool .tool__icon {
visibility: visible;
}
}
}*/
.chart-header__title {
max-width: calc(100% - 100px);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.chart-header__tools {
display: flex;
justify-content: space-between;
align-items: center;
.chart-header__tool {
margin-left: 20px;
cursor: pointer;
.tool__icon {
// visibility: hidden;
font-size: 14px;
color: $--color-text-primary;
}
.nz-chart-dropdown {
position: absolute;
top: 44px;
right: 0;
left: unset;
transform-origin: center top;
z-index: 1000;
width: 180px;
li {
padding-left: 15px !important;
padding-right: 0 !important;
width: calc(100% - 15px);
text-align: left;
i {
margin-right: 10px;
}
&:hover i {
color: $--color-primary;
}
}
}
}
}
.chart-header-error{
position: absolute;
left: 0;
top: -1px;
}
}
.chart-screen-header.list-page{
background: $--background-color-empty;
}
.chart-screen-header {
display: flex;
justify-content:space-between;
align-items:center;
padding: 0 20px 0 20px;
height: 39px;
font-size: 14px;
line-height: 40px;
color: $--color-text-primary;
transition: all 0.2s;
width: 100%;
box-sizing: border-box;
margin-top: 15px;
&.chart-header--float {
position: absolute;
width: 100%;
z-index: 100;
box-sizing: border-box;
height: 10px;
opacity: 0;
transition: all linear .2s;
&:hover {
height: 39px;
opacity: 1;
}
}
.chart-header__title {
max-width: calc(100% - 100px);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
font-size: 18px;
color: #333;
font-weight: 700;
}
.chart-header__tools {
display: flex;
justify-content: space-between;
align-items: center;
.chart-header__tool {
margin-left: 20px;
cursor: pointer;
.tool__icon {
visibility: hidden;
font-size: 14px;
color: $--color-text-primary;
}
.nz-chart-dropdown {
position: absolute;
top: 44px;
right: 0;
left: unset;
transform-origin: center top;
z-index: 1000;
width: 180px;
li {
padding-left: 15px !important;
padding-right: 0 !important;
width: calc(100% - 15px);
text-align: left;
i {
margin-right: 10px;
}
&:hover i {
color: $--color-primary;
}
}
}
}
}
.chart-header-error{
position: absolute;
left: 0;
top: -1px;
}
}
&>.cn-chart {
position: relative;
border-radius: 2px;
height: 100%;
width: 100%;
.chart-drawing {
height: 100%;
width: 100%;
}
}
&>.cn-chart__whois>.cn-chart__body {
overflow: auto;
}
&>.cn-chart__echarts, &>.cn-chart__table, &>.cn-chart__map, &>.cn-chart__group, &>.cn-chart__block, &>.cn-chart__whois, &>.cn-chart__dns-record, &>.cn-chart__app-basic {
display: flex;
flex-direction: column;
.cn-chart__header {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
padding: 10px 20px 10px 18px;
height: 47px;
.cn-chart__title {
font-size: 16px;
color: #333333;
font-weight: bold;
}
.header__operations {
color: #999;
}
}
.cn-chart__body {
flex: auto;
display: flex;
.el-descriptions {
padding-top: 30px;
}
&>.el-descriptions {
flex: 0 0 350px;
padding: 30px 36px;
}
.chart-location {
display: flex;
flex: 1;
flex-direction: column;
padding: 0 20px 20px 0;
}
.el-descriptions__content {
color: #3976CB;
}
}
}
&>.cn-chart__block {
&>.cn-chart__header {
height: 60px;
border-bottom: none !important;
}
&>.cn-chart__body {
display: grid !important;
grid-template-columns: repeat(30, 1fr);
grid-auto-flow: row;
grid-gap: 10px;
padding: 0 20px;
&>.cn-chart {
border: 1px solid #E7EAED;
}
/* detail页面block下的五连图的标题样式改变 */
.cn-chart__group .cn-chart__echarts {
.cn-chart__header {
border-bottom: none !important;
.header__title {
font-size: 14px !important;
color: #3976CB !important;
}
}
}
}
}
.cn-chart__group {
.cn-chart__header {
border-bottom: 1px solid $--content-right-background-color;
}
&>.cn-chart__body {
display: grid !important;
grid-gap: 10px;
padding: 0 20px;
.cn-chart {
border: none;
box-shadow: none;
}
}
}
&>.cn-chart__title {
display: flex;
align-items: center;
font-size: 20px;
padding-left: 10px;
color: #333;
background-color: transparent;
box-shadow: none;
border: none;
}
&>.cn-chart__tabs {
padding: 10px 25px 10px 15px;
.el-tabs__nav-wrap::after {
height: 1px;
}
&>.el-tabs__header {
margin-bottom: 10px;
}
&>.el-tabs__content {
height: calc(100% - 40px);
}
}
&>.cn-chart__table {
.cn-chart__header {
border-bottom: 1px solid $--content-right-background-color;
.header__operations {
display: flex;
justify-content: end;
align-items: center;
.header__operation.header__operation--table {
display: flex;
align-items: center;
height: 22px;
margin-left: 10px;
color: $--color-primary;
border: 1px solid $--color-primary;
border-radius: $--border-radius-primary;
.option__button {
display: flex;
align-items: center;
height: 100%;
padding: 0 5px;
cursor: pointer;
background-color: white;
transition: all linear .2s;
}
.option__button:hover {
background-color: #EFF2F5;
}
.option__button.icon-group-item:first-of-type:not(:last-of-type) {
padding: 0 5px 0 0;
}
.option__button.icon-group-item:last-of-type:not(:first-of-type) {
padding: 0 0 0 5px;
}
.option__select {
.el-input__inner {
width: 80px;
padding-right: 20px;
border: none;
height: 100%;
line-height: 20px;
color: $--color-primary;
}
.el-input__prefix > div {
font-weight: normal;
line-height: 19px;
color: $--color-primary;
}
.el-input__suffix {
display: flex;
.el-input__suffix-inner {
line-height: 14px;
.el-select__caret {
line-height: 14px;
width: 16px;
color: $--color-primary;
}
}
}
}
.option__select.select-column {
.el-input__inner {
width: 86px;
padding-left: 8px;
}
}
.icon-group-divide {
height: 14px;
width: 1px;
background-color: $--color-primary;
}
i {
font-size: 12px;
}
}
}
}
.cn-chart__body {
flex: auto;
overflow-y: auto;
.el-table {
padding: 0 10px;
&:before {
height: 0;
}
thead {
color: #333;
}
th.is-leaf, td {
border-bottom: none;
}
th {
padding-bottom: 5px;
}
td {
padding: 4px 0;
color: #333;
}
}
}
}
&>.cn-chart__echarts {
.cn-chart__header {
border-bottom: 1px solid $--content-right-background-color;
.header__operations {
display: flex;
justify-content: end;
align-items: center;
.header__operation.header__operation--echarts {
display: flex;
align-items: center;
height: 22px;
margin-left: 10px;
color: $--color-primary;
border: 1px solid $--color-primary;
border-radius: $--border-radius-primary;
.option__button {
display: flex;
align-items: center;
height: 100%;
padding: 0 5px;
cursor: pointer;
background-color: white;
transition: all linear .2s;
}
.option__button:hover {
background-color: #EFF2F5;
}
.option__button.icon-group-item:first-of-type:not(:last-of-type) {
padding: 0 5px 0 0;
}
.option__button.icon-group-item:last-of-type:not(:first-of-type) {
padding: 0 0 0 5px;
}
.option__select {
.el-input__inner {
width: 120px;
padding-right: 20px;
border: none;
height: 100%;
line-height: 20px;
color: $--color-primary;
}
.el-input__prefix > div {
font-weight: normal;
line-height: 19px;
color: $--color-primary;
}
.el-input__suffix {
display: flex;
.el-input__suffix-inner {
line-height: 14px;
.el-select__caret {
line-height: 14px;
width: 16px;
color: $--color-primary;
}
}
}
}
.option__select.select-column {
.el-input__inner {
width: 86px;
padding-left: 8px;
}
}
.icon-group-divide {
height: 14px;
width: 1px;
background-color: $--color-primary;
}
i {
font-size: 12px;
}
}
}
}
.cn-chart__body {
overflow: hidden auto;
.el-table {
padding: 0 10px;
&:before {
height: 0;
}
thead {
color: #333;
}
th.is-leaf, td {
border-bottom: none;
}
th {
padding-bottom: 5px;
}
td {
padding: 4px 0;
color: #333;
}
}
}
.cn-chart__body.pie-with-table {
flex-basis: 40%;
}
.cn-chart__footer.pie-with-table {
flex-basis: 60%;
padding: 10px 30px 30px;
}
}
.pie-table {
font-size: 14px;
color: #333333;
font-weight: 500;
.el-table__header-wrapper {
.cell {
color: #333;
}
}
.el-table__expanded-cell[class*=cell] {
padding: 0;
}
.expand-table .el-table__body .el-table__row:last-of-type td {
border: none;
}
.expand-table {
font-weight: 400;
color: #606266;
.el-table__body-wrapper {
height: auto !important;
}
}
}
.chart__legend {
width: calc(100% - 40px);
border: 1px solid #E7EAED;
color: #5f6368;
margin: auto;
margin-bottom: 15px;
.chart__table-top {
width: 100%;
height: 30px;
border-bottom: #E7EAED 1px solid;
display: flex;
div {
font-size: 13px;
line-height: 28px;
color: $--color-primary;
}
}
.chart__table-below {
height: 240px;
width: 100%;
font-size: 13px;
}
.table-below-box {
width: 100%;
display: flex;
align-items: center;
line-height: 24px;
}
.table-below-box:hover {
background-color: #f9f9f9;
border: 0;
color: #383838;
}
.table__below-color {
width: 27px;
height: 7px;
flex-shrink: 0;
padding-left: 10px;
div {
height: 100%;
width: 100%;
border-radius: 24%;
}
}
.table__below-title {
padding: 0 6px;
flex-shrink: 1;
flex-grow: 1;
overflow: hidden;
min-width: 200px;
text-overflow: ellipsis;
white-space: nowrap;
}
.table__below-statistics {
width: 80px;
flex-shrink: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.table-below-box:not(.chart__table-top) {
cursor: pointer;
}
.table-below-box.table-below-box--inactivated {
color: #ccc;
.table__below-color div {
background-color: #ccc !important;
}
}
}
}
}
}
}

View File

@@ -9,3 +9,5 @@
### 源码改动
- theme-chalk/src/common/var.scss 首行增加$--theme属性
- theme-chalk/src/mixins/mixins.scss 改动较多,如需了解请比对文件差异
- // $arrow-selector: #{& + '__arrow'};
改为 $arrow-selector: '.el-popper__arrow';

View File

@@ -140,7 +140,7 @@
}
@at-root {
#{renderThemeClass($selector)}##{&}#{$specSelector}.#{$block+$element-separator+$element+$modifierCombo} {
#{renderThemeClass($selector)}#{&}#{$specSelector}.#{$block+$element-separator+$element+$modifierCombo} {
@content;
}
}
@@ -164,9 +164,8 @@
}
@mixin when($state) {
$selector: &;
@at-root {
#{renderThemeClass($selector)}&.#{$state-prefix + $state} {
&.#{$state-prefix + $state} {
@content;
}
}

View File

@@ -12,7 +12,8 @@
word-wrap: break-word;
visibility: visible;
$arrow-selector: #{& + '__arrow'};
// $arrow-selector: #{& + '__arrow'};
$arrow-selector: '.el-popper__arrow';
@include when(dark) {
color: $--color-white;

View File

@@ -51,4 +51,12 @@ $--chart-single-value-icon-background-color: #E8F6FF;
$--content-right-background-color: #EFF2F5; //右侧背景色
// 空白背景色
$--background-color-empty: #fffffe;
$--background-color-1: #EFEFEF;
// 普通字色(覆盖element-ui内置变量)
$--color-text-regular: #666665;
$--border-color-light: #E7EAED;
$--chart-box-border-color: $--border-color-light;
$--chart-title-hover-background-color: #323238;
@import './common';

View File

@@ -51,4 +51,12 @@ $--chart-single-value-icon-background-color: #E8F6FF;
$--content-right-background-color: #EFF2F5; //右侧背景色
// 空白背景色
$--background-color-empty: #fffffe;
$--background-color-1: #EFEFEF;
// 普通字色(覆盖element-ui内置变量)
$--color-text-regular: #666665;
$--border-color-light: #E7EAED;
$--chart-box-border-color: $--border-color-light;
$--chart-title-hover-background-color: $--background-color-1;
@import './common';

View File

@@ -0,0 +1,36 @@
<template>
<div class="chart__loading" v-show="showLoading">
<i class="el-icon-loading"></i>
</div>
</template>
<script>
export default {
name: 'loading',
props: {
loading: Boolean
},
data () {
return {
showLoading: false
}
},
methods: {
startLoading () {
this.showLoading = true
},
endLoading () {
this.showLoading = false
}
},
watch: {
loading: {
deep: true,
immediate: true,
handler (n) {
this.showLoading = n
}
}
}
}
</script>

View File

@@ -5,6 +5,8 @@
<cn-header></cn-header>
<cn-container v-if="containerShow" ref="container"></cn-container>
</main>
<!-- 临时文本dom用来计算文本长度 -->
<span class="temp-dom"></span>
</div>
</template>

View File

@@ -131,7 +131,7 @@
</template>
<script>
import rightBoxMixin from '@/mixins/rightBox'
import rightBoxMixin from '@/mixins/right-box'
import { get, post, put } from '@/utils/http'
import { panelTypeAndRouteMapping } from '@/utils/constants'
import { api } from '@/utils/api'

View File

@@ -90,7 +90,7 @@
</template>
<script>
import rightBoxMixin from '@/mixins/rightBox'
import rightBoxMixin from '@/mixins/right-box'
import { api } from '@/utils/api'
import { VAceEditor } from 'vue3-ace-editor'
import 'ace-builds/src-noconflict/mode-javascript'

View File

@@ -55,7 +55,7 @@
</template>
<script>
import rightBoxMixin from '@/mixins/rightBox'
import rightBoxMixin from '@/mixins/right-box'
import { get, post, put } from '@/utils/http'
export default {
name: 'I18nBox',

View File

@@ -48,7 +48,7 @@
</template>
<script>
import rightBoxMixin from '@/mixins/rightBox'
import rightBoxMixin from '@/mixins/right-box'
import { get, post, put } from '@/utils/http'
export default {
name: 'userBox',

View File

@@ -59,7 +59,6 @@
<el-switch
id="account-input-status"
v-model="editObject.status"
:active-color="theme.themeColor"
:disabled="(editObject.username === loginName) || (editObject.username==='admin' && editObject.id==1)"
:active-value="1"
:inactive-value="0">
@@ -85,7 +84,7 @@
</template>
<script>
import rightBoxMixin from '@/mixins/rightBox'
import rightBoxMixin from '@/mixins/right-box'
import { get, post, put } from '@/utils/http'
export default {
name: 'UserBox',

View File

@@ -44,7 +44,6 @@
<el-switch
v-if="scope.row.id"
v-model="scope.row.status"
:active-color="theme.themeColor"
active-value="1"
:disabled="(scope.row.username === loginName) || (scope.row.username==='admin' && scope.row.id==1) "
inactive-value="0"

View File

@@ -11,7 +11,7 @@ import i18n from '@/i18n'
import hljsVuePlugin from '@highlightjs/vue-plugin'
import 'highlight.js/styles/color-brewer.css'
import '@/assets/css/main.scss' // 样式入口
// import VueGridLayout from 'vue-grid-layout'
import VueGridLayout from 'vue-grid-layout'
import ElementPlus from 'element-plus'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc' // dependent on utc plugin
@@ -34,7 +34,7 @@ app.use(store)
app.use(ElementPlus)
app.use(i18n)
app.use(hljsVuePlugin)
// app.use(VueGridLayout)
app.use(VueGridLayout)
app.directive('has', hasPermission) // 注册指令
app.directive('click-outside', clickOutside)

View File

@@ -1,4 +1,3 @@
import theme from '@/assets/css/theme.scss'
import { hasButton } from '@/permission'
export default {
data () {
@@ -11,7 +10,6 @@ export default {
refresh: false,
query: false
},
theme: theme, // scss主题变量
prevent_opt: {
save: false,
import: false,

View File

@@ -8,10 +8,9 @@ import { storageKey } from '@/utils/constants'
import { loadI18n } from '@/i18n'
const loginWhiteList = ['/login', '/'] // 免登陆白名单
const permissionWhiteList = [...loginWhiteList, '/entityDetail'] // 权限白名单
const permissionWhiteList = [...loginWhiteList] // 权限白名单
router.beforeEach(async (to, from, next) => {
// await test(to, from)
// 加载iso-3166-2资源
loadGeoData()
// 加载baseUrl

View File

@@ -1,9 +1,11 @@
import { createStore } from 'vuex'
import user from './modules/user'
import panel from './modules/panel'
const store = createStore({
modules: {
user
user,
panel
},
state () {
return {

127
src/store/modules/panel.js Normal file
View File

@@ -0,0 +1,127 @@
const panel = {
state: {
showRightBox: false,
chart: '',
delChart: false,
groupId: '',
type: '',
timeRange: [],
nowTimeType: {},
headerH: 0.5,
headerHPadding: 0.5,
rowHeight: 150,
chartListId: '',
chartLastPosition: {
x: 0,
y: 0
}
},
mutations: {
setShowRightBox (state, flag) {
state.showRightBox = flag
},
setChart (state, chart) {
state.chart = chart
},
setDelChart (state, flag) {
state.delChart = flag
},
setType (state, type) {
state.type = type
},
setGroupId (state, id) {
state.groupId = id
},
setPanelTime (state, time) {
state.timeRange = time
},
setPanelNowTimeType (state, nowTimeType) {
state.nowTimeType = nowTimeType
},
setChartLastPosition (state, position) {
state.chartLastPosition = position
},
setHeaderH (state, h) {
state.headerH = h.headerH
state.headerHPadding = h.headerHPadding
state.rowHeight = h.rowHeight
},
cleanPanel (state) {
state.showRightBox = false
state.chart = ''
state.delChart = false
state.groupId = ''
state.type = ''
},
setChartListId (state, id) {
state.chartListId = id
}
},
getters: {
getShowRightBox (state) {
return state.showRightBox
},
getChart (state) {
return state.chart
},
getType (state) {
return state.type
},
getGroupId (state) {
return state.groupId
},
getTimeRange (state) {
return state.timeRange
},
getNowTimeType (state) {
return state.nowTimeType
},
getDelChart (state) {
return state.delChart
},
getHeaderH (state) {
return state.headerH
},
getHeaderHPadding (state) {
return state.headerHPadding
},
getRowHeight (state) {
return state.rowHeight
},
getChartLastPosition (state, position) {
return state.chartLastPosition
},
getChartListId (state, id) {
return state.chartListId
}
},
actions: {
dispatchShowRightBox (store, flag) {
store.commit('setShowRightBox', flag)
},
dispatchChart (store, chart) {
store.commit('setShowRightBox', chart)
},
dispatchDelChart (store, playload) {
store.commit('setDelChart', true)
store.commit('setChart', playload.chart)
store.commit('setType', playload.type)
},
dispatchPanelTime (store, playload) {
store.commit('setPanelTime', playload.time)
store.commit('setPanelNowTimeType', playload.nowTimeType)
},
dispatchEditChart (store, playload) {
store.commit('setShowRightBox', true)
store.commit('setChart', playload.chart)
store.commit('setType', playload.type)
},
dispatchHeaderH (store, playload) {
store.commit('setHeaderH', playload.headerH)
},
clearPanel (store) {
store.commit('cleanPanel')
}
}
}
export default panel

View File

@@ -129,7 +129,12 @@ export const unitTypes = {
export const chartTableDefaultPageSize = 10 // table类型图表默认每页数据量
export const chartTableTopOptions = [10, 100] // table类型图表的TOP-N选项
export const chartActiveIpTableOrderOptions = ['machine'] // active ip table类型图表的order 选项
export const chartPieTableTopOptions = [{ name: 'Sessions', value: 'sessions' }, { name: 'Packets', value: 'packets' }, { name: 'Bytes', value: 'bytes' }] // table类型图表的TOP-N选项
// table类型图表的TOP-N选项
export const chartPieTableTopOptions = [
{ name: 'Sessions', value: 'sessions' },
{ name: 'Packets', value: 'packets' },
{ name: 'Bytes', value: 'bytes' }
]
export const iso36112 = {
[storageKey.iso36112Capital]: 'data/countriesWithCapital',

View File

@@ -1,3 +1,4 @@
import _ from 'lodash'
// 获取初始化时间,默认最近一周
Date.prototype.setStart = function () {
this.setHours(0)
@@ -9,128 +10,28 @@ Date.prototype.setEnd = function () {
this.setMinutes(59)
this.setSeconds(59)
}
export function getDefaultDate () {
let start = this.getDays(-7)
let end = this.getDays(0)
start.setStart()
end.setEnd()
// let start = this.getHoursTime(-1);
// let end = this.getHoursTime(0);
start = this.timeFormate(start, 'yyyy-MM-dd hh:mm:ss')
end = this.timeFormate(end, 'yyyy-MM-dd hh:mm:ss')
this.selectDate = [start, end]
// 将时间转化为秒
export function getSecond (time) {
const ms = getMillisecond(time)
return ms ? Math.floor(ms / 1000) : null
}
export function getHoursTime (hours) {
const today = new Date().getTime()
const date = new Date(today + (hours * 60 * 60 * 1000))
return date
}
// 初始化日期
export function getDays (days) {
const today = new Date().getTime()
return new Date(today + (days * 24 * 60 * 60 * 1000))
}
export function formatDate (date, type) {
const yy = date.getFullYear()
const dateM = date.getMonth() + 1
const mm = dateM > 9 ? dateM : `0${dateM}`
const dateD = date.getDate()
const dd = dateD > 9 ? dateD : `0${dateD}`
if (type) {
return `${yy}${type}${mm}${type}${dd}`
}
return `${yy}${mm}${dd}`
}
export function timeFormate (date, fmt = 'yyyy-MM-dd hh:mm:ss') {
const time = new Date(date)
let week = ''
switch (time.getDay()) {
case 0:
week = '周日'
break
case 1:
week = '周一'
break
case 2:
week = '周二'
break
case 3:
week = '周三'
break
case 4:
week = '周四'
break
case 5:
week = '周五'
break
case 6:
week = '周六'
break
default:
// eslint-disable-next-line no-unused-vars
week = ''
break
}
}
export function getStep (startTime, endTime) {
const start = new Date(startTime)
const end = new Date(endTime)
let step = '15s'
const numInterval = end.getTime() - start.getTime()
const oneDay = 86400000
const sevenDay = 604800000
const thirtyDay = 2592000000
if (numInterval < oneDay) { // 小于1天step为15s
step = '15s'
} else if (numInterval < sevenDay) {
step = '5m'
} else if (numInterval < thirtyDay) {
step = '10m'
} else {
step = '30m'
}
return step
}
export function getNumStr (num) {
if (num >= 1000) {
const kbNum = num / 1000
if (kbNum >= 1000) {
const mbNum = kbNum / 1000
if (mbNum > 1000) {
const gbNum = mbNum / 1000
if (gbNum > 1000) {
const tbNum = gbNum / 1000
if (tbNum > 1000) {
const pbNum = tbNum / 1000
return `${pbNum.toFixed(2)}PB`
}
return `${tbNum.toFixed(2)}TB`
}
return `${gbNum.toFixed(2)}GB`
}
return `${mbNum.toFixed(2)}MB`
// 将时间转化为毫秒
export function getMillisecond (time) {
let ms = null
if (_.isDate(time)) {
ms = time.getTime()
} else if (_.isNumber(time)) {
const timeStr = _.toString(time)
const difference = timeStr.length - 13
if (difference >= 0) {
ms = timeStr.slice(0, 13)
} else {
ms = Math.floor(time * (10 ** (0 - difference)))
}
return `${kbNum.toFixed(2)}KB`
}
return num.toFixed(2)
}
export function debounce (fn, delay) {
// 记录上一次的延时器
let timer = null
delay = delay || 200
return function () {
const args = arguments
const that = this
// 清除上一次延时器
clearTimeout(timer)
timer = setTimeout(function () {
fn.apply(that, args)
}, delay)
}
return ms
}
// 初始化日期
export function getNowTime (interval) {
const endTime = window.$dayJs.tz().valueOf()
const startTime = endTime - interval * 60 * 1000

View File

@@ -425,6 +425,7 @@ export function replaceUrlPlaceholder (url, params) {
_.forIn(params, (value, key) => {
url = url.replace('{{' + key + '}}', value)
})
url = url.replace(/{{(.*?)}}/g, '')
return url
}
// 双引号替换为单引号

View File

@@ -22,10 +22,10 @@
<template v-for="chart in tab.children">
<chart
v-if="activeTab == tab.id"
:key="chart.id"
:chart="chart"
:key="Chart.id"
:chart="Chart"
:time-filter="timeFilter"
:ref="`chart-${chart.id}`"
:ref="`chart-${Chart.id}`"
:entity="entity"
@getCurrentTimeRange="getCurrentTimeRange"
></chart>
@@ -367,11 +367,11 @@
</div>
</div>
<div class="cn-chart__body">
<template v-for="chart in chartInfo.children" :key="chart.id">
<template v-for="chart in chartInfo.children" :key="Chart.id">
<chart
:chart="chart"
:chart="Chart"
:time-filter="timeFilter"
:ref="`chart-${chart.id}`"
:ref="`chart-${Chart.id}`"
:entity="entity"
:parent-data="groupData"
:from-block="fromBlock"
@@ -402,11 +402,11 @@
</div>
</div>
<div class="cn-chart__body">
<template v-for="chart in chartInfo.children" :key="chart.id">
<template v-for="chart in chartInfo.children" :key="Chart.id">
<chart
:chart="chart"
:chart="Chart"
:time-filter="chartTimeFilter"
:ref="`chart-${chart.id}`"
:ref="`chart-${Chart.id}`"
:entity="entity"
:parent-data="groupData"
:from-block="true"

132
src/views/charts/Chart2.vue Normal file
View File

@@ -0,0 +1,132 @@
<template>
<div class="cn-chart">
<loading :loading="loading"></loading>
<chart-no-data v-if="!loading && (isNoData || isError)"></chart-no-data>
<template v-else>
<chart-tabs
v-if="isTabs"
:chart-info="chartInfo"
:query-params="queryParams"
></chart-tabs>
<chart-map
v-else-if="isMap"
:chart-info="chartInfo"
:chart-data="chartData"
:query-params="queryParams"
@showLoading="showLoading"
></chart-map>
</template>
</div>
</template>
<script>
import Loading from '@/components/common/Loading'
import ChartNoData from './charts/ChartNoData'
import ChartTabs from './charts/ChartTabs'
import ChartMap from './charts/ChartMap'
import {
isEcharts,
isSingleValue,
isTable,
isActiveIpTable,
isTitle,
isMap,
getOption,
isEchartsWithTable,
isEchartsWithStatistics,
isEchartsTimeBar,
isEchartsCategoryBar,
isMapLine,
isMapBlock,
isSingleValueWithEcharts,
isSingleValueWithEchartsTemp,
isRelationShip,
isTabs,
isGroup,
isSankey,
isIpBasicInfo,
isIpOpenPort,
isIpHostedDomain,
isDomainWhois,
isDomainDnsRecord,
isCryptocurrencyEventList,
isAppBasicInfo,
isAppRelatedDomain,
isBlock
} from './charts/tools'
import _ from 'lodash'
export default {
name: 'chart',
components: {
Loading,
ChartNoData,
ChartTabs,
ChartMap
},
props: {
chartInfo: Object,
chartData: [Object, Array, String], // 数据在父组件查询后传入,本组件内不查询,只根据接传递的数据来渲染
queryParams: Object, // 接口请求参数
customChartOption: Object, // 需要自定义echarts的option时传入非必须传入该值时仍需传对应格式的chartData
isFullscreen: Boolean,
loading: Boolean,
panelLock: Boolean,
isError: Boolean
},
computed: {
isNoData () {
return _.isEmpty(this.chartData)
},
chartOption () {
if (this.customChartOption) {
return _.cloneDeep(this.customChartOption)
} else {
return getOption(this.chartInfo.type)
}
}
},
methods: {
resize () {
this.$refs['chart' + this.chartInfo.id] && this.$refs['chart' + this.chartInfo.id].resize()
},
showLoading (show) {
this.$emit('showLoading', show)
}
},
setup (props) {
return {
isEcharts: isEcharts(props.chartInfo.type),
isEchartsTimeBar: isEchartsTimeBar(props.chartInfo.type),
isEchartsCategoryBar: isEchartsCategoryBar(props.chartInfo.type),
isEchartsWithTable: isEchartsWithTable(props.chartInfo.type),
isEchartsWithStatistics: isEchartsWithStatistics(props.chartInfo.type),
isSingleValue: isSingleValue(props.chartInfo.type),
isSingleValueWithEcharts: isSingleValueWithEcharts(props.chartInfo.type),
isSingleValueWithEchartsTemp: isSingleValueWithEchartsTemp(props.chartInfo.type),
isRelationShip: isRelationShip(props.chartInfo.type),
isTable: isTable(props.chartInfo.type),
isActiveIpTable: isActiveIpTable(props.chartInfo.type),
isMap: isMap(props.chartInfo.type),
isTitle: isTitle(props.chartInfo.type),
isMapLine: isMapLine(props.chartInfo.type),
isMapBlock: isMapBlock(props.chartInfo.type),
isTabs: isTabs(props.chartInfo.type),
isGroup: isGroup(props.chartInfo.type),
isBlock: isBlock(props.chartInfo.type),
isSankey: isSankey(props.chartInfo.type),
isIpBasicInfo: isIpBasicInfo(props.chartInfo.type),
isIpHostedDomain: isIpHostedDomain(props.chartInfo.type),
isIpOpenPort: isIpOpenPort(props.chartInfo.type),
isDomainWhois: isDomainWhois(props.chartInfo.type),
isDomainDnsRecord: isDomainDnsRecord(props.chartInfo.type),
isCryptocurrencyEventList: isCryptocurrencyEventList(props.chartInfo.type),
isAppBasicInfo: isAppBasicInfo(props.chartInfo.type),
isAppRelatedDomain: isAppRelatedDomain(props.chartInfo.type)
}
}
}
</script>

View File

@@ -0,0 +1,52 @@
<template>
<div class="chart-header" :class="{'chart-header--title-chart': isTitle}">
<div class="chart-header__title">{{chartInfo.name}}</div>
<chart-error :isError="isError" :errorInfo="errorInfo"></chart-error>
<div class="chart-header__tools" v-if="!isTitle">
<el-popover trigger="click" placement="top" :content="chartInfo.remark" v-if="chartInfo.remark">
<template #reference>
<span class="header__operation-btn"><i class="cn-icon el-icon-info"></i></span>
</template>
</el-popover>
<span class="header__operation-btn" @click="refresh"><i class="cn-icon cn-icon-refresh"></i></span>
</div>
</div>
</template>
<script>
import { isTitle } from './charts/tools'
import ChartError from '@/components/charts/ChartError'
export default {
name: 'ChartHeader',
props: {
chartInfo: Object,
errorInfo: {
type: String,
default: ''
},
isError: {
type: Boolean,
default: false
}
},
components: {
ChartError
},
data () {
return {
dropdownMenuShow: false,
errorText: ''
}
},
methods: {
refresh () {
this.$emit('refresh')
}
},
setup (props) {
return {
isTitle: isTitle(props.chartInfo.type)
}
}
}
</script>

View File

@@ -1,13 +1,17 @@
<template>
<div style="padding: 10px 0 20px 20px; overflow: auto" v-if="!isEntityDetail">
<div id="cn-panel"
:class="(isCryptocurrency)?'cn-panel cn-panel-crypto':'cn-panel'"
>
<div style="padding: 10px 10px 20px 10px; overflow: auto" v-if="!isEntityDetail">
<div id="cn-panel" class="cn-panel2">
<div class="panel__time">
<DateTimeRange class="date-time-range" :start-time="timeFilter.startTime" :end-time="timeFilter.endTime" ref="dateTimeRange" @change="reload"/>
<TimeRefresh class="date-time-range" @change="timeRefreshChange" :end-time="timeFilter.endTime"/>
</div>
<chart
<panel-chart-list
:time-filter="timeFilter"
:data-list="chartList"
:panel-lock="panelLock"
>
</panel-chart-list>
<!-- <chart
v-for="chart in chartList"
:key="chart.id"
:chart="chart"
@@ -15,7 +19,7 @@
:ref="`chart-${chart.id}`"
:entity="entity"
@getCurrentTimeRange="getCurrentTimeRange"
></chart>
></chart>-->
<!-- <grid-layout v-model:layout="chartList"
:col-num="12"
:row-height="30"
@@ -40,12 +44,12 @@
<div class="entity-detail__body">
<div class="cn-panel" @scroll="scroll" id="cn-panel">
<template v-for="chart in chartList" :key="chart.id">
<chart
<!-- <chart
:chart="chart"
:ref="`chart-${chart.id}`"
:entity="entity"
@getCurrentTimeRange="getCurrentTimeRange"
></chart>
></chart>-->
</template>
</div>
</div>
@@ -58,7 +62,7 @@ import { ref } from 'vue'
import { panelTypeAndRouteMapping } from '@/utils/constants'
import { api, getPanelList, getChartList } from '@/utils/api'
import { getNowTime } from '@/utils/date-util'
import Chart from './Chart'
import PanelChartList from './PanelChartList'
import DateTimeRange from '@/components/common/TimeRange/DateTimeRange'
import TimeRefresh from '@/components/common/TimeRange/TimeRefresh'
@@ -70,13 +74,14 @@ export default {
typeName: String
},
components: {
Chart,
DateTimeRange,
TimeRefresh
TimeRefresh,
PanelChartList
},
data () {
return {
chartList: [], // 普通panel的chart
panelLock: true,
// entity详情的chart
detailTabs: [],
detailChartList: [],

View File

@@ -0,0 +1,309 @@
<template>
<!-- chart外层箱子 -->
<div :class="{'panel-chart--fullscreen': isFullscreen, 'panel-chart--title-chart': isTitle}" class="panel-chart" :id="isFullscreen ? ('chart-screen-' + chartInfo.id ) : ('chart-local-' + chartInfo.id)">
<!-- title和工具栏支持浮动 -->
<chart-header
v-if="!isFullscreen && showHeader"
:is-error="isError"
:error-info="errorInfo"
:chart-data="chartData"
:chart-info="chartInfo"
@loadMore="loadMore"
@refresh="refresh"
@showFullscreen="showFullscreen"
></chart-header>
<!-- chart -->
<!-- 数据查询后传入chart组件chart组件内不查询只根据接传递的数据来渲染 -->
<chart
ref="chart"
v-if="(!isGroup || !chartInfo.param.collapse) && !isTitle"
:chart-data="chartData"
:chart-info="chartInfo"
:query-params="queryParams"
:panel-lock="panelLock"
:is-error="isError"
:loading="loading"
:is-fullscreen="isFullscreen"
@showLoading="showLoading"
></chart>
</div>
</template>
<script>
import ChartHeader from './ChartHeader'
import Chart from '@/views/charts/Chart2'
import {
isEcharts,
isSingleValue,
isTable,
isActiveIpTable,
isTitle,
isMap,
isEchartsWithTable,
isEchartsWithStatistics,
isEchartsTimeBar,
isEchartsCategoryBar,
isMapLine,
isMapBlock,
isSingleValueWithEcharts,
isSingleValueWithEchartsTemp,
isRelationShip,
isTabs,
isGroup,
isSankey,
isIpBasicInfo,
isIpOpenPort,
isIpHostedDomain,
isDomainWhois,
isDomainDnsRecord,
isCryptocurrencyEventList,
isAppBasicInfo,
isAppRelatedDomain,
isBlock
} from './charts/tools'
import { tableTitleMapping } from '@/components/charts/chart-table-title'
import { replaceUrlPlaceholder } from '@/utils/tools'
import { getNowTime, getSecond } from '@/utils/date-util'
import { chartPieTableTopOptions, chartTableDefaultPageSize, chartTableTopOptions } from '@/utils/constants'
import { get } from '@/utils/http'
import { ref } from 'vue'
export default {
name: 'PanelChart',
components: {
ChartHeader,
Chart
},
props: {
chartInfo: Object, // 其中的param json串已转化为对象
timeFilter: Object, // 时间范围
isFullscreen: Boolean,
panelLock: Boolean,
showHeader: {
type: Boolean,
default: true
}
},
data () {
return {
chartData: null, // 图表要渲染的数据请求接口得到传入chart组件chart组件内除特别情况(多级)外不做查询
loading: false,
isError: false, // 接口响应是否报错
errorInfo: '', // 接口具体错误信息
queryTimeRange: { // 实际查询接口时使用的时间
startTime: '',
endTime: ''
},
queryParams: {},
standaloneTimeRange: { // 单个图表刷新时,重新获取时间范围,且不影响到其他图表
use: false,
startTime: '',
endTime: ''
},
orderPieTable: chartPieTableTopOptions[0].value,
// 挖矿右下角图
activeIpTable: {
orderBy: 'machine',
tableData: [
{
name: '192.168.20.21',
num: 111
}, {
name: '192.168.20.22',
num: 345
}, {
name: '192.168.20.23',
num: 111
}, {
name: '192.168.20.24',
num: 345
}, {
name: '192.168.20.25',
num: 111
}, {
name: '192.168.20.26',
num: 345
}
] // table的所有数据
},
table: {
pageSize: chartTableDefaultPageSize,
limit: chartTableTopOptions[0], // top-n
orderBy: 'sessions'
}
}
},
computed: {
headerH () {
return this.$store.getters.getHeaderH
},
headerHPadding () {
return this.$store.getters.getHeaderHPadding
}
},
methods: {
/* 参数 extraParams 额外请求参数isRefresh 是否是刷新 */
getChartData (url, extraParams = {}, isRefresh) {
this.loading = true
this.standaloneTimeRange.use = !!isRefresh
try {
// 单个图表刷新时,使用单独的时间
if (this.standaloneTimeRange.use) {
this.queryTimeRange = { startTime: getSecond(this.standaloneTimeRange.startTime), endTime: getSecond(this.standaloneTimeRange.endTime) }
} else if (this.timeFilter) {
this.queryTimeRange = { startTime: getSecond(this.timeFilter.startTime), endTime: getSecond(this.timeFilter.endTime) }
} else {
this.queryTimeRange = { startTime: getSecond(this.chartTimeFilter.startTime), endTime: getSecond(this.chartTimeFilter.endTime) }
}
const chartParams = this.chartInfo.params
// 接口查询参数
this.queryParams = {
...this.handleQueryParams(),
...this.queryTimeRange,
...this.entity,
...extraParams
}
const requestUrl = url || chartParams.url
if (requestUrl) {
get(replaceUrlPlaceholder(requestUrl, this.queryParams)).then(response => {
if (response.code === 200) {
this.chartData = response.data.result
this.isError = false
} else {
this.isError = true
this.noData = true
this.errorInfo = response.msg || response.message || 'Unknown'
}
})
}
} catch (e) {
console.error(e)
} finally {
setTimeout(() => {
this.loading = false
}, 200)
}
},
handleQueryParams () {
const params = {}
if (this.isEchartsWithTable) {
params.limit = 10
params.order = this.orderPieTable
} else if (this.isRelationShip) {
params.limit = 5
} else if (this.isActiveIpTable) {
params.order = this.activeIpTable.orderBy
} else if (this.isTable) {
params.limit = this.table.limit
params.order = this.table.orderBy
}
return params
},
resize () {
this.$refs.chart.resize()
},
refresh () {
const myEndTime = window.$dayJs.tz().valueOf()
const myStartTime = myEndTime - this.chartTimeFilter.dateRangeValue * 60 * 1000
this.standaloneTimeRange.use = true
this.standaloneTimeRange.startTime = myStartTime
this.standaloneTimeRange.endTime = myEndTime
this.getChartData(null, {}, true)
},
showFullscreen (show) {
this.$emit('showFullscreen', show, this.chartInfo)
},
loadMore () {
this.$nextTick(() => {
this.$refs.chart && this.$refs.chart.$refs['chart' + this.chartInfo.id].initChart()
})
},
getDataKey (r) {
let key = ''
let labelText = ''
if (r.establishLatency || r.establishLatency === 0) {
key = 'establishLatency'
labelText = this.$t('networkAppPerformance.tripTime')
} else if (r.httpResponseLatency || r.httpResponseLatency === 0) {
key = 'httpResponseLatency'
labelText = this.$t('networkAppPerformance.httpResponse')
} else if (r.sslConLatency || r.sslConLatency === 0) {
key = 'sslConLatency'
labelText = this.$t('networkAppPerformance.sslResponse')
} else if (r.sequenceGapLossPercent || r.sequenceGapLossPercent === 0) {
key = 'sequenceGapLossPercent'
labelText = this.$t('networkAppPerformance.packetLossRate')
} else if (r.pktRetransPercent || r.pktRetransPercent === 0) {
key = 'pktRetransPercent'
labelText = this.$t('networkAppPerformance.retransmissionRate')
} else if (r.sessions || r.sessions === 0) {
key = 'sessions'
labelText = this.$t('overall.sessions')
}
return { key, labelText }
},
getTableTitle (data) {
if (data.length > 0) {
const dataColumns = Object.keys(data[0]) // 返回数据的字段
const columns = dataColumns.map(c => tableTitleMapping[c]) // 展示字段
const keys = ['clientIp', 'serverIp', 'ip', 'appId', 'app', 'appName', 'domain']
return columns.sort((a, b) => {
if (keys.indexOf(a.prop) > -1) {
return -1
} else if (keys.indexOf(b.prop) > -1) {
return 1
} else {
return 0
}
})
} else {
return []
}
},
showLoading (show) {
this.loading = show
}
},
mounted () {
this.getChartData()
},
setup (props) {
const dateRangeValue = 60
const { startTime, endTime } = getNowTime(dateRangeValue)
const chartTimeFilter = ref({ startTime, endTime, dateRangeValue })
return {
chartTimeFilter,
isEcharts: isEcharts(props.chartInfo.type),
isEchartsTimeBar: isEchartsTimeBar(props.chartInfo.type),
isEchartsCategoryBar: isEchartsCategoryBar(props.chartInfo.type),
isEchartsWithTable: isEchartsWithTable(props.chartInfo.type),
isEchartsWithStatistics: isEchartsWithStatistics(props.chartInfo.type),
isSingleValue: isSingleValue(props.chartInfo.type),
isSingleValueWithEcharts: isSingleValueWithEcharts(props.chartInfo.type),
isSingleValueWithEchartsTemp: isSingleValueWithEchartsTemp(props.chartInfo.type),
isRelationShip: isRelationShip(props.chartInfo.type),
isTable: isTable(props.chartInfo.type),
isActiveIpTable: isActiveIpTable(props.chartInfo.type),
isMap: isMap(props.chartInfo.type),
isTitle: isTitle(props.chartInfo.type),
isMapLine: isMapLine(props.chartInfo.type),
isMapBlock: isMapBlock(props.chartInfo.type),
isTabs: isTabs(props.chartInfo.type),
isGroup: isGroup(props.chartInfo.type),
isBlock: isBlock(props.chartInfo.type),
isSankey: isSankey(props.chartInfo.type),
isIpBasicInfo: isIpBasicInfo(props.chartInfo.type),
isIpHostedDomain: isIpHostedDomain(props.chartInfo.type),
isIpOpenPort: isIpOpenPort(props.chartInfo.type),
isDomainWhois: isDomainWhois(props.chartInfo.type),
isDomainDnsRecord: isDomainDnsRecord(props.chartInfo.type),
isCryptocurrencyEventList: isCryptocurrencyEventList(props.chartInfo.type),
isAppBasicInfo: isAppBasicInfo(props.chartInfo.type),
isAppRelatedDomain: isAppRelatedDomain(props.chartInfo.type)
}
}
}
</script>

View File

@@ -0,0 +1,343 @@
<template>
<div :id='`chartList${(isGroup ? "Group" : "") + timestamp}`' class="chart-list" ref="layoutBox">
<grid-layout
ref="layout"
v-if="gridLayoutShow"
v-model:layout="copyDataList"
:col-num="30"
:is-draggable="!panelLock"
:is-resizable="!panelLock"
:margin="[10, 10]"
:row-height="40"
:vertical-compact="true"
:use-css-transforms="false"
>
<grid-item
v-for="item in copyDataList"
:key="item.id"
:h="item.h"
:i="item.i"
:w="item.w"
:x="item.x"
:y="item.y"
:static="item.static"
:ref="'grid-item' + item.id"
:isResizable = "item.type === 'group' ? false: null"
dragAllowFrom=".chart-header"
dragIgnoreFrom=".chart-header__tools"
>
<panel-chart
:ref="'chart' + item.id"
:chart-info="item"
:show-header="true"
:time-filter="timeFilter"
@showFullscreen="showFullscreen"
></panel-chart>
</grid-item>
</grid-layout>
<!-- noData -->
<div v-if="noData" class="no-data">
<div class="no-data-div">No data</div>
</div>
<!-- 全屏查看 -->
<el-dialog
v-model="fullscreen.visible"
:show-close="false"
class="chart-fullscreen"
destroy-on-close
fullscreen
:modal-append-to-body="false"
>
<panel-chart
:ref="'chart-fullscreen' + fullscreen.chartInfo.id"
:chart-info="fullscreen.chartInfo"
:is-fullscreen="true"
:panelLock="panelLock"
:time-filter="timeFilter"
@showFullscreen="showFullscreen"
></panel-chart>
</el-dialog>
</div>
</template>
<script>
import PanelChart from '@/views/charts/PanelChart'
import VueGridLayout from 'vue-grid-layout'
import { getTypeCategory } from './charts/tools'
export default {
name: 'PanelChartList',
components: {
PanelChart,
GridLayout: VueGridLayout.GridLayout,
GridItem: VueGridLayout.GridItem
},
props: {
timeFilter: Object, // 时间范围
panelLock: { type: Boolean, default: true },
isGroup: Boolean,
dataList: Array // 看板中所有图表信息
},
data () {
return {
gridLayoutLoading: false,
gridLayoutShow: false,
copyDataList: [],
noData: false, // no data
tempDom: { height: '', width: '' },
stepWidth: null,
timer: null,
timestamp: new Date().getTime(),
fullscreen: {
visible: false,
chartData: [],
chartInfo: {}
},
scrollTop: 0,
scrollTopTimer: null
}
},
methods: {
init () {
const dom = document.getElementById(this.isGroup ? `chartListGroup${this.timestamp}` : `chartList${this.timestamp}`)
if (dom) {
this.stepWidth = Math.floor(dom.offsetWidth / 12)
}
},
showFullscreen (show, chartInfo) {
this.fullscreen.chartInfo = chartInfo
this.fullscreen.visible = show
}
},
watch: {
dataList: {
deep: true,
handler (n, o) {
this.gridLayoutShow = false
this.gridLayoutLoading = true
this.noData = !n || n.length < 1
/*const nn = JSON.parse(`
[
{
"id": 1,
"name": "Sessions",
"i18n": "overall.sessions",
"panelId": 1,
"pid": 0,
"type": 11,
"x": 20,
"y": 1,
"w": 10,
"h": 5,
"params": "{\\"url\\":\\"/interface/traffic/sessions?startTime={{startTime}}&endTime={{endTime}}\\",\\"unitType\\":\\"number\\"}",
"remark": "",
"state": 1,
"buildIn": 0,
"children": [],
"parent": null,
"panel": {
"id": 1
}
},
{
"id": 2,
"name": "Traffic summary",
"i18n": "trafficSummary.trafficSummary",
"panelId": 1,
"pid": 0,
"type": 93,
"x": 0,
"y": 0,
"w": 30,
"h": 1,
"params": "",
"remark": "",
"state": 1,
"buildIn": 0,
"children": [],
"parent": null,
"panel": {
"id": 1
}
},
{
"id": 3,
"name": "Active list",
"i18n": "trafficSummary.activeList",
"panelId": 1,
"pid": 0,
"type": 93,
"x": 0,
"y": 16,
"w": 30,
"h": 1,
"params": "",
"remark": "",
"state": 1,
"buildIn": 0,
"children": [],
"parent": null,
"panel": {
"id": 1
}
},
{
"id": 4,
"name": "Domain analysis statistics",
"i18n": "trafficSummary.domainStatistics",
"panelId": 1,
"pid": 0,
"type": 93,
"x": 0,
"y": 35,
"w": 30,
"h": 1,
"params": "",
"remark": "",
"state": 1,
"buildIn": 0,
"children": [],
"parent": null,
"panel": {
"id": 1
}
},
{
"id": 6,
"name": "Packets",
"i18n": "overall.packets",
"panelId": 1,
"pid": 0,
"type": 11,
"x": 20,
"y": 6,
"w": 10,
"h": 5,
"params": "{\\"url\\":\\"/interface/traffic/packets?startTime={{startTime}}&endTime={{endTime}}\\",\\"unitType\\":\\"number\\"}",
"remark": "",
"state": 1,
"buildIn": 0,
"children": [],
"parent": null,
"panel": {
"id": 1
}
},
{
"id": 9,
"name": "Throughput",
"i18n": "trafficSummary.throughput",
"panelId": 1,
"pid": 0,
"type": 11,
"x": 20,
"y": 11,
"w": 10,
"h": 5,
"params": "{\\"url\\":\\"/interface/traffic/throughput?startTime={{startTime}}&endTime={{endTime}}\\",\\"unitType\\":\\"byte\\"}",
"remark": "packets",
"state": 1,
"buildIn": 0,
"children": [],
"parent": null,
"panel": {
"id": 1
}
},
{
"id": 10,
"name": "Active client",
"i18n": "trafficSummary.activeClient",
"panelId": 1,
"pid": 0,
"type": 61,
"x": 0,
"y": 17,
"w": 10,
"h": 9,
"params": "{\\"url\\":\\"/interface/traffic/activeClientIP?startTime={{startTime}}&endTime={{endTime}}&order={{order}}&limit={{limit}}\\"}",
"remark": "",
"state": 1,
"buildIn": 0,
"children": [],
"parent": null,
"panel": {
"id": 1
}
},
{
"id": 11,
"name": "Domain category",
"i18n": "trafficSummary.domainCategory",
"panelId": 1,
"pid": 0,
"type": 31,
"x": 0,
"y": 36,
"w": 15,
"h": 13,
"params": "{\\"url\\":\\"/interface/traffic/topDomainCategories?startTime={{startTime}}&endTime={{endTime}}&limit={{limit}}\\",\\"nameColumn\\":\\"fqdnCategoryName\\",\\"valueColumn\\":\\"uniqDomains\\",\\"tableNameColumn\\":\\"domain\\",\\"urlTable\\":\\"/interface/traffic/activeDomainCategories?startTime={{startTime}}&endTime={{endTime}}&fqdnCategoryName={{fqdnCategoryName}}&limit={{limit}}&order={{order}}\\",\\"urlChildrenTable\\":\\"/interface/traffic/domainCategoryServerIpList?startTime={{startTime}}&endTime={{endTime}}&order={{order}}&limit={{limit}}&domain={{domain}}\\"}",
"remark": "",
"state": 1,
"buildIn": 0,
"panel": {
"id": 1
}
},
{
"id": 12,
"name": "Traffic map",
"i18n": "trafficSummary.trafficMap",
"panelId": 1,
"pid": 0,
"type": 2,
"x": 0,
"y": 1,
"w": 20,
"h": 15,
"params": "{\\"url\\":\\"/interface/traffic/map?startTime={{startTime}}&endTime={{endTime}}&country={{country}}&province={{province}}&city={{city}}\\",\\"unitType\\":\\"byte\\",\\"valueColumn\\":\\"bytes\\"}",
"remark": "traffic summaryyy",
"state": 1,
"buildIn": 0,
"children": [],
"parent": null,
"panel": {
"id": 1
}
}
]
`)*/
const tempList = n.map(item => {
let params = {}
const height = item.h
if (item.params) {
try {
params = JSON.parse(item.params)
} catch (e) {
console.error(e)
}
params.showHeader = true
}
return {
...item,
i: item.id,
w: item.w,
h: height,
x: item.x || 0,
y: item.y || 0,
params,
category: getTypeCategory(item.type) // 类别是echarts还是map等等
}
})
this.$nextTick(() => {
this.copyDataList = JSON.parse(JSON.stringify(tempList))
setTimeout(() => {
this.gridLayoutShow = true
})
this.gridLayoutLoading = false
})
}
}
}
}
</script>

View File

@@ -0,0 +1,263 @@
<template>
<div class="map-back" v-show="showMapBackButton" @click="mapBack">&lt; back</div>
<div class="chart-drawing" :id="`chart${chartInfo.id}`"></div>
</template>
<script>
import * as L from 'leaflet'
import * as am4Core from '@amcharts/amcharts4/core'
import * as am4Maps from '@amcharts/amcharts4/maps'
import { getGeoData, replaceUrlPlaceholder } from '@/utils/tools'
import { storageKey } from '@/utils/constants'
import 'leaflet/dist/leaflet.css'
import icon from 'leaflet/dist/images/marker-icon.png'
import iconShadow from 'leaflet/dist/images/marker-shadow.png'
import {
isIpBasicInfo,
isMapBlock
} from './tools'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
import { HeatLegend } from '@/components/amcharts/heatLegend'
import { getData } from '@/utils/api'
export default {
name: 'ChartMap',
data () {
return {
myChart: null,
mapPictureUrl: '/Tiles/{z}/{x}/{y}.png',
showMapBackButton: false,
polygonSeries: null, // 世界地图series
countrySeries: null // 下钻国家series
}
},
props: {
chartInfo: Object,
chartData: [Array, Object],
queryParams: Object
},
methods: {
initMap (id) {
if (this.isIpBasicInfo) {
L.Marker.prototype.options.icon = L.icon({
iconUrl: icon,
shadowUrl: iconShadow
})
const map = L.map(`chart${this.chartInfo.id}`, {
minZoom: 3,
maxZoom: 7,
zoom: 5,
attributionControl: false,
zoomControl: false,
maxBounds: L.latLngBounds(L.latLng(-90, -180), L.latLng(90, 180))
})
L.tileLayer(
this.mapPictureUrl,
{ noWrap: true }
).addTo(map)
const attribution = L.control.attribution({ position: 'bottomright', prefix: '' })
attribution.addAttribution(' © OpenStreetMap contributors')
attribution.addTo(map)
/* L.control.zoom({
position: 'bottomright',
zoomInText: '<i class="nz-icon nz-icon-enlarge"></i>',
zoomOutText: '<i class="nz-icon nz-icon-narrow"></i>',
zoomInTitle: '',
zoomOutTitle: ''
}).addTo(map) */
this.myChart = map
this.loadLeafletMap()
} else {
const chart = am4Core.create(id, am4Maps.MapChart)
chart.geodata = getGeoData(storageKey.iso36112WorldLow)
chart.projection = new am4Maps.projections.Miller()
this.myChart = chart
const polygonSeries = chart.series.push(new am4Maps.MapPolygonSeries())
polygonSeries.useGeodata = true
polygonSeries.exclude = ['AQ'] // 排除南极洲
polygonSeries.tooltip.getFillFromObject = false
polygonSeries.tooltip.background.fill = am4Core.color('#FFFFFF')
this.polygonSeries = polygonSeries
const polygonTemplate = polygonSeries.mapPolygons.template
polygonTemplate.tooltipHTML = this.generateTooltipHTML()
polygonTemplate.nonScalingStroke = true
polygonTemplate.strokeWidth = 0.5
polygonTemplate.fill = am4Core.color('rgba(176,196,222,.5)')
this.loadAm4ChartMap(this.polygonSeries)
// 地图点击事件
polygonTemplate.events.on('hit', async ev => {
const countryId = ev.target.dataItem.dataContext.id
if (countryId) {
ev.target.series.chart.zoomToMapObject(ev.target)
ev.target.isHover = false
this.countrySeries = chart.series.push(new am4Maps.MapPolygonSeries())
this.countrySeries.tooltip.getFillFromObject = false
this.countrySeries.tooltip.background.fill = am4Core.color('#FFFFFF')
const countryTemplate = this.countrySeries.mapPolygons.template
countryTemplate.tooltipHTML = this.generateTooltipHTML()
countryTemplate.nonScalingStroke = true
countryTemplate.strokeWidth = 0.5
countryTemplate.fill = am4Core.color('rgba(176,196,222,.5)')
const geoData = getGeoData(countryId)
if (geoData) {
this.countrySeries.geodata = geoData
this.polygonSeries.hide()
const country = ev.target.dataItem.dataContext.serverCountry
const queryParams = { ...this.queryParams, country }
const chartData = await getData(replaceUrlPlaceholder(this.chartInfo.params.url, queryParams))
this.loadAm4ChartMap(this.countrySeries, ev.target.dataItem.dataContext.serverCountry, chartData)
}
}
})
}
},
loadAm4ChartMap (polygonSeries, country, chartData) {
this.$emit('showLoading', true)
try {
// 清除数据
polygonSeries.data.splice(0)
// 清除legend
this.myChart.children.each((s, i) => {
if (s && s.className !== 'Container') {
this.myChart.children.removeIndex(i)
}
})
this.showMapBackButton = !!country
const chartParams = this.chartInfo.params
const data = chartData || this.chartData
if (data && this.isMapBlock) {
const sumData = []
data.forEach(r => {
const hit = sumData.find(s => s.id === r.serverId)
const { key, labelText } = this.getDataKey(r)
const value = Number(r[key]) || 0
if (hit) {
hit.value += value
} else {
sumData.push({
id: r.serverId,
serverCountry: r.serverCountry,
key,
labelText,
value
})
}
})
const seriesData = sumData.map(r => ({
...r,
showValue: (r.value || r.value === 0) ? valueToRangeValue(r.value, chartParams.unitType).join(' ') : ''
}))
polygonSeries.data = [...seriesData]
const sorted = seriesData.sort((a, b) => b.value - a.value)
const allZero = this.$_.isEmpty(sorted) || Number(sorted[0].value) === 0 // 数据全为0的情况legend只显示1个颜色
polygonSeries.heatRules.push({
property: 'fill',
target: polygonSeries.mapPolygons.template,
min: this.myChart.colors.getIndex(1).brighten(1),
max: allZero ? this.myChart.colors.getIndex(1).brighten(1) : this.myChart.colors.getIndex(1).brighten(-0.3)
})
const heatLegend = this.myChart.createChild(HeatLegend)
heatLegend.markerContainer.height = 6
heatLegend.series = polygonSeries
heatLegend.align = 'left'
heatLegend.markerCount = allZero ? 1 : 3
heatLegend.minValue = 0
heatLegend.fontSize = 12
heatLegend.maxValue = allZero ? 1 : Number(sorted[0].value)
heatLegend.width = allZero ? am4Core.percent(10) : am4Core.percent(25)
heatLegend.marginLeft = 15
heatLegend.valign = 'bottom'
const minRange = heatLegend.valueAxis.axisRanges.create()
minRange.value = heatLegend.minValue
minRange.label.text = minRange.value === 0 ? 0 : unitConvert(heatLegend.minValue, chartParams.unitType).join(' ')
const maxRange = heatLegend.valueAxis.axisRanges.create()
maxRange.value = heatLegend.maxValue
maxRange.label.text = maxRange.value === 0 ? 0 : unitConvert(heatLegend.maxValue, chartParams.unitType).join(' ')
heatLegend.valueAxis.renderer.labels.template.adapter.add('text', function (labelText) {
return ''
})
}
} catch (e) {
console.error(e)
} finally {
setTimeout(() => {
this.$emit('showLoading', false)
}, 200)
}
},
loadLeafletMap () {
this.$emit('showLoading', true)
try {
this.myChart.setView([this.chartData.latitude, this.chartData.longitude], 5)
const myIcon = L.divIcon({
className: 'cn-icon cn-icon-position2 position-icon'
})
L.marker([this.chartData.latitude, this.chartData.longitude], { icon: myIcon }).addTo(this.myChart)
} finally {
setTimeout(() => {
this.$emit('showLoading', false)
}, 200)
}
},
mapBack () {
this.countrySeries.hide()
this.$nextTick(() => {
this.showMapBackButton = false
this.polygonSeries.show()
this.myChart.goHome()
})
},
generateTooltipHTML () {
return `
<div class="map-tooltip" style="padding-bottom: 10px;">
<div class="map-tooltip__title">{name}</div>
<div class="map-tooltip__content">
<span>{labelText}</span>
<span>{showValue}</span>
</div>
</div>
`
},
getDataKey (r) {
let key = ''
let labelText = ''
if (r.establishLatency || r.establishLatency === 0) {
key = 'establishLatency'
labelText = this.$t('networkAppPerformance.tripTime')
} else if (r.httpResponseLatency || r.httpResponseLatency === 0) {
key = 'httpResponseLatency'
labelText = this.$t('networkAppPerformance.httpResponse')
} else if (r.sslConLatency || r.sslConLatency === 0) {
key = 'sslConLatency'
labelText = this.$t('networkAppPerformance.sslResponse')
} else if (r.sequenceGapLossPercent || r.sequenceGapLossPercent === 0) {
key = 'sequenceGapLossPercent'
labelText = this.$t('networkAppPerformance.packetLossRate')
} else if (r.pktRetransPercent || r.pktRetransPercent === 0) {
key = 'pktRetransPercent'
labelText = this.$t('networkAppPerformance.retransmissionRate')
} else if (r.sessions || r.sessions === 0) {
key = 'sessions'
labelText = this.$t('overall.sessions')
}
return { key, labelText }
}
},
mounted () {
this.initMap(`chart${this.chartInfo.id}`)
},
setup (props) {
return {
isIpBasicInfo: isIpBasicInfo(props.chartInfo.type),
isMapBlock: isMapBlock(props.chartInfo.type)
}
}
}
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div class="panel-chart__no-data">No data</div>
</template>
<script>
export default {
name: 'ChartNoData'
}
</script>

View File

@@ -0,0 +1,68 @@
<template>
<el-tabs
class="cn-chart cn-chart__tabs"
v-model="activeTab"
@tab-click="changeTab"
:style="computePosition"
:ref="`chart-${chart.id}`"
>
<el-tab-pane
v-for="tab in chartInfo.children"
:label="tab.i18n ? $t(tab.i18n) : tab.name" :name="`${tab.id}`"
:key="tab.id"
:ref="`chart-${chart.id}`"
>
<template v-for="chart in tab.children" :key="chart.id">
<panel-chart
:ref="'chart' + chart.id"
:chart-info="chart"
:show-header="true"
:time-filter="timeFilter"
@showFullscreen="showFullscreen"
></panel-chart>
</template>
</el-tab-pane>
</el-tabs>
</template>
<script>
import _ from 'lodash'
import PanelChart from '@/views/charts/PanelChart'
export default {
name: 'ChartTabs',
props: {
chartInfo: Object,
chartData: [Object, Array, String], // 数据在父组件查询后传入,本组件内不查询,只根据接传递的数据来渲染
queryParams: Object // 接口请求参数
},
components: {
PanelChart
},
computed: {
timeFilter () {
return {
startTime: this.queryParams.startTime,
endTime: this.queryParams.endTime
}
}
},
data () {
return {
}
},
methods: {
showFullscreen () {
}
},
setup (props) {
let activeTab = ''
if (!_.isEmpty(props.chartInfo.children)) {
activeTab = `${props.chartInfo.children[0].id}`
}
return {
activeTab
}
}
}
</script>

View File

@@ -0,0 +1,828 @@
/**
* @author 陈劲松
* @date 2021/6/16
* @description chart option和一些工具
*/
import { format } from 'echarts'
import { unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import _ from 'lodash'
export const chartColor = ['#5370C6', '#90CC74', '#FAC858', '#EE6666',
'#73BFDE', '#3BA172', '#FC8452', '#9960B4',
'#E97CCC', '#FEA69E', '#0F8AB2', '#57CBAC',
'#5888BC', '#63B6AC', '#EDC6B2', '#D5746B']
export const chartBarColor = ['#0F8AB2', '#57CBAC']
export function getChartColor (index) {
return chartColor[index % chartColor.length]
}
export function getCharBartColor (index) {
return chartBarColor[index % chartBarColor.length]
}
const line = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: axiosFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time'
},
yAxis: {
type: 'value',
axisLabel: {
formatter: function (value, index, a, b) {
return unitConvert(value, unitTypes.number).join(' ')
}
},
minInterval: 1
},
animation: false,
grid: {
left: 55,
bottom: 30,
top: 100,
right: 25
},
color: chartColor,
legend: {
tooltip: {
show: true,
formatter: '{a}'
},
show: true,
right: 23,
top: 8,
padding: 2,
orient: 'horizontal',
icon: 'circle',
itemGap: 10,
itemWidth: 10,
textStyle: {
padding: [0, 0, 0, 2],
fontSize: 14
},
formatter: tooLongFormatter
},
axisLabel: {
fontSize: 14
},
series: [
{
name: '',
type: 'line',
smooth: false,
symbol: 'none',
data: []
}
]
}
const lineWithStatistics = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: axiosFormatter,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time'
},
animation: false,
yAxis: {
type: 'value',
axisLabel: {
formatter: function (value, index) {
return unitConvert(value, unitTypes.number).join(' ')
}
},
minInterval: 1
},
color: chartColor,
grid: {
left: 55,
bottom: 30,
top: 20,
right: 20
},
legend: {
show: false
},
axisLabel: {
fontSize: 14
},
series: [
{
name: '',
type: 'line',
smooth: false,
symbol: 'none',
data: []
}
]
}
const lineStack = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: axiosFormatter,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time'
},
color: chartColor,
yAxis: {
type: 'value',
axisLabel: {
formatter: function (value, index) {
return unitConvert(value, unitTypes.number).join(' ')
}
},
minInterval: 1
},
grid: {
left: 55,
bottom: 45,
top: 10,
right: 180
},
legend: {
show: true,
right: 30,
top: 'middle',
orient: 'vertical',
icon: 'circle',
itemGap: 20,
itemWidth: 10,
formatter: tooLongFormatter,
textStyle: {
padding: [0, 0, 0, 5],
fontSize: 14
}
},
axisLabel: {
fontSize: 14
},
series: [
{
name: '',
type: 'line',
stack: 'value',
areaStyle: {},
symbol: 'none',
data: []
}
]
}
const pieWithTable = {
tooltip: {
appendToBody: true
},
color: chartColor,
animation: false,
legend: {
orient: 'vertical',
type: 'plain',
left: '60%',
top: 'middle',
icon: 'circle',
itemWidth: 10, // 设置宽度
itemHeight: 10, // 设置高度
itemGap: 20,
formatter: tooLongFormatter,
tooltip: {
show: true
}
},
series: [
{
type: 'pie',
selectedMode: 'single',
radius: ['42%', '65%'],
center: ['30%', '50%'],
data: [],
label: {
formatter: '{d}%'
},
tooltip: {
formatter: function (param, index, callback) {
return `${param.name}: ${unitConvert(param.value, param.data.unitType).join(' ')}`
}
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
const ipHostedDomain = {
color: chartColor,
animation: false,
tooltip: {
show: true
},
legend: {
orient: 'vertical',
type: 'plain',
right: '8%',
top: 'middle',
icon: 'circle',
itemWidth: 10, // 设置宽度
itemHeight: 10, // 设置高度
itemGap: 20,
tooltip: {
show: true
}
},
series: [
{
type: 'pie',
selectedMode: 'single',
radius: ['42%', '65%'],
center: ['36%', '50%'],
data: [],
label: {
formatter: '{d}%'
},
tooltip: {
formatter: function (param, index, callback) {
return `${param.name}: ${unitConvert(param.value, param.data.unitType).join(' ')}`
}
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
const singleValueLine = {
tooltip: {
show: true,
enterable: true,
showContent: true,
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
}
},
xAxis: {
type: 'time',
show: false
},
yAxis: {
type: 'value',
show: false
},
animation: false,
grid: {
left: 0,
bottom: 2,
top: 5,
right: 0
},
color: chartColor,
legend: {
show: false
},
series: [
{
type: 'line',
legendHoverLink: false,
itemStyle: {
normal: {
color: '#81C9FF',
lineStyle: {
width: 2
}
}
},
data: [],
showSymbol: false,
areaStyle: { color: '#C9EAFF' }
}
]
}
export const entityListLineOption = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: axiosFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time',
show: false
},
yAxis: {
type: 'value',
show: false
},
animation: false,
grid: {
left: 0,
bottom: 2,
top: 5,
right: 0
},
color: chartColor,
legend: {
show: false
},
series: [
{
type: 'line',
legendHoverLink: false,
itemStyle: {
normal: {
lineStyle: {
width: 2
}
}
},
data: [],
showSymbol: false
}
]
}
const relationShip = {
grid: {
left: 0,
bottom: 50,
top: 80,
right: 0
},
series: [
{
type: 'graph',
layout: 'force',
symbolSize: 40,
roam: true,
force: {
repulsion: 350
},
draggable: true,
label: { show: true },
edgeSymbol: ['none', 'arrow'],
edgeSymbolSize: 7,
data: [],
links: []
}
]
}
const sankey = {
tooltip: {
trigger: 'item',
triggerOn: 'mousemove'
},
series: [
{
type: 'sankey',
data: [],
links: [],
right: '5%',
top: 50,
bottom: 100,
levels: [
{
depth: 0,
itemStyle: {
color: '#47D49C'
},
lineStyle: {
color: '#999'
}
}, {
depth: 1,
itemStyle: {
color: '#A69BF5'
},
lineStyle: {
color: '#999'
}
}, {
depth: 2,
itemStyle: {
color: '#73A0FA'
},
lineStyle: {
color: '#999'
}
}
]
}
]
}
const ipOpenPortBar = {
xAxis: {
type: 'category',
axisTick: { show: false },
axisLine: { show: false }
},
grid: {
top: 30,
left: 60,
right: 50,
bottom: 50
},
yAxis: {
type: 'value',
show: false
},
series: [{
barWidth: 38,
data: [],
type: 'bar',
label: { show: true, position: 'top' },
barCategoryGap: '10%'
}]
}
const categoryBar = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: categoryVerticalFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'category',
axisTick: { show: false },
axisLine: { show: false }
},
grid: {
top: 20,
left: 10,
right: 25,
bottom: 20,
containLabel: true
},
yAxis: {
type: 'value',
axisTick: { show: false },
axisLine: { show: false }
},
color: chartColor,
series: [{
barWidth: 15,
data: [],
type: 'bar',
label: { show: false },
barCategoryGap: '10%',
itemStyle: {
color: function (params) {
return getCharBartColor([params.dataIndex])
}
}
}]
}
const timeBar = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: timeVerticalFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time',
axisTick: { show: false },
axisLine: { show: false },
axisLabel: {
interval: 0,
// rotate: -40, //设置日期显示样式(倾斜度)
formatter: function (value) { // 在这里写你需要的时间格式
const t_date = new Date(value)
return [t_date.getMonth() + 1, t_date.getDate()].join('/') + ' ' + [t_date.getHours(), t_date.getMinutes()].join(':')
}
}
},
grid: {
top: 20,
left: 25,
right: 25,
bottom: 20,
containLabel: true
},
yAxis: {
type: 'value',
axisTick: { show: false },
axisLine: { show: false },
axisLabel: {
formatter: function (value, index, a, b) {
return unitConvert(value, unitTypes.number).join(' ')
}
},
minInterval: 1
},
color: chartColor,
series: [{
barWidth: 15,
data: [],
type: 'bar',
label: { show: false },
barCategoryGap: '10%',
itemStyle: {
color: function (params) {
return getCharBartColor([params.dataIndex])
}
}
}]
}
const typeOptionMappings = [
{ value: 11, option: line }, // 常规折线图
{ value: 12, option: lineWithStatistics }, // 带统计表格的折线图
{ value: 13, option: lineStack }, // 折线堆叠图
{ value: 22, option: ipOpenPortBar }, // ip详情--开放端口的柱状图
{ value: 23, option: timeBar }, // 矿机所属单位
{ value: 24, option: categoryBar }, // 挖矿事件统计
{ value: 31, option: pieWithTable }, // 常规折线图
{ value: 33, option: ipHostedDomain }, // ip详情--托管域名
{ value: 34, option: ipHostedDomain }, // app详情--相关域名
{ value: 42, option: relationShip }, // 关系图
{ value: 43, option: sankey }, // 桑基图
{ value: 52, option: singleValueLine }
]
const typeCategory = {
MAP: 'map',
TABLE: 'table',
ECHARTS: 'echarts',
TITLE: 'title',
SINGLE: 'singleValue',
TABS: 'tabs'
}
export function getTypeCategory (type) {
if (isMap(type)) {
return typeCategory.MAP
} else if (isEcharts(type)) {
return typeCategory.ECHARTS
} else if (isTable(type)) {
return typeCategory.TABLE
} else if (isSingleValue(type)) {
return typeCategory.SINGLE
} else if (isTitle(type)) {
return typeCategory.TITLE
} else if (isTabs(type)) {
return typeCategory.TABS
}
}
/* 柱状图:挖矿事件统计(time类型柱状图) */
export function isEchartsTimeBar (type) {
return type == 23
}
/* 柱状图:矿机所属单位(category类型柱状图) */
export function isEchartsCategoryBar (type) {
return type == 24
}
/* 饼图柱状图等 */
export function isEcharts (type) {
return type >= 11 && type <= 50
}
/* 地图 */
export function isMap (type) {
return type >= 1 && type <= 10
}
/* 连线地图 */
export function isMapLine (type) {
return type === 1
}
/* 色块地图 */
export function isMapBlock (type) {
return type === 2
}
/* 带统计的折线图 */
export function isEchartsWithStatistics (type) {
return type === 12
}
/* 关系图 */
export function isRelationShip (type) {
return type === 42
}
/* 桑基图 */
export function isSankey (type) {
return type === 43
}
/* 单值 */
export function isSingleValue (type) {
return type >= 51 && type <= 60
}
/* 带折线图的单值 */
export function isSingleValueWithEcharts (type) {
return type === 52
}
/* 带折线图的单值 */
export function isSingleValueWithEchartsTemp (type) {
return type === 55
}
/* 带Table的饼图 */
export function isEchartsWithTable (type) {
return type === 31
}
/* table */
export function isTable (type) {
return type >= 61 && type <= 70
}
/* table */
export function isActiveIpTable (type) {
return type == 63
}
/* title */
export function isTitle (type) {
return type === 93
}
/* tabs */
export function isTabs (type) {
return type === 91
}
/* IP实体基本信息 */
export function isIpBasicInfo (type) {
return type === 4
}
/* IP实体开放端口 */
export function isIpOpenPort (type) {
return type === 22
}
/* IP实体托管域名 */
export function isIpHostedDomain (type) {
return type === 33
}
/* APP实体相关域名 */
export function isAppRelatedDomain (type) {
return type === 34
}
/* APP实体基本信息 */
export function isAppBasicInfo (type) {
return type === 82
}
/* DOMAIN实体Whois */
export function isDomainWhois (type) {
return type === 83
}
/* DOMAIN实体DNS记录 */
export function isDomainDnsRecord (type) {
return type === 84
}
/* 近期挖矿事件 */
export function isCryptocurrencyEventList (type) {
return type === 85
}
/* 组 */
export function isGroup (type) {
return type === 94
}
/* 实体详情块 */
export function isBlock (type) {
return type === 95
}
export function getOption (type) {
const mapping = typeOptionMappings.find(m => m.value === type)
return mapping && mapping.option ? _.cloneDeep(mapping.option) : null
}
export const layoutConstant = {
HEADER: 'header',
FOOTER: 'footer'
}
export function getLayout (type) {
const layout = []
if (!isSingleValue(type) && !isTitle(type)) {
layout.push(layoutConstant.HEADER)
}
if (type === 12 || type === 31) {
layout.push(layoutConstant.FOOTER)
}
return layout
}
function tooLongFormatter (name) {
return format.truncateText(name, 110, '12')
}
function axiosFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
const tData = item.data[0]
if (i === 0) {
str += '<div style="margin-bottom: 5px">'
str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss')
str += '</div>'
}
str += '<div class="cn-chart-tooltip-box">'
str += item.marker
str += `<span class="cn-chart-tooltip-content">
${item.seriesName}
</span>`
str += `<span class="cn-chart-tooltip-value">
${unitConvert(item.data[1], item.data[2]).join(' ')}
</span>`
str += '</div>'
})
str += '</div>'
return str
}
export function timeVerticalFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
const tData = item.data[0]
if (i === 0) {
str += '<div style="margin-bottom: 5px">'
str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss')
str += '</div>'
}
str += '<div class="cn-chart-tooltip-box">'
str += item.marker
str += `<span class="cn-chart-tooltip-content">
${item.seriesName}
</span>`
str += `<span class="cn-chart-tooltip-value">
${unitConvert(item.data[1], item.data[2]).join(' ')}
</span>`
str += '</div>'
})
str += '</div>'
return str
}
export function timeHorizontalFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
const tData = item.data[1]
if (i === 0) {
str += '<div style="margin-bottom: 5px">'
str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss')
str += '</div>'
}
str += '<div class="cn-chart-tooltip-box">'
str += item.marker
str += `<span class="cn-chart-tooltip-content">
${item.seriesName}
</span>`
str += `<span class="cn-chart-tooltip-value">
${unitConvert(item.data[0], item.data[2]).join(' ')}
</span>`
str += '</div>'
})
str += '</div>'
return str
}
export function categoryHorizontalFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
str += '<div class="cn-chart-tooltip-box">'
str += item.data[1] + ': ' + item.data[0]
str += '</div>'
})
str += '</div>'
return str
}
export function categoryVerticalFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
str += '<div class="cn-chart-tooltip-box">'
str += item.data[0] + ': ' + item.data[1]
str += '</div>'
})
str += '</div>'
return str
}

View File

@@ -0,0 +1,135 @@
import unitConvert from '@/utils/unit-convert'
import { unitTypes } from '@/utils/constants'
import {
categoryVerticalFormatter,
chartColor,
getCharBartColor,
timeVerticalFormatter
} from '@/views/charts/charts/tools'
export const ipOpenPortBar = {
xAxis: {
type: 'category',
axisTick: { show: false },
axisLine: { show: false }
},
grid: {
top: 30,
left: 60,
right: 50,
bottom: 50
},
yAxis: {
type: 'value',
show: false
},
series: [{
barWidth: 38,
data: [],
type: 'bar',
label: { show: true, position: 'top' },
barCategoryGap: '10%'
}]
}
export const categoryBar = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: categoryVerticalFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'category',
axisTick: { show: false },
axisLine: { show: false }
},
grid: {
top: 20,
left: 10,
right: 25,
bottom: 20,
containLabel: true
},
yAxis: {
type: 'value',
axisTick: { show: false },
axisLine: { show: false }
},
color: chartColor,
series: [{
barWidth: 15,
data: [],
type: 'bar',
label: { show: false },
barCategoryGap: '10%',
itemStyle: {
color: function (params) {
return getCharBartColor([params.dataIndex])
}
}
}]
}
export const timeBar = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: timeVerticalFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time',
axisTick: { show: false },
axisLine: { show: false },
axisLabel: {
interval: 0,
// rotate: -40, //设置日期显示样式(倾斜度)
formatter: function (value) { // 在这里写你需要的时间格式
const tDate = new Date(value)
return [tDate.getMonth() + 1, tDate.getDate()].join('/') + ' ' + [tDate.getHours(), tDate.getMinutes()].join(':')
}
}
},
grid: {
top: 20,
left: 25,
right: 25,
bottom: 20,
containLabel: true
},
yAxis: {
type: 'value',
axisTick: { show: false },
axisLine: { show: false },
axisLabel: {
formatter: function (value, index, a, b) {
return unitConvert(value, unitTypes.number).join(' ')
}
},
minInterval: 1
},
color: chartColor,
series: [{
barWidth: 15,
data: [],
type: 'bar',
label: { show: false },
barCategoryGap: '10%',
itemStyle: {
color: function (params) {
return getCharBartColor([params.dataIndex])
}
}
}]
}

View File

@@ -0,0 +1,25 @@
export const relationShip = {
grid: {
left: 0,
bottom: 50,
top: 80,
right: 0
},
series: [
{
type: 'graph',
layout: 'force',
symbolSize: 40,
roam: true,
force: {
repulsion: 350
},
draggable: true,
label: { show: true },
edgeSymbol: ['none', 'arrow'],
edgeSymbolSize: 7,
data: [],
links: []
}
]
}

View File

@@ -0,0 +1,274 @@
import unitConvert from '@/utils/unit-convert'
import { unitTypes } from '@/utils/constants'
import { chartColor } from '@/views/charts/charts/chart-options'
import { axisFormatter, tooLongFormatter } from '../tools'
export const line = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: axisFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time'
},
yAxis: {
type: 'value',
axisLabel: {
formatter: function (value, index, a, b) {
return unitConvert(value, unitTypes.number).join(' ')
}
},
minInterval: 1
},
animation: false,
grid: {
left: 55,
bottom: 30,
top: 100,
right: 25
},
color: chartColor,
legend: {
tooltip: {
show: true,
formatter: '{a}'
},
show: true,
right: 23,
top: 8,
padding: 2,
orient: 'horizontal',
icon: 'circle',
itemGap: 10,
itemWidth: 10,
textStyle: {
padding: [0, 0, 0, 2],
fontSize: 14
},
formatter: tooLongFormatter
},
axisLabel: {
fontSize: 14
},
series: [
{
name: '',
type: 'line',
smooth: false,
symbol: 'none',
data: []
}
]
}
export const lineWithStatistics = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: axisFormatter,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time'
},
animation: false,
yAxis: {
type: 'value',
axisLabel: {
formatter: function (value, index) {
return unitConvert(value, unitTypes.number).join(' ')
}
},
minInterval: 1
},
color: chartColor,
grid: {
left: 55,
bottom: 30,
top: 20,
right: 20
},
legend: {
show: false
},
axisLabel: {
fontSize: 14
},
series: [
{
name: '',
type: 'line',
smooth: false,
symbol: 'none',
data: []
}
]
}
export const lineStack = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: axisFormatter,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time'
},
color: chartColor,
yAxis: {
type: 'value',
axisLabel: {
formatter: function (value, index) {
return unitConvert(value, unitTypes.number).join(' ')
}
},
minInterval: 1
},
grid: {
left: 55,
bottom: 45,
top: 10,
right: 180
},
legend: {
show: true,
right: 30,
top: 'middle',
orient: 'vertical',
icon: 'circle',
itemGap: 20,
itemWidth: 10,
formatter: tooLongFormatter,
textStyle: {
padding: [0, 0, 0, 5],
fontSize: 14
}
},
axisLabel: {
fontSize: 14
},
series: [
{
name: '',
type: 'line',
stack: 'value',
areaStyle: {},
symbol: 'none',
data: []
}
]
}
export const singleValueLine = {
tooltip: {
show: true,
enterable: true,
showContent: true,
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
}
},
xAxis: {
type: 'time',
show: false
},
yAxis: {
type: 'value',
show: false
},
animation: false,
grid: {
left: 0,
bottom: 2,
top: 5,
right: 0
},
color: chartColor,
legend: {
show: false
},
series: [
{
type: 'line',
legendHoverLink: false,
itemStyle: {
normal: {
color: '#81C9FF',
lineStyle: {
width: 2
}
}
},
data: [],
showSymbol: false,
areaStyle: { color: '#C9EAFF' }
}
]
}
export const entityListLine = {
tooltip: {
appendToBody: true,
trigger: 'axis',
textStyle: {
width: '20px',
overflow: 'truncate'
},
formatter: axisFormatter,
show: true,
className: 'nz-chart-tooltip',
extraCssText: 'box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);max-width: 300px !important'
},
xAxis: {
type: 'time',
show: false
},
yAxis: {
type: 'value',
show: false
},
animation: false,
grid: {
left: 0,
bottom: 2,
top: 5,
right: 0
},
color: chartColor,
legend: {
show: false
},
series: [
{
type: 'line',
legendHoverLink: false,
itemStyle: {
normal: {
lineStyle: {
width: 2
}
}
},
data: [],
showSymbol: false
}
]
}

View File

@@ -0,0 +1,93 @@
import unitConvert from '@/utils/unit-convert'
import { chartColor } from '@/views/charts/charts/chart-options'
import { tooLongFormatter } from '../tools'
export const pieWithTable = {
tooltip: {
appendToBody: true
},
color: chartColor,
animation: false,
legend: {
orient: 'vertical',
type: 'plain',
left: '60%',
top: 'middle',
icon: 'circle',
itemWidth: 10, // 设置宽度
itemHeight: 10, // 设置高度
itemGap: 20,
formatter: tooLongFormatter,
tooltip: {
show: true
}
},
series: [
{
type: 'pie',
selectedMode: 'single',
radius: ['42%', '65%'],
center: ['30%', '50%'],
data: [],
label: {
formatter: '{d}%'
},
tooltip: {
formatter: function (param, index, callback) {
return `${param.name}: ${unitConvert(param.value, param.data.unitType).join(' ')}`
}
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
export const ipHostedDomain = {
color: chartColor,
animation: false,
tooltip: {
show: true
},
legend: {
orient: 'vertical',
type: 'plain',
right: '8%',
top: 'middle',
icon: 'circle',
itemWidth: 10, // 设置宽度
itemHeight: 10, // 设置高度
itemGap: 20,
tooltip: {
show: true
}
},
series: [
{
type: 'pie',
selectedMode: 'single',
radius: ['42%', '65%'],
center: ['36%', '50%'],
data: [],
label: {
formatter: '{d}%'
},
tooltip: {
formatter: function (param, index, callback) {
return `${param.name}: ${unitConvert(param.value, param.data.unitType).join(' ')}`
}
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}

View File

@@ -0,0 +1,43 @@
export const sankey = {
tooltip: {
trigger: 'item',
triggerOn: 'mousemove'
},
series: [
{
type: 'sankey',
data: [],
links: [],
right: '5%',
top: 50,
bottom: 100,
levels: [
{
depth: 0,
itemStyle: {
color: '#47D49C'
},
lineStyle: {
color: '#999'
}
}, {
depth: 1,
itemStyle: {
color: '#A69BF5'
},
lineStyle: {
color: '#999'
}
}, {
depth: 2,
itemStyle: {
color: '#73A0FA'
},
lineStyle: {
color: '#999'
}
}
]
}
]
}

View File

@@ -0,0 +1,337 @@
import unitConvert from '@/utils/unit-convert'
import { format } from 'echarts'
import _ from 'lodash'
import { line, lineWithStatistics, lineStack, singleValueLine } from './options/line'
import { ipOpenPortBar, timeBar, categoryBar } from './options/bar'
import { pieWithTable, ipHostedDomain } from './options/pie'
import { relationShip } from './options/graph'
import { sankey } from './options/sankey'
export const chartColor = ['#5370C6', '#90CC74', '#FAC858', '#EE6666',
'#73BFDE', '#3BA172', '#FC8452', '#9960B4',
'#E97CCC', '#FEA69E', '#0F8AB2', '#57CBAC',
'#5888BC', '#63B6AC', '#EDC6B2', '#D5746B']
export const chartBarColor = ['#0F8AB2', '#57CBAC']
export function getChartColor (index) {
return chartColor[index % chartColor.length]
}
export function getCharBartColor (index) {
return chartBarColor[index % chartBarColor.length]
}
const typeOptionMappings = [
{ value: 11, option: line }, // 常规折线图
{ value: 12, option: lineWithStatistics }, // 带统计表格的折线图
{ value: 13, option: lineStack }, // 折线堆叠图
{ value: 22, option: ipOpenPortBar }, // ip详情--开放端口的柱状图
{ value: 23, option: timeBar }, // 矿机所属单位
{ value: 24, option: categoryBar }, // 挖矿事件统计
{ value: 31, option: pieWithTable }, // 常规折线图
{ value: 33, option: ipHostedDomain }, // ip详情--托管域名
{ value: 34, option: ipHostedDomain }, // app详情--相关域名
{ value: 42, option: relationShip }, // 关系图
{ value: 43, option: sankey }, // 桑基图
{ value: 52, option: singleValueLine } // 单值图中的折线图
]
export function getOption (type) {
const mapping = typeOptionMappings.find(m => m.value === type)
return mapping && mapping.option ? _.cloneDeep(mapping.option) : null
}
/* 柱状图:挖矿事件统计(time类型柱状图) */
export function isEchartsTimeBar (type) {
return type === 23
}
/* 柱状图:矿机所属单位(category类型柱状图) */
export function isEchartsCategoryBar (type) {
return type === 24
}
/* 饼图柱状图等 */
export function isEcharts (type) {
return type >= 11 && type <= 50
}
/* 地图 */
export function isMap (type) {
return type >= 1 && type <= 10
}
/* 连线地图 */
export function isMapLine (type) {
return type === 1
}
/* 色块地图 */
export function isMapBlock (type) {
return type === 2
}
/* 带统计的折线图 */
export function isEchartsWithStatistics (type) {
return type === 12
}
/* 关系图 */
export function isRelationShip (type) {
return type === 42
}
/* 桑基图 */
export function isSankey (type) {
return type === 43
}
/* 单值 */
export function isSingleValue (type) {
return type >= 51 && type <= 60
}
/* 带折线图的单值 */
export function isSingleValueWithEcharts (type) {
return type === 52
}
/* 带折线图的单值 */
export function isSingleValueWithEchartsTemp (type) {
return type === 55
}
/* 带Table的饼图 */
export function isEchartsWithTable (type) {
return type === 31
}
/* table */
export function isTable (type) {
return type >= 61 && type <= 70
}
/* table */
export function isActiveIpTable (type) {
return type === 63
}
/* title */
export function isTitle (type) {
return type === 93
}
/* tabs */
export function isTabs (type) {
return type === 91
}
/* IP实体基本信息 */
export function isIpBasicInfo (type) {
return type === 4
}
/* IP实体开放端口 */
export function isIpOpenPort (type) {
return type === 22
}
/* IP实体托管域名 */
export function isIpHostedDomain (type) {
return type === 33
}
/* APP实体相关域名 */
export function isAppRelatedDomain (type) {
return type === 34
}
/* APP实体基本信息 */
export function isAppBasicInfo (type) {
return type === 82
}
/* DOMAIN实体Whois */
export function isDomainWhois (type) {
return type === 83
}
/* DOMAIN实体DNS记录 */
export function isDomainDnsRecord (type) {
return type === 84
}
/* 近期挖矿事件 */
export function isCryptocurrencyEventList (type) {
return type === 85
}
/* 组 */
export function isGroup (type) {
return type === 94
}
/* 实体详情块 */
export function isBlock (type) {
return type === 95
}
/* 根据type获取图表分类 */
const typeCategory = {
MAP: 'map',
TABLE: 'table',
ECHARTS: 'echarts',
TITLE: 'title',
SINGLE: 'singleValue',
TABS: 'tabs'
}
export function getTypeCategory (type) {
if (isMap(type)) {
return typeCategory.MAP
} else if (isEcharts(type)) {
return typeCategory.ECHARTS
} else if (isTable(type)) {
return typeCategory.TABLE
} else if (isSingleValue(type)) {
return typeCategory.SINGLE
} else if (isTitle(type)) {
return typeCategory.TITLE
} else if (isTabs(type)) {
return typeCategory.TABS
}
}
/* 根据type获取布局 */
export const layoutConstant = {
HEADER: 'header',
FOOTER: 'footer'
}
export function getLayout (type) {
const layout = []
if (!isSingleValue(type) && !isTitle(type)) {
layout.push(layoutConstant.HEADER)
}
if (type === 12 || type === 31) {
layout.push(layoutConstant.FOOTER)
}
return layout
}
export function getGroupHeight (arr) {
if (arr.length) {
let lastItem = []
let maxY = arr[0].y
arr.forEach((children, index) => {
if (maxY === children.y) {
lastItem.push(children)
} else if (maxY < children.y) {
maxY = children.y
lastItem = [children]
}
})
let maxHeight = 0
lastItem.forEach(last => {
if (maxHeight < last.height) {
maxHeight = last.height
}
})
if (maxY < 0) {
maxY = 0
}
return maxHeight + maxY
} else {
return 1
}
}
export function getLayoutPosition (arr) {
if (arr.length) {
let lastItem = []
let maxY = 0
arr.forEach((children, index) => {
if (maxY === children.y) {
lastItem.push(children)
} else if (maxY < children.y) {
maxY = children.y
lastItem = [children]
}
})
let maxX = 0
lastItem.forEach(last => {
if (maxX < last.x + last.span) {
maxX = last.x + last.span
}
})
return {
x: maxX,
y: maxY
}
} else {
return {
x: 0,
y: 0
}
}
}
export function axisFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
const tData = item.data[0]
if (i === 0) {
str += '<div style="margin-bottom: 5px">'
str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss')
str += '</div>'
}
str += '<div class="cn-chart-tooltip-box">'
str += item.marker
str += `<span class="cn-chart-tooltip-content">
${item.seriesName}
</span>`
str += `<span class="cn-chart-tooltip-value">
${unitConvert(item.data[1], item.data[2]).join(' ')}
</span>`
str += '</div>'
})
str += '</div>'
return str
}
export function tooLongFormatter (name) {
return format.truncateText(name, 110, '12')
}
export function timeHorizontalFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
const tData = item.data[1]
if (i === 0) {
str += '<div style="margin-bottom: 5px">'
str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss')
str += '</div>'
}
str += '<div class="cn-chart-tooltip-box">'
str += item.marker
str += `<span class="cn-chart-tooltip-content">
${item.seriesName}
</span>`
str += `<span class="cn-chart-tooltip-value">
${unitConvert(item.data[0], item.data[2]).join(' ')}
</span>`
str += '</div>'
})
str += '</div>'
return str
}
export function categoryHorizontalFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
str += '<div class="cn-chart-tooltip-box">'
str += item.data[1] + ': ' + item.data[0]
str += '</div>'
})
str += '</div>'
return str
}
export function categoryVerticalFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
str += '<div class="cn-chart-tooltip-box">'
str += item.data[0] + ': ' + item.data[1]
str += '</div>'
})
str += '</div>'
return str
}
export function timeVerticalFormatter (params) {
let str = '<div>'
params.forEach((item, i) => {
const tData = item.data[0]
if (i === 0) {
str += '<div style="margin-bottom: 5px">'
str += window.$dayJs.tz(tData).format('YYYY-MM-DD HH:mm:ss')
str += '</div>'
}
str += '<div class="cn-chart-tooltip-box">'
str += item.marker
str += `<span class="cn-chart-tooltip-content">
${item.seriesName}
</span>`
str += `<span class="cn-chart-tooltip-value">
${unitConvert(item.data[1], item.data[2]).join(' ')}
</span>`
str += '</div>'
})
str += '</div>'
return str
}

View File

@@ -53,7 +53,7 @@
</template>
<script>
import cnDataList from '@/components/table/CnDataList'
import dataListMixin from '@/mixins/dataList'
import dataListMixin from '@/mixins/data-list'
import chartTable from '@/components/table/settings/ChartTable'
import chartBox from '@/components/rightBox/settings/ChartBox'
import { api } from '@/utils/api'

View File

@@ -78,7 +78,7 @@
import cnDataList from '@/components/table/CnDataList'
import galaxyProxyBox from '@/components/rightBox/settings/GalaxyProxyBox'
import galaxyProxyTable from '@/components/table/settings/GalaxyProxyTable'
import dataListMixin from '@/mixins/dataList'
import dataListMixin from '@/mixins/data-list'
import { api } from '@/utils/api'
import { get, put } from '@/utils/http'
import TopToolMoreOptions from '@/components/common/popBox/TopToolMoreOptions'

View File

@@ -55,7 +55,7 @@
</template>
<script>
import cnDataList from '@/components/table/CnDataList'
import dataListMixin from '@/mixins/dataList'
import dataListMixin from '@/mixins/data-list'
import i18nTable from '@/components/table/settings/I18nTable'
import i18nBox from '@/components/rightBox/settings/I18nBox'
import { put } from '@/utils/http'

View File

@@ -33,7 +33,7 @@
</template>
<script>
import cnDataList from '@/components/table/CnDataList'
import dataListMixin from '@/mixins/dataList'
import dataListMixin from '@/mixins/data-list'
import operationLogTable from '@/components/table/settings/OperationLogTable'
import { api } from '@/utils/api'

View File

@@ -48,7 +48,7 @@
</template>
<script>
import cnDataList from '@/components/table/CnDataList'
import dataListMixin from '@/mixins/dataList'
import dataListMixin from '@/mixins/data-list'
import rolesTable from '@/components/table/settings/RoleTable'
import roleBox from '@/components/rightBox/settings/RoleBox'
import { api } from '@/utils/api'

View File

@@ -53,7 +53,7 @@
</template>
<script>
import cnDataList from '@/components/table/CnDataList'
import dataListMixin from '@/mixins/dataList'
import dataListMixin from '@/mixins/data-list'
import userTable from '@/components/table/settings/UserTable'
import userBox from '@/components/rightBox/settings/UserBox'
import { api } from '@/utils/api'