2 ChangeDetectionStrategy,
9 } from '@angular/core';
18 } from 'carbon-components-angular';
19 import { ProductiveCardComponent } from '~/app/shared/components/productive-card/productive-card.component';
20 import { MeterChartComponent, MeterChartOptions } from '@carbon/charts-angular';
23 PromethuesGaugeMetricResult,
25 } from '~/app/shared/api/prometheus.service';
26 import { FormatterService } from '~/app/shared/services/formatter.service';
27 import { BehaviorSubject, Subject } from 'rxjs';
28 import { switchMap, takeUntil } from 'rxjs/operators';
30 const CHART_HEIGHT = '45px';
34 BLOCK: $localize`Block`,
35 FILE: $localize`Filesystem`,
36 OBJECT: $localize`Object`
39 const CapacityType = {
50 [CapacityType.RAW]: `sum by (application) (ceph_pool_bytes_used * on(pool_id) group_left(instance, name, application) ceph_pool_metadata{application=~"(.*Block.*)|(.*Filesystem.*)|(.*Object.*)|(..*)"})`,
51 [CapacityType.USED]: `sum by (application) (ceph_pool_stored * on(pool_id) group_left(instance, name, application) ceph_pool_metadata{application=~"(.*Block.*)|(.*Filesystem.*)|(.*Object.*)|(..*)"})`
54 const chartGroupLabels = [StorageType.BLOCK, StorageType.FILE, StorageType.OBJECT];
57 selector: 'cd-overview-storage-card',
61 ProductiveCardComponent,
70 templateUrl: './overview-storage-card.component.html',
71 styleUrl: './overview-storage-card.component.scss',
72 encapsulation: ViewEncapsulation.None,
73 changeDetection: ChangeDetectionStrategy.OnPush
75 export class OverviewStorageCardComponent implements OnInit, OnDestroy {
77 set total(value: number) {
78 const [totalValue, totalUnit] = this.formatterService.formatToBinary(value, true);
79 if (Number.isNaN(totalValue)) return;
80 this.totalRaw = totalValue;
81 this.totalRawUnit = totalUnit;
82 this.setTotalAndUsed();
85 set used(value: number) {
86 const [usedValue, usedUnit] = this.formatterService.formatToBinary(value, true);
87 if (Number.isNaN(usedValue)) return;
88 this.usedRaw = usedValue;
89 this.usedRawUnit = usedUnit;
90 this.setTotalAndUsed();
96 isRawCapacity: boolean = true;
97 selectedStorageType: string = StorageType.ALL;
98 selectedCapacityType: string = CapacityType.RAW;
99 options: MeterChartOptions = {
100 height: CHART_HEIGHT,
105 breakdownFormatter: (_e) => null,
106 totalFormatter: (_e) => null
118 allData: ChartData[] = null;
119 displayData: ChartData[] = null;
121 { content: StorageType.ALL },
122 { content: StorageType.BLOCK },
123 { content: StorageType.FILE },
124 { content: StorageType.OBJECT }
128 private prometheusService: PrometheusService,
129 private formatterService: FormatterService,
130 private cdr: ChangeDetectorRef
133 private destroy$ = new Subject<void>();
134 private capacityType$ = new BehaviorSubject<string>(CapacityType.RAW);
136 private setTotalAndUsed() {
137 // Chart reacts to 'options' and 'data' object changes only, hence mandatory to replace whole object.
141 ...this.options.meter,
143 ...this.options.meter.proportional,
144 total: this.totalRaw,
145 unit: this.totalRawUnit
149 valueFormatter: (value) => `${value.toLocaleString()} ${this.usedRawUnit}`
155 private getAllData(data: PromqlGuageMetric) {
156 const result = data?.result ?? [];
157 const chartData = result
158 .map((r: PromethuesGaugeMetricResult) => {
159 const group = r?.metric?.application;
160 const value = this.formatterService.convertToUnit(r?.value?.[1], 'B', this.usedRawUnit, 10);
161 return { group, value };
163 // Removing 0 values and legends other than Block, Filesystem, and Object.
164 .filter((r) => chartGroupLabels.includes(r.group) && r.value > 0);
168 private setChartData() {
169 if (this.selectedStorageType === StorageType.ALL) {
170 this.displayData = this.allData;
172 this.displayData = this.allData.filter(
173 (d: ChartData) => d.group === this.selectedStorageType
178 private setDropdownItemsAndStorageType() {
179 const dynamicItems = this.allData.map((data) => ({ content: data.group }));
180 const hasExistingItem = dynamicItems.some((item) => item.content === this.selectedStorageType);
182 if (dynamicItems.length === 1) {
183 this.dropdownItems = dynamicItems;
184 this.selectedStorageType = dynamicItems[0]?.content;
186 this.dropdownItems = [{ content: StorageType.ALL }, ...dynamicItems];
188 // Change the current dropdown selection to 'ALL' if prev selection is absent in current data, and current data has more than one item.
189 if (!hasExistingItem && dynamicItems.length > 1) {
190 this.selectedStorageType = StorageType.ALL;
194 private updateCard() {
195 this.cdr.markForCheck();
198 public toggleRawCapacity(isChecked: boolean) {
199 this.isRawCapacity = isChecked;
200 this.selectedCapacityType = isChecked ? CapacityType.RAW : CapacityType.USED;
201 // Reloads Prometheus Query
202 this.capacityType$.next(this.selectedCapacityType);
205 public onStorageTypeSelect(selected: { item: { content: string; selected: true } }) {
206 this.selectedStorageType = selected?.item?.content;
213 switchMap((capacityType) =>
214 this.prometheusService.getPrometheusQueryData({
215 params: Query[capacityType]
218 takeUntil(this.destroy$)
220 .subscribe((data: PromqlGuageMetric) => {
221 this.allData = this.getAllData(data);
222 this.setDropdownItemsAndStorageType();
228 ngOnDestroy(): void {
229 this.destroy$.next();
230 this.destroy$.complete();