id="clusterStatus">
<div class="d-flex flex-column justify-content-center align-items-center">
<ng-template #healthChecks>
- <ul>
- <li *ngFor="let check of healthData.health.checks">
- <span [ngStyle]="check.severity | healthColor"
- [class.health-warn-description]="check.severity === 'HEALTH_WARN'">
- {{ check.type }}</span>: {{ check.summary.message }}
- </li>
- </ul>
+ <cd-health-checks [healthData]="healthData.health.checks"></cd-health-checks>
</ng-template>
<ng-template #healthWarningAndError>
<div class="info-card-content-clickable mt-1"
</div>
<div class="d-flex flex-column ms-4 me-4 mt-4 mb-4">
<ng-template #healthChecks>
- <ng-container *ngTemplateOutlet="logsLink"></ng-container>
- <ul>
- <li *ngFor="let check of healthData.health.checks">
- <span [ngStyle]="check.severity | healthColor"
- [class.health-warn-description]="check.severity === 'HEALTH_WARN'">
- {{ check.type }}</span>: {{ check.summary.message }}
- </li>
- </ul>
+ <cd-health-checks *ngIf="healthData?.health?.checks"
+ [healthData]="healthData.health.checks"></cd-health-checks>
</ng-template>
<div class="d-flex flex-row">
popoverClass="info-card-popover-cluster-status"
[openDelay]="300"
[closeDelay]="500"
- triggers="mouseenter:mouseleave"
+ triggers="mouseenter"
*ngIf="healthData.health?.checks?.length"
i18n>Cluster</a>
<span class="ms-2 mt-n1 lead"
*ngIf="healthData.health?.status">
<ng-container *ngIf="healthData.health?.checks?.length > 0">
<ng-template #healthChecks>
- <ng-container *ngTemplateOutlet="logsLink"></ng-container>
- <ul>
- <li *ngFor="let check of healthData.health.checks">
- <span [ngStyle]="check.severity | healthColor"
- [class.health-warn-description]="check.severity === 'HEALTH_WARN'">
- {{ check.type }}</span>: {{ check.summary.message }}
- </li>
- </ul>
+ <cd-health-checks [healthData]="healthData"></cd-health-checks>
</ng-template>
<div class="info-card-content-clickable"
[ngStyle]="healthData.health.status | healthColor"
import { SharedModule } from '~/app/shared/shared.module';
import { DeviceListComponent } from './device-list/device-list.component';
import { SmartListComponent } from './smart-list/smart-list.component';
+import { HealthChecksComponent } from './health-checks/health-checks.component';
@NgModule({
imports: [CommonModule, DataTableModule, SharedModule, NgbNavModule, NgxPipeFunctionModule],
- exports: [DeviceListComponent, SmartListComponent],
- declarations: [DeviceListComponent, SmartListComponent]
+ exports: [DeviceListComponent, SmartListComponent, HealthChecksComponent],
+ declarations: [DeviceListComponent, SmartListComponent, HealthChecksComponent]
})
export class CephSharedModule {}
--- /dev/null
+<ng-container *ngTemplateOutlet="logsLink"></ng-container>
+<ul>
+ <li *ngFor="let check of healthData">
+ <span [ngStyle]="check.severity | healthColor"
+ [class.health-warn-description]="check.severity === 'HEALTH_WARN'">
+ {{ check.type }}</span>: {{ check.summary.message }} <br>
+ <div *ngIf="check.type === 'CEPHADM_FAILED_DAEMON'"
+ class="failed-daemons">
+ <cd-help-text>
+ <b>Failed Daemons:</b>
+ <div *ngFor="let failedDaemons of getFailedDaemons(check.detail); let last = last">
+ {{ failedDaemons }}
+ {{ !last ? ', ' : '' }}
+ </div>
+ </cd-help-text>
+ </div>
+ <div *ngFor="let details of check?.detail">
+ <cd-help-text>{{ details?.message }}</cd-help-text>
+ </div>
+ </li>
+</ul>
+
+<ng-template #logsLink>
+ <ng-container *ngIf="permissions.log.read">
+ <p class="logs-link"
+ i18n><i [ngClass]="[icons.infoCircle]"></i> See <a routerLink="/logs">Logs</a> for more details.</p>
+ </ng-container>
+</ng-template>
--- /dev/null
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HealthChecksComponent } from './health-checks.component';
+import { HealthColorPipe } from '~/app/shared/pipes/health-color.pipe';
+import { By } from '@angular/platform-browser';
+import { CssHelper } from '~/app/shared/classes/css-helper';
+
+describe('HealthChecksComponent', () => {
+ let component: HealthChecksComponent;
+ let fixture: ComponentFixture<HealthChecksComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [HealthChecksComponent, HealthColorPipe],
+ providers: [CssHelper]
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(HealthChecksComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should show the correct health warning for failed daemons', () => {
+ component.healthData = [
+ {
+ severity: 'HEALTH_WARN',
+ summary: {
+ message: '1 failed cephadm daemon(s)',
+ count: 1
+ },
+ detail: [
+ {
+ message: 'daemon ceph-exporter.ceph-node-00 on ceph-node-00 is in error state'
+ }
+ ],
+ muted: false,
+ type: 'CEPHADM_FAILED_DAEMON'
+ }
+ ];
+ fixture.detectChanges();
+ const failedDaemons = fixture.debugElement.query(By.css('.failed-daemons'));
+ expect(failedDaemons.nativeElement.textContent).toContain(
+ 'Failed Daemons: ceph-exporter.ceph-node-00 '
+ );
+ });
+});
--- /dev/null
+import { Component, Input } from '@angular/core';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { Permissions } from '~/app/shared/models/permissions';
+
+@Component({
+ selector: 'cd-health-checks',
+ templateUrl: './health-checks.component.html',
+ styleUrls: ['./health-checks.component.scss']
+})
+export class HealthChecksComponent {
+ @Input()
+ healthData: any;
+
+ icons = Icons;
+
+ permissions: Permissions;
+
+ constructor(private authStorageService: AuthStorageService) {
+ this.permissions = this.authStorageService.getPermissions();
+ }
+
+ getFailedDaemons(detail: any[]): string[] {
+ return detail.map(
+ (failedDaemons) => failedDaemons.message.split('daemon ')?.[1].split(' on ')[0]
+ );
+ }
+}