Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
dadec81
Update _app.js
JohnDuprey Dec 19, 2025
eb6c5e9
Remove isInitialized check from app rendering
JohnDuprey Dec 19, 2025
bd01077
cleanup console logs
JohnDuprey Dec 19, 2025
b0c87ea
Improve 'hasValue' condition logic in CippFormCondition
JohnDuprey Dec 19, 2025
600ef52
Remove unused addedField from Intune templates config
JohnDuprey Dec 19, 2025
c802dd6
Refactor tenant selection and URL sync logic
JohnDuprey Dec 19, 2025
19f1347
update text
JohnDuprey Dec 19, 2025
450e397
fix conditions
JohnDuprey Dec 19, 2025
dc8be58
Clean up breadcrumb query params and titles
JohnDuprey Dec 19, 2025
034a98e
fix useEffect
JohnDuprey Dec 19, 2025
58b930d
Remove debug console.log statements from breadcrumb nav
JohnDuprey Dec 19, 2025
5474006
Update CippBreadcrumbNav.jsx
JohnDuprey Dec 19, 2025
9d4d643
Refactor standards pages and update alignment routes
JohnDuprey Dec 19, 2025
401c4cf
cleanup console logs
JohnDuprey Dec 19, 2025
d40f2f5
Improve 'hasValue' condition logic in CippFormCondition
JohnDuprey Dec 19, 2025
e8f8df6
Remove unused addedField from Intune templates config
JohnDuprey Dec 19, 2025
35f2eed
Update standards route and remove list-standards page
JohnDuprey Dec 19, 2025
9f97de3
Improve breadcrumb title resolution and path validation
JohnDuprey Dec 19, 2025
f3f6392
Add conditional logic to CreateGroups switch
JohnDuprey Dec 19, 2025
ccaf6b1
Bump version to 8.8.2
JohnDuprey Dec 19, 2025
3ee55dd
Add tenant filter to backup history for AllTenants
JohnDuprey Dec 19, 2025
b321935
Update CippRestoreBackupDrawer.jsx
JohnDuprey Dec 19, 2025
0bbc79c
Use backup tenant in AllTenants restore context
JohnDuprey Dec 20, 2025
bac437a
Update configuration-backup.js
JohnDuprey Dec 20, 2025
16146ca
Merge pull request #5124 from KelvinTegelaar/dev
JohnDuprey Dec 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion generate-placeholders.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const pages = [
{ title: "BPA Report Builder", path: "/tenant/tools/bpa-report-builder" },
{ title: "Standards", path: "/tenant/standards" },
{ title: "Edit Standards", path: "/tenant/standards/list-applied-standards" },
{ title: "List Standards", path: "/tenant/standards/list-standards" },
{ title: "List Standards", path: "/tenant/standards" },
{ title: "Best Practice Analyser", path: "/tenant/standards/bpa-report" },
{ title: "Domains Analyser", path: "/tenant/standards/domains-analyser" },
{ title: "Conditional Access", path: "/tenant/administration" },
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cipp",
"version": "8.8.1",
"version": "8.8.2",
"author": "CIPP Contributors",
"homepage": "https://cipp.app/",
"bugs": {
Expand Down
2 changes: 1 addition & 1 deletion public/version.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"version": "8.8.1"
"version": "8.8.2"
}
102 changes: 80 additions & 22 deletions src/components/CippComponents/CippBreadcrumbNav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async function loadTabOptions() {
"/email/administration/exchange-retention",
"/cipp/custom-data",
"/cipp/super-admin",
"/tenant/standards/list-standards",
"/tenant/standards",
"/tenant/manage",
"/tenant/administration/applications",
"/tenant/administration/tenants",
Expand Down Expand Up @@ -499,6 +499,46 @@ export const CippBreadcrumbNav = () => {
return result;
};

// Check if a path is valid and return its title from navigation or tabs
const getPathInfo = (path) => {
if (!path) return { isValid: false, title: null };

const normalizedPath = path.replace(/\/$/, "");

// Helper function to recursively search menu items
const findInMenu = (items) => {
for (const item of items) {
if (item.path) {
const normalizedItemPath = item.path.replace(/\/$/, "");
if (normalizedItemPath === normalizedPath) {
return { isValid: true, title: item.title };
}
}
if (item.items && item.items.length > 0) {
const found = findInMenu(item.items);
if (found.isValid) {
return found;
}
}
}
return { isValid: false, title: null };
};

// Check if path exists in navigation
const menuResult = findInMenu(nativeMenuItems);
if (menuResult.isValid) {
return menuResult;
}

// Check if path exists in tab options
const matchingTab = tabOptions.find((tab) => tab.path.replace(/\/$/, "") === normalizedPath);
if (matchingTab) {
return { isValid: true, title: matchingTab.title };
}

return { isValid: false, title: null };
};

// Handle click for hierarchical breadcrumbs
const handleHierarchicalClick = (path, query) => {
if (path) {
Expand Down Expand Up @@ -580,6 +620,9 @@ export const CippBreadcrumbNav = () => {
>
{breadcrumbs.map((crumb, index) => {
const isLast = index === breadcrumbs.length - 1;
const pathInfo = getPathInfo(crumb.path);
// Use title from nav/tabs if available, otherwise use the crumb's title
const displayTitle = pathInfo.title || crumb.title;

// Items without paths (headers/groups) - show as text
if (!crumb.path) {
Expand All @@ -590,31 +633,46 @@ export const CippBreadcrumbNav = () => {
variant="subtitle2"
sx={{ fontWeight: isLast ? 500 : 400 }}
>
{crumb.title}
{displayTitle}
</Typography>
);
}

// All items with paths are clickable, including the last one
return (
<Link
key={index}
component="button"
variant="subtitle2"
onClick={() => handleHierarchicalClick(crumb.path, crumb.query)}
sx={{
textDecoration: "none",
color: isLast ? "text.primary" : "text.secondary",
fontWeight: isLast ? 500 : 400,
"&:hover": {
textDecoration: "underline",
color: "primary.main",
},
}}
>
{crumb.title}
</Link>
);
// Items with valid paths are clickable
// Items with invalid paths (fallback) are shown as plain text
if (pathInfo.isValid) {
return (
<Link
key={index}
component="button"
variant="subtitle2"
onClick={() => handleHierarchicalClick(crumb.path, crumb.query)}
sx={{
textDecoration: "none",
color: isLast ? "text.primary" : "text.secondary",
fontWeight: isLast ? 500 : 400,
"&:hover": {
textDecoration: "underline",
color: "primary.main",
},
}}
>
{displayTitle}
</Link>
);
} else {
// Invalid path - show as text only
return (
<Typography
key={index}
color={isLast ? "text.primary" : "text.secondary"}
variant="subtitle2"
sx={{ fontWeight: isLast ? 500 : 400 }}
>
{displayTitle}
</Typography>
);
}
})}
</Breadcrumbs>
</Box>
Expand Down
29 changes: 23 additions & 6 deletions src/components/CippComponents/CippCADeployDrawer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import CippJsonView from "../CippFormPages/CippJSONView";
import { CippApiResults } from "./CippApiResults";
import { useSettings } from "../../hooks/use-settings";
import { CippFormTenantSelector } from "./CippFormTenantSelector";
import { CippFormCondition } from "./CippFormCondition";

export const CippCADeployDrawer = ({
buttonText = "Deploy CA Policy",
Expand All @@ -24,6 +25,10 @@ export const CippCADeployDrawer = ({
const CATemplates = ApiGetCall({ url: "/api/ListCATemplates", queryKey: "CATemplates" });
const [JSONData, setJSONData] = useState();
const watcher = useWatch({ control: formControl.control, name: "TemplateList" });
const selectedReplaceMode = useWatch({
control: formControl.control,
name: "replacename",
});

// Use external open state if provided, otherwise use internal state
const drawerVisible = open !== null ? open : internalDrawerVisible;
Expand Down Expand Up @@ -199,13 +204,25 @@ export const CippCADeployDrawer = ({
label="Disable Security Defaults if enabled when creating policy"
formControl={formControl}
/>

<CippFormComponent
type="switch"
name="CreateGroups"
label="Create groups if they do not exist"
<CippFormCondition
formControl={formControl}
/>
field="replacename"
compareType="is"
compareValue="displayName"
action="disable"
>
<CippFormComponent
type="switch"
name="CreateGroups"
label="Create groups if they do not exist"
formControl={formControl}
helperText={
selectedReplaceMode !== "displayName"
? "Select 'Replace by display name' to create groups specified in the template."
: "Enable this option to create groups that do not exist in the tenant."
}
/>
</CippFormCondition>
</Stack>
</CippOffCanvas>
</>
Expand Down
2 changes: 1 addition & 1 deletion src/components/CippComponents/CippCentralSearch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ async function loadTabOptions() {
"/email/administration/exchange-retention",
"/cipp/custom-data",
"/cipp/super-admin",
"/tenant/standards/list-standards",
"/tenant/standards",
"/tenant/manage",
"/tenant/administration/applications",
"/tenant/administration/tenants",
Expand Down
18 changes: 13 additions & 5 deletions src/components/CippComponents/CippFormCondition.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const CippFormCondition = (props) => {

if (
field === undefined ||
compareValue === undefined ||
(compareValue === undefined && compareType !== "hasValue") ||
children === undefined ||
formControl === undefined
) {
Expand Down Expand Up @@ -148,10 +148,18 @@ export const CippFormCondition = (props) => {
watcher.length >= compareValue
);
case "hasValue":
return (
(watcher !== undefined && watcher !== null && watcher !== "") ||
(watcher?.value !== undefined && watcher?.value !== null && watcher?.value !== "")
);
// Check watchedValue (the extracted value based on propertyName)
// For simple values (strings, numbers)
if (watchedValue === undefined || watchedValue === null || watchedValue === "") {
return false;
}
// If it's an array, check if it has elements
if (Array.isArray(watchedValue)) {
return watchedValue.length > 0;
}
console.log("watched value:", watchedValue);
// For any other truthy value (objects, numbers, strings), consider it as having a value
return true;
case "labelEq":
return Array.isArray(watcher) && watcher.some((item) => item?.label === compareValue);
case "labelContains":
Expand Down
15 changes: 13 additions & 2 deletions src/components/CippComponents/CippRestoreBackupDrawer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ApiPostCall } from "../../api/ApiCall";
export const CippRestoreBackupDrawer = ({
buttonText = "Restore Backup",
backupName = null,
backupData = null,
requiredPermissions = [],
PermissionButton = Button,
...props
Expand Down Expand Up @@ -85,7 +86,12 @@ export const CippRestoreBackupDrawer = ({
const values = formControl.getValues();
const startDate = new Date();
const unixTime = Math.floor(startDate.getTime() / 1000) - 45;
const tenantFilterValue = tenantFilter;

// If in AllTenants context, use the tenant from the backup data
let tenantFilterValue = tenantFilter;
if (tenantFilter === "AllTenants" && backupData?.tenantSource) {
tenantFilterValue = backupData.tenantSource;
}

const shippedValues = {
TenantFilter: tenantFilterValue,
Expand Down Expand Up @@ -202,7 +208,12 @@ export const CippRestoreBackupDrawer = ({
queryKey: `BackupList-${tenantFilter}-autocomplete`,
labelField: (option) => {
const match = option.BackupName.match(/.*_(\d{4}-\d{2}-\d{2})-(\d{2})(\d{2})/);
return match ? `${match[1]} @ ${match[2]}:${match[3]}` : option.BackupName;
const dateTime = match
? `${match[1]} @ ${match[2]}:${match[3]}`
: option.BackupName;
const tenantDisplay =
tenantFilter === "AllTenants" ? ` (${option.TenantFilter})` : "";
return `${dateTime}${tenantDisplay}`;
},
valueField: "BackupName",
data: {
Expand Down
40 changes: 0 additions & 40 deletions src/components/CippStandards/CippStandardsSideBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,34 +144,12 @@ const CippStandardsSideBar = ({
}

// Filter out current template if editing
console.log("Duplicate detection debug:", {
edit,
currentGUID: watchForm.GUID,
allTemplates: driftValidationApi.data?.map((t) => ({
GUID: t.GUID,
standardId: t.standardId,
standardName: t.standardName,
})),
});

const existingTemplates = driftValidationApi.data.filter((template) => {
const shouldInclude =
edit && watchForm.GUID ? template.standardId !== watchForm.GUID : true;
console.log(
`Template ${template.standardId} (${template.standardName}): shouldInclude=${shouldInclude}, currentGUID=${watchForm.GUID}`
);
return shouldInclude;
});

console.log(
"Filtered templates:",
existingTemplates?.map((t) => ({
GUID: t.GUID,
standardId: t.standardId,
standardName: t.standardName,
}))
);

// Get tenant groups data
const groups = tenantGroupsApi.data?.Results || [];

Expand All @@ -198,45 +176,27 @@ const CippStandardsSideBar = ({
});

// Check for conflicts with unique templates
console.log("Checking conflicts with unique templates:", uniqueTemplates);
console.log("Selected tenant list:", selectedTenantList);

for (const templateId in uniqueTemplates) {
const template = uniqueTemplates[templateId];
const templateTenants = template.tenants;

console.log(
`Checking template ${templateId} (${template.standardName}) with tenants:`,
templateTenants
);

const hasConflict = selectedTenantList.some((selectedTenant) => {
// Check if any template tenant matches the selected tenant
const conflict = templateTenants.some((templateTenant) => {
if (selectedTenant === "AllTenants" || templateTenant === "AllTenants") {
console.log(
`Conflict found: ${selectedTenant} vs ${templateTenant} (AllTenants match)`
);
return true;
}
const match = selectedTenant === templateTenant;
if (match) {
console.log(`Conflict found: ${selectedTenant} vs ${templateTenant} (exact match)`);
}
return match;
});
return conflict;
});

console.log(`Template ${templateId} has conflict: ${hasConflict}`);

if (hasConflict) {
conflicts.push(template.standardName || "Unknown Template");
}
}

console.log("Final conflicts:", conflicts);

if (conflicts.length > 0) {
setDriftError(
`This template has tenants that are assigned to another Drift Template. You can only assign one Drift Template to each tenant. Please check the ${conflicts.join(
Expand Down
5 changes: 1 addition & 4 deletions src/data/standards.json
Original file line number Diff line number Diff line change
Expand Up @@ -5056,10 +5056,7 @@
"queryKey": "ListIntuneTemplates-tag-autcomplete",
"url": "/api/ListIntuneTemplates?mode=Tag",
"labelField": "label",
"valueField": "value",
"addedField": {
"templates": "templates"
}
"valueField": "value"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/use-securescore.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function useSecureScore({ waiting = true } = {}) {
complianceInformation: translation?.complianceInformation,
actionUrl: remediation
? //this needs to be updated to be a direct url to apply this standard.
"/tenant/standards/list-standards"
"/tenant/standards"
: translation?.actionUrl,
remediation: remediation
? `1. Enable the CIPP Standard: ${remediation.label}`
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/HeaderedTabbedLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const HeaderedTabbedLayout = (props) => {
!mdDown && {
flexGrow: 1,
overflow: "auto",
height: "calc(100vh - 30px)",
height: "calc(100vh - 350px)",
}
}
>
Expand Down
Loading