<div class="nvmeof-content-layout">
<div class="nvmeof-content-main">
<ng-container *ngIf="gatewayGroup$ | async as gateways">
+ <!-- Details Card Section -->
+ <ng-container *ngIf="selection.hasSelection">
+ <cd-details-card
+ cardTitle="Details"
+ i18n-cardTitle
+ [details]="selectedGatewayDetails"
+ [showEditButton]="true"
+ [editButtonLabel]="actionLabels.EDIT"
+ [columns]="4"
+ (editClicked)="editSelectedGatewayGroup()">
+ </cd-details-card>
+ </ng-container>
+
<cd-table
#table
[data]="gateways"
i18n-emptyStateTitle
emptyStateMessage="Set up your first gateway group to start using NVMe over Fabrics. This will allow you to create high-performance block storage with NVMe/TCP protocol."
i18n-emptyStateMessage>
- <cd-table-actions class="table-actions"
- [permission]="permission"
- [selection]="selection"
- [tableActions]="tableActions">
- </cd-table-actions>
+ <div class="table-actions">
+ <cd-table-actions
+ [permission]="permission"
+ [selection]="selection"
+ [tableActions]="tableActions">
+ </cd-table-actions>
+ </div>
</cd-table>
</ng-container>
</div>
display: block;
margin-bottom: layout.$spacing-05;
}
+
+.table-actions {
+ margin-top: layout.$spacing-03;
+}
this.router.navigate([this.viewUrl, groupName]);
}
+ editSelectedGatewayGroup(): void {
+ const selectedGroup = this.selection.first();
+ if (!selectedGroup) {
+ return;
+ }
+ this.router.navigate([this.urlBuilder.getEdit(selectedGroup.name)]);
+ }
+
+ private buildGatewayDetails(selectedGroup: any): DetailItem[] {
+ if (!selectedGroup) {
+ return [];
+ }
+
+ const runningGateways = selectedGroup.statusCount?.running ?? 0;
+ const errorGateways = selectedGroup.statusCount?.error ?? 0;
+ const totalGateways = runningGateways + errorGateways;
+
+ return [
+ {
+ label: $localize`Gateway name`,
+ value: selectedGroup.name
+ },
+ {
+ label: $localize`Gateway nodes`,
+ value: totalGateways
+ },
+ {
+ label: $localize`Encryption`,
+ value: selectedGroup.spec?.enable_auth ? $localize`Enabled` : $localize`Disabled`,
+ type: 'status'
+ },
+ {
+ label: $localize`mTLS`,
+ value: selectedGroup.spec?.enable_mtls ? $localize`Enabled` : $localize`Disabled`,
+ type: 'status'
+ }
+ ];
+ }
+
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
<div class="content-theme">
@if (mode === 'details' && groupName) {
- <cd-details-card cardTitle="Details" i18n-cardTitle [details]="gatewayDetails" [showEditButton]="false"
- class="cds-mb-5">
+ <cd-details-card cardTitle="Details" i18n-cardTitle [details]="gatewayDetails" [showEditButton]="true"
+ editButtonLabel="Edit" i18n-editButtonLabel (editClicked)="onEditGateway()" class="cds-mb-5">
</cd-details-card>
}
this.hostsLoaded.emit(this.count);
}
+ onEditGateway(): void {}
+
private buildGatewayDetails(
serviceSpec: CephServiceSpec,
gatewayNodeCount: number
i18n>Choose Certificate Authority</label>
<cds-radio-group formControlName="certificateType"
orientation="horizontal"
- helperText="Select how certificates will be signed for this service. Choose internal to use the cluster’s CA, or external to upload certificates signed by your organization."
+ helperText="Select how certificates will be signed for this service. Choose internal to use the cluster's CA, or external to upload certificates signed by your organization."
i18n-helperText>
<cds-radio [value]="CertificateType.internal"
(change)="onCertificateTypeChange(CertificateType.internal)"
});
}
this.detectChanges();
+ this.updateRgwPlacementControlsState();
+ this.updateGrafanaPasswordControlState();
}
detectChanges(): void {
} else {
this.showRealmCreationForm = false;
}
- this.updateRgwControlStates();
+ this.updateRgwControlStates();
},
(_error) => {
const defaultZone = new RgwZone();
defaultZonegroup.name = 'default';
this.zoneList.push(defaultZone);
this.zonegroupList.push(defaultZonegroup);
- this.updateRgwControlStates();
+ this.updateRgwControlStates();
}
);
}
onServiceTypeChange(selectedServiceType: string) {
this.setServiceId(selectedServiceType);
+ this.updateGrafanaPasswordControlState(selectedServiceType);
this.serviceIds = this.serviceList
?.filter((service) => service['service_type'] === selectedServiceType)
}
}
+ private updateRgwPlacementControlsState(): void {
+ this.toggleFormControlState('realm_name', this.editing || this.realmList.length === 0);
+ this.toggleFormControlState('zonegroup_name', this.editing || this.zonegroupList.length === 0);
+ this.toggleFormControlState('zone_name', this.editing || this.zoneList.length === 0);
+ }
+
+ private updateGrafanaPasswordControlState(serviceType = this.serviceForm?.get('service_type')?.value): void {
+ this.toggleFormControlState(
+ 'grafana_admin_password',
+ this.editing && serviceType === 'grafana'
+ );
+ }
+
+ private toggleFormControlState(controlName: string, disabled: boolean): void {
+ const control = this.serviceForm.get(controlName);
+ if (!control) {
+ return;
+ }
+ if (disabled && control.enabled) {
+ control.disable({ emitEvent: false });
+ return;
+ }
+ if (!disabled && control.disabled) {
+ control.enable({ emitEvent: false });
+ }
+ }
+
onPlacementChange(selected: string) {
if (selected === 'label') {
this.serviceForm.get('count').setValue(null);
@NgModule({
imports: [
CommonModule,
- ProductiveCardComponent,
FormsModule,
ReactiveFormsModule,
NgbAlertModule,
TagModule,
LinkModule,
LayerModule,
- ThemeModule
+ ThemeModule,
+ ProductiveCardComponent
],
declarations: [
SparklineComponent,
}
</ng-template>
- <div [cdsStack]="'vertical'"
- [gap]="0">
+ <div>
@if (details && details.length > 0) {
- <div [cdsStack]="'horizontal'"
- [gap]="5">
- @for (detail of getVisibleDetails(); track detail.label) {
- <div [cdsStack]="'vertical'"
- [gap]="2">
- <label class="cds--type-label-01">{{ detail.label }}</label>
- <div class="cds--type-body-01">
- @switch (detail.type) {
- @case ('status') {
- <div [cdsStack]="'horizontal'" [gap]="3">
- <cd-icon [type]="getStatusIcon(detail)"></cd-icon>
- <span>{{ getDisplayValue(detail.value) }}</span>
- </div>
- }
-
- @default {
- <span>{{ getDisplayValue(detail.value) }}</span>
- }
- }
+ <div class="cds--grid cds--no-gutter">
+ <div class="cds--row">
+ @for (detail of getVisibleDetails(); track detail.label) {
+ <div class="cds--col">
+ <div [cdsStack]="'vertical'" [gap]="2">
+ <label class="cds--type-label-01">{{ detail.label }}</label>
+ <div class="cds--type-body-01">
+ @switch (detail.type) {
+ @case ('status') {
+ <div [cdsStack]="'horizontal'" [gap]="3">
+ <cd-icon [type]="getStatusIcon(detail)"></cd-icon>
+ <span>{{ getDisplayValue(detail.value) }}</span>
+ </div>
+ }
+
+ @default {
+ <span>{{ getDisplayValue(detail.value) }}</span>
+ }
+ }
+ </div>
+ </div>
</div>
+ }
</div>
- }
</div>
}
<ng-content></ng-content>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DetailsCardComponent } from './details-card.component';
+import { ProductiveCardComponent } from '../productive-card/productive-card.component';
describe('DetailsCardComponent', () => {
let component: DetailsCardComponent;
beforeEach(async () => {
await TestBed.configureTestingModule({
- declarations: [DetailsCardComponent]
+ declarations: [DetailsCardComponent],
+ imports: [ProductiveCardComponent]
}).compileComponents();
fixture = TestBed.createComponent(DetailsCardComponent);
onEditClick(): void {
this.editClicked.emit();
}
-
getVisibleDetails(): DetailItem[] {
return (this.details || []).filter((detail) => !detail.hidden);
}