Skip to content

Commit

Permalink
mgr/dashboard: capacity card
Browse files Browse the repository at this point in the history
mgr/dashboard: fix variable names and using Math.round() now

mgr/dashboard cluster endpoint changes

mgr/dashboard: refactoring

mgr/dashboard: string concatenation to interpolation and typedict to namedtuple

mgr/dashboard: change function order

Signed-off-by: Pedro Gonzalez Gomez <pegonzal@redhat.com>
  • Loading branch information
Pegonzal committed Aug 24, 2022
1 parent 176649c commit 569e5f1
Show file tree
Hide file tree
Showing 13 changed files with 356 additions and 11 deletions.
4 changes: 4 additions & 0 deletions src/pybind/mgr/dashboard/controllers/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ def list(self):
parameters={'status': (str, 'Cluster Status')})
def singleton_set(self, status: str):
ClusterModel(status).to_db()

@RESTController.Collection('GET', 'capacity')
def get_capacity(self):
return ClusterModel.get_capacity()
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div class="chart-container">
<canvas baseChart
#chartCanvas
[datasets]="chartConfig.dataset"
[chartType]="chartConfig.chartType"
[options]="chartConfig.options"
[labels]="chartConfig.labels"
[colors]="chartConfig.colors"
[plugins]="doughnutChartPlugins"
class="chart-canvas">
</canvas>
<div class="chartjs-tooltip"
#chartTooltip>
<table></table>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@use './src/styles/chart-tooltip';

$canvas-width: 100%;
$canvas-height: 100%;

.chart-container {
height: $canvas-height;
margin-left: auto;
margin-right: auto;
position: unset;
width: $canvas-width;
}

.chart-canvas {
height: $canvas-height;
margin-left: auto;
margin-right: auto;
max-height: $canvas-height;
max-width: $canvas-width;
position: unset;
width: $canvas-width;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CssHelper } from '~/app/shared/classes/css-helper';
import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
import { configureTestBed } from '~/testing/unit-test-helper';
import { DashboardPieComponent } from './dashboard-pie.component';

describe('DashboardPieComponent', () => {
let component: DashboardPieComponent;
let fixture: ComponentFixture<DashboardPieComponent>;

configureTestBed({
schemas: [NO_ERRORS_SCHEMA],
declarations: [DashboardPieComponent],
providers: [CssHelper, DimlessBinaryPipe]
});

beforeEach(() => {
fixture = TestBed.createComponent(DashboardPieComponent);
component = fixture.componentInstance;
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import { Component, Input, OnChanges, OnInit } from '@angular/core';

import * as Chart from 'chart.js';
import _ from 'lodash';
import { PluginServiceGlobalRegistrationAndOptions } from 'ng2-charts';

import { CssHelper } from '~/app/shared/classes/css-helper';
import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';

@Component({
selector: 'cd-dashboard-pie',
templateUrl: './dashboard-pie.component.html',
styleUrls: ['./dashboard-pie.component.scss']
})
export class DashboardPieComponent implements OnChanges, OnInit {
@Input()
data: any;
@Input()
highThreshold: number;
@Input()
lowThreshold: number;

color: string;

chartConfig: any = {
chartType: 'doughnut',
labels: ['', '', ''],
dataset: [
{
label: null,
backgroundColor: [
this.cssHelper.propertyValue('chart-color-light-gray'),
this.cssHelper.propertyValue('chart-color-slight-dark-gray'),
this.cssHelper.propertyValue('chart-color-dark-gray')
]
},
{
label: null,
borderWidth: 0,
backgroundColor: [
this.cssHelper.propertyValue('chart-color-blue'),
this.cssHelper.propertyValue('chart-color-white')
]
}
],
options: {
cutoutPercentage: 70,
events: ['click', 'mouseout', 'touchstart'],
legend: {
display: true,
position: 'right',
labels: {
boxWidth: 10,
usePointStyle: false,
generateLabels: (chart: any) => {
const labels = { 0: {}, 1: {}, 2: {} };
labels[0] = {
text: $localize`Capacity: ${chart.data.datasets[1].data[0]}%`,
fillStyle: chart.data.datasets[1].backgroundColor[0],
strokeStyle: chart.data.datasets[1].backgroundColor[0]
};
labels[1] = {
text: $localize`Warning: ${chart.data.datasets[0].data[0]}%`,
fillStyle: chart.data.datasets[0].backgroundColor[1],
strokeStyle: chart.data.datasets[0].backgroundColor[1]
};
labels[2] = {
text: $localize`Danger: ${
chart.data.datasets[0].data[0] + chart.data.datasets[0].data[1]
}%`,
fillStyle: chart.data.datasets[0].backgroundColor[2],
strokeStyle: chart.data.datasets[0].backgroundColor[2]
};

return labels;
}
}
},
plugins: {
center_text: true
},
tooltips: {
enabled: true,
displayColors: false,
backgroundColor: this.cssHelper.propertyValue('chart-color-tooltip-background'),
cornerRadius: 0,
bodyFontSize: 14,
bodyFontStyle: '600',
position: 'nearest',
xPadding: 12,
yPadding: 12,
filter: (tooltipItem: any) => {
return tooltipItem.datasetIndex === 1;
},
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
}
}
};

public doughnutChartPlugins: PluginServiceGlobalRegistrationAndOptions[] = [
{
id: 'center_text',
beforeDraw(chart: Chart) {
const cssHelper = new CssHelper();
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;
}

ctx.save();
const label = chart.data.datasets[0].label[0].split('\n');

const centerX = (chart.chartArea.left + chart.chartArea.right) / 2;
const centerY = (chart.chartArea.top + chart.chartArea.bottom) / 2;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';

ctx.font = `24px ${defaultFontFamily}`;
ctx.fillText(label[0], centerX, centerY - 10);

if (label.length > 1) {
ctx.font = `14px ${defaultFontFamily}`;
ctx.fillStyle = cssHelper.propertyValue('chart-color-center-text-description');
ctx.fillText(label[1], centerX, centerY + 10);
}
ctx.restore();
}
}
];

constructor(private cssHelper: CssHelper, private dimlessBinary: DimlessBinaryPipe) {}

ngOnInit() {
this.prepareRawUsage(this.chartConfig, this.data);
}

ngOnChanges() {
this.prepareRawUsage(this.chartConfig, this.data);
}

private prepareRawUsage(chart: Record<string, any>, data: Record<string, any>) {
const nearFullRatioPercent = this.lowThreshold * 100;
const fullRatioPercent = this.highThreshold * 100;
const percentAvailable = this.calcPercentage(data.max - data.current, data.max);
const percentUsed = this.calcPercentage(data.current, data.max);
if (percentUsed >= fullRatioPercent) {
this.color = 'chart-color-red';
} else if (percentUsed >= nearFullRatioPercent) {
this.color = 'chart-color-yellow';
} else {
this.color = 'chart-color-blue';
}

chart.dataset[0].data = [
Math.round(nearFullRatioPercent),
Math.round(Math.abs(nearFullRatioPercent - fullRatioPercent)),
Math.round(100 - fullRatioPercent)
];

chart.dataset[1].data = [percentUsed, percentAvailable];
chart.dataset[1].backgroundColor[0] = this.cssHelper.propertyValue(this.color);

chart.dataset[0].label = [`${percentUsed}%\nof ${this.dimlessBinary.transform(data.max)}`];
}

private calcPercentage(dividend: number, divisor: number) {
if (!_.isNumber(dividend) || !_.isNumber(divisor) || divisor === 0) {
return 0;
}
return Math.ceil((dividend / divisor) * 100 * 100) / 100;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ChartsModule } from 'ng2-charts';
import { SharedModule } from '~/app/shared/shared.module';
import { CephSharedModule } from '../shared/ceph-shared.module';
import { CardComponent } from './card/card.component';
import { DashboardPieComponent } from './dashboard-pie/dashboard-pie.component';
import { DashboardComponent } from './dashboard/dashboard.component';

@NgModule({
Expand All @@ -24,6 +25,6 @@ import { DashboardComponent } from './dashboard/dashboard.component';
ReactiveFormsModule
],

declarations: [DashboardComponent, CardComponent]
declarations: [DashboardComponent, CardComponent, DashboardPieComponent]
})
export class NewDashboardModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@
<cd-card title="Capacity"
i18n-title
class="col-sm-3 px-3">
Text
<ng-container *ngIf="{osdSettings: osdSettings$ | async, capacity: capacity$ | async} as values">
<ng-container *ngIf="values.osdSettings && values.capacity">
<cd-dashboard-pie [data]="{max: values.capacity.total_bytes, current: values.capacity.total_used_raw_bytes}"
[lowThreshold]="values.osdSettings.nearfull_ratio"
[highThreshold]="values.osdSettings.full_ratio">
</cd-dashboard-pie>
</ng-container>
</ng-container>
</cd-card>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';

import { BehaviorSubject, of } from 'rxjs';

import { ConfigurationService } from '~/app/shared/api/configuration.service';
import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
import { CssHelper } from '~/app/shared/classes/css-helper';
import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
import { SummaryService } from '~/app/shared/services/summary.service';
import { configureTestBed } from '~/testing/unit-test-helper';
import { CardComponent } from '../card/card.component';
import { DashboardPieComponent } from '../dashboard-pie/dashboard-pie.component';
import { DashboardComponent } from './dashboard.component';

export class SummaryServiceMock {
Expand Down Expand Up @@ -47,9 +52,14 @@ describe('CardComponent', () => {
};

configureTestBed({
imports: [HttpClientTestingModule],
declarations: [DashboardComponent, CardComponent],
providers: [{ provide: SummaryService, useClass: SummaryServiceMock }]
imports: [RouterTestingModule, HttpClientTestingModule],
declarations: [DashboardComponent, CardComponent, DashboardPieComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [
CssHelper,
DimlessBinaryPipe,
{ provide: SummaryService, useClass: SummaryServiceMock }
]
});

beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,56 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnDestroy, OnInit } from '@angular/core';

import _ from 'lodash';
import { Observable, Subscription } from 'rxjs';

import { ClusterService } from '~/app/shared/api/cluster.service';
import { ConfigurationService } from '~/app/shared/api/configuration.service';
import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
import { OsdService } from '~/app/shared/api/osd.service';
import { DashboardDetails } from '~/app/shared/models/cd-details';
import { Permissions } from '~/app/shared/models/permissions';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import {
FeatureTogglesMap$,
FeatureTogglesService
} from '~/app/shared/services/feature-toggles.service';
import { SummaryService } from '~/app/shared/services/summary.service';

@Component({
selector: 'cd-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
export class DashboardComponent implements OnInit, OnDestroy {
detailsCardData: DashboardDetails = {};

osdSettings$: Observable<any>;
interval = new Subscription();
permissions: Permissions;
enabledFeature$: FeatureTogglesMap$;
color: string;
capacity$: Observable<any>;
constructor(
private summaryService: SummaryService,
private configService: ConfigurationService,
private mgrModuleService: MgrModuleService
) {}
private mgrModuleService: MgrModuleService,
private clusterService: ClusterService,
private osdService: OsdService,
private authStorageService: AuthStorageService,
private featureToggles: FeatureTogglesService
) {
this.permissions = this.authStorageService.getPermissions();
this.enabledFeature$ = this.featureToggles.get();
}

ngOnInit() {
this.getDetailsCardData();

this.osdSettings$ = this.osdService.getOsdSettings();
this.capacity$ = this.clusterService.getCapacity();
}

ngOnDestroy() {
this.interval.unsubscribe();
}

getDetailsCardData() {
Expand Down

0 comments on commit 569e5f1

Please sign in to comment.