From 978940176ea652289565ac5243f3c132fdbeeee0 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Thu, 19 Sep 2024 15:23:22 -0700 Subject: [PATCH 01/23] Bare bones --- .../cards/MdmSettings/MdmSettings.tsx | 6 + .../CertificatesSection.tests.tsx | 0 .../CertificatesSection.tsx | 132 ++++++++++++++++++ .../CertificatesSection/_styles.scss | 51 +++++++ .../components/CertificatesSection/index.ts | 1 + frontend/router/paths.ts | 1 + 6 files changed, 191 insertions(+) create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tests.tsx create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/_styles.scss create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/index.ts diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx index b6e0f02795f0..bf9f45288f5a 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx @@ -17,6 +17,7 @@ import VppSection from "./components/VppSection"; import IdpSection from "./components/IdpSection"; import EulaSection from "./components/EulaSection"; import EndUserMigrationSection from "./components/EndUserMigrationSection"; +import ScepSection from "./components/CertificatesSection/CertificatesSection"; const baseClass = "mdm-settings"; @@ -130,6 +131,11 @@ const MdmSettings = ({ router }: IMdmSettingsProps) => { isVppOn={!noVppTokenUploaded} isPremiumTier={!!isPremiumTier} /> + {isPremiumTier && !!config?.mdm.apple_bm_enabled_and_configured && ( <> diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tests.tsx new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx new file mode 100644 index 000000000000..08c3134fe966 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx @@ -0,0 +1,132 @@ +import React, { useContext } from "react"; +import { InjectedRouter } from "react-router"; + +import PATHS from "router/paths"; +import { AppContext } from "context/app"; + +import Card from "components/Card"; +import Button from "components/buttons/Button"; +import Icon from "components/Icon"; + +import SettingsSection from "pages/admin/components/SettingsSection"; +import PremiumFeatureMessage from "components/PremiumFeatureMessage"; + +const baseClass = "certificates-section"; + +interface IScepCardProps { + isAppleMdmOn: boolean; + isScepOn: boolean; + router: InjectedRouter; +} + +const ScepCard = ({ isAppleMdmOn, isScepOn, router }: IScepCardProps) => { + const navigateToScepSetup = () => { + router.push(PATHS.ADMIN_INTEGRATIONS_SCEP_SETUP); + }; + + const appleMdmDiabledContent = ( +
+
+

Simple Certificate Enrollment Protocol (SCEP)

+

+ To enable Fleet to get SCEP certificates from your custom SCEP server + and install them on macOS hosts, first turn on Apple (macOS, iOS, + iPadOS) MDM. +

+

+ Fleet currently supports Microsoft's Network Device Enrollment + Service (NDES) as a custom SCEP server. +

+
+
+ ); + + const isScepOnContent = ( +
+

+ + + Volume Purchasing Program (VPP) is enabled. TODO THIS IS NOT ON FIGMA + YET? + +

+ +
+ ); + + const isScepOffContent = ( +
+
+

Simple Certificate Enrollment Protocol (SCEP)

+

+ Add a SCEP connection to enable Fleet to get SCEP certificates from + your custom SCEP server and install them on macOS hosts.{" "} +

+

+ Fleet currently supports Microsoft's Network Device Enrollment + Service (NDES) as a custom SCEP server. +

+
+ +
+ ); + + const renderCardContent = () => { + if (!isAppleMdmOn) { + return appleMdmDiabledContent; + } + + return isScepOn ? isScepOnContent : isScepOffContent; + }; + + return ( + + {renderCardContent()} + + ); +}; + +interface IScepSectionProps { + router: InjectedRouter; + isScepOn: boolean; + isPremiumTier: boolean; +} + +const ScepSection = ({ + router, + isScepOn, + isPremiumTier, +}: IScepSectionProps) => { + const { config } = useContext(AppContext); + + const renderContent = () => { + if (!isPremiumTier) { + return ; + } + + return ( + + ); + }; + + return ( + + <>{renderContent()} + + ); +}; + +export default ScepSection; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/_styles.scss new file mode 100644 index 000000000000..9cbe7e0e6372 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/_styles.scss @@ -0,0 +1,51 @@ +.vpp-section { + + // TODO: this is very similar to the Apple Mdm card, consider pulling out + // into a shared component if we do this pattern one more time. + &__card { + + h3 { + font-size: $x-small; + font-weight: $bold; + margin: 0 0 $pad-xsmall; + } + + p { + margin: 0; + + span { + display: flex; + align-items: center; + gap: $pad-small; + } + } + + &__turn-on-mdm { + flex-direction: column; + align-items: flex-start; + gap: $pad-medium; + + .button { + height: auto; + } + } + } + + &__mdm-disabled-content { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: $pad-medium; + } + + &__vpp-on-content, &__vpp-off-content { + display: flex; + justify-content: space-between; + align-items: center; + gap: $pad-medium; + } + + &__add-vpp-button { + white-space: nowrap; + } +} diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/index.ts b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/index.ts new file mode 100644 index 000000000000..e2b7b0e18d5f --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/index.ts @@ -0,0 +1 @@ +export { default } from "./CertificatesSection"; diff --git a/frontend/router/paths.ts b/frontend/router/paths.ts index 1077575c7996..6ad85ce49324 100644 --- a/frontend/router/paths.ts +++ b/frontend/router/paths.ts @@ -46,6 +46,7 @@ export default { ADMIN_INTEGRATIONS_CALENDARS: `${URL_PREFIX}/settings/integrations/calendars`, ADMIN_INTEGRATIONS_VPP: `${URL_PREFIX}/settings/integrations/mdm/vpp`, ADMIN_INTEGRATIONS_VPP_SETUP: `${URL_PREFIX}/settings/integrations/vpp/setup`, + ADMIN_INTEGRATIONS_SCEP_SETUP: `${URL_PREFIX}/settings/integrations/scep/setup`, ADMIN_TEAMS: `${URL_PREFIX}/settings/teams`, ADMIN_ORGANIZATION: `${URL_PREFIX}/settings/organization`, From 2cc9d44793f055e8af4ca521dc5bc5cae882a663 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 20 Sep 2024 10:07:22 -0700 Subject: [PATCH 02/23] Create SectionCard and remove redundant code/styling --- .../cards/MdmSettings/_styles.scss | 16 +-- .../AppleAutomaticEnrollmentCard.tsx | 76 ++++++------ .../WindowsAutomaticEnrollmentCard.tsx | 35 +++--- .../AutomaticEnrollmentSection/_styles.scss | 29 ----- .../CertificatesSection.tsx | 112 ++++++++---------- .../CertificatesSection/_styles.scss | 51 -------- .../AppleMdmCard/AppleMdmCard.tsx | 63 +++++----- .../AppleMdmCard/_styles.scss | 47 -------- .../WindowsMdmCard/WindowsMdmCard.tsx | 65 +++++----- .../WindowsMdmCard/_styles.scss | 43 ------- .../MdmSettingsSection/_styles.scss | 3 +- .../components/SectionCard/SectionCard.tsx | 37 ++++++ .../components/SectionCard/_styles.scss | 36 ++++++ .../components/SectionCard/index.ts | 1 + .../components/VppSection/VppSection.tsx | 90 ++++++-------- .../components/VppSection/_styles.scss | 51 -------- 16 files changed, 283 insertions(+), 472 deletions(-) delete mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/_styles.scss delete mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/_styles.scss delete mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/_styles.scss create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/SectionCard.tsx create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/_styles.scss create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/index.ts delete mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/_styles.scss diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/_styles.scss index a957eece068a..93d3b2c5385c 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/_styles.scss @@ -1,19 +1,5 @@ .mdm-settings { display: flex; flex-direction: column; - gap: 80px; - - &__section { - .mdm-settings-team-btn { - margin-left: 12px; - - .children-wrapper { - gap: $pad-small; - } - } - - .component__tooltip-wrapper__tip-text { - max-width: initial; - } - } + gap: $pad-xxxlarge; } diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/AppleAutomaticEnrollmentCard/AppleAutomaticEnrollmentCard.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/AppleAutomaticEnrollmentCard/AppleAutomaticEnrollmentCard.tsx index 69d2c65ad542..a5ab3acca57d 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/AppleAutomaticEnrollmentCard/AppleAutomaticEnrollmentCard.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/AppleAutomaticEnrollmentCard/AppleAutomaticEnrollmentCard.tsx @@ -1,10 +1,9 @@ import React from "react"; -import Card from "components/Card"; import Button from "components/buttons/Button"; import Icon from "components/Icon/Icon"; -const baseClass = "automatic-enrollment-card"; +import SectionCard from "../../SectionCard"; interface IAppleAutomaticEnrollmentCardProps { isAppleMdmOn: boolean; @@ -17,36 +16,31 @@ const AppleAutomaticEnrollmentCard = ({ viewDetails, configured, }: IAppleAutomaticEnrollmentCardProps) => { - let icon = ""; - let msg = - "To enable automatic enrollment for macOS, iOS, and iPadOS hosts, first turn on Apple MDM."; - if (isAppleMdmOn && !configured) { - msg = - "Add an Apple Business Manager (ABM) connection to automatically enroll newly " + - "purchased Apple hosts when they're first unboxed and set up by your end users."; - } else if (isAppleMdmOn && configured) { - msg = "Automatic enrollment for Apple (macOS, iOS, iPadOS) is enabled."; - icon = "success"; - } + const appleMdmDiabledCard = ( + + To enable automatic enrollment for macOS, iOS, and iPadOS hosts, first + turn on Apple MDM. + + ); + + const isAbmConfiguredCard = ( + + + Edit + + } + > + Automatic enrollment for Apple (macOS, iOS, iPadOS) is enabled. + + ); - return ( - -
- {!icon && ( -

Automatic enrollment for Apple (macOS, iOS, iPadOS) hosts.

- )} -

- {icon ? ( - - - {msg} - - ) : ( - msg - )} -

-
- {isAppleMdmOn && !configured && ( + const isAbmNotConfiguredCard = ( + Add ABM - )} - {isAppleMdmOn && configured && ( - - )} -
+ } + > + Add an Apple Business Manager (ABM) connection to automatically enroll + newly purchased Apple hosts when they're first unboxed and set up by + your end users. + ); + + if (!isAppleMdmOn) { + return appleMdmDiabledCard; + } + + return configured ? isAbmConfiguredCard : isAbmNotConfiguredCard; }; export default AppleAutomaticEnrollmentCard; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/WindowsAutomaticEnrollmentCard/WindowsAutomaticEnrollmentCard.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/WindowsAutomaticEnrollmentCard/WindowsAutomaticEnrollmentCard.tsx index 6c58f6fb51c7..94d8651eb4ce 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/WindowsAutomaticEnrollmentCard/WindowsAutomaticEnrollmentCard.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/WindowsAutomaticEnrollmentCard/WindowsAutomaticEnrollmentCard.tsx @@ -1,10 +1,8 @@ import React from "react"; -import Card from "components/Card"; import Button from "components/buttons/Button"; import Icon from "components/Icon/Icon"; - -const baseClass = "automatic-enrollment-card"; +import SectionCard from "../../SectionCard"; interface IWindowsAutomaticEnrollmentCardProps { viewDetails: () => void; @@ -14,22 +12,21 @@ const WindowsAutomaticEnrollmentCard = ({ viewDetails, }: IWindowsAutomaticEnrollmentCardProps) => { return ( - -
-

Windows automatic enrollment

-

- To use automatic enrollment for Windows hosts and Windows Autopilot - you need to connect Fleet to Azure AD first. -

-
- -
+ + Details + + } + > + To use automatic enrollment for Windows hosts and Windows Autopilot you + need to connect Fleet to Azure AD first. + ); }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/_styles.scss index d1a74a4e63db..9a2476246b0a 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/_styles.scss @@ -3,34 +3,5 @@ display: flex; flex-direction: column; gap: $pad-xxlarge; - - // styles for the apple and windows automatic enrollment cards. - .automatic-enrollment-card { - font-size: $x-small; - display: flex; - justify-content: space-between; - align-items: center; - gap: $pad-medium; - - h3 { - font-size: $x-small; - font-weight: $bold; - margin: 0 0 $pad-xsmall; - } - - p { - margin: 0; - - span { - display: flex; - align-items: center; - gap: $pad-small; - } - } - - .add-abm-button { - white-space: nowrap; - } - } } } diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx index 08c3134fe966..9329c80194e2 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx @@ -4,12 +4,12 @@ import { InjectedRouter } from "react-router"; import PATHS from "router/paths"; import { AppContext } from "context/app"; -import Card from "components/Card"; import Button from "components/buttons/Button"; import Icon from "components/Icon"; import SettingsSection from "pages/admin/components/SettingsSection"; import PremiumFeatureMessage from "components/PremiumFeatureMessage"; +import SectionCard from "../SectionCard"; const baseClass = "certificates-section"; @@ -24,75 +24,63 @@ const ScepCard = ({ isAppleMdmOn, isScepOn, router }: IScepCardProps) => { router.push(PATHS.ADMIN_INTEGRATIONS_SCEP_SETUP); }; - const appleMdmDiabledContent = ( -
-
-

Simple Certificate Enrollment Protocol (SCEP)

-

- To enable Fleet to get SCEP certificates from your custom SCEP server - and install them on macOS hosts, first turn on Apple (macOS, iOS, - iPadOS) MDM. -

-

- Fleet currently supports Microsoft's Network Device Enrollment - Service (NDES) as a custom SCEP server. -

-
-
- ); - - const isScepOnContent = ( -
+ const appleMdmDiabledCard = ( + +

+ To enable Fleet to get SCEP certificates from your custom SCEP server + and install them on macOS hosts, first turn on Apple (macOS, iOS, + iPadOS) MDM. +

- - - Volume Purchasing Program (VPP) is enabled. TODO THIS IS NOT ON FIGMA - YET? - + Fleet currently supports Microsoft's Network Device Enrollment + Service (NDES) as a custom SCEP server.

- -
+ ); - const isScepOffContent = ( -
-
-

Simple Certificate Enrollment Protocol (SCEP)

-

- Add a SCEP connection to enable Fleet to get SCEP certificates from - your custom SCEP server and install them on macOS hosts.{" "} -

-

- Fleet currently supports Microsoft's Network Device Enrollment - Service (NDES) as a custom SCEP server. -

-
- -
+ const isScepOnCard = ( + + + Edit + + } + > + TODO: Need Figma design for this + ); - const renderCardContent = () => { - if (!isAppleMdmOn) { - return appleMdmDiabledContent; - } + const isScepOffCard = ( + + Add SCEP + + } + > +

+ Add a SCEP connection to enable Fleet to get SCEP certificates from your + custom SCEP server and install them on macOS hosts.{" "} +

+

+ Fleet currently supports Microsoft's Network Device Enrollment + Service (NDES) as a custom SCEP server. +

+
+ ); - return isScepOn ? isScepOnContent : isScepOffContent; - }; + if (!isAppleMdmOn) { + return appleMdmDiabledCard; + } - return ( - - {renderCardContent()} - - ); + return !isScepOn ? isScepOnCard : isScepOffCard; }; interface IScepSectionProps { diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/_styles.scss deleted file mode 100644 index 9cbe7e0e6372..000000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/_styles.scss +++ /dev/null @@ -1,51 +0,0 @@ -.vpp-section { - - // TODO: this is very similar to the Apple Mdm card, consider pulling out - // into a shared component if we do this pattern one more time. - &__card { - - h3 { - font-size: $x-small; - font-weight: $bold; - margin: 0 0 $pad-xsmall; - } - - p { - margin: 0; - - span { - display: flex; - align-items: center; - gap: $pad-small; - } - } - - &__turn-on-mdm { - flex-direction: column; - align-items: flex-start; - gap: $pad-medium; - - .button { - height: auto; - } - } - } - - &__mdm-disabled-content { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: $pad-medium; - } - - &__vpp-on-content, &__vpp-off-content { - display: flex; - justify-content: space-between; - align-items: center; - gap: $pad-medium; - } - - &__add-vpp-button { - white-space: nowrap; - } -} diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/AppleMdmCard.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/AppleMdmCard.tsx index ebfebcfb445a..06fda86b7b30 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/AppleMdmCard.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/AppleMdmCard.tsx @@ -6,43 +6,48 @@ import Card from "components/Card"; import DataError from "components/DataError"; import { AxiosError } from "axios"; import { IMdmApple } from "interfaces/mdm"; +import SectionCard from "../../SectionCard"; const baseClass = "apple-mdm-card"; -interface ITurnOnAppleMdmProps { +interface ITurnOnAppleMdmCardProps { onClickTurnOn: () => void; } -const TurnOnAppleMdm = ({ onClickTurnOn }: ITurnOnAppleMdmProps) => { +const TurnOnAppleMdmCard = ({ onClickTurnOn }: ITurnOnAppleMdmCardProps) => { return ( -
-
-

Turn on Apple (macOS, iOS, iPadOS) MDM

-

Enforce settings, OS updates, disk encryption, and more.

-
- -
+ + Turn on + + } + > + Enforce settings, OS updates, disk encryption, and more. + ); }; -interface ITurnOffAppleMdmProps { +interface ITurnOffAppleMdmCardProps { onClickDetails: () => void; } -const SeeDetailsAppleMdm = ({ onClickDetails }: ITurnOffAppleMdmProps) => { +const SeeDetailsAppleMdmCard = ({ + onClickDetails, +}: ITurnOffAppleMdmCardProps) => { return ( -
-
- -

Apple (macOS, iOS, iPadOS) MDM turned on.

-
- -
+ + + Edit + + } + > + Apple (macOS, iOS, iPadOS) MDM turned on. + ); }; @@ -74,14 +79,10 @@ const AppleMdmCard = ({ return ; } - return ( - - {appleAPNSInfo !== undefined ? ( - - ) : ( - - )} - + return appleAPNSInfo !== undefined ? ( + + ) : ( + ); }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/_styles.scss deleted file mode 100644 index a335e512232c..000000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/_styles.scss +++ /dev/null @@ -1,47 +0,0 @@ -.apple-mdm-card { - font-size: $x-small; - - p { - margin: 0; - } - - &__turn-on-apple-mdm, - &__turn-off-mac-os { - display: flex; - justify-content: space-between; - align-items: center; - - p { - margin-right: $pad-small; - } - - button { - .children-wrapper { - text-wrap: nowrap; - } - } - } - - &__turn-on-apple-mdm { - h3 { - font-size: $x-small; - font-weight: $bold; - margin: 0 0 $pad-xsmall; - } - - p { - max-width: 520px; - } - } - - &__turn-off-mac-os { - > div { - display: flex; - align-items: center; - } - - p { - margin-left: $pad-small; - } - } -} diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/WindowsMdmCard.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/WindowsMdmCard.tsx index 27ae21d5caaf..5e401b87606f 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/WindowsMdmCard.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/WindowsMdmCard.tsx @@ -5,43 +5,50 @@ import { AppContext } from "context/app"; import Card from "components/Card/Card"; import Button from "components/buttons/Button"; import Icon from "components/Icon"; +import SectionCard from "../../SectionCard"; const baseClass = "windows-mdm-card"; -interface ITurnOnWindowsMdmProps { +interface ITurnOnWindowsMdmCardProps { onClickTurnOn: () => void; } -const TurnOnWindowsMdm = ({ onClickTurnOn }: ITurnOnWindowsMdmProps) => { +const TurnOnWindowsMdmCard = ({ + onClickTurnOn, +}: ITurnOnWindowsMdmCardProps) => { return ( -
-
-

Turn on Windows MDM

-

Turn MDM on for Windows hosts with fleetd.

-
- -
+ + Turn on + + } + > + Turn MDM on for Windows hosts with fleetd. + ); }; -interface ITurnOffWindowsMdmProps { +interface ITurnOffWindowsMdmCardProps { onClickEdit: () => void; } -const TurnOffWindowsMdm = ({ onClickEdit }: ITurnOffWindowsMdmProps) => { +const TurnOffWindowsMdmCard = ({ + onClickEdit, +}: ITurnOffWindowsMdmCardProps) => { return ( -
-
- -

Windows MDM turned on (servers excluded).

-
- -
+ + + Edit + + } + > + Windows MDM turned on (servers excluded). + ); }; @@ -59,14 +66,10 @@ const WindowsMdmCard = ({ const isWindowsMdmEnabled = config?.mdm?.windows_enabled_and_configured ?? false; - return ( - - {isWindowsMdmEnabled ? ( - - ) : ( - - )} - + return isWindowsMdmEnabled ? ( + + ) : ( + ); }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/_styles.scss deleted file mode 100644 index 79bc9b91b47e..000000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/_styles.scss +++ /dev/null @@ -1,43 +0,0 @@ -.windows-mdm-card { - font-size: $x-small; - - p { - margin: 0; - } - - &__turn-on-windows, - &__turn-off-windows { - display: flex; - justify-content: space-between; - align-items: center; - - p { - margin-right: $pad-small; - } - - button { - .children-wrapper { - text-wrap: nowrap; - } - } - } - - &__turn-on-windows { - h3 { - font-size: $x-small; - font-weight: $bold; - margin: 0 0 $pad-xsmall; - } - } - - &__turn-off-windows { - > div { - display: flex; - align-items: center; - } - - p { - margin-left: $pad-small; - } - } -} diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/_styles.scss index 60dd8e6136d7..4aa347ba9df9 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/_styles.scss @@ -1,8 +1,7 @@ .mdm-settings-section { - &__content { display: flex; flex-direction: column; - gap: 40px; + gap: $pad-xxlarge; } } diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/SectionCard.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/SectionCard.tsx new file mode 100644 index 000000000000..30a65790caf2 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/SectionCard.tsx @@ -0,0 +1,37 @@ +import React from "react"; + +import Card from "components/Card"; +import Icon from "components/Icon"; +import { IconNames } from "components/icons"; + +const baseClass = "section-card"; + +interface ISectionCardProps { + children: React.ReactNode; + header?: string; + iconName?: IconNames; + cta?: JSX.Element; + // className?: string; TODO: If we want custom classNames +} + +const SectionCard = ({ + children, + header, + iconName, + cta, +}: ISectionCardProps) => { + return ( + +
+ {iconName && } +
+ {header &&

{header}

} + {children} +
+
+ {cta &&
{cta}
} +
+ ); +}; + +export default SectionCard; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/_styles.scss new file mode 100644 index 000000000000..9951e8007ad9 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/_styles.scss @@ -0,0 +1,36 @@ +.section-card { + display: flex; + justify-content: space-between; + align-items: center; + gap: $pad-xlarge; + + &__content-wrapper { + display: flex; + align-items: flex-start; + gap: $pad-small; + margin: 0; + + span { + display: flex; + align-items: center; + gap: $pad-small; + } + } + + .button { + height: auto; + white-space: nowrap; + } + + &__content { + display: flex; + flex-direction: column; + font-size: $x-small; + + h3 { + font-size: $x-small; + font-weight: $bold; + margin: 0 0 $pad-xsmall; + } + } +} diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/index.ts b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/index.ts new file mode 100644 index 000000000000..5ab00b1ed35a --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/index.ts @@ -0,0 +1 @@ +export { default } from "./SectionCard"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/VppSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/VppSection.tsx index afcd3af3c213..084b17041cbf 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/VppSection.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/VppSection.tsx @@ -15,6 +15,7 @@ import Spinner from "components/Spinner"; import DataError from "components/DataError"; import SettingsSection from "pages/admin/components/SettingsSection"; import PremiumFeatureMessage from "components/PremiumFeatureMessage"; +import SectionCard from "../SectionCard"; const baseClass = "vpp-section"; @@ -29,65 +30,50 @@ const VppCard = ({ isAppleMdmOn, isVppOn, router }: IVppCardProps) => { router.push(PATHS.ADMIN_INTEGRATIONS_VPP_SETUP); }; - const appleMdmDiabledContent = ( -
-
-

Volume Purchasing Program (VPP)

-

- To enable Volume Purchasing Program (VPP), first turn on Apple (macOS, - iOS, iPadOS) MDM. -

-
-
+ const appleMdmDiabledCard = ( + + To enable Volume Purchasing Program (VPP), first turn on Apple (macOS, + iOS, iPadOS) MDM. + ); - const isVppOnContent = ( -
-

- - - Volume Purchasing Program (VPP) is enabled. - -

- -
+ const isVppOnCard = ( + + + Edit + + } + > + Volume Purchasing Program (VPP) is enabled. + ); - const isVppOffContent = ( -
-
-

Volume Purchasing Program (VPP)

-

- Add a VPP connection to install Apple App Store apps purchased through - Apple Business Manager. -

-
- -
+ const isVppOffCard = ( + + Add VPP + + } + > + Add a VPP connection to install Apple App Store apps purchased through + Apple Business Manager. + ); - const renderCardContent = () => { - if (!isAppleMdmOn) { - return appleMdmDiabledContent; - } + if (!isAppleMdmOn) { + return appleMdmDiabledCard; + } - return isVppOn ? isVppOnContent : isVppOffContent; - }; - - return ( - - {renderCardContent()} - - ); + return isVppOn ? isVppOnCard : isVppOffCard; }; interface IVppSectionProps { diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/_styles.scss deleted file mode 100644 index 9cbe7e0e6372..000000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/_styles.scss +++ /dev/null @@ -1,51 +0,0 @@ -.vpp-section { - - // TODO: this is very similar to the Apple Mdm card, consider pulling out - // into a shared component if we do this pattern one more time. - &__card { - - h3 { - font-size: $x-small; - font-weight: $bold; - margin: 0 0 $pad-xsmall; - } - - p { - margin: 0; - - span { - display: flex; - align-items: center; - gap: $pad-small; - } - } - - &__turn-on-mdm { - flex-direction: column; - align-items: flex-start; - gap: $pad-medium; - - .button { - height: auto; - } - } - } - - &__mdm-disabled-content { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: $pad-medium; - } - - &__vpp-on-content, &__vpp-off-content { - display: flex; - justify-content: space-between; - align-items: center; - gap: $pad-medium; - } - - &__add-vpp-button { - white-space: nowrap; - } -} From 650b5a54b4504501b9bb586c91b993710a5a7d5c Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 20 Sep 2024 12:32:02 -0700 Subject: [PATCH 03/23] Progress towareds scep page --- .../cards/MdmSettings/ScepPage/ScepPage.tsx | 301 ++++++++++++++++++ .../cards/MdmSettings/ScepPage/__styles.scss | 15 + .../cards/MdmSettings/ScepPage/index.ts | 1 + 3 files changed, 317 insertions(+) create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/__styles.scss create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/index.ts diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx new file mode 100644 index 000000000000..02051100f5e9 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -0,0 +1,301 @@ +import React, { useContext, useState, useCallback } from "react"; +import { InjectedRouter } from "react-router"; +import { isAxiosError } from "axios"; + +import PATHS from "router/paths"; +import configAPI from "services/entities/config"; +import { getErrorReason } from "interfaces/errors"; +import { NotificationContext } from "context/notification"; +import { AppContext } from "context/app"; + +import MainContent from "components/MainContent/MainContent"; +import Button from "components/buttons/Button"; +import BackLink from "components/BackLink/BackLink"; +import CustomLink from "components/CustomLink"; +import FileUploader from "components/FileUploader"; +// @ts-ignore +import InputField from "components/forms/fields/InputField"; + +const baseClass = "scep-page"; + +interface ISetCertificateOptions { + enable: boolean; + successMessage: string; + errorMessage: string; + router: InjectedRouter; + ndesUrl?: string; + ndesUsername?: string; + ndesPassword?: string; +} + +const useSetCertificate = ({ + enable, + successMessage, + errorMessage, + router, + ndesUrl, + ndesUsername, + ndesPassword, +}: ISetCertificateOptions) => { + const { setConfig } = useContext(AppContext); + const { renderFlash } = useContext(NotificationContext); + + const [isUploading, setIsUploading] = useState(false); + + const onSetupSuccess = useCallback(() => { + router.push(PATHS.ADMIN_INTEGRATIONS_MDM); + }, [router]); + + const onFileUpload = useCallback( + async (files: FileList | null) => { + if (!files?.length) { + renderFlash("error", "No file selected"); + return; + } + setIsUploading(true); + try { + // TODO: Replace with correct API call + // await mdmAppleApi.uploadApplePushCertificate(files[0]); + renderFlash("success", "MDM turned on successfully."); + onSetupSuccess(); + } catch (e) { + const msg = getErrorReason(e); + if ( + msg.toLowerCase().includes("invalid certificate") || + msg.toLowerCase().includes("required private key") + ) { + renderFlash("error", msg); + } else { + renderFlash("error", "Couldn’t connect. Please try again."); + } + setIsUploading(false); + } + }, + [renderFlash, onSetupSuccess] + ); + + const turnOnWindowsMdm = async () => { + try { + const updatedConfig = await configAPI.updateMDMConfig( + { + windows_enabled_and_configured: enable, + }, + true + ); + setConfig(updatedConfig); + renderFlash("success", successMessage); + } catch (e) { + let msg = errorMessage; + if (enable && isAxiosError(e) && e.response?.status === 422) { + msg = + getErrorReason(e, { + nameEquals: "mdm.windows_enabled_and_configured", + }) || msg; + } + renderFlash("error", msg); + } finally { + router.push(PATHS.ADMIN_INTEGRATIONS_MDM); + } + }; + + return turnOnWindowsMdm; +}; + +interface IWindowsMdmOnContentProps { + router: InjectedRouter; + onFileUpload: () => void; + isUploading: boolean; + formData: any; // TODO + onInputChange: ({ name, value }: IFormField) => void; +} + +const WindowsMdmOnContent = ({ + router, + onFileUpload, + isUploading, + formData, + onInputChange, +}: IWindowsMdmOnContentProps) => { + const turnOnWindowsMdm = useSetCertificate({ + enable: true, + successMessage: "Windows MDM turned on (servers excluded).", + errorMessage: "Unable to turn on Windows MDM. Please try again.", + router, + }); + + return ( + <> +

SCEP

+

+ Add a SCEP connection to enable Fleet to get SCEP certificates from your + custom SCEP server and install them on macOS hosts. +

+

+ Fleet currently supports Microsoft's Network Device Enrollment + Service (NDES) as a custom SCEP server. +

+
+
    +
  1. + 1. + Configure your NDES admin account using the form below: +
    + + + +
    +
  2. +
  3. + 2. + + Follow instructions to get your signing certificate from NDES{" "} + + +
  4. +
  5. + 3. + Upload your certificate (.pfx file) below. +
    + +
  6. +
+
+ + ); +}; + +interface IWindowsMdmOffContentProps { + router: InjectedRouter; +} + +const WindowsMdmOffContent = ({ router }: IWindowsMdmOffContentProps) => { + const turnOffWindowsMdm = useSetCertificate({ + enable: false, + successMessage: "Windows MDM turned off.", + errorMessage: "Unable to turn off Windows MDM. Please try again.", + router, + }); + + return ( + <> +

Turn off Windows MDM

+

+ MDM will no longer be turned on for Windows hosts that enroll to Fleet. +

+

Hosts with MDM already turned on MDM will not have MDM removed.

+ + + ); +}; + +interface IScepPageProps { + router: InjectedRouter; + onFileUpload: () => void; + isUploading: boolean; +} + +interface INdesFormData { + url: string; + username: string; + password: string; +} + +export interface IFormField { + name: string; + value: string; +} + +const ScepPage = ({ router, onFileUpload, isUploading }: IScepPageProps) => { + const { config } = useContext(AppContext); + + const ndesInfoReturnedFromApi = { + url: "", + username: "", + password: "", + }; + + const { + url: ndesUrl, + username: ndesUsername, + password: ndesPassword, + } = ndesInfoReturnedFromApi; + + const [formData, setFormData] = useState({ + url: ndesUrl || "", + username: ndesUsername || "", + password: ndesPassword || "", + }); + + const onInputChange = ({ name, value }: IFormField) => { + setFormData({ ...formData, [name]: value }); + }; + + const isScepCertificateUploaded = false; + // config?.mdm?.windows_enabled_and_configured ?? false; // TODO + + return ( + + <> + + {isScepCertificateUploaded ? ( + + ) : ( + + )} + + + ); +}; + +export default ScepPage; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/__styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/__styles.scss new file mode 100644 index 000000000000..38e0a9b4f85f --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/__styles.scss @@ -0,0 +1,15 @@ +.scep-page { + &__back-to-mdm { + margin-bottom: $pad-xlarge; + } + + h1 { + margin-bottom: $pad-xxlarge; + font-size: $x-large; + } + + p { + font-size: $x-small; + margin: 0 0 $pad-large; + } +} diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/index.ts b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/index.ts new file mode 100644 index 000000000000..362df28ec8b0 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/index.ts @@ -0,0 +1 @@ +export { default } from "./ScepPage"; From c7825bba93f2195abdaf75ee03dc04439626889f Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 20 Sep 2024 15:44:22 -0700 Subject: [PATCH 04/23] Add pfx file uploader, most of SCEP page done --- .../components/FileUploader/FileUploader.tsx | 1 + frontend/components/graphics/FilePfx.tsx | 57 +++++++++++++++ frontend/components/graphics/index.ts | 2 + .../MdmSettings/AppleMdmPage/_styles.scss | 2 +- .../cards/MdmSettings/ScepPage/ScepPage.tsx | 71 +++++++++++-------- .../cards/MdmSettings/ScepPage/__styles.scss | 49 +++++++++++++ .../CertificatesSection.tsx | 2 +- frontend/router/index.tsx | 2 + frontend/router/paths.ts | 2 +- 9 files changed, 154 insertions(+), 34 deletions(-) create mode 100644 frontend/components/graphics/FilePfx.tsx diff --git a/frontend/components/FileUploader/FileUploader.tsx b/frontend/components/FileUploader/FileUploader.tsx index 6ba3bfa42f02..1143043eb563 100644 --- a/frontend/components/FileUploader/FileUploader.tsx +++ b/frontend/components/FileUploader/FileUploader.tsx @@ -18,6 +18,7 @@ export type ISupportedGraphicNames = Extract< | "file-py" | "file-script" | "file-pdf" + | "file-pfx" | "file-pkg" | "file-p7m" | "file-pem" diff --git a/frontend/components/graphics/FilePfx.tsx b/frontend/components/graphics/FilePfx.tsx new file mode 100644 index 000000000000..375272e704c9 --- /dev/null +++ b/frontend/components/graphics/FilePfx.tsx @@ -0,0 +1,57 @@ +import React from "react"; + +const FilePfx = () => { + return ( + + + + + + + + + + + + + + + + + ); +}; + +export default FilePfx; diff --git a/frontend/components/graphics/index.ts b/frontend/components/graphics/index.ts index 38862d51da16..86502c6517f8 100644 --- a/frontend/components/graphics/index.ts +++ b/frontend/components/graphics/index.ts @@ -9,6 +9,7 @@ import FilePs1 from "./FilePs1"; import FilePy from "./FilePy"; import FileScript from "./FileScript"; import FilePdf from "./FilePdf"; +import FilePfx from "./FilePfx"; import FilePkg from "./FilePkg"; import FileP7m from "./FileP7m"; import FilePem from "./FilePem"; @@ -44,6 +45,7 @@ export const GRAPHIC_MAP = { "file-py": FilePy, "file-script": FileScript, "file-pdf": FilePdf, + "file-pfx": FilePfx, "file-pkg": FilePkg, "file-p7m": FileP7m, "file-pem": FilePem, diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/_styles.scss index 2538cc73ba9e..85a91f749679 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/_styles.scss @@ -35,7 +35,7 @@ &__setup-instructions-list { padding: 0; margin: 0; - list-style: none; + // list-style: none; display: flex; flex-direction: column; gap: $pad-large; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx index 02051100f5e9..7b59f2c9de91 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -101,21 +101,23 @@ const useSetCertificate = ({ return turnOnWindowsMdm; }; -interface IWindowsMdmOnContentProps { +interface IScepCertificateContentProps { router: InjectedRouter; onFileUpload: () => void; + onFormSubmit: () => void; isUploading: boolean; formData: any; // TODO onInputChange: ({ name, value }: IFormField) => void; } -const WindowsMdmOnContent = ({ +const ScepCertificateContent = ({ router, onFileUpload, + onFormSubmit, isUploading, formData, onInputChange, -}: IWindowsMdmOnContentProps) => { +}: IScepCertificateContentProps) => { const turnOnWindowsMdm = useSetCertificate({ enable: true, successMessage: "Windows MDM turned on (servers excluded).", @@ -129,17 +131,16 @@ const WindowsMdmOnContent = ({

Add a SCEP connection to enable Fleet to get SCEP certificates from your custom SCEP server and install them on macOS hosts. -

-

+
+
Fleet currently supports Microsoft's Network Device Enrollment Service (NDES) as a custom SCEP server.

-
    +
    1. - 1. - Configure your NDES admin account using the form below: -
      + Configure your NDES admin account using the form below: +
      -
      + +
    2. - 2. Follow instructions to get your signing certificate from NDES{" "}
    3. - 3. - Upload your certificate (.pfx file) below. -
      + Upload your certificate (.pfx file) below.
    4. @@ -209,22 +213,20 @@ interface IWindowsMdmOffContentProps { router: InjectedRouter; } -const WindowsMdmOffContent = ({ router }: IWindowsMdmOffContentProps) => { - const turnOffWindowsMdm = useSetCertificate({ +// TODO: Confirm as this is not in Figma +const UploadCertificateContent = ({ router }: IWindowsMdmOffContentProps) => { + const removeScepCertificate = useSetCertificate({ enable: false, - successMessage: "Windows MDM turned off.", - errorMessage: "Unable to turn off Windows MDM. Please try again.", + successMessage: "SCEP certificate was removed.", + errorMessage: "Unable to remove SCEP certificate. Please try again.", router, }); return ( <> -

      Turn off Windows MDM

      -

      - MDM will no longer be turned on for Windows hosts that enroll to Fleet. -

      -

      Hosts with MDM already turned on MDM will not have MDM removed.

      - +

      Remove SCEP certificate

      +

      TODO

      + ); }; @@ -232,6 +234,7 @@ const WindowsMdmOffContent = ({ router }: IWindowsMdmOffContentProps) => { interface IScepPageProps { router: InjectedRouter; onFileUpload: () => void; + onSaveNdes: () => void; isUploading: boolean; } @@ -246,7 +249,12 @@ export interface IFormField { value: string; } -const ScepPage = ({ router, onFileUpload, isUploading }: IScepPageProps) => { +const ScepPage = ({ + router, + onFileUpload, + onSaveNdes, + isUploading, +}: IScepPageProps) => { const { config } = useContext(AppContext); const ndesInfoReturnedFromApi = { @@ -283,11 +291,12 @@ const ScepPage = ({ router, onFileUpload, isUploading }: IScepPageProps) => { className={`${baseClass}__back-to-mdm`} /> {isScepCertificateUploaded ? ( - + ) : ( - span { + flex: 1 0 100%; + } + + p { + margin: 0; + } + + .url-inputs-wrapper { + flex: 1 0 100%; + gap: $pad-small; + } + } + } } diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx index 9329c80194e2..91d53442e7b5 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx @@ -21,7 +21,7 @@ interface IScepCardProps { const ScepCard = ({ isAppleMdmOn, isScepOn, router }: IScepCardProps) => { const navigateToScepSetup = () => { - router.push(PATHS.ADMIN_INTEGRATIONS_SCEP_SETUP); + router.push(PATHS.ADMIN_INTEGRATIONS_SCEP); }; const appleMdmDiabledCard = ( diff --git a/frontend/router/index.tsx b/frontend/router/index.tsx index 19b35229fc0b..eb35f520152f 100644 --- a/frontend/router/index.tsx +++ b/frontend/router/index.tsx @@ -63,6 +63,7 @@ import OSSettings from "pages/ManageControlsPage/OSSettings"; import SetupExperience from "pages/ManageControlsPage/SetupExperience/SetupExperience"; import WindowsMdmPage from "pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage"; import AppleMdmPage from "pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage"; +import ScepPage from "pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage"; import Scripts from "pages/ManageControlsPage/Scripts/Scripts"; import WindowsAutomaticEnrollmentPage from "pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage"; import AppleBusinessManagerPage from "pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage"; @@ -194,6 +195,7 @@ const routes = ( + {/* This redirect is used to handle old apple automatic enrollments page */} Date: Mon, 23 Sep 2024 09:33:37 -0700 Subject: [PATCH 05/23] Progress, WIP, designs not done so pause --- .../DeleteScepModal/DeleteScepModal.tsx | 40 ++++++ .../ScepPage/DeleteScepModal/index.ts | 1 + .../cards/MdmSettings/ScepPage/ScepPage.tsx | 114 ++++++++++++------ .../components/EulaSection/EulaSection.tsx | 4 - .../components/DeleteEulaModal/_styles.scss | 1 - frontend/services/entities/mdm.ts | 6 + 6 files changed, 123 insertions(+), 43 deletions(-) create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/DeleteScepModal.tsx create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/index.ts delete mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/EulaSection/components/DeleteEulaModal/_styles.scss diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/DeleteScepModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/DeleteScepModal.tsx new file mode 100644 index 000000000000..aae90bca0156 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/DeleteScepModal.tsx @@ -0,0 +1,40 @@ +import React from "react"; + +import Modal from "components/Modal"; +import Button from "components/buttons/Button"; + +const baseClass = "delete-scep-modal"; + +interface IDeleteScepModalProps { + onDelete: () => void; + onCancel: () => void; +} + +const DeleteScepModal = ({ onDelete, onCancel }: IDeleteScepModalProps) => { + return ( + onDelete()} + > + <> +

      + {/* End users won’t be required to agree to this SCEP on macOS hosts that + automatically enroll. */} + TODO +

      +
      + + +
      + +
      + ); +}; + +export default DeleteScepModal; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/index.ts b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/index.ts new file mode 100644 index 000000000000..96960f8a2309 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/index.ts @@ -0,0 +1 @@ +export { default } from "./DeleteScepModal"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx index 7b59f2c9de91..403b304fb46d 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -1,9 +1,12 @@ import React, { useContext, useState, useCallback } from "react"; +import { useQuery } from "react-query"; import { InjectedRouter } from "react-router"; -import { isAxiosError } from "axios"; +import { AxiosError, isAxiosError } from "axios"; +import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; import PATHS from "router/paths"; import configAPI from "services/entities/config"; +import mdmAPI, { IScepMetadataResponse } from "services/entities/mdm"; import { getErrorReason } from "interfaces/errors"; import { NotificationContext } from "context/notification"; import { AppContext } from "context/app"; @@ -37,42 +40,81 @@ const useSetCertificate = ({ ndesUsername, ndesPassword, }: ISetCertificateOptions) => { - const { setConfig } = useContext(AppContext); + const { isPremiumTier, config, setConfig } = useContext(AppContext); const { renderFlash } = useContext(NotificationContext); + const isMdmEnabled = !!config?.mdm.enabled_and_configured; + const [isUploading, setIsUploading] = useState(false); + const [certFile, setCertFile] = useState(null); + const [showDeleteCertModal, setShowDeleteCertModal] = useState(false); + + // get the scep metadata + const { + data: scepMetaData, + isLoading: isLoadingScep, + isError: isScepError, + error: ScepError, + refetch: refetchScepMetadata, + } = useQuery( + ["scep-metadata"], + () => mdmAPI.getEULAMetadata(), // TODO CHANGE + { + ...DEFAULT_USE_QUERY_OPTIONS, + retry: false, + enabled: isPremiumTier && isMdmEnabled, + } + ); + + const onDeleteCert = async () => { + if (!certFile) return; + + try { + // await mdmAPI.deleteEULA(eulaMetadata.token); + renderFlash("success", "Successfully deleted!"); + } catch { + renderFlash("error", "Couldn’t delete. Please try again."); + } finally { + setShowDeleteCertModal(false); + onDelete(); + } + }; const onSetupSuccess = useCallback(() => { router.push(PATHS.ADMIN_INTEGRATIONS_MDM); }, [router]); - const onFileUpload = useCallback( - async (files: FileList | null) => { - if (!files?.length) { - renderFlash("error", "No file selected"); - return; - } - setIsUploading(true); - try { - // TODO: Replace with correct API call - // await mdmAppleApi.uploadApplePushCertificate(files[0]); - renderFlash("success", "MDM turned on successfully."); - onSetupSuccess(); - } catch (e) { - const msg = getErrorReason(e); - if ( - msg.toLowerCase().includes("invalid certificate") || - msg.toLowerCase().includes("required private key") - ) { - renderFlash("error", msg); - } else { - renderFlash("error", "Couldn’t connect. Please try again."); - } - setIsUploading(false); + const onSelectFile = useCallback((files: FileList | null) => { + const file = files?.[0]; + if (file) { + setCertFile(file); + } + }, []); + + const onFileUpload = useCallback(async () => { + if (!certFile) { + renderFlash("error", "No file selected"); + return; + } + setIsUploading(true); + try { + // TODO: Replace with correct API call + // await mdmAppleApi.uploadApplePushCertificate(files[0]); + renderFlash("success", "Certificate added successfully."); // TODO: Verbiage wireframed by product/design + onSetupSuccess(); + } catch (e) { + const msg = getErrorReason(e); + if ( + msg.toLowerCase().includes("invalid certificate") || + msg.toLowerCase().includes("required private key") + ) { + renderFlash("error", msg); + } else { + renderFlash("error", "Couldn’t add certificate. Please try again."); } - }, - [renderFlash, onSetupSuccess] - ); + setIsUploading(false); + } + }, [renderFlash, onSetupSuccess]); const turnOnWindowsMdm = async () => { try { @@ -103,28 +145,23 @@ const useSetCertificate = ({ interface IScepCertificateContentProps { router: InjectedRouter; - onFileUpload: () => void; + onSelectFile: () => void; onFormSubmit: () => void; isUploading: boolean; formData: any; // TODO + certFile: any; // TODO onInputChange: ({ name, value }: IFormField) => void; } const ScepCertificateContent = ({ router, - onFileUpload, + onSelectFile, onFormSubmit, isUploading, formData, + certFile, onInputChange, }: IScepCertificateContentProps) => { - const turnOnWindowsMdm = useSetCertificate({ - enable: true, - successMessage: "Windows MDM turned on (servers excluded).", - errorMessage: "Unable to turn on Windows MDM. Please try again.", - router, - }); - return ( <>

      SCEP

      @@ -200,7 +237,8 @@ const ScepCertificateContent = ({ disabled={isUploading} graphicName="file-pfx" message="Signing certificate (.pfx)" - onFileUpload={onFileUpload} + onFileUpload={onSelectFile} + fileDetails={certFile ? { name: certFile.name } : undefined} />
    diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/EulaSection/EulaSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/EulaSection/EulaSection.tsx index bf6f632eeb84..bbc2b8cf7459 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/EulaSection/EulaSection.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/EulaSection/EulaSection.tsx @@ -1,12 +1,8 @@ import React, { useContext, useState } from "react"; -import { useQuery } from "react-query"; -import { AxiosResponse } from "axios"; -import { IApiError } from "interfaces/errors"; import mdmAPI, { IEulaMetadataResponse } from "services/entities/mdm"; import { NotificationContext } from "context/notification"; -import Spinner from "components/Spinner"; import SettingsSection from "pages/admin/components/SettingsSection"; import EulaUploader from "./components/EulaUploader/EulaUploader"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/EulaSection/components/DeleteEulaModal/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/EulaSection/components/DeleteEulaModal/_styles.scss deleted file mode 100644 index b773f1e8a415..000000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/EulaSection/components/DeleteEulaModal/_styles.scss +++ /dev/null @@ -1 +0,0 @@ -.delete-eula-modal {} diff --git a/frontend/services/entities/mdm.ts b/frontend/services/entities/mdm.ts index f8cae4461f83..bd44e70f6c05 100644 --- a/frontend/services/entities/mdm.ts +++ b/frontend/services/entities/mdm.ts @@ -18,6 +18,12 @@ export interface IEulaMetadataResponse { created_at: string; } +export interface IScepMetadataResponse { + name: string; + token: string; + created_at: string; +} + export type ProfileStatusSummaryResponse = Record; export interface IDiskEncryptionStatusAggregate { From 093789a3ba95479c190d33250e8dba45edbdd789 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Tue, 8 Oct 2024 14:15:29 -0400 Subject: [PATCH 06/23] Major progress towards new design, several TODOs --- frontend/__mocks__/appleMdm.ts | 20 +- frontend/interfaces/activity.ts | 3 + frontend/interfaces/integration.ts | 3 + .../ActivityItem/ActivityItem.tsx | 27 + .../cards/MdmSettings/MdmSettings.tsx | 2 +- .../cards/MdmSettings/ScepPage/ScepPage.tsx | 467 +++++++++--------- .../cards/MdmSettings/ScepPage/__styles.scss | 4 + .../CertificatesSection.tests.tsx | 0 .../components/CertificatesSection/index.ts | 1 - .../ScepSection/ScepSection.tests.tsx | 104 ++++ .../ScepSection.tsx} | 47 +- .../components/ScepSection/__styles.scss | 5 + .../components/ScepSection/index.ts | 1 + .../components/SectionCard/SectionCard.tsx | 8 +- frontend/services/entities/mdm_apple.ts | 10 + frontend/test/handlers/apple_mdm.ts | 17 +- website/config/routes.js | 1 + 17 files changed, 458 insertions(+), 262 deletions(-) delete mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tests.tsx delete mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/index.ts create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tests.tsx rename frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/{CertificatesSection/CertificatesSection.tsx => ScepSection/ScepSection.tsx} (65%) create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/__styles.scss create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/index.ts diff --git a/frontend/__mocks__/appleMdm.ts b/frontend/__mocks__/appleMdm.ts index c58a96f891ac..b3351f580007 100644 --- a/frontend/__mocks__/appleMdm.ts +++ b/frontend/__mocks__/appleMdm.ts @@ -1,5 +1,10 @@ import { IMdmApple } from "interfaces/mdm"; -import { IGetVppInfoResponse, IVppApp } from "services/entities/mdm_apple"; +import { + IGetVppInfoResponse, + IVppApp, + IGetScepInfoResponse, + IScepInfo, +} from "services/entities/mdm_apple"; const DEFAULT_MDM_APPLE_MOCK: IMdmApple = { common_name: "APSP:12345", @@ -40,4 +45,17 @@ export const createMockVppApp = (overrides?: Partial): IVppApp => { return { ...DEFAULT_MDM_APPLE_VPP_APP_MOCK, ...overrides }; }; +const DEFAULT_MDM_APPLE_SCEP_INFO_MOCK: IScepInfo = { + url: "https://example.com/scep", + admin_url: "https://example.com/mscep_admin/", + username: "Administrator@example.com", + password: "insecure", +}; + +export const createMockScepInfo = ( + overrides?: Partial +): IScepInfo => { + return { ...DEFAULT_MDM_APPLE_SCEP_INFO_MOCK, ...overrides }; +}; + export default createMockMdmApple; diff --git a/frontend/interfaces/activity.ts b/frontend/interfaces/activity.ts index d8eacf643e7e..f7750fbb4444 100644 --- a/frontend/interfaces/activity.ts +++ b/frontend/interfaces/activity.ts @@ -45,6 +45,9 @@ export enum ActivityType { DeletedAppleOSProfile = "deleted_macos_profile", /** Note: BE not renamed (yet) from macOS even though activity is also used for iOS and iPadOS */ EditedAppleOSProfile = "edited_macos_profile", + CreatedNdesScepProxy = "created_ndes_scep_proxy", + DeletedNdesScepProxy = "deleted_ndes_scep_proxy", + EditedNdesScepProxy = "edited_ndes_scep_proxy", CreatedWindowsProfile = "created_windows_profile", DeletedWindowsProfile = "deleted_windows_profile", EditedWindowsProfile = "edited_windows_profile", diff --git a/frontend/interfaces/integration.ts b/frontend/interfaces/integration.ts index f6302a67b861..2a760e114333 100644 --- a/frontend/interfaces/integration.ts +++ b/frontend/interfaces/integration.ts @@ -1,3 +1,5 @@ +import { IScepInfo } from "services/entities/mdm_apple"; + export type IIntegrationType = "jira" | "zendesk"; export interface IJiraIntegration { url: string; @@ -84,6 +86,7 @@ export interface IZendeskJiraIntegrations { // Partial`, but that leads to a mess of types to resolve. export interface IGlobalIntegrations extends IZendeskJiraIntegrations { google_calendar?: IGlobalCalendarIntegration[] | null; + ndes_scep_proxy?: IScepInfo | null; } export interface ITeamIntegrations extends IZendeskJiraIntegrations { diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx index b6a59a417286..3416a92993f4 100644 --- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx +++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx @@ -414,6 +414,33 @@ const TAGGED_TEMPLATES = { ); }, + addedNdesScepProxy: (activity: IActivity) => { + return ( + <> + {" "} + added Microsoft's Network Device Enrollment Service (NDES) as your + SCEP server. + + ); + }, + deletedNdesScepProxy: () => { + return ( + <> + {" "} + added Microsoft's Network Device Enrollment Service (NDES) as your + SCEP server. + + ); + }, + editedNdesScepProxy: () => { + return ( + <> + {" "} + edited configurations for Microsoft's Network Device Enrollment + Service (NDES) as your SCEP server. + + ); + }, createdWindowsProfile: (activity: IActivity, isPremiumTier: boolean) => { const profileName = activity.details?.profile_name; return ( diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx index bf9f45288f5a..939f0a2a6b6f 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx @@ -17,7 +17,7 @@ import VppSection from "./components/VppSection"; import IdpSection from "./components/IdpSection"; import EulaSection from "./components/EulaSection"; import EndUserMigrationSection from "./components/EndUserMigrationSection"; -import ScepSection from "./components/CertificatesSection/CertificatesSection"; +import ScepSection from "./components/ScepSection/ScepSection"; const baseClass = "mdm-settings"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx index 403b304fb46d..a6ec0a2d4afd 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -1,12 +1,11 @@ -import React, { useContext, useState, useCallback } from "react"; +import React, { useContext, useState } from "react"; import { useQuery } from "react-query"; import { InjectedRouter } from "react-router"; -import { AxiosError, isAxiosError } from "axios"; -import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; import PATHS from "router/paths"; import configAPI from "services/entities/config"; -import mdmAPI, { IScepMetadataResponse } from "services/entities/mdm"; +import { IScepInfo } from "services/entities/mdm_apple"; +import { IConfig } from "interfaces/config"; import { getErrorReason } from "interfaces/errors"; import { NotificationContext } from "context/notification"; import { AppContext } from "context/app"; @@ -15,269 +14,190 @@ import MainContent from "components/MainContent/MainContent"; import Button from "components/buttons/Button"; import BackLink from "components/BackLink/BackLink"; import CustomLink from "components/CustomLink"; -import FileUploader from "components/FileUploader"; +import Card from "components/Card"; // @ts-ignore import InputField from "components/forms/fields/InputField"; +import TooltipWrapper from "components/TooltipWrapper"; +import PremiumFeatureMessage from "components/PremiumFeatureMessage"; +import Spinner from "components/Spinner"; +import DataError from "components/DataError"; + +import { SCEP_SERVER_TIP_CONTENT } from "../components/ScepSection/ScepSection"; const baseClass = "scep-page"; -interface ISetCertificateOptions { - enable: boolean; - successMessage: string; - errorMessage: string; +const BAD_SCEP_URL_ERROR = "Invalid SCEP URL. Please correct and try again."; +const BAD_CREDENTIALS_ERROR = + "Invalid admin URL or credentials. Please correct and try again."; +const CACHE_ERROR = + "The NDES password cache is full. Please increase the number of cached passwords in NDES and try again."; +const DEFAULT_ERROR = + "Something went wrong updating your SCEP server. Please try again."; + +interface ITurnOnMdmMessageProps { router: InjectedRouter; - ndesUrl?: string; - ndesUsername?: string; - ndesPassword?: string; } -const useSetCertificate = ({ - enable, - successMessage, - errorMessage, - router, - ndesUrl, - ndesUsername, - ndesPassword, -}: ISetCertificateOptions) => { - const { isPremiumTier, config, setConfig } = useContext(AppContext); - const { renderFlash } = useContext(NotificationContext); - - const isMdmEnabled = !!config?.mdm.enabled_and_configured; - - const [isUploading, setIsUploading] = useState(false); - const [certFile, setCertFile] = useState(null); - const [showDeleteCertModal, setShowDeleteCertModal] = useState(false); - - // get the scep metadata - const { - data: scepMetaData, - isLoading: isLoadingScep, - isError: isScepError, - error: ScepError, - refetch: refetchScepMetadata, - } = useQuery( - ["scep-metadata"], - () => mdmAPI.getEULAMetadata(), // TODO CHANGE - { - ...DEFAULT_USE_QUERY_OPTIONS, - retry: false, - enabled: isPremiumTier && isMdmEnabled, - } +const TurnOnMdmMessage = ({ router }: ITurnOnMdmMessageProps) => { + return ( +
    +

    Turn on Apple MDM

    + {/* TODO: Confirm wording missed Figma spec */} +

    To help your end users connect to Wi-Fi, first turn on Apple MDM.

    + +
    ); - - const onDeleteCert = async () => { - if (!certFile) return; - - try { - // await mdmAPI.deleteEULA(eulaMetadata.token); - renderFlash("success", "Successfully deleted!"); - } catch { - renderFlash("error", "Couldn’t delete. Please try again."); - } finally { - setShowDeleteCertModal(false); - onDelete(); - } - }; - - const onSetupSuccess = useCallback(() => { - router.push(PATHS.ADMIN_INTEGRATIONS_MDM); - }, [router]); - - const onSelectFile = useCallback((files: FileList | null) => { - const file = files?.[0]; - if (file) { - setCertFile(file); - } - }, []); - - const onFileUpload = useCallback(async () => { - if (!certFile) { - renderFlash("error", "No file selected"); - return; - } - setIsUploading(true); - try { - // TODO: Replace with correct API call - // await mdmAppleApi.uploadApplePushCertificate(files[0]); - renderFlash("success", "Certificate added successfully."); // TODO: Verbiage wireframed by product/design - onSetupSuccess(); - } catch (e) { - const msg = getErrorReason(e); - if ( - msg.toLowerCase().includes("invalid certificate") || - msg.toLowerCase().includes("required private key") - ) { - renderFlash("error", msg); - } else { - renderFlash("error", "Couldn’t add certificate. Please try again."); - } - setIsUploading(false); - } - }, [renderFlash, onSetupSuccess]); - - const turnOnWindowsMdm = async () => { - try { - const updatedConfig = await configAPI.updateMDMConfig( - { - windows_enabled_and_configured: enable, - }, - true - ); - setConfig(updatedConfig); - renderFlash("success", successMessage); - } catch (e) { - let msg = errorMessage; - if (enable && isAxiosError(e) && e.response?.status === 422) { - msg = - getErrorReason(e, { - nameEquals: "mdm.windows_enabled_and_configured", - }) || msg; - } - renderFlash("error", msg); - } finally { - router.push(PATHS.ADMIN_INTEGRATIONS_MDM); - } - }; - - return turnOnWindowsMdm; }; interface IScepCertificateContentProps { router: InjectedRouter; - onSelectFile: () => void; - onFormSubmit: () => void; - isUploading: boolean; + onFormSubmit: (evt: React.MouseEvent) => Promise; formData: any; // TODO - certFile: any; // TODO onInputChange: ({ name, value }: IFormField) => void; + config: IConfig | null; + isPremiumTier: boolean; + isLoading: boolean; + isSaving: boolean; + showDataError: boolean; } const ScepCertificateContent = ({ router, - onSelectFile, onFormSubmit, - isUploading, formData, - certFile, onInputChange, + config, + isPremiumTier, + isLoading, + isSaving, + showDataError, }: IScepCertificateContentProps) => { + if (!isPremiumTier) { + return ; + } + + if (!config?.mdm.enabled_and_configured) { + return ; + } + + if (isLoading) { + return ; + } + + // TODO: error UI + if (showDataError) { + return ( +
    + +
    + ); + } + return ( <> -

    SCEP

    - Add a SCEP connection to enable Fleet to get SCEP certificates from your - custom SCEP server and install them on macOS hosts. -
    -
    - Fleet currently supports Microsoft's Network Device Enrollment - Service (NDES) as a custom SCEP server. + To help your end users connect to Wi-Fi you can add your{" "} + + SCEP server + + .

    1. - Configure your NDES admin account using the form below: -
      - - - + Connect to your Network Device Enrollment Service ( + - - + ) admin account: + + +
      + + + + + + +
    2. - Follow instructions to get your signing certificate from NDES{" "} + Now head over to{" "} + {" "} + to configure how SCEP certificates are delivered to your hosts.{" "}
    3. -
    4. - Upload your certificate (.pfx file) below. - -
    ); }; -interface IWindowsMdmOffContentProps { - router: InjectedRouter; -} - -// TODO: Confirm as this is not in Figma -const UploadCertificateContent = ({ router }: IWindowsMdmOffContentProps) => { - const removeScepCertificate = useSetCertificate({ - enable: false, - successMessage: "SCEP certificate was removed.", - errorMessage: "Unable to remove SCEP certificate. Please try again.", - router, - }); - - return ( - <> -

    Remove SCEP certificate

    -

    TODO

    - - - ); -}; - interface IScepPageProps { router: InjectedRouter; - onFileUpload: () => void; - onSaveNdes: () => void; - isUploading: boolean; } interface INdesFormData { url: string; + adminUrl: string; username: string; password: string; } @@ -287,38 +207,104 @@ export interface IFormField { value: string; } -const ScepPage = ({ - router, - onFileUpload, - onSaveNdes, - isUploading, -}: IScepPageProps) => { - const { config } = useContext(AppContext); +const ScepPage = ({ router }: IScepPageProps) => { + const { config, isPremiumTier, setConfig } = useContext(AppContext); - const ndesInfoReturnedFromApi = { + const { renderFlash } = useContext(NotificationContext); + + const isMdmEnabled = !!config?.mdm.enabled_and_configured; + + const [isUpdatingNdesScepProxy, setIsUpdatingNdesScepProxy] = useState(false); + + const ndesInfoReturnedFromApi: IScepInfo = { url: "", + admin_url: "", username: "", password: "", }; const { - url: ndesUrl, - username: ndesUsername, - password: ndesPassword, + url, + admin_url: adminUrl, + username, + password, } = ndesInfoReturnedFromApi; const [formData, setFormData] = useState({ - url: ndesUrl || "", - username: ndesUsername || "", - password: ndesPassword || "", + url: "", + adminUrl: "", + username: "", + password: "", + }); + + const { + isLoading: isLoadingAppConfig, + refetch: refetchConfig, + isError: isErrorAppConfig, + error: errorAppConfig, + } = useQuery(["config"], () => configAPI.loadAll(), { + select: (data: IConfig) => data, + onSuccess: (data) => { + if (data.integrations.ndes_scep_proxy) { + setFormData({ + url: data.integrations.ndes_scep_proxy.url || "", + adminUrl: data.integrations.ndes_scep_proxy.admin_url || "", + username: data.integrations.ndes_scep_proxy.username || "", + password: data.integrations.ndes_scep_proxy.password || "", + }); + } + }, }); const onInputChange = ({ name, value }: IFormField) => { setFormData({ ...formData, [name]: value }); }; - const isScepCertificateUploaded = false; - // config?.mdm?.windows_enabled_and_configured ?? false; // TODO + const onFormSubmit = async (evt: React.MouseEvent) => { + setIsUpdatingNdesScepProxy(true); + + evt.preventDefault(); + + const isRemovingNdesScepProxy = + formData.url === "" && + formData.adminUrl === "" && + username === "" && + password === ""; + + // Format for API + const formDataToSubmit = isRemovingNdesScepProxy + ? null // Send null if no fields are set + : [ + { + url: formData.url, + admin_url: formData.adminUrl, + username: formData.username, + password: formData.password || null, + }, + ]; + + // Update integrations.ndes_scep_proxy only + const destination = { + ndes_scep_proxy: formDataToSubmit, + }; + + try { + await configAPI.update({ integrations: destination }); + renderFlash("success", "Successfully added your SCEP server."); + refetchConfig(); + } catch (error) { + console.error(error); + if (getErrorReason(error).includes("TODO")) { + renderFlash("error", BAD_SCEP_URL_ERROR); + } else if (getErrorReason(error).includes("TODO")) { + renderFlash("error", BAD_CREDENTIALS_ERROR); + } else if (getErrorReason(error).includes("TODO")) { + renderFlash("error", CACHE_ERROR); + } else renderFlash("error", DEFAULT_ERROR); + } finally { + setIsUpdatingNdesScepProxy(false); + } + }; return ( @@ -328,18 +314,23 @@ const ScepPage = ({ path={PATHS.ADMIN_INTEGRATIONS_MDM} className={`${baseClass}__back-to-mdm`} /> - {isScepCertificateUploaded ? ( - - ) : ( +
    +
    +

    Simple Certificate Enrollment Protocol (SCEP)

    +
    + - )} +
    ); diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/__styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/__styles.scss index 4a757aa35e4e..c109c7d67579 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/__styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/__styles.scss @@ -26,6 +26,10 @@ form { gap: $pad-small; + + button { + align-self: flex-end; + } } li { diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tests.tsx deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/index.ts b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/index.ts deleted file mode 100644 index e2b7b0e18d5f..000000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./CertificatesSection"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tests.tsx new file mode 100644 index 000000000000..3658b76a2451 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tests.tsx @@ -0,0 +1,104 @@ +import React from "react"; +import { screen } from "@testing-library/react"; + +import { createCustomRenderer, createMockRouter } from "test/test-utils"; +import mockServer from "test/mock-server"; +import { + defaultVppInfoHandler, + errorNoVppInfoHandler, +} from "test/handlers/apple_mdm"; +import createMockConfig, { createMockMdmConfig } from "__mocks__/configMock"; + +import ScepSection from "./ScepSection"; + +describe("Scep Section", () => { + it("renders mdm is off message when apple mdm is not turned on ", async () => { + mockServer.use(defaultVppInfoHandler); + + const render = createCustomRenderer({ + context: { + app: { + config: createMockConfig({ + mdm: createMockMdmConfig({ enabled_and_configured: false }), + }), + }, + }, + withBackendMock: true, + }); + + render( + + ); + + expect( + await screen.findByText(/first turn on Apple \(macOS, iOS, iPadOS\) MDM/i) + ).toBeInTheDocument(); + }); + + it("renders add scep when scep is disabled", async () => { + mockServer.use(errorNoVppInfoHandler); + + const render = createCustomRenderer({ + context: { + app: { + config: createMockConfig({ + mdm: createMockMdmConfig({ enabled_and_configured: true }), + }), + }, + }, + withBackendMock: true, + }); + + render( + + ); + + expect( + await screen.findByRole("button", { name: "Add SCEP" }) + ).toBeInTheDocument(); + }); + + it("renders edit scep when scep is enabled", async () => { + mockServer.use(defaultVppInfoHandler); + + const render = createCustomRenderer({ + context: { + app: { + config: createMockConfig({ + mdm: createMockMdmConfig({ enabled_and_configured: true }), + }), + }, + }, + withBackendMock: true, + }); + render(); + expect( + await screen.findByRole("button", { name: "Edit" }) + ).toBeInTheDocument(); + }); + + it("render the premium message when not in premium tier", async () => { + mockServer.use(defaultVppInfoHandler); + + const render = createCustomRenderer({ + context: { + app: { + config: createMockConfig({ + mdm: createMockMdmConfig({ enabled_and_configured: true }), + }), + }, + }, + withBackendMock: true, + }); + render( + + ); + expect( + await screen.findByText("This feature is included in Fleet Premium.") + ).toBeInTheDocument(); + }); +}); diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx similarity index 65% rename from frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx rename to frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx index 91d53442e7b5..4e8898e8a260 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/CertificatesSection/CertificatesSection.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx @@ -9,9 +9,11 @@ import Icon from "components/Icon"; import SettingsSection from "pages/admin/components/SettingsSection"; import PremiumFeatureMessage from "components/PremiumFeatureMessage"; +import TooltipWrapper from "components/TooltipWrapper"; + import SectionCard from "../SectionCard"; -const baseClass = "certificates-section"; +const baseClass = "scep-section"; interface IScepCardProps { isAppleMdmOn: boolean; @@ -19,27 +21,32 @@ interface IScepCardProps { router: InjectedRouter; } +export const SCEP_SERVER_TIP_CONTENT = + "Fleet currently supports Microsoft's Network Device Enrollment Service (NDES) as a SCEP server."; + const ScepCard = ({ isAppleMdmOn, isScepOn, router }: IScepCardProps) => { const navigateToScepSetup = () => { router.push(PATHS.ADMIN_INTEGRATIONS_SCEP); }; const appleMdmDiabledCard = ( - -

    - To enable Fleet to get SCEP certificates from your custom SCEP server - and install them on macOS hosts, first turn on Apple (macOS, iOS, - iPadOS) MDM. -

    +

    - Fleet currently supports Microsoft's Network Device Enrollment - Service (NDES) as a custom SCEP server. + To help your end users connect to Wi-Fi by adding your{" "} + + SCEP server + + , first turn on Apple (macOS, iOS, iPadOS) MDM.

    ); const isScepOnCard = ( @@ -48,12 +55,14 @@ const ScepCard = ({ isAppleMdmOn, isScepOn, router }: IScepCardProps) => { } > - TODO: Need Figma design for this + Microsoft's Network Device Enrollment Service (NDES) added as your + SCEP server. Your end users can connect to Wi-Fi. ); const isScepOffCard = ( { } >

    - Add a SCEP connection to enable Fleet to get SCEP certificates from your - custom SCEP server and install them on macOS hosts.{" "} -

    -

    - Fleet currently supports Microsoft's Network Device Enrollment - Service (NDES) as a custom SCEP server. + To help your end users connect to Wi-Fi, you can add your{" "} + + SCEP server + + .

    ); @@ -80,7 +88,7 @@ const ScepCard = ({ isAppleMdmOn, isScepOn, router }: IScepCardProps) => { return appleMdmDiabledCard; } - return !isScepOn ? isScepOnCard : isScepOffCard; + return isScepOn ? isScepOnCard : isScepOffCard; }; interface IScepSectionProps { @@ -111,7 +119,10 @@ const ScepSection = ({ }; return ( - + <>{renderContent()} ); diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/__styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/__styles.scss new file mode 100644 index 000000000000..f68621be463f --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/__styles.scss @@ -0,0 +1,5 @@ +.scep-section { + .section-card__content-wrapper span { + display: initial; + } +} diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/index.ts b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/index.ts new file mode 100644 index 000000000000..82ede66c077c --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/index.ts @@ -0,0 +1 @@ +export { default } from "./ScepSection"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/SectionCard.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/SectionCard.tsx index 30a65790caf2..e23ced2aa465 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/SectionCard.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/SectionCard/SectionCard.tsx @@ -3,6 +3,7 @@ import React from "react"; import Card from "components/Card"; import Icon from "components/Icon"; import { IconNames } from "components/icons"; +import classnames from "classnames"; const baseClass = "section-card"; @@ -11,7 +12,7 @@ interface ISectionCardProps { header?: string; iconName?: IconNames; cta?: JSX.Element; - // className?: string; TODO: If we want custom classNames + className?: string; } const SectionCard = ({ @@ -19,9 +20,12 @@ const SectionCard = ({ header, iconName, cta, + className, }: ISectionCardProps) => { + const cardClasses = classnames(baseClass, className); + return ( - +
    {iconName && }
    diff --git a/frontend/services/entities/mdm_apple.ts b/frontend/services/entities/mdm_apple.ts index 4a50779df44d..5baa7972bb8e 100644 --- a/frontend/services/entities/mdm_apple.ts +++ b/frontend/services/entities/mdm_apple.ts @@ -30,6 +30,16 @@ export interface IGetVppAppsResponse { app_store_apps: IVppApp[]; } +export interface IScepInfo { + url: string; + admin_url: string; + username: string; + password: string | null; +} +export interface IGetScepInfoResponse { + ndes_scep_proxy: IScepInfo; +} + export interface IGetVppTokensResponse { vpp_tokens: IMdmVppToken[]; } diff --git a/frontend/test/handlers/apple_mdm.ts b/frontend/test/handlers/apple_mdm.ts index 5f8be1c3764d..732db4b0b6b6 100644 --- a/frontend/test/handlers/apple_mdm.ts +++ b/frontend/test/handlers/apple_mdm.ts @@ -1,6 +1,6 @@ import { rest } from "msw"; -import { createMockVppInfo } from "__mocks__/appleMdm"; +import { createMockVppInfo, createMockScepInfo } from "__mocks__/appleMdm"; import { baseUrl } from "test/test-utils"; // eslint-disable-next-line import/prefer-default-export @@ -17,3 +17,18 @@ export const errorNoVppInfoHandler = rest.get( return res(context.status(404)); } ); + +// eslint-disable-next-line import/prefer-default-export +export const defaultScepInfoHandler = rest.get( + baseUrl("/scep"), + (req, res, context) => { + return res(context.json(createMockScepInfo())); + } +); + +export const errorNoScepInfoHandler = rest.get( + baseUrl("/scep"), + (req, res, context) => { + return res(context.status(404)); + } +); diff --git a/website/config/routes.js b/website/config/routes.js index 92618498739c..672fb50202ef 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -543,6 +543,7 @@ module.exports.routes = { 'GET /learn-more-about/os-updates': '/docs/using-fleet/mdm-os-updates', 'GET /sign-in-to/microsoft-automatic-enrollment-tool': 'https://portal.azure.com', 'GET /learn-more-about/custom-os-settings': '/docs/using-fleet/mdm-custom-os-settings', + 'GET /learn-more-about/ndes': 'https://learn.microsoft.com/en-us/windows-server/identity/ad-cs/network-device-enrollment-service-overview', // TODO: Confirm URL 'GET /learn-more-about/enrolling-hosts': '/docs/using-fleet/adding-hosts', 'GET /learn-more-about/setup-assistant': '/docs/using-fleet/mdm-macos-setup-experience#macos-setup-assistant', 'GET /learn-more-about/policy-automations': '/docs/using-fleet/automations', From 08f4f75a6fa69a88f06630bb0b1551138c13de3d Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Wed, 9 Oct 2024 11:33:35 -0400 Subject: [PATCH 07/23] Remove unused PFX work, delete cert modal, refactor turnonmdmmessage component for reuse --- .../components/FileUploader/FileUploader.tsx | 1 - .../TurnOnMdmMessage/TurnOnMdmMessage.tsx | 10 +++- .../components/TurnOnMdmMessage/index.ts | 0 frontend/components/graphics/FilePfx.tsx | 57 ------------------- frontend/components/graphics/index.ts | 2 - .../ActivityItem/ActivityItem.tsx | 4 +- .../OSSettings/OSSettings.tsx | 2 +- .../OSUpdates/OSUpdates.tsx | 2 +- .../SetupExperience/SetupExperience.tsx | 2 +- .../AppleBusinessManagerPage.tsx | 34 +++-------- .../AppleBusinessManagerPage/_styles.scss | 3 +- .../MdmSettings/AppleMdmPage/_styles.scss | 2 +- .../DeleteScepModal/DeleteScepModal.tsx | 40 ------------- .../ScepPage/DeleteScepModal/index.ts | 1 - .../cards/MdmSettings/ScepPage/ScepPage.tsx | 33 ++++------- .../cards/MdmSettings/VppPage/VppPage.tsx | 34 +++-------- .../cards/MdmSettings/VppPage/_styles.scss | 6 +- 17 files changed, 44 insertions(+), 189 deletions(-) rename frontend/{pages/ManageControlsPage => }/components/TurnOnMdmMessage/TurnOnMdmMessage.tsx (71%) rename frontend/{pages/ManageControlsPage => }/components/TurnOnMdmMessage/index.ts (100%) delete mode 100644 frontend/components/graphics/FilePfx.tsx delete mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/DeleteScepModal.tsx delete mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/index.ts diff --git a/frontend/components/FileUploader/FileUploader.tsx b/frontend/components/FileUploader/FileUploader.tsx index 1143043eb563..6ba3bfa42f02 100644 --- a/frontend/components/FileUploader/FileUploader.tsx +++ b/frontend/components/FileUploader/FileUploader.tsx @@ -18,7 +18,6 @@ export type ISupportedGraphicNames = Extract< | "file-py" | "file-script" | "file-pdf" - | "file-pfx" | "file-pkg" | "file-p7m" | "file-pem" diff --git a/frontend/pages/ManageControlsPage/components/TurnOnMdmMessage/TurnOnMdmMessage.tsx b/frontend/components/TurnOnMdmMessage/TurnOnMdmMessage.tsx similarity index 71% rename from frontend/pages/ManageControlsPage/components/TurnOnMdmMessage/TurnOnMdmMessage.tsx rename to frontend/components/TurnOnMdmMessage/TurnOnMdmMessage.tsx index 41d6de6debee..3264e3269986 100644 --- a/frontend/pages/ManageControlsPage/components/TurnOnMdmMessage/TurnOnMdmMessage.tsx +++ b/frontend/components/TurnOnMdmMessage/TurnOnMdmMessage.tsx @@ -10,9 +10,13 @@ const baseClass = "turn-on-mdm-message"; interface ITurnOnMdmMessageProps { router: InjectedRouter; + /** Default: Manage your hosts */ + header?: string; + /** Default: MDM must be turned on to change settings on your hosts. */ + info?: string; } -const TurnOnMdmMessage = ({ router }: ITurnOnMdmMessageProps) => { +const TurnOnMdmMessage = ({ router, header, info }: ITurnOnMdmMessageProps) => { const { isGlobalAdmin } = useContext(AppContext); const onConnectClick = () => { @@ -35,8 +39,8 @@ const TurnOnMdmMessage = ({ router }: ITurnOnMdmMessageProps) => { return ( ); diff --git a/frontend/pages/ManageControlsPage/components/TurnOnMdmMessage/index.ts b/frontend/components/TurnOnMdmMessage/index.ts similarity index 100% rename from frontend/pages/ManageControlsPage/components/TurnOnMdmMessage/index.ts rename to frontend/components/TurnOnMdmMessage/index.ts diff --git a/frontend/components/graphics/FilePfx.tsx b/frontend/components/graphics/FilePfx.tsx deleted file mode 100644 index 375272e704c9..000000000000 --- a/frontend/components/graphics/FilePfx.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from "react"; - -const FilePfx = () => { - return ( - - - - - - - - - - - - - - - - - ); -}; - -export default FilePfx; diff --git a/frontend/components/graphics/index.ts b/frontend/components/graphics/index.ts index 86502c6517f8..38862d51da16 100644 --- a/frontend/components/graphics/index.ts +++ b/frontend/components/graphics/index.ts @@ -9,7 +9,6 @@ import FilePs1 from "./FilePs1"; import FilePy from "./FilePy"; import FileScript from "./FileScript"; import FilePdf from "./FilePdf"; -import FilePfx from "./FilePfx"; import FilePkg from "./FilePkg"; import FileP7m from "./FileP7m"; import FilePem from "./FilePem"; @@ -45,7 +44,6 @@ export const GRAPHIC_MAP = { "file-py": FilePy, "file-script": FileScript, "file-pdf": FilePdf, - "file-pfx": FilePfx, "file-pkg": FilePkg, "file-p7m": FileP7m, "file-pem": FilePem, diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx index 3416a92993f4..2bca2b373e74 100644 --- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx +++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx @@ -427,8 +427,8 @@ const TAGGED_TEMPLATES = { return ( <> {" "} - added Microsoft's Network Device Enrollment Service (NDES) as your - SCEP server. + removed Microsoft's Network Device Enrollment Service (NDES) as + your SCEP server. ); }, diff --git a/frontend/pages/ManageControlsPage/OSSettings/OSSettings.tsx b/frontend/pages/ManageControlsPage/OSSettings/OSSettings.tsx index 65caa724e8e4..cabcdaf56492 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/OSSettings.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/OSSettings.tsx @@ -9,7 +9,7 @@ import mdmAPI from "services/entities/mdm"; import OS_SETTINGS_NAV_ITEMS from "./OSSettingsNavItems"; import ProfileStatusAggregate from "./ProfileStatusAggregate"; -import TurnOnMdmMessage from "../components/TurnOnMdmMessage"; +import TurnOnMdmMessage from "../../../components/TurnOnMdmMessage"; const baseClass = "os-settings"; diff --git a/frontend/pages/ManageControlsPage/OSUpdates/OSUpdates.tsx b/frontend/pages/ManageControlsPage/OSUpdates/OSUpdates.tsx index efe6b7fb229a..ebbda487a9a3 100644 --- a/frontend/pages/ManageControlsPage/OSUpdates/OSUpdates.tsx +++ b/frontend/pages/ManageControlsPage/OSUpdates/OSUpdates.tsx @@ -15,7 +15,7 @@ import PremiumFeatureMessage from "components/PremiumFeatureMessage"; import Spinner from "components/Spinner"; import EndUserOSRequirementPreview from "./components/EndUserOSRequirementPreview"; -import TurnOnMdmMessage from "../components/TurnOnMdmMessage/TurnOnMdmMessage"; +import TurnOnMdmMessage from "../../../components/TurnOnMdmMessage/TurnOnMdmMessage"; import CurrentVersionSection from "./components/CurrentVersionSection"; import TargetSection from "./components/TargetSection"; import { parseOSUpdatesCurrentVersionsQueryParams } from "./components/CurrentVersionSection/CurrentVersionSection"; diff --git a/frontend/pages/ManageControlsPage/SetupExperience/SetupExperience.tsx b/frontend/pages/ManageControlsPage/SetupExperience/SetupExperience.tsx index 99541e0269ce..fc1940ff1ef2 100644 --- a/frontend/pages/ManageControlsPage/SetupExperience/SetupExperience.tsx +++ b/frontend/pages/ManageControlsPage/SetupExperience/SetupExperience.tsx @@ -10,7 +10,7 @@ import PremiumFeatureMessage from "components/PremiumFeatureMessage"; import EmptyTable from "components/EmptyTable"; import SETUP_EXPERIENCE_NAV_ITEMS from "./SetupExperienceNavItems"; -import TurnOnMdmMessage from "../components/TurnOnMdmMessage"; +import TurnOnMdmMessage from "../../../components/TurnOnMdmMessage"; const baseClass = "setup-experience"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/AppleBusinessManagerPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/AppleBusinessManagerPage.tsx index c484171eeb16..0fb497e705e1 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/AppleBusinessManagerPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/AppleBusinessManagerPage.tsx @@ -19,6 +19,7 @@ import DataError from "components/DataError"; import MainContent from "components/MainContent"; import Spinner from "components/Spinner"; import PremiumFeatureMessage from "components/PremiumFeatureMessage"; +import TurnOnMdmMessage from "components/TurnOnMdmMessage"; import AppleBusinessManagerTable from "./components/AppleBusinessManagerTable"; import AddAbmModal from "./components/AddAbmModal"; @@ -28,30 +29,6 @@ import EditTeamsAbmModal from "./components/EditTeamsAbmModal"; const baseClass = "apple-business-manager-page"; -interface ITurnOnMdmMessageProps { - router: InjectedRouter; -} - -const TurnOnMdmMessage = ({ router }: ITurnOnMdmMessageProps) => { - return ( -
    -

    Turn on Apple MDM

    -

    - To add your ABM and enable automatic enrollment for macOS, iOS, and - iPadOS hosts, first turn on Apple MDM. -

    - -
    - ); -}; - interface IAddAbmMessageProps { onAddAbm: () => void; } @@ -168,7 +145,14 @@ const AppleBusinessManagerPage = ({ router }: { router: InjectedRouter }) => { } if (!config?.mdm.enabled_and_configured) { - return ; + return ( + + ); } if (isLoading) { diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/_styles.scss index ea3356cff8e9..1e26950bc2af 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/_styles.scss @@ -22,8 +22,7 @@ } } - - &__turn-on-mdm-message, &__add-adm-message { + &__add-adm-message { margin: 0 auto; text-align: center; width: 450px; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/_styles.scss index 85a91f749679..2538cc73ba9e 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage/_styles.scss @@ -35,7 +35,7 @@ &__setup-instructions-list { padding: 0; margin: 0; - // list-style: none; + list-style: none; display: flex; flex-direction: column; gap: $pad-large; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/DeleteScepModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/DeleteScepModal.tsx deleted file mode 100644 index aae90bca0156..000000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/DeleteScepModal.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from "react"; - -import Modal from "components/Modal"; -import Button from "components/buttons/Button"; - -const baseClass = "delete-scep-modal"; - -interface IDeleteScepModalProps { - onDelete: () => void; - onCancel: () => void; -} - -const DeleteScepModal = ({ onDelete, onCancel }: IDeleteScepModalProps) => { - return ( - onDelete()} - > - <> -

    - {/* End users won’t be required to agree to this SCEP on macOS hosts that - automatically enroll. */} - TODO -

    -
    - - -
    - -
    - ); -}; - -export default DeleteScepModal; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/index.ts b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/index.ts deleted file mode 100644 index 96960f8a2309..000000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/DeleteScepModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./DeleteScepModal"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx index a6ec0a2d4afd..e41649c2fa33 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -21,6 +21,7 @@ import TooltipWrapper from "components/TooltipWrapper"; import PremiumFeatureMessage from "components/PremiumFeatureMessage"; import Spinner from "components/Spinner"; import DataError from "components/DataError"; +import TurnOnMdmMessage from "components/TurnOnMdmMessage"; import { SCEP_SERVER_TIP_CONTENT } from "../components/ScepSection/ScepSection"; @@ -34,28 +35,6 @@ const CACHE_ERROR = const DEFAULT_ERROR = "Something went wrong updating your SCEP server. Please try again."; -interface ITurnOnMdmMessageProps { - router: InjectedRouter; -} - -const TurnOnMdmMessage = ({ router }: ITurnOnMdmMessageProps) => { - return ( -
    -

    Turn on Apple MDM

    - {/* TODO: Confirm wording missed Figma spec */} -

    To help your end users connect to Wi-Fi, first turn on Apple MDM.

    - -
    - ); -}; - interface IScepCertificateContentProps { router: InjectedRouter; onFormSubmit: (evt: React.MouseEvent) => Promise; @@ -84,7 +63,15 @@ const ScepCertificateContent = ({ } if (!config?.mdm.enabled_and_configured) { - return ; + return ( + <> + + + ); } if (isLoading) { diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/VppPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/VppPage.tsx index 3c08f6291211..113c967e3fd8 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/VppPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/VppPage.tsx @@ -16,6 +16,7 @@ import Button from "components/buttons/Button"; import DataError from "components/DataError"; import Spinner from "components/Spinner"; import PremiumFeatureMessage from "components/PremiumFeatureMessage"; +import TurnOnMdmMessage from "components/TurnOnMdmMessage"; import AddVppModal from "./components/AddVppModal"; import RenewVppModal from "./components/RenewVppModal"; @@ -25,30 +26,6 @@ import EditTeamsVppModal from "./components/EditTeamsVppModal"; const baseClass = "vpp-page"; -interface ITurnOnMdmMessageProps { - router: InjectedRouter; -} - -const TurnOnMdmMessage = ({ router }: ITurnOnMdmMessageProps) => { - return ( -
    -

    Turn on Apple MDM

    -

    - To install Apple App Store apps purchased through Apple Business - Manager, first turn on Apple MDM. -

    - -
    - ); -}; - interface IAddVppMessageProps { onAddVpp: () => void; } @@ -163,7 +140,14 @@ const VppPage = ({ router }: IVppPageProps) => { } if (!config?.mdm.enabled_and_configured) { - return ; + return ( + + ); } if (isLoading) { diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/_styles.scss index dd76601ce95b..5a3e43c8f268 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/_styles.scss @@ -22,8 +22,6 @@ } } - - &__turn-on-mdm-message, &__add-vpp-message { margin: 0 auto; text-align: center; @@ -33,13 +31,13 @@ align-items: center; justify-content: center; - >h2 { + > h2 { margin-bottom: $pad-small; font-size: $small; font-weight: $bold; } - >p { + > p { margin: 0 0 $pad-medium; } } From 993cf7d4bed93d0bdfad1a2b2d9e13bf9412858f Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Wed, 9 Oct 2024 14:08:24 -0400 Subject: [PATCH 08/23] Add client side validation, clean up turn on mdm --- .../TurnOnMdmMessage.tests.tsx | 62 +++++++ .../cards/MdmSettings/MdmSettings.tsx | 4 +- .../cards/MdmSettings/ScepPage/ScepPage.tsx | 166 +++++++++++++----- 3 files changed, 191 insertions(+), 41 deletions(-) create mode 100644 frontend/components/TurnOnMdmMessage/TurnOnMdmMessage.tests.tsx diff --git a/frontend/components/TurnOnMdmMessage/TurnOnMdmMessage.tests.tsx b/frontend/components/TurnOnMdmMessage/TurnOnMdmMessage.tests.tsx new file mode 100644 index 000000000000..0f602731a480 --- /dev/null +++ b/frontend/components/TurnOnMdmMessage/TurnOnMdmMessage.tests.tsx @@ -0,0 +1,62 @@ +import React from "react"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { createCustomRenderer, createMockRouter } from "test/test-utils"; + +import TurnOnMdmMessage from "./TurnOnMdmMessage"; + +describe("TurnOnMdmMessage", () => { + it("renders with default header and info", () => { + render(); + + expect(screen.getByText("Manage your hosts")).toBeInTheDocument(); + expect( + screen.getByText( + "MDM must be turned on to change settings on your hosts." + ) + ).toBeInTheDocument(); + }); + + it("renders with custom header and info", () => { + render( + + ); + + expect(screen.getByText("Custom header")).toBeInTheDocument(); + expect(screen.getByText("Custom info")).toBeInTheDocument(); + }); + + it('renders "Turn on" button for global admin pushes to /settings/integrration/mdm when "Turn on" button is clicked', () => { + const customRender = createCustomRenderer({ + context: { + app: { + isGlobalAdmin: true, + }, + }, + }); + + customRender(); + + fireEvent.click(screen.getByText("Turn on")); + expect(createMockRouter().push).toHaveBeenCalledWith( + "/settings/integrations/mdm" + ); + }); + + it('does not render "Turn on" button for non-global admin', () => { + const customRender = createCustomRenderer({ + context: { + app: { + isGlobalAdmin: false, + }, + }, + }); + + customRender(); + + expect(screen.queryByText("Turn on")).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx index 939f0a2a6b6f..11774043483b 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx @@ -94,6 +94,8 @@ const MdmSettings = ({ router }: IMdmSettingsProps) => { const noVppTokenUploaded = !vppData || !vppData.vpp_tokens.length; const hasVppError = isVppError && !noVppTokenUploaded; + const noScepCredentials = !config?.integrations.ndes_scep_proxy; + // We are relying on the API to give us a 404 to // tell use the user has not uploaded a eula. const noEulaUploaded = eulaError && eulaError.status === 404; @@ -133,7 +135,7 @@ const MdmSettings = ({ router }: IMdmSettingsProps) => { /> {isPremiumTier && !!config?.mdm.apple_bm_enabled_and_configured && ( diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx index e41649c2fa33..29ed220d9c09 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from "react"; +import React, { useContext, useState, useEffect } from "react"; import { useQuery } from "react-query"; import { InjectedRouter } from "react-router"; @@ -15,6 +15,7 @@ import Button from "components/buttons/Button"; import BackLink from "components/BackLink/BackLink"; import CustomLink from "components/CustomLink"; import Card from "components/Card"; +import validateUrl from "components/forms/validators/valid_url"; // @ts-ignore import InputField from "components/forms/fields/InputField"; import TooltipWrapper from "components/TooltipWrapper"; @@ -38,12 +39,14 @@ const DEFAULT_ERROR = interface IScepCertificateContentProps { router: InjectedRouter; onFormSubmit: (evt: React.MouseEvent) => Promise; - formData: any; // TODO + formData: INdesFormData; // TODO + formErrors: INdesFormErrors; onInputChange: ({ name, value }: IFormField) => void; config: IConfig | null; isPremiumTier: boolean; isLoading: boolean; isSaving: boolean; + isSavingDisabled: boolean; showDataError: boolean; } @@ -51,11 +54,13 @@ const ScepCertificateContent = ({ router, onFormSubmit, formData, + formErrors, onInputChange, config, isPremiumTier, isLoading, isSaving, + isSavingDisabled, showDataError, }: IScepCertificateContentProps) => { if (!isPremiumTier) { @@ -64,13 +69,11 @@ const ScepCertificateContent = ({ if (!config?.mdm.enabled_and_configured) { return ( - <> - - + ); } @@ -87,6 +90,12 @@ const ScepCertificateContent = ({ ); } + const allEmptyValues = + !formData.scepUrl && + !formData.adminUrl && + !formData.username && + !formData.password; + return ( <>

    @@ -111,46 +120,76 @@ const ScepCertificateContent = ({

    + The URL used by client devices +
    to request and retrieve certificates. + + } + value={formData.scepUrl} + parseTarget + onChange={onInputChange} + error={!!formErrors.scepUrl && formErrors.scepUrl} placeholder="https://example.com/certsrv/mscep/mscep.dll" /> + The admin interface for managing the SCEP +
    service and viewing configuration details. + + } + value={formData.adminUrl} + parseTarget + onChange={onInputChange} + error={!!formErrors.adminUrl && formErrors.adminUrl} placeholder="https://example.com/certsrv/mscep_admin/" /> + The username in the down-level logon name format +
    + required to log in to the SCEP admin page. + + } value={formData.username} - onInputChange={onInputChange} + parseTarget + onChange={onInputChange} placeholder="username@example.microsoft.com" /> + The password to use to log in +
    + to the SCEP admin page. + + } + value={formData.password || ""} + parseTarget + onChange={onInputChange} + placeholder="••••••••" + blockAutoComplete /> @@ -183,10 +222,15 @@ interface IScepPageProps { } interface INdesFormData { - url: string; + scepUrl: string; adminUrl: string; username: string; - password: string; + password: null; +} + +interface INdesFormErrors { + scepUrl?: string | null; + adminUrl?: string | null; } export interface IFormField { @@ -201,59 +245,99 @@ const ScepPage = ({ router }: IScepPageProps) => { const isMdmEnabled = !!config?.mdm.enabled_and_configured; - const [isUpdatingNdesScepProxy, setIsUpdatingNdesScepProxy] = useState(false); - const ndesInfoReturnedFromApi: IScepInfo = { url: "", admin_url: "", username: "", - password: "", + password: null, }; const { - url, + url: scepUrl, admin_url: adminUrl, username, password, } = ndesInfoReturnedFromApi; const [formData, setFormData] = useState({ - url: "", + scepUrl: "", adminUrl: "", username: "", - password: "", + password: null, }); + const [formErrors, setFormErrors] = useState({}); + const [isSavingDisabled, setIsSavingDisabled] = useState(true); + const [isUpdatingNdesScepProxy, setIsUpdatingNdesScepProxy] = useState(false); const { isLoading: isLoadingAppConfig, refetch: refetchConfig, isError: isErrorAppConfig, - error: errorAppConfig, } = useQuery(["config"], () => configAPI.loadAll(), { select: (data: IConfig) => data, onSuccess: (data) => { if (data.integrations.ndes_scep_proxy) { setFormData({ - url: data.integrations.ndes_scep_proxy.url || "", + scepUrl: data.integrations.ndes_scep_proxy.url || "", adminUrl: data.integrations.ndes_scep_proxy.admin_url || "", username: data.integrations.ndes_scep_proxy.username || "", - password: data.integrations.ndes_scep_proxy.password || "", + password: null, }); } }, }); + useEffect(() => { + const allFieldsEmpty = + formData.scepUrl === "" && + formData.adminUrl === "" && + formData.username === "" && + (formData.password === "" || formData.password === null); + + const allFieldsPresent = + formData.scepUrl !== "" && + formData.adminUrl !== "" && + formData.username !== "" && + formData.password !== ""; + + const isPasswordNull = formData.password === null; + + if (!allFieldsEmpty && !allFieldsPresent) { + setIsSavingDisabled(true); + } else setIsSavingDisabled(false); + }, [formData]); + const onInputChange = ({ name, value }: IFormField) => { + console.log("setFormData"); setFormData({ ...formData, [name]: value }); }; const onFormSubmit = async (evt: React.MouseEvent) => { - setIsUpdatingNdesScepProxy(true); - evt.preventDefault(); + const scepUrlValid = validateUrl({ url: formData.scepUrl }); + const adminUrlValid = validateUrl({ url: formData.adminUrl }); + const newFormErrors = { + scepUrl: + scepUrlValid || formData.scepUrl === "" + ? undefined + : "Must be a valid URL.", + adminUrl: + adminUrlValid || formData.adminUrl === "" + ? undefined + : "Must be a valid URL.", + }; + + setFormErrors(newFormErrors); + + if (!scepUrlValid && !adminUrlValid) { + return; + } + + setIsUpdatingNdesScepProxy(true); + const isRemovingNdesScepProxy = - formData.url === "" && + formData.scepUrl === "" && formData.adminUrl === "" && username === "" && password === ""; @@ -263,7 +347,7 @@ const ScepPage = ({ router }: IScepPageProps) => { ? null // Send null if no fields are set : [ { - url: formData.url, + url: formData.scepUrl, admin_url: formData.adminUrl, username: formData.username, password: formData.password || null, @@ -310,11 +394,13 @@ const ScepPage = ({ router }: IScepPageProps) => { router={router} onFormSubmit={onFormSubmit} formData={formData} + formErrors={formErrors} onInputChange={onInputChange} config={config} isPremiumTier={isPremiumTier || false} isLoading={isLoadingAppConfig} isSaving={isUpdatingNdesScepProxy} + isSavingDisabled={isSavingDisabled} showDataError={isErrorAppConfig} />
    From 2f4ce8b2256a54a42ab15829d7f26a5933ec8784 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Wed, 9 Oct 2024 15:06:46 -0400 Subject: [PATCH 09/23] Missing activity item code --- .../cards/ActivityFeed/ActivityItem/ActivityItem.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx index 2bca2b373e74..46562d4e5552 100644 --- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx +++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx @@ -414,7 +414,7 @@ const TAGGED_TEMPLATES = { ); }, - addedNdesScepProxy: (activity: IActivity) => { + createdNdesScepProxy: () => { return ( <> {" "} @@ -1131,7 +1131,6 @@ const getDetail = ( case ActivityType.EditedIpadosMinVersion: { return TAGGED_TEMPLATES.editedAppleosMinVersion("iPadOS", activity); } - case ActivityType.ReadHostDiskEncryptionKey: { return TAGGED_TEMPLATES.readHostDiskEncryptionKey(activity); } @@ -1144,6 +1143,15 @@ const getDetail = ( case ActivityType.EditedAppleOSProfile: { return TAGGED_TEMPLATES.editedAppleOSProfile(activity, isPremiumTier); } + case ActivityType.CreatedNdesScepProxy: { + return TAGGED_TEMPLATES.createdNdesScepProxy(); + } + case ActivityType.DeletedNdesScepProxy: { + return TAGGED_TEMPLATES.deletedNdesScepProxy(); + } + case ActivityType.EditedNdesScepProxy: { + return TAGGED_TEMPLATES.editedNdesScepProxy(); + } case ActivityType.CreatedWindowsProfile: { return TAGGED_TEMPLATES.createdWindowsProfile(activity, isPremiumTier); } From 0c8a19be3215a154129cece47ff489a036e12ab4 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Thu, 10 Oct 2024 09:56:45 -0400 Subject: [PATCH 10/23] Add tests --- .../ActivityItem/ActivityItem.tests.tsx | 42 ++++++ .../MdmSettings/ScepPage/ScepPage.tests.tsx | 135 ++++++++++++++++++ .../cards/MdmSettings/ScepPage/ScepPage.tsx | 7 +- 3 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tests.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tests.tsx index b12c50a80211..f806d7a8bf19 100644 --- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tests.tsx +++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tests.tsx @@ -1370,4 +1370,46 @@ describe("Activity Feed", () => { render(); expect(screen.getByText("An end user")).toBeInTheDocument(); }); + + it("renders createdNdesScepProxy activity correctly", () => { + const activity = createMockActivity({ + type: ActivityType.CreatedNdesScepProxy, + }); + render(); + + expect(screen.getByText(/Test User/)).toBeInTheDocument(); + expect( + screen.getByText( + /added Microsoft's Network Device Enrollment Service \(NDES\) as your SCEP server/ + ) + ).toBeInTheDocument(); + }); + + it("renders editedNdesScepProxy activity correctly", () => { + const activity = createMockActivity({ + type: ActivityType.EditedNdesScepProxy, + }); + render(); + + expect(screen.getByText(/Test User/)).toBeInTheDocument(); + expect( + screen.getByText( + /edited configurations for Microsoft's Network Device Enrollment Service \(NDES\) as your SCEP server/ + ) + ).toBeInTheDocument(); + }); + + it("renders deletedNdesScepProxy activity correctly", () => { + const activity = createMockActivity({ + type: ActivityType.DeletedNdesScepProxy, + }); + render(); + + expect(screen.getByText(/Test User/)).toBeInTheDocument(); + expect( + screen.getByText( + /removed Microsoft's Network Device Enrollment Service \(NDES\) as your SCEP server/ + ) + ).toBeInTheDocument(); + }); }); diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx new file mode 100644 index 000000000000..e8529bb16cce --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx @@ -0,0 +1,135 @@ +import React from "react"; +import { fireEvent, render, screen } from "@testing-library/react"; + +import { createCustomRenderer, createMockRouter } from "test/test-utils"; + +import createMockConfig, { createMockMdmConfig } from "__mocks__/configMock"; + +import { ScepCertificateContent } from "./ScepPage"; + +const FORM_DATA = { scepUrl: "", adminUrl: "", username: "", password: "" }; + +describe("Scep Page", () => { + it("renders PremiumFeatureMessage for non-premium tier", () => { + render( + + ); + expect( + screen.getByText("This feature is included in Fleet Premium.") + ).toBeInTheDocument(); + }); + it("renders TurnOnMdmMessage when MDM is not enabled", () => { + render( + + ); + expect(screen.getByText("Turn on Apple MDM")).toBeInTheDocument(); + }); + it("renders Spinner when loading", () => { + render( + + ); + expect(screen.getByTestId("spinner")).toBeInTheDocument(); + }); + it("renders DataError when showDataError is true", () => { + render( + + ); + expect(screen.getByText("Something's gone wrong.")).toBeInTheDocument(); + }); + it("renders form fields correctly", () => { + render( + + ); + expect(screen.getByLabelText("SCEP URL")).toBeInTheDocument(); + expect(screen.getByLabelText("Admin URL")).toBeInTheDocument(); + expect(screen.getByLabelText("Username")).toBeInTheDocument(); + expect(screen.getByLabelText("Password")).toBeInTheDocument(); + }); + it("displays error messages for invalid inputs", () => { + const FORM_ERRORS = { scepUrl: "Invalid URL", adminUrl: "Invalid URL" }; + const INVALID_FORM_DATA = { + scepUrl: "invalid", + adminUrl: "invalid", + username: "", + password: "", + }; + render( + + ); + expect(screen.getAllByLabelText("Invalid URL").length).toBe(2); + }); +}); diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx index 29ed220d9c09..105e1d99731d 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -50,7 +50,7 @@ interface IScepCertificateContentProps { showDataError: boolean; } -const ScepCertificateContent = ({ +export const ScepCertificateContent = ({ router, onFormSubmit, formData, @@ -225,7 +225,7 @@ interface INdesFormData { scepUrl: string; adminUrl: string; username: string; - password: null; + password: string | null; } interface INdesFormErrors { @@ -300,15 +300,12 @@ const ScepPage = ({ router }: IScepPageProps) => { formData.username !== "" && formData.password !== ""; - const isPasswordNull = formData.password === null; - if (!allFieldsEmpty && !allFieldsPresent) { setIsSavingDisabled(true); } else setIsSavingDisabled(false); }, [formData]); const onInputChange = ({ name, value }: IFormField) => { - console.log("setFormData"); setFormData({ ...formData, [name]: value }); }; From 6a567d499aa80f195afd16fb177342807b510cc7 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 11 Oct 2024 10:22:52 -0400 Subject: [PATCH 11/23] Cleanup: renaming, fix proptypes, form stuff --- .../forms/fields/InputField/InputField.jsx | 4 +- .../MdmSettings/ScepPage/ScepPage.tests.tsx | 12 +- .../cards/MdmSettings/ScepPage/ScepPage.tsx | 108 ++++++------------ 3 files changed, 45 insertions(+), 79 deletions(-) diff --git a/frontend/components/forms/fields/InputField/InputField.jsx b/frontend/components/forms/fields/InputField/InputField.jsx index 758ef70ab4c0..0762fb40523b 100644 --- a/frontend/components/forms/fields/InputField/InputField.jsx +++ b/frontend/components/forms/fields/InputField/InputField.jsx @@ -35,7 +35,7 @@ class InputField extends Component { PropTypes.number, ]).isRequired, parseTarget: PropTypes.bool, - tooltip: PropTypes.string, + tooltip: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), labelTooltipPosition: PropTypes.string, helpText: PropTypes.oneOfType([ PropTypes.string, @@ -43,7 +43,7 @@ class InputField extends Component { PropTypes.object, ]), enableCopy: PropTypes.bool, - copyButtonPosition: PropTypes.oneOfType(["inside", "outside"]), + copyButtonPosition: PropTypes.oneOf(["inside", "outside"]), ignore1password: PropTypes.bool, }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx index e8529bb16cce..35401cd0ee83 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx @@ -21,7 +21,7 @@ describe("Scep Page", () => { config={createMockConfig()} isLoading={false} isSaving={false} - isSavingDisabled={false} + saveButtonDisabled={false} showDataError={false} isPremiumTier={false} // test /> @@ -43,7 +43,7 @@ describe("Scep Page", () => { })} isLoading={false} isSaving={false} - isSavingDisabled={false} + saveButtonDisabled={false} showDataError={false} isPremiumTier /> @@ -61,7 +61,7 @@ describe("Scep Page", () => { config={createMockConfig()} isLoading // test isSaving={false} - isSavingDisabled={false} + saveButtonDisabled={false} showDataError={false} isPremiumTier /> @@ -79,7 +79,7 @@ describe("Scep Page", () => { config={createMockConfig()} isLoading={false} isSaving={false} - isSavingDisabled={false} + saveButtonDisabled={false} showDataError // test isPremiumTier /> @@ -97,7 +97,7 @@ describe("Scep Page", () => { config={createMockConfig()} isLoading={false} isSaving={false} - isSavingDisabled={false} + saveButtonDisabled={false} showDataError={false} isPremiumTier /> @@ -125,7 +125,7 @@ describe("Scep Page", () => { config={createMockConfig()} isLoading={false} isSaving={false} - isSavingDisabled={false} + saveButtonDisabled={false} showDataError={false} isPremiumTier /> diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx index 105e1d99731d..5b4b9a52539c 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -4,7 +4,6 @@ import { InjectedRouter } from "react-router"; import PATHS from "router/paths"; import configAPI from "services/entities/config"; -import { IScepInfo } from "services/entities/mdm_apple"; import { IConfig } from "interfaces/config"; import { getErrorReason } from "interfaces/errors"; import { NotificationContext } from "context/notification"; @@ -46,7 +45,7 @@ interface IScepCertificateContentProps { isPremiumTier: boolean; isLoading: boolean; isSaving: boolean; - isSavingDisabled: boolean; + saveButtonDisabled: boolean; showDataError: boolean; } @@ -60,7 +59,7 @@ export const ScepCertificateContent = ({ isPremiumTier, isLoading, isSaving, - isSavingDisabled, + saveButtonDisabled, showDataError, }: IScepCertificateContentProps) => { if (!isPremiumTier) { @@ -90,12 +89,6 @@ export const ScepCertificateContent = ({ ); } - const allEmptyValues = - !formData.scepUrl && - !formData.adminUrl && - !formData.username && - !formData.password; - return ( <>

    @@ -130,9 +123,9 @@ export const ScepCertificateContent = ({ } value={formData.scepUrl} - parseTarget onChange={onInputChange} - error={!!formErrors.scepUrl && formErrors.scepUrl} + parseTarget + error={formErrors.scepUrl} placeholder="https://example.com/certsrv/mscep/mscep.dll" /> } value={formData.adminUrl} - parseTarget onChange={onInputChange} - error={!!formErrors.adminUrl && formErrors.adminUrl} + parseTarget + error={formErrors.adminUrl} placeholder="https://example.com/certsrv/mscep_admin/" /> } value={formData.username} - parseTarget onChange={onInputChange} + parseTarget placeholder="username@example.microsoft.com" /> } value={formData.password || ""} - parseTarget + type="password" onChange={onInputChange} + parseTarget placeholder="••••••••" blockAutoComplete /> @@ -189,7 +183,7 @@ export const ScepCertificateContent = ({ variant="brand" className="button-wrap" isLoading={isSaving} - disabled={isSavingDisabled} + disabled={saveButtonDisabled} > Save @@ -239,74 +233,49 @@ export interface IFormField { } const ScepPage = ({ router }: IScepPageProps) => { - const { config, isPremiumTier, setConfig } = useContext(AppContext); + const { isPremiumTier, setConfig } = useContext(AppContext); const { renderFlash } = useContext(NotificationContext); - const isMdmEnabled = !!config?.mdm.enabled_and_configured; - - const ndesInfoReturnedFromApi: IScepInfo = { - url: "", - admin_url: "", - username: "", - password: null, - }; - - const { - url: scepUrl, - admin_url: adminUrl, - username, - password, - } = ndesInfoReturnedFromApi; - const [formData, setFormData] = useState({ scepUrl: "", adminUrl: "", username: "", - password: null, + password: "", }); + + console.log("formData", formData); const [formErrors, setFormErrors] = useState({}); - const [isSavingDisabled, setIsSavingDisabled] = useState(true); + const [saveButtonDisabled, setSaveButtonDisabled] = useState(true); const [isUpdatingNdesScepProxy, setIsUpdatingNdesScepProxy] = useState(false); const { + data: appConfig, isLoading: isLoadingAppConfig, refetch: refetchConfig, isError: isErrorAppConfig, } = useQuery(["config"], () => configAPI.loadAll(), { select: (data: IConfig) => data, onSuccess: (data) => { - if (data.integrations.ndes_scep_proxy) { - setFormData({ - scepUrl: data.integrations.ndes_scep_proxy.url || "", - adminUrl: data.integrations.ndes_scep_proxy.admin_url || "", - username: data.integrations.ndes_scep_proxy.username || "", - password: null, - }); - } + setConfig(data); }, }); useEffect(() => { - const allFieldsEmpty = - formData.scepUrl === "" && - formData.adminUrl === "" && - formData.username === "" && - (formData.password === "" || formData.password === null); - - const allFieldsPresent = - formData.scepUrl !== "" && - formData.adminUrl !== "" && - formData.username !== "" && - formData.password !== ""; + const areAllFieldsEmpty = Object.values(formData).every( + (val) => val === "" || val === null + ); + const areAllFieldsComplete = Object.values(formData).every( + (val) => val !== "" && val !== null + ); - if (!allFieldsEmpty && !allFieldsPresent) { - setIsSavingDisabled(true); - } else setIsSavingDisabled(false); + setSaveButtonDisabled(!areAllFieldsEmpty && !areAllFieldsComplete); }, [formData]); const onInputChange = ({ name, value }: IFormField) => { - setFormData({ ...formData, [name]: value }); + setFormErrors((prev) => ({ ...prev, [name]: null })); + setFormData((prev) => ({ ...prev, [name]: value })); + console.log("onInputChange formData", formData); }; const onFormSubmit = async (evt: React.MouseEvent) => { @@ -336,21 +305,18 @@ const ScepPage = ({ router }: IScepPageProps) => { const isRemovingNdesScepProxy = formData.scepUrl === "" && formData.adminUrl === "" && - username === "" && - password === ""; + formData.username === "" && + (formData.password === "" || formData.password === null); // Format for API const formDataToSubmit = isRemovingNdesScepProxy ? null // Send null if no fields are set - : [ - { - url: formData.scepUrl, - admin_url: formData.adminUrl, - username: formData.username, - password: formData.password || null, - }, - ]; - + : { + url: formData.scepUrl, + admin_url: formData.adminUrl, + username: formData.username, + password: formData.password || null, + }; // Update integrations.ndes_scep_proxy only const destination = { ndes_scep_proxy: formDataToSubmit, @@ -393,11 +359,11 @@ const ScepPage = ({ router }: IScepPageProps) => { formData={formData} formErrors={formErrors} onInputChange={onInputChange} - config={config} + config={appConfig || null} isPremiumTier={isPremiumTier || false} isLoading={isLoadingAppConfig} isSaving={isUpdatingNdesScepProxy} - isSavingDisabled={isSavingDisabled} + saveButtonDisabled={saveButtonDisabled} showDataError={isErrorAppConfig} />

    From f92beb81db450cea347d0c5adcd4495377d7116c Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 11 Oct 2024 11:20:45 -0400 Subject: [PATCH 12/23] Cleanup: tooltip styling, success/error messages --- .../components/TooltipWrapper/_styles.scss | 1 - .../cards/MdmSettings/ScepPage/ScepPage.tsx | 48 ++++++++++--------- .../components/ScepSection/ScepSection.tsx | 9 +++- .../components/ScepSection/__styles.scss | 2 +- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/frontend/components/TooltipWrapper/_styles.scss b/frontend/components/TooltipWrapper/_styles.scss index b9132eaabc6d..66e0c7df1a97 100644 --- a/frontend/components/TooltipWrapper/_styles.scss +++ b/frontend/components/TooltipWrapper/_styles.scss @@ -1,5 +1,4 @@ .component__tooltip-wrapper { - &.show-arrow { @include tooltip5-arrow-styles; } diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx index 5b4b9a52539c..ad1f2b7fef63 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -31,7 +31,7 @@ const BAD_SCEP_URL_ERROR = "Invalid SCEP URL. Please correct and try again."; const BAD_CREDENTIALS_ERROR = "Invalid admin URL or credentials. Please correct and try again."; const CACHE_ERROR = - "The NDES password cache is full. Please increase the number of cached passwords in NDES and try again."; + "The NDES password cache is full. Please increase the number of cached passwords in NDES and try again. By default, NDES caches 5 passwords and they expire 60 minutes after they are created."; const DEFAULT_ERROR = "Something went wrong updating your SCEP server. Please try again."; @@ -102,14 +102,14 @@ export const ScepCertificateContent = ({
    1. {/* TODO: confirm URL */} - <> +
      Connect to your Network Device Enrollment Service ( ) admin account: - +
      { - const { isPremiumTier, setConfig } = useContext(AppContext); + const { isPremiumTier, config, setConfig } = useContext(AppContext); const { renderFlash } = useContext(NotificationContext); const [formData, setFormData] = useState({ - scepUrl: "", - adminUrl: "", - username: "", - password: "", + scepUrl: config?.integrations.ndes_scep_proxy?.url || "", + adminUrl: config?.integrations.ndes_scep_proxy?.admin_url || "", + username: config?.integrations.ndes_scep_proxy?.username || "", + password: config?.integrations.ndes_scep_proxy?.password || "", }); - console.log("formData", formData); const [formErrors, setFormErrors] = useState({}); const [saveButtonDisabled, setSaveButtonDisabled] = useState(true); const [isUpdatingNdesScepProxy, setIsUpdatingNdesScepProxy] = useState(false); @@ -275,7 +274,6 @@ const ScepPage = ({ router }: IScepPageProps) => { const onInputChange = ({ name, value }: IFormField) => { setFormErrors((prev) => ({ ...prev, [name]: null })); setFormData((prev) => ({ ...prev, [name]: value })); - console.log("onInputChange formData", formData); }; const onFormSubmit = async (evt: React.MouseEvent) => { @@ -296,21 +294,20 @@ const ScepPage = ({ router }: IScepPageProps) => { setFormErrors(newFormErrors); - if (!scepUrlValid && !adminUrlValid) { + const areAllFieldsEmpty = Object.values(formData).every( + (val) => val === "" || val === null + ); + const isRemovingNdesScepProxy = areAllFieldsEmpty; + + if (!isRemovingNdesScepProxy && (!scepUrlValid || !adminUrlValid)) { return; } setIsUpdatingNdesScepProxy(true); - const isRemovingNdesScepProxy = - formData.scepUrl === "" && - formData.adminUrl === "" && - formData.username === "" && - (formData.password === "" || formData.password === null); - // Format for API const formDataToSubmit = isRemovingNdesScepProxy - ? null // Send null if no fields are set + ? null : { url: formData.scepUrl, admin_url: formData.adminUrl, @@ -324,15 +321,22 @@ const ScepPage = ({ router }: IScepPageProps) => { try { await configAPI.update({ integrations: destination }); - renderFlash("success", "Successfully added your SCEP server."); + renderFlash( + "success", + `Successfully ${ + isRemovingNdesScepProxy ? "removed" : "added" + } your SCEP server.` + ); refetchConfig(); } catch (error) { console.error(error); - if (getErrorReason(error).includes("TODO")) { + if (getErrorReason(error).includes("invalid SCEP URL")) { renderFlash("error", BAD_SCEP_URL_ERROR); - } else if (getErrorReason(error).includes("TODO")) { + } else if ( + getErrorReason(error).includes("invalid admin URL or credentials") + ) { renderFlash("error", BAD_CREDENTIALS_ERROR); - } else if (getErrorReason(error).includes("TODO")) { + } else if (getErrorReason(error).includes("the password cache is full")) { renderFlash("error", CACHE_ERROR); } else renderFlash("error", DEFAULT_ERROR); } finally { diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx index 4e8898e8a260..f8e56872f839 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx @@ -21,8 +21,13 @@ interface IScepCardProps { router: InjectedRouter; } -export const SCEP_SERVER_TIP_CONTENT = - "Fleet currently supports Microsoft's Network Device Enrollment Service (NDES) as a SCEP server."; +export const SCEP_SERVER_TIP_CONTENT = ( + <> + Fleet currently supports Microsoft's Network Device +
      + Enrollment Service (NDES) as a SCEP server. + +); const ScepCard = ({ isAppleMdmOn, isScepOn, router }: IScepCardProps) => { const navigateToScepSetup = () => { diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/__styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/__styles.scss index f68621be463f..9a6af45ec8fe 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/__styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/__styles.scss @@ -1,5 +1,5 @@ .scep-section { .section-card__content-wrapper span { - display: initial; + display: inline-flex; } } From 5acbbcfbe13868b2b76add70f7e626c7baaa99fe Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 11 Oct 2024 11:30:36 -0400 Subject: [PATCH 13/23] Clean up: peripheral lint warnings --- .../components/RenewAbmModal/helpers.tsx | 2 -- .../cards/MdmSettings/ScepPage/ScepPage.tests.tsx | 4 ++-- .../MdmSettingsSection/AppleMdmCard/AppleMdmCard.tsx | 2 +- .../MdmSettingsSection/WindowsMdmCard/WindowsMdmCard.tsx | 2 +- .../MdmSettings/components/ScepSection/ScepSection.tsx | 2 +- .../cards/MdmSettings/components/VppSection/VppSection.tsx | 7 ------- 6 files changed, 5 insertions(+), 14 deletions(-) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/RenewAbmModal/helpers.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/RenewAbmModal/helpers.tsx index 7eaa905cb97a..d892e23eb088 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/RenewAbmModal/helpers.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage/components/RenewAbmModal/helpers.tsx @@ -1,5 +1,3 @@ -import React from "react"; - import { getErrorReason } from "interfaces/errors"; const DEFAULT_ERROR_MESSAGE = "Couldn’t renew. Please try again."; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx index 35401cd0ee83..9b1f5c26760b 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx @@ -1,7 +1,7 @@ import React from "react"; -import { fireEvent, render, screen } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; -import { createCustomRenderer, createMockRouter } from "test/test-utils"; +import { createMockRouter } from "test/test-utils"; import createMockConfig, { createMockMdmConfig } from "__mocks__/configMock"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/AppleMdmCard.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/AppleMdmCard.tsx index 06fda86b7b30..8b3f9a8f9723 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/AppleMdmCard.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/AppleMdmCard.tsx @@ -2,7 +2,6 @@ import React from "react"; import Button from "components/buttons/Button"; import Icon from "components/Icon"; -import Card from "components/Card"; import DataError from "components/DataError"; import { AxiosError } from "axios"; import { IMdmApple } from "interfaces/mdm"; @@ -17,6 +16,7 @@ interface ITurnOnAppleMdmCardProps { const TurnOnAppleMdmCard = ({ onClickTurnOn }: ITurnOnAppleMdmCardProps) => { return ( diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/WindowsMdmCard.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/WindowsMdmCard.tsx index 5e401b87606f..011b305d681a 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/WindowsMdmCard.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/WindowsMdmCard.tsx @@ -2,7 +2,6 @@ import React, { useContext } from "react"; import { AppContext } from "context/app"; -import Card from "components/Card/Card"; import Button from "components/buttons/Button"; import Icon from "components/Icon"; import SectionCard from "../../SectionCard"; @@ -18,6 +17,7 @@ const TurnOnWindowsMdmCard = ({ }: ITurnOnWindowsMdmCardProps) => { return ( diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx index f8e56872f839..aa41f2d959eb 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx @@ -23,7 +23,7 @@ interface IScepCardProps { export const SCEP_SERVER_TIP_CONTENT = ( <> - Fleet currently supports Microsoft's Network Device + Fleet currently supports Microsoft's Network Device
      Enrollment Service (NDES) as a SCEP server. diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/VppSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/VppSection.tsx index 084b17041cbf..c19ccbc4cabf 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/VppSection.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/VppSection.tsx @@ -1,18 +1,11 @@ import React, { useContext } from "react"; import { InjectedRouter } from "react-router"; -import { useQuery } from "react-query"; -import { AxiosError } from "axios"; import PATHS from "router/paths"; import { AppContext } from "context/app"; -import mdmAppleAPI, { IGetVppInfoResponse } from "services/entities/mdm_apple"; -import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; -import Card from "components/Card"; import Button from "components/buttons/Button"; import Icon from "components/Icon"; -import Spinner from "components/Spinner"; -import DataError from "components/DataError"; import SettingsSection from "pages/admin/components/SettingsSection"; import PremiumFeatureMessage from "components/PremiumFeatureMessage"; import SectionCard from "../SectionCard"; From 905304f9a29ec831d96bcc28c3e19a18072473f7 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 11 Oct 2024 11:49:59 -0400 Subject: [PATCH 14/23] Remove previous design mdm test data in lieu of config data, changelog --- changes/22125-scep-ndes-proxy | 1 + frontend/__mocks__/appleMdm.ts | 20 +------------------ frontend/__mocks__/configMock.ts | 1 + .../ScepSection/ScepSection.tests.tsx | 17 ---------------- frontend/services/entities/mdm.ts | 6 ------ frontend/test/handlers/apple_mdm.ts | 17 +--------------- 6 files changed, 4 insertions(+), 58 deletions(-) create mode 100644 changes/22125-scep-ndes-proxy diff --git a/changes/22125-scep-ndes-proxy b/changes/22125-scep-ndes-proxy new file mode 100644 index 000000000000..580ab00d1477 --- /dev/null +++ b/changes/22125-scep-ndes-proxy @@ -0,0 +1 @@ +- Add ability to connect a SCEP NDES proxy diff --git a/frontend/__mocks__/appleMdm.ts b/frontend/__mocks__/appleMdm.ts index b3351f580007..c58a96f891ac 100644 --- a/frontend/__mocks__/appleMdm.ts +++ b/frontend/__mocks__/appleMdm.ts @@ -1,10 +1,5 @@ import { IMdmApple } from "interfaces/mdm"; -import { - IGetVppInfoResponse, - IVppApp, - IGetScepInfoResponse, - IScepInfo, -} from "services/entities/mdm_apple"; +import { IGetVppInfoResponse, IVppApp } from "services/entities/mdm_apple"; const DEFAULT_MDM_APPLE_MOCK: IMdmApple = { common_name: "APSP:12345", @@ -45,17 +40,4 @@ export const createMockVppApp = (overrides?: Partial): IVppApp => { return { ...DEFAULT_MDM_APPLE_VPP_APP_MOCK, ...overrides }; }; -const DEFAULT_MDM_APPLE_SCEP_INFO_MOCK: IScepInfo = { - url: "https://example.com/scep", - admin_url: "https://example.com/mscep_admin/", - username: "Administrator@example.com", - password: "insecure", -}; - -export const createMockScepInfo = ( - overrides?: Partial -): IScepInfo => { - return { ...DEFAULT_MDM_APPLE_SCEP_INFO_MOCK, ...overrides }; -}; - export default createMockMdmApple; diff --git a/frontend/__mocks__/configMock.ts b/frontend/__mocks__/configMock.ts index bcaaffb9ee42..63bd83065177 100644 --- a/frontend/__mocks__/configMock.ts +++ b/frontend/__mocks__/configMock.ts @@ -139,6 +139,7 @@ const DEFAULT_CONFIG_MOCK: IConfig = { jira: [], zendesk: [], google_calendar: [], + ndes_scep_proxy: null, }, logging: { debug: false, diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tests.tsx index 3658b76a2451..8935316dbf37 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tests.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tests.tsx @@ -2,19 +2,12 @@ import React from "react"; import { screen } from "@testing-library/react"; import { createCustomRenderer, createMockRouter } from "test/test-utils"; -import mockServer from "test/mock-server"; -import { - defaultVppInfoHandler, - errorNoVppInfoHandler, -} from "test/handlers/apple_mdm"; import createMockConfig, { createMockMdmConfig } from "__mocks__/configMock"; import ScepSection from "./ScepSection"; describe("Scep Section", () => { it("renders mdm is off message when apple mdm is not turned on ", async () => { - mockServer.use(defaultVppInfoHandler); - const render = createCustomRenderer({ context: { app: { @@ -23,7 +16,6 @@ describe("Scep Section", () => { }), }, }, - withBackendMock: true, }); render( @@ -36,8 +28,6 @@ describe("Scep Section", () => { }); it("renders add scep when scep is disabled", async () => { - mockServer.use(errorNoVppInfoHandler); - const render = createCustomRenderer({ context: { app: { @@ -46,7 +36,6 @@ describe("Scep Section", () => { }), }, }, - withBackendMock: true, }); render( @@ -59,8 +48,6 @@ describe("Scep Section", () => { }); it("renders edit scep when scep is enabled", async () => { - mockServer.use(defaultVppInfoHandler); - const render = createCustomRenderer({ context: { app: { @@ -69,7 +56,6 @@ describe("Scep Section", () => { }), }, }, - withBackendMock: true, }); render(); expect( @@ -78,8 +64,6 @@ describe("Scep Section", () => { }); it("render the premium message when not in premium tier", async () => { - mockServer.use(defaultVppInfoHandler); - const render = createCustomRenderer({ context: { app: { @@ -88,7 +72,6 @@ describe("Scep Section", () => { }), }, }, - withBackendMock: true, }); render( ; export interface IDiskEncryptionStatusAggregate { diff --git a/frontend/test/handlers/apple_mdm.ts b/frontend/test/handlers/apple_mdm.ts index 732db4b0b6b6..5f8be1c3764d 100644 --- a/frontend/test/handlers/apple_mdm.ts +++ b/frontend/test/handlers/apple_mdm.ts @@ -1,6 +1,6 @@ import { rest } from "msw"; -import { createMockVppInfo, createMockScepInfo } from "__mocks__/appleMdm"; +import { createMockVppInfo } from "__mocks__/appleMdm"; import { baseUrl } from "test/test-utils"; // eslint-disable-next-line import/prefer-default-export @@ -17,18 +17,3 @@ export const errorNoVppInfoHandler = rest.get( return res(context.status(404)); } ); - -// eslint-disable-next-line import/prefer-default-export -export const defaultScepInfoHandler = rest.get( - baseUrl("/scep"), - (req, res, context) => { - return res(context.json(createMockScepInfo())); - } -); - -export const errorNoScepInfoHandler = rest.get( - baseUrl("/scep"), - (req, res, context) => { - return res(context.status(404)); - } -); From e6552db0312b152b1a81601ff408feba79face6e Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 11 Oct 2024 11:58:39 -0400 Subject: [PATCH 15/23] Cleanup config interface --- frontend/interfaces/integration.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/interfaces/integration.ts b/frontend/interfaces/integration.ts index 2a760e114333..93a80c6d87dd 100644 --- a/frontend/interfaces/integration.ts +++ b/frontend/interfaces/integration.ts @@ -1,5 +1,3 @@ -import { IScepInfo } from "services/entities/mdm_apple"; - export type IIntegrationType = "jira" | "zendesk"; export interface IJiraIntegration { url: string; @@ -19,6 +17,13 @@ export interface IZendeskIntegration { enable_software_vulnerabilities?: boolean; } +export interface IScepIntegration { + url: string; + admin_url: string; + username: string; + password: string | null; +} + export interface IIntegration { url: string; username?: string; @@ -86,7 +91,7 @@ export interface IZendeskJiraIntegrations { // Partial`, but that leads to a mess of types to resolve. export interface IGlobalIntegrations extends IZendeskJiraIntegrations { google_calendar?: IGlobalCalendarIntegration[] | null; - ndes_scep_proxy?: IScepInfo | null; + ndes_scep_proxy?: IScepIntegration | null; } export interface ITeamIntegrations extends IZendeskJiraIntegrations { From f6481d83b2128ef95929757fd6e8945c2e57437c Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 11 Oct 2024 14:34:01 -0400 Subject: [PATCH 16/23] All form data must be type string --- .../cards/MdmSettings/ScepPage/ScepPage.tests.tsx | 2 +- .../cards/MdmSettings/ScepPage/ScepPage.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx index 9b1f5c26760b..d4c2cd429774 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx @@ -73,7 +73,7 @@ describe("Scep Page", () => { { useEffect(() => { const areAllFieldsEmpty = Object.values(formData).every( - (val) => val === "" || val === null + (val) => val === "" ); const areAllFieldsComplete = Object.values(formData).every( - (val) => val !== "" && val !== null + (val) => val !== "" ); setSaveButtonDisabled(!areAllFieldsEmpty && !areAllFieldsComplete); @@ -295,7 +295,7 @@ const ScepPage = ({ router }: IScepPageProps) => { setFormErrors(newFormErrors); const areAllFieldsEmpty = Object.values(formData).every( - (val) => val === "" || val === null + (val) => val === "" ); const isRemovingNdesScepProxy = areAllFieldsEmpty; @@ -312,7 +312,7 @@ const ScepPage = ({ router }: IScepPageProps) => { url: formData.scepUrl, admin_url: formData.adminUrl, username: formData.username, - password: formData.password || null, + password: formData.password, }; // Update integrations.ndes_scep_proxy only const destination = { From 16584fd2c53cde0d20a58bb2542d8a9558ce05fc Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 11 Oct 2024 15:18:23 -0400 Subject: [PATCH 17/23] Password type string --- frontend/interfaces/integration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/interfaces/integration.ts b/frontend/interfaces/integration.ts index 93a80c6d87dd..802fd1fe5f34 100644 --- a/frontend/interfaces/integration.ts +++ b/frontend/interfaces/integration.ts @@ -21,7 +21,7 @@ export interface IScepIntegration { url: string; admin_url: string; username: string; - password: string | null; + password: string; } export interface IIntegration { From 34768aff7ec2544d6c5285ab0ca3202f5095c873 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 11 Oct 2024 15:22:52 -0400 Subject: [PATCH 18/23] Remove repetitive interface --- frontend/services/entities/mdm_apple.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/frontend/services/entities/mdm_apple.ts b/frontend/services/entities/mdm_apple.ts index 5baa7972bb8e..4a50779df44d 100644 --- a/frontend/services/entities/mdm_apple.ts +++ b/frontend/services/entities/mdm_apple.ts @@ -30,16 +30,6 @@ export interface IGetVppAppsResponse { app_store_apps: IVppApp[]; } -export interface IScepInfo { - url: string; - admin_url: string; - username: string; - password: string | null; -} -export interface IGetScepInfoResponse { - ndes_scep_proxy: IScepInfo; -} - export interface IGetVppTokensResponse { vpp_tokens: IMdmVppToken[]; } From d2b4b120b8279ab73053f9e9ee184878c525f5c4 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 11 Oct 2024 15:54:46 -0400 Subject: [PATCH 19/23] Fix typo --- .../AppleAutomaticEnrollmentCard.tsx | 4 ++-- .../MdmSettings/components/ScepSection/ScepSection.tsx | 8 ++++---- .../MdmSettings/components/VppSection/VppSection.tsx | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/AppleAutomaticEnrollmentCard/AppleAutomaticEnrollmentCard.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/AppleAutomaticEnrollmentCard/AppleAutomaticEnrollmentCard.tsx index a5ab3acca57d..108fc079d86b 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/AppleAutomaticEnrollmentCard/AppleAutomaticEnrollmentCard.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AutomaticEnrollmentSection/AppleAutomaticEnrollmentCard/AppleAutomaticEnrollmentCard.tsx @@ -16,7 +16,7 @@ const AppleAutomaticEnrollmentCard = ({ viewDetails, configured, }: IAppleAutomaticEnrollmentCardProps) => { - const appleMdmDiabledCard = ( + const appleMdmDisabledCard = ( To enable automatic enrollment for macOS, iOS, and iPadOS hosts, first turn on Apple MDM. @@ -57,7 +57,7 @@ const AppleAutomaticEnrollmentCard = ({ ); if (!isAppleMdmOn) { - return appleMdmDiabledCard; + return appleMdmDisabledCard; } return configured ? isAbmConfiguredCard : isAbmNotConfiguredCard; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx index aa41f2d959eb..f353a855a19d 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/ScepSection/ScepSection.tsx @@ -34,7 +34,7 @@ const ScepCard = ({ isAppleMdmOn, isScepOn, router }: IScepCardProps) => { router.push(PATHS.ADMIN_INTEGRATIONS_SCEP); }; - const appleMdmDiabledCard = ( + const appleMdmDisabledCard = ( { } > -

      +

      To help your end users connect to Wi-Fi, you can add your{" "} SCEP server . -

      +
      ); if (!isAppleMdmOn) { - return appleMdmDiabledCard; + return appleMdmDisabledCard; } return isScepOn ? isScepOnCard : isScepOffCard; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/VppSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/VppSection.tsx index c19ccbc4cabf..ed6d64ec6eca 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/VppSection.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/VppSection/VppSection.tsx @@ -23,7 +23,7 @@ const VppCard = ({ isAppleMdmOn, isVppOn, router }: IVppCardProps) => { router.push(PATHS.ADMIN_INTEGRATIONS_VPP_SETUP); }; - const appleMdmDiabledCard = ( + const appleMdmDisabledCard = ( To enable Volume Purchasing Program (VPP), first turn on Apple (macOS, iOS, iPadOS) MDM. @@ -63,7 +63,7 @@ const VppCard = ({ isAppleMdmOn, isVppOn, router }: IVppCardProps) => { ); if (!isAppleMdmOn) { - return appleMdmDiabledCard; + return appleMdmDisabledCard; } return isVppOn ? isVppOnCard : isVppOffCard; From 52b8bdfe8307371e81fffa1b3de0d8fae72383d4 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 11 Oct 2024 16:10:07 -0400 Subject: [PATCH 20/23] remove done TODO --- .../IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx index 48c1d455032b..b85780251916 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -38,7 +38,7 @@ const DEFAULT_ERROR = interface IScepCertificateContentProps { router: InjectedRouter; onFormSubmit: (evt: React.MouseEvent) => Promise; - formData: INdesFormData; // TODO + formData: INdesFormData; formErrors: INdesFormErrors; onInputChange: ({ name, value }: IFormField) => void; config: IConfig | null; From 653e52949a355c82099319339f50ba8c5e624f0a Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 11 Oct 2024 16:26:34 -0400 Subject: [PATCH 21/23] Clean code --- .../IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx index b85780251916..b061e94e37eb 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -294,10 +294,10 @@ const ScepPage = ({ router }: IScepPageProps) => { setFormErrors(newFormErrors); - const areAllFieldsEmpty = Object.values(formData).every( + // Remove when all fields set to empty + const isRemovingNdesScepProxy = Object.values(formData).every( (val) => val === "" ); - const isRemovingNdesScepProxy = areAllFieldsEmpty; if (!isRemovingNdesScepProxy && (!scepUrlValid || !adminUrlValid)) { return; From 221139e259f9019a13bd24aad0c20a3f7c802570 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 11 Oct 2024 16:44:46 -0400 Subject: [PATCH 22/23] Call getErrorReason once --- .../cards/MdmSettings/ScepPage/ScepPage.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx index b061e94e37eb..0f09cf35d1d8 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -330,13 +330,12 @@ const ScepPage = ({ router }: IScepPageProps) => { refetchConfig(); } catch (error) { console.error(error); - if (getErrorReason(error).includes("invalid SCEP URL")) { + const reason = getErrorReason(error); + if (reason.includes("invalid SCEP URL")) { renderFlash("error", BAD_SCEP_URL_ERROR); - } else if ( - getErrorReason(error).includes("invalid admin URL or credentials") - ) { + } else if (reason.includes("invalid admin URL or credentials")) { renderFlash("error", BAD_CREDENTIALS_ERROR); - } else if (getErrorReason(error).includes("the password cache is full")) { + } else if (reason.includes("the password cache is full")) { renderFlash("error", CACHE_ERROR); } else renderFlash("error", DEFAULT_ERROR); } finally { From 2725f3c615f76a38253740fed6d6125f919c7c9c Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 14 Oct 2024 09:25:20 -0400 Subject: [PATCH 23/23] Refactor save button disabled --- .../MdmSettings/ScepPage/ScepPage.tests.tsx | 6 ----- .../cards/MdmSettings/ScepPage/ScepPage.tsx | 23 ++++++------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx index d4c2cd429774..69ae7e1337cc 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx @@ -21,7 +21,6 @@ describe("Scep Page", () => { config={createMockConfig()} isLoading={false} isSaving={false} - saveButtonDisabled={false} showDataError={false} isPremiumTier={false} // test /> @@ -43,7 +42,6 @@ describe("Scep Page", () => { })} isLoading={false} isSaving={false} - saveButtonDisabled={false} showDataError={false} isPremiumTier /> @@ -61,7 +59,6 @@ describe("Scep Page", () => { config={createMockConfig()} isLoading // test isSaving={false} - saveButtonDisabled={false} showDataError={false} isPremiumTier /> @@ -79,7 +76,6 @@ describe("Scep Page", () => { config={createMockConfig()} isLoading={false} isSaving={false} - saveButtonDisabled={false} showDataError // test isPremiumTier /> @@ -97,7 +93,6 @@ describe("Scep Page", () => { config={createMockConfig()} isLoading={false} isSaving={false} - saveButtonDisabled={false} showDataError={false} isPremiumTier /> @@ -125,7 +120,6 @@ describe("Scep Page", () => { config={createMockConfig()} isLoading={false} isSaving={false} - saveButtonDisabled={false} showDataError={false} isPremiumTier /> diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx index 0f09cf35d1d8..21fae064bc95 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx @@ -45,7 +45,6 @@ interface IScepCertificateContentProps { isPremiumTier: boolean; isLoading: boolean; isSaving: boolean; - saveButtonDisabled: boolean; showDataError: boolean; } @@ -59,7 +58,6 @@ export const ScepCertificateContent = ({ isPremiumTier, isLoading, isSaving, - saveButtonDisabled, showDataError, }: IScepCertificateContentProps) => { if (!isPremiumTier) { @@ -89,6 +87,12 @@ export const ScepCertificateContent = ({ ); } + const disableSave = + // all fields aren't empty + !Object.values(formData).every((val) => val === "") && + // all fields aren't complete + !Object.values(formData).every((val) => val !== ""); + return ( <>

      @@ -183,7 +187,7 @@ export const ScepCertificateContent = ({ variant="brand" className="button-wrap" isLoading={isSaving} - disabled={saveButtonDisabled} + disabled={disableSave} > Save @@ -245,7 +249,6 @@ const ScepPage = ({ router }: IScepPageProps) => { }); const [formErrors, setFormErrors] = useState({}); - const [saveButtonDisabled, setSaveButtonDisabled] = useState(true); const [isUpdatingNdesScepProxy, setIsUpdatingNdesScepProxy] = useState(false); const { @@ -260,17 +263,6 @@ const ScepPage = ({ router }: IScepPageProps) => { }, }); - useEffect(() => { - const areAllFieldsEmpty = Object.values(formData).every( - (val) => val === "" - ); - const areAllFieldsComplete = Object.values(formData).every( - (val) => val !== "" - ); - - setSaveButtonDisabled(!areAllFieldsEmpty && !areAllFieldsComplete); - }, [formData]); - const onInputChange = ({ name, value }: IFormField) => { setFormErrors((prev) => ({ ...prev, [name]: null })); setFormData((prev) => ({ ...prev, [name]: value })); @@ -366,7 +358,6 @@ const ScepPage = ({ router }: IScepPageProps) => { isPremiumTier={isPremiumTier || false} isLoading={isLoadingAppConfig} isSaving={isUpdatingNdesScepProxy} - saveButtonDisabled={saveButtonDisabled} showDataError={isErrorAppConfig} />