diff --git a/.env b/.env
index 423168a628..07b6eec919 100644
--- a/.env
+++ b/.env
@@ -7,4 +7,7 @@ VITE_APP_BASENAME=${BASENAME}
VITE_APP_VITE_BASENAME=${VITE_BASENAME}
VITE_APP_WEBSOCKET_DEBUG=false
VITE_APP_USABILLA_ID=fd6cf482fbbb
+
+# Feature flags
VITE_APP_STATIC_IPS_ENABLED=false
+VITE_APP_KERNEL_CRASH_DUMPS_ENABLED=false
\ No newline at end of file
diff --git a/src/app/settings/views/Configuration/KernelParametersForm/KernelParametersForm.test.tsx b/src/app/settings/views/Configuration/KernelParametersForm/KernelParametersForm.test.tsx
index 246edd3b87..2c423e1a05 100644
--- a/src/app/settings/views/Configuration/KernelParametersForm/KernelParametersForm.test.tsx
+++ b/src/app/settings/views/Configuration/KernelParametersForm/KernelParametersForm.test.tsx
@@ -1,6 +1,7 @@
import { Provider } from "react-redux";
import { MemoryRouter } from "react-router-dom";
import configureStore from "redux-mock-store";
+import { test } from "vitest";
import KernelParametersForm, {
Labels as FormLabels,
@@ -9,7 +10,16 @@ import KernelParametersForm, {
import { ConfigNames } from "@/app/store/config/types";
import type { RootState } from "@/app/store/root/types";
import * as factory from "@/testing/factories";
-import { screen, render } from "@/testing/utils";
+import {
+ screen,
+ render,
+ renderWithBrowserRouter,
+ userEvent,
+ waitFor,
+} from "@/testing/utils";
+
+const kernelCrashDumpEnabled =
+ process.env.VITE_APP_KERNEL_CRASH_DUMP_ENABLED === "true";
const mockStore = configureStore();
@@ -46,4 +56,75 @@ describe("KernelParametersForm", () => {
})
).toHaveValue("foo");
});
+
+ it("dispatches an action to update the kernel_opts value", async () => {
+ const state = { ...initialState };
+ const store = mockStore(state);
+
+ renderWithBrowserRouter(, { store });
+
+ await userEvent.clear(
+ screen.getByRole("textbox", { name: FormLabels.GlobalBootParams })
+ );
+ await userEvent.type(
+ screen.getByRole("textbox", { name: FormLabels.GlobalBootParams }),
+ "bar"
+ );
+
+ await userEvent.click(screen.getByRole("button", { name: "Save" }));
+ expect(
+ store.getActions().find((action) => action.type === "config/update")
+ ).toStrictEqual({
+ meta: {
+ method: "bulk_update",
+ model: "config",
+ },
+ type: "config/update",
+ payload: {
+ params: {
+ items: {
+ kernel_opts: "bar",
+ },
+ },
+ },
+ });
+ });
+
+ test.runIf(kernelCrashDumpEnabled)(
+ "shows a tooltip for minimum OS requirements",
+ async () => {
+ renderWithBrowserRouter(, {
+ state: { ...initialState },
+ });
+
+ await userEvent.hover(
+ screen.getAllByRole("button", { name: "help-mid-dark" })[1]
+ );
+
+ await waitFor(() => {
+ expect(screen.getByRole("tooltip")).toHaveTextContent(
+ "Ubuntu 24.04 LTS or higher."
+ );
+ });
+ }
+ );
+
+ test.runIf(kernelCrashDumpEnabled)(
+ "shows a tooltip for minimum hardware requirements",
+ async () => {
+ renderWithBrowserRouter(, {
+ state: { ...initialState },
+ });
+
+ await userEvent.hover(
+ screen.getAllByRole("button", { name: "help-mid-dark" })[0]
+ );
+
+ await waitFor(() => {
+ expect(screen.getByRole("tooltip")).toHaveTextContent(
+ ">= 4 CPU threads, >= 6GB RAM, Reserve >5x RAM size as free disk space in /var."
+ );
+ });
+ }
+ );
});
diff --git a/src/app/settings/views/Configuration/KernelParametersForm/KernelParametersForm.tsx b/src/app/settings/views/Configuration/KernelParametersForm/KernelParametersForm.tsx
index de25597ec8..0fd8cd5ca2 100644
--- a/src/app/settings/views/Configuration/KernelParametersForm/KernelParametersForm.tsx
+++ b/src/app/settings/views/Configuration/KernelParametersForm/KernelParametersForm.tsx
@@ -1,8 +1,10 @@
+import { ExternalLink } from "@canonical/maas-react-components";
import { useDispatch, useSelector } from "react-redux";
import * as Yup from "yup";
import FormikField from "@/app/base/components/FormikField";
import FormikForm from "@/app/base/components/FormikForm";
+import TooltipButton from "@/app/base/components/TooltipButton";
import { configActions } from "@/app/store/config";
import configSelectors from "@/app/store/config/selectors";
@@ -13,6 +15,7 @@ type KernelParametersValues = {
export enum Labels {
FormLabel = "Configuration - Kernel parameters",
GlobalBootParams = "Global boot parameters always passed to the kernel",
+ KernelCrashDump = "Try to enable kernel crash dump by default",
}
const KernelParametersSchema = Yup.object()
@@ -45,18 +48,54 @@ const KernelParametersForm = (): JSX.Element => {
label: "Kernel parameters form",
}}
onSubmit={(values, { resetForm }) => {
- dispatch(updateConfig(values));
+ dispatch(updateConfig({ kernel_opts: values.kernel_opts }));
resetForm({ values });
}}
saved={saved}
saving={saving}
validationSchema={KernelParametersSchema}
>
+ General
+ {import.meta.env.VITE_APP_KERNEL_CRASH_DUMP_ENABLED === "true" && (
+ <>
+ Kernel crash dump
+
+ To enable kernel crash dump, the hardware{" "}
+
+ {" "}
+ >= 4 CPU threads,
>= 6GB RAM,
+ Reserve >5x RAM size as free disk space in /var.
+
+ }
+ />{" "}
+ and OS{" "}
+ {" "}
+ must meet the minimum requirements and secure boot must be
+ disabled. Check crash dump status in machine details.{" "}
+
+ More about kernel crash dump
+
+ >
+ }
+ label={Labels.KernelCrashDump}
+ name="kernel_crash_dump"
+ type="checkbox"
+ />
+ >
+ )}
);
};
diff --git a/src/scss/_patterns_icons.scss b/src/scss/_patterns_icons.scss
index 60c878ce92..dcaf87bbec 100644
--- a/src/scss/_patterns_icons.scss
+++ b/src/scss/_patterns_icons.scss
@@ -171,6 +171,11 @@
@include maas-icon-sidebar-collapse();
}
+ .p-icon--help-mid-dark {
+ @extend %icon;
+ @include vf-icon-help($color-mid-dark);
+ }
+
[class*="p-circle--"].is-inline,
[class*="p-icon--"].is-inline {
margin-right: $sph--small;