<cd-productive-card class="overview-alerts-card">
@if (vm$ | async; as vm) {
+ <!-- HEADER -->
<ng-template #header>
<h2 class="cds--type-heading-compact-02"
i18n>
View all
</button>
</ng-template>
-
+ <!-- TOTAL COUNT -->
<div>
<span class="cds--type-heading-07">{{ vm.total }}</span>
<cd-icon [type]="vm.icon"></cd-icon>
{{ vm.statusText }}
</small>
+ <!-- SUB TOTAL COUNTS -->
<div class="cds-mt-6">
@if (vm.badges.length) {
- <div class="overview-alerts-card-badges">
- @for (b of vm.badges; track b.key; let first = $first) {
+ <div [cdsStack]="compact? 'horizontal' : 'vertical'">
+ @for (b of vm.badges; track b.key; let last = $last) {
<span
class="overview-alerts-card-badge"
- [class.overview-alerts-card-badge-with-border]="!first">
+ [class.overview-alerts-card-badge-with-border--right]="!last && compact"
+ [class.overview-alerts-card-badge-with-border--bottom]="!compact">
<cd-icon [type]="b.icon"></cd-icon>
<a
cdsLink
class="cds-ml-3"
[routerLink]="['/monitoring/active-alerts']"
[queryParams]="{ severity: b.key }">
+ @if(compact) {
{{ b.count }}
+ } @else {
+ {{b.key | upperFirst}} ({{ b.count }})
+ }
</a>
</span>
}
.overview-alerts-card {
- // height: 100%;
- &-badges {
- display: flex;
- align-items: center;
- }
-
&-badge {
display: inline-flex;
align-items: center;
+ }
+
+ &-badge-with-border--right {
+ border-right: 1px solid var(--cds-border-subtle);
padding: 0 var(--cds-spacing-04);
}
- &-badge-with-border {
- border-left: 1px solid var(--cds-border-subtle);
+ &-badge-with-border--bottom {
+ border-bottom: 1px solid var(--cds-border-subtle);
+ padding-bottom: var(--cds-spacing-03);
}
&-need-attention {
color: var(--cds-text-secondary);
}
+ // Override to make alert card take full available height.
+ // Carbon tiles set a min height of 4 rem which was causing alerts card not to stretch with its sibling card.
.cds--tile {
min-block-size: 0;
height: 100%;
}
+
+ &-badge cd-icon svg {
+ display: block;
+ }
}
fixture.detectChanges();
const badgeEls = Array.from(
- fixture.nativeElement.querySelectorAll(
- '.overview-alerts-card-badges .overview-alerts-card-badge'
- )
+ fixture.nativeElement.querySelectorAll('.overview-alerts-card-badge')
) as HTMLElement[];
expect(badgeEls.length).toBe(2);
- expect(badgeEls[0].classList.contains('overview-alerts-card-badge-with-border')).toBe(false);
- expect(badgeEls[1].classList.contains('overview-alerts-card-badge-with-border')).toBe(true);
+ expect(badgeEls[0].classList.contains('overview-alerts-card-badge-with-border--right')).toBe(
+ true
+ );
+ expect(badgeEls[1].classList.contains('overview-alerts-card-badge-with-border--right')).toBe(
+ false
+ );
});
});
import {
ChangeDetectionStrategy,
Component,
+ Input,
OnInit,
ViewEncapsulation,
inject
import { combineLatest } from 'rxjs';
import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service';
-import { ButtonModule, GridModule, LinkModule, TilesModule } from 'carbon-components-angular';
+import {
+ ButtonModule,
+ GridModule,
+ LayoutModule,
+ LinkModule,
+ TilesModule
+} from 'carbon-components-angular';
import { RouterModule } from '@angular/router';
import { ProductiveCardComponent } from '~/app/shared/components/productive-card/productive-card.component';
import { ComponentsModule } from '~/app/shared/components/components.module';
import { map, shareReplay, startWith } from 'rxjs/operators';
+import { PipesModule } from '~/app/shared/pipes/pipes.module';
const AlertIcon = {
error: 'error',
RouterModule,
ProductiveCardComponent,
ButtonModule,
- LinkModule
+ LinkModule,
+ LayoutModule,
+ PipesModule
],
templateUrl: './overview-alerts-card.component.html',
styleUrl: './overview-alerts-card.component.scss',
encapsulation: ViewEncapsulation.None
})
export class OverviewAlertsCardComponent implements OnInit {
+ @Input() compact = true;
private readonly prometheusAlertService = inject(PrometheusAlertService);
ngOnInit(): void {
import { UpgradeInfoInterface } from '~/app/shared/models/upgrade.interface';
import { UpgradeService } from '~/app/shared/api/upgrade.service';
import { catchError, filter, map, startWith } from 'rxjs/operators';
-import { HealthCardVM } from '~/app/shared/models/overview';
+import { HealthCardTabSection, HealthCardVM } from '~/app/shared/models/overview';
type OverviewHealthData = {
summary: Summary;
upgrade: UpgradeInfoInterface;
};
-type TabSection = 'system' | 'hardware' | 'resiliency';
-
interface HealthItemConfig {
key: 'mon' | 'mgr' | 'osd' | 'hosts';
label: string;
@Input({ required: true }) vm!: HealthCardVM;
@Output() viewIncidents = new EventEmitter<void>();
+ @Output() activeSectionChange = new EventEmitter<HealthCardTabSection | null>();
- activeSection: TabSection | null = null;
+ activeSection: HealthCardTabSection | null = null;
healthItems: HealthItemConfig[] = [
{ key: 'mon', label: $localize`Monitor` },
{ key: 'mgr', label: $localize`Manager` },
{ key: 'hosts', label: $localize`Nodes` }
];
- toggleSection(section: TabSection) {
+ toggleSection(section: HealthCardTabSection) {
this.activeSection = this.activeSection === section ? null : section;
+ this.activeSectionChange.emit(this.activeSection);
}
readonly data$: Observable<OverviewHealthData> = combineLatest([
<cd-overview-health-card
[vm]="health"
(viewIncidents)="togglePanel()"
+ (activeSectionChange)="activeHealthTab = $event"
></cd-overview-health-card>
</div>
<div cdsCol
class="cds-mb-5"
[columnNumbers]="{lg: 5}">
- <cd-overview-alerts-card></cd-overview-alerts-card>
+ <cd-overview-alerts-card [compact]="!activeHealthTab"></cd-overview-alerts-card>
</div>
</div>
<div cdsRow>
(closed)="togglePanel()">
<div panel-header-description
class="cds--type-body-01">
- <span>Health incidents are Ceph health checks warnings indicating conditions that require attention and remain until resolved.</span>
+ <span i18n>Health incidents are Ceph health checks warnings indicating conditions that require attention and remain until resolved.</span>
</div>
<div class="panel-content">
@for (check of health?.checks; track key) {
expect(vm.mon).toEqual(
expect.objectContaining({
- value: '3/3',
+ value: 'Quorum: 3/3',
severity: expect.any(String)
})
);
import { HealthCheck, HealthSnapshotMap } from '~/app/shared/models/health.interface';
import {
HealthCardCheckVM,
+ HealthCardTabSection,
HealthCardVM,
HealthDisplayVM,
HealthIconMap,
})
export class OverviewComponent {
isHealthPanelOpen = false;
+ activeHealthTab: HealthCardTabSection | null = null;
private readonly healthService = inject(HealthService);
private readonly refreshIntervalService = inject(RefreshIntervalService);
osd: HealthCardSubStateVM;
hosts: HealthCardSubStateVM;
}
+
+export type HealthCardTabSection = 'system' | 'hardware' | 'resiliency';