LCOV - code coverage report
Current view: top level - pkg/kdump - kdump-view.jsx Coverage Total Hit
Test: cockpit Lines: 66.9 % 493 330
Test Date: 2026-07-03 07:31:16

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

Generated by: LCOV version 2.0-1