From b2360b1a6101b5cc61c236047ce7c757fd02c93d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alfonso=20Mart=C3=ADnez?= Date: Thu, 13 Aug 2020 14:29:38 +0200 Subject: [PATCH] mgr/dashboard: Landing Page improvements MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Fixes: https://tracker.ceph.com/issues/42072 Signed-off-by: Alfonso Martínez (cherry picked from commit d66e684b9ec83cca8a58b0a7b8661c568eb0cf6d) Conflicts: src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.scss src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.html src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.scss src/pybind/mgr/dashboard/frontend/src/styles/defaults/_bootstrap-defaults.scss this file doesn't exist in octopus, so I moved the code into: src/pybind/mgr/dashboard/frontend/src/stykes/defaults.scss --- .../integration/ui/dashboard.e2e-spec.ts | 21 ++- .../health-pie/health-pie-color.enum.ts | 7 - .../health-pie/health-pie.component.html | 1 + .../health-pie/health-pie.component.scss | 7 +- .../health-pie/health-pie.component.spec.ts | 10 ++ .../health-pie/health-pie.component.ts | 151 ++++++++++------- .../dashboard/health/health.component.html | 159 +++++++++--------- .../dashboard/health/health.component.spec.ts | 36 ++-- .../ceph/dashboard/health/health.component.ts | 158 +++++++++-------- .../info-card/info-card.component.html | 4 +- .../info-card/info-card.component.scss | 4 +- .../mgr/dashboard/frontend/src/styles.scss | 10 ++ .../src/styles/bootstrap-extends.scss | 19 +++ .../frontend/src/styles/defaults.scss | 15 ++ 14 files changed, 349 insertions(+), 253 deletions(-) delete mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie-color.enum.ts diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts index f149a4b0ab34c..397745f9745d0 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts @@ -45,23 +45,22 @@ describe('Dashboard Main Page', () => { // 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++) { @@ -72,8 +71,8 @@ describe('Dashboard Main Page', () => { 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'); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie-color.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie-color.enum.ts deleted file mode 100644 index fbeadcc2d3f89..0000000000000 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie-color.enum.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum HealthPieColor { - DEFAULT_RED = '#ff7592', - DEFAULT_BLUE = '#1d699d', - DEFAULT_ORANGE = '#ffa500', - DEFAULT_MAGENTA = '#564d65', - DEFAULT_GREEN = '#00bb00' -} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.html index 02b72b25119df..ba8176beab3b5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.html @@ -6,6 +6,7 @@ [options]="chartConfig.options" [labels]="chartConfig.labels" [colors]="chartConfig.colors" + [plugins]="doughnutChartPlugins" class="chart-canvas">
{ 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']; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.ts index f00d73f5ae6a0..0c08973da8a4f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health-pie/health-pie.component.ts @@ -11,11 +11,11 @@ import { 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', @@ -42,62 +42,102 @@ export class HealthPieComponent implements OnChanges, OnInit { 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, data: Record) => { + 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'; }; @@ -113,39 +153,40 @@ export class HealthPieComponent implements OnChanges, OnInit { 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(': '); @@ -170,18 +211,4 @@ export class HealthPieComponent implements OnChanges, OnInit { 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; - } - }); - } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.html index 84ce11fb23cbd..409bf08eaecbf 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.html @@ -34,7 +34,8 @@ container="body" containerClass="info-card-popover-cluster-status" (click)="healthChecksTarget.toggle()"> - {{ healthData.health.status }} + {{ healthData.health.status }}
@@ -44,6 +45,15 @@ + + {{ healthData.hosts }} total + + - - - {{ healthData.hosts }} total - - - - - - {{ (healthData.client_perf.read_op_per_sec + healthData.client_perf.write_op_per_sec) | round:1 }} - - - - {{ ((healthData.client_perf.read_bytes_sec + healthData.client_perf.write_bytes_sec) | dimlessBinary) + '/s' }} - - - - - - - N/A - - - - - {{ (healthData.client_perf.recovering_bytes_per_sec | dimlessBinary) + '/s' }} - - - - {{ healthData.scrub_status }} - - - - - {{ healthData.pools.length }} - - - - {{ healthData.pg_info.pgs_per_osd | dimless }} - - @@ -243,6 +175,65 @@ + + + {{ healthData.pools.length }} + + + + {{ healthData.pg_info.pgs_per_osd | dimless }} + + + + + + + + + + + + + + + + {{ (healthData.client_perf.recovering_bytes_per_sec | dimlessBinary) + '/s' }} + + + + {{ healthData.scrub_status }} + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.spec.ts index 50a96ee6b51b5..d2fea1406cdcd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.spec.ts @@ -94,7 +94,7 @@ describe('HealthComponent', () => { 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', () => { @@ -119,7 +119,7 @@ describe('HealthComponent', () => { expect(infoGroups.length).toBe(3); const infoCards = fixture.debugElement.nativeElement.querySelectorAll('cd-info-card'); - expect(infoCards.length).toBe(15); + expect(infoCards.length).toBe(14); }); }); @@ -141,7 +141,7 @@ describe('HealthComponent', () => { 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', () => { @@ -172,7 +172,7 @@ describe('HealthComponent', () => { 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', () => { @@ -256,17 +256,25 @@ describe('HealthComponent', () => { }); 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', () => { @@ -274,7 +282,7 @@ describe('HealthComponent', () => { 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', () => { @@ -289,7 +297,7 @@ describe('HealthComponent', () => { } } }); - expect(chart).toEqual(expectedChart([1, 2, 3, 4])); + expect(chart).toEqual(expectedChart([1, 2, 3, 4], '10\nPGs')); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts index 7b6e5f8a0fbc9..11c048374dedb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts @@ -16,7 +16,6 @@ import { } 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', @@ -30,39 +29,28 @@ export class HealthComponent implements OnInit, OnDestroy { 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, @@ -102,22 +90,44 @@ export class HealthComponent implements OnInit, OnDestroy { 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) { + 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, data: Record) { @@ -130,20 +140,18 @@ export class HealthComponent implements OnInit, OnDestroy { 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, data: Record) { @@ -160,54 +168,64 @@ export class HealthComponent implements OnInit, OnDestroy { 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, data: Record) { - 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() { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.html index 526b3e9d74e96..84acc3492bcda 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.html @@ -1,14 +1,14 @@
-
+

{{ cardTitle }} {{ cardTitle }} -

+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.scss index 07732f3edc871..22cc7c7dcd30a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/info-card/info-card.component.scss @@ -21,9 +21,9 @@ $card-font-max-size: 21px; padding-top: 40px !important; .card-title { + left: -0.6rem; position: absolute; - left: 0; - top: 0; + top: -0.3rem; } } } diff --git a/src/pybind/mgr/dashboard/frontend/src/styles.scss b/src/pybind/mgr/dashboard/frontend/src/styles.scss index 12aec546555c8..0965336587087 100644 --- a/src/pybind/mgr/dashboard/frontend/src/styles.scss +++ b/src/pybind/mgr/dashboard/frontend/src/styles.scss @@ -22,6 +22,16 @@ $badge-font-size: 1rem; $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'; diff --git a/src/pybind/mgr/dashboard/frontend/src/styles/bootstrap-extends.scss b/src/pybind/mgr/dashboard/frontend/src/styles/bootstrap-extends.scss index 83ea59c76009b..5932596f82eb1 100644 --- a/src/pybind/mgr/dashboard/frontend/src/styles/bootstrap-extends.scss +++ b/src/pybind/mgr/dashboard/frontend/src/styles/bootstrap-extends.scss @@ -9,6 +9,10 @@ cd-info-card { @extend .pb-2; .card-body { + .card-title { + @extend .pl-2; + } + .card-text { @extend .pt-2; } @@ -61,6 +65,21 @@ cd-health { &.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; + } } } diff --git a/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss b/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss index 1b4382ca1a050..cbae2bb1fd5af 100644 --- a/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss +++ b/src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss @@ -192,3 +192,18 @@ $color-rgw-icon: $color-blue-gray !default; } } } + +// 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 +); -- 2.39.5