it('should verify that cards exist on dashboard in proper order', () => {
// Ensures that cards are all displayed on the dashboard tab while being in the proper
// order, checks for card title and position via indexing into a list of all cards.
- const order = ['Details', 'Status', 'Capacity', 'Inventory', 'Cluster utilization'];
+ const order = ['Details', 'Status', 'Capacity', 'Inventory', 'Cluster Utilization'];
for (let i = 0; i < order.length; i++) {
dashboard.card(i).should('contain.text', order[i]);
-<div class="row">
- <div class="col-3 center-text">
+<div class="row mt-2">
+ <div class="col-3 d-flex flex-column align-self-center">
<br>
- <b class="chartTitle"
+ <b class="chartTitle pb-2"
i18n>{{ chartTitle }}</b>
- <br>
- <span [ngbTooltip]="label"
- i18n>{{currentData}} {{ currentDataUnits }}</span>
- <br>
- <span [ngbTooltip]="label2"
- i18n>{{currentData2}} {{ currentDataUnits2 }}</span>
+ <div
+ i18n>
+ <div class="d-inline-flex align-items-center gap-1">
+ <div *ngIf="!maxValue"
+ class="blue-box">
+ </div>
+ <div *ngIf="label2">{{ label }}:
+ </div>
+ {{ currentData || 'N/A' }} {{ currentDataUnits }}
+ <div *ngIf="maxValue && currentData"> used of
+ {{ maxConvertedValue }} {{ maxConvertedValueUnits }}
+ </div>
+ </div>
+ </div>
+ <div *ngIf="label2"
+ i18n>
+ <div class="d-inline-flex align-items-center gap-1">
+ <div class="yellow-box"></div>
+ <div *ngIf="label2 !== chartTitle" >{{ label2 }}: </div>
+ <div>{{ currentData2 || 'N/A' }} {{ currentDataUnits2 }}</div>
+ </div>
+ </div>
</div>
- <div class="col-9">
- <div class="chart">
+ <div class="col-9 d-flex flex-column">
+ <div class="chart mt-3">
<canvas baseChart
[datasets]="chartData.dataset"
[options]="options"
-.center-text {
- margin-top: 1.2vw;
- position: relative;
-}
+@use './src/styles/vendor/variables' as vv;
.chart {
- height: 8vh;
- margin-top: 15px;
+ height: 9vh;
+}
+
+.blue-box {
+ background-color: vv.$chart-color-strong-blue;
+ border: 2px double vv.$chart-color-light-gray;
+ height: 13px;
+ width: 13px;
+}
+
+.yellow-box {
+ background-color: vv.$chart-color-orange;
+ border: 2px double vv.$chart-color-light-gray;
+ height: 13px;
+ width: 13px;
}
currentData: number;
currentDataUnits2?: string;
currentData2?: number;
+ maxConvertedValue?: number;
+ maxConvertedValueUnits?: string;
chartDataUnits: string;
-
chartData: any = {
dataset: [
{
label: '',
data: [{ x: 0, y: 0 }],
- tension: 0,
+ tension: 0.2,
pointBackgroundColor: this.cssHelper.propertyValue('chart-color-strong-blue'),
backgroundColor: this.cssHelper.propertyValue('chart-color-translucent-blue'),
- borderColor: this.cssHelper.propertyValue('chart-color-strong-blue')
+ borderColor: this.cssHelper.propertyValue('chart-color-strong-blue'),
+ borderWidth: 1
},
{
label: '',
data: [],
- tension: 0,
+ tension: 0.2,
pointBackgroundColor: this.cssHelper.propertyValue('chart-color-orange'),
- backgroundColor: this.cssHelper.propertyValue('chart-color-yellow'),
- borderColor: this.cssHelper.propertyValue('chart-color-orange')
+ backgroundColor: this.cssHelper.propertyValue('chart-color-translucent-yellow'),
+ borderColor: this.cssHelper.propertyValue('chart-color-orange'),
+ borderWidth: 1
}
]
};
options: any = {
responsive: true,
maintainAspectRatio: false,
+ animation: false,
elements: {
point: {
radius: 0
display: false
},
tooltips: {
+ mode: 'index',
custom: function (tooltipModel: { x: number; y: number }) {
tooltipModel.x = 10;
tooltipModel.y = 0;
callbacks: {
title: function (tooltipItem: any): any {
return tooltipItem[0].xLabel;
+ },
+ label: (tooltipItems: any, data: any) => {
+ return (
+ ' ' +
+ data.datasets[tooltipItems.datasetIndex].label +
+ ' - ' +
+ tooltipItems.value +
+ ' ' +
+ this.chartDataUnits
+ );
}
}
},
display: false
},
time: {
- tooltipFormat: 'YYYY/MM/DD hh:mm:ss'
+ tooltipFormat: 'DD/MM/YYYY - HH:mm:ss'
}
}
],
},
ticks: {
beginAtZero: true,
- maxTicksLimit: 3,
+ maxTicksLimit: 4,
callback: (value: any) => {
if (value === 0) {
return null;
}
private updateChartData(): void {
+ this.chartData.dataset[0].label = this.label;
+ this.chartData.dataset[1].label = this.label2;
+ this.setChartTicks();
if (this.data) {
- this.setChartTicks();
this.chartData.dataset[0].data = this.formatData(this.data);
- this.chartData.dataset[0].label = this.label;
[this.currentData, this.currentDataUnits] = this.convertUnits(
this.data[this.data.length - 1][1]
).split(' ');
+ [this.maxConvertedValue, this.maxConvertedValueUnits] = this.convertUnits(
+ this.maxValue
+ ).split(' ');
}
if (this.data2) {
this.chartData.dataset[1].data = this.formatData(this.data2);
- this.chartData.dataset[1].label = this.label2;
[this.currentData2, this.currentDataUnits2] = this.convertUnits(
this.data2[this.data2.length - 1][1]
).split(' ');
dataWithUnits = this.numberFormatter.formatBytesFromTo(
data,
this.dataUnits,
- this.chartDataUnits
+ this.chartDataUnits,
+ this.decimals
);
} else if (this.dataUnits === 'B/s') {
dataWithUnits = this.numberFormatter.formatBytesPerSecondFromTo(
data,
this.dataUnits,
- this.chartDataUnits
+ this.chartDataUnits,
+ this.decimals
);
} else if (this.dataUnits === 'ms') {
dataWithUnits = this.numberFormatter.formatSecondsFromTo(
dataWithUnits = this.numberFormatter.formatUnitlessFromTo(
data,
this.dataUnits,
- this.chartDataUnits
+ this.chartDataUnits,
+ this.decimals
);
}
}
private convertUnits(data: any): any {
let dataWithUnits: string = '';
if (this.dataUnits === 'B') {
- dataWithUnits = this.dimlessBinary.transform(data);
+ dataWithUnits = this.dimlessBinary.transform(data, this.decimals);
} else if (this.dataUnits === 'B/s') {
- dataWithUnits = this.dimlessBinaryPerSecond.transform(data);
+ dataWithUnits = this.dimlessBinaryPerSecond.transform(data, this.decimals);
} else if (this.dataUnits === 'ms') {
dataWithUnits = this.formatter.format_number(data, 1000, ['ms', 's'], this.decimals);
} else {
- dataWithUnits = this.dimlessPipe.transform(data);
+ dataWithUnits = this.dimlessPipe.transform(data, this.decimals);
}
return dataWithUnits;
}
let maxValueDataUnits = '';
let extraRoom = 1.2;
- if (this.maxValue) {
- extraRoom = 1.0;
- [maxValue, maxValueDataUnits] = this.convertUnits(this.maxValue).split(' ');
- } else if (this.data) {
- extraRoom = 1.2;
+ if (this.data) {
let maxValueData = Math.max(...this.data.map((values: any) => values[1]));
if (this.data2) {
let maxValueData2 = Math.max(...this.data2.map((values: any) => values[1]));
const yAxesTicks = this.chart.chart.options.scales.yAxes[0].ticks;
yAxesTicks.suggestedMax = maxValue * extraRoom;
yAxesTicks.suggestedMin = 0;
- yAxesTicks.stepSize = Number((yAxesTicks.suggestedMax / 2).toFixed(0));
yAxesTicks.callback = (value: any) => {
if (value === 0) {
return null;
-select#timepicker {
- border: 0;
-}
-
.timeSelector {
position: absolute;
right: 18px;
},
{
name: $localize`Last 30 minutes`,
- value: this.timeToDate(30 * 60, 6)
+ value: this.timeToDate(30 * 60, 7)
},
{
name: $localize`Last 1 hour`,
- value: this.timeToDate(3600, 12)
+ value: this.timeToDate(3600, 14)
},
{
name: $localize`Last 3 hours`,
- value: this.timeToDate(3 * 3600, 36)
+ value: this.timeToDate(3 * 3600, 42)
},
{
name: $localize`Last 6 hours`,
- value: this.timeToDate(6 * 3600, 72)
+ value: this.timeToDate(6 * 3600, 84)
},
{
name: $localize`Last 12 hours`,
- value: this.timeToDate(12 * 3600, 144)
+ value: this.timeToDate(12 * 3600, 168)
},
{
name: $localize`Last 24 hours`,
- value: this.timeToDate(24 * 3600, 288)
- },
- {
- name: $localize`Last 2 days`,
- value: this.timeToDate(48 * 3600, 576)
- },
- {
- name: $localize`Last 7 days`,
- value: this.timeToDate(168 * 3600, 2016)
+ value: this.timeToDate(24 * 3600, 336)
}
];
this.time = this.times[3].value;
this.selectedTime.emit(this.timeToDate(this.time.end - this.time.start, this.time.step));
}
- private timeToDate(secondsAgo: number, step: number): any {
+ public timeToDate(secondsAgo: number, step: number): any {
const date: number = moment().unix() - secondsAgo;
const dateNow: number = moment().unix();
const formattedDate: any = {
</li>
</cd-card>
- <cd-card cardTitle="Cluster utilization"
+ <cd-card cardTitle="Cluster Utilization"
i18n-title
class="col-sm-9 px-3 d-flex"
aria-label="Cluster utilization card">
<cd-dashboard-time-selector (selectedTime)="getPrometheusData($event)">
</cd-dashboard-time-selector>
<ng-container *ngIf="capacity">
- <cd-dashboard-area-chart chartTitle="Used Capacity"
+ <cd-dashboard-area-chart chartTitle="Used Capacity (RAW)"
[maxValue]="capacity.total_bytes"
dataUnits="B"
label="Used Capacity"
</ng-container>
<cd-dashboard-area-chart chartTitle="IOPS"
dataUnits=""
- label="OPS"
- label2="IPS"
- [data]="queriesResults.OPS"
- [data2]="queriesResults.IPS">
+ decimals="0"
+ label="Reads"
+ label2="Writes"
+ [data]="queriesResults.READIOPS"
+ [data2]="queriesResults.WRITEIOPS">
</cd-dashboard-area-chart>
- <cd-dashboard-area-chart chartTitle="Latency"
+ <cd-dashboard-area-chart chartTitle="OSD Latencies"
dataUnits="ms"
- decimals="3"
- label="Read"
- label2="Write"
+ decimals="2"
+ label="Apply"
+ label2="Commit"
[data]="queriesResults.READLATENCY"
[data2]="queriesResults.WRITELATENCY">
</cd-dashboard-area-chart>
<cd-dashboard-area-chart chartTitle="Client Throughput"
dataUnits="B/s"
- label="Read"
- label2="Write"
+ decimals="2"
+ label="Reads"
+ label2="Writes"
[data]="queriesResults.READCLIENTTHROUGHPUT"
[data2]="queriesResults.WRITECLIENTTHROUGHPUT">
</cd-dashboard-area-chart>
<cd-dashboard-area-chart chartTitle="Recovery Throughput"
dataUnits="B/s"
+ decimals="2"
label="Recovery Throughput"
[data]="queriesResults.RECOVERYBYTES">
</cd-dashboard-area-chart>
readonly lastHourDateObject = {
start: moment().unix() - 3600,
end: moment().unix(),
- step: 12
+ step: 14
};
constructor(
export enum Promqls {
USEDCAPACITY = 'ceph_cluster_total_used_bytes',
- IPS = 'sum(rate(ceph_osd_op_w_in_bytes[1m]))',
- OPS = 'sum(rate(ceph_osd_op_r_out_bytes[1m]))',
+ WRITEIOPS = 'sum(rate(ceph_pool_wr[1m]))',
+ READIOPS = 'sum(rate(ceph_pool_rd[1m]))',
READLATENCY = 'avg_over_time(ceph_osd_apply_latency_ms[1m])',
WRITELATENCY = 'avg_over_time(ceph_osd_commit_latency_ms[1m])',
READCLIENTTHROUGHPUT = 'sum(rate(ceph_pool_rd_bytes[1m]))',
export class DimlessBinaryPerSecondPipe implements PipeTransform {
constructor(private formatter: FormatterService) {}
- transform(value: any): any {
- return this.formatter.format_number(value, 1024, [
- 'B/s',
- 'KiB/s',
- 'MiB/s',
- 'GiB/s',
- 'TiB/s',
- 'PiB/s',
- 'EiB/s',
- 'ZiB/s',
- 'YiB/s'
- ]);
+ transform(value: any, decimals: number = 1): any {
+ return this.formatter.format_number(
+ value,
+ 1024,
+ ['B/s', 'KiB/s', 'MiB/s', 'GiB/s', 'TiB/s', 'PiB/s', 'EiB/s', 'ZiB/s', 'YiB/s'],
+ decimals
+ );
}
}
export class DimlessBinaryPipe implements PipeTransform {
constructor(private formatter: FormatterService) {}
- transform(value: any): any {
- return this.formatter.format_number(value, 1024, [
- 'B',
- 'KiB',
- 'MiB',
- 'GiB',
- 'TiB',
- 'PiB',
- 'EiB',
- 'ZiB',
- 'YiB'
- ]);
+ transform(value: any, decimals: number = 1): any {
+ return this.formatter.format_number(
+ value,
+ 1024,
+ ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'],
+ decimals
+ );
}
}
export class DimlessPipe implements PipeTransform {
constructor(private formatter: FormatterService) {}
- transform(value: any): any {
- return this.formatter.format_number(value, 1000, ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']);
+ transform(value: any, decimals: number = 1): any {
+ return this.formatter.format_number(
+ value,
+ 1000,
+ ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'],
+ decimals
+ );
}
}
$chart-color-strong-blue: #0078c8 !default;
$chart-color-translucent-blue: #0096dc80 !default;
$chart-color-border: #00000020 !default;
+$chart-color-translucent-yellow: #ef923472 !default;
// Typography