Unverified Commit c542ac6a authored by Denys Butenko's avatar Denys Butenko Committed by GitHub
Browse files

NAS-117177 / 22.12 / Finish Space Management Card (#6880)

parent efbdf8a0
Showing with 491 additions and 84 deletions
+491 -84
......@@ -102,6 +102,11 @@ export class ThemeUtils {
return output.map((str) => parseFloat(str));
}
rgbToString(rgb: number[], alpha?: number): string {
const a = alpha ? alpha.toString() : '1';
return 'rgba(' + rgb.join(',') + ',' + a + ')';
}
colorFromMeta(meta: string): string {
const trimFront = meta.replace('var(--', '');
const trimmed = trimFront.replace(')', '');
......
......@@ -170,7 +170,7 @@ import { ImportDiskParams, PoolFindResult, PoolImportParams } from 'app/interfac
import { CreatePoolScrubTask, PoolScrubTask, PoolScrubTaskParams } from 'app/interfaces/pool-scrub.interface';
import { PoolUnlockQuery, PoolUnlockResult } from 'app/interfaces/pool-unlock-query.interface';
import {
CreatePool, Pool, PoolAttachParams, PoolExpandParams, PoolReplaceParams, UpdatePool,
CreatePool, Pool, PoolAttachParams, PoolExpandParams, PoolInstance, PoolInstanceParams, PoolReplaceParams, UpdatePool,
} from 'app/interfaces/pool.interface';
import { Process } from 'app/interfaces/process.interface';
import { QueryParams } from 'app/interfaces/query-api.interface';
......@@ -720,6 +720,7 @@ export type ApiDirectory = {
'pool.unlock_services_restart_choices': { params: [id: number]; response: Choices };
'pool.update': { params: [id: number, update: UpdatePool]; response: Pool };
'pool.upgrade': { params: [id: number]; response: boolean };
'pool.get_instance_by_name': { params: PoolInstanceParams; response: PoolInstance };
// Replication
'replication.list_datasets': { params: [transport: TransportMode, credentials?: number]; response: string[] };
......
......@@ -8,6 +8,7 @@ export interface VDevGroup {
export type DeviceNestedDataNode = VDev | VDevGroup;
// eslint-disable-next-line @typescript-eslint/naming-convention
export function isVDev(obj: DeviceNestedDataNode): obj is VDev {
return 'stats' in obj;
}
......@@ -114,3 +114,31 @@ export type PoolExpandParams = [
id: number,
params?: { geli: { passphrase: string } },
];
export type PoolInstanceParams = [
name: string,
];
export interface PoolInstance {
id: number;
name: string;
guid: string;
encrypt: number;
encryptkey: string;
encryptkey_path: string;
is_decrypted: boolean;
status: string;
path: string;
scan: PoolScanUpdate;
is_upgraded: boolean;
healthy: boolean;
warning: boolean;
status_detail: string;
size: number;
allocated: number;
free: number;
freeing: number;
fragmentation: string;
autotrim: ZfsProperty<string>;
topology: PoolTopology;
}
......@@ -354,19 +354,14 @@ export class WidgetCpuComponent extends WidgetComponent implements AfterViewInit
const bgRgb = this.utils.convertToRgb((this.currentTheme[color as keyof Theme]) as string).rgb;
ds.backgroundColor = this.rgbToString(bgRgb as any, 0.85);
ds.borderColor = this.rgbToString(bgRgb as any);
ds.backgroundColor = this.utils.rgbToString(bgRgb as any, 0.85);
ds.borderColor = this.utils.rgbToString(bgRgb as any);
datasets.push(ds);
});
return datasets;
}
rgbToString(rgb: string[], alpha?: number): string {
const a = alpha ? alpha.toString() : '1';
return 'rgba(' + rgb.join(',') + ',' + a + ')';
}
stripVar(str: string): string {
return str.replace('var(--', '').replace(')', '');
}
......
......@@ -17,7 +17,6 @@ import {
import { ThemeUtils } from 'app/core/classes/theme-utils/theme-utils';
import { CoreEvent } from 'app/interfaces/events';
import { MemoryStatsEventData } from 'app/interfaces/events/memory-stats-event.interface';
import { Theme } from 'app/interfaces/theme.interface';
import { WidgetComponent } from 'app/pages/dashboard/components/widget/widget.component';
import { WidgetMemoryData } from 'app/pages/dashboard/interfaces/widget-data.interface';
import { CoreService } from 'app/services/core-service/core.service';
......@@ -48,7 +47,6 @@ export class WidgetMemoryComponent extends WidgetComponent implements AfterViewI
configurable = false;
chartId = UUID.UUID();
colorPattern: string[];
currentTheme: Theme;
labels: string[] = [this.translate.instant('Free'), this.translate.instant('ZFS Cache'), this.translate.instant('Services')];
......@@ -136,8 +134,7 @@ export class WidgetMemoryComponent extends WidgetComponent implements AfterViewI
data: this.parseMemData(data),
};
this.memData = config;
this.currentTheme = this.themeService.currentTheme();
this.colorPattern = this.processThemeColors(this.currentTheme);
this.colorPattern = this.themeService.getColorPattern();
this.isReady = true;
this.renderChart();
}
......@@ -218,26 +215,14 @@ export class WidgetMemoryComponent extends WidgetComponent implements AfterViewI
// Create the data...
data.forEach((item, index) => {
const bgColor = this.colorPattern[index];
const bgColorType = this.utils.getValueType(bgColor);
const bgRgb = this.themeService.getRgbBackgroundColorByIndex(index);
const bgRgb = bgColorType === 'hex' ? this.utils.hexToRgb(bgColor).rgb : this.utils.rgbToArray(bgColor);
(ds.backgroundColor as ChartColor[]).push(this.rgbToString(bgRgb, 0.85));
(ds.borderColor as ChartColor[]).push(this.rgbToString(bgRgb));
(ds.backgroundColor as ChartColor[]).push(this.utils.rgbToString(bgRgb, 0.85));
(ds.borderColor as ChartColor[]).push(this.utils.rgbToString(bgRgb));
});
datasets.push(ds);
return datasets;
}
private processThemeColors(theme: Theme): string[] {
return theme.accentColors.map((color) => theme[color]);
}
rgbToString(rgb: number[], alpha?: number): string {
const a = alpha ? alpha.toString() : '1';
return 'rgba(' + rgb.join(',') + ',' + a + ')';
}
}
<mat-card class="card">
<mat-card-header>
<h3 mat-card-title>{{ 'Capacity Management' | translate }}</h3>
<button mat-button (click)="editDataset()" [disabled]="isLoadingProperties">{{ 'Edit' | translate }}</button>
<h3 mat-card-title>
<ng-container *ngIf="isFilesystem">{{ 'Dataset Space Management' | translate }}</ng-container>
<ng-container *ngIf="isZvol">{{ 'Zvol Space Management' | translate }}</ng-container>
</h3>
<button mat-button (click)="editDataset()">{{ 'Edit' | translate }}</button>
</mat-card-header>
<mat-card-content>
<div class="legend-placeholder">
<ng-container *ngIf="isLoadingProperties; else loaderSkel"></ng-container>
<ng-container *ngIf="isLoadingProperties; else loaderSkel"></ng-container>
</div>
<div class="chart-placeholder">
<ng-container *ngIf="isLoadingProperties; else loaderSkel"></ng-container>
</div>
<div class="legend-placeholder">
<ng-container *ngIf="isLoadingProperties; else loaderSkel"></ng-container>
<ng-container *ngIf="isLoadingProperties; else loaderSkel"></ng-container>
<ng-container *ngIf="isLoadingProperties; else loaderSkel"></ng-container>
<ix-space-management-chart [dataset]="dataset"></ix-space-management-chart>
<div class="chart-extra">
<ng-container *ngIf="!isZvol">
<div class="details-item" *ngIf="!isLoadingProperties && extraProperties?.refreservation?.parsed">
<span class="label">{{ 'Reserved for Dataset' | translate }}:</span>
<span class="value">{{ extraProperties.refreservation.parsed | filesize: { standard: 'iec', round: 0 } }}</span>
</div>
<div class="details-item" *ngIf="!isLoadingProperties && extraProperties?.reservation?.parsed">
<span class="label">{{ 'Reserved for Dataset & Children' | translate }}:</span>
<span class="value">{{ extraProperties.reservation.parsed | filesize: { standard: 'iec', round: 0 } }}</span>
</div>
</ng-container>
<ng-container *ngIf="isZvol">
<!-- TODO: Fix me please after NAS-117254 is done -->
<div class="details-item" *ngIf="false">
<span class="label">{{ 'Provisioning Type' | translate }}:</span>
<span class="value">{{ 'Thick' | translate }}</span>
</div>
<div class="details-item">
<span class="label">{{ 'Volume Size' | translate }}:</span>
<span class="value" *ngIf="!isLoadingProperties; else valuePlaceholder">
{{ extraProperties.volsize.parsed | filesize: { standard: 'iec', round: 0 } }}
</span>
</div>
</ng-container>
</div>
<div *ngIf="!dataset.locked">
<div class="details">
<div class="details-item">
<span class="label">
{{ 'User Quotas' | translate }}:
<ng-container *ngIf="!isLoadingProperties; else valuePlaceholder">
<span class="label">
<ng-container *ngIf="isFilesystem">{{ 'Space Available to Dataset' | translate }}</ng-container>
<ng-container *ngIf="isZvol">{{ 'Space Available to Zvol' | translate }}</ng-container>:
</span>
<span class="value">{{ dataset.available.parsed | filesize: { standard: 'iec', round: 0 } }}</span>
</ng-container>
</div>
<div class="details-item" *ngIf="isFilesystem && hasQuota">
<span class="label">{{ 'Applied Dataset Quotas' | translate }}:</span>
<span class="value">
{{ '{n} (applies to descendants)' | translate: { n: extraProperties.quota.parsed | filesize: { standard: 'iec', round: 0 } } }}
</span>
<span *ngIf="!isLoadingQuotas; else loaderSkel" class="value">
{{ 'Quotas set for {n} users' | translate: { n: userQuotas } }}
</div>
<div class="details-item" *ngIf="hasInheritedQuotas">
<span class="label">{{ 'Inherited Quotas' | translate }}:</span>
<span class="value">
{{ '{n} from {dataset}' | translate: { n: inheritedQuotasDataset.quota.parsed | filesize: { standard: 'iec', round: 0 }, dataset: inheritedQuotasDataset.name } }}
</span>
<a [routerLink]="['/', 'datasets', dataset.id, 'user-quotas']">
<a [routerLink]="['/', 'datasets', inheritedQuotasDataset.id]">{{ 'Go To Dataset' }}</a>
</div>
</div>
<div class="details">
<div class="details-item">
<span class="label">{{ 'User Quotas' | translate }}:</span>
<ng-container *ngIf="checkQuotas; else noneTemplate">
<span class="value" *ngIf="!isLoadingQuotas; else valuePlaceholder">
{{ 'Quotas set for {n} users' | translate: { n: userQuotas } }}
</span>
</ng-container>
<a *ngIf="checkQuotas" [routerLink]="['/', 'datasets', dataset.id, 'user-quotas']">
{{ 'Manage User Quotas' | translate }}
</a>
</div>
<div class="details-item">
<span class="label">
{{ 'Group Quotas' | translate }}:
</span>
<span *ngIf="!isLoadingQuotas; else loaderSkel" class="value">
{{ 'Quotas set for {n} groups' | translate: { n: groupQuotas } }}
</span>
<a [routerLink]="['/', 'datasets', dataset.id, 'group-quotas']">
<span class="label">{{ 'Group Quotas' | translate }}:</span>
<ng-container *ngIf="checkQuotas; else noneTemplate">
<span class="value" *ngIf="!isLoadingQuotas; else valuePlaceholder">
{{ 'Quotas set for {n} groups' | translate: { n: groupQuotas } }}
</span>
</ng-container>
<a *ngIf="checkQuotas" [routerLink]="['/', 'datasets', dataset.id, 'group-quotas']">
{{ 'Manage Group Quotas' | translate }}
</a>
</div>
......@@ -44,6 +87,10 @@
</mat-card-content>
</mat-card>
<ng-template #loaderSkel>
<ngx-skeleton-loader></ngx-skeleton-loader>
<ng-template #valuePlaceholder>
<ngx-skeleton-loader class="value-placeholder"></ngx-skeleton-loader>
</ng-template>
<ng-template #noneTemplate>
{{ 'None' | translate }}
</ng-template>
......@@ -2,22 +2,31 @@
.card {
@include details-card();
}
.legend-placeholder {
align-items: center;
display: flex;
gap: 12px;
}
.chart-extra {
align-items: center;
display: flex;
justify-content: space-around;
.chart-placeholder {
align-items: center;
display: flex;
gap: 12px;
.details-item {
margin: 20px 0 0;
}
}
ngx-skeleton-loader {
::ng-deep .loader {
height: 64px;
.details-item {
.value-placeholder {
flex: 1;
}
}
}
.details {
border-top: 1px solid var(--lines);
box-sizing: border-box;
margin: 20px -25px 0; // Fragile. Rely on external styles
padding: 20px 25px 0 !important; // Fragile. Rely on external styles
}
ix-space-management-chart {
padding: 0 0 0 8px;
}
......@@ -2,14 +2,14 @@ import {
Component, ChangeDetectionStrategy, ChangeDetectorRef, Input, OnChanges, OnInit,
} from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { maxBy } from 'lodash';
import { Subscription, forkJoin } from 'rxjs';
import { map } from 'rxjs/operators';
import { DatasetQuotaType } from 'app/enums/dataset.enum';
import { map, take } from 'rxjs/operators';
import { DatasetType, DatasetQuotaType } from 'app/enums/dataset.enum';
import { Dataset } from 'app/interfaces/dataset.interface';
import {
DatasetCapacitySettingsComponent,
} from 'app/pages/datasets/components/dataset-capacity-management-card/dataset-capacity-settings/dataset-capacity-settings.component';
import { DatasetCapacitySettingsComponent } from 'app/pages/datasets/components/dataset-capacity-management-card/dataset-capacity-settings/dataset-capacity-settings.component';
import { DatasetInTree } from 'app/pages/datasets/store/dataset-in-tree.interface';
import { DatasetTreeStore } from 'app/pages/datasets/store/dataset-store.service';
import { WebSocketService } from 'app/services';
import { IxSlideInService } from 'app/services/ix-slide-in.service';
......@@ -23,17 +23,39 @@ import { IxSlideInService } from 'app/services/ix-slide-in.service';
export class DatasetCapacityManagementCardComponent implements OnInit, OnChanges {
@Input() dataset: DatasetInTree;
inheritedQuotasDataset: DatasetInTree;
extraProperties: Dataset;
extraPropertiesSubscription: Subscription;
isLoadingQuotas = false;
isLoadingProperties = false;
isLoadingQuotas = false;
quotasSubscription: Subscription;
userQuotas: number;
groupQuotas: number;
get isFilesystem(): boolean {
return this.dataset.type === DatasetType.Filesystem;
}
get isZvol(): boolean {
return this.dataset.type === DatasetType.Volume;
}
get checkQuotas(): boolean {
return !this.dataset.locked && this.isFilesystem;
}
get hasQuota(): boolean {
return Boolean(this.extraProperties?.quota?.parsed);
}
get hasInheritedQuotas(): boolean {
return this.inheritedQuotasDataset?.quota?.parsed && this.inheritedQuotasDataset?.id !== this.dataset?.id;
}
constructor(
private ws: WebSocketService,
private cdr: ChangeDetectorRef,
private datasetStore: DatasetTreeStore,
private slideInService: IxSlideInService,
) {}
......@@ -45,7 +67,8 @@ export class DatasetCapacityManagementCardComponent implements OnInit, OnChanges
ngOnChanges(): void {
this.loadExtraProperties();
if (!this.dataset.locked) {
this.getInheritedQuotas();
if (this.checkQuotas) {
this.getQuotas();
}
}
......@@ -83,6 +106,20 @@ export class DatasetCapacityManagementCardComponent implements OnInit, OnChanges
});
}
getInheritedQuotas(): void {
this.datasetStore.selectedBranch$.pipe(
map((datasets) => {
const datasetWithQuotas = datasets.filter((dataset) => Boolean(dataset?.quota?.parsed));
return maxBy(datasetWithQuotas, (dataset) => dataset.quota.parsed);
}),
take(1),
untilDestroyed(this),
).subscribe((dataset) => {
this.inheritedQuotasDataset = dataset;
this.cdr.markForCheck();
});
}
editDataset(): void {
const editDatasetComponent = this.slideInService.open(DatasetCapacitySettingsComponent, { wide: true });
editDatasetComponent.setDatasetForEdit(this.extraProperties);
......
......@@ -12,6 +12,7 @@
<div *ngIf="!dataset.locked" class="add-buttons">
<button
*ngIf="!isZvol"
mat-button
(click)="onAddZvol()"
>{{ 'Add Zvol' | translate }}</button>
......
......@@ -51,6 +51,10 @@ export class DatasetDetailsPanelComponent implements OnInit {
return isRootDataset(this.dataset);
}
get isZvol(): boolean {
return this.dataset.type === DatasetType.Volume;
}
onAddDataset(): void {
const addDatasetComponent = this.modalService.openInSlideIn(DatasetFormComponent);
addDatasetComponent.setParent(this.dataset.id);
......
......@@ -6,9 +6,9 @@
<span class="name">{{ label }}</span>
</div>
<div class="cell">
{{ '{value} Used' | translate: { value: dataset.used.parsed | filesize: { standard: 'iec' } } }}
{{ '{value} Used' | translate: { value: dataset.used.parsed | filesize: { standard: 'iec', round: 0 } } }}
/
{{ '{value} Left' | translate: { value: dataset.available.parsed | filesize: { standard: 'iec' } } }}
{{ '{value} Left' | translate: { value: dataset.available.parsed | filesize: { standard: 'iec', round: 0 } } }}
</div>
<div class="cell cell-encryption">
<ix-dataset-encryption-cell [dataset]="dataset"></ix-dataset-encryption-cell>
......
<div class="chart-wrapper" *ngIf="!isLoading; else chartPlaceholder">
<canvas
#canvas
baseChart
chartType="doughnut"
[datasets]="chartData"
[options]="chartOptions"
[width]="180"
[height]="180"
></canvas>
</div>
<div class="chart-description">
<h3 class="chart-header">
<span class="chart-title">{{ 'Total Allocation' | translate }}:</span>
<span *ngIf="!isLoading; else valueLoader">
{{ extraProperties.used.parsed | filesize: { standard: 'iec', round: 0 } }}
</span>
</h3>
<div class="legend-wrapper" *ngIf="!isLoading; else legendPlaceholder">
<div class="legend-list-item" *ngIf="extraProperties.usedbydataset.parsed">
<span class="legend-label">
<span class="legend-swatch"></span>
{{ 'Data Written' | translate }}
</span>
<span class="legend-value">
{{ extraProperties.usedbydataset.parsed | filesize: { standard: 'iec', round: 0 } }}
<ng-container *ngIf="extraProperties.usedbydataset.parsed">
({{ extraProperties.usedbydataset.parsed / extraProperties.used.parsed | percent }})
</ng-container>
</span>
</div>
<div class="legend-list-item" *ngIf="extraProperties.usedbysnapshots.parsed">
<span class="legend-label">
<span class="legend-swatch"></span>
<ng-container *ngIf="!isZvol">{{ 'Snapshots' | translate }}</ng-container>
<ng-container *ngIf="isZvol">{{ 'Used by Snapshots' | translate }}</ng-container>
</span>
<span class="legend-value">
{{ extraProperties.usedbysnapshots.parsed | filesize: { standard: 'iec', round: 0 } }}
<ng-container *ngIf="extraProperties.usedbysnapshots.parsed">
({{ extraProperties.usedbysnapshots.parsed / extraProperties.used.parsed | percent }})
</ng-container>
</span>
</div>
<ng-container *ngIf="!isZvol">
<div class="legend-list-item" *ngIf="extraProperties.usedbychildren.parsed">
<span class="legend-label">
<span class="legend-swatch"></span>
{{ 'Children' | translate }}
</span>
<span class="legend-value">
{{ extraProperties.usedbychildren.parsed | filesize: { standard: 'iec', round: 0 } }}
<ng-container *ngIf="extraProperties.usedbychildren.parsed">
({{ extraProperties.usedbychildren.parsed / extraProperties.used.parsed | percent }})
</ng-container>
</span>
</div>
</ng-container>
</div>
</div>
<ng-template #legendPlaceholder>
<ngx-skeleton-loader class="legend-placeholder"></ngx-skeleton-loader>
<ngx-skeleton-loader class="legend-placeholder"></ngx-skeleton-loader>
<ngx-skeleton-loader class="legend-placeholder"></ngx-skeleton-loader>
</ng-template>
<ng-template #chartPlaceholder>
<ngx-skeleton-loader class="chart-placeholder"></ngx-skeleton-loader>
</ng-template>
<ng-template #valueLoader>
<ngx-skeleton-loader class="value-placeholder"></ngx-skeleton-loader>
</ng-template>
$chart-size: 180px !default;
:host {
display: flex;
gap: 24px;
min-height: $chart-size;
}
.chart-wrapper {
height: $chart-size;
width: $chart-size;
}
.chart-description {
flex: 1;
}
.chart-header {
align-items: center;
border-bottom: 1px solid var(--lines);
box-sizing: border-box;
display: flex;
font-size: 18px;
font-weight: normal;
gap: 8px;
margin-bottom: 12px;
padding: 16px 8px 12px;
white-space: nowrap;
}
.chart-placeholder {
align-items: center;
display: flex;
flex: 0 0 auto;
height: $chart-size;
width: $chart-size;
::ng-deep .loader {
border-radius: 50%;
height: $chart-size;
margin: 0;
width: $chart-size;
}
}
.value-placeholder {
align-items: center;
display: inline-flex;
flex: 1;
::ng-deep .loader {
margin: 0;
}
}
.legend-placeholder {
align-items: center;
display: flex;
flex: 1;
margin-bottom: 8px;
::ng-deep .loader {
margin: 0;
}
}
.legend-list-item {
align-items: center;
display: flex;
justify-content: space-between;
margin-bottom: 8px;
.legend-label {
align-items: center;
display: flex;
gap: 8px;
}
.legend-swatch {
background-color: var(--lines);
border-radius: 50%;
display: inline-block;
height: 12px;
width: 12px;
}
&:nth-child(1) .legend-swatch {
background-color: var(--blue);
}
&:nth-child(2) .legend-swatch {
background-color: var(--magenta);
}
&:nth-child(3) .legend-swatch {
background-color: var(--orange);
}
&:nth-child(4) .legend-swatch {
background-color: var(--orange);
}
}
import {
ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges,
} from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { ChartColor, ChartDataSets, ChartOptions } from 'chart.js';
import { Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import { DatasetType } from 'app/enums/dataset.enum';
import { Dataset } from 'app/interfaces/dataset.interface';
import { DatasetInTree } from 'app/pages/datasets/store/dataset-in-tree.interface';
import { WebSocketService } from 'app/services';
import { ThemeService } from 'app/services/theme/theme.service';
@UntilDestroy()
@Component({
selector: 'ix-space-management-chart',
templateUrl: './space-management-chart.component.html',
styleUrls: ['./space-management-chart.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SpaceManagementChartComponent implements OnChanges {
@Input() dataset: DatasetInTree;
isLoading = false;
extraProperties: Dataset;
subscription: Subscription;
chartData: ChartDataSets[] = [{ data: [] }];
chartOptions: ChartOptions = {
tooltips: {
enabled: false,
},
responsive: false,
maintainAspectRatio: true,
legend: {
display: false,
},
responsiveAnimationDuration: 0,
animation: {
duration: 0,
animateRotate: false,
animateScale: false,
},
hover: {
animationDuration: 0,
},
};
get isZvol(): boolean {
return this.dataset.type === DatasetType.Volume;
}
constructor(
private cdr: ChangeDetectorRef,
private ws: WebSocketService,
private themeService: ThemeService,
) {}
ngOnChanges(): void {
this.loadExtraProperties();
}
loadExtraProperties(): void {
this.isLoading = true;
this.cdr.markForCheck();
this.subscription?.unsubscribe();
this.subscription = this.ws.call('pool.dataset.query', [[['id', '=', this.dataset.id]]]).pipe(
map((datasets) => datasets[0]),
untilDestroyed(this),
).subscribe((dataset) => {
this.extraProperties = dataset;
this.updateChartData();
this.isLoading = false;
this.cdr.markForCheck();
});
}
private updateChartData(): void {
const data: number[] = [];
if (this.isZvol) {
data.push(
this.extraProperties.usedbydataset.parsed,
this.extraProperties.usedbysnapshots.parsed,
);
} else {
data.push(
this.extraProperties.usedbydataset.parsed,
this.extraProperties.usedbysnapshots.parsed,
this.extraProperties.usedbychildren.parsed,
);
}
this.chartData = this.makeDatasets(data);
}
private makeDatasets(data: number[]): ChartDataSets[] {
const datasets: ChartDataSets[] = [];
const filteredData = data.filter(Boolean);
const ds: ChartDataSets = {
data: filteredData,
backgroundColor: [],
borderColor: [],
borderWidth: 1,
type: 'doughnut',
};
filteredData.forEach((_, index) => {
const bgRgb = this.themeService.getRgbBackgroundColorByIndex(index);
(ds.backgroundColor as ChartColor[]).push(this.themeService.getUtils().rgbToString(bgRgb, 0.85));
(ds.borderColor as ChartColor[]).push(this.themeService.getUtils().rgbToString(bgRgb));
});
datasets.push(ds);
return datasets;
}
}
......@@ -10,6 +10,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSortModule } from '@angular/material/sort';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { ChartsModule } from 'ng2-charts';
import { NgxFilesizeModule } from 'ngx-filesize';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { CommonDirectivesModule } from 'app/directives/common/common-directives.module';
......@@ -39,11 +40,13 @@ import { DatasetCapacitySettingsComponent } from './components/dataset-capacity-
import { DatasetIconComponent } from './components/dataset-icon/dataset-icon.component';
import { DatasetEncryptionCellComponent } from './components/dataset-node/dataset-encryption-cell/dataset-encryption-cell.component';
import { DatasetNodeComponent } from './components/dataset-node/dataset-node.component';
import { SpaceManagementChartComponent } from './components/space-management-chart/space-management-chart.component';
@NgModule({
imports: [
CommonModule,
CommonDirectivesModule,
ChartsModule,
LayoutModule,
routing,
TranslateModule,
......@@ -86,6 +89,7 @@ import { DatasetNodeComponent } from './components/dataset-node/dataset-node.com
DatasetIconComponent,
DatasetEncryptionCellComponent,
ZvolFormComponent,
SpaceManagementChartComponent,
DatasetCapacitySettingsComponent,
],
providers: [
......
......@@ -18,4 +18,5 @@ export type DatasetInTree = Pick<Dataset,
| 'pool'
| 'type'
| 'used'
| 'quota'
>;
......@@ -50,6 +50,7 @@ describe('DatasetTreeStore', () => {
'encryptionroot',
'keyformat',
'keystatus',
'quota',
],
},
order_by: ['name'],
......
......@@ -80,6 +80,7 @@ export class DatasetTreeStore extends ComponentStore<DatasetTreeState> {
'encryptionroot',
'keyformat',
'keystatus',
'quota',
],
},
order_by: ['name'],
......
......@@ -20,7 +20,6 @@ import { ProductType } from 'app/enums/product-type.enum';
import { CoreEvent } from 'app/interfaces/events';
import { ReportingGraph } from 'app/interfaces/reporting-graph.interface';
import { ReportingAggregationKeys, ReportingData } from 'app/interfaces/reporting.interface';
import { Theme } from 'app/interfaces/theme.interface';
import { EmptyConfig, EmptyType } from 'app/modules/entity/entity-empty/entity-empty.component';
import { WidgetComponent } from 'app/pages/dashboard/components/widget/widget.component';
import { LineChartComponent } from 'app/pages/reports-dashboard/components/line-chart/line-chart.component';
......@@ -213,8 +212,8 @@ export class ReportComponent extends WidgetComponent implements AfterViewInit, O
filter(Boolean),
map(() => this.themeService.currentTheme()),
untilDestroyed(this),
).subscribe((theme: Theme) => {
this.chartColors = this.processThemeColors(theme);
).subscribe(() => {
this.chartColors = this.themeService.getColorPattern();
});
this.store$.select(selectTimezone).pipe(untilDestroyed(this)).subscribe((timezone) => {
......@@ -256,10 +255,6 @@ export class ReportComponent extends WidgetComponent implements AfterViewInit, O
this.fetchReportData(rrdOptions, changes.report.currentValue, identifier);
}
private processThemeColors(theme: Theme): string[] {
return theme.accentColors.map((color) => theme[color]);
}
setChartInteractive(value: boolean): void {
this.isActive = value;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment