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}