diff --git a/src/app/machines/views/MachineList/MachineList.test.tsx b/src/app/machines/views/MachineList/MachineList.test.tsx
index 46b2a3164e..0f1b0b3622 100644
--- a/src/app/machines/views/MachineList/MachineList.test.tsx
+++ b/src/app/machines/views/MachineList/MachineList.test.tsx
@@ -20,6 +20,7 @@ import type { RootState } from "app/store/root/types";
import {
NodeStatus,
NodeStatusCode,
+ NodeType,
TestStatusStatus,
} from "app/store/types/node";
import {
@@ -33,6 +34,9 @@ import {
testStatus as testStatusFactory,
machineStateList as machineStateListFactory,
machineStateListGroup as machineStateListGroupFactory,
+ vaultEnabledState as vaultEnabledStateFactory,
+ controllerState as controllerStateFactory,
+ controller as controllerFactory,
} from "testing/factories";
import { renderWithBrowserRouter } from "testing/utils";
@@ -181,6 +185,7 @@ describe("MachineList", () => {
loaded: false,
loading: false,
},
+ vaultEnabled: vaultEnabledStateFactory({ data: false, loaded: true }),
osInfo: {
data: osInfoFactory({
osystems: [["ubuntu", "Ubuntu"]],
@@ -191,6 +196,10 @@ describe("MachineList", () => {
loading: false,
},
}),
+ controller: controllerStateFactory({
+ loaded: true,
+ items: [controllerFactory({ vault_configured: false })],
+ }),
machine: machineStateFactory({
items: machines,
lists: {
@@ -705,4 +714,106 @@ describe("MachineList", () => {
expect(fetches).toHaveLength(2);
expect(fetches[fetches.length - 1].payload.params.page_number).toBe(2);
});
+
+ it("shows a warning notification if not all controllers are configured with Vault", async () => {
+ const controllers = [
+ controllerFactory({
+ system_id: "abc123",
+ vault_configured: true,
+ node_type: NodeType.REGION_CONTROLLER,
+ }),
+ controllerFactory({
+ system_id: "def456",
+ vault_configured: false,
+ node_type: NodeType.REGION_AND_RACK_CONTROLLER,
+ }),
+ ];
+ state.controller.items = controllers;
+
+ renderWithBrowserRouter(
+ ,
+ { wrapperProps: { state } }
+ );
+
+ expect(screen.getByTestId("vault-notification")).toHaveTextContent(
+ "Configure 1 other controller with Vault to complete this operation."
+ );
+ });
+
+ it("shows a warning notification if all controllers are configured with Vault but secrets are not migrated", async () => {
+ const controllers = [
+ controllerFactory({
+ system_id: "abc123",
+ vault_configured: true,
+ node_type: NodeType.REGION_CONTROLLER,
+ }),
+ controllerFactory({
+ system_id: "def456",
+ vault_configured: true,
+ node_type: NodeType.REGION_AND_RACK_CONTROLLER,
+ }),
+ ];
+ state.controller.items = controllers;
+
+ renderWithBrowserRouter(
+ ,
+ { wrapperProps: { state } }
+ );
+
+ expect(screen.getByTestId("vault-notification")).toHaveTextContent(
+ "Migrate your secrets to Vault to complete this operation."
+ );
+ });
+
+ it("doesn't show a warning notification if Vault setup has not been started", async () => {
+ const controllers = [
+ controllerFactory({
+ system_id: "abc123",
+ vault_configured: false,
+ node_type: NodeType.REGION_CONTROLLER,
+ }),
+ controllerFactory({
+ system_id: "def456",
+ vault_configured: false,
+ node_type: NodeType.REGION_AND_RACK_CONTROLLER,
+ }),
+ ];
+ state.controller.items = controllers;
+
+ renderWithBrowserRouter(
+ ,
+ { wrapperProps: { state } }
+ );
+
+ expect(screen.queryByTestId("vault-notification")).not.toBeInTheDocument();
+ });
+
+ it("doesn't show a warning notification if Vault is fully configured", async () => {
+ const controllers = [
+ controllerFactory({
+ system_id: "abc123",
+ vault_configured: true,
+ node_type: NodeType.REGION_CONTROLLER,
+ }),
+ controllerFactory({
+ system_id: "def456",
+ vault_configured: true,
+ node_type: NodeType.REGION_AND_RACK_CONTROLLER,
+ }),
+ ];
+ state.controller.items = controllers;
+ state.general = generalStateFactory({
+ vaultEnabled: vaultEnabledStateFactory({
+ data: true,
+ loaded: true,
+ }),
+ });
+
+ renderWithBrowserRouter(
+ ,
+ { wrapperProps: { state } }
+ );
+
+ expect(screen.queryByTestId("vault-notification")).not.toBeInTheDocument();
+ });
});
diff --git a/src/app/machines/views/MachineList/MachineList.tsx b/src/app/machines/views/MachineList/MachineList.tsx
index e401da7ca7..c8a5e8e655 100644
--- a/src/app/machines/views/MachineList/MachineList.tsx
+++ b/src/app/machines/views/MachineList/MachineList.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState } from "react";
import type { ValueOf } from "@canonical/react-components";
+import { Link, Notification } from "@canonical/react-components";
import { useDispatch, useSelector } from "react-redux";
import { useStorageState } from "react-storage-hooks";
@@ -11,11 +12,16 @@ import { DEFAULTS } from "./MachineListTable/constants";
import { useWindowTitle } from "app/base/hooks";
import type { SetSearchFilter, SortDirection } from "app/base/types";
+import { actions as controllerActions } from "app/store/controller";
+import controllerSelectors from "app/store/controller/selectors";
+import { actions as generalActions } from "app/store/general";
+import { vaultEnabled as vaultEnabledSelectors } from "app/store/general/selectors";
import { actions as machineActions } from "app/store/machine";
import machineSelectors from "app/store/machine/selectors";
import { FetchGroupKey } from "app/store/machine/types";
import { mapSortDirection, FilterMachineItems } from "app/store/machine/utils";
import { useFetchMachines } from "app/store/machine/utils/hooks";
+import type { RootState } from "app/store/root/types";
type Props = {
headerFormOpen?: boolean;
@@ -74,6 +80,12 @@ const MachineList = ({
[]
);
+ const { unconfiguredControllers, configuredControllers } = useSelector(
+ (state: RootState) =>
+ controllerSelectors.getVaultConfiguredControllers(state)
+ );
+ const vaultEnabled = useSelector(vaultEnabledSelectors.get);
+
const { callId, loading, machineCount, machines, machinesErrors } =
useFetchMachines({
collapsedGroups: hiddenGroups,
@@ -94,6 +106,12 @@ const MachineList = ({
[dispatch]
);
+ // Fetch vault enabled status and controllers on page load
+ useEffect(() => {
+ dispatch(controllerActions.fetch());
+ dispatch(generalActions.fetchVaultEnabled());
+ }, [dispatch]);
+
return (
<>
{errors && !headerFormOpen ? (
@@ -103,6 +121,32 @@ const MachineList = ({
/>
) : null}
{!headerFormOpen ? : null}
+ {configuredControllers.length >= 1 &&
+ unconfiguredControllers.length >= 1 ? (
+
+ Configure {unconfiguredControllers.length} other{" "}
+
+ {unconfiguredControllers.length > 1 ? "controllers" : "controller"}
+ {" "}
+ with Vault to complete this operation. Check the{" "}
+ security settings{" "}
+ for more information.
+
+ ) : unconfiguredControllers.length === 0 && vaultEnabled === false ? (
+
+ Migrate your secrets to Vault to complete this operation. Check the{" "}
+ security settings{" "}
+ for more information.
+
+ ) : null}