Add UI banner when VPP token is about to expire/has expired (#20734)

relate to #19691

Adds a UI banner that conditionally shows on every page (excluding my
device page) if the VPP token that was uploaded is within 30 days of
expiring. It will also show a warning banner if the token has already
expired.

**Token about to expire:**


![image](https://github.com/user-attachments/assets/f82a6cf5-405a-4911-87fe-1a354bfadee3)

**Token has expired:**


![image](https://github.com/user-attachments/assets/87bc51b9-f948-4d5b-835a-ff81116b0fa1)

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
- [ ] Added/updated tests
- [c] Manual QA for all new/changed functionality
This commit is contained in:
Gabriel Hernandez
2024-07-26 15:39:27 +01:00
committed by GitHub
parent b32853ad5b
commit e07313c7de
12 changed files with 117 additions and 47 deletions
@@ -0,0 +1 @@
- add a warning banner to the UI if the uploaded VPP token is about to expire/has expired.
+7
View File
@@ -66,6 +66,7 @@ const App = ({ children, location }: IAppProps): JSX.Element => {
setEnrollSecret,
setABMExpiry,
setAPNsExpiry,
setVppExpiry,
setSandboxExpiry,
setNoSandboxHosts,
} = useContext(AppContext);
@@ -111,6 +112,12 @@ const App = ({ children, location }: IAppProps): JSX.Element => {
} catch (error) {
console.error(error);
}
try {
const vppInfo = await mdmAppleAPI.getVppInfo();
setVppExpiry(vppInfo.renew_date);
} catch (e) {
console.log(e);
}
}
}
@@ -87,7 +87,11 @@ const InfoBanner = ({
);
}
return <div className={wrapperClasses}>{content}</div>;
return (
<div className={wrapperClasses} role="status">
{content}
</div>
);
};
export default InfoBanner;
@@ -1,5 +0,0 @@
.license-expiry-banner {
margin-bottom: $pad-large;
position: sticky; // Needed for settings page scroll
z-index: 4; // Needed for settings page scroll
}
@@ -1,5 +0,0 @@
.apple-bm-renewal-message {
margin-bottom: $pad-large;
position: sticky; // Needed for settings page scroll
z-index: 4; // Needed for settings page scroll
}
@@ -1,5 +0,0 @@
.apple-bm-terms-message {
margin-bottom: $pad-large;
position: sticky; // Needed for settings page scroll
z-index: 4; // Needed for settings page scroll
}
@@ -1,5 +0,0 @@
.apple-pn-cert-renewal-message {
margin-bottom: $pad-large;
position: sticky; // Needed for settings page scroll
z-index: 4; // Needed for settings page scroll
}
+27 -26
View File
@@ -11,6 +11,7 @@ import { AppContext } from "context/app";
import LicenseExpirationBanner from "components/LicenseExpirationBanner";
import ApplePNCertRenewalMessage from "components/MDM/ApplePNCertRenewalMessage";
import AppleBMRenewalMessage from "components/MDM/AppleBMRenewalMessage";
import VppRenewalMessage from "./banners/VppRenewalMessage";
interface IMainContentProps {
children: ReactNode;
@@ -36,8 +37,9 @@ const MainContent = ({
config,
isPremiumTier,
noSandboxHosts,
apnsExpiry,
abmExpiry,
apnsExpiry = "",
abmExpiry = "",
vppExpiry = "",
} = useContext(AppContext);
const sandboxExpiryTime =
@@ -47,39 +49,38 @@ const MainContent = ({
const renderAppWideBanner = () => {
const isAppleBmTermsExpired = config?.mdm?.apple_bm_terms_expired;
const isApplePnsExpired = hasLicenseExpired(apnsExpiry || "");
const willApplePnsExpireIn30Days = willExpireWithinXDays(
apnsExpiry || "",
30
);
const isAppleBmExpired = hasLicenseExpired(abmExpiry || ""); // NOTE: See Rachel's related FIXME added to App.tsx in https://github.com/fleetdm/fleet/pull/19571
const willAppleBmExpireIn30Days = willExpireWithinXDays(
abmExpiry || "",
30
);
const isApplePnsExpired = hasLicenseExpired(apnsExpiry);
const willApplePnsExpireIn30Days = willExpireWithinXDays(apnsExpiry, 30);
const isAppleBmExpired = hasLicenseExpired(abmExpiry); // NOTE: See Rachel's related FIXME added to App.tsx in https://github.com/fleetdm/fleet/pull/19571
const willAppleBmExpireIn30Days = willExpireWithinXDays(abmExpiry, 30);
const isFleetLicenseExpired = hasLicenseExpired(
config?.license.expiration || ""
);
const isVppExpired = hasLicenseExpired(vppExpiry);
const willVppExpireIn30Days = willExpireWithinXDays(vppExpiry, 30);
let banner: JSX.Element | null = null;
if (isPremiumTier) {
if (isApplePnsExpired || willApplePnsExpireIn30Days) {
return <ApplePNCertRenewalMessage expired={isApplePnsExpired} />;
}
if (isAppleBmExpired || willAppleBmExpireIn30Days) {
return <AppleBMRenewalMessage expired={isAppleBmExpired} />;
}
if (isAppleBmTermsExpired) {
return <AppleBMTermsMessage />;
}
if (isFleetLicenseExpired) {
return <LicenseExpirationBanner />;
banner = <ApplePNCertRenewalMessage expired={isApplePnsExpired} />;
} else if (isAppleBmExpired || willAppleBmExpireIn30Days) {
banner = <AppleBMRenewalMessage expired={isAppleBmExpired} />;
} else if (isAppleBmTermsExpired) {
banner = <AppleBMTermsMessage />;
} else if (isFleetLicenseExpired) {
banner = <LicenseExpirationBanner />;
} else if (isVppExpired || willVppExpireIn30Days) {
banner = <VppRenewalMessage expired={isVppExpired} />;
}
}
return <></>;
if (banner) {
return <div className={`${baseClass}__warning-banner`}>{banner}</div>;
}
return null;
};
return (
<div className={classes}>
@@ -10,4 +10,11 @@
> :not(.main-content--animation-disabled) {
animation: fade-in 250ms ease-out;
}
&__warning-banner {
margin-bottom: $pad-large;
// TODO: figure out if sticky styling is needed?
position: sticky; // Needed for settings page scroll
z-index: 4; // Needed for settings page scroll
}
}
@@ -0,0 +1,44 @@
import React from "react";
import CustomLink from "components/CustomLink";
import InfoBanner from "components/InfoBanner";
const baseClass = "vpp-renewal-message";
interface IVppRenewalMessageProps {
expired: boolean;
}
const VppRenewalMessage = ({ expired }: IVppRenewalMessageProps) => {
return (
<InfoBanner
className={baseClass}
color="yellow"
cta={
<CustomLink
url="https://fleetdm.com/learn-more-about/renew-vpp"
text="Renew VPP"
className={`${baseClass}__new-tab`}
newTab
color="core-fleet-black"
iconColor="core-fleet-black"
/>
}
>
{expired ? (
<>
Your Volume Purchasing Program (VPP) content token has expired. You
can&apos;t add or install App Store apps.
</>
) : (
<>
Your Volume Purchasing Program (VPP) content token is less than 30
days from expiration. If it expires, you won&apos;t be able to add or
install App Store apps.
</>
)}
</InfoBanner>
);
};
export default VppRenewalMessage;
@@ -0,0 +1 @@
export { default } from "./VppRenewalMessage";
+25
View File
@@ -20,6 +20,7 @@ enum ACTIONS {
SET_ENROLL_SECRET = "SET_ENROLL_SECRET",
SET_ABM_EXPIRY = "SET_ABM_EXPIRY",
SET_APNS_EXPIRY = "SET_APNS_EXPIRY",
SET_VPP_EXPIRY = "SET_VPP_EXPIRY",
SET_SANDBOX_EXPIRY = "SET_SANDBOX_EXPIRY",
SET_NO_SANDBOX_HOSTS = "SET_NO_SANDBOX_HOSTS",
SET_FILTERED_HOSTS_PATH = "SET_FILTERED_HOSTS_PATH",
@@ -62,6 +63,11 @@ interface ISetAPNsExpiryAction {
apnsExpiry: string;
}
interface ISetVppExpiryAction {
type: ACTIONS.SET_VPP_EXPIRY;
vppExpiry: string;
}
interface ISetSandboxExpiryAction {
type: ACTIONS.SET_SANDBOX_EXPIRY;
sandboxExpiry: string;
@@ -99,6 +105,7 @@ type IAction =
| ISetEnrollSecretAction
| ISetABMExpiryAction
| ISetAPNsExpiryAction
| ISetVppExpiryAction
| ISetSandboxExpiryAction
| ISetNoSandboxHostsAction
| ISetFilteredHostsPathAction
@@ -139,12 +146,14 @@ type InitialStateType = {
isNoAccess?: boolean;
abmExpiry?: string;
apnsExpiry?: string;
vppExpiry?: string;
sandboxExpiry?: string;
noSandboxHosts?: boolean;
filteredHostsPath?: string;
filteredSoftwarePath?: string;
filteredQueriesPath?: string;
filteredPoliciesPath?: string;
isVppEnabled?: boolean;
setAvailableTeams: (
user: IUser | null,
availableTeams: ITeamSummary[]
@@ -155,6 +164,7 @@ type InitialStateType = {
setEnrollSecret: (enrollSecret: IEnrollSecret[]) => void;
setAPNsExpiry: (apnsExpiry: string) => void;
setABMExpiry: (abmExpiry: string) => void;
setVppExpiry: (vppExpiry: string) => void;
setSandboxExpiry: (sandboxExpiry: string) => void;
setNoSandboxHosts: (noSandboxHosts: boolean) => void;
setFilteredHostsPath: (filteredHostsPath: string) => void;
@@ -203,6 +213,7 @@ export const initialState = {
setEnrollSecret: () => null,
setAPNsExpiry: () => null,
setABMExpiry: () => null,
setVppExpiry: () => null,
setSandboxExpiry: () => null,
setNoSandboxHosts: () => null,
setFilteredHostsPath: () => null,
@@ -337,6 +348,13 @@ const reducer = (state: InitialStateType, action: IAction) => {
apnsExpiry,
};
}
case ACTIONS.SET_VPP_EXPIRY: {
const { vppExpiry } = action;
return {
...state,
vppExpiry,
};
}
case ACTIONS.SET_SANDBOX_EXPIRY: {
const { sandboxExpiry } = action;
return {
@@ -399,6 +417,7 @@ const AppProvider = ({ children }: Props): JSX.Element => {
sandboxExpiry: state.sandboxExpiry,
abmExpiry: state.abmExpiry,
apnsExpiry: state.apnsExpiry,
vppExpiry: state.vppExpiry,
noSandboxHosts: state.noSandboxHosts,
filteredHostsPath: state.filteredHostsPath,
filteredSoftwarePath: state.filteredSoftwarePath,
@@ -450,6 +469,12 @@ const AppProvider = ({ children }: Props): JSX.Element => {
setAPNsExpiry: (apnsExpiry: string) => {
dispatch({ type: ACTIONS.SET_APNS_EXPIRY, apnsExpiry });
},
setVppExpiry: (vppExpiry: string) => {
dispatch({
type: ACTIONS.SET_VPP_EXPIRY,
vppExpiry,
});
},
setSandboxExpiry: (sandboxExpiry: string) => {
dispatch({ type: ACTIONS.SET_SANDBOX_EXPIRY, sandboxExpiry });
},