Line data Source code
1 : /*
2 : * Copyright (C) 2025 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 113 : import cockpit from "cockpit";
7 113 : import React from "react";
8 :
9 : import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
10 : import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
11 : import { DropdownList } from "@patternfly/react-core/dist/esm/components/Dropdown";
12 : import { ExclamationCircleIcon, ExclamationTriangleIcon } from "@patternfly/react-icons";
13 : import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
14 : import { Icon } from "@patternfly/react-core/dist/esm/components/Icon/index.js";
15 :
16 : import { format_temperature } from "../utils.js";
17 : import { StorageCard, StorageDescription } from "../pages.jsx";
18 : import { StorageBarMenu, StorageMenuItem } from "../storage-controls.jsx";
19 :
20 113 : const _ = cockpit.gettext;
21 :
22 113 : const selftestStatusDescription = {
23 : // Shared values
24 113 : success: _("Successful"),
25 113 : aborted: _("Aborted"),
26 113 : inprogress: _("In progress"),
27 :
28 : // SATA special values
29 113 : interrupted: _("Interrupted"),
30 113 : fatal: _("Did not complete"),
31 113 : error_unknown: _("Failed (Unknown)"),
32 113 : error_electrical: _("Failed (Electrical)"),
33 113 : error_servo: _("Failed (Servo)"),
34 113 : error_read: _("Failed (Read)"),
35 113 : error_handling: _("Failed (Damaged)"),
36 :
37 : // NVMe special values
38 113 : ctrl_reset: _("Aborted by a Controller Level Reset"),
39 113 : ns_removed: _("Aborted due to a removal of a namespace from the namespace inventory"),
40 113 : aborted_format: _("Aborted due to the processing of a Format NVM command"),
41 113 : fatal_error: _("A fatal error occurred during the self-test operation"),
42 113 : unknown_seg_fail: _("Completed with a segment that failed and the segment that failed is not known"),
43 113 : known_seg_fail: _("Completed with one or more failed segments"),
44 113 : aborted_unknown: _("Aborted for unknown reason"),
45 113 : aborted_sanitize: _("Aborted due to a sanitize operation"),
46 113 : };
47 :
48 : // NVMe reports reasons why selftest failed
49 113 : const nvmeCriticalWarning = {
50 113 : spare: _("Spare capacity is below the threshold"),
51 113 : temperature: _("Temperature outside of recommended thresholds"),
52 113 : degraded: _("Degraded"),
53 113 : readonly: _("All media is in read-only mode"),
54 113 : volatile_mem: _("Volatile memory backup failed"),
55 113 : pmr_readonly: _("Persistent memory has become read-only")
56 113 : };
57 :
58 1 : const SmartActions = ({ smart_info }) => {
59 1 : const smartSelftestStatus = smart_info.SmartSelftestStatus;
60 :
61 0 : const runSelfTest = (type) => {
62 0 : smart_info.SmartSelftestStart(type, {});
63 0 : };
64 :
65 0 : const abortSelfTest = () => {
66 0 : smart_info.SmartSelftestAbort({});
67 0 : };
68 :
69 1 : const testDisabled = smartSelftestStatus === "inprogress";
70 :
71 1 : const actionItems = (
72 1 : <DropdownList>
73 1 : <StorageMenuItem isDisabled={testDisabled}
74 0 : onClick={() => { runSelfTest('short') }}
75 : >
76 1 : {_("Run short test")}
77 1 : </StorageMenuItem>
78 1 : <StorageMenuItem isDisabled={testDisabled}
79 0 : onClick={() => { runSelfTest('extended') }}
80 : >
81 1 : {_("Run extended test")}
82 1 : </StorageMenuItem>
83 1 : <StorageMenuItem isDisabled={!testDisabled}
84 0 : onClick={() => { abortSelfTest() }}
85 : >
86 1 : {_("Abort test")}
87 1 : </StorageMenuItem>
88 1 : </DropdownList>
89 : );
90 :
91 1 : return (
92 1 : <StorageBarMenu isKebab label={_("Actions")} menuItems={actionItems} />
93 : );
94 1 : };
95 :
96 1 : export const isSmartOK = (drive_type, smart_info) => {
97 1 : return (drive_type === "ata" && !smart_info.SmartFailing) ||
98 0 : (drive_type === "nvme" && smart_info.SmartCriticalWarning.length === 0);
99 1 : };
100 :
101 1 : export const SmartCard = ({ card, smart_info, drive_type }) => {
102 1 : const powerOnHours = (drive_type === "ata")
103 1 : ? Math.floor(smart_info.SmartPowerOnSeconds / 3600)
104 0 : : smart_info.SmartPowerOnHours;
105 :
106 1 : const smartOK = isSmartOK(drive_type, smart_info);
107 :
108 1 : const status = selftestStatusDescription[smart_info.SmartSelftestStatus] +
109 1 : ((smart_info.SmartSelftestStatus === "inprogress" && smart_info.SmartSelftestPercentRemaining !== -1)
110 1 : ? `, ${100 - smart_info.SmartSelftestPercentRemaining}%`
111 1 : : "");
112 :
113 1 : const assesment = (
114 1 : <Flex spaceItems={{ default: 'spaceItemsXs' }}>
115 1 : { !smartOK &&
116 1 : <Icon status="danger">
117 1 : <ExclamationCircleIcon />
118 1 : </Icon>
119 : }
120 1 : { drive_type === "ata" && !smartOK &&
121 1 : <span className="cockpit-disk-failing">{_("Disk is failing")}</span>
122 : }
123 0 : { drive_type === "nvme" && !smartOK &&
124 0 : (<span className="cockpit-disk-failing">
125 0 : {_("Disk is failing") + ": " + smart_info.SmartCriticalWarning.map(reason => nvmeCriticalWarning[reason]).join(", ")}
126 0 : </span>)
127 : }
128 1 : { smartOK &&
129 1 : <span>{_("Disk is OK")}</span>
130 : }
131 1 : { smart_info.SmartTemperature > 0
132 1 : ? <span>({format_temperature(smart_info.SmartTemperature)})</span>
133 1 : : null
134 : }
135 1 : </Flex>
136 : );
137 :
138 1 : return (
139 1 : <StorageCard card={card} actions={<SmartActions smart_info={smart_info} />}>
140 1 : <CardBody>
141 1 : <DescriptionList isHorizontal horizontalTermWidthModifier={{ default: '20ch' }}>
142 1 : <StorageDescription title={_("Assessment")}>
143 1 : {assesment}
144 1 : </StorageDescription>
145 1 : <StorageDescription title={_("Power on hours")}
146 1 : value={cockpit.format(_("$0 hours"), powerOnHours)}
147 1 : />
148 1 : <StorageDescription title={_("Self-test status")}
149 1 : value={status}
150 1 : />
151 1 : {drive_type === "ata" && smart_info.SmartNumBadSectors > 0 &&
152 1 : <StorageDescription title={_("Number of bad sectors")}>
153 1 : <Flex flexWrap={{ default: "nowrap" }} spaceItems={{ default: "spaceItemsXs" }}>
154 1 : <Icon status="warning">
155 1 : <ExclamationTriangleIcon />
156 1 : </Icon>
157 1 : {smart_info.SmartNumBadSectors}
158 1 : </Flex>
159 1 : </StorageDescription>
160 : }
161 1 : {drive_type === "ata" && smart_info.SmartNumAttributesFailing > 0 &&
162 1 : <StorageDescription title={_("Attributes failing")}>
163 1 : <Flex flexWrap={{ default: "nowrap" }} spaceItems={{ default: "spaceItemsXs" }}>
164 1 : <Icon status="warning">
165 1 : <ExclamationTriangleIcon />
166 1 : </Icon>
167 1 : {smart_info.SmartNumAttributesFailing}
168 1 : </Flex>
169 1 : </StorageDescription>
170 : }
171 1 : </DescriptionList>
172 1 : </CardBody>
173 1 : </StorageCard>
174 : );
175 1 : };
|