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==",
|
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
|
||||||
"dev": true
|
"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": {
|
"check-types": {
|
||||||
"version": "8.0.3",
|
"version": "8.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
"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": {
|
"move-concurrently": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
|
||||||
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
|
"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": {
|
"vue-class-component": {
|
||||||
"version": "7.2.6",
|
"version": "7.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.6.tgz",
|
"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"
|
"test": "vue-cli-service test:unit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chart.js": "2.9.3",
|
||||||
"vue": "2.6.11",
|
"vue": "2.6.11",
|
||||||
|
"vue-chartjs": "3.5.0",
|
||||||
"vue-class-component": "7.2.6",
|
"vue-class-component": "7.2.6",
|
||||||
"vue-clipboard2": "0.3.1",
|
"vue-clipboard2": "0.3.1",
|
||||||
"vue-jest": "3.0.5",
|
"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 Vue from 'vue';
|
||||||
import Vuex, { ModuleTree, Store, StoreOptions } from 'vuex';
|
import Vuex, { ModuleTree, Store, StoreOptions } from 'vuex';
|
||||||
|
|
||||||
|
import { BandwidthClient } from '@/api/bandwidth';
|
||||||
import { NodesClient } from '@/api/nodes';
|
import { NodesClient } from '@/api/nodes';
|
||||||
import { Operators as OperatorsClient } from '@/api/operators';
|
import { Operators as OperatorsClient } from '@/api/operators';
|
||||||
import { PayoutsClient } from '@/api/payouts';
|
import { PayoutsClient } from '@/api/payouts';
|
||||||
|
import { BandwidthModule, BandwidthState } from '@/app/store/bandwidth';
|
||||||
import { NodesModule, NodesState } from '@/app/store/nodes';
|
import { NodesModule, NodesState } from '@/app/store/nodes';
|
||||||
import { OperatorsModule, OperatorsState } from '@/app/store/operators';
|
import { OperatorsModule, OperatorsState } from '@/app/store/operators';
|
||||||
import { PayoutsModule, PayoutsState } from '@/app/store/payouts';
|
import { PayoutsModule, PayoutsState } from '@/app/store/payouts';
|
||||||
|
import { Bandwidth } from '@/bandwidth/service';
|
||||||
import { Nodes } from '@/nodes/service';
|
import { Nodes } from '@/nodes/service';
|
||||||
import { Operators } from '@/operators';
|
import { Operators } from '@/operators';
|
||||||
import { Payouts } from '@/payouts/service';
|
import { Payouts } from '@/payouts/service';
|
||||||
@ -23,6 +26,7 @@ export class RootState {
|
|||||||
nodes: NodesState;
|
nodes: NodesState;
|
||||||
payouts: PayoutsState;
|
payouts: PayoutsState;
|
||||||
operators: OperatorsState;
|
operators: OperatorsState;
|
||||||
|
bandwidth: BandwidthState;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,16 +37,23 @@ export class MultinodeStoreOptions implements StoreOptions<RootState> {
|
|||||||
public readonly state: RootState;
|
public readonly state: RootState;
|
||||||
public readonly modules: ModuleTree<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.strict = true;
|
||||||
this.state = {
|
this.state = {
|
||||||
nodes: nodes.state,
|
nodes: nodes.state,
|
||||||
payouts: payouts.state,
|
payouts: payouts.state,
|
||||||
|
bandwidth: bandwidth.state,
|
||||||
operators: operators.state,
|
operators: operators.state,
|
||||||
};
|
};
|
||||||
this.modules = {
|
this.modules = {
|
||||||
nodes,
|
nodes,
|
||||||
payouts,
|
payouts,
|
||||||
|
bandwidth,
|
||||||
operators,
|
operators,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -53,15 +64,18 @@ const nodesClient: NodesClient = new NodesClient();
|
|||||||
const nodesService: Nodes = new Nodes(nodesClient);
|
const nodesService: Nodes = new Nodes(nodesClient);
|
||||||
const payoutsClient: PayoutsClient = new PayoutsClient();
|
const payoutsClient: PayoutsClient = new PayoutsClient();
|
||||||
const payoutsService: Payouts = new Payouts(payoutsClient);
|
const payoutsService: Payouts = new Payouts(payoutsClient);
|
||||||
|
const bandwidthClient = new BandwidthClient();
|
||||||
|
const bandwidthService = new Bandwidth(bandwidthClient);
|
||||||
const operatorsClient: OperatorsClient = new OperatorsClient();
|
const operatorsClient: OperatorsClient = new OperatorsClient();
|
||||||
const operatorsService: Operators = new Operators(operatorsClient);
|
const operatorsService: Operators = new Operators(operatorsClient);
|
||||||
|
|
||||||
// Modules
|
// Modules
|
||||||
const nodesModule: NodesModule = new NodesModule(nodesService);
|
const nodesModule: NodesModule = new NodesModule(nodesService);
|
||||||
const payoutsModule: PayoutsModule = new PayoutsModule(payoutsService);
|
const payoutsModule: PayoutsModule = new PayoutsModule(payoutsService);
|
||||||
|
const bandwidthModule: BandwidthModule = new BandwidthModule(bandwidthService);
|
||||||
const operatorsModule: OperatorsModule = new OperatorsModule(operatorsService);
|
const operatorsModule: OperatorsModule = new OperatorsModule(operatorsService);
|
||||||
|
|
||||||
// Store
|
// Store
|
||||||
export const store: Store<RootState> = new Vuex.Store<RootState>(
|
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 />
|
<node-selection-dropdown />
|
||||||
<satellite-selection-dropdown />
|
<satellite-selection-dropdown />
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from 'vue-property-decorator';
|
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 NodeSelectionDropdown from '@/app/components/common/NodeSelectionDropdown.vue';
|
||||||
import SatelliteSelectionDropdown from '@/app/components/common/SatelliteSelectionDropdown.vue';
|
import SatelliteSelectionDropdown from '@/app/components/common/SatelliteSelectionDropdown.vue';
|
||||||
|
|
||||||
import { UnauthorizedError } from '@/api';
|
import { UnauthorizedError } from '@/api';
|
||||||
|
import { BandwidthTraffic } from '@/bandwidth';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
|
EgressChart,
|
||||||
|
IngressChart,
|
||||||
|
BandwidthChart,
|
||||||
NodeSelectionDropdown,
|
NodeSelectionDropdown,
|
||||||
SatelliteSelectionDropdown,
|
SatelliteSelectionDropdown,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class BandwidthPage extends Vue {
|
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> {
|
public async mounted(): Promise<void> {
|
||||||
|
window.addEventListener('resize', this.recalculateChartDimensions);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('nodes/fetch');
|
await this.$store.dispatch('nodes/fetch');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -36,6 +108,66 @@ export default class BandwidthPage extends Vue {
|
|||||||
|
|
||||||
// TODO: notify error
|
// 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>
|
</script>
|
||||||
@ -68,5 +200,68 @@ export default class BandwidthPage extends Vue {
|
|||||||
max-width: unset;
|
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>
|
</style>
|
||||||
|
@ -23,6 +23,19 @@ export class BandwidthRollup {
|
|||||||
public deletes: number = 0,
|
public deletes: number = 0,
|
||||||
public intervalStart: Date = new Date(),
|
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.
|
* Base10 sizes.
|
||||||
*/
|
*/
|
||||||
enum SizeBreakpoints {
|
export enum SizeBreakpoints {
|
||||||
KB = 1e3,
|
KB = 1e3,
|
||||||
MB = 1e6,
|
MB = 1e6,
|
||||||
GB = 1e9,
|
GB = 1e9,
|
||||||
|
Loading…
Reference in New Issue
Block a user