From 6235a6fcd2ff71645b7da55e581914579288ad06 Mon Sep 17 00:00:00 2001
From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com>
Date: Tue, 30 Dec 2025 17:59:18 +0100
Subject: [PATCH] Report updates
---
.../CippComponents/AuthMethodSankey.jsx | 14 +-
src/components/CippComponents/CaSankey.jsx | 26 +-
src/pages/dashboardv2/index.js | 1153 +++++++++++------
3 files changed, 800 insertions(+), 393 deletions(-)
diff --git a/src/components/CippComponents/AuthMethodSankey.jsx b/src/components/CippComponents/AuthMethodSankey.jsx
index 6ef4e61e666d..200cb4274766 100644
--- a/src/components/CippComponents/AuthMethodSankey.jsx
+++ b/src/components/CippComponents/AuthMethodSankey.jsx
@@ -13,17 +13,21 @@ export const AuthMethodSankey = ({ data }) => {
id: "Single factor",
nodeColor: "hsl(0, 100%, 50%)",
},
+ {
+ id: "Multi factor",
+ nodeColor: "hsl(200, 70%, 50%)",
+ },
{
id: "Phishable",
- nodeColor: "hsl(12, 76%, 61%)",
+ nodeColor: "hsl(39, 100%, 50%)",
},
{
id: "Phone",
- nodeColor: "hsl(12, 76%, 61%)",
+ nodeColor: "hsl(39, 100%, 45%)",
},
{
id: "Authenticator",
- nodeColor: "hsl(12, 76%, 61%)",
+ nodeColor: "hsl(39, 100%, 55%)",
},
{
id: "Phish resistant",
@@ -31,11 +35,11 @@ export const AuthMethodSankey = ({ data }) => {
},
{
id: "Passkey",
- nodeColor: "hsl(99, 70%, 50%)",
+ nodeColor: "hsl(140, 70%, 50%)",
},
{
id: "WHfB",
- nodeColor: "hsl(99, 70%, 50%)",
+ nodeColor: "hsl(160, 70%, 50%)",
},
],
links: data,
diff --git a/src/components/CippComponents/CaSankey.jsx b/src/components/CippComponents/CaSankey.jsx
index 5b860e45dda5..ffad546d8738 100644
--- a/src/components/CippComponents/CaSankey.jsx
+++ b/src/components/CippComponents/CaSankey.jsx
@@ -6,24 +6,32 @@ export const CaSankey = ({ data }) => {
data={{
nodes: [
{
- id: "User sign in",
+ id: "Enabled users",
nodeColor: "hsl(28, 100%, 53%)",
},
{
- id: "No CA applied",
- nodeColor: "hsl(0, 100%, 50%)",
+ id: "MFA registered",
+ nodeColor: "hsl(99, 70%, 50%)",
},
{
- id: "CA applied",
- nodeColor: "hsl(12, 76%, 61%)",
+ id: "Not registered",
+ nodeColor: "hsl(39, 100%, 50%)",
},
{
- id: "No MFA",
- nodeColor: "hsl(0, 69%, 50%)",
+ id: "CA policy",
+ nodeColor: "hsl(99, 70%, 50%)",
},
{
- id: "MFA",
- nodeColor: "hsl(99, 70%, 50%)",
+ id: "Security defaults",
+ nodeColor: "hsl(140, 70%, 50%)",
+ },
+ {
+ id: "Per-user MFA",
+ nodeColor: "hsl(200, 70%, 50%)",
+ },
+ {
+ id: "No enforcement",
+ nodeColor: "hsl(0, 100%, 50%)",
},
],
links: data,
diff --git a/src/pages/dashboardv2/index.js b/src/pages/dashboardv2/index.js
index 90b78b4f2e3d..9dbcf827492c 100644
--- a/src/pages/dashboardv2/index.js
+++ b/src/pages/dashboardv2/index.js
@@ -64,6 +64,211 @@ import {
Work as BriefcaseIcon,
} from "@mui/icons-material";
+// Helper function to process MFAState data into Sankey chart format
+const processMFAStateData = (mfaState) => {
+ if (!mfaState || !Array.isArray(mfaState) || mfaState.length === 0) {
+ return null;
+ }
+
+ // Count enabled users only
+ const enabledUsers = mfaState.filter((user) => user.AccountEnabled === true);
+
+ if (enabledUsers.length === 0) {
+ return null;
+ }
+
+ // Split by MFA registration status
+ let registeredUsers = 0;
+ let notRegisteredUsers = 0;
+
+ // For registered users, split by protection method
+ let registeredCA = 0;
+ let registeredSD = 0;
+ let registeredPerUser = 0;
+ let registeredNone = 0;
+
+ // For not registered users, split by protection method
+ let notRegisteredCA = 0;
+ let notRegisteredSD = 0;
+ let notRegisteredPerUser = 0;
+ let notRegisteredNone = 0;
+
+ enabledUsers.forEach((user) => {
+ const hasRegistered = user.MFARegistration === true;
+ const coveredByCA = user.CoveredByCA?.startsWith("Enforced") || false;
+ const coveredBySD = user.CoveredBySD === true;
+ const perUserEnabled = user.PerUser === "enforced" || user.PerUser === "enabled";
+
+ // Consider PerUser as MFA enabled/registered
+ if (hasRegistered || perUserEnabled) {
+ registeredUsers++;
+ // Per-User gets its own separate terminal path
+ if (perUserEnabled) {
+ registeredPerUser++;
+ } else if (coveredByCA) {
+ registeredCA++;
+ } else if (coveredBySD) {
+ registeredSD++;
+ } else {
+ registeredNone++;
+ }
+ } else {
+ notRegisteredUsers++;
+ if (coveredByCA) {
+ notRegisteredCA++;
+ } else if (coveredBySD) {
+ notRegisteredSD++;
+ } else {
+ notRegisteredNone++;
+ }
+ }
+ });
+
+ const registeredPercentage = ((registeredUsers / enabledUsers.length) * 100).toFixed(1);
+ const protectedPercentage = (
+ ((registeredCA + registeredSD + registeredPerUser) / enabledUsers.length) *
+ 100
+ ).toFixed(1);
+
+ const nodes = [
+ { source: "Enabled users", target: "MFA registered", value: registeredUsers },
+ { source: "Enabled users", target: "Not registered", value: notRegisteredUsers },
+ ];
+
+ // Add protection methods for registered users
+ if (registeredCA > 0)
+ nodes.push({ source: "MFA registered", target: "CA policy", value: registeredCA });
+ if (registeredSD > 0)
+ nodes.push({ source: "MFA registered", target: "Security defaults", value: registeredSD });
+ if (registeredPerUser > 0)
+ nodes.push({ source: "MFA registered", target: "Per-user MFA", value: registeredPerUser });
+ if (registeredNone > 0)
+ nodes.push({ source: "MFA registered", target: "No enforcement", value: registeredNone });
+
+ // Add protection methods for not registered users
+ if (notRegisteredCA > 0)
+ nodes.push({ source: "Not registered", target: "CA policy", value: notRegisteredCA });
+ if (notRegisteredSD > 0)
+ nodes.push({ source: "Not registered", target: "Security defaults", value: notRegisteredSD });
+ if (notRegisteredPerUser > 0)
+ nodes.push({ source: "Not registered", target: "Per-user MFA", value: notRegisteredPerUser });
+ if (notRegisteredNone > 0)
+ nodes.push({ source: "Not registered", target: "No enforcement", value: notRegisteredNone });
+
+ return {
+ description: `${registeredPercentage}% of enabled users have registered MFA methods. ${protectedPercentage}% are protected by policies requiring MFA.`,
+ nodes: nodes,
+ };
+};
+
+// Helper function to process MFAState data into Auth Methods Sankey chart format
+const processAuthMethodsData = (mfaState) => {
+ if (!mfaState || !Array.isArray(mfaState) || mfaState.length === 0) {
+ return null;
+ }
+
+ // Count enabled users only
+ const enabledUsers = mfaState.filter((user) => user.AccountEnabled === true);
+
+ if (enabledUsers.length === 0) {
+ return null;
+ }
+
+ // Categorize MFA methods as phishable or phish-resistant
+ const phishableMethods = ["mobilePhone", "email", "microsoftAuthenticatorPush"];
+ const phishResistantMethods = ["fido2", "windowsHelloForBusiness", "x509Certificate"];
+
+ let singleFactor = 0;
+ let phishableCount = 0;
+ let phishResistantCount = 0;
+ let perUserMFA = 0;
+
+ // Breakdown of phishable methods
+ let phoneCount = 0;
+ let authenticatorCount = 0;
+
+ // Breakdown of phish-resistant methods
+ let passkeyCount = 0;
+ let whfbCount = 0;
+
+ enabledUsers.forEach((user) => {
+ const methods = user.MFAMethods || [];
+ const perUser = user.PerUser === "enforced" || user.PerUser === "enabled";
+ const hasRegistered = user.MFARegistration === true;
+
+ // If user has per-user MFA enforced but no specific methods, count as generic MFA
+ if (perUser && !hasRegistered && methods.length === 0) {
+ perUserMFA++;
+ return;
+ }
+
+ // Check if user has any MFA methods
+ if (!hasRegistered || methods.length === 0) {
+ singleFactor++;
+ return;
+ }
+
+ // Categorize by method type
+ const hasPhishResistant = methods.some((m) => phishResistantMethods.includes(m));
+ const hasPhishable = methods.some((m) => phishableMethods.includes(m));
+
+ if (hasPhishResistant) {
+ phishResistantCount++;
+ // Count specific phish-resistant methods
+ if (methods.includes("fido2") || methods.includes("x509Certificate")) {
+ passkeyCount++;
+ }
+ if (methods.includes("windowsHelloForBusiness")) {
+ whfbCount++;
+ }
+ } else if (hasPhishable) {
+ phishableCount++;
+ // Count specific phishable methods
+ if (methods.includes("mobilePhone") || methods.includes("email")) {
+ phoneCount++;
+ }
+ if (
+ methods.includes("microsoftAuthenticatorPush") ||
+ methods.includes("softwareOneTimePasscode")
+ ) {
+ authenticatorCount++;
+ }
+ } else {
+ // Has MFA methods but not in our categorized lists
+ phishableCount++;
+ authenticatorCount++;
+ }
+ });
+
+ const mfaPercentage = (
+ ((phishableCount + phishResistantCount + perUserMFA) / enabledUsers.length) *
+ 100
+ ).toFixed(1);
+ const phishResistantPercentage = ((phishResistantCount / enabledUsers.length) * 100).toFixed(1);
+
+ const nodes = [
+ { source: "Users", target: "Single factor", value: singleFactor },
+ { source: "Users", target: "Multi factor", value: perUserMFA },
+ { source: "Users", target: "Phishable", value: phishableCount },
+ { source: "Users", target: "Phish resistant", value: phishResistantCount },
+ ];
+
+ // Add phishable method breakdowns
+ if (phoneCount > 0) nodes.push({ source: "Phishable", target: "Phone", value: phoneCount });
+ if (authenticatorCount > 0)
+ nodes.push({ source: "Phishable", target: "Authenticator", value: authenticatorCount });
+
+ // Add phish-resistant method breakdowns
+ if (passkeyCount > 0)
+ nodes.push({ source: "Phish resistant", target: "Passkey", value: passkeyCount });
+ if (whfbCount > 0) nodes.push({ source: "Phish resistant", target: "WHfB", value: whfbCount });
+
+ return {
+ description: `${mfaPercentage}% of enabled users have MFA configured. ${phishResistantPercentage}% use phish-resistant authentication methods.`,
+ nodes: nodes,
+ };
+};
+
const Page = () => {
const settings = useSettings();
const { currentTenant } = settings;
@@ -127,11 +332,11 @@ const Page = () => {
DeviceCount: testsApi.data.TenantCounts.Devices || 0,
ManagedDeviceCount: testsApi.data.TenantCounts.ManagedDevices || 0,
},
- OverviewCaMfaAllUsers: dashboardDemoData.TenantInfo.OverviewCaMfaAllUsers,
+ OverviewCaMfaAllUsers: processMFAStateData(testsApi.data.MFAState),
OverviewCaDevicesAllUsers: dashboardDemoData.TenantInfo.OverviewCaDevicesAllUsers,
OverviewAuthMethodsPrivilegedUsers:
dashboardDemoData.TenantInfo.OverviewAuthMethodsPrivilegedUsers,
- OverviewAuthMethodsAllUsers: dashboardDemoData.TenantInfo.OverviewAuthMethodsAllUsers,
+ OverviewAuthMethodsAllUsers: processAuthMethodsData(testsApi.data.MFAState),
DeviceOverview: dashboardDemoData.TenantInfo.DeviceOverview,
},
}
@@ -269,11 +474,13 @@ const Page = () => {
Name
-
- {organization.isFetching
- ? "Loading..."
- : organization.data?.displayName || "Not Available"}
-
+ {organization.isFetching ? (
+
+ ) : (
+
+ {organization.data?.displayName || "Not Available"}
+
+ )}
@@ -281,9 +488,7 @@ const Page = () => {
{organization.isFetching ? (
-
- Loading...
-
+
) : organization.data?.id ? (
) : (
@@ -299,9 +504,7 @@ const Page = () => {
{organization.isFetching ? (
-
- Loading...
-
+
) : organization.data?.verifiedDomains?.find((d) => d.isDefault)?.name ||
currentTenant ? (
{
/>
- {reportData.SecureScore && reportData.SecureScore.length > 0 ? (
+ {testsApi.isFetching ? (
+
+
+
+ ) : reportData.SecureScore && reportData.SecureScore.length > 0 ? (
{
- {reportData.SecureScore && reportData.SecureScore.length > 0 ? (
+ {testsApi.isFetching ? (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) : reportData.SecureScore && reportData.SecureScore.length > 0 ? (
@@ -831,10 +1052,25 @@ const Page = () => {
/>
- {reportData.TenantInfo.OverviewAuthMethodsAllUsers?.nodes && (
+ {testsApi.isFetching ? (
+
+ ) : reportData.TenantInfo.OverviewAuthMethodsAllUsers?.nodes ? (
+ ) : (
+
+
+ No authentication method data available
+
+
)}
@@ -862,8 +1098,23 @@ const Page = () => {
/>
- {reportData.TenantInfo.OverviewCaMfaAllUsers?.nodes && (
+ {testsApi.isFetching ? (
+
+ ) : reportData.TenantInfo.OverviewCaMfaAllUsers?.nodes ? (
+ ) : (
+
+
+ No MFA data available
+
+
)}
@@ -886,10 +1137,25 @@ const Page = () => {
/>
- {reportData.TenantInfo.OverviewCaDevicesAllUsers?.nodes && (
+ {testsApi.isFetching ? (
+
+ ) : reportData.TenantInfo.OverviewCaDevicesAllUsers?.nodes ? (
+ ) : (
+
+
+ No device sign-in data available
+
+
)}
@@ -919,96 +1185,116 @@ const Page = () => {
sx={{ pb: 1 }}
/>
-
-
-
-
-
-
-
-
-
-
+ {testsApi.isFetching ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+ )}
-
-
-
- Desktops
-
-
- {Math.round(
- ((reportData.TenantInfo.DeviceOverview.ManagedDevices.desktopCount || 0) /
- (reportData.TenantInfo.DeviceOverview.ManagedDevices.totalCount || 1)) *
- 100
- )}
- %
-
+ {testsApi.isFetching ? (
+
+
+
+
+
+
+
+
-
-
-
- Mobiles
-
-
- {Math.round(
- ((reportData.TenantInfo.DeviceOverview.ManagedDevices.mobileCount || 0) /
- (reportData.TenantInfo.DeviceOverview.ManagedDevices.totalCount || 1)) *
- 100
- )}
- %
-
+ ) : (
+
+
+
+ Desktops
+
+
+ {Math.round(
+ ((reportData.TenantInfo.DeviceOverview?.ManagedDevices?.desktopCount ||
+ 0) /
+ (reportData.TenantInfo.DeviceOverview?.ManagedDevices?.totalCount ||
+ 1)) *
+ 100
+ )}
+ %
+
+
+
+
+
+ Mobiles
+
+
+ {Math.round(
+ ((reportData.TenantInfo.DeviceOverview?.ManagedDevices?.mobileCount ||
+ 0) /
+ (reportData.TenantInfo.DeviceOverview?.ManagedDevices?.totalCount ||
+ 1)) *
+ 100
+ )}
+ %
+
+
-
+ )}
@@ -1026,86 +1312,104 @@ const Page = () => {
sx={{ pb: 1 }}
/>
-
-
-
-
-
-
+ {testsApi.isFetching ? (
+
+ ) : (
+
+
+
+
+
+
+ )}
-
-
-
-
-
- Compliant
-
+ {testsApi.isFetching ? (
+
+
+
+
+
+
+
-
- {Math.round(
- (reportData.TenantInfo.DeviceOverview.DeviceCompliance
- .compliantDeviceCount /
- (reportData.TenantInfo.DeviceOverview.DeviceCompliance
- .compliantDeviceCount +
- reportData.TenantInfo.DeviceOverview.DeviceCompliance
- .nonCompliantDeviceCount)) *
- 100
- )}
- %
-
-
-
-
-
-
- Non-compliant
+ ) : (
+
+
+
+
+
+ Compliant
+
+
+
+ {(() => {
+ const compliant =
+ reportData.TenantInfo.DeviceOverview?.DeviceCompliance
+ ?.compliantDeviceCount || 0;
+ const nonCompliant =
+ reportData.TenantInfo.DeviceOverview?.DeviceCompliance
+ ?.nonCompliantDeviceCount || 0;
+ const total = compliant + nonCompliant;
+ return total > 0 ? Math.round((compliant / total) * 100) : 0;
+ })()}
+ %
+
+
+
+
+
+
+
+ Non-compliant
+
+
+
+ {(() => {
+ const compliant =
+ reportData.TenantInfo.DeviceOverview?.DeviceCompliance
+ ?.compliantDeviceCount || 0;
+ const nonCompliant =
+ reportData.TenantInfo.DeviceOverview?.DeviceCompliance
+ ?.nonCompliantDeviceCount || 0;
+ const total = compliant + nonCompliant;
+ return total > 0 ? Math.round((nonCompliant / total) * 100) : 0;
+ })()}
+ %
-
- {Math.round(
- (reportData.TenantInfo.DeviceOverview.DeviceCompliance
- .nonCompliantDeviceCount /
- (reportData.TenantInfo.DeviceOverview.DeviceCompliance
- .compliantDeviceCount +
- reportData.TenantInfo.DeviceOverview.DeviceCompliance
- .nonCompliantDeviceCount)) *
- 100
- )}
- %
-
-
+ )}
@@ -1123,78 +1427,104 @@ const Page = () => {
sx={{ pb: 1 }}
/>
-
-
-
-
-
-
+ {testsApi.isFetching ? (
+
+ ) : (
+
+
+
+
+
+
+ )}
-
-
-
-
-
- Corporate
-
+ {testsApi.isFetching ? (
+
+
+
+
+
+
+
-
- {Math.round(
- (reportData.TenantInfo.DeviceOverview.DeviceOwnership.corporateCount /
- (reportData.TenantInfo.DeviceOverview.DeviceOwnership.corporateCount +
- reportData.TenantInfo.DeviceOverview.DeviceOwnership.personalCount)) *
- 100
- )}
- %
-
-
-
-
-
-
- Personal
+ ) : (
+
+
+
+
+
+ Corporate
+
+
+
+ {(() => {
+ const corporate =
+ reportData.TenantInfo.DeviceOverview?.DeviceOwnership
+ ?.corporateCount || 0;
+ const personal =
+ reportData.TenantInfo.DeviceOverview?.DeviceOwnership
+ ?.personalCount || 0;
+ const total = corporate + personal;
+ return total > 0 ? Math.round((corporate / total) * 100) : 0;
+ })()}
+ %
+
+
+
+
+
+
+
+ Personal
+
+
+
+ {(() => {
+ const corporate =
+ reportData.TenantInfo.DeviceOverview?.DeviceOwnership
+ ?.corporateCount || 0;
+ const personal =
+ reportData.TenantInfo.DeviceOverview?.DeviceOwnership
+ ?.personalCount || 0;
+ const total = corporate + personal;
+ return total > 0 ? Math.round((personal / total) * 100) : 0;
+ })()}
+ %
-
- {Math.round(
- (reportData.TenantInfo.DeviceOverview.DeviceOwnership.personalCount /
- (reportData.TenantInfo.DeviceOverview.DeviceOwnership.corporateCount +
- reportData.TenantInfo.DeviceOverview.DeviceOwnership.personalCount)) *
- 100
- )}
- %
-
-
+ )}
@@ -1213,10 +1543,25 @@ const Page = () => {
/>
- {reportData.TenantInfo.DeviceOverview.DesktopDevicesSummary?.nodes && (
+ {testsApi.isFetching ? (
+
+ ) : reportData.TenantInfo.DeviceOverview?.DesktopDevicesSummary?.nodes ? (
+ ) : (
+
+
+ No desktop device data available
+
+
)}
@@ -1226,82 +1571,101 @@ const Page = () => {
-
-
-
- Entra joined
-
-
- {(() => {
- const nodes =
- reportData.TenantInfo.DeviceOverview.DesktopDevicesSummary?.nodes || [];
- const entraJoined =
- nodes.find((n) => n.target === "Entra joined")?.value || 0;
- const windowsDevices =
- nodes.find(
- (n) => n.source === "Desktop devices" && n.target === "Windows"
- )?.value || 0;
- const macOSDevices =
- nodes.find(
- (n) => n.source === "Desktop devices" && n.target === "macOS"
- )?.value || 0;
- const total = windowsDevices + macOSDevices;
- return Math.round((entraJoined / (total || 1)) * 100);
- })()}
- %
-
-
-
-
-
- Entra hybrid joined
-
-
- {(() => {
- const nodes =
- reportData.TenantInfo.DeviceOverview.DesktopDevicesSummary?.nodes || [];
- const entraHybrid =
- nodes.find((n) => n.target === "Entra hybrid joined")?.value || 0;
- const windowsDevices =
- nodes.find(
- (n) => n.source === "Desktop devices" && n.target === "Windows"
- )?.value || 0;
- const macOSDevices =
- nodes.find(
- (n) => n.source === "Desktop devices" && n.target === "macOS"
- )?.value || 0;
- const total = windowsDevices + macOSDevices;
- return Math.round((entraHybrid / (total || 1)) * 100);
- })()}
- %
-
+ {testsApi.isFetching ? (
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- Entra registered
-
-
- {(() => {
- const nodes =
- reportData.TenantInfo.DeviceOverview.DesktopDevicesSummary?.nodes || [];
- const entraRegistered =
- nodes.find((n) => n.target === "Entra registered")?.value || 0;
- const windowsDevices =
- nodes.find(
- (n) => n.source === "Desktop devices" && n.target === "Windows"
- )?.value || 0;
- const macOSDevices =
- nodes.find(
- (n) => n.source === "Desktop devices" && n.target === "macOS"
- )?.value || 0;
- const total = windowsDevices + macOSDevices;
- return Math.round((entraRegistered / (total || 1)) * 100);
- })()}
- %
-
+ ) : (
+
+
+
+ Entra joined
+
+
+ {(() => {
+ const nodes =
+ reportData.TenantInfo.DeviceOverview?.DesktopDevicesSummary?.nodes ||
+ [];
+ const entraJoined =
+ nodes.find((n) => n.target === "Entra joined")?.value || 0;
+ const windowsDevices =
+ nodes.find(
+ (n) => n.source === "Desktop devices" && n.target === "Windows"
+ )?.value || 0;
+ const macOSDevices =
+ nodes.find(
+ (n) => n.source === "Desktop devices" && n.target === "macOS"
+ )?.value || 0;
+ const total = windowsDevices + macOSDevices;
+ return Math.round((entraJoined / (total || 1)) * 100);
+ })()}
+ %
+
+
+
+
+
+ Entra hybrid joined
+
+
+ {(() => {
+ const nodes =
+ reportData.TenantInfo.DeviceOverview?.DesktopDevicesSummary?.nodes ||
+ [];
+ const entraHybrid =
+ nodes.find((n) => n.target === "Entra hybrid joined")?.value || 0;
+ const windowsDevices =
+ nodes.find(
+ (n) => n.source === "Desktop devices" && n.target === "Windows"
+ )?.value || 0;
+ const macOSDevices =
+ nodes.find(
+ (n) => n.source === "Desktop devices" && n.target === "macOS"
+ )?.value || 0;
+ const total = windowsDevices + macOSDevices;
+ return Math.round((entraHybrid / (total || 1)) * 100);
+ })()}
+ %
+
+
+
+
+
+ Entra registered
+
+
+ {(() => {
+ const nodes =
+ reportData.TenantInfo.DeviceOverview?.DesktopDevicesSummary?.nodes ||
+ [];
+ const entraRegistered =
+ nodes.find((n) => n.target === "Entra registered")?.value || 0;
+ const windowsDevices =
+ nodes.find(
+ (n) => n.source === "Desktop devices" && n.target === "Windows"
+ )?.value || 0;
+ const macOSDevices =
+ nodes.find(
+ (n) => n.source === "Desktop devices" && n.target === "macOS"
+ )?.value || 0;
+ const total = windowsDevices + macOSDevices;
+ return Math.round((entraRegistered / (total || 1)) * 100);
+ })()}
+ %
+
+
-
+ )}
@@ -1320,10 +1684,25 @@ const Page = () => {
/>
- {reportData.TenantInfo.DeviceOverview.MobileSummary?.nodes && (
+ {testsApi.isFetching ? (
+
+ ) : reportData.TenantInfo.DeviceOverview?.MobileSummary?.nodes ? (
+ ) : (
+
+
+ No mobile device data available
+
+
)}
@@ -1333,72 +1712,88 @@ const Page = () => {
-
-
-
- Android compliant
-
-
- {(() => {
- const nodes =
- reportData.TenantInfo.DeviceOverview.MobileSummary?.nodes || [];
- const androidCompliant = nodes
- .filter(
- (n) => n.source?.includes("Android") && n.target === "Compliant"
- )
- .reduce((sum, n) => sum + (n.value || 0), 0);
- const androidTotal =
- nodes.find(
- (n) => n.source === "Mobile devices" && n.target === "Android"
- )?.value || 0;
- return androidTotal > 0
- ? Math.round((androidCompliant / androidTotal) * 100)
- : 0;
- })()}
- %
-
-
-
-
-
- iOS compliant
-
-
- {(() => {
- const nodes =
- reportData.TenantInfo.DeviceOverview.MobileSummary?.nodes || [];
- const iosCompliant = nodes
- .filter((n) => n.source?.includes("iOS") && n.target === "Compliant")
- .reduce((sum, n) => sum + (n.value || 0), 0);
- const iosTotal =
- nodes.find((n) => n.source === "Mobile devices" && n.target === "iOS")
- ?.value || 0;
- return iosTotal > 0 ? Math.round((iosCompliant / iosTotal) * 100) : 0;
- })()}
- %
-
+ {testsApi.isFetching ? (
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- Total devices
-
-
- {(() => {
- const nodes =
- reportData.TenantInfo.DeviceOverview.MobileSummary?.nodes || [];
- const androidTotal =
- nodes.find(
- (n) => n.source === "Mobile devices" && n.target === "Android"
- )?.value || 0;
- const iosTotal =
- nodes.find((n) => n.source === "Mobile devices" && n.target === "iOS")
- ?.value || 0;
- return androidTotal + iosTotal;
- })()}
-
+ ) : (
+
+
+
+ Android compliant
+
+
+ {(() => {
+ const nodes =
+ reportData.TenantInfo.DeviceOverview?.MobileSummary?.nodes || [];
+ const androidCompliant = nodes
+ .filter(
+ (n) => n.source?.includes("Android") && n.target === "Compliant"
+ )
+ .reduce((sum, n) => sum + (n.value || 0), 0);
+ const androidTotal =
+ nodes.find(
+ (n) => n.source === "Mobile devices" && n.target === "Android"
+ )?.value || 0;
+ return androidTotal > 0
+ ? Math.round((androidCompliant / androidTotal) * 100)
+ : 0;
+ })()}
+ %
+
+
+
+
+
+ iOS compliant
+
+
+ {(() => {
+ const nodes =
+ reportData.TenantInfo.DeviceOverview?.MobileSummary?.nodes || [];
+ const iosCompliant = nodes
+ .filter((n) => n.source?.includes("iOS") && n.target === "Compliant")
+ .reduce((sum, n) => sum + (n.value || 0), 0);
+ const iosTotal =
+ nodes.find((n) => n.source === "Mobile devices" && n.target === "iOS")
+ ?.value || 0;
+ return iosTotal > 0 ? Math.round((iosCompliant / iosTotal) * 100) : 0;
+ })()}
+ %
+
+
+
+
+
+ Total devices
+
+
+ {(() => {
+ const nodes =
+ reportData.TenantInfo.DeviceOverview?.MobileSummary?.nodes || [];
+ const androidTotal =
+ nodes.find(
+ (n) => n.source === "Mobile devices" && n.target === "Android"
+ )?.value || 0;
+ const iosTotal =
+ nodes.find((n) => n.source === "Mobile devices" && n.target === "iOS")
+ ?.value || 0;
+ return androidTotal + iosTotal;
+ })()}
+
+
-
+ )}