LCOV - code coverage report
Current view: top level - pkg/systemd/overview-cards - motdCard.jsx Coverage Total Hit
Test: cockpit Lines: 87.3 % 79 69
Test Date: 2026-07-13 10:00:01

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2019 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6           92 : import React, { useState } from 'react';
       7              : 
       8              : import { Alert, AlertActionCloseButton } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
       9              : import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
      10              : import {
      11              :     Modal, ModalBody, ModalFooter, ModalHeader
      12              : } from '@patternfly/react-core/dist/esm/components/Modal/index.js';
      13              : import { Stack } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js";
      14              : import { TextArea } from "@patternfly/react-core/dist/esm/components/TextArea/index.js";
      15              : import { EditIcon } from '@patternfly/react-icons';
      16              : import { ModalError } from 'cockpit-components-inline-notification.jsx';
      17              : import { superuser } from "superuser";
      18              : import { useDialogs } from "dialogs.jsx";
      19              : import { useInit } from "hooks";
      20              : 
      21           92 : import cockpit from "cockpit";
      22              : 
      23              : import './motdCard.scss';
      24              : 
      25           92 : const _ = cockpit.gettext;
      26              : 
      27            1 : const MotdEditDialog = ({ text, expectedTag }) => {
      28            1 :     const Dialogs = useDialogs();
      29            1 :     const [value, setValue] = useState(text);
      30            1 :     const [error, setError] = useState(null);
      31            1 :     const [errorDetail, setErrorDetail] = useState(null);
      32              : 
      33            1 :     return (
      34            1 :         <Modal position="top"
      35            1 :                variant="medium" isOpen
      36            1 :                id="motd-box-edit-modal"
      37            1 :                onClose={Dialogs.close}>
      38              : 
      39            1 :             <ModalHeader title={_("Edit /etc/motd")} />
      40            1 :             <ModalBody>
      41            1 :                 <Stack hasGutter>
      42            1 :                     {error &&
      43            0 :                     <ModalError dialogError={error}
      44            0 :                                  dialogErrorDetail={errorDetail} />}
      45            1 :                     <TextArea resizeOrientation="vertical"
      46            1 :                               value={value}
      47            1 :                               onChange={(_event, value) => setValue(value)} />
      48            1 :                 </Stack>
      49            1 :             </ModalBody>
      50            1 :             <ModalFooter>
      51            1 :                 <Button variant='primary'
      52            1 :                         onClick={() => cockpit.file("/etc/motd", { superuser: "try", err: "message" })
      53            1 :                                 .replace(value, expectedTag)
      54            1 :                                 .then(Dialogs.close)
      55            0 :                                 .catch(exc => {
      56            0 :                                     setError(_("Failed to save changes in /etc/motd"));
      57            0 :                                     setErrorDetail(exc.message);
      58            0 :                                 })}>
      59            1 :                     {_("Save changes")}
      60            1 :                 </Button>
      61            1 :                 <Button variant='link'
      62            1 :                         onClick={Dialogs.close}>
      63            1 :                     {_("Cancel")}
      64            1 :                 </Button>
      65            1 :             </ModalFooter>
      66            1 :         </Modal>);
      67            1 : };
      68              : 
      69           92 : export const MotdCard = () => {
      70           92 :     const Dialogs = useDialogs();
      71           92 :     const [motdText, setMotdText] = useState("");
      72           92 :     const [motdTag, setMotdTag] = useState(null);
      73           92 :     const [motdVisible, setMotdVisible] = useState(false);
      74              : 
      75           92 :     useInit(() => {
      76           87 :         cockpit.file("/etc/motd").watch((content, tag) => {
      77              :             /* trim initial empty lines and trailing space, but keep initial spaces to not break ASCII art */
      78           87 :             if (content)
      79           16 :                 content = content.trimEnd().replace(/^\s*\n/, '');
      80           16 :             if (content && content != cockpit.localStorage.getItem('dismissed-motd')) {
      81           16 :                 setMotdText(content);
      82           16 :                 setMotdTag(tag);
      83           16 :                 setMotdVisible(true);
      84           16 :             } else {
      85           87 :                 setMotdVisible(false);
      86           87 :             }
      87           87 :         });
      88           92 :     });
      89              : 
      90            0 :     function hideAlert() {
      91            0 :         setMotdVisible(false);
      92            0 :         cockpit.localStorage.setItem('dismissed-motd', motdText);
      93            0 :     }
      94              : 
      95           92 :     if (!motdVisible)
      96           92 :         return null;
      97              : 
      98           17 :     const actionClose = <>
      99           17 :         {superuser.allowed &&
     100           17 :         <Button icon={<EditIcon />} variant="plain"
     101           17 :                 id="motd-box-edit"
     102            1 :                 onClick={() => Dialogs.show(<MotdEditDialog text={motdText} expectedTag={motdTag} />)}
     103           17 :                 aria-label={_("Edit motd")} />}
     104           92 :         <AlertActionCloseButton onClose={hideAlert} />
     105           92 :     </>;
     106              : 
     107           92 :     return <Alert id="motd-box" isInline className="motd-box"
     108           92 :                   variant="custom"
     109           92 :                   title={<pre id="motd">{motdText}</pre>}
     110           92 :                   actionClose={actionClose} />;
     111           92 : };
        

Generated by: LCOV version 2.0-1