web/multinode: bandwidth charts added
bandwidth/egress/ingress chart and api/vuex connection Change-Id: I16ba2bb82854a1d198384b3b8e6ffc4e58d8bb91
This commit is contained in:
parent
cc5de4288b
commit
cece8e4110
36
web/multinode/package-lock.json
generated
36
web/multinode/package-lock.json
generated
@ -4234,6 +4234,32 @@
|
||||
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
|
||||
"dev": true
|
||||
},
|
||||
"chart.js": {
|
||||
"version": "2.9.3",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz",
|
||||
"integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==",
|
||||
"requires": {
|
||||
"chartjs-color": "^2.1.0",
|
||||
"moment": "^2.10.2"
|
||||
}
|
||||
},
|
||||
"chartjs-color": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz",
|
||||
"integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==",
|
||||
"requires": {
|
||||
"chartjs-color-string": "^0.6.0",
|
||||
"color-convert": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"chartjs-color-string": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
|
||||
"integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
|
||||
"requires": {
|
||||
"color-name": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"check-types": {
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
|
||||
@ -10471,6 +10497,11 @@
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
|
||||
},
|
||||
"move-concurrently": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||
@ -16003,6 +16034,11 @@
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
|
||||
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
|
||||
},
|
||||
"vue-chartjs": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-3.5.0.tgz",
|
||||
"integrity": "sha512-yWNhG3B6g6lvYqNInP0WaDWNZG/SNb6XnltkjR0wYC5pmLm6jvdiotj8er7Mui8qkJGfLZe6ULjrZdHWjegAUg=="
|
||||
},
|
||||
"vue-class-component": {
|
||||
"version": "7.2.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.6.tgz",
|
||||
|
@ -9,7 +9,9 @@
|
||||
"test": "vue-cli-service test:unit"
|
||||
},
|
||||
"dependencies": {
|
||||
"chart.js": "2.9.3",
|
||||
"vue": "2.6.11",
|
||||
"vue-chartjs": "3.5.0",
|
||||
"vue-class-component": "7.2.6",
|
||||
"vue-clipboard2": "0.3.1",
|
||||
"vue-jest": "3.0.5",
|
||||
|
207
web/multinode/src/app/components/bandwidth/BandwidthChart.vue
Normal file
207
web/multinode/src/app/components/bandwidth/BandwidthChart.vue
Normal file
@ -0,0 +1,207 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="chart">
|
||||
<p class="bandwidth-chart__data-dimension">{{ chartDataDimension }}</p>
|
||||
<VChart
|
||||
id="bandwidth-chart"
|
||||
:chart-data="chartData"
|
||||
:width="chartWidth"
|
||||
:height="chartHeight"
|
||||
:tooltip-constructor="bandwidthTooltip"
|
||||
:key="chartKey"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component } from 'vue-property-decorator';
|
||||
|
||||
import BaseChart from '@/app/components/common/BaseChart.vue';
|
||||
|
||||
import { ChartData, Tooltip, TooltipParams } from '@/app/types/chart';
|
||||
import { Chart as ChartUtils } from '@/app/utils/chart';
|
||||
import { BandwidthRollup } from '@/bandwidth';
|
||||
import { Size } from '@/private/memory/size';
|
||||
|
||||
/**
|
||||
* stores bandwidth data for bandwidth chart's tooltip
|
||||
*/
|
||||
class BandwidthTooltip {
|
||||
public normalEgress: string;
|
||||
public normalIngress: string;
|
||||
public repairIngress: string;
|
||||
public repairEgress: string;
|
||||
public auditEgress: string;
|
||||
public date: string;
|
||||
|
||||
public constructor(bandwidth: BandwidthRollup) {
|
||||
this.normalEgress = Size.toBase10String(bandwidth.egress.usage);
|
||||
this.normalIngress = Size.toBase10String(bandwidth.ingress.usage);
|
||||
this.repairIngress = Size.toBase10String(bandwidth.ingress.repair);
|
||||
this.repairEgress = Size.toBase10String(bandwidth.egress.repair);
|
||||
this.auditEgress = Size.toBase10String(bandwidth.egress.audit);
|
||||
this.date = bandwidth.intervalStart.toUTCString().slice(0, 16);
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class BandwidthChart extends BaseChart {
|
||||
private get allBandwidth(): BandwidthRollup[] {
|
||||
return ChartUtils.populateEmptyBandwidth(this.$store.state.bandwidth.traffic.bandwidthDaily);
|
||||
}
|
||||
|
||||
public get chartDataDimension(): string {
|
||||
if (!this.$store.state.bandwidth.traffic.bandwidthDaily.length) {
|
||||
return 'Bytes';
|
||||
}
|
||||
|
||||
return ChartUtils.getChartDataDimension(this.allBandwidth.map((elem) => {
|
||||
return elem.egress.usage + elem.egress.repair + elem.egress.audit
|
||||
+ elem.ingress.repair + elem.ingress.usage;
|
||||
}));
|
||||
}
|
||||
|
||||
public get chartData(): ChartData {
|
||||
let data: number[] = [0];
|
||||
const daysCount = ChartUtils.daysDisplayedOnChart();
|
||||
const chartBackgroundColor = '#F2F6FC';
|
||||
const chartBorderColor = '#1F49A3';
|
||||
const chartBorderWidth = 1;
|
||||
|
||||
if (this.allBandwidth.length) {
|
||||
data = ChartUtils.normalizeChartData(this.allBandwidth.map(elem => {
|
||||
return elem.egress.usage + elem.egress.repair + elem.egress.audit
|
||||
+ elem.ingress.repair + elem.ingress.usage;
|
||||
}));
|
||||
}
|
||||
|
||||
return new ChartData(daysCount, chartBackgroundColor, chartBorderColor, chartBorderWidth, data);
|
||||
}
|
||||
|
||||
public bandwidthTooltip(tooltipModel: any): void {
|
||||
const tooltipParams = new TooltipParams(tooltipModel, 'bandwidth-chart', 'bandwidth-tooltip',
|
||||
'bandwidth-tooltip-point', this.tooltipMarkUp(tooltipModel),
|
||||
285, 125, 6, 4, `#1f49a3`);
|
||||
|
||||
Tooltip.custom(tooltipParams);
|
||||
}
|
||||
|
||||
private tooltipMarkUp(tooltipModel: any): string {
|
||||
if (!tooltipModel.dataPoints) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const dataIndex = tooltipModel.dataPoints[0].index;
|
||||
const dataPoint = new BandwidthTooltip(this.allBandwidth[dataIndex]);
|
||||
|
||||
return `<div class='tooltip-header'>
|
||||
<p>EGRESS</p>
|
||||
<p class='tooltip-header__ingress'>INGRESS</p>
|
||||
</div>
|
||||
<div class='tooltip-body'>
|
||||
<div class='tooltip-body__info'>
|
||||
<p>USAGE</p>
|
||||
<p class='tooltip-body__info__egress-value'><b class="tooltip-bold-text">${dataPoint.normalEgress}</b></p>
|
||||
<p class='tooltip-body__info__ingress-value'><b class="tooltip-bold-text">${dataPoint.normalIngress}</b></p>
|
||||
</div>
|
||||
<div class='tooltip-body__info'>
|
||||
<p>REPAIR</p>
|
||||
<p class='tooltip-body__info__egress-value'><b class="tooltip-bold-text">${dataPoint.repairEgress}</b></p>
|
||||
<p class='tooltip-body__info__ingress-value'><b class="tooltip-bold-text">${dataPoint.repairIngress}</b></p>
|
||||
</div>
|
||||
<div class='tooltip-body__info'>
|
||||
<p>AUDIT</p>
|
||||
<p class='tooltip-body__info__egress-value'><b class="tooltip-bold-text">${dataPoint.auditEgress}</b></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class='tooltip-footer'>
|
||||
<p>${dataPoint.date}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bandwidth-chart {
|
||||
z-index: 102;
|
||||
|
||||
&__data-dimension {
|
||||
font-size: 13px;
|
||||
color: var(--c-title);
|
||||
margin: 0 0 5px 31px !important;
|
||||
font-family: 'font_medium', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
#bandwidth-tooltip {
|
||||
background: white;
|
||||
border: 1px solid var(--c-gray--light);
|
||||
min-width: 250px;
|
||||
min-height: 230px;
|
||||
font-size: 12px;
|
||||
border-radius: 14px;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
color: var(--c-title);
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#bandwidth-tooltip-point {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.tooltip-header {
|
||||
display: flex;
|
||||
padding: 10px 0 0 92px;
|
||||
line-height: 40px;
|
||||
|
||||
&__ingress {
|
||||
margin-left: 29px;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip-body {
|
||||
margin: 8px;
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
border-radius: 12px;
|
||||
padding: 14px 17px 14px 14px;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
position: relative;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
|
||||
.tooltip-bold-text {
|
||||
color: #1f49a3;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__egress-value {
|
||||
position: absolute;
|
||||
left: 83px;
|
||||
}
|
||||
|
||||
&__ingress-value {
|
||||
position: absolute;
|
||||
left: 158px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip-footer {
|
||||
font-size: 12px;
|
||||
width: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 0 16px 0;
|
||||
color: var(--c-title);
|
||||
}
|
||||
</style>
|
168
web/multinode/src/app/components/bandwidth/EgressChart.vue
Normal file
168
web/multinode/src/app/components/bandwidth/EgressChart.vue
Normal file
@ -0,0 +1,168 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="chart">
|
||||
<p class="egress-chart__data-dimension">{{ chartDataDimension }}</p>
|
||||
<VChart
|
||||
id="egress-chart"
|
||||
:chart-data="chartData"
|
||||
:width="chartWidth"
|
||||
:height="chartHeight"
|
||||
:tooltip-constructor="egressTooltip"
|
||||
:key="chartKey"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component } from 'vue-property-decorator';
|
||||
|
||||
import BaseChart from '@/app/components/common/BaseChart.vue';
|
||||
|
||||
import { ChartData, Tooltip, TooltipParams } from '@/app/types/chart';
|
||||
import { Chart as ChartUtils } from '@/app/utils/chart';
|
||||
import { BandwidthRollup } from '@/bandwidth';
|
||||
import { Size } from '@/private/memory/size';
|
||||
|
||||
/**
|
||||
* stores egress data for egress bandwidth chart's tooltip
|
||||
*/
|
||||
class EgressTooltip {
|
||||
public normalEgress: string;
|
||||
public repairEgress: string;
|
||||
public auditEgress: string;
|
||||
public date: string;
|
||||
|
||||
public constructor(bandwidth: BandwidthRollup) {
|
||||
this.normalEgress = Size.toBase10String(bandwidth.egress.usage);
|
||||
this.repairEgress = Size.toBase10String(bandwidth.egress.repair);
|
||||
this.auditEgress = Size.toBase10String(bandwidth.egress.audit);
|
||||
this.date = bandwidth.intervalStart.toUTCString().slice(0, 16);
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class EgressChart extends BaseChart {
|
||||
private get allBandwidth(): BandwidthRollup[] {
|
||||
return ChartUtils.populateEmptyBandwidth(this.$store.state.bandwidth.traffic.bandwidthDaily);
|
||||
}
|
||||
|
||||
public get chartDataDimension(): string {
|
||||
if (!this.$store.state.bandwidth.traffic.bandwidthDaily.length) {
|
||||
return 'Bytes';
|
||||
}
|
||||
|
||||
return ChartUtils.getChartDataDimension(this.allBandwidth.map((elem) => {
|
||||
return elem.egress.audit + elem.egress.repair + elem.egress.usage;
|
||||
}));
|
||||
}
|
||||
|
||||
public get chartData(): ChartData {
|
||||
let data: number[] = [0];
|
||||
const daysCount = ChartUtils.daysDisplayedOnChart();
|
||||
const chartBackgroundColor = '#edf9f4';
|
||||
const chartBorderColor = '#48a77f';
|
||||
const chartBorderWidth = 1;
|
||||
|
||||
if (this.allBandwidth.length) {
|
||||
data = ChartUtils.normalizeChartData(this.allBandwidth.map(elem => elem.egress.audit + elem.egress.repair + elem.egress.usage));
|
||||
}
|
||||
|
||||
return new ChartData(daysCount, chartBackgroundColor, chartBorderColor, chartBorderWidth, data);
|
||||
}
|
||||
|
||||
public egressTooltip(tooltipModel): void {
|
||||
const tooltipParams = new TooltipParams(tooltipModel, 'egress-chart', 'egress-tooltip',
|
||||
'egress-tooltip-point', this.tooltipMarkUp(tooltipModel),
|
||||
235, 94, 6, 4, `#48a77f`);
|
||||
|
||||
Tooltip.custom(tooltipParams);
|
||||
}
|
||||
|
||||
private tooltipMarkUp(tooltipModel: any): string {
|
||||
if (!tooltipModel.dataPoints) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const dataIndex = tooltipModel.dataPoints[0].index;
|
||||
const dataPoint = new EgressTooltip(this.allBandwidth[dataIndex]);
|
||||
|
||||
return `<div class='egress-tooltip-body'>
|
||||
<div class='egress-tooltip-body__info'>
|
||||
<p>USAGE</p>
|
||||
<b class="egress-tooltip-bold-text">${dataPoint.normalEgress}</b>
|
||||
</div>
|
||||
<div class='egress-tooltip-body__info'>
|
||||
<p>REPAIR</p>
|
||||
<b class="egress-tooltip-bold-text">${dataPoint.repairEgress}</b>
|
||||
</div>
|
||||
<div class='egress-tooltip-body__info'>
|
||||
<p>AUDIT</p>
|
||||
<b class="egress-tooltip-bold-text">${dataPoint.auditEgress}</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class='egress-tooltip-footer'>
|
||||
<p>${dataPoint.date}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.egress-chart {
|
||||
z-index: 102;
|
||||
|
||||
&__data-dimension {
|
||||
font-size: 13px;
|
||||
color: var(--c-title);
|
||||
margin: 0 0 5px 31px !important;
|
||||
font-family: 'font_medium', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
#egress-tooltip {
|
||||
background: white;
|
||||
border: 1px solid var(--c-gray--light);
|
||||
min-width: 190px;
|
||||
min-height: 170px;
|
||||
font-size: 12px;
|
||||
border-radius: 14px;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
color: var(--c-title);
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.egress-tooltip-body {
|
||||
margin: 8px;
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14px;
|
||||
position: relative;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.egress-tooltip-bold-text {
|
||||
color: #2e5f46;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.egress-tooltip-footer {
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
width: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 0 16px 0;
|
||||
color: var(--c-title);
|
||||
font-family: 'font_bold', sans-serif;
|
||||
}
|
||||
</style>
|
166
web/multinode/src/app/components/bandwidth/IngressChart.vue
Normal file
166
web/multinode/src/app/components/bandwidth/IngressChart.vue
Normal file
@ -0,0 +1,166 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="chart">
|
||||
<p class="ingress-chart__data-dimension">{{ chartDataDimension }}</p>
|
||||
<VChart
|
||||
id="ingress-chart"
|
||||
:chart-data="chartData"
|
||||
:width="chartWidth"
|
||||
:height="chartHeight"
|
||||
:tooltip-constructor="ingressTooltip"
|
||||
:key="chartKey"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component } from 'vue-property-decorator';
|
||||
|
||||
import BaseChart from '@/app/components/common/BaseChart.vue';
|
||||
|
||||
import { ChartData, Tooltip, TooltipParams } from '@/app/types/chart';
|
||||
import { Chart as ChartUtils } from '@/app/utils/chart';
|
||||
import { BandwidthRollup } from '@/bandwidth';
|
||||
import { Size } from '@/private/memory/size';
|
||||
|
||||
/**
|
||||
* stores ingress data for ingress bandwidth chart's tooltip
|
||||
*/
|
||||
class IngressTooltip {
|
||||
public normalIngress: string;
|
||||
public repairIngress: string;
|
||||
public date: string;
|
||||
|
||||
public constructor(bandwidth: BandwidthRollup) {
|
||||
this.normalIngress = Size.toBase10String(bandwidth.ingress.usage);
|
||||
this.repairIngress = Size.toBase10String(bandwidth.ingress.repair);
|
||||
this.date = bandwidth.intervalStart.toUTCString().slice(0, 16);
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class IngressChart extends BaseChart {
|
||||
private get allBandwidth(): BandwidthRollup[] {
|
||||
return ChartUtils.populateEmptyBandwidth(this.$store.state.bandwidth.traffic.bandwidthDaily);
|
||||
}
|
||||
|
||||
public get chartDataDimension(): string {
|
||||
if (!this.$store.state.bandwidth.traffic.bandwidthDaily.length) {
|
||||
return 'Bytes';
|
||||
}
|
||||
|
||||
return ChartUtils.getChartDataDimension(this.allBandwidth.map((elem) => {
|
||||
return elem.ingress.repair + elem.ingress.usage;
|
||||
}));
|
||||
}
|
||||
|
||||
public get chartData(): ChartData {
|
||||
let data: number[] = [0];
|
||||
const daysCount = ChartUtils.daysDisplayedOnChart();
|
||||
const chartBackgroundColor = '#fff4df';
|
||||
const chartBorderColor = '#e1a128';
|
||||
const chartBorderWidth = 1;
|
||||
|
||||
if (this.allBandwidth.length) {
|
||||
data = ChartUtils.normalizeChartData(this.allBandwidth.map(elem => elem.ingress.repair + elem.ingress.usage));
|
||||
}
|
||||
|
||||
return new ChartData(daysCount, chartBackgroundColor, chartBorderColor, chartBorderWidth, data);
|
||||
}
|
||||
|
||||
public ingressTooltip(tooltipModel): void {
|
||||
const tooltipParams = new TooltipParams(tooltipModel, 'ingress-chart', 'ingress-tooltip',
|
||||
'ingress-tooltip-point', this.tooltipMarkUp(tooltipModel),
|
||||
185, 94, 6, 4, `#e1a128`);
|
||||
|
||||
Tooltip.custom(tooltipParams);
|
||||
}
|
||||
|
||||
private tooltipMarkUp(tooltipModel: any): string {
|
||||
if (!tooltipModel.dataPoints) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const dataIndex = tooltipModel.dataPoints[0].index;
|
||||
const dataPoint = new IngressTooltip(this.allBandwidth[dataIndex]);
|
||||
|
||||
return `<div class='ingress-tooltip-body'>
|
||||
<div class='ingress-tooltip-body__info'>
|
||||
<p>USAGE</p>
|
||||
<b class="ingress-tooltip-bold-text">${dataPoint.normalIngress}</b>
|
||||
</div>
|
||||
<div class='ingress-tooltip-body__info'>
|
||||
<p>REPAIR</p>
|
||||
<b class="ingress-tooltip-bold-text">${dataPoint.repairIngress}</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class='ingress-tooltip-footer'>
|
||||
<p>${dataPoint.date}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ingress-chart {
|
||||
z-index: 102;
|
||||
|
||||
&__data-dimension {
|
||||
font-size: 13px;
|
||||
color: var(--c-title);
|
||||
margin: 0 0 5px 31px !important;
|
||||
font-family: 'font_medium', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
#ingress-tooltip {
|
||||
background: white;
|
||||
border: 1px solid var(--c-gray--light);
|
||||
min-width: 190px;
|
||||
min-height: 170px;
|
||||
font-size: 12px;
|
||||
border-radius: 14px;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
color: var(--c-title);
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#ingress-tooltip-point {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.ingress-tooltip-body {
|
||||
margin: 8px;
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14px;
|
||||
position: relative;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.ingress-tooltip-bold-text {
|
||||
color: #6e4f15;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ingress-tooltip-footer {
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
width: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 0 16px 0;
|
||||
color: var(--c-title);
|
||||
font-family: 'font_bold', sans-serif;
|
||||
}
|
||||
</style>
|
63
web/multinode/src/app/components/common/BaseChart.vue
Normal file
63
web/multinode/src/app/components/common/BaseChart.vue
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||
|
||||
import VChart from '@/app/components/common/VChart.vue';
|
||||
|
||||
@Component ({
|
||||
components: {
|
||||
VChart,
|
||||
},
|
||||
})
|
||||
export default class BaseChart extends Vue {
|
||||
@Prop({default: 0})
|
||||
public width: number;
|
||||
@Prop({default: 0})
|
||||
public height: number;
|
||||
@Prop({default: false})
|
||||
public isDarkMode: boolean;
|
||||
|
||||
public chartWidth: number = this.width;
|
||||
public chartHeight: number = this.height;
|
||||
/**
|
||||
* Used for chart re rendering.
|
||||
*/
|
||||
public chartKey: number = 0;
|
||||
|
||||
public $refs: {
|
||||
chartContainer: HTMLElement;
|
||||
};
|
||||
|
||||
@Watch('width')
|
||||
@Watch('isDarkMode')
|
||||
public rebuildChart(): void {
|
||||
this.chartHeight = this.height;
|
||||
this.chartWidth = this.width;
|
||||
this.chartKey += 1;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&__data-dimension {
|
||||
font-size: 13px;
|
||||
color: #586c86;
|
||||
margin: 0 0 5px 31px !important;
|
||||
font-family: 'font_medium', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
|
||||
.chart-container {
|
||||
width: calc(100% - 36px);
|
||||
padding: 24px 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
148
web/multinode/src/app/components/common/VChart.vue
Normal file
148
web/multinode/src/app/components/common/VChart.vue
Normal file
@ -0,0 +1,148 @@
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<script lang="ts">
|
||||
import * as VueChart from 'vue-chartjs';
|
||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||
|
||||
import { ChartData } from '@/app/types/chart';
|
||||
|
||||
class DayShowingConditions {
|
||||
public readonly day: string;
|
||||
public readonly daysArray: string[];
|
||||
|
||||
public constructor(day: string, daysArray: string[]) {
|
||||
this.day = day;
|
||||
this.daysArray = daysArray;
|
||||
}
|
||||
|
||||
public countMiddleDateValue(): number {
|
||||
return this.daysArray.length / 2;
|
||||
}
|
||||
|
||||
public isDayFirstOrLast(): boolean {
|
||||
return this.day === this.daysArray[0] || this.day === this.daysArray[this.daysArray.length - 1];
|
||||
}
|
||||
|
||||
public isDayAfterEighthDayOfTheMonth(): boolean {
|
||||
return this.daysArray.length > 8 && this.daysArray.length <= 31;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
extends: VueChart.Line,
|
||||
})
|
||||
export default class VChart extends Vue {
|
||||
@Prop({default: '$'})
|
||||
private readonly currency: string;
|
||||
@Prop({default: () => { console.error('Tooltip constructor is undefined'); } })
|
||||
private tooltipConstructor: (tooltipModel) => void;
|
||||
@Prop({default: {}})
|
||||
private readonly chartData: ChartData;
|
||||
|
||||
@Watch('chartData')
|
||||
private onDataChange(news: object, old: object) {
|
||||
/**
|
||||
* renderChart method is inherited from BaseChart which is extended by VChart.Line
|
||||
*/
|
||||
(this as any).renderChart(this.chartData, this.chartOptions);
|
||||
}
|
||||
|
||||
public mounted(): void {
|
||||
/**
|
||||
* renderChart method is inherited from BaseChart which is extended by VChart.Line
|
||||
*/
|
||||
(this as any).renderChart(this.chartData, this.chartOptions);
|
||||
}
|
||||
|
||||
public get chartOptions(): object {
|
||||
const filterCallback = this.filterDaysDisplayed;
|
||||
|
||||
return {
|
||||
responsive: false,
|
||||
maintainAspectRatios: false,
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
radius: 0,
|
||||
hoverRadius: 0,
|
||||
hitRadius: 500,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
display: true,
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
color: '#586C86',
|
||||
},
|
||||
gridLines: {
|
||||
borderDash: [2, 5],
|
||||
drawBorder: false,
|
||||
},
|
||||
}],
|
||||
xAxes: [{
|
||||
display: true,
|
||||
ticks: {
|
||||
fontFamily: 'font_regular',
|
||||
autoSkip: false,
|
||||
maxRotation: 0,
|
||||
minRotation: 0,
|
||||
callback: filterCallback,
|
||||
},
|
||||
gridLines: {
|
||||
display: false,
|
||||
},
|
||||
}],
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
left: 25,
|
||||
},
|
||||
},
|
||||
tooltips: {
|
||||
enabled: false,
|
||||
|
||||
custom: ((tooltipModel) => {
|
||||
this.tooltipConstructor(tooltipModel);
|
||||
}),
|
||||
|
||||
labels: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private filterDaysDisplayed(day: string, dayIndex: string, labelArray: string[]): string | undefined {
|
||||
const eighthDayOfTheMonth = 8;
|
||||
const isBeforeEighthDayOfTheMonth = labelArray.length <= eighthDayOfTheMonth;
|
||||
const dayShowingConditions = new DayShowingConditions(day, labelArray);
|
||||
|
||||
if (isBeforeEighthDayOfTheMonth || this.areDaysShownOnEvenDaysAmount(dayShowingConditions)
|
||||
|| this.areDaysShownOnNotEvenDaysAmount(dayShowingConditions)) {
|
||||
return day;
|
||||
}
|
||||
}
|
||||
|
||||
private areDaysShownOnEvenDaysAmount(dayShowingConditions: DayShowingConditions): boolean {
|
||||
const isDaysAmountEven = dayShowingConditions.daysArray.length % 2 === 0;
|
||||
const isDateValueInMiddleInEvenAmount = dayShowingConditions.day ===
|
||||
dayShowingConditions.daysArray[dayShowingConditions.countMiddleDateValue() - 1];
|
||||
|
||||
return dayShowingConditions.isDayFirstOrLast() || (isDaysAmountEven
|
||||
&& dayShowingConditions.isDayAfterEighthDayOfTheMonth() && isDateValueInMiddleInEvenAmount);
|
||||
}
|
||||
|
||||
private areDaysShownOnNotEvenDaysAmount(dayShowingConditions: DayShowingConditions): boolean {
|
||||
const isDaysAmountNotEven = dayShowingConditions.daysArray.length % 2 !== 0;
|
||||
const isDateValueInMiddleInNotEvenAmount = dayShowingConditions.day
|
||||
=== dayShowingConditions.daysArray[Math.floor(dayShowingConditions.countMiddleDateValue())];
|
||||
|
||||
return dayShowingConditions.isDayFirstOrLast() || (isDaysAmountNotEven
|
||||
&& dayShowingConditions.isDayAfterEighthDayOfTheMonth() && isDateValueInMiddleInNotEvenAmount);
|
||||
}
|
||||
}
|
||||
</script>
|
@ -4,12 +4,15 @@
|
||||
import Vue from 'vue';
|
||||
import Vuex, { ModuleTree, Store, StoreOptions } from 'vuex';
|
||||
|
||||
import { BandwidthClient } from '@/api/bandwidth';
|
||||
import { NodesClient } from '@/api/nodes';
|
||||
import { Operators as OperatorsClient } from '@/api/operators';
|
||||
import { PayoutsClient } from '@/api/payouts';
|
||||
import { BandwidthModule, BandwidthState } from '@/app/store/bandwidth';
|
||||
import { NodesModule, NodesState } from '@/app/store/nodes';
|
||||
import { OperatorsModule, OperatorsState } from '@/app/store/operators';
|
||||
import { PayoutsModule, PayoutsState } from '@/app/store/payouts';
|
||||
import { Bandwidth } from '@/bandwidth/service';
|
||||
import { Nodes } from '@/nodes/service';
|
||||
import { Operators } from '@/operators';
|
||||
import { Payouts } from '@/payouts/service';
|
||||
@ -23,6 +26,7 @@ export class RootState {
|
||||
nodes: NodesState;
|
||||
payouts: PayoutsState;
|
||||
operators: OperatorsState;
|
||||
bandwidth: BandwidthState;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,16 +37,23 @@ export class MultinodeStoreOptions implements StoreOptions<RootState> {
|
||||
public readonly state: RootState;
|
||||
public readonly modules: ModuleTree<RootState>;
|
||||
|
||||
public constructor(nodes: NodesModule, payouts: PayoutsModule, operators: OperatorsModule) {
|
||||
public constructor(
|
||||
nodes: NodesModule,
|
||||
payouts: PayoutsModule,
|
||||
operators: OperatorsModule,
|
||||
bandwidth: BandwidthModule,
|
||||
) {
|
||||
this.strict = true;
|
||||
this.state = {
|
||||
nodes: nodes.state,
|
||||
payouts: payouts.state,
|
||||
bandwidth: bandwidth.state,
|
||||
operators: operators.state,
|
||||
};
|
||||
this.modules = {
|
||||
nodes,
|
||||
payouts,
|
||||
bandwidth,
|
||||
operators,
|
||||
};
|
||||
}
|
||||
@ -53,15 +64,18 @@ const nodesClient: NodesClient = new NodesClient();
|
||||
const nodesService: Nodes = new Nodes(nodesClient);
|
||||
const payoutsClient: PayoutsClient = new PayoutsClient();
|
||||
const payoutsService: Payouts = new Payouts(payoutsClient);
|
||||
const bandwidthClient = new BandwidthClient();
|
||||
const bandwidthService = new Bandwidth(bandwidthClient);
|
||||
const operatorsClient: OperatorsClient = new OperatorsClient();
|
||||
const operatorsService: Operators = new Operators(operatorsClient);
|
||||
|
||||
// Modules
|
||||
const nodesModule: NodesModule = new NodesModule(nodesService);
|
||||
const payoutsModule: PayoutsModule = new PayoutsModule(payoutsService);
|
||||
const bandwidthModule: BandwidthModule = new BandwidthModule(bandwidthService);
|
||||
const operatorsModule: OperatorsModule = new OperatorsModule(operatorsService);
|
||||
|
||||
// Store
|
||||
export const store: Store<RootState> = new Vuex.Store<RootState>(
|
||||
new MultinodeStoreOptions(nodesModule, payoutsModule, operatorsModule),
|
||||
new MultinodeStoreOptions(nodesModule, payoutsModule, operatorsModule, bandwidthModule),
|
||||
);
|
||||
|
177
web/multinode/src/app/types/chart.ts
Normal file
177
web/multinode/src/app/types/chart.ts
Normal file
@ -0,0 +1,177 @@
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
/**
|
||||
* ChartData class holds info for ChartData entity.
|
||||
*/
|
||||
export class ChartData {
|
||||
public labels: string[];
|
||||
public datasets: DataSets[] = [];
|
||||
|
||||
public constructor(labels: string[], backgroundColor: string, borderColor: string, borderWidth: number, data: number[]) {
|
||||
this.labels = labels;
|
||||
|
||||
for (let i = 0; i < this.labels.length; i++) {
|
||||
this.datasets[i] = new DataSets(backgroundColor, borderColor, borderWidth, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DiskStatChartData class holds info for Disk Stat Chart.
|
||||
*/
|
||||
export class DiskStatChartData {
|
||||
public constructor(
|
||||
public datasets: DiskStatDataSet[] = [new DiskStatDataSet()],
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* DiskStatDataSet describes all required data for disk stat chart dataset.
|
||||
*/
|
||||
export class DiskStatDataSet {
|
||||
public constructor(
|
||||
public label: string = '',
|
||||
public backgroundColor: string[] = ['#D6D6D6', '#0059D0', '#8FA7C6'],
|
||||
public data: number[] = [],
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* DataSets class holds info for chart's DataSets entity.
|
||||
*/
|
||||
class DataSets {
|
||||
public backgroundColor: string;
|
||||
public borderColor: string;
|
||||
public borderWidth: number;
|
||||
public data: number[];
|
||||
|
||||
public constructor(backgroundColor: string, borderColor: string, borderWidth: number, data: number[]) {
|
||||
this.backgroundColor = backgroundColor;
|
||||
this.borderColor = borderColor;
|
||||
this.borderWidth = borderWidth;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* StylingConstants holds tooltip styling constants
|
||||
*/
|
||||
class StylingConstants {
|
||||
public static tooltipOpacity = '1';
|
||||
public static tooltipPosition = 'absolute';
|
||||
public static pointWidth = '10px';
|
||||
public static pointHeight = '10px';
|
||||
public static borderRadius = '20px';
|
||||
}
|
||||
|
||||
/**
|
||||
* Styling holds tooltip's styling configuration
|
||||
*/
|
||||
class Styling {
|
||||
public constructor(
|
||||
public tooltipModel: any,
|
||||
public element: HTMLElement,
|
||||
public topPosition: number,
|
||||
public leftPosition: number,
|
||||
public chartPosition: ClientRect,
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* TooltipParams holds tooltip's configuration
|
||||
*/
|
||||
export class TooltipParams {
|
||||
public constructor(
|
||||
public tooltipModel: any,
|
||||
public chartId: string,
|
||||
public tooltipId: string,
|
||||
public pointId: string,
|
||||
public markUp: string,
|
||||
public tooltipTop: number,
|
||||
public tooltipLeft: number,
|
||||
public pointTop: number,
|
||||
public pointLeft: number,
|
||||
public color: string,
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tooltip provides custom tooltip rendering
|
||||
*/
|
||||
export class Tooltip {
|
||||
public static custom(params: TooltipParams): void {
|
||||
const chart = document.getElementById(params.chartId);
|
||||
if (!chart) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tooltip: HTMLElement = Tooltip.createTooltip(params.tooltipId);
|
||||
const point: HTMLElement = Tooltip.createPoint(params.pointId);
|
||||
|
||||
if (!params.tooltipModel.opacity) {
|
||||
Tooltip.remove(tooltip, point);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.tooltipModel.body) {
|
||||
Tooltip.render(tooltip, params.markUp);
|
||||
}
|
||||
|
||||
const position = chart.getBoundingClientRect();
|
||||
|
||||
const tooltipStyling = new Styling(params.tooltipModel, tooltip, params.tooltipTop, params.tooltipLeft, position);
|
||||
Tooltip.elemStyling(tooltipStyling);
|
||||
|
||||
const pointStyling = new Styling(params.tooltipModel, point, params.pointTop, params.pointLeft, position);
|
||||
Tooltip.elemStyling(pointStyling);
|
||||
|
||||
Tooltip.pointStyling(point, params.color);
|
||||
}
|
||||
|
||||
private static createTooltip(id: string): HTMLElement {
|
||||
let tooltipEl = document.getElementById(id);
|
||||
if (!tooltipEl) {
|
||||
tooltipEl = document.createElement('div');
|
||||
tooltipEl.id = id;
|
||||
document.body.appendChild(tooltipEl);
|
||||
}
|
||||
|
||||
return tooltipEl;
|
||||
}
|
||||
|
||||
private static createPoint(id: string): HTMLElement {
|
||||
let tooltipPoint = document.getElementById(id);
|
||||
if (!tooltipPoint) {
|
||||
tooltipPoint = document.createElement('div');
|
||||
tooltipPoint.id = id;
|
||||
document.body.appendChild(tooltipPoint);
|
||||
}
|
||||
|
||||
return tooltipPoint;
|
||||
}
|
||||
|
||||
private static remove(tooltipEl: HTMLElement, tooltipPoint: HTMLElement) {
|
||||
document.body.removeChild(tooltipEl);
|
||||
document.body.removeChild(tooltipPoint);
|
||||
}
|
||||
|
||||
private static render(tooltip: HTMLElement, markUp: string) {
|
||||
tooltip.innerHTML = markUp;
|
||||
}
|
||||
|
||||
private static elemStyling(elemStyling: Styling) {
|
||||
elemStyling.element.style.opacity = StylingConstants.tooltipOpacity;
|
||||
elemStyling.element.style.position = StylingConstants.tooltipPosition;
|
||||
elemStyling.element.style.left = `${elemStyling.chartPosition.left + elemStyling.tooltipModel.caretX - elemStyling.leftPosition}px`;
|
||||
elemStyling.element.style.top = `${elemStyling.chartPosition.top + window.pageYOffset + elemStyling.tooltipModel.caretY - elemStyling.topPosition}px`;
|
||||
}
|
||||
|
||||
private static pointStyling(point: HTMLElement, color: string) {
|
||||
point.style.width = StylingConstants.pointWidth;
|
||||
point.style.height = StylingConstants.pointHeight;
|
||||
point.style.backgroundColor = color;
|
||||
point.style.borderRadius = StylingConstants.borderRadius;
|
||||
}
|
||||
}
|
185
web/multinode/src/app/utils/chart.ts
Normal file
185
web/multinode/src/app/utils/chart.ts
Normal file
@ -0,0 +1,185 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import { BandwidthRollup } from '@/bandwidth';
|
||||
import { SizeBreakpoints } from '@/private/memory/size';
|
||||
|
||||
const shortMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];
|
||||
|
||||
// TODO: move to diskspace package
|
||||
/**
|
||||
* Stamp is storage usage stamp for satellite at some point in time
|
||||
*/
|
||||
export class Stamp {
|
||||
public atRestTotal: number;
|
||||
public intervalStart: Date;
|
||||
|
||||
public constructor(atRestTotal: number = 0, intervalStart: Date = new Date()) {
|
||||
this.atRestTotal = atRestTotal;
|
||||
this.intervalStart = intervalStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new empty instance of stamp with defined date
|
||||
* @param date - holds specific date of the month
|
||||
* @returns Stamp - new empty instance of stamp with defined date
|
||||
*/
|
||||
public static emptyWithDate(date: number): Stamp {
|
||||
const now = new Date();
|
||||
now.setUTCDate(date);
|
||||
now.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
return new Stamp(0, now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to display correct and convenient data on chart.
|
||||
*/
|
||||
export class Chart {
|
||||
/**
|
||||
* Brings chart data to a more compact form.
|
||||
* @param data - holds array of chart data in numeric form
|
||||
* @returns data - numeric array of normalized data
|
||||
*/
|
||||
public static normalizeChartData(data: number[]): number[] {
|
||||
const maxBytes = Math.ceil(Math.max(...data));
|
||||
|
||||
let divider: number = SizeBreakpoints.PB;
|
||||
switch (true) {
|
||||
case maxBytes < SizeBreakpoints.MB:
|
||||
divider = SizeBreakpoints.KB;
|
||||
break;
|
||||
case maxBytes < SizeBreakpoints.GB:
|
||||
divider = SizeBreakpoints.MB;
|
||||
break;
|
||||
case maxBytes < SizeBreakpoints.TB:
|
||||
divider = SizeBreakpoints.GB;
|
||||
break;
|
||||
case maxBytes < SizeBreakpoints.PB:
|
||||
divider = SizeBreakpoints.TB;
|
||||
break;
|
||||
}
|
||||
|
||||
return data.map(elem => elem / divider);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets chart data dimension depending on data size.
|
||||
* @param data - holds array of chart data in numeric form
|
||||
* @returns dataDimension - string of data dimension
|
||||
*/
|
||||
public static getChartDataDimension(data: number[]): string {
|
||||
const maxBytes = Math.ceil(Math.max(...data));
|
||||
|
||||
let dataDimension: string;
|
||||
switch (true) {
|
||||
case maxBytes < SizeBreakpoints.MB:
|
||||
dataDimension = 'KB';
|
||||
break;
|
||||
case maxBytes < SizeBreakpoints.GB:
|
||||
dataDimension = 'MB';
|
||||
break;
|
||||
case maxBytes < SizeBreakpoints.TB:
|
||||
dataDimension = 'GB';
|
||||
break;
|
||||
case maxBytes < SizeBreakpoints.PB:
|
||||
dataDimension = 'TB';
|
||||
break;
|
||||
default:
|
||||
dataDimension = 'PB';
|
||||
}
|
||||
|
||||
return dataDimension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to display correct number of days on chart's labels.
|
||||
*
|
||||
* @returns daysDisplayed - array of days converted to a string by using the current or specified locale
|
||||
*/
|
||||
public static daysDisplayedOnChart(): string[] {
|
||||
const daysDisplayed = Array<string>(new Date().getUTCDate());
|
||||
const currentMonth = shortMonthNames[new Date().getUTCMonth()].toUpperCase();
|
||||
|
||||
for (let i = 0; i < daysDisplayed.length; i++) {
|
||||
daysDisplayed[i] = `${currentMonth} ${i + 1}`;
|
||||
}
|
||||
|
||||
if (daysDisplayed.length === 1) {
|
||||
daysDisplayed.unshift('0');
|
||||
}
|
||||
|
||||
return daysDisplayed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds missing bandwidth usage for bandwidth chart data for each day of month.
|
||||
* @param fetchedData - array of data that is spread over missing bandwidth usage for each day of the month
|
||||
* @returns bandwidthChartData - array of filled data
|
||||
*/
|
||||
public static populateEmptyBandwidth(fetchedData: BandwidthRollup[]): BandwidthRollup[] {
|
||||
const bandwidthChartData: BandwidthRollup[] = new Array(new Date().getUTCDate());
|
||||
const data: BandwidthRollup[] = fetchedData || [];
|
||||
|
||||
if (data.length === 0) {
|
||||
return bandwidthChartData;
|
||||
}
|
||||
|
||||
outer:
|
||||
for (let i = 0; i < bandwidthChartData.length; i++) {
|
||||
const date = i + 1;
|
||||
|
||||
for (let j = 0; j < data.length; j++) {
|
||||
if (data[j].intervalStart.getUTCDate() === date) {
|
||||
bandwidthChartData[i] = data[j];
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
|
||||
bandwidthChartData[i] = BandwidthRollup.emptyWithDate(date);
|
||||
}
|
||||
|
||||
if (bandwidthChartData.length === 1) {
|
||||
bandwidthChartData.unshift(BandwidthRollup.emptyWithDate(1));
|
||||
bandwidthChartData[0].intervalStart.setUTCHours(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
return bandwidthChartData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds missing stamps for storage chart data for each day of month.
|
||||
* @param fetchedData - array of data that is spread over missing stamps for each day of the month
|
||||
* @returns storageChartData - array of filled data
|
||||
*/
|
||||
public static populateEmptyStamps(fetchedData: Stamp[]): Stamp[] {
|
||||
const storageChartData: Stamp[] = new Array(new Date().getUTCDate());
|
||||
const data: Stamp[] = fetchedData || [];
|
||||
|
||||
if (data.length === 0) {
|
||||
return storageChartData;
|
||||
}
|
||||
|
||||
outer:
|
||||
for (let i = 0; i < storageChartData.length; i++) {
|
||||
const date = i + 1;
|
||||
|
||||
for (let j = 0; j < data.length; j++) {
|
||||
if (data[j].intervalStart.getUTCDate() === date) {
|
||||
storageChartData[i] = data[j];
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
|
||||
storageChartData[i] = Stamp.emptyWithDate(date);
|
||||
}
|
||||
|
||||
if (storageChartData.length === 1) {
|
||||
storageChartData.unshift(Stamp.emptyWithDate(1));
|
||||
storageChartData[0].intervalStart.setUTCHours(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
return storageChartData;
|
||||
}
|
||||
}
|
@ -8,25 +8,97 @@
|
||||
<node-selection-dropdown />
|
||||
<satellite-selection-dropdown />
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<div class="chart-container__title-area">
|
||||
<p class="chart-container__title-area__title">Bandwidth Used This Month</p>
|
||||
<div class="chart-container__title-area__buttons-area">
|
||||
<button
|
||||
name="Show Bandwidth Chart"
|
||||
class="chart-container__title-area__chart-choice-item"
|
||||
:class="{ 'active': (!isEgressChartShown && !isIngressChartShown) }"
|
||||
@click.stop="openBandwidthChart"
|
||||
>
|
||||
Bandwidth
|
||||
</button>
|
||||
<button
|
||||
name="Show Egress Chart"
|
||||
class="chart-container__title-area__chart-choice-item"
|
||||
:class="{ 'active': isEgressChartShown }"
|
||||
@click.stop="openEgressChart"
|
||||
>
|
||||
Egress
|
||||
</button>
|
||||
<button
|
||||
name="Show Ingress Chart"
|
||||
class="chart-container__title-area__chart-choice-item"
|
||||
:class="{ 'active': isIngressChartShown }"
|
||||
@click.stop="openIngressChart"
|
||||
>
|
||||
Ingress
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="chart-container__amount" v-if="isEgressChartShown"><b>{{ bandwidth.egressSummary | bytesToBase10String }}</b></p>
|
||||
<p class="chart-container__amount" v-else-if="isIngressChartShown"><b>{{ bandwidth.ingressSummary | bytesToBase10String }}</b></p>
|
||||
<p class="chart-container__amount" v-else><b>{{ bandwidth.bandwidthSummary | bytesToBase10String }}</b></p>
|
||||
<div class="chart-container__chart" ref="chart" onresize="recalculateChartDimensions()" >
|
||||
<EgressChart v-if="isEgressChartShown" :height="chartHeight" :width="chartWidth"/>
|
||||
<IngressChart v-else-if="isIngressChartShown" :height="chartHeight" :width="chartWidth"/>
|
||||
<BandwidthChart v-else :height="chartHeight" :width="chartWidth"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import BandwidthChart from '@/app/components/bandwidth/BandwidthChart.vue';
|
||||
import EgressChart from '@/app/components/bandwidth/EgressChart.vue';
|
||||
import IngressChart from '@/app/components/bandwidth/IngressChart.vue';
|
||||
import NodeSelectionDropdown from '@/app/components/common/NodeSelectionDropdown.vue';
|
||||
import SatelliteSelectionDropdown from '@/app/components/common/SatelliteSelectionDropdown.vue';
|
||||
|
||||
import { UnauthorizedError } from '@/api';
|
||||
import { BandwidthTraffic } from '@/bandwidth';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
EgressChart,
|
||||
IngressChart,
|
||||
BandwidthChart,
|
||||
NodeSelectionDropdown,
|
||||
SatelliteSelectionDropdown,
|
||||
},
|
||||
})
|
||||
export default class BandwidthPage extends Vue {
|
||||
public chartWidth: number = 0;
|
||||
public chartHeight: number = 0;
|
||||
public isEgressChartShown: boolean = false;
|
||||
public isIngressChartShown: boolean = false;
|
||||
public $refs: {
|
||||
chart: HTMLElement;
|
||||
};
|
||||
|
||||
public get bandwidth(): BandwidthTraffic {
|
||||
return this.$store.state.bandwidth.traffic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used container size recalculation for charts resizing.
|
||||
*/
|
||||
public recalculateChartDimensions(): void {
|
||||
this.chartWidth = this.$refs['chart'].clientWidth;
|
||||
this.chartHeight = this.$refs['chart'].clientHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle hook after initial render.
|
||||
* Adds event on window resizing to recalculate size of charts.
|
||||
*/
|
||||
public async mounted(): Promise<void> {
|
||||
window.addEventListener('resize', this.recalculateChartDimensions);
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('nodes/fetch');
|
||||
} catch (error) {
|
||||
@ -36,6 +108,66 @@ export default class BandwidthPage extends Vue {
|
||||
|
||||
// TODO: notify error
|
||||
}
|
||||
|
||||
await this.fetchBandwidth();
|
||||
|
||||
// Subscribes on period or satellite change
|
||||
this.$store.subscribe(async (mutation) => {
|
||||
const watchedMutations = [ 'nodes/setSelectedNode', 'nodes/setSelectedSatellite' ];
|
||||
|
||||
if (watchedMutations.includes(mutation.type)) {
|
||||
await this.fetchBandwidth();
|
||||
}
|
||||
});
|
||||
|
||||
this.recalculateChartDimensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle hook before component destruction.
|
||||
* Removes event on window resizing.
|
||||
*/
|
||||
public beforeDestroy(): void {
|
||||
window.removeEventListener('resize', this.recalculateChartDimensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes bandwidth chart source to summary of ingress and egress.
|
||||
*/
|
||||
public openBandwidthChart(): void {
|
||||
this.isEgressChartShown = false;
|
||||
this.isIngressChartShown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes bandwidth chart source to ingress.
|
||||
*/
|
||||
public openIngressChart(): void {
|
||||
this.isEgressChartShown = false;
|
||||
this.isIngressChartShown = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes bandwidth chart source to egress.
|
||||
*/
|
||||
public openEgressChart(): void {
|
||||
this.isEgressChartShown = true;
|
||||
this.isIngressChartShown = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches bandwidth information.
|
||||
*/
|
||||
private async fetchBandwidth(): Promise<void> {
|
||||
try {
|
||||
await this.$store.dispatch('bandwidth/fetch');
|
||||
} catch (error) {
|
||||
if (error instanceof UnauthorizedError) {
|
||||
// TODO: redirect to login screen.
|
||||
}
|
||||
|
||||
// TODO: notify error
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -68,5 +200,68 @@ export default class BandwidthPage extends Vue {
|
||||
max-width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
& .chart-container {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 401px;
|
||||
background-color: white;
|
||||
border: 1px solid var(--c-gray--light);
|
||||
border-radius: 11px;
|
||||
padding: 32px 30px;
|
||||
margin: 20px 0 13px 0;
|
||||
position: relative;
|
||||
|
||||
&__title-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&__buttons-area {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-size: 14px;
|
||||
color: var(--c-gray);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&__chart-choice-item {
|
||||
padding: 6px 8px;
|
||||
background-color: #e7e9eb;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
color: #586474;
|
||||
max-height: 25px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
margin-left: 9px;
|
||||
border: none;
|
||||
|
||||
&.active {
|
||||
background-color: #d5d9dc;
|
||||
color: #131d3a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__amount {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 32px;
|
||||
line-height: 57px;
|
||||
color: var(--c-title);
|
||||
}
|
||||
|
||||
&__chart {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: calc(100% - 10px);
|
||||
height: 240px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -23,6 +23,19 @@ export class BandwidthRollup {
|
||||
public deletes: number = 0,
|
||||
public intervalStart: Date = new Date(),
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates new empty instance of used bandwidth with defined date.
|
||||
* @param date - holds specific date of the month
|
||||
* @returns BandwidthUsed - new empty instance of used bandwidth with defined date
|
||||
*/
|
||||
public static emptyWithDate(date: number): BandwidthRollup {
|
||||
const now = new Date();
|
||||
now.setUTCDate(date);
|
||||
now.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
return new BandwidthRollup(new Egress(0, 0, 0), new Ingress(0, 0), 0, now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,7 +4,7 @@
|
||||
/**
|
||||
* Base10 sizes.
|
||||
*/
|
||||
enum SizeBreakpoints {
|
||||
export enum SizeBreakpoints {
|
||||
KB = 1e3,
|
||||
MB = 1e6,
|
||||
GB = 1e9,
|
||||
|
Loading…
Reference in New Issue
Block a user