/*
* Copyright (C) 2016 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
import '../lib/patternfly/patternfly-6-cockpit.scss';
import cockpit from "cockpit";
import React, { useEffect, useState } from "react";
import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Checkbox } from "@patternfly/react-core/dist/esm/components/Checkbox/index.js";
import { Card, CardBody, CardTitle } from "@patternfly/react-core/dist/esm/components/Card/index.js";
import { HelperText, HelperTextItem } from "@patternfly/react-core/dist/esm/components/HelperText/index.js";
import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
import { Form, FormGroup, FormSection } from "@patternfly/react-core/dist/esm/components/Form/index.js";
import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect/index.js";
import { Page, PageSection } from "@patternfly/react-core/dist/esm/components/Page/index.js";
import { CodeBlockCode } from "@patternfly/react-core/dist/esm/components/CodeBlock/index.js";
import { DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
import {
Modal, ModalBody, ModalFooter, ModalHeader
} from '@patternfly/react-core/dist/esm/components/Modal/index.js';
import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner/index.js";
import { Switch } from "@patternfly/react-core/dist/esm/components/Switch/index.js";
import { Content, ContentVariants } from "@patternfly/react-core/dist/esm/components/Content/index.js";
import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js";
import { Title } from "@patternfly/react-core/dist/esm/components/Title/index.js";
import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip/index.js";
import { useDialogs, DialogsContext } from "dialogs.jsx";
import { read_os_release } from "os-release.js";
import { fmt_to_fragments } from 'utils.jsx';
import { FormHelper } from "cockpit-components-form-helper";
import { ModalError } from 'cockpit-components-inline-notification.jsx';
import { PrivilegedButton } from "cockpit-components-privileged";
import { ModificationsExportDialog } from "cockpit-components-modifications";
const _ = cockpit.gettext;
const DEFAULT_KDUMP_PATH = "/var/crash";
const exportAnsibleTask = (settings, os_release) => {
const target = Object.keys(settings.targets)[0];
const targetSettings = settings.targets[target];
const kdump_core_collector = settings.core_collector;
let role_name = "linux-system-roles";
if (os_release?.PLATFORM_ID?.startsWith('platform:el') || os_release.ID_LIKE?.includes('rhel')) {
role_name = "rhel-system-roles";
}
let ansible = `
---
# Also available via https://galaxy.ansible.com/ui/standalone/roles/linux-system-roles/kdump/
- name: install ${role_name}
package:
name: ${role_name}
state: present
delegate_to: 127.0.0.1
become: true
- name: run kdump system role
include_role:
name: ${role_name}.kdump
vars:
kdump_path: ${targetSettings.path || DEFAULT_KDUMP_PATH}
kdump_core_collector: ${kdump_core_collector}`;
if (target === "ssh") {
// HACK: we should not have to specify kdump_ssh_user and kdump_ssh_user as it is in kdump_target.location
// https://github.com/linux-system-roles/kdump/issues/184
let ssh_user;
let ssh_server;
const parts = targetSettings.server.split('@');
if (parts.length === 1) {
ssh_user = "root";
ssh_server = parts[0];
} else if (parts.length === 2) {
ssh_user = parts[0];
ssh_server = parts[1];
} else {
throw new Error("ssh server contains two @ symbols");
}
ansible += `
kdump_target:
type: ssh
kdump_sshkey: ${targetSettings.sshkey}
kdump_ssh_server: ${ssh_server}
kdump_ssh_user: ${ssh_user}`;
} else if (target === "nfs") {
ansible += `
kdump_target:
type: nfs
location: ${targetSettings.server}:${targetSettings.export}
`;
} else if (target !== "local") {
// target is unsupported
throw new Error("Unsupported kdump target"); // not-covered: assertion
}
return ansible;
};
function getLocation(target, config) {
let path = target.path || DEFAULT_KDUMP_PATH;
if (target.type === "ssh") {
path = `${target.server}:${path}`;
} else if (target.type === "nfs") {
if (!config.nfs_supports_directory) {
path = '';
}
path = path[0] !== '/' ? '/' + path : path;
path = `${target.server}:${target.export + path}`;
}
return path;
}
const KdumpSettingsModal = ({ settings, initialTarget, handleSave }) => {
const Dialogs = useDialogs();
const compressionAllowed = settings.compression?.allowed;
const [isSaving, setIsSaving] = useState(false);
const [error, setError] = useState(null);
const [isFormValid, setFormValid] = useState(true);
const [validationErrors, setValidationErrors] = useState({});
const [storageLocation, setStorageLocation] = useState(Object.keys(settings.targets)[0]);
// common options
const [compressionEnabled, setCompressionEnabled] = useState(settings.compression?.enabled);
const [directory, setDirectory] = useState(initialTarget.path || DEFAULT_KDUMP_PATH);
// nfs and ssh
const [server, setServer] = useState(settings.targets.nfs?.server || settings.targets.ssh?.server);
// nfs
const [exportPath, setExportPath] = useState(settings.targets.nfs?.export || "");
// ssh
const [sshkey, setSSHKey] = useState(settings.targets.ssh?.sshkey || "");
useEffect(() => {
// We can't use a ref in a functional component
const elem = document.querySelector("#kdump-settings-form");
if (elem)
setFormValid(elem.checkValidity());
}, [storageLocation, directory, sshkey, server, exportPath]);
const changeStorageLocation = target => {
setError(null);
setDirectory(DEFAULT_KDUMP_PATH);
setServer("");
setStorageLocation(target);
};
const changeSSHKey = value => {
if (value.trim() && !value.match("/.+")) {
setValidationErrors({ sshkey: _("SSH key isn't a path") });
} else {
setValidationErrors({});
}
setSSHKey(value);
};
const saveSettings = () => {
setError(null);
setIsSaving(true);
const newSettings = {
compression: {
allowed: compressionAllowed,
enabled: compressionEnabled,
},
targets: {
[storageLocation]: {
type: storageLocation,
// HACK: to not needlessly write a path /var/crash as this is the default,
// set an empty string.
path: directory === DEFAULT_KDUMP_PATH ? "" : directory,
}
},
_internal: {
...settings._internal
}
};
if (storageLocation === "ssh") {
newSettings.targets.ssh.server = server;
newSettings.targets.ssh.sshkey = sshkey;
}
if (storageLocation === "nfs") {
newSettings.targets.nfs.server = server;
newSettings.targets.nfs.export = exportPath;
}
handleSave(newSettings)
.then(Dialogs.close)
.finally(() => setIsSaving(false))
.catch(error => {
if (error.details) {
// avoid bad summary like "systemd job RestartUnit ["kdump.service","replace"] failed with result failed"
// if we have a more concrete journal and trim journal's `kdump: ` prefix.
error.message = _("Unable to save settings");
error.details =