// order, checks for card title and position via indexing into a list of all info cards.
const order = [
'Cluster Status',
+ 'Hosts',
'Monitors',
'OSDs',
- 'Manager Daemons',
- 'Hosts',
+ 'Managers',
'Object Gateways',
'Metadata Servers',
'iSCSI Gateways',
- 'Client IOPS',
- 'Client Throughput',
- 'Client Read/Write',
- 'Recovery Throughput',
- 'Scrub',
- 'Pools',
'Raw Capacity',
'Objects',
+ 'PG Status',
+ 'Pools',
'PGs per OSD',
- 'PG Status'
+ 'Client Read/Write',
+ 'Client Throughput',
+ 'Recovery Throughput',
+ 'Scrubbing'
];
for (let i = 0; i < order.length; i++) {
it('should verify that info card group titles are present and in the right order', () => {
cy.location('hash').should('eq', '#/dashboard');
dashboard.infoGroupTitle(0).should('eq', 'Status');
- dashboard.infoGroupTitle(1).should('eq', 'Performance');
- dashboard.infoGroupTitle(2).should('eq', 'Capacity');
+ dashboard.infoGroupTitle(1).should('eq', 'Capacity');
+ dashboard.infoGroupTitle(2).should('eq', 'Performance');
});
});
+++ /dev/null
-export enum HealthPieColor {
- DEFAULT_RED = '#ff7592',
- DEFAULT_BLUE = '#1d699d',
- DEFAULT_ORANGE = '#ffa500',
- DEFAULT_MAGENTA = '#564d65',
- DEFAULT_GREEN = '#00bb00'
-}
[options]="chartConfig.options"
[labels]="chartConfig.labels"
[colors]="chartConfig.colors"
+ [plugins]="doughnutChartPlugins"
class="chart-canvas">
</canvas>
<div class="chartjs-tooltip"
-@import '../../../../styles/chart-tooltip.scss';
+@import './src/styles/chart-tooltip.scss';
+@import './src/styles/defaults';
$canvas-width: 100%;
$canvas-height: 100%;
.chart-container {
+ @each $key_name, $value in $health-chart-colors {
+ --color-#{$key_name}: #{$value};
+ }
+
position: unset;
width: $canvas-width;
height: $canvas-height;
expect(component.chartConfig.dataset[0].data).toEqual(initialData);
});
+ it('should set colors from css variables', () => {
+ const cssVar = '--my-color';
+ const cssVarColor = '#73c5c5';
+ component['getCssVar'] = (name: string) => (name === cssVar ? cssVarColor : '');
+ component.chartConfig.colors[0].backgroundColor = [cssVar, '#ffffff'];
+ fixture.detectChanges();
+
+ expect(component.chartConfig.colors[0].backgroundColor).toEqual([cssVarColor, '#ffffff']);
+ });
+
describe('tooltip body', () => {
const tooltipBody = ['text: 10000'];
import * as Chart from 'chart.js';
import * as _ from 'lodash';
+import { PluginServiceGlobalRegistrationAndOptions } from 'ng2-charts';
import { ChartTooltip } from '../../../shared/models/chart-tooltip';
import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
-import { HealthPieColor } from './health-pie-color.enum';
@Component({
selector: 'cd-health-pie',
prepareFn = new EventEmitter();
chartConfig: any = {
- chartType: 'pie',
+ chartType: 'doughnut',
dataset: [
{
label: null,
borderWidth: 0
}
],
+ colors: [
+ {
+ backgroundColor: [
+ '--color-green',
+ '--color-yellow',
+ '--color-orange',
+ '--color-red',
+ '--color-blue'
+ ]
+ }
+ ],
options: {
+ cutoutPercentage: 90,
+ events: ['click', 'mouseout', 'touchstart'],
legend: {
display: true,
position: 'right',
- labels: { usePointStyle: true },
- onClick: (event: any, legendItem: any) => {
- this.onLegendClick(event, legendItem);
+ labels: {
+ boxWidth: 10,
+ usePointStyle: false
}
},
- animation: { duration: 0 },
+ plugins: {
+ center_text: true
+ },
tooltips: {
- enabled: false
+ enabled: true,
+ displayColors: false,
+ backgroundColor: 'rgba(0,0,0,0.8)',
+ cornerRadius: 0,
+ bodyFontSize: 14,
+ bodyFontStyle: '600',
+ position: 'nearest',
+ xPadding: 12,
+ yPadding: 12,
+ callbacks: {
+ label: (item: Record<string, any>, data: Record<string, any>) => {
+ let text = data.labels[item.index];
+ if (!text.includes('%')) {
+ text = `${text} (${data.datasets[item.datasetIndex].data[item.index]}%)`;
+ }
+ return text;
+ }
+ }
},
title: {
display: false
}
}
};
- private hiddenSlices: any[] = [];
-
- constructor(private dimlessBinary: DimlessBinaryPipe, private dimless: DimlessPipe) {}
- ngOnInit() {
- // An extension to Chart.js to enable rendering some
- // text in the middle of a doughnut
- Chart.pluginService.register({
- beforeDraw: function (chart: any) {
- if (!chart.options.center_text) {
+ public doughnutChartPlugins: PluginServiceGlobalRegistrationAndOptions[] = [
+ {
+ id: 'center_text',
+ beforeDraw(chart: Chart) {
+ const defaultFontColorA = '#151515';
+ const defaultFontColorB = '#72767B';
+ const defaultFontFamily = 'Helvetica Neue, Helvetica, Arial, sans-serif';
+ Chart.defaults.global.defaultFontFamily = defaultFontFamily;
+ const ctx = chart.ctx;
+ if (!chart.options.plugins.center_text || !chart.data.datasets[0].label) {
return;
}
- const width = chart.chart.width,
- height = chart.chart.height,
- ctx = chart.chart.ctx;
+ ctx.save();
+ const label = chart.data.datasets[0].label.split('\n');
- ctx.restore();
- const fontSize = (height / 114).toFixed(2);
- ctx.font = fontSize + 'em sans-serif';
+ const centerX = (chart.chartArea.left + chart.chartArea.right) / 2;
+ const centerY = (chart.chartArea.top + chart.chartArea.bottom) / 2;
+ ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
- const text = chart.options.center_text,
- textX = Math.round((width - ctx.measureText(text).width) / 2),
- textY = height / 2;
+ ctx.font = `24px ${defaultFontFamily}`;
+ ctx.fillStyle = defaultFontColorA;
+ ctx.fillText(label[0], centerX, centerY - 10);
- ctx.fillText(text, textX, textY);
- ctx.save();
+ if (label.length > 1) {
+ ctx.font = `14px ${defaultFontFamily}`;
+ ctx.fillStyle = defaultFontColorB;
+ ctx.fillText(label[1], centerX, centerY + 10);
+ }
+ ctx.restore();
}
- });
+ }
+ ];
+
+ constructor(private dimlessBinary: DimlessBinaryPipe, private dimless: DimlessPipe) {}
+ ngOnInit() {
const getStyleTop = (tooltip: any, positionY: number) => {
return positionY + tooltip.caretY - tooltip.height - 10 + 'px';
};
getStyleTop
);
- const getBody = (body: any) => {
+ chartTooltip.getBody = (body: any) => {
return this.getChartTooltipBody(body);
};
- chartTooltip.getBody = getBody;
-
- this.chartConfig.options.tooltips.custom = (tooltip: any) => {
- chartTooltip.customTooltips(tooltip);
- };
-
- this.chartConfig.colors = [
- {
- backgroundColor: [
- HealthPieColor.DEFAULT_RED,
- HealthPieColor.DEFAULT_BLUE,
- HealthPieColor.DEFAULT_ORANGE,
- HealthPieColor.DEFAULT_GREEN,
- HealthPieColor.DEFAULT_MAGENTA
- ]
- }
- ];
-
_.merge(this.chartConfig, this.config);
+ this.setColorsFromCssVars();
+
this.prepareFn.emit([this.chartConfig, this.data]);
}
ngOnChanges() {
this.prepareFn.emit([this.chartConfig, this.data]);
- this.hideSlices();
this.setChartSliceBorderWidth();
}
+ private setColorsFromCssVars() {
+ this.chartConfig.colors.forEach(
+ (colorEl: { backgroundColor: string[] }, colorIndex: number) => {
+ colorEl.backgroundColor.forEach((bgColor: string, bgColorIndex: number) => {
+ if (bgColor.startsWith('--')) {
+ this.chartConfig.colors[colorIndex].backgroundColor[bgColorIndex] = this.getCssVar(
+ bgColor
+ );
+ }
+ });
+ }
+ );
+ }
+
+ private getCssVar(name: string): string {
+ return getComputedStyle(document.querySelector('.chart-container')).getPropertyValue(name);
+ }
+
private getChartTooltipBody(body: string[]) {
const bodySplit = body[0].split(': ');
this.chartConfig.dataset[0].borderWidth = nonZeroValueSlices > 1 ? 1 : 0;
}
-
- private onLegendClick(event: any, legendItem: any) {
- event.stopPropagation();
- this.hiddenSlices[legendItem.index] = !legendItem.hidden;
- this.ngOnChanges();
- }
-
- private hideSlices() {
- _.forEach(this.chartConfig.dataset[0].data, (_slice, sliceIndex: number) => {
- if (this.hiddenSlices[sliceIndex]) {
- this.chartConfig.dataset[0].data[sliceIndex] = undefined;
- }
- });
- }
}
container="body"
containerClass="info-card-popover-cluster-status"
(click)="healthChecksTarget.toggle()">
- {{ healthData.health.status }}
+ {{ healthData.health.status }} <i *ngIf="healthData.health?.status != 'HEALTH_OK'"
+ class="fa fa-exclamation-triangle"></i>
</div>
</ng-container>
<ng-container *ngIf="!healthData.health?.checks?.length">
</ng-container>
</cd-info-card>
+ <cd-info-card cardTitle="Hosts"
+ i18n-cardTitle
+ link="/hosts"
+ class="cd-status-card"
+ contentClass="content-highlight"
+ *ngIf="healthData.hosts != null">
+ {{ healthData.hosts }} total
+ </cd-info-card>
+
<cd-info-card cardTitle="Monitors"
i18n-cardTitle
link="/monitor"
</span>
</cd-info-card>
- <cd-info-card cardTitle="Manager Daemons"
+ <cd-info-card cardTitle="Managers"
i18n-cardTitle
class="cd-status-card"
contentClass="content-highlight"
</span>
</cd-info-card>
- <cd-info-card cardTitle="Hosts"
- i18n-cardTitle
- link="/hosts"
- class="cd-status-card"
- contentClass="content-highlight"
- *ngIf="healthData.hosts != null">
- {{ healthData.hosts }} total
- </cd-info-card>
-
<cd-info-card cardTitle="Object Gateways"
i18n-cardTitle
link="/rgw/daemon"
<cd-info-card cardTitle="Metadata Servers"
i18n-cardTitle
class="cd-status-card"
- *ngIf="((enabledFeature.cephfs && healthData.fs_map) | mdsSummary) as transformedResult"
+ *ngIf="(enabledFeature.cephfs && healthData.fs_map | mdsSummary) as transformedResult"
[contentClass]="(transformedResult.length > 1 ? 'text-area-size-2' : '') + ' content-highlight'">
<!-- TODO: check text-area-size-2 -->
<span *ngFor="let result of transformedResult"
</cd-info-card>
</cd-info-group>
- <cd-info-group groupTitle="Performance"
- i18n-groupTitle
- *ngIf="healthData.client_perf || healthData.scrub_status">
-
- <cd-info-card cardTitle="Client IOPS"
- i18n-cardTitle
- class="cd-performance-card"
- contentClass="content-highlight"
- *ngIf="healthData.client_perf">
- {{ (healthData.client_perf.read_op_per_sec + healthData.client_perf.write_op_per_sec) | round:1 }}
- </cd-info-card>
-
- <cd-info-card cardTitle="Client Throughput"
- i18n-cardTitle
- class="cd-performance-card"
- contentClass="content-highlight"
- *ngIf="healthData.client_perf">
- {{ ((healthData.client_perf.read_bytes_sec + healthData.client_perf.write_bytes_sec) | dimlessBinary) + '/s' }}
- </cd-info-card>
-
- <cd-info-card cardTitle="Client Read/Write"
- i18n-cardTitle
- class="cd-performance-card"
- [contentClass]="isClientReadWriteChartShowable() ? 'content-chart': 'content-highlight'"
- *ngIf="healthData.client_perf">
- <cd-health-pie *ngIf="isClientReadWriteChartShowable()"
- [data]="healthData"
- (prepareFn)="prepareReadWriteRatio($event[0], $event[1])">
- </cd-health-pie>
- <span *ngIf="!isClientReadWriteChartShowable()">
- N/A
- </span>
- </cd-info-card>
-
- <cd-info-card cardTitle="Recovery Throughput"
- i18n-cardTitle
- class="cd-performance-card"
- contentClass="content-highlight"
- *ngIf="healthData.client_perf">
- {{ (healthData.client_perf.recovering_bytes_per_sec | dimlessBinary) + '/s' }}
- </cd-info-card>
-
- <cd-info-card cardTitle="Scrub"
- i18n-cardTitle
- class="cd-performance-card"
- contentClass="content-highlight"
- *ngIf="healthData.scrub_status">
- {{ healthData.scrub_status }}
- </cd-info-card>
- </cd-info-group>
-
<cd-info-group groupTitle="Capacity"
i18n-groupTitle
*ngIf="healthData.pools
|| healthData.df
|| healthData.pg_info">
- <cd-info-card cardTitle="Pools"
- i18n-cardTitle
- link="/pool"
- class="cd-capacity-card order-md-1 order-lg-4 order-xl-1"
- contentClass="content-highlight"
- *ngIf="healthData.pools">
- {{ healthData.pools.length }}
- </cd-info-card>
-
<cd-info-card cardTitle="Raw Capacity"
i18n-cardTitle
- class="cd-capacity-card order-md-3 order-lg-1 order-xl-2"
+ class="cd-capacity-card cd-chart-card"
contentClass="content-chart"
*ngIf="healthData.df">
<cd-health-pie [data]="healthData"
<cd-info-card cardTitle="Objects"
i18n-cardTitle
- class="cd-capacity-card order-md-4 order-lg-2 order-xl-3"
+ class="cd-capacity-card cd-chart-card"
contentClass="content-chart"
*ngIf="healthData.pg_info?.object_stats?.num_objects != null">
<cd-health-pie [data]="healthData"
- [config]="objectsChartConfig"
(prepareFn)="prepareObjects($event[0], $event[1])">
</cd-health-pie>
</cd-info-card>
- <cd-info-card cardTitle="PGs per OSD"
- i18n-cardTitle
- class="cd-capacity-card order-md-2 order-lg-5 order-xl-4"
- contentClass="content-highlight"
- *ngIf="healthData.pg_info">
- {{ healthData.pg_info.pgs_per_osd | dimless }}
- </cd-info-card>
-
<cd-info-card cardTitle="PG Status"
i18n-cardTitle
- class="cd-capacity-card order-md-5 order-lg-3 order-xl-5"
+ class="cd-capacity-card cd-chart-card"
contentClass="content-chart"
(click)="pgStatusTarget.toggle()"
*ngIf="healthData.pg_info">
</div>
</div>
</cd-info-card>
+
+ <cd-info-card cardTitle="Pools"
+ i18n-cardTitle
+ link="/pool"
+ class="cd-capacity-card"
+ contentClass="content-highlight"
+ *ngIf="healthData.pools">
+ {{ healthData.pools.length }}
+ </cd-info-card>
+
+ <cd-info-card cardTitle="PGs per OSD"
+ i18n-cardTitle
+ class="cd-capacity-card"
+ contentClass="content-highlight"
+ *ngIf="healthData.pg_info">
+ {{ healthData.pg_info.pgs_per_osd | dimless }}
+ </cd-info-card>
+ </cd-info-group>
+
+ <cd-info-group groupTitle="Performance"
+ i18n-groupTitle
+ *ngIf="healthData.client_perf || healthData.scrub_status">
+ <cd-info-card cardTitle="Client Read/Write"
+ i18n-cardTitle
+ class="cd-performance-card cd-chart-card"
+ contentClass="content-chart"
+ *ngIf="healthData.client_perf">
+ <cd-health-pie [data]="healthData"
+ [config]="clientStatsConfig"
+ (prepareFn)="prepareReadWriteRatio($event[0], $event[1])">
+ </cd-health-pie>
+ </cd-info-card>
+
+ <cd-info-card cardTitle="Client Throughput"
+ i18n-cardTitle
+ class="cd-performance-card cd-chart-card"
+ contentClass="content-chart"
+ *ngIf="healthData.client_perf">
+ <cd-health-pie [data]="healthData"
+ [config]="clientStatsConfig"
+ (prepareFn)="prepareClientThroughput($event[0], $event[1])">
+ </cd-health-pie>
+ </cd-info-card>
+
+ <cd-info-card cardTitle="Recovery Throughput"
+ i18n-cardTitle
+ class="cd-performance-card"
+ contentClass="content-highlight"
+ *ngIf="healthData.client_perf">
+ {{ (healthData.client_perf.recovering_bytes_per_sec | dimlessBinary) + '/s' }}
+ </cd-info-card>
+
+ <cd-info-card cardTitle="Scrubbing"
+ i18n-cardTitle
+ class="cd-performance-card"
+ contentClass="content-highlight"
+ *ngIf="healthData.scrub_status">
+ {{ healthData.scrub_status }}
+ </cd-info-card>
</cd-info-group>
<ng-template #logsLink>
expect(infoGroups.length).toBe(3);
const infoCards = fixture.debugElement.nativeElement.querySelectorAll('cd-info-card');
- expect(infoCards.length).toBe(18);
+ expect(infoCards.length).toBe(17);
});
describe('features disabled', () => {
expect(infoGroups.length).toBe(3);
const infoCards = fixture.debugElement.nativeElement.querySelectorAll('cd-info-card');
- expect(infoCards.length).toBe(15);
+ expect(infoCards.length).toBe(14);
});
});
expect(infoGroups.length).toBe(2);
const infoCards = fixture.debugElement.nativeElement.querySelectorAll('cd-info-card');
- expect(infoCards.length).toBe(10);
+ expect(infoCards.length).toBe(9);
});
it('should render all except "Performance" group and cards', () => {
expect(infoGroups.length).toBe(2);
const infoCards = fixture.debugElement.nativeElement.querySelectorAll('cd-info-card');
- expect(infoCards.length).toBe(13);
+ expect(infoCards.length).toBe(12);
});
it('should render all groups and 1 card per group', () => {
});
describe('preparePgStatus', () => {
- const calcPercentage = (data: number) => Math.round((data / 10) * 100) || 0;
-
- const expectedChart = (data: number[]) => ({
+ const expectedChart = (data: number[], label: string = null) => ({
labels: [
- `Clean (${calcPercentage(data[0])}%)`,
- `Working (${calcPercentage(data[1])}%)`,
- `Warning (${calcPercentage(data[2])}%)`,
- `Unknown (${calcPercentage(data[3])}%)`
+ `Clean: ${component['dimless'].transform(data[0])}`,
+ `Working: ${component['dimless'].transform(data[1])}`,
+ `Warning: ${component['dimless'].transform(data[2])}`,
+ `Unknown: ${component['dimless'].transform(data[3])}`
],
options: {},
- dataset: [{ data: data }]
+ dataset: [
+ {
+ data: data.map((i) =>
+ component['calcPercentage'](
+ i,
+ data.reduce((j, k) => j + k)
+ )
+ ),
+ label: label
+ }
+ ]
});
it('gets no data', () => {
component.preparePgStatus(chart, {
pg_info: {}
});
- expect(chart).toEqual(expectedChart([undefined, undefined, undefined, undefined]));
+ expect(chart).toEqual(expectedChart([0, 0, 0, 0], '0\nPGs'));
});
it('gets data from all categories', () => {
}
}
});
- expect(chart).toEqual(expectedChart([1, 2, 3, 4]));
+ expect(chart).toEqual(expectedChart([1, 2, 3, 4], '10\nPGs'));
});
});
} from '../../../shared/services/feature-toggles.service';
import { RefreshIntervalService } from '../../../shared/services/refresh-interval.service';
import { PgCategoryService } from '../../shared/pg-category.service';
-import { HealthPieColor } from '../health-pie/health-pie-color.enum';
@Component({
selector: 'cd-health',
enabledFeature$: FeatureTogglesMap$;
icons = Icons;
- rawCapacityChartConfig = {
- options: {
- title: { display: true, position: 'bottom' }
- }
- };
- objectsChartConfig = {
- options: {
- title: { display: true, position: 'bottom' }
- },
+ clientStatsConfig = {
colors: [
{
- backgroundColor: [
- HealthPieColor.DEFAULT_GREEN,
- HealthPieColor.DEFAULT_MAGENTA,
- HealthPieColor.DEFAULT_ORANGE,
- HealthPieColor.DEFAULT_RED
- ]
+ backgroundColor: ['--color-cyan', '--color-purple']
}
]
};
- pgStatusChartConfig = {
+
+ rawCapacityChartConfig = {
colors: [
{
- backgroundColor: [
- HealthPieColor.DEFAULT_GREEN,
- HealthPieColor.DEFAULT_BLUE,
- HealthPieColor.DEFAULT_ORANGE,
- HealthPieColor.DEFAULT_RED
- ]
+ backgroundColor: ['--color-blue', '--color-gray']
}
]
};
+ pgStatusChartConfig = {
+ options: {
+ events: ['']
+ }
+ };
+
constructor(
private healthService: HealthService,
private i18n: I18n,
this.healthData.client_perf.write_op_per_sec + this.healthData.client_perf.read_op_per_sec;
ratioLabels.push(
- `${this.i18n('Writes')} (${this.calcPercentage(
- this.healthData.client_perf.write_op_per_sec,
- total
- )}%)`
- );
- ratioData.push(this.healthData.client_perf.write_op_per_sec);
- ratioLabels.push(
- `${this.i18n('Reads')} (${this.calcPercentage(
- this.healthData.client_perf.read_op_per_sec,
- total
- )}%)`
+ `${this.i18n(`Reads`)}: ${this.dimless.transform(
+ this.healthData.client_perf.read_op_per_sec
+ )} ${this.i18n(`/s`)}`
);
ratioData.push(this.healthData.client_perf.read_op_per_sec);
+ ratioLabels.push(this.healthData.client_perf.write_op_per_sec);
+ ratioData.push(this.calcPercentage(this.healthData.client_perf.write_op_per_sec, total));
+ chart.labels = ratioLabels;
chart.dataset[0].data = ratioData;
+ chart.dataset[0].label = `${this.dimless.transform(total)}\n${this.i18n(`IOPS`)}`;
+ }
+
+ prepareClientThroughput(chart: Record<string, any>) {
+ const ratioLabels = [];
+ const ratioData = [];
+
+ const total =
+ this.healthData.client_perf.read_bytes_sec + this.healthData.client_perf.write_bytes_sec;
+
+ ratioLabels.push(
+ `${this.i18n(`Reads`)}: ${this.dimlessBinary.transform(
+ this.healthData.client_perf.read_bytes_sec
+ )}${this.i18n(`/s`)}`
+ );
+ ratioData.push(this.calcPercentage(this.healthData.client_perf.read_bytes_sec, total));
+ ratioLabels.push(
+ `${this.i18n(`Writes`)}: ${this.dimlessBinary.transform(
+ this.healthData.client_perf.write_bytes_sec
+ )}${this.i18n(`/s`)}`
+ );
+ ratioData.push(this.calcPercentage(this.healthData.client_perf.write_bytes_sec, total));
+
chart.labels = ratioLabels;
+ chart.dataset[0].data = ratioData;
+ chart.dataset[0].label = `${this.dimlessBinary.transform(total).replace(' ', '\n')}${this.i18n(
+ `/s`
+ )}`;
}
prepareRawUsage(chart: Record<string, any>, data: Record<string, any>) {
data.df.stats.total_bytes
);
- chart.dataset[0].data = [data.df.stats.total_used_raw_bytes, data.df.stats.total_avail_bytes];
+ chart.dataset[0].data = [percentUsed, percentAvailable];
chart.labels = [
- `${this.dimlessBinary.transform(data.df.stats.total_used_raw_bytes)} ${this.i18n(
- 'Used'
- )} (${percentUsed}%)`,
- `${this.dimlessBinary.transform(
+ `${this.i18n(`Used`)}: ${this.dimlessBinary.transform(data.df.stats.total_used_raw_bytes)}`,
+ `${this.i18n(`Avail.`)}: ${this.dimlessBinary.transform(
data.df.stats.total_bytes - data.df.stats.total_used_raw_bytes
- )} ${this.i18n('Avail.')} (${percentAvailable}%)`
+ )}`
];
- chart.options.title.text = `${this.dimlessBinary.transform(
+ chart.dataset[0].label = `${percentUsed}%\nof ${this.dimlessBinary.transform(
data.df.stats.total_bytes
- )} ${this.i18n('total')}`;
+ )}`;
}
preparePgStatus(chart: Record<string, any>, data: Record<string, any>) {
totalPgs += pgAmount;
});
+ for (const categoryType of this.pgCategoryService.getAllTypes()) {
+ if (_.isUndefined(categoryPgAmount[categoryType])) {
+ categoryPgAmount[categoryType] = 0;
+ }
+ }
+
chart.dataset[0].data = this.pgCategoryService
.getAllTypes()
- .map((categoryType) => categoryPgAmount[categoryType]);
+ .map((categoryType) => this.calcPercentage(categoryPgAmount[categoryType], totalPgs));
chart.labels = [
- `${this.i18n('Clean')} (${this.calcPercentage(categoryPgAmount['clean'], totalPgs)}%)`,
- `${this.i18n('Working')} (${this.calcPercentage(categoryPgAmount['working'], totalPgs)}%)`,
- `${this.i18n('Warning')} (${this.calcPercentage(categoryPgAmount['warning'], totalPgs)}%)`,
- `${this.i18n('Unknown')} (${this.calcPercentage(categoryPgAmount['unknown'], totalPgs)}%)`
+ `${this.i18n(`Clean`)}: ${this.dimless.transform(categoryPgAmount['clean'])}`,
+ `${this.i18n(`Working`)}: ${this.dimless.transform(categoryPgAmount['working'])}`,
+ `${this.i18n(`Warning`)}: ${this.dimless.transform(categoryPgAmount['warning'])}`,
+ `${this.i18n(`Unknown`)}: ${this.dimless.transform(categoryPgAmount['unknown'])}`
];
+
+ chart.dataset[0].label = `${totalPgs}\n${this.i18n(`PGs`)}`;
}
prepareObjects(chart: Record<string, any>, data: Record<string, any>) {
- const totalReplicas = data.pg_info.object_stats.num_object_copies;
+ const objectCopies = data.pg_info.object_stats.num_object_copies;
const healthy =
- totalReplicas -
+ objectCopies -
data.pg_info.object_stats.num_objects_misplaced -
data.pg_info.object_stats.num_objects_degraded -
data.pg_info.object_stats.num_objects_unfound;
+ const healthyPercentage = this.calcPercentage(healthy, objectCopies);
+ const misplacedPercentage = this.calcPercentage(
+ data.pg_info.object_stats.num_objects_misplaced,
+ objectCopies
+ );
+ const degradedPercentage = this.calcPercentage(
+ data.pg_info.object_stats.num_objects_degraded,
+ objectCopies
+ );
+ const unfoundPercentage = this.calcPercentage(
+ data.pg_info.object_stats.num_objects_unfound,
+ objectCopies
+ );
chart.labels = [
- `${this.i18n('Healthy')} (${this.calcPercentage(healthy, totalReplicas)}%)`,
- `${this.i18n('Misplaced')} (${this.calcPercentage(
- data.pg_info.object_stats.num_objects_misplaced,
- totalReplicas
- )}%)`,
- `${this.i18n('Degraded')} (${this.calcPercentage(
- data.pg_info.object_stats.num_objects_degraded,
- totalReplicas
- )}%)`,
- `${this.i18n('Unfound')} (${this.calcPercentage(
- data.pg_info.object_stats.num_objects_unfound,
- totalReplicas
- )}%)`
+ `${this.i18n(`Healthy`)}: ${healthyPercentage}%`,
+ `${this.i18n(`Misplaced`)}: ${misplacedPercentage}%`,
+ `${this.i18n(`Degraded`)}: ${degradedPercentage}%`,
+ `${this.i18n(`Unfound`)}: ${unfoundPercentage}%`
];
chart.dataset[0].data = [
- healthy,
- data.pg_info.object_stats.num_objects_misplaced,
- data.pg_info.object_stats.num_objects_degraded,
- data.pg_info.object_stats.num_objects_unfound
+ healthyPercentage,
+ misplacedPercentage,
+ degradedPercentage,
+ unfoundPercentage
];
- chart.options.title.text = `${this.dimless.transform(
+ chart.dataset[0].label = `${this.dimless.transform(
data.pg_info.object_stats.num_objects
- )} ${this.i18n('total')} (${this.dimless.transform(totalReplicas)} ${this.i18n('replicas')})`;
-
- chart.options.maintainAspectRatio = window.innerWidth >= 375;
+ )}\n${this.i18n(`objects`)}`;
}
isClientReadWriteChartShowable() {
<div class="card"
[ngClass]="cardClass">
<div class="card-body d-flex align-items-center justify-content-center">
- <h5 class="card-title m-4">
+ <h4 class="card-title m-4">
<a *ngIf="link; else noLinkTitle"
[routerLink]="link">{{ cardTitle }}</a>
<ng-template #noLinkTitle>
{{ cardTitle }}
</ng-template>
- </h5>
+ </h4>
<div class="card-text text-center"
[ngClass]="contentClass">
padding-top: 40px !important;
.card-title {
+ left: -0.6rem;
position: absolute;
- left: 0;
- top: 0;
+ top: -0.3rem;
}
}
}
$form-feedback-font-size: 100%;
$popover-max-width: 350px;
+// https://getbootstrap.com/docs/4.5/layout/grid/#variables
+$grid-breakpoints: (
+ xs: 0,
+ sm: 576px,
+ md: 768px,
+ lg: 992px,
+ xl: 1200px,
+ 2xl: 1450px
+);
+
@import '~bootstrap/scss/bootstrap';
@import '~fork-awesome/scss/fork-awesome';
@import 'app/ceph/dashboard/info-card/info-card-popover.scss';
@extend .pb-2;
.card-body {
+ .card-title {
+ @extend .pl-2;
+ }
+
.card-text {
@extend .pt-2;
}
&.cd-capacity-card {
@extend .col-xl;
}
+
+ &.cd-capacity-card {
+ @extend .col-lg-3;
+ }
+
+ &.cd-performance-card {
+ @extend .col-lg-6;
+ }
+
+ &.cd-chart-card {
+ @extend .col-md-12;
+ @extend .col-lg-6;
+ @extend .col-xl-4;
+ @extend .col-2xl-3;
+ }
}
}
}
}
}
+
+// This was backported from _bootstrap-defaults.scss in master
+$health-chart-colors: (
+ 'red': #c9190b,
+ 'blue': #06c,
+ 'orange': #ef9234,
+ 'yellow': #f6d173,
+ 'magenta': #009596,
+ 'green': #7cc674,
+ 'gray': #ededed,
+ 'light-blue': #519de9,
+ 'light-yellow': #f9e0a2,
+ 'cyan': #73c5c5,
+ 'purple': #3c3d99
+);