]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.ts
import 15.2.4
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / shared / smart-list / smart-list.component.ts
1 import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
2
3 import { I18n } from '@ngx-translate/i18n-polyfill';
4 import * as _ from 'lodash';
5
6 import { HostService } from '../../../shared/api/host.service';
7 import { OsdService } from '../../../shared/api/osd.service';
8 import { CdTableColumn } from '../../../shared/models/cd-table-column';
9 import {
10 HddSmartDataV1,
11 NvmeSmartDataV1,
12 SmartDataResult,
13 SmartError,
14 SmartErrorResult
15 } from '../../../shared/models/smart';
16
17 @Component({
18 selector: 'cd-smart-list',
19 templateUrl: './smart-list.component.html',
20 styleUrls: ['./smart-list.component.scss']
21 })
22 export class SmartListComponent implements OnInit, OnChanges {
23 @Input()
24 osdId: number = null;
25 @Input()
26 hostname: string = null;
27
28 loading = false;
29 incompatible = false;
30 error = false;
31
32 data: { [deviceId: string]: SmartDataResult | SmartErrorResult } = {};
33
34 smartDataColumns: CdTableColumn[];
35
36 constructor(
37 private i18n: I18n,
38 private osdService: OsdService,
39 private hostService: HostService
40 ) {}
41
42 isSmartError(data: any): data is SmartError {
43 return _.get(data, 'error') !== undefined;
44 }
45
46 isNvmeSmartData(data: any): data is NvmeSmartDataV1 {
47 return _.get(data, 'device.protocol', '').toLowerCase() === 'nvme';
48 }
49
50 isHddSmartData(data: any): data is HddSmartDataV1 {
51 return _.get(data, 'device.protocol', '').toLowerCase() === 'ata';
52 }
53
54 private fetchData(data: any) {
55 const result: { [deviceId: string]: SmartDataResult | SmartErrorResult } = {};
56 _.each(data, (smartData, deviceId) => {
57 if (this.isSmartError(smartData)) {
58 let userMessage = '';
59 if (smartData.smartctl_error_code === -22) {
60 userMessage = this.i18n(
61 `Smartctl has received an unknown argument (error code {{code}}). \
62 You may be using an incompatible version of smartmontools. Version >= 7.0 of \
63 smartmontools is required to successfully retrieve data.`,
64 { code: smartData.smartctl_error_code }
65 );
66 } else {
67 userMessage = this.i18n('An error with error code {{code}} occurred.', {
68 code: smartData.smartctl_error_code
69 });
70 }
71 const _result: SmartErrorResult = {
72 error: smartData.error,
73 smartctl_error_code: smartData.smartctl_error_code,
74 smartctl_output: smartData.smartctl_output,
75 userMessage: userMessage,
76 device: smartData.dev,
77 identifier: smartData.nvme_vendor
78 };
79 result[deviceId] = _result;
80 return;
81 }
82
83 // Prepare S.M.A.R.T data
84 if (smartData.json_format_version[0] === 1) {
85 // Version 1.x
86 if (this.isHddSmartData(smartData)) {
87 result[deviceId] = this.extractHddData(smartData);
88 } else if (this.isNvmeSmartData(smartData)) {
89 result[deviceId] = this.extractNvmeData(smartData);
90 }
91 return;
92 } else {
93 this.incompatible = true;
94 }
95 });
96 this.data = result;
97 this.loading = false;
98 }
99
100 private extractNvmeData(smartData: NvmeSmartDataV1): SmartDataResult {
101 const info = _.omitBy(smartData, (_value, key) =>
102 ['nvme_smart_health_information_log'].includes(key)
103 );
104 return {
105 info: info,
106 smart: {
107 nvmeData: smartData.nvme_smart_health_information_log
108 },
109 device: smartData.device.name,
110 identifier: smartData.serial_number
111 };
112 }
113
114 private extractHddData(smartData: HddSmartDataV1): SmartDataResult {
115 const info = _.omitBy(smartData, (_value, key) =>
116 ['ata_smart_attributes', 'ata_smart_selective_self_test_log', 'ata_smart_data'].includes(key)
117 );
118 return {
119 info: info,
120 smart: {
121 attributes: smartData.ata_smart_attributes,
122 data: smartData.ata_smart_data
123 },
124 device: info.device.name,
125 identifier: info.serial_number
126 };
127 }
128
129 private updateData() {
130 this.loading = true;
131
132 if (this.osdId !== null) {
133 this.osdService.getSmartData(this.osdId).subscribe(this.fetchData.bind(this), (error) => {
134 error.preventDefault();
135 this.error = error;
136 this.loading = false;
137 });
138 } else if (this.hostname !== null) {
139 this.hostService.getSmartData(this.hostname).subscribe(this.fetchData.bind(this), (error) => {
140 error.preventDefault();
141 this.error = error;
142 this.loading = false;
143 });
144 }
145 }
146
147 ngOnInit() {
148 this.smartDataColumns = [
149 { prop: 'id', name: this.i18n('ID') },
150 { prop: 'name', name: this.i18n('Name') },
151 { prop: 'raw.value', name: this.i18n('Raw') },
152 { prop: 'thresh', name: this.i18n('Threshold') },
153 { prop: 'value', name: this.i18n('Value') },
154 { prop: 'when_failed', name: this.i18n('When Failed') },
155 { prop: 'worst', name: this.i18n('Worst') }
156 ];
157 }
158
159 ngOnChanges(changes: SimpleChanges): void {
160 this.data = {}; // Clear previous data
161 if (changes.osdId) {
162 this.osdId = changes.osdId.currentValue;
163 } else if (changes.hostname) {
164 this.hostname = changes.hostname.currentValue;
165 }
166 this.updateData();
167 }
168 }