import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
import { Icons } from '~/app/shared/enum/icons.enum';
import { PrometheusListHelper } from '~/app/shared/helpers/prometheus-list-helper';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
import { CdTableAction } from '~/app/shared/models/cd-table-action';
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
export class ActiveAlertListComponent extends PrometheusListHelper implements OnInit {
@ViewChild('externalLinkTpl', { static: true })
externalLinkTpl: TemplateRef<any>;
+ @ViewChild(TableComponent) table: TableComponent;
columns: CdTableColumn[];
innerColumns: CdTableColumn[];
tableActions: CdTableAction[];
this.route.queryParams.subscribe((params) => {
const severity = params['severity'];
this.filters[1].filterInitValue = SeverityMap[severity];
+ if (params['search']) {
+ setTimeout(() => {
+ if (this.table) {
+ this.table.search = params['search'];
+ this.table.updateFilter();
+ }
+ });
+ }
});
}
size="sm"
class="overview-health-card-resiliency-btn"
[routerLink]="['/monitoring/active-alerts']"
+ [queryParams]="{ search: 'CephPG' }"
>
<span i18n>View alerts ({{ pgAlertCount }})</span>
<cd-icon type="arrowRight"></cd-icon>
<div class="overview-health-card-resiliency-chart-text">
<p
i18n
- class="cds--type-helper-text-01 overview-health-card-secondary-text cds-mt-2"
+ class="cds--type-helper-text-01 overview-health-card-secondary-text cds-mt-2 cds-mb-3"
>
<em>Data resiliency</em> reflects data availability and replication (% of placement
groups that are active and clean).
let isLast = $last
) {
@if (item.count) {
- <p
- [class.cds-mb-2]="!isLast"
- [class.cds-mb-0]="isLast"
- >
+ <p class="cds-mb-0">
<span class="cds--type-label-01 cds-mr-1">{{ item?.state }}:</span>
<span class="cds--type-label-01 overview-health-card-bold"
>{{ item.count }} %</span
}
&-tab-content {
- padding: var(--cds-spacing-04) 0;
+ padding: var(--cds-spacing-04) 0 0 0;
}
&-tab-content-item {
import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import { HardwareNameMapping } from '~/app/shared/enum/hardware.enum';
+import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service';
const MOCK_HW_SUMMARY = {
total: {
{ provide: MgrModuleService, useValue: mockMgrModuleService },
{ provide: HardwareService, useValue: mockHardwareService },
{ provide: HealthService, useValue: mockHealthService },
+ { provide: PrometheusAlertService, useValue: { totalAlerts$: of(0), alerts: [] } },
provideRouter([])
]
}).compileComponents();
},
{ provide: HardwareService, useValue: { getSummary: jest.fn(() => of(healthyMock)) } },
{ provide: HealthService, useValue: { getTelemetryStatus: jest.fn(() => of(false)) } },
+ { provide: PrometheusAlertService, useValue: { totalAlerts$: of(0), alerts: [] } },
provideRouter([])
]
}).compileComponents();
},
{ provide: HardwareService, useValue: { getSummary: jest.fn(() => of(warnMock)) } },
{ provide: HealthService, useValue: { getTelemetryStatus: jest.fn(() => of(false)) } },
+ { provide: PrometheusAlertService, useValue: { totalAlerts$: of(0), alerts: [] } },
provideRouter([])
]
}).compileComponents();
},
{ provide: HardwareService, useValue: { getSummary: jest.fn(() => of(null)) } },
{ provide: HealthService, useValue: { getTelemetryStatus: jest.fn(() => of(false)) } },
+ { provide: PrometheusAlertService, useValue: { totalAlerts$: of(0), alerts: [] } },
provideRouter([])
]
}).compileComponents();
{ provide: MgrModuleService, useValue: { getConfig: jest.fn(() => of({})) } },
{ provide: HardwareService, useValue: { getSummary: jest.fn(() => of(null)) } },
{ provide: HealthService, useValue: { getTelemetryStatus: jest.fn(() => of(false)) } },
+ { provide: PrometheusAlertService, useValue: { totalAlerts$: of(0), alerts: [] } },
provideRouter([])
]
}).compileComponents();
HardwareCardVM,
buildHardwareCardVM
} from '~/app/shared/models/overview';
+import { AlertState } from '~/app/shared/models/prometheus-alerts';
import { HardwareService } from '~/app/shared/api/hardware.service';
import { HealthService } from '~/app/shared/api/health.service';
import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
import { GaugeChartComponent } from '@carbon/charts-angular';
import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service';
+const PG_ALERT_PREFIX = 'CephPG';
+
type OverviewHealthData = {
summary: Summary;
upgrade: UpgradeInfoInterface | null;
shareReplay({ bufferSize: 1, refCount: true })
);
- readonly pgAlertCount$ = this.prometheusAlertService.pgAlerts$.pipe(startWith(0));
+ readonly pgAlertCount$ = this.prometheusAlertService.totalAlerts$.pipe(
+ map(
+ () =>
+ this.prometheusAlertService.alerts.filter(
+ (a) =>
+ a.status.state === AlertState.ACTIVE && a.labels.alertname?.startsWith(PG_ALERT_PREFIX)
+ ).length
+ ),
+ startWith(0)
+ );
readonly telemetryEnabled$: Observable<boolean> = this.healthService.getTelemetryStatus().pipe(
map((enabled: any) => !!enabled),
+++ /dev/null
-import { buildHealthCardVM } from './overview';
-import { HealthSnapshotMap } from './health.interface';
-
-function makeSnapshot(checks: Record<string, any> = {}): HealthSnapshotMap {
- return {
- fsid: 'test-fsid',
- health: { status: 'HEALTH_OK', checks, mutes: [] },
- monmap: { num_mons: 3 },
- osdmap: { in: 3, up: 3, num_osds: 3 },
- pgmap: {
- pgs_by_state: [{ state_name: 'active+clean', count: 100 }],
- num_pools: 1,
- bytes_used: 0,
- bytes_total: 1000,
- num_pgs: 100,
- write_bytes_sec: 0,
- read_bytes_sec: 0,
- recovering_bytes_per_sec: 0
- },
- mgrmap: { num_active: 1, num_standbys: 1 },
- fsmap: { num_active: 0, num_standbys: 0 },
- num_rgw_gateways: 0,
- num_iscsi_gateways: { up: 0, down: 0 },
- num_hosts: 3
- } as any;
-}
-
-describe('buildHealthCardVM', () => {
- it('should not include pgAlertCount in the VM', () => {
- const vm = buildHealthCardVM(makeSnapshot());
- expect('pgAlertCount' in vm).toBe(false);
- });
-
- it('should build resiliencyHealth from health checks', () => {
- const vm = buildHealthCardVM(
- makeSnapshot({
- PG_DEGRADED: {
- severity: 'HEALTH_WARN',
- summary: { message: 'degraded', count: 5 },
- muted: false
- }
- })
- );
- expect(vm.resiliencyHealth.severity).toBe('warn');
- });
-
- it('should report ok resiliency when no PG checks', () => {
- const vm = buildHealthCardVM(makeSnapshot());
- expect(vm.resiliencyHealth.severity).toBe('ok');
- });
-});
private warningSubject = new BehaviorSubject<number>(0);
readonly warningAlerts$ = this.warningSubject.asObservable();
- private pgAlertsSubject = new BehaviorSubject<number>(0);
- readonly pgAlerts$ = this.pgAlertsSubject.asObservable();
-
constructor(
private alertFormatter: PrometheusAlertFormatter,
private prometheusService: PrometheusService
0
);
- const activePgAlerts = alerts.filter(
- (alert) =>
- alert.status.state === AlertState.ACTIVE &&
- alert.labels.alertname?.startsWith('CephPG')
- ).length;
-
this.totalSubject.next(this.activeAlerts);
this.criticalSubject.next(this.activeCriticalAlerts);
this.warningSubject.next(this.activeWarningAlerts);
- this.pgAlertsSubject.next(activePgAlerts);
this.alerts = alerts
.reverse()