LCOV - code coverage report
Current view: top level - pkg/storaged/drive - smart-details.jsx Coverage Total Hit
Test: cockpit Lines: 87.9 % 124 109
Test Date: 2026-07-13 10:00:01

            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 : };
        

Generated by: LCOV version 2.0-1