diff --git a/src/components/CippComponents/CippBreadcrumbNav.jsx b/src/components/CippComponents/CippBreadcrumbNav.jsx
index c0db7786d253..ed7e4be84c1b 100644
--- a/src/components/CippComponents/CippBreadcrumbNav.jsx
+++ b/src/components/CippComponents/CippBreadcrumbNav.jsx
@@ -62,6 +62,27 @@ export const CippBreadcrumbNav = () => {
const titleCheckCountRef = useRef(0);
const titleCheckIntervalRef = useRef(null);
+ // Helper function to filter out unnecessary query parameters
+ const getCleanQueryParams = (query) => {
+ const cleaned = { ...query };
+ // Remove tenantFilter if it's "AllTenants" or not explicitly needed
+ if (cleaned.tenantFilter === "AllTenants" || cleaned.tenantFilter === undefined) {
+ delete cleaned.tenantFilter;
+ }
+ return cleaned;
+ };
+
+ // Helper function to clean page titles
+ const cleanPageTitle = (title) => {
+ if (!title) return title;
+ // Remove AllTenants and any surrounding separators
+ return title
+ .replace(/\s*-\s*AllTenants\s*/, "")
+ .replace(/AllTenants\s*-\s*/, "")
+ .replace(/AllTenants/, "")
+ .trim();
+ };
+
// Load tab options on mount
useEffect(() => {
loadTabOptions().then(setTabOptions);
@@ -109,6 +130,9 @@ export const CippBreadcrumbNav = () => {
pageTitle = parts.slice(0, -1).join(" - ").trim();
}
+ // Clean AllTenants from title
+ pageTitle = cleanPageTitle(pageTitle);
+
// Skip if title is empty, generic, or error page
if (
!pageTitle ||
@@ -155,7 +179,10 @@ export const CippBreadcrumbNav = () => {
if (samePath && !sameTitle) {
// Same URL but title changed - update the entry
const updated = [...prevHistory];
- updated[prevHistory.length - 1] = currentPage;
+ updated[prevHistory.length - 1] = {
+ ...currentPage,
+ query: getCleanQueryParams(currentPage.query),
+ };
if (titleCheckIntervalRef.current) {
clearInterval(titleCheckIntervalRef.current);
titleCheckIntervalRef.current = null;
@@ -173,7 +200,11 @@ export const CippBreadcrumbNav = () => {
// URL not in history (except possibly as last entry which we handled) - add as new entry
if (existingIndex === -1) {
- const newHistory = [...prevHistory, currentPage];
+ const cleanedCurrentPage = {
+ ...currentPage,
+ query: getCleanQueryParams(currentPage.query),
+ };
+ const newHistory = [...prevHistory, cleanedCurrentPage];
// Keep only the last MAX_HISTORY_STORAGE pages
const trimmedHistory =
@@ -192,7 +223,10 @@ export const CippBreadcrumbNav = () => {
titleCheckIntervalRef.current = null;
}
const updated = prevHistory.slice(0, existingIndex + 1);
- updated[existingIndex] = currentPage;
+ updated[existingIndex] = {
+ ...currentPage,
+ query: getCleanQueryParams(currentPage.query),
+ };
return updated;
});
};
@@ -211,9 +245,10 @@ export const CippBreadcrumbNav = () => {
const handleBreadcrumbClick = (index) => {
const page = history[index];
if (page) {
+ const cleanedQuery = getCleanQueryParams(page.query);
router.push({
pathname: page.path,
- query: page.query,
+ query: cleanedQuery,
});
}
};
@@ -247,15 +282,18 @@ export const CippBreadcrumbNav = () => {
return;
}
- const pageTitle = document.title.replace(" - CIPP", "").trim();
+ let pageTitle = document.title.replace(" - CIPP", "").trim();
const parts = pageTitle.split(" - ");
const cleanTitle =
parts.length > 1 && parts[parts.length - 1].includes(".")
? parts.slice(0, -1).join(" - ").trim()
: pageTitle;
- if (cleanTitle && cleanTitle !== "CIPP" && !cleanTitle.toLowerCase().includes("loading")) {
- setCurrentPageTitle(cleanTitle);
+ // Clean AllTenants from title
+ const finalTitle = cleanPageTitle(cleanTitle);
+
+ if (finalTitle && finalTitle !== "CIPP" && !finalTitle.toLowerCase().includes("loading")) {
+ setCurrentPageTitle(finalTitle);
// Stop checking once we have a valid title
if (hierarchicalTitleCheckRef.current) {
clearInterval(hierarchicalTitleCheckRef.current);
@@ -316,11 +354,11 @@ export const CippBreadcrumbNav = () => {
// Check if this item matches the current path
if (item.path && pathsMatch(item.path, currentPath)) {
- // If this is the current page, include current query params
+ // If this is the current page, include current query params (cleaned)
if (item.path === currentPath) {
const lastItem = currentBreadcrumb[currentBreadcrumb.length - 1];
if (lastItem) {
- lastItem.query = { ...router.query };
+ lastItem.query = getCleanQueryParams(router.query);
}
}
return currentBreadcrumb;
@@ -339,6 +377,32 @@ export const CippBreadcrumbNav = () => {
let result = findPathInMenu(nativeMenuItems);
+ // If we found a menu item, check if the current path matches any tab
+ // If so, tabOptions wins and we use its label
+ if (result.length > 0 && tabOptions.length > 0) {
+ const normalizedCurrentPath = currentPath.replace(/\/$/, "");
+
+ // Check if current path matches any tab (exact match)
+ const matchingTab = tabOptions.find((tab) => {
+ const normalizedTabPath = tab.path.replace(/\/$/, "");
+ return normalizedTabPath === normalizedCurrentPath;
+ });
+
+ if (matchingTab) {
+ // Tab matches the current path - use tab's label instead of config's
+ result = result.map((item, idx) => {
+ if (idx === result.length - 1) {
+ return {
+ ...item,
+ title: matchingTab.title,
+ type: "tab",
+ };
+ }
+ return item;
+ });
+ }
+ }
+
// If not found in main menu, check if it's a tab page
if (result.length === 0 && tabOptions.length > 0) {
const normalizedCurrentPath = currentPath.replace(/\/$/, "");
@@ -395,12 +459,12 @@ export const CippBreadcrumbNav = () => {
if (basePagePath.length > 0) {
result = basePagePath;
- // Add the tab as the final breadcrumb with current query params
+ // Add the tab as the final breadcrumb with current query params (cleaned)
result.push({
title: matchingTab.title,
path: matchingTab.path,
type: "tab",
- query: { ...router.query }, // Include current query params for tab page
+ query: getCleanQueryParams(router.query), // Include current query params for tab page
});
}
}
@@ -411,7 +475,10 @@ export const CippBreadcrumbNav = () => {
const lastItem = result[result.length - 1];
if (lastItem.path && lastItem.path !== currentPath && currentPath.startsWith(lastItem.path)) {
// Use the tracked page title if available, otherwise fall back to document.title
- const tabTitle = currentPageTitle || document.title.replace(" - CIPP", "").trim();
+ let tabTitle = currentPageTitle || document.title.replace(" - CIPP", "").trim();
+
+ // Clean AllTenants from title
+ tabTitle = cleanPageTitle(tabTitle);
// Add tab as an additional breadcrumb item
if (
@@ -423,7 +490,7 @@ export const CippBreadcrumbNav = () => {
title: tabTitle,
path: currentPath,
type: "tab",
- query: { ...router.query }, // Include current query params
+ query: getCleanQueryParams(router.query), // Include current query params (cleaned)
});
}
}
@@ -435,10 +502,11 @@ export const CippBreadcrumbNav = () => {
// Handle click for hierarchical breadcrumbs
const handleHierarchicalClick = (path, query) => {
if (path) {
- if (query && Object.keys(query).length > 0) {
+ const cleanedQuery = getCleanQueryParams(query);
+ if (cleanedQuery && Object.keys(cleanedQuery).length > 0) {
router.push({
pathname: path,
- query: query,
+ query: cleanedQuery,
});
} else {
router.push(path);
@@ -478,7 +546,7 @@ export const CippBreadcrumbNav = () => {
title,
path,
type: "fallback",
- query: index === pathSegments.length - 1 ? { ...router.query } : {},
+ query: index === pathSegments.length - 1 ? getCleanQueryParams(router.query) : {},
};
});
@@ -488,7 +556,7 @@ export const CippBreadcrumbNav = () => {
currentPageTitle !== "CIPP" &&
!currentPageTitle.toLowerCase().includes("loading")
) {
- breadcrumbs[breadcrumbs.length - 1].title = currentPageTitle;
+ breadcrumbs[breadcrumbs.length - 1].title = cleanPageTitle(currentPageTitle);
}
}
}
diff --git a/src/components/CippComponents/CippTenantSelector.jsx b/src/components/CippComponents/CippTenantSelector.jsx
index de58af87c29a..688afab83bb8 100644
--- a/src/components/CippComponents/CippTenantSelector.jsx
+++ b/src/components/CippComponents/CippTenantSelector.jsx
@@ -184,7 +184,7 @@ export const CippTenantSelector = (props) => {
// Cancel all in-flight queries before changing tenant
queryClient.cancelQueries();
- // Update router and settings
+ // Update router only - let the URL watcher handle settings
query.tenantFilter = currentTenant.value;
router.replace(
{
@@ -194,53 +194,52 @@ export const CippTenantSelector = (props) => {
undefined,
{ shallow: true }
);
-
- settings.handleUpdate({
- currentTenant: currentTenant.value,
- });
}
- //if we have a tenantfilter, we add the tenantfilter to the title of the tab/page so its "Tenant - original title".
}
}, [currentTenant?.value]);
- // This effect handles when the URL parameter changes externally
+ // This effect handles when the URL parameter changes (from deep link or user selection)
+ // This is the single source of truth for tenant changes
useEffect(() => {
- if (!router.isReady || !tenantList.isSuccess || !settings.isInitialized) return;
+ if (!router.isReady || !tenantList.isSuccess) return;
- // Get the current tenant from URL or settings
- const urlTenant = router.query.tenantFilter || settings.currentTenant;
+ const urlTenant = router.query.tenantFilter;
- // Only update if there's a URL tenant and it's different from our current state
- if (urlTenant && (!currentTenant || urlTenant !== currentTenant.value)) {
+ // Only process if we have a URL tenant
+ if (urlTenant) {
// Find the tenant in our list
const matchingTenant = tenantList.data.find(
({ defaultDomainName }) => defaultDomainName === urlTenant
);
if (matchingTenant) {
- setSelectedTenant({
- value: urlTenant,
- label: `${matchingTenant.displayName} (${urlTenant})`,
- addedFields: {
- defaultDomainName: matchingTenant.defaultDomainName,
- displayName: matchingTenant.displayName,
- customerId: matchingTenant.customerId,
- initialDomainName: matchingTenant.initialDomainName,
- },
- });
+ // Update local state if different
+ if (!currentTenant || urlTenant !== currentTenant.value) {
+ setSelectedTenant({
+ value: urlTenant,
+ label: `${matchingTenant.displayName} (${urlTenant})`,
+ addedFields: {
+ defaultDomainName: matchingTenant.defaultDomainName,
+ displayName: matchingTenant.displayName,
+ customerId: matchingTenant.customerId,
+ initialDomainName: matchingTenant.initialDomainName,
+ },
+ });
+ }
+
+ // Update settings if different (null filter in settings-context prevents saving null)
+ if (settings.currentTenant !== urlTenant) {
+ settings.handleUpdate({
+ currentTenant: urlTenant,
+ });
+ }
}
}
- }, [
- router.isReady,
- router.query.tenantFilter,
- tenantList.isSuccess,
- settings.currentTenant,
- settings.isInitialized,
- ]);
+ }, [router.isReady, router.query.tenantFilter, tenantList.isSuccess]);
// This effect ensures the tenant filter parameter is included in the URL when missing
useEffect(() => {
- if (!router.isReady || !settings.isInitialized || !settings.currentTenant) return;
+ if (!router.isReady || !settings.currentTenant) return;
// If the tenant parameter is missing from the URL but we have it in settings
if (!router.query.tenantFilter && settings.currentTenant) {
@@ -254,7 +253,7 @@ export const CippTenantSelector = (props) => {
{ shallow: true }
);
}
- }, [router.isReady, router.query, settings.currentTenant, settings.isInitialized]);
+ }, [router.isReady, router.query.tenantFilter, settings.currentTenant]);
useEffect(() => {
if (tenant && currentTenant?.value && currentTenant?.value !== "AllTenants") {
diff --git a/src/components/CippStandards/CippStandardsSideBar.jsx b/src/components/CippStandards/CippStandardsSideBar.jsx
index 170c1b73e28f..53a6d571b8a8 100644
--- a/src/components/CippStandards/CippStandardsSideBar.jsx
+++ b/src/components/CippStandards/CippStandardsSideBar.jsx
@@ -556,7 +556,7 @@ const CippStandardsSideBar = ({
title="Add Standard"
api={{
confirmText: isDriftMode
- ? "This template will automatically every hour to detect drift. Are you sure you want to apply this Drift Template?"
+ ? "This template will automatically every 12 hours to detect drift. Are you sure you want to apply this Drift Template?"
: watchForm.runManually
? "Are you sure you want to apply this standard? This template has been set to never run on a schedule. After saving the template you will have to run it manually."
: "Are you sure you want to apply this standard? This will apply the template and run every 3 hours.",
diff --git a/src/layouts/config.js b/src/layouts/config.js
index cb3833d87085..8923b99832d1 100644
--- a/src/layouts/config.js
+++ b/src/layouts/config.js
@@ -195,7 +195,7 @@ export const nativeMenuItems = [
items: [
{
title: "Standards Management",
- path: "/tenant/standards/list-standards",
+ path: "/tenant/standards/alignment",
permissions: ["Tenant.Standards.*"],
},
{
diff --git a/src/pages/endpoint/MEM/devices/index.js b/src/pages/endpoint/MEM/devices/index.js
index 49a658899ea9..e2d5c52ef4ef 100644
--- a/src/pages/endpoint/MEM/devices/index.js
+++ b/src/pages/endpoint/MEM/devices/index.js
@@ -178,10 +178,7 @@ const Page = () => {
GUID: "id",
Action: "resetPasscode",
},
- condition: (row) =>
- row.operatingSystem === "iOS" ||
- row.operatingSystem === "macOS" ||
- row.operatingSystem === "Android",
+ condition: (row) => row.operatingSystem === "Android",
confirmText:
"Are you sure you want to reset the passcode for [deviceName]? A new passcode will be generated and displayed.",
},
@@ -194,10 +191,7 @@ const Page = () => {
GUID: "id",
Action: "removeDevicePasscode",
},
- condition: (row) =>
- row.operatingSystem === "iOS" ||
- row.operatingSystem === "macOS" ||
- row.operatingSystem === "Android",
+ condition: (row) => row.operatingSystem === "iOS",
confirmText:
"Are you sure you want to remove the passcode from [deviceName]? This will remove the device passcode requirement.",
},
diff --git a/src/pages/tenant/administration/tenants/groups/edit.js b/src/pages/tenant/administration/tenants/groups/edit.js
index dd41f5f382ed..33edecd5e1c6 100644
--- a/src/pages/tenant/administration/tenants/groups/edit.js
+++ b/src/pages/tenant/administration/tenants/groups/edit.js
@@ -148,7 +148,7 @@ const Page = () => {
dynamicRules: formattedDynamicRules,
});
}
- }, [groupDetails.isSuccess, groupDetails.data]);
+ }, [groupDetails.isSuccess, groupDetails.data, id]);
const customDataFormatter = (values) => {
const formattedData = {
diff --git a/src/pages/tenant/manage/driftManagementActions.js b/src/pages/tenant/manage/driftManagementActions.js
index 6141a292e5aa..5d7cd8e80488 100644
--- a/src/pages/tenant/manage/driftManagementActions.js
+++ b/src/pages/tenant/manage/driftManagementActions.js
@@ -49,11 +49,13 @@ export const createDriftManagementActions = ({
// Use Next.js router for internal navigation
import("next/router")
.then(({ default: router }) => {
- router.push(`/tenant/standards/template?id=${templateId}&type=${templateType}`);
+ router.push(
+ `/tenant/standards/templates/template?id=${templateId}&type=${templateType}`
+ );
})
.catch(() => {
// Fallback to window.location if router is not available
- window.location.href = `/tenant/standards/template?id=${templateId}&type=${templateType}`;
+ window.location.href = `/tenant/standards/templates/template?id=${templateId}&type=${templateType}`;
});
},
});
diff --git a/src/pages/tenant/standards/list-standards/drift-alignment/index.js b/src/pages/tenant/standards/alignment/index.js
similarity index 53%
rename from src/pages/tenant/standards/list-standards/drift-alignment/index.js
rename to src/pages/tenant/standards/alignment/index.js
index ef0aa4f974e3..6ddd7a186223 100644
--- a/src/pages/tenant/standards/list-standards/drift-alignment/index.js
+++ b/src/pages/tenant/standards/alignment/index.js
@@ -1,11 +1,12 @@
import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx";
import { Layout as DashboardLayout } from "/src/layouts/index.js";
import { TabbedLayout } from "/src/layouts/TabbedLayout";
+import { Delete, Add } from "@mui/icons-material";
import { EyeIcon } from "@heroicons/react/24/outline";
import tabOptions from "../tabOptions.json";
const Page = () => {
- const pageTitle = "Drift Alignment";
+ const pageTitle = "Standard & Drift Alignment";
const actions = [
{
@@ -15,6 +16,28 @@ const Page = () => {
color: "info",
target: "_self",
},
+ {
+ label: "Manage Drift",
+ link: "/tenant/manage/drift?templateId=[standardId]&tenantFilter=[tenantFilter]",
+ icon: