diff --git a/src/components/CippCards/CippBannerListCard.jsx b/src/components/CippCards/CippBannerListCard.jsx
index a0bab7612a02..aa7fc42a8bca 100644
--- a/src/components/CippCards/CippBannerListCard.jsx
+++ b/src/components/CippCards/CippBannerListCard.jsx
@@ -16,7 +16,7 @@ import { CippPropertyListCard } from "./CippPropertyListCard";
import { CippDataTable } from "../CippTable/CippDataTable";
export const CippBannerListCard = (props) => {
- const { items = [], isCollapsible = false, isFetching = false, ...other } = props;
+ const { items = [], isCollapsible = false, isFetching = false, children, ...other } = props;
const [expanded, setExpanded] = useState(null);
const handleExpand = useCallback((itemId) => {
@@ -145,14 +145,18 @@ export const CippBannerListCard = (props) => {
{isCollapsible && (
- {item?.propertyItems?.length > 0 && (
-
- )}
- {item?.table && }
+
+ {item?.propertyItems?.length > 0 && (
+
+ )}
+ {item?.table && }
+ {item?.children && {item.children}}
+ {item?.actionButton && {item.actionButton}}
+
)}
@@ -180,8 +184,12 @@ CippBannerListCard.propTypes = {
subtext: PropTypes.string,
statusColor: PropTypes.string,
statusText: PropTypes.string,
+ actionButton: PropTypes.element,
propertyItems: PropTypes.array,
+ table: PropTypes.object,
+ actionButton: PropTypes.element,
isFetching: PropTypes.bool,
+ children: PropTypes.node,
})
).isRequired,
isCollapsible: PropTypes.bool,
diff --git a/src/components/CippComponents/CippLocationDialog.jsx b/src/components/CippComponents/CippLocationDialog.jsx
new file mode 100644
index 000000000000..9970524ed681
--- /dev/null
+++ b/src/components/CippComponents/CippLocationDialog.jsx
@@ -0,0 +1,52 @@
+import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from "@mui/material";
+import { useState } from "react";
+import dynamic from "next/dynamic"; // Import dynamic from next/dynamic
+import { CippPropertyList } from "./CippPropertyList"; // Import CippPropertyList
+import { LocationOn } from "@mui/icons-material";
+
+const CippMap = dynamic(() => import("./CippMap"), { ssr: false }); // Dynamic import for CippMap
+
+export const CippLocationDialog = ({ location }) => {
+ const [open, setOpen] = useState(false);
+
+ const handleOpen = () => {
+ setOpen(true);
+ };
+
+ const handleClose = () => {
+ setOpen(false);
+ };
+
+ const markers = [
+ {
+ position: [location.geoCoordinates.latitude, location.geoCoordinates.longitude],
+ popup: `${location.city}, ${location.state}, ${location.countryOrRegion}`,
+ },
+ ];
+
+ const properties = [
+ { label: "City", value: location.city },
+ { label: "State", value: location.state },
+ { label: "Country/Region", value: location.countryOrRegion },
+ ];
+
+ return (
+ <>
+ }>
+ Show Map
+
+
+ >
+ );
+};
diff --git a/src/components/CippTable/util-columnsFromAPI.js b/src/components/CippTable/util-columnsFromAPI.js
index 886a6f1d258b..e783ebe16133 100644
--- a/src/components/CippTable/util-columnsFromAPI.js
+++ b/src/components/CippTable/util-columnsFromAPI.js
@@ -30,12 +30,17 @@ const mergeKeys = (dataArray) => {
export const utilColumnsFromAPI = (dataArray) => {
const dataSample = mergeKeys(dataArray);
-
+ const skipRecursion = ["location"];
const generateColumns = (obj, parentKey = "") => {
return Object.keys(obj)
.map((key) => {
const accessorKey = parentKey ? `${parentKey}.${key}` : key;
- if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])) {
+ if (
+ typeof obj[key] === "object" &&
+ obj[key] !== null &&
+ !Array.isArray(obj[key]) &&
+ !skipRecursion.includes(key)
+ ) {
return generateColumns(obj[key], accessorKey);
}
diff --git a/src/data/signinErrorCodes.json b/src/data/signinErrorCodes.json
new file mode 100644
index 000000000000..a4d279753b9a
--- /dev/null
+++ b/src/data/signinErrorCodes.json
@@ -0,0 +1,17 @@
+{
+ "0": "Success",
+ "50126": "Invalid username or password",
+ "70044": "The session has expired or is invalid due to sign-in frequency checks by conditional access",
+ "50089": "Flow token expired",
+ "53003": "Access has been blocked by Conditional Access policies",
+ "50140": "This error occurred due to 'Keep me signed in' interrupt when the user was signing-in",
+ "50097": "Device authentication required",
+ "65001": "Application X doesn't have permission to access application Y or the permission has been revoked",
+ "50053": "Account is locked because user tried to sign in too many times with an incorrect user ID or password",
+ "50020": "The user is unauthorized",
+ "50125": "Sign-in was interrupted due to a password reset or password registration entry",
+ "50074": "User did not pass the MFA challenge",
+ "50133": "Session is invalid due to expiration or recent password change",
+ "530002": "Your device is required to be compliant to access this resource",
+ "9001011": "Device policy contains unsupported required device state"
+}
\ No newline at end of file
diff --git a/src/pages/identity/administration/users/user/exchange.jsx b/src/pages/identity/administration/users/user/exchange.jsx
index a9d325f8f35a..bef693434d29 100644
--- a/src/pages/identity/administration/users/user/exchange.jsx
+++ b/src/pages/identity/administration/users/user/exchange.jsx
@@ -18,6 +18,10 @@ import CippExchangeSettingsForm from "../../../../../components/CippFormPages/Ci
import { useForm } from "react-hook-form";
import { Alert, Button, Collapse, CircularProgress, Typography } from "@mui/material";
import { CippApiResults } from "../../../../../components/CippComponents/CippApiResults";
+import { TrashIcon } from "@heroicons/react/24/outline";
+import { CippPropertyListCard } from "../../../../../components/CippCards/CippPropertyListCard";
+import { getCippTranslation } from "../../../../../utils/get-cipp-translation";
+import { getCippFormatting } from "../../../../../utils/get-cipp-formatting";
const Page = () => {
const userSettingsDefaults = useSettings();
@@ -55,6 +59,12 @@ const Page = () => {
waiting: waiting,
});
+ const mailboxRulesRequest = ApiGetCall({
+ url: `/api/ListUserMailboxRules?UserId=${userId}&tenantFilter=${userSettingsDefaults.currentTenant}`,
+ queryKey: `MailboxRules-${userId}`,
+ waiting: waiting,
+ });
+
useEffect(() => {
if (oooRequest.isSuccess) {
formControl.setValue("ooo.ExternalMessage", oooRequest.data?.ExternalMessage);
@@ -156,6 +166,79 @@ const Page = () => {
})) || [],
},
];
+
+ const mailboxRuleActions = [
+ {
+ label: "Remove Mailbox Rule",
+ type: "GET",
+ icon: ,
+ url: "/api/ExecRemoveMailboxRule",
+ data: { ruleId: "Identity", userPrincipalName: "UserPrincipalName" },
+ confirmText: "Are you sure you want to remove this mailbox rule?",
+ multiPost: false,
+ },
+ ];
+
+ const mailboxRulesCard = [
+ {
+ id: 1,
+ cardLabelBox: {
+ cardLabelBoxHeader: mailboxRulesRequest.isFetching ? (
+
+ ) : mailboxRulesRequest.data?.length !== 0 ? (
+
+ ) : (
+
+ ),
+ },
+ text: "Current Mailbox Rules",
+ subtext: mailboxRulesRequest.data?.length
+ ? "Mailbox rules are configured for this user"
+ : "No mailbox rules configured for this user",
+ statusColor: "green.main",
+ table: {
+ title: "Mailbox Rules",
+ hideTitle: true,
+ data: mailboxRulesRequest.data || [],
+ simpleColumns: [
+ "Enabled",
+ "Name",
+ "Description",
+ "Redirect To",
+ "Copy To Folder",
+ "Move To Folder",
+ "Soft Delete Message",
+ "Delete Message",
+ ],
+ actions: mailboxRuleActions,
+ offCanvas: {
+ children: (data) => {
+ const keys = Object.keys(data).filter(
+ (key) => !key.includes("@odata") && !key.includes("@data")
+ );
+ const properties = [];
+ keys.forEach((key) => {
+ if (data[key] && data[key].length > 0) {
+ properties.push({
+ label: getCippTranslation(key),
+ value: getCippFormatting(data[key], key),
+ });
+ }
+ });
+ return (
+
+ );
+ },
+ },
+ },
+ },
+ ];
+
return (
{
items={calCard}
isCollapsible={calPermissions.data?.length !== 0}
/>
+
import("/src/components/CippComponents/CippMap"), { ssr: false });
+
+import { Button, Dialog, DialogTitle, DialogContent, IconButton } from "@mui/material";
+import { Close } from "@mui/icons-material";
+import { CippPropertyList } from "../../../../../components/CippComponents/CippPropertyList";
+
+const SignInLogsDialog = ({ open, onClose, userId, tenantFilter }) => {
+ return (
+
+ );
+};
const Page = () => {
- const popover = usePopover();
- const createDialog = useDialog();
const userSettingsDefaults = useSettings();
const router = useRouter();
const { userId } = router.query;
const [waiting, setWaiting] = useState(false);
+ const [signInLogsDialogOpen, setSignInLogsDialogOpen] = useState(false);
+
useEffect(() => {
if (userId) {
setWaiting(true);
@@ -112,6 +158,20 @@ const Page = () => {
subtext: `Logged into application ${signInData.resourceDisplayName || "Unknown Application"}`,
statusColor: signInData.status.errorCode === 0 ? "success.main" : "error.main",
statusText: signInData.status.errorCode === 0 ? "Success" : "Failed",
+ actionButton: (
+
+ ),
propertyItems: [
{
label: "Client App Used",
@@ -131,6 +191,39 @@ const Page = () => {
value: signInData.status?.additionalDetails || "N/A",
},
],
+ children: (
+ <>
+ {signInData?.location && (
+ <>
+ Location
+
+
+
+
+
+
+
+
+ >
+ )}
+ >
+ ),
};
// Prepare the conditional access policies items
@@ -459,6 +552,12 @@ const Page = () => {
)}
+ setSignInLogsDialogOpen(false)}
+ userId={userId}
+ tenantFilter={userSettingsDefaults.currentTenant}
+ />
);
};
diff --git a/src/pages/identity/reports/signin-report/index.js b/src/pages/identity/reports/signin-report/index.js
index 4f89e7a4b0b4..6304b8fa0da6 100644
--- a/src/pages/identity/reports/signin-report/index.js
+++ b/src/pages/identity/reports/signin-report/index.js
@@ -1,5 +1,8 @@
import { Layout as DashboardLayout } from "/src/layouts/index.js";
import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx";
+import { useState } from "react";
+import { Button, Grid, TextField, Switch, FormControlLabel } from "@mui/material";
+import CippButtonCard from "/src/components/CippCards/CippButtonCard";
const Page = () => {
const pageTitle = "Sign Ins Report";
@@ -16,14 +19,93 @@ const Page = () => {
"locationcipp",
];
+ const [filterValues, setFilterValues] = useState({
+ Days: 7,
+ filter: "",
+ failedLogonsOnly: false,
+ FailureThreshold: 0,
+ });
+
+ const [appliedFilters, setAppliedFilters] = useState(filterValues);
+
+ const handleFilterChange = (event) => {
+ const { name, value, type, checked } = event.target;
+ setFilterValues({
+ ...filterValues,
+ [name]: type === "checkbox" ? checked : value,
+ });
+ };
+
+ const handleFilterSubmit = () => {
+ setAppliedFilters(filterValues);
+ };
+
+ const tableFilter = (
+
+
+
+
+
+
+
+
+
+
+ }
+ label="Failed Logons Only"
+ />
+
+ {filterValues.failedLogonsOnly && (
+
+
+
+ )}
+
+
+
+
+
+ );
+
return (
<>
>
);
diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js
index d8e7ff471c6d..a11dea40b087 100644
--- a/src/utils/get-cipp-formatting.js
+++ b/src/utils/get-cipp-formatting.js
@@ -13,11 +13,13 @@ import { CippCopyToClipBoard } from "../components/CippComponents/CippCopyToClip
import { getCippLicenseTranslation } from "./get-cipp-license-translation";
import CippDataTableButton from "../components/CippTable/CippDataTableButton";
import { LinearProgressWithLabel } from "../components/linearProgressWithLabel";
+import { CippLocationDialog } from "../components/CippComponents/CippLocationDialog";
import { isoDuration, en } from "@musement/iso-duration";
import { CippTimeAgo } from "../components/CippComponents/CippTimeAgo";
import { getCippRoleTranslation } from "./get-cipp-role-translation";
import { CogIcon, ServerIcon, UserIcon, UsersIcon } from "@heroicons/react/24/outline";
import { getCippTranslation } from "./get-cipp-translation";
+import { getSignInErrorCodeTranslation } from "./get-cipp-signin-errorcode-translation";
export const getCippFormatting = (data, cellName, type, canReceive) => {
const isText = type === "text";
@@ -353,6 +355,14 @@ export const getCippFormatting = (data, cellName, type, canReceive) => {
);
}
+ if (cellName === "status.errorCode") {
+ return getSignInErrorCodeTranslation(data);
+ }
+
+ if (cellName === "location") {
+ return isText ? JSON.stringify(data) : ;
+ }
+
const translateProps = ["riskLevel", "riskState", "riskDetail", "enrollmentType", "profileType"];
if (translateProps.includes(cellName)) {
diff --git a/src/utils/get-cipp-signin-errorcode-translation.js b/src/utils/get-cipp-signin-errorcode-translation.js
new file mode 100644
index 000000000000..10f0c257a4d1
--- /dev/null
+++ b/src/utils/get-cipp-signin-errorcode-translation.js
@@ -0,0 +1,9 @@
+import SignInErrorCodes from "../data/SignInErrorCodes.json";
+
+export const getSignInErrorCodeTranslation = (errorCode) => {
+ if (SignInErrorCodes.hasOwnProperty(String(errorCode))) {
+ return SignInErrorCodes[String(errorCode)];
+ } else {
+ return errorCode;
+ }
+};