LCOV - code coverage report
Current view: top level - pkg/kdump - kdump-view.jsx Coverage Total Hit
Test: cockpit Lines: 63.8 % 517 330
Test Date: 2026-07-06 08:56:22

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2016 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6              : import '../lib/patternfly/patternfly-6-cockpit.scss';
       7              : import cockpit from "cockpit";
       8              : 
       9            3 : import React, { useEffect, useState } from "react";
      10              : import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
      11              : import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
      12              : import { Checkbox } from "@patternfly/react-core/dist/esm/components/Checkbox/index.js";
      13              : import { Card, CardBody, CardTitle } from "@patternfly/react-core/dist/esm/components/Card/index.js";
      14              : import { HelperText, HelperTextItem } from "@patternfly/react-core/dist/esm/components/HelperText/index.js";
      15              : import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
      16              : import { Form, FormGroup, FormSection } from "@patternfly/react-core/dist/esm/components/Form/index.js";
      17              : import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect/index.js";
      18              : import { Page, PageSection } from "@patternfly/react-core/dist/esm/components/Page/index.js";
      19              : import { CodeBlockCode } from "@patternfly/react-core/dist/esm/components/CodeBlock/index.js";
      20              : import { DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
      21              : import {
      22              :     Modal, ModalBody, ModalFooter, ModalHeader
      23              : } from '@patternfly/react-core/dist/esm/components/Modal/index.js';
      24              : import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner/index.js";
      25              : import { Switch } from "@patternfly/react-core/dist/esm/components/Switch/index.js";
      26              : import { Content, ContentVariants } from "@patternfly/react-core/dist/esm/components/Content/index.js";
      27              : import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js";
      28              : import { Title } from "@patternfly/react-core/dist/esm/components/Title/index.js";
      29              : import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip/index.js";
      30              : 
      31              : import { useDialogs, DialogsContext } from "dialogs.jsx";
      32              : import { read_os_release } from "os-release.js";
      33              : import { fmt_to_fragments } from 'utils.jsx';
      34              : import { FormHelper } from "cockpit-components-form-helper";
      35              : import { ModalError } from 'cockpit-components-inline-notification.jsx';
      36              : import { PrivilegedButton } from "cockpit-components-privileged";
      37              : import { ModificationsExportDialog } from "cockpit-components-modifications";
      38              : 
      39            3 : const _ = cockpit.gettext;
      40            3 : const DEFAULT_KDUMP_PATH = "/var/crash";
      41              : 
      42            0 : const exportAnsibleTask = (settings, os_release) => {
      43            0 :     const target = Object.keys(settings.targets)[0];
      44            0 :     const targetSettings = settings.targets[target];
      45            0 :     const kdump_core_collector = settings.core_collector;
      46              : 
      47            0 :     let role_name = "linux-system-roles";
      48            0 :     if (os_release?.PLATFORM_ID?.startsWith('platform:el') || os_release.ID_LIKE?.includes('rhel')) {
      49            0 :         role_name = "rhel-system-roles";
      50            0 :     }
      51              : 
      52            0 :     let ansible = `
      53              : ---
      54              : # Also available via https://galaxy.ansible.com/ui/standalone/roles/linux-system-roles/kdump/
      55            0 : - name: install ${role_name}
      56              :   package:
      57            0 :     name: ${role_name}
      58              :     state: present
      59              :   delegate_to: 127.0.0.1
      60              :   become: true
      61              : - name: run kdump system role
      62              :   include_role:
      63            0 :     name: ${role_name}.kdump
      64              :   vars:
      65            0 :     kdump_path: ${targetSettings.path || DEFAULT_KDUMP_PATH}
      66            0 :     kdump_core_collector: ${kdump_core_collector}`;
      67              : 
      68            0 :     if (target === "ssh") {
      69              :         // HACK: we should not have to specify kdump_ssh_user and kdump_ssh_user as it is in kdump_target.location
      70              :         // https://github.com/linux-system-roles/kdump/issues/184
      71            0 :         let ssh_user;
      72            0 :         let ssh_server;
      73            0 :         const parts = targetSettings.server.split('@');
      74            0 :         if (parts.length === 1) {
      75            0 :             ssh_user = "root";
      76            0 :             ssh_server = parts[0];
      77            0 :         } else if (parts.length === 2) {
      78            0 :             ssh_user = parts[0];
      79            0 :             ssh_server = parts[1];
      80            0 :         } else {
      81            0 :             throw new Error("ssh server contains two @ symbols");
      82            0 :         }
      83            0 :         ansible += `
      84              :     kdump_target:
      85              :       type: ssh
      86            0 :     kdump_sshkey: ${targetSettings.sshkey}
      87            0 :     kdump_ssh_server: ${ssh_server}
      88            0 :     kdump_ssh_user: ${ssh_user}`;
      89            0 :     } else if (target === "nfs") {
      90            0 :         ansible += `
      91              :     kdump_target:
      92              :       type: nfs
      93            0 :       location: ${targetSettings.server}:${targetSettings.export}
      94              : `;
      95            0 :     } else if (target !== "local") {
      96              :         // target is unsupported
      97            0 :         throw new Error("Unsupported kdump target"); // not-covered: assertion
      98            0 :     }
      99              : 
     100            0 :     return ansible;
     101            0 : };
     102              : 
     103            3 : function getLocation(target, config) {
     104            2 :     let path = target.path || DEFAULT_KDUMP_PATH;
     105              : 
     106            2 :     if (target.type === "ssh") {
     107            2 :         path = `${target.server}:${path}`;
     108            2 :     } else if (target.type == "nfs") {
     109            1 :         if (!config.nfs_supports_directory) {
     110            1 :             path = '';
     111            1 :         }
     112            1 :         path = path[0] !== '/' ? '/' + path : path;
     113            2 :         path = `${target.server}:${target.export + path}`;
     114            2 :     }
     115              : 
     116            3 :     return path;
     117            3 : }
     118              : 
     119            2 : const KdumpSettingsModal = ({ settings, initialTarget, handleSave }) => {
     120            2 :     const Dialogs = useDialogs();
     121            2 :     const compressionAllowed = settings.compression?.allowed;
     122            2 :     const [isSaving, setIsSaving] = useState(false);
     123            2 :     const [error, setError] = useState(null);
     124            2 :     const [isFormValid, setFormValid] = useState(true);
     125            2 :     const [validationErrors, setValidationErrors] = useState({});
     126              : 
     127            2 :     const [storageLocation, setStorageLocation] = useState(Object.keys(settings.targets)[0]);
     128              :     // common options
     129            2 :     const [compressionEnabled, setCompressionEnabled] = useState(settings.compression?.enabled);
     130            2 :     const [directory, setDirectory] = useState(initialTarget.path || DEFAULT_KDUMP_PATH);
     131              :     // nfs and ssh
     132            2 :     const [server, setServer] = useState(settings.targets.nfs?.server || settings.targets.ssh?.server);
     133              :     // nfs
     134            2 :     const [exportPath, setExportPath] = useState(settings.targets.nfs?.export || "");
     135              :     // ssh
     136            2 :     const [sshkey, setSSHKey] = useState(settings.targets.ssh?.sshkey || "");
     137              : 
     138            2 :     useEffect(() => {
     139              :         // We can't use a ref in a functional component
     140            2 :         const elem = document.querySelector("#kdump-settings-form");
     141            2 :         if (elem)
     142            2 :             setFormValid(elem.checkValidity());
     143            2 :     }, [storageLocation, directory, sshkey, server, exportPath]);
     144              : 
     145            2 :     const changeStorageLocation = target => {
     146            2 :         setError(null);
     147            2 :         setDirectory(DEFAULT_KDUMP_PATH);
     148            2 :         setServer("");
     149            2 :         setStorageLocation(target);
     150            2 :     };
     151              : 
     152            2 :     const changeSSHKey = value => {
     153            2 :         if (value.trim() && !value.match("/.+")) {
     154            2 :             setValidationErrors({ sshkey: _("SSH key isn't a path") });
     155            2 :         } else {
     156            2 :             setValidationErrors({});
     157            2 :         }
     158            2 :         setSSHKey(value);
     159            2 :     };
     160              : 
     161            2 :     const saveSettings = () => {
     162            2 :         setError(null);
     163            2 :         setIsSaving(true);
     164            2 :         const newSettings = {
     165            2 :             compression: {
     166            2 :                 allowed: compressionAllowed,
     167            2 :                 enabled: compressionEnabled,
     168            2 :             },
     169            2 :             targets: {
     170            2 :                 [storageLocation]: {
     171            2 :                     type: storageLocation,
     172              :                     // HACK: to not needlessly write a path /var/crash as this is the default,
     173              :                     // set an empty string.
     174            2 :                     path: directory === DEFAULT_KDUMP_PATH ? "" : directory,
     175            2 :                 }
     176            2 :             },
     177            2 :             _internal: {
     178            2 :                 ...settings._internal
     179            2 :             }
     180            2 :         };
     181              : 
     182            2 :         if (storageLocation === "ssh") {
     183            2 :             newSettings.targets.ssh.server = server;
     184            2 :             newSettings.targets.ssh.sshkey = sshkey;
     185            2 :         }
     186              : 
     187            2 :         if (storageLocation === "nfs") {
     188            2 :             newSettings.targets.nfs.server = server;
     189            2 :             newSettings.targets.nfs.export = exportPath;
     190            2 :         }
     191              : 
     192            2 :         handleSave(newSettings)
     193            2 :                 .then(Dialogs.close)
     194            2 :                 .finally(() => setIsSaving(false))
     195            0 :                 .catch(error => {
     196            0 :                     if (error.details) {
     197              :                         // avoid bad summary like "systemd job RestartUnit ["kdump.service","replace"] failed with result failed"
     198              :                         // if we have a more concrete journal and trim journal's `kdump: ` prefix.
     199            0 :                         error.message = _("Unable to save settings");
     200            0 :                         error.details = <CodeBlockCode>{ error.details.replaceAll(/\nkdump: /g, "\n") }</CodeBlockCode>;
     201            0 :                         setError(error);
     202            0 :                     } else {
     203              :                         // without a journal, show the error as-is
     204            0 :                         setError(new Error(cockpit.format(_("Unable to save settings: $0"), String(error))));
     205            0 :                     }
     206            0 :                 });
     207            2 :     };
     208              : 
     209            2 :     return (
     210            2 :         <Modal position="top" variant="small" id="kdump-settings-dialog" isOpen
     211            2 :                onClose={Dialogs.close}>
     212            2 :             <ModalHeader title={_("Crash dump location")} />
     213            2 :             <ModalBody>
     214            0 :                 {error && <ModalError isExpandable
     215            0 :                                       dialogError={error.message || error}
     216            0 :                                       dialogErrorDetail={error.details} />}
     217            2 :                 <Form id="kdump-settings-form" isHorizontal>
     218            2 :                     <FormGroup fieldId="kdump-settings-location" label={_("Location")}>
     219            2 :                         <FormSelect key="location" onChange={(_, val) => changeStorageLocation(val)}
     220            2 :                                     id="kdump-settings-location" value={storageLocation}>
     221            2 :                             <FormSelectOption value='local'
     222            2 :                                               label={_("Local filesystem")} />
     223            2 :                             <FormSelectOption value='ssh'
     224            2 :                                               label={_("Remote over SSH")} />
     225            2 :                             <FormSelectOption value='nfs'
     226            2 :                                               label={_("Remote over NFS")} />
     227            2 :                         </FormSelect>
     228            2 :                     </FormGroup>
     229              : 
     230            2 :                     {storageLocation === "local" &&
     231            2 :                         <FormGroup fieldId="kdump-settings-local-directory" label={_("Directory")} isRequired>
     232            2 :                             <TextInput id="kdump-settings-local-directory" key="directory"
     233            2 :                                        placeholder={DEFAULT_KDUMP_PATH} value={directory}
     234            2 :                                        data-stored={directory}
     235            2 :                                        onChange={(_event, value) => setDirectory(value)}
     236            2 :                                        isRequired />
     237            2 :                         </FormGroup>
     238              :                     }
     239              : 
     240            2 :                     {storageLocation === "nfs" &&
     241            2 :                         <>
     242            2 :                             <FormGroup fieldId="kdump-settings-nfs-server" label={_("Server")} isRequired>
     243            2 :                                 <TextInput id="kdump-settings-nfs-server" key="server"
     244            2 :                                         placeholder="penguin.example.com" value={server}
     245            2 :                                         onChange={(_event, value) => setServer(value)} isRequired />
     246            2 :                             </FormGroup>
     247            2 :                             <FormGroup fieldId="kdump-settings-nfs-export" label={_("Export")} isRequired>
     248            2 :                                 <TextInput id="kdump-settings-nfs-export" key="export"
     249            2 :                                         placeholder="/export/cores" value={exportPath}
     250            2 :                                         onChange={(_event, value) => setExportPath(value)} isRequired />
     251            2 :                             </FormGroup>
     252            2 :                             {settings.nfs_supports_directory &&
     253            1 :                                 <FormGroup fieldId="kdump-settings-nfs-directory" label={_("Directory")} isRequired>
     254            1 :                                     <TextInput id="kdump-settings-nfs-directory" key="directory"
     255            1 :                                             placeholder={DEFAULT_KDUMP_PATH} value={directory}
     256            1 :                                             data-stored={directory}
     257            1 :                                             onChange={(_event, value) => setDirectory(value)}
     258            1 :                                             isRequired />
     259            1 :                                 </FormGroup>
     260              :                             }
     261            2 :                         </>
     262              :                     }
     263              : 
     264            2 :                     {storageLocation === "ssh" &&
     265            2 :                         <>
     266            2 :                             <FormGroup fieldId="kdump-settings-ssh-server" label={_("Server")} isRequired>
     267            2 :                                 <TextInput id="kdump-settings-ssh-server" key="server"
     268            2 :                                            placeholder="user@server.com" value={server}
     269            2 :                                            onChange={(_event, value) => setServer(value)} isRequired />
     270            2 :                             </FormGroup>
     271              : 
     272            2 :                             <FormGroup fieldId="kdump-settings-ssh-key" label={_("SSH key")}>
     273            2 :                                 <TextInput id="kdump-settings-ssh-key" key="ssh"
     274            2 :                                            placeholder="/root/.ssh/kdump_id_rsa" value={sshkey}
     275            2 :                                            onChange={(_event, value) => changeSSHKey(value)}
     276            2 :                                            validated={validationErrors.sshkey ? "error" : "default"} />
     277            2 :                                 <FormHelper helperTextInvalid={validationErrors.sshkey} />
     278            2 :                             </FormGroup>
     279              : 
     280            2 :                             <FormGroup fieldId="kdump-settings-ssh-directory" label={_("Directory")} isRequired>
     281            2 :                                 <TextInput id="kdump-settings-ssh-directory" key="directory"
     282            2 :                                            placeholder={DEFAULT_KDUMP_PATH} value={directory}
     283            2 :                                            data-stored={directory}
     284            2 :                                            onChange={(_event, value) => setDirectory(value)}
     285            2 :                                            isRequired />
     286            2 :                             </FormGroup>
     287            2 :                         </>
     288              :                     }
     289              : 
     290            2 :                     <FormSection>
     291            2 :                         <FormGroup fieldId="kdump-settings-compression" label={_("Compression")} hasNoPaddingTop>
     292            2 :                             <Checkbox id="kdump-settings-compression"
     293            2 :                                       isChecked={compressionEnabled}
     294            2 :                                       onChange={(_, c) => setCompressionEnabled(c)}
     295            2 :                                       isDisabled={!compressionAllowed}
     296            2 :                                       label={_("Compress crash dumps to save space")} />
     297            2 :                         </FormGroup>
     298            2 :                     </FormSection>
     299            2 :                 </Form>
     300            2 :             </ModalBody>
     301            2 :             <ModalFooter>
     302            2 :                 <Button variant="primary"
     303            2 :                         isLoading={isSaving}
     304            2 :                         isDisabled={isSaving || !isFormValid || Object.keys(validationErrors).length !== 0}
     305            2 :                         onClick={saveSettings}>
     306            2 :                     {_("Save changes")}
     307            2 :                 </Button>
     308            2 :                 <Button variant="link"
     309            2 :                         isDisabled={isSaving}
     310            2 :                         className="cancel"
     311            2 :                         onClick={Dialogs.close}>
     312            2 :                     {_("Cancel")}
     313            2 :                 </Button>
     314            2 :             </ModalFooter>
     315            2 :         </Modal>);
     316            2 : };
     317              : 
     318            0 : const KdumpTestDialog = ({ verifyMessage, onCrashKernel }) => {
     319            0 :     const Dialogs = useDialogs();
     320            0 :     const [task, setTask] = useState(null);
     321            0 :     const [error, setError] = useState(null);
     322              : 
     323            0 :     function crash() {
     324            0 :         setError(null);
     325            0 :         setTask(onCrashKernel()
     326            0 :                 .then(Dialogs.close)
     327            0 :                 .catch(error => {
     328            0 :                     setTask(null);
     329            0 :                     setError(error);
     330            0 :                 }));
     331            0 :     }
     332              : 
     333            0 :     return (
     334            0 :         <Modal position="top" variant="small" id="kdump-test-dialog" isOpen
     335            0 :                onClose={Dialogs.close}>
     336            0 :             <ModalHeader title={_("Test kdump settings")} titleIconVariant="warning" />
     337            0 :             <ModalBody>
     338            0 :                 {error && <ModalError dialogError={error.message || error} />}
     339            0 :                 <Content>
     340            0 :                     <Content component={ContentVariants.p}>
     341            0 :                         {_("Test kdump settings by crashing the kernel. This may take a while and the system might not automatically reboot. Do not purposefully crash the system while any important task is running.")}
     342            0 :                     </Content>
     343            0 :                     {verifyMessage && <Content component={ContentVariants.p}>
     344            0 :                         {verifyMessage}
     345            0 :                     </Content>}
     346            0 :                 </Content>
     347            0 :             </ModalBody>
     348            0 :             <ModalFooter>
     349            0 :                 <Button variant="danger"
     350            0 :                         isLoading={!!task}
     351            0 :                         isDisabled={!!task}
     352            0 :                         onClick={crash}>
     353            0 :                     {_("Crash system")}
     354            0 :                 </Button>
     355            0 :                 <Button variant="link"
     356            0 :                         isDisabled={!!task}
     357            0 :                         onClick={Dialogs.close}>
     358            0 :                     {_("Cancel")}
     359            0 :                 </Button>
     360            0 :             </ModalFooter>
     361            0 :         </Modal>);
     362            0 : };
     363              : 
     364              : /* Show kdump status of the system and offer options to change or test the state
     365              :  * Expected properties:
     366              :  * kdumpActive       kdump service status
     367              :  * onSetServiceState called when the OnOff state is toggled (for kdumpActive), parameter: desired state
     368              :  * stateChanging     whether we're currently waiting for our last change to take effect
     369              :  * onSaveSettings   called with current dialog settings when the user clicks Save
     370              :  * kdumpStatus       object as described in kdump-client
     371              :  * reservedMemory    memory reserved at boot time for kdump use
     372              :  * onCrashKernel     callback to crash the kernel via kdumpClient, expects a promise
     373              :  */
     374            3 : export class KdumpPage extends React.Component {
     375            3 :     static contextType = DialogsContext;
     376              : 
     377            3 :     constructor(props) {
     378            3 :         super(props);
     379            3 :         this.state = { os_release: null };
     380              : 
     381            3 :         this.handleTestSettingsClick = this.handleTestSettingsClick.bind(this);
     382            3 :         this.handleSettingsClick = this.handleSettingsClick.bind(this);
     383            3 :         this.handleAutomationClick = this.handleAutomationClick.bind(this);
     384            3 :         read_os_release().then(os_release => this.setState({ os_release }));
     385            3 :     }
     386              : 
     387            0 :     handleTestSettingsClick() {
     388            0 :         const Dialogs = this.context;
     389              :         // if we have multiple targets defined, the config is invalid
     390            0 :         const target = this.props.kdumpStatus.target;
     391            0 :         let verifyMessage;
     392            0 :         if (!target.multipleTargets) {
     393            0 :             const path = getLocation(target, this.props.kdumpStatus.config);
     394            0 :             if (target.type === "local") {
     395            0 :                 verifyMessage = fmt_to_fragments(
     396            0 :                     ' ' + _("Results of the crash will be stored in $0 as $1, if kdump is properly configured."),
     397            0 :                     <span className="pf-v6-u-font-family-monospace-vf">{path}</span>,
     398            0 :                     <span className="pf-v6-u-font-family-monospace-vf">vmcore</span>);
     399            0 :             } else if (target.type === "ssh" || target.type == "nfs") {
     400            0 :                 verifyMessage = fmt_to_fragments(
     401            0 :                     ' ' + _("Results of the crash will be copied through $0 to $1 as $2, if kdump is properly configured."),
     402            0 :                     <span className="pf-v6-u-font-family-monospace-vf">{target.type === "ssh" ? "SSH" : "NFS"}</span>,
     403            0 :                     <span className="pf-v6-u-font-family-monospace-vf">{path}</span>,
     404            0 :                     <span className="pf-v6-u-font-family-monospace-vf">vmcore</span>);
     405            0 :             }
     406            0 :         }
     407              : 
     408            0 :         Dialogs.show(<KdumpTestDialog verifyMessage={verifyMessage}
     409            0 :                                       onCrashKernel={this.props.onCrashKernel} />);
     410            0 :     }
     411              : 
     412            0 :     handleServiceDetailsClick() {
     413            0 :         cockpit.jump("/system/services#/kdump.service", cockpit.transport.host);
     414            0 :     }
     415              : 
     416            2 :     handleSettingsClick() {
     417            2 :         const Dialogs = this.context;
     418            2 :         Dialogs.show(<KdumpSettingsModal settings={this.props.kdumpStatus.config}
     419            2 :                                          initialTarget={this.props.kdumpStatus.target}
     420            2 :                                          handleSave={this.props.onSaveSettings} />);
     421            2 :     }
     422              : 
     423            0 :     handleAutomationClick() {
     424            0 :         const Dialogs = this.context;
     425            0 :         let enableCrashKernel = '';
     426            0 :         let kdumpconf = this.props.exportConfig(this.props.kdumpStatus.config);
     427            0 :         kdumpconf = kdumpconf.replaceAll('$', '\\$');
     428            0 :         if (this.state.os_release.NAME?.includes('Fedora')) {
     429            0 :             enableCrashKernel = `
     430              : # A reboot will be required if crashkernel was not set before
     431              : kdumpctl reset-crashkernel`;
     432            0 :         }
     433            0 :         let shell;
     434            0 :         if (this.state.os_release.NAME?.includes('MicroOS')) {
     435            0 :             enableCrashKernel = `
     436              : # A reboot will be required if crashkernel was not set before
     437              : transactional-update setup-kdump`;
     438            0 :             shell = `
     439              : cat > /etc/kdump.conf << EOF
     440            0 :  ${kdumpconf}
     441              : EOF
     442            0 : ${enableCrashKernel}
     443              :         `;
     444            0 :         } else {
     445            0 :             shell = `
     446              : cat > /etc/kdump.conf << EOF
     447            0 : ${kdumpconf}
     448              : EOF
     449              : systemctl enable --now kdump.service
     450            0 : ${enableCrashKernel}
     451              : `;
     452            0 :         }
     453              : 
     454            0 :         Dialogs.show(
     455            0 :             <ModificationsExportDialog
     456            0 :               ansible={ this.state.os_release.NAME?.includes('MicroOS') ? null : exportAnsibleTask(this.props.kdumpStatus.config, this.state.os_release)}
     457            0 :               shell={shell}
     458            0 :               onClose={Dialogs.close}
     459            0 :             />);
     460            0 :     }
     461              : 
     462            3 :     render() {
     463            3 :         let kdumpLocation = (
     464            3 :             <div className="dialog-wait-ct">
     465            3 :                 <Spinner size="md" />
     466            3 :                 <span>{ _("Loading...") }</span>
     467            3 :             </div>
     468              :         );
     469            3 :         let targetCanChange = false;
     470            3 :         if (this.props.kdumpStatus && this.props.kdumpStatus.target) {
     471              :             // if we have multiple targets defined, the config is invalid
     472            3 :             const target = this.props.kdumpStatus.target;
     473            0 :             if (target.multipleTargets) {
     474            0 :                 kdumpLocation = _("invalid: multiple targets defined");
     475            0 :             } else {
     476            3 :                 const locationPath = getLocation(target, this.props.kdumpStatus.config);
     477            3 :                 if (target.type == "local") {
     478            3 :                     kdumpLocation = cockpit.format(_("Local, $0"), locationPath);
     479            3 :                     targetCanChange = true;
     480            2 :                 } else if (target.type == "ssh") {
     481            2 :                     kdumpLocation = cockpit.format(_("Remote over SSH, $0"), locationPath);
     482            2 :                     targetCanChange = true;
     483            2 :                 } else if (target.type == "nfs") {
     484            2 :                     kdumpLocation = cockpit.format(_("Remote over NFS, $0"), locationPath);
     485            2 :                     targetCanChange = true;
     486            0 :                 } else if (target.type == "raw") {
     487            0 :                     kdumpLocation = _("Raw to a device");
     488            0 :                 } else if (target.type == "mount") {
     489              :                     /* mount targets outside of nfs are too complex for the
     490              :                      * current target dialog */
     491            0 :                     kdumpLocation = _("On a mounted device");
     492            0 :                 } else if (target.type == "ftp") {
     493            1 :                     kdumpLocation = _("Remote over FTP");
     494            1 :                 } else if (target.type == "sftp") {
     495            1 :                     kdumpLocation = _("Remote over SFTP");
     496            1 :                 } else if (target.type == "cifs") {
     497            1 :                     kdumpLocation = _("Remote over CIFS/SMB");
     498            1 :                 } else {
     499            1 :                     kdumpLocation = _("No configuration found");
     500            1 :                 }
     501            3 :             }
     502            3 :         }
     503              :         // this.storeLocation(this.props.kdumpStatus.config);
     504            3 :         const settingsLink = targetCanChange && <Button variant="link" isInline id="kdump-change-target" onClick={this.handleSettingsClick}>{_("Edit")}</Button>;
     505            3 :         let reservedMemory;
     506            3 :         if (this.props.reservedMemory === undefined) {
     507              :             // still waiting for result
     508            3 :             reservedMemory = (
     509            3 :                 <div className="dialog-wait-ct">
     510            3 :                     <Spinner size="md" />
     511            3 :                     <span>{ _("Reading...") }</span>
     512            3 :                 </div>
     513              :             );
     514            3 :         } else if (this.props.reservedMemory === 0) {
     515              :             // nothing reserved
     516            3 :             reservedMemory = <span>{_("None")} </span>;
     517            0 :         } else if (Number.isInteger(this.props.reservedMemory)) {
     518              :             // TODO: hint at using debug_mem_level to identify actual memory required?
     519            0 :             reservedMemory = <span>{cockpit.format_bytes(this.props.reservedMemory, { base2: true })}</span>;
     520            0 :         } else {
     521              :             // error while reading
     522            0 :             reservedMemory = null;
     523            0 :         }
     524              : 
     525            3 :         const serviceRunning = this.props.kdumpStatus?.target &&
     526            3 :                              this.props.kdumpStatus?.installed &&
     527            3 :                              this.props.kdumpStatus?.state === "running";
     528              : 
     529            3 :         let testButton;
     530            0 :         if (serviceRunning) {
     531            0 :             testButton = (
     532            0 :                 <PrivilegedButton variant="secondary" isDanger
     533            0 :                                   excuse={ _("The user $0 is not permitted to test crash the kernel") }
     534            0 :                                   onClick={this.handleTestSettingsClick}>
     535            0 :                     { _("Test configuration") }
     536            0 :                 </PrivilegedButton>
     537              :             );
     538            0 :         } else {
     539            3 :             const tooltip = _("Test is only available while the kdump service is running.");
     540            3 :             testButton = (
     541            3 :                 <Tooltip id="tip-test" content={tooltip}>
     542            3 :                     <Button variant="secondary" isDanger isAriaDisabled>
     543            3 :                         {_("Test configuration")}
     544            3 :                     </Button>
     545            3 :                 </Tooltip>
     546              :             );
     547            3 :         }
     548              : 
     549            3 :         let automationButton = null;
     550            3 :         if (this.props.kdumpStatus && this.props.kdumpStatus.config !== null && this.state.os_release !== null && targetCanChange) {
     551            3 :             automationButton = (
     552            3 :                 <FlexItem align={{ md: 'alignRight' }}>
     553            3 :                     <Button id="kdump-automation-script" variant="secondary" onClick={this.handleAutomationClick}>
     554            3 :                         {_("View automation script")}
     555            3 :                     </Button>
     556            3 :                 </FlexItem>
     557              :             );
     558            3 :         }
     559              : 
     560            3 :         let kdumpSwitch;
     561            3 :         let kdumpSwitchHelper;
     562            3 :         if (!this.props.kdumpCmdlineEnabled) {
     563            3 :             kdumpSwitchHelper = _("Currently not supported");
     564            0 :         } else {
     565            0 :             kdumpSwitch = (<Switch isChecked={!!serviceRunning}
     566            0 :                 onChange={this.props.onSetServiceState}
     567            0 :                 aria-label={_("kdump status")}
     568            0 :                 label={serviceRunning ? _("Enabled") : _("Disabled")}
     569            0 :                 isDisabled={this.props.stateChanging} />);
     570            0 :         }
     571              : 
     572            3 :         let alertMessage;
     573            3 :         let alertDetail;
     574            3 :         if (!this.props.stateChanging && this.props.kdumpStatus && this.props.kdumpStatus.installed !== undefined) {
     575            3 :             if (this.props.kdumpStatus.installed) {
     576            3 :                 if (this.props.reservedMemory == 0) {
     577            3 :                     alertMessage = fmt_to_fragments(
     578            3 :                         _("Kernel did not boot with the $0 setting"),
     579            3 :                         <span className="pf-v6-u-font-family-monospace-vf">crashkernel</span>
     580            3 :                     );
     581            3 :                     alertDetail = fmt_to_fragments(
     582            3 :                         _("Reserve memory at boot time by setting a '$0' option on the kernel command line. For example, append '$1' to $2  in $3 or use your distribution's kernel argument editor."),
     583            3 :                         <span className="pf-v6-u-font-family-monospace-vf">crashkernel</span>,
     584            3 :                         <span className="pf-v6-u-font-family-monospace-vf">crashkernel=512M</span>,
     585            3 :                         <span className="pf-v6-u-font-family-monospace-vf">GRUB_CMDLINE_LINUX</span>,
     586            3 :                         <span className="pf-v6-u-font-family-monospace-vf">/etc/default/grub</span>
     587            3 :                     );
     588            0 :                 } else if (this.props.kdumpStatus.state == "failed") {
     589            0 :                     alertMessage = (
     590            0 :                         <>
     591            0 :                             {_("Service has an error")}
     592            0 :                             <Button variant="link" isInline className="pf-v6-u-ml-sm" onClick={this.handleServiceDetailsClick}>{_("more details")}</Button>
     593            0 :                         </>
     594              :                     );
     595            0 :                 }
     596            0 :             } else {
     597            0 :                 alertMessage = _("Kdump service is not installed.");
     598            0 :                 alertDetail = fmt_to_fragments(
     599            0 :                     _("Install the $0 package."),
     600            0 :                     <span className="pf-v6-u-font-family-monospace-vf">kexec-tools</span>
     601            0 :                 );
     602            0 :             }
     603            3 :         }
     604            3 :         return (
     605            3 :             <Page className="pf-m-no-sidebar">
     606            3 :                 <PageSection hasBodyWrapper={false}>
     607            3 :                     <Flex spaceItems={{ default: 'spaceItemsMd' }} alignItems={{ default: 'alignItemsCenter' }}>
     608            3 :                         <Title headingLevel="h2" size="3xl">
     609            3 :                             {_("Kernel crash dump")}
     610            3 :                         </Title>
     611            3 :                         {kdumpSwitch}
     612            3 :                         {kdumpSwitchHelper &&
     613            3 :                             <HelperText className="subtle-helper-text">
     614            3 :                                 <HelperTextItem>{kdumpSwitchHelper}</HelperTextItem>
     615            3 :                             </HelperText>}
     616            3 :                         {automationButton}
     617            3 :                     </Flex>
     618            3 :                 </PageSection>
     619            3 :                 <PageSection hasBodyWrapper={false}>
     620              : 
     621            3 :                     {alertMessage &&
     622            3 :                         <Alert variant='danger'
     623            3 :                             className="pf-v6-u-mb-md"
     624            3 :                             isLiveRegion={this.props.isLiveRegion}
     625            3 :                             isInline
     626            3 :                             title={alertMessage}>
     627            3 :                             {alertDetail}
     628            3 :                         </Alert>
     629              :                     }
     630            3 :                     <Card isPlain>
     631            3 :                         <CardTitle>
     632            3 :                             <Title headingLevel="h4" size="xl">
     633            3 :                                 {_("Kdump settings")}
     634            3 :                             </Title>
     635            3 :                         </CardTitle>
     636            3 :                         <CardBody>
     637            3 :                             <DescriptionList className="pf-m-horizontal-on-sm">
     638            3 :                                 <DescriptionListGroup>
     639            3 :                                     <DescriptionListTerm>{_("Reserved memory")}</DescriptionListTerm>
     640            3 :                                     <DescriptionListDescription>
     641            3 :                                         {reservedMemory}
     642            3 :                                     </DescriptionListDescription>
     643            3 :                                 </DescriptionListGroup>
     644              : 
     645            3 :                                 <DescriptionListGroup>
     646            3 :                                     <DescriptionListTerm>{_("Crash dump location")}</DescriptionListTerm>
     647            3 :                                     <DescriptionListDescription>
     648            3 :                                         <Flex spaceItems={{ default: 'spaceItemsSm' }}>
     649            3 :                                             <span id="kdump-target-info">{ kdumpLocation }</span>
     650            3 :                                             {settingsLink}
     651            3 :                                         </Flex>
     652            3 :                                     </DescriptionListDescription>
     653            3 :                                 </DescriptionListGroup>
     654              : 
     655            3 :                                 <DescriptionListGroup>
     656            3 :                                     <DescriptionListTerm />
     657            3 :                                     <DescriptionListDescription>
     658            3 :                                         {testButton}
     659            3 :                                     </DescriptionListDescription>
     660            3 :                                 </DescriptionListGroup>
     661            3 :                             </DescriptionList>
     662            3 :                         </CardBody>
     663            3 :                     </Card>
     664            3 :                 </PageSection>
     665            3 :             </Page>
     666              :         );
     667            3 :     }
     668            3 : }
        

Generated by: LCOV version 2.0-1