diff --git a/.github/agents/CIPP-Frontend-Alert-Agent.md b/.github/agents/CIPP-Frontend-Alert-Agent.md new file mode 100644 index 000000000000..6b29ec142fed --- /dev/null +++ b/.github/agents/CIPP-Frontend-Alert-Agent.md @@ -0,0 +1,45 @@ +--- +name: CIPP Frontend Alert Registrar +description: > + Adds new alert entries to src/data/alerts.json in the CIPP frontend. + The agent must never modify any other file or perform any other change. +--- + +# CIPP Frontend Alert Registrar + +## Mission + +You are a **frontend alert registrar** responsible for updating the `src/data/alerts.json` file to include new alerts. + +Your role is **strictly limited** to adding a new JSON entry describing the alert’s metadata. +You do not touch or inspect any other part of the codebase. + +--- + +## Scope of Work + +This agent is used when a new alert must be surfaced to the frontend — for example, after a new backend `Get-CIPPAlert*.ps1` alert has been added. + +Tasks include: + +- Opening `src/data/alerts.json` +- Appending one new JSON object describing the new alert +- Preserving JSON structure, indentation, and trailing commas exactly as in the existing file +- Validating that the resulting JSON is syntactically correct + + +## Alert Format + +Each alert entry in `src/data/alerts.json` is a JSON object with the following structure: + +```json +{ + "name": "", + "label": "A nice label for the alert", + "requiresInput": true, + "inputType": "switch", + "inputLabel": "Exclude disabled users?", + "inputName": "InactiveLicensedUsersExcludeDisabled", + "recommendedRunInterval": "1d" +} +``` diff --git a/.gitignore b/.gitignore index 97735ad0415c..78ea4526e7ce 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ node_modules .pnp .pnp.js build +package-lock.json # testing coverage diff --git a/cspell.json b/cspell.json index 9ffb9e0d6e27..00a5c1785b77 100644 --- a/cspell.json +++ b/cspell.json @@ -14,8 +14,10 @@ "cipp", "CIPP", "CIPP-API", + "CISA", "Datto", "DMARC", + "EIDSCA", "Entra", "ESET", "GDAP", diff --git a/generate-placeholders.js b/generate-placeholders.js index 2b34888fca7a..304e6402e4ed 100644 --- a/generate-placeholders.js +++ b/generate-placeholders.js @@ -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" }, diff --git a/package.json b/package.json index a355bff79afa..6d01e9706b32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cipp", - "version": "8.7.1", + "version": "8.8.2", "author": "CIPP Contributors", "homepage": "https://cipp.app/", "bugs": { @@ -36,8 +36,10 @@ "@mui/system": "7.3.2", "@mui/x-date-pickers": "^8.11.1", "@musement/iso-duration": "^1.0.0", + "@nivo/core": "^0.99.0", + "@nivo/sankey": "^0.99.0", "@react-pdf/renderer": "^4.3.0", - "@reduxjs/toolkit": "2.9.0", + "@reduxjs/toolkit": "^2.11.2", "@tanstack/query-sync-storage-persister": "^5.76.0", "@tanstack/react-query": "^5.51.11", "@tanstack/react-query-devtools": "^5.51.11", @@ -51,6 +53,7 @@ "@tiptap/react": "^3.4.1", "@tiptap/starter-kit": "^3.4.1", "@uiw/react-json-view": "^2.0.0-alpha.30", + "@vvo/tzdb": "^6.198.0", "apexcharts": "5.3.5", "axios": "^1.7.2", "date-fns": "4.1.0", @@ -97,6 +100,7 @@ "react-time-ago": "^7.3.3", "react-virtuoso": "^4.12.8", "react-window": "^2.1.0", + "recharts": "^3.6.0", "redux": "5.0.1", "redux-devtools-extension": "2.13.9", "redux-persist": "^6.0.0", diff --git a/public/secureScore.json b/public/secureScore.json index e5fbf11c7b98..3e59bbd95d5d 100644 --- a/public/secureScore.json +++ b/public/secureScore.json @@ -49,7 +49,7 @@ "vendor": "Microsoft" }, "id": "aad_limited_administrative_roles", - "title": "Ensure 'Microsoft Azure Management' is limited to administrative roles" + "title": "Ensure \u0027Microsoft Azure Management\u0027 is limited to administrative roles" }, { "service": "AzureAD", @@ -62,7 +62,7 @@ "vendor": "Microsoft" }, "id": "aad_linkedin_connection_disables", - "title": "Ensure 'LinkedIn account connections' is disabled" + "title": "Ensure \u0027LinkedIn account connections\u0027 is disabled" }, { "service": "AzureAD", @@ -101,7 +101,7 @@ "vendor": "Microsoft" }, "id": "aad_phishing_MFA_strength", - "title": "Ensure 'Phishing-resistant MFA strength' is required for Administrators" + "title": "Ensure \u0027Phishing-resistant MFA strength\u0027 is required for Administrators" }, { "service": "AzureAD", @@ -1011,7 +1011,7 @@ "vendor": "Microsoft" }, "id": "admincenter_owned_apps_and_services", - "title": "Ensure 'User owned apps and services' is restricted" + "title": "Ensure \u0027User owned apps and services\u0027 is restricted" }, { "service": "AzureAD", @@ -1102,7 +1102,7 @@ "vendor": "Microsoft" }, "id": "exo_individualsharing", - "title": "Ensure 'External sharing' of calendars is not available" + "title": "Ensure \u0027External sharing\u0027 of calendars is not available" }, { "service": "EXO", @@ -1388,7 +1388,7 @@ "vendor": "Microsoft" }, "id": "MDA_CitrixSF_LoginFailLockoutSecs", - "title": "Enhance 'login maximum attempts' - Lockout timer" + "title": "Enhance \u0027login maximum attempts\u0027 - Lockout timer" }, { "service": "MDA_CitrixSF", @@ -1401,7 +1401,7 @@ "vendor": "Microsoft" }, "id": "MDA_CitrixSF_LoginFailMaxAttempts", - "title": "Enhance 'login maximum attempts' - Number of attempts" + "title": "Enhance \u0027login maximum attempts\u0027 - Number of attempts" }, { "service": "MDA_CitrixSF", @@ -1518,7 +1518,7 @@ "vendor": "Microsoft" }, "id": "MDA_GitHub_DependencyInsights", - "title": "Disable 'Allow members to view dependency insights'" + "title": "Disable \u0027Allow members to view dependency insights\u0027" }, { "service": "MDA_GitHub", @@ -1531,7 +1531,7 @@ "vendor": "Microsoft" }, "id": "MDA_GitHub_EmailNotificationRestrictedToVerifiedOrApprovedDomains", - "title": "Enabled 'email notification delivery for this enterprise is restricted to verified or approved domains'" + "title": "Enabled \u0027email notification delivery for this enterprise is restricted to verified or approved domains\u0027" }, { "service": "MDA_GitHub", @@ -1557,7 +1557,7 @@ "vendor": "Microsoft" }, "id": "MDA_GitHub_OutsideCollabInvitation", - "title": "Disable 'Allow repository administrators to invite outside collaborators to repositories for this organization" + "title": "Disable \u0027Allow repository administrators to invite outside collaborators to repositories for this organization" }, { "service": "MDA_GitHub", @@ -1583,7 +1583,7 @@ "vendor": "Microsoft" }, "id": "MDA_GitHub_PublicRepoCreation", - "title": "Disable 'Members will be able to create public repositories, visible to anyone'" + "title": "Disable \u0027Members will be able to create public repositories, visible to anyone\u0027" }, { "service": "MDA_GitHub", @@ -1596,7 +1596,7 @@ "vendor": "Microsoft" }, "id": "MDA_GitHub_RepoTransferOrDeletion", - "title": "Disable 'members with admin permissions for repositories can delete or transfer repositories'" + "title": "Disable \u0027members with admin permissions for repositories can delete or transfer repositories\u0027" }, { "service": "MDA_GitHub", @@ -1609,7 +1609,7 @@ "vendor": "Microsoft" }, "id": "MDA_GitHub_RepoVisibility_change", - "title": "Disable 'Allow members to change repository visibilities for this organization'" + "title": "Disable \u0027Allow members to change repository visibilities for this organization\u0027" }, { "service": "MDA_GitHub", @@ -2701,7 +2701,7 @@ "vendor": "Microsoft" }, "id": "mdo_connectionfilter", - "title": "Don't add allowed IP addresses in the connection filter policy " + "title": "Don\u0027t add allowed IP addresses in the connection filter policy " }, { "service": "MDO", @@ -3286,7 +3286,7 @@ "vendor": "Microsoft" }, "id": "PWAgePolicyNew", - "title": "Ensure the 'Password expiration policy' is set to 'Set passwords to never expire (recommended)'" + "title": "Ensure the \u0027Password expiration policy\u0027 is set to \u0027Set passwords to never expire (recommended)\u0027" }, { "service": "AzureAD", @@ -3301,1995 +3301,6 @@ "id": "RoleOverlap", "title": "Use least privileged administrative roles" }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_100", - "title": "Disable JavaScript on Adobe Reader 2015" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_101", - "title": "Disable JavaScript on Adobe 2015" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_15", - "title": "Enable Automatic Updates" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_16", - "title": "Enable 'Hide Option to Enable or Disable Updates'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_17", - "title": "Disable 'Allow running plugins that are outdated'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_19", - "title": "Disable 'Continue running background apps when Google Chrome is closed'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_20", - "title": "Disable 'AutoFill'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2000", - "title": "Turn on Microsoft Defender for Endpoint sensor" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2001", - "title": "Fix Microsoft Defender for Endpoint sensor data collection" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2002", - "title": "Fix Microsoft Defender for Endpoint impaired communications" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2003", - "title": "Turn on Tamper Protection" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2004", - "title": "Enable EDR in block mode" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2010", - "title": "Turn on Microsoft Defender Antivirus" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2011", - "title": "Update Microsoft Defender Antivirus definitions" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2012", - "title": "Turn on real-time protection" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2013", - "title": "Turn on PUA protection in block mode" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2014", - "title": "Fix Windows Defender Antivirus cloud service connectivity" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2016", - "title": "Enable cloud-delivered protection" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2020", - "title": "Turn on all system-level Exploit protection settings" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2021", - "title": "Set controlled folder access to enabled or audit mode" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2030", - "title": "Update Microsoft Defender for Endpoint core components" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2060", - "title": "Set Microsoft Defender SmartScreen app and file checking to block or warn" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2061", - "title": "Set Microsoft Defender SmartScreen Microsoft Edge site and download checking to block or warn" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2070", - "title": "Turn on Microsoft Defender Firewall" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2071", - "title": "Secure Microsoft Defender Firewall domain profile" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2072", - "title": "Secure Microsoft Defender firewall private profile" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2073", - "title": "Secure Microsoft Defender Firewall public profile" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2080", - "title": "Turn on Microsoft Defender Credential Guard" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2090", - "title": "Encrypt all BitLocker-supported drives" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2091", - "title": "Resume BitLocker protection on all drives" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2093", - "title": "Ensure BitLocker drive compatibility" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_21", - "title": "Block webpages from automatically running Flash plugins" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_22", - "title": "Disable 'Password Manager'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_23", - "title": "Enable 'Block third party cookies'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_24", - "title": "Set 'Remote Desktop security level' to 'TLS'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_25", - "title": "Enable 'Local Security Authority (LSA) protection'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2500", - "title": "Block executable content from email client and webmail" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2501", - "title": "Block all Office applications from creating child processes" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2502", - "title": "Block Office applications from creating executable content" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2503", - "title": "Block Office applications from injecting code into other processes" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2504", - "title": "Block JavaScript or VBScript from launching downloaded executable content" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2505", - "title": "Block execution of potentially obfuscated scripts" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2506", - "title": "Block Win32 API calls from Office macros" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2507", - "title": "Block executable files from running unless they meet a prevalence, age, or trusted list criterion" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2508", - "title": "Use advanced protection against ransomware" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2509", - "title": "Block credential stealing from the Windows local security authority subsystem (lsass.exe)" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2510", - "title": "Block process creations originating from PSExec and WMI commands" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2511", - "title": "Block untrusted and unsigned processes that run from USB" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2512", - "title": "Block Office communication application from creating child processes" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2513", - "title": "Block Adobe Reader from creating child processes" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2514", - "title": "Block persistence through WMI event subscription" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_2515", - "title": "Block abuse of exploited vulnerable signed drivers" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_26", - "title": "Enable 'Safe DLL Search Mode'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_27", - "title": "Set User Account Control (UAC) to automatically deny elevation requests" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_28", - "title": "Set 'Interactive logon: Machine inactivity limit' to '1-900 seconds'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_29", - "title": "Disable 'Enumerate administrator accounts on elevation'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_30", - "title": "Disable 'Insecure guest logons' in SMB" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_3001", - "title": "Fix unquoted service path for Windows services" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_3002", - "title": "Change service executable path to a common protected location" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_3003", - "title": "Change service account to avoid cached password in windows registry" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_3010", - "title": "Disable the built-in Administrator account" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_3011", - "title": "Disable the built-in Guest account" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_32", - "title": "Set 'Minimum password length' to '14 or more characters'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_33", - "title": "Set 'Enforce password history' to '24 or more password(s)'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_34", - "title": "Set 'Maximum password age' to '60 or fewer days, but not 0'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_35", - "title": "Set 'Minimum password age' to '1 or more day(s)'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_36", - "title": "Enable 'Domain member: Require strong (Windows 2000 or later) session key'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_37", - "title": "Enable 'Domain member: Digitally encrypt or sign secure channel data (always)'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_38", - "title": "Enable Set 'Domain member: Digitally encrypt secure channel data (when possible)'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_39", - "title": "Enable 'Domain member: Digitally sign secure channel data (when possible)'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_40", - "title": "Disable 'Domain member: Disable machine account password changes'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_4000", - "title": "Disallow offline access to shares" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_4001", - "title": "Remove share write permission set to ‘Everyone’" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_4002", - "title": "Remove shares from the root folder" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_4003", - "title": "Set folder access-based enumeration for shares" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_41", - "title": "Set 'Account lockout duration' to 15 minutes or more" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_42", - "title": "Set 'Reset account lockout counter after' to 15 minutes or more" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_43", - "title": "Disable Microsoft Defender Firewall notifications when programs are blocked for Domain profile" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_44", - "title": "Set 'Account lockout threshold' to 1-10 invalid login attempts" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_45", - "title": "Set user authentication for remote connections by using Network Level Authentication to 'Enabled'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_46", - "title": "Disable Microsoft Defender Firewall notifications when programs are blocked for Private profile" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_49", - "title": "Disable Microsoft Defender Firewall notifications when programs are blocked for Public profile" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_50", - "title": "Disable merging of local Microsoft Defender Firewall rules with group policy firewall rules for the Public profile" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5001", - "title": "Fix Microsoft Defender for Endpoint sensor data collection in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5002", - "title": "Fix Microsoft Defender for Endpoint impaired communications in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5003", - "title": "Set minimum password length to 15 or more characters in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5004", - "title": "Set 'Enforce password history' to '24 or more password(s)' in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5005", - "title": "Set 'Maximum password age' to '90 or fewer days, but not 0' in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5006", - "title": "Set account lockout threshold to 5 or lower in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5007", - "title": "Turn on Firewall in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5009", - "title": "Enable Gatekeeper in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5010", - "title": "Enable System Integrity Protection (SIP) in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5011", - "title": "Enable FileVault Disk Encryption in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5013", - "title": "Ensure screensaver is set to start in 20 minutes or less in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5014", - "title": "Secure Home Folders in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5090", - "title": "Turn on Microsoft Defender Antivirus real-time protection in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5091", - "title": "Turn on Microsoft Defender Antivirus PUA protection in block mode in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5094", - "title": "Enable Microsoft Defender Antivirus cloud-delivered protection in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_5095", - "title": "Update Microsoft Defender Antivirus definitions in macOS" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_51", - "title": "Disable merging of local Microsoft Defender Firewall connection rules with group policy firewall rules for the Public profile" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_52", - "title": "Enable 'Apply UAC restrictions to local accounts on network logons'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_53", - "title": "Disable SMBv1 client driver" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_54", - "title": "Disable SMBv1 server" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_55", - "title": "Disable 'Network access: Let Everyone permissions apply to anonymous users'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_57", - "title": "Disable 'WDigest Authentication'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_58", - "title": "Disable 'Installation and configuration of Network Bridge on your DNS domain network'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_59", - "title": "Enable 'Require domain users to elevate when setting a network's location'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_60", - "title": "Prohibit use of Internet Connection Sharing on your DNS domain network" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_6001", - "title": "Fix Microsoft Defender for Endpoint sensor data collection for Linux" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_6002", - "title": "Fix Microsoft Defender for Endpoint impaired communications for Linux" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_6014", - "title": "Unrestricted Access Accounts for Linux" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_6090", - "title": "Turn on Microsoft Defender Antivirus real-time protection for Linux" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_6091", - "title": "Turn on Microsoft Defender Antivirus PUA protection in block mode for Linux" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_6094", - "title": "Enable Microsoft Defender Antivirus cloud-delivered protection for Linux" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_6095", - "title": "Update Microsoft Defender Antivirus definitions for Linux" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_61", - "title": "Set 'Minimum PIN length for startup' to '6 or more characters'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_62", - "title": "Enable 'Require additional authentication at startup'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_63", - "title": "Disable 'Configure Offer Remote Assistance'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_64", - "title": "Restrict anonymous access to named pipes and Shares" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_65", - "title": "Disable 'Store LAN Manager hash value on next password change'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_66", - "title": "Disable 'Always install with elevated privileges'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_67", - "title": "Disable 'Autoplay for non-volume devices'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_68", - "title": "Disable 'Anonymous enumeration of SAM accounts'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_69", - "title": "Disable 'Autoplay' for all drives" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_70", - "title": "Set default behavior for 'AutoRun' to 'Enabled: Do not execute any autorun commands'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_71", - "title": "Enable 'Limit local account use of blank passwords to console logon only'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_72", - "title": "Set LAN Manager authentication level to 'Send NTLMv2 response only. Refuse LM & NTLM'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_73", - "title": "Disable 'Allow Basic authentication' for WinRM Client" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_74", - "title": "Disable 'Allow Basic authentication' for WinRM Service" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_75", - "title": "Disable Flash on Adobe Reader DC" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_76", - "title": "Disable JavaScript on Adobe Reader DC" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_77", - "title": "Disable Flash on Adobe Acrobat Pro XI" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_78", - "title": "Disable JavaScript on Adobe Acrobat Pro XI" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_79", - "title": "Disable running or installing downloaded software with invalid signature" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_80", - "title": "Block Flash activation in Office documents" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_81", - "title": "Set IPv6 source routing to highest protection" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_82", - "title": "Disable IP source routing" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_83", - "title": "Enable Explorer Data Execution Prevention (DEP)" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_85", - "title": "Block outdated ActiveX controls for Internet Explorer" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_87", - "title": "Disable Solicited Remote Assistance" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_88", - "title": "Disable Anonymous enumeration of shares" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_89", - "title": "Enable scanning of removable drives during a full scan" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_9", - "title": "Enable 'Local Machine Zone Lockdown Security'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_90", - "title": "Enable Microsoft Defender Antivirus email scanning" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_91", - "title": "Enable Microsoft Defender Antivirus real-time behavior monitoring" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_92", - "title": "Enable Microsoft Defender Antivirus scanning of downloaded files and attachments" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_93", - "title": "Disable the local storage of passwords and credentials" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_94", - "title": "Disable sending unencrypted password to third-party SMB servers" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_95", - "title": "Enable 'Microsoft network client: Digitally sign communications (always)'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_96", - "title": "Enable 'Network Protection'" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_97", - "title": "Disable JavaScript on Adobe DC" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_98", - "title": "Disable JavaScript on Adobe Reader 2017" - }, - { - "service": "MDATP", - "tier": "Core", - "userImpact": "Unknown", - "vendorInformation": { - "provider": "SecureScore", - "providerVersion": null, - "subProvider": null, - "vendor": "Microsoft" - }, - "id": "scid_99", - "title": "Disable JavaScript on Adobe Acrobat 2017" - }, { "service": "AzureAD", "tier": "Defense In Depth", @@ -5301,7 +3312,7 @@ "vendor": "Microsoft" }, "id": "SelfServicePasswordReset", - "title": "Ensure 'Self service password reset enabled' is set to 'All'" + "title": "Ensure \u0027Self service password reset enabled\u0027 is set to \u0027All\u0027" }, { "service": "AzureAD", @@ -5353,7 +3364,7 @@ "vendor": "Microsoft" }, "id": "spo_external_users_sharing", - "title": "Ensure that SharePoint guest users cannot share items they don't own" + "title": "Ensure that SharePoint guest users cannot share items they don\u0027t own" }, { "service": "SPO", diff --git a/public/version.json b/public/version.json index 1194adaf2480..7a3adc4ddaba 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "8.7.1" + "version": "8.8.2" } \ No newline at end of file diff --git a/public/version_latest.txt b/public/version_latest.txt deleted file mode 100644 index 5e39348ef037..000000000000 --- a/public/version_latest.txt +++ /dev/null @@ -1 +0,0 @@ -99.99.99 \ No newline at end of file diff --git a/src/components/CippCards/CippBannerListCard.jsx b/src/components/CippCards/CippBannerListCard.jsx index 7c96e641df6e..c1fe171cd4e2 100644 --- a/src/components/CippCards/CippBannerListCard.jsx +++ b/src/components/CippCards/CippBannerListCard.jsx @@ -116,7 +116,7 @@ export const CippBannerListCard = (props) => { {/* Main Text and Subtext */} - + { }, }} > - + {item?.icon && ( - + {item.icon} )} @@ -48,14 +52,27 @@ export const CippInfoBar = ({ data, isFetching }) => { { if (!item?.icon) { - return { pl: 2 }; + return { pl: 2, minWidth: 0, flex: 1 }; } + return { minWidth: 0, flex: 1 }; }} > - + {item.name} - + {isFetching ? : item.data} @@ -64,14 +81,27 @@ export const CippInfoBar = ({ data, isFetching }) => { { if (!item?.icon) { - return { pl: 2 }; + return { pl: 2, minWidth: 0, flex: 1 }; } + return { minWidth: 0, flex: 1 }; }} > - + {item.name} - + {isFetching ? : item.data} diff --git a/src/components/CippComponents/AuthMethodSankey.jsx b/src/components/CippComponents/AuthMethodSankey.jsx new file mode 100644 index 000000000000..6ef4e61e666d --- /dev/null +++ b/src/components/CippComponents/AuthMethodSankey.jsx @@ -0,0 +1,45 @@ +import { CippSankey } from "./CippSankey"; + +export const AuthMethodSankey = ({ data }) => { + return ( + + ); +}; diff --git a/src/components/CippComponents/CaDeviceSankey.jsx b/src/components/CippComponents/CaDeviceSankey.jsx new file mode 100644 index 000000000000..81f49ac38acb --- /dev/null +++ b/src/components/CippComponents/CaDeviceSankey.jsx @@ -0,0 +1,33 @@ +import { CippSankey } from "./CippSankey"; + +export const CaDeviceSankey = ({ data }) => { + return ( + + ); +}; diff --git a/src/components/CippComponents/CaSankey.jsx b/src/components/CippComponents/CaSankey.jsx new file mode 100644 index 000000000000..5b860e45dda5 --- /dev/null +++ b/src/components/CippComponents/CaSankey.jsx @@ -0,0 +1,33 @@ +import { CippSankey } from "./CippSankey"; + +export const CaSankey = ({ data }) => { + return ( + + ); +}; diff --git a/src/components/CippComponents/CippAddDomainDrawer.jsx b/src/components/CippComponents/CippAddDomainDrawer.jsx new file mode 100644 index 000000000000..f5017f3fa379 --- /dev/null +++ b/src/components/CippComponents/CippAddDomainDrawer.jsx @@ -0,0 +1,121 @@ +import { useState, useEffect } from "react"; +import { Button, Box, Alert } from "@mui/material"; +import { useForm, useFormState } from "react-hook-form"; +import { AddCircleOutline } from "@mui/icons-material"; +import { CippOffCanvas } from "./CippOffCanvas"; +import { CippApiResults } from "./CippApiResults"; +import { useSettings } from "../../hooks/use-settings"; +import { ApiPostCall } from "../../api/ApiCall"; +import { Stack } from "@mui/system"; +import { CippFormComponent } from "./CippFormComponent"; + +export const CippAddDomainDrawer = ({ + buttonText = "Add Domain", + requiredPermissions = [], + PermissionButton = Button, +}) => { + const [drawerVisible, setDrawerVisible] = useState(false); + const userSettingsDefaults = useSettings(); + + const formControl = useForm({ + mode: "onChange", + defaultValues: { + tenantFilter: userSettingsDefaults.currentTenant, + }, + }); + + const createDomain = ApiPostCall({ + datafromUrl: true, + relatedQueryKeys: [`Domains - ${userSettingsDefaults.currentTenant}`], + }); + + const { isValid, isDirty } = useFormState({ control: formControl.control }); + + useEffect(() => { + if (createDomain.isSuccess) { + formControl.reset({ + tenantFilter: userSettingsDefaults.currentTenant, + }); + } + }, [createDomain.isSuccess]); + + const handleSubmit = (values) => { + createDomain.mutate({ + url: "/api/AddDomain", + data: values, + }); + }; + + const handleCloseDrawer = () => { + setDrawerVisible(false); + formControl.reset({ + tenantFilter: userSettingsDefaults.currentTenant, + }); + }; + + const formFields = [ + { + type: "textField", + name: "domain", + label: "Domain Name", + placeholder: "example.com", + required: "Domain name is required", + }, + ]; + + return ( + <> + setDrawerVisible(true)} + startIcon={} + > + {buttonText} + + + +
+ + +
+
+ } + > + + + Add a new domain to the current tenant. Ensure that the appropriate DNS records are + configured by checking the verification and service records after adding the domain. You + can find these in the "More info" section once the domain is added. + + + + + + ); +}; diff --git a/src/components/CippComponents/CippAddUserDrawer.jsx b/src/components/CippComponents/CippAddUserDrawer.jsx index b202679e545a..6e8e333b317f 100644 --- a/src/components/CippComponents/CippAddUserDrawer.jsx +++ b/src/components/CippComponents/CippAddUserDrawer.jsx @@ -3,7 +3,6 @@ import { Button, Box } from "@mui/material"; import { useForm, useWatch, useFormState } from "react-hook-form"; import { PersonAdd } from "@mui/icons-material"; import { CippOffCanvas } from "./CippOffCanvas"; -import { CippFormUserSelector } from "./CippFormUserSelector"; import { CippApiResults } from "./CippApiResults"; import { useSettings } from "../../hooks/use-settings"; import { ApiPostCall } from "../../api/ApiCall"; diff --git a/src/components/CippComponents/CippAddVacationModeDrawer.jsx b/src/components/CippComponents/CippAddVacationModeDrawer.jsx index 45fb368fe970..9434b456547e 100644 --- a/src/components/CippComponents/CippAddVacationModeDrawer.jsx +++ b/src/components/CippComponents/CippAddVacationModeDrawer.jsx @@ -28,6 +28,8 @@ export const CippAddVacationModeDrawer = ({ PolicyId: null, startDate: null, endDate: null, + reference: null, + postExecution: [], excludeLocationAuditAlerts: false, }, }); @@ -85,6 +87,8 @@ export const CippAddVacationModeDrawer = ({ PolicyId: null, startDate: null, endDate: null, + reference: null, + postExecution: [], excludeLocationAuditAlerts: false, }); } @@ -105,6 +109,8 @@ export const CippAddVacationModeDrawer = ({ StartDate: formData.startDate, EndDate: formData.endDate, vacation: true, + reference: formData.reference || null, + postExecution: formData.postExecution || [], excludeLocationAuditAlerts: formData.excludeLocationAuditAlerts || false, }; @@ -124,6 +130,8 @@ export const CippAddVacationModeDrawer = ({ PolicyId: null, startDate: null, endDate: null, + reference: null, + postExecution: [], }); }; @@ -295,6 +303,33 @@ export const CippAddVacationModeDrawer = ({ }} /> + + {/* Post Execution Actions */} + + + + + + + {policyHasLocationTarget && ( { const handleActionClick = (row, action, formData) => { setIsFormSubmitted(true); let finalData = {}; + let isBulkRequest = false; if (typeof api?.customDataformatter === "function") { finalData = api.customDataformatter(row, action, formData); + // If customDataformatter returns an array, enable bulk request mode + isBulkRequest = Array.isArray(finalData); } else { if (action.multiPost === undefined) action.multiPost = false; @@ -201,7 +204,7 @@ export const CippApiDialog = (props) => { if (action.type === "POST") { actionPostRequest.mutate({ url: action.url, - bulkRequest: false, + bulkRequest: isBulkRequest, data: finalData, }); } else if (action.type === "GET") { @@ -209,7 +212,7 @@ export const CippApiDialog = (props) => { url: action.url, waiting: true, queryKey: Date.now(), - bulkRequest: false, + bulkRequest: isBulkRequest, data: finalData, }); } diff --git a/src/components/CippComponents/CippApplicationDeployDrawer.jsx b/src/components/CippComponents/CippApplicationDeployDrawer.jsx index b8b095937725..16384787e6ec 100644 --- a/src/components/CippComponents/CippApplicationDeployDrawer.jsx +++ b/src/components/CippComponents/CippApplicationDeployDrawer.jsx @@ -241,7 +241,7 @@ export const CippApplicationDeployDrawer = ({ - Provide a custom Office Configuration XML. When using custom XML, all other - Office configuration options above will be ignored. See{" "} - + Provide a custom Office Configuration XML. When using custom XML, all other Office + configuration options above will be ignored. See{" "} + Office Customization Tool {" "} to generate XML. diff --git a/src/components/CippComponents/CippAutocomplete.jsx b/src/components/CippComponents/CippAutocomplete.jsx index 4532c6a035bb..daaa6dda867a 100644 --- a/src/components/CippComponents/CippAutocomplete.jsx +++ b/src/components/CippComponents/CippAutocomplete.jsx @@ -26,29 +26,31 @@ const MemoTextField = React.memo(function MemoTextField({ const { InputProps, ...otherParams } = params; return ( - + + }} + /> + ); }); diff --git a/src/components/CippComponents/CippBreadcrumbNav.jsx b/src/components/CippComponents/CippBreadcrumbNav.jsx index 9a5ad7c807cd..ff146073ae1c 100644 --- a/src/components/CippComponents/CippBreadcrumbNav.jsx +++ b/src/components/CippComponents/CippBreadcrumbNav.jsx @@ -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", @@ -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) }); } } @@ -432,13 +499,54 @@ 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) { - 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); @@ -457,9 +565,43 @@ export const CippBreadcrumbNav = () => { // Render based on mode if (mode === "hierarchical") { - const breadcrumbs = buildHierarchicalBreadcrumbs(); + let breadcrumbs = buildHierarchicalBreadcrumbs(); - // Don't show if no breadcrumbs found + // Fallback: If no breadcrumbs found in navigation config, generate from URL path + if (breadcrumbs.length === 0) { + const pathSegments = router.pathname.split("/").filter((segment) => segment); + + if (pathSegments.length > 0) { + breadcrumbs = pathSegments.map((segment, index) => { + // Build the path up to this segment + const path = "/" + pathSegments.slice(0, index + 1).join("/"); + + // Format segment as title (replace hyphens with spaces, capitalize words) + const title = segment + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); + + return { + title, + path, + type: "fallback", + query: index === pathSegments.length - 1 ? getCleanQueryParams(router.query) : {}, + }; + }); + + // If we have a current page title from document.title, use it for the last breadcrumb + if ( + currentPageTitle && + currentPageTitle !== "CIPP" && + !currentPageTitle.toLowerCase().includes("loading") + ) { + breadcrumbs[breadcrumbs.length - 1].title = cleanPageTitle(currentPageTitle); + } + } + } + + // Don't show if still no breadcrumbs found if (breadcrumbs.length === 0) { return null; } @@ -478,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) { @@ -488,31 +633,46 @@ export const CippBreadcrumbNav = () => { variant="subtitle2" sx={{ fontWeight: isLast ? 500 : 400 }} > - {crumb.title} + {displayTitle}
); } - // All items with paths are clickable, including the last one - return ( - 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} - - ); + // Items with valid paths are clickable + // Items with invalid paths (fallback) are shown as plain text + if (pathInfo.isValid) { + return ( + 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} + + ); + } else { + // Invalid path - show as text only + return ( + + {displayTitle} + + ); + } })}
diff --git a/src/components/CippComponents/CippCADeployDrawer.jsx b/src/components/CippComponents/CippCADeployDrawer.jsx index 419663a2ebd6..6b2a4a633ff4 100644 --- a/src/components/CippComponents/CippCADeployDrawer.jsx +++ b/src/components/CippComponents/CippCADeployDrawer.jsx @@ -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", @@ -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; @@ -199,6 +204,25 @@ export const CippCADeployDrawer = ({ label="Disable Security Defaults if enabled when creating policy" formControl={formControl} /> + + + diff --git a/src/components/CippComponents/CippCentralSearch.jsx b/src/components/CippComponents/CippCentralSearch.jsx index 11e4b1e67a62..0c7bf858e88b 100644 --- a/src/components/CippComponents/CippCentralSearch.jsx +++ b/src/components/CippComponents/CippCentralSearch.jsx @@ -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", @@ -88,14 +88,6 @@ async function loadTabOptions() { */ function filterItemsByPermissionsAndRoles(items, userPermissions, userRoles) { return items.filter((item) => { - // Check roles if specified - if (item.roles && item.roles.length > 0) { - const hasRole = item.roles.some((requiredRole) => userRoles.includes(requiredRole)); - if (!hasRole) { - return false; - } - } - // Check permissions with pattern matching support if (item.permissions && item.permissions.length > 0) { const hasPermission = userPermissions?.some((userPerm) => { diff --git a/src/components/CippComponents/CippDomainServiceConfigurationRecords.jsx b/src/components/CippComponents/CippDomainServiceConfigurationRecords.jsx new file mode 100644 index 000000000000..a6a75fd2d4f3 --- /dev/null +++ b/src/components/CippComponents/CippDomainServiceConfigurationRecords.jsx @@ -0,0 +1,134 @@ +import React, { useState } from "react"; +import { useSettings } from "../../hooks/use-settings"; +import { ApiGetCall } from "../../api/ApiCall"; +import { + Card, + CardContent, + CardHeader, + Stack, + Box, + IconButton, + Tooltip, + Typography, + Chip, + CircularProgress, +} from "@mui/material"; +import { ContentCopy, Check } from "@mui/icons-material"; + +const DnsRecordField = ({ label, value, copyable = true }) => { + const [copied, setCopied] = useState(false); + + const handleCopy = () => { + navigator.clipboard.writeText(value); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + + + {label} + + + {value} + + + {copyable && ( + + + {copied ? : } + + + )} + + ); +}; + +const renderRecordDetails = (record) => { + switch (record.recordType) { + case "Mx": + return ( + <> + + + + ); + case "Txt": + return ; + case "CName": + return ; + case "Srv": + return ( + <> + + + + + + + + ); + default: + return null; + } +}; + +export const CippDomainServiceConfigurationRecords = ({ row }) => { + const tenantFilter = useSettings().currentTenant; + + const recordsQuery = ApiGetCall({ + url: "/api/ListGraphRequest", + queryKey: `domain-service-config-${row.id}`, + waiting: true, + data: { + Endpoint: `domains/${row.id}/serviceConfigurationRecords`, + tenantFilter: tenantFilter, + }, + }); + + if (recordsQuery.isLoading) { + return ( + + + + ); + } + + if (recordsQuery.isError) { + return Failed to load records; + } + + const records = recordsQuery.data?.Results || []; + + if (records.length === 0) { + return No service configuration records found; + } + + return ( + + {records.map((record) => ( + + + {record.label} + + + + } + subheader={`TTL: ${record.ttl} | Optional: ${record.isOptional ? "Yes" : "No"}`} + sx={{ pb: 1 }} + /> + + {renderRecordDetails(record)} + + + ))} + + ); +}; diff --git a/src/components/CippComponents/CippDomainVerificationRecords.jsx b/src/components/CippComponents/CippDomainVerificationRecords.jsx new file mode 100644 index 000000000000..2a8a741cd264 --- /dev/null +++ b/src/components/CippComponents/CippDomainVerificationRecords.jsx @@ -0,0 +1,133 @@ +import React, { useState } from "react"; +import { useSettings } from "../../hooks/use-settings"; +import { ApiGetCall } from "../../api/ApiCall"; +import { + Card, + CardContent, + CardHeader, + Stack, + Box, + IconButton, + Tooltip, + Typography, + Chip, + CircularProgress, +} from "@mui/material"; +import { ContentCopy, Check } from "@mui/icons-material"; + +const DnsRecordField = ({ label, value, copyable = true }) => { + const [copied, setCopied] = useState(false); + + const handleCopy = () => { + navigator.clipboard.writeText(value); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + + + {label} + + + {value} + + + {copyable && ( + + + {copied ? : } + + + )} + + ); +}; + +const renderRecordDetails = (record) => { + switch (record.recordType) { + case "Txt": + return ; + case "Mx": + return ( + <> + + + + ); + case "CName": + return ; + case "Srv": + return ( + <> + + + + + + + + ); + default: + return null; + } +}; + +export const CippDomainVerificationRecords = ({ row }) => { + const tenantFilter = useSettings().currentTenant; + + const recordsQuery = ApiGetCall({ + url: "/api/ListGraphRequest", + queryKey: `domain-verification-${row.id}`, + waiting: true, + data: { + Endpoint: `domains/${row.id}/verificationDnsRecords`, + tenantFilter: tenantFilter, + }, + }); + + if (recordsQuery.isLoading) { + return ( + + + + ); + } + + if (recordsQuery.isError) { + return Failed to load records; + } + + const records = recordsQuery.data?.Results || []; + + if (records.length === 0) { + return No verification records found; + } + + return ( + + {records.map((record) => ( + + + {record.label} + + + } + subheader={`TTL: ${record.ttl} | Optional: ${record.isOptional ? "Yes" : "No"}`} + sx={{ pb: 1 }} + /> + + {renderRecordDetails(record)} + + + ))} + + ); +}; diff --git a/src/components/CippComponents/CippFormComponent.jsx b/src/components/CippComponents/CippFormComponent.jsx index 4aff78c756f0..5e76c4152ad1 100644 --- a/src/components/CippComponents/CippFormComponent.jsx +++ b/src/components/CippComponents/CippFormComponent.jsx @@ -11,6 +11,7 @@ import { Button, Box, Input, + Tooltip, } from "@mui/material"; import { CippAutoComplete } from "./CippAutocomplete"; import { CippTextFieldWithVariables } from "./CippTextFieldWithVariables"; @@ -121,23 +122,78 @@ export const CippFormComponent = (props) => { )} /> - - {get(errors, convertedName, {})?.message} - + {get(errors, convertedName, {})?.message && ( + + {get(errors, convertedName, {})?.message} + + )} ); case "textField": return ( <> -
- - !disableVariables ? ( + +
+ + !disableVariables ? ( + + ) : ( + + ) + } + /> +
+
+ {get(errors, convertedName, {})?.message && ( + + {get(errors, convertedName, {})?.message} + + )} + {helperText && ( + + {helperText} + + )} + + ); + case "textFieldWithVariables": + return ( + <> + +
+ ( { label={label} value={field.value || ""} onChange={field.onChange} + tenantFilter={tenantFilter} includeSystemVariables={includeSystemVariables} /> - ) : ( - - ) - } - /> -
- - {get(errors, convertedName, {})?.message} - - {helperText && ( - - {helperText} + )} + /> +
+ + {get(errors, convertedName, {})?.message && ( + + {get(errors, convertedName, {})?.message} )} - - ); - case "textFieldWithVariables": - return ( - <> -
- ( - - )} - /> -
- - {get(errors, convertedName, {})?.message} - {helperText && ( {helperText} @@ -216,22 +227,26 @@ export const CippFormComponent = (props) => { return ( <>
- + + +
- - {get(errors, convertedName, {})?.message} - + {get(errors, convertedName, {})?.message && ( + + {get(errors, convertedName, {})?.message} + + )} {helperText && ( {helperText} @@ -243,21 +258,25 @@ export const CippFormComponent = (props) => { return ( <>
- + + +
- - {get(errors, convertedName, {})?.message} - + {get(errors, convertedName, {})?.message && ( + + {get(errors, convertedName, {})?.message} + + )} {helperText && ( {helperText} @@ -285,9 +304,11 @@ export const CippFormComponent = (props) => { } /> - - {get(errors, convertedName, {})?.message} - + {get(errors, convertedName, {})?.message && ( + + {get(errors, convertedName, {})?.message} + + )} {helperText && ( {helperText} @@ -303,9 +324,11 @@ export const CippFormComponent = (props) => { - - {get(errors, convertedName, {})?.message} - + {get(errors, convertedName, {})?.message && ( + + {get(errors, convertedName, {})?.message} + + )} ); @@ -348,9 +371,11 @@ export const CippFormComponent = (props) => { }} /> - - {get(errors, convertedName, {})?.message} - + {get(errors, convertedName, {})?.message && ( + + {get(errors, convertedName, {})?.message} + + )} ); @@ -682,9 +707,11 @@ export const CippFormComponent = (props) => { )} /> - - {get(errors, convertedName, {})?.message} - + {get(errors, convertedName, {})?.message && ( + + {get(errors, convertedName, {})?.message} + + )} {helperText && ( {helperText} diff --git a/src/components/CippComponents/CippFormCondition.jsx b/src/components/CippComponents/CippFormCondition.jsx index 9ec49a57ef5c..dd9a48cbf95d 100644 --- a/src/components/CippComponents/CippFormCondition.jsx +++ b/src/components/CippComponents/CippFormCondition.jsx @@ -18,7 +18,7 @@ export const CippFormCondition = (props) => { if ( field === undefined || - compareValue === undefined || + (compareValue === undefined && compareType !== "hasValue") || children === undefined || formControl === undefined ) { @@ -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": diff --git a/src/components/CippComponents/CippFormDomainSelector.jsx b/src/components/CippComponents/CippFormDomainSelector.jsx index 8d9cbea7dca2..71b6e9753455 100644 --- a/src/components/CippComponents/CippFormDomainSelector.jsx +++ b/src/components/CippComponents/CippFormDomainSelector.jsx @@ -38,11 +38,13 @@ export const CippFormDomainSelector = ({ }, dataFilter: (domains) => { // Always sort domains so that the default domain appears first - return domains.sort((a, b) => { - if (a.addedFields?.isDefault === true) return -1; - if (b.addedFields?.isDefault === true) return 1; - return 0; - }); + return domains + .filter((domain) => domain?.addedFields?.isVerified === true) + .sort((a, b) => { + if (a.addedFields?.isDefault === true) return -1; + if (b.addedFields?.isDefault === true) return 1; + return 0; + }); }, }), [currentTenant, selectedTenant, preselectDefaultDomain, multiple] diff --git a/src/components/CippComponents/CippNotificationForm.jsx b/src/components/CippComponents/CippNotificationForm.jsx index bd7a5ce3887c..f02f23cf211b 100644 --- a/src/components/CippComponents/CippNotificationForm.jsx +++ b/src/components/CippComponents/CippNotificationForm.jsx @@ -5,6 +5,7 @@ import CippFormComponent from "./CippFormComponent"; import { ApiGetCall } from "../../api/ApiCall"; import { useDialog } from "../../hooks/use-dialog"; import { CippApiDialog } from "./CippApiDialog"; +import { useFormState } from "react-hook-form"; export const CippNotificationForm = ({ formControl, @@ -19,6 +20,8 @@ export const CippNotificationForm = ({ queryKey: "ListNotificationConfig", }); + const formState = useFormState({ control: formControl.control }); + // Define log types and severity types const logTypes = [ { label: "Updates Status", value: "Updates" }, @@ -82,6 +85,7 @@ export const CippNotificationForm = ({ label="Email Addresses (Comma separated)" name="email" formControl={formControl} + helperText="Enter one or more email addresses to receive notifications." /> @@ -91,6 +95,7 @@ export const CippNotificationForm = ({ label="Webhook URL" name="webhook" formControl={formControl} + helperText="Enter the webhook URL to send notifications to. The URL should be configured to receive a POST request." /> @@ -134,7 +139,11 @@ export const CippNotificationForm = ({ {showTestButton && ( - @@ -179,6 +188,7 @@ export const CippNotificationForm = ({ text: "This is a test from Notification Settings", }), }} + allowResubmit={true} /> )} diff --git a/src/components/CippComponents/CippRestoreBackupDrawer.jsx b/src/components/CippComponents/CippRestoreBackupDrawer.jsx index f011b499820f..b8782aa6d401 100644 --- a/src/components/CippComponents/CippRestoreBackupDrawer.jsx +++ b/src/components/CippComponents/CippRestoreBackupDrawer.jsx @@ -13,6 +13,7 @@ import { ApiPostCall } from "../../api/ApiCall"; export const CippRestoreBackupDrawer = ({ buttonText = "Restore Backup", backupName = null, + backupData = null, requiredPermissions = [], PermissionButton = Button, ...props @@ -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, @@ -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: { diff --git a/src/components/CippComponents/CippSankey.jsx b/src/components/CippComponents/CippSankey.jsx new file mode 100644 index 000000000000..9fd49e3bc712 --- /dev/null +++ b/src/components/CippComponents/CippSankey.jsx @@ -0,0 +1,63 @@ +import { ResponsiveSankey } from "@nivo/sankey"; +import { useSettings } from "/src/hooks/use-settings"; + +export const CippSankey = ({ data }) => { + const settings = useSettings(); + const isDark = settings.currentTheme?.value === "dark"; + + const theme = { + tooltip: { + container: { + background: isDark ? "rgba(33, 33, 33, 0.95)" : "rgba(255, 255, 255, 0.95)", + color: isDark ? "#ffffff" : "#000000", + border: isDark ? "1px solid #555" : "1px solid #ccc", + borderRadius: "4px", + boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)", + fontSize: "12px", + padding: "8px 12px", + }, + }, + labels: { + text: { + fontSize: 12, + }, + }, + }; + + return ( +
+ node.nodeColor} + nodeOpacity={1} + nodeHoverOthersOpacity={0.35} + nodeThickness={18} + nodeSpacing={24} + nodeBorderWidth={0} + nodeBorderColor={{ + from: "color", + modifiers: [["darker", 0.8]], + }} + nodeBorderRadius={3} + linkOpacity={0.5} + linkHoverOthersOpacity={0.1} + linkContract={3} + linkBlendMode={isDark ? "lighten" : "multiply"} + enableLinkGradient={true} + labelPosition="inside" + labelOrientation="horizontal" + labelPadding={16} + labelTextColor={isDark ? "#ffffff" : "#000000"} + sort="input" + legends={[]} + valueFormat={(value) => `${value}`} + /> +
+ ); +}; diff --git a/src/components/CippComponents/CippTenantSelector.jsx b/src/components/CippComponents/CippTenantSelector.jsx index 36dc655be6be..688afab83bb8 100644 --- a/src/components/CippComponents/CippTenantSelector.jsx +++ b/src/components/CippComponents/CippTenantSelector.jsx @@ -19,18 +19,22 @@ import { ServerIcon, UsersIcon, } from "@heroicons/react/24/outline"; -import { useEffect, useState, useMemo } from "react"; +import { useEffect, useState, useMemo, useCallback, useRef } from "react"; import { useRouter } from "next/router"; import { CippOffCanvas } from "./CippOffCanvas"; import { useSettings } from "../../hooks/use-settings"; import { getCippError } from "../../utils/get-cipp-error"; +import { useQueryClient } from "@tanstack/react-query"; export const CippTenantSelector = (props) => { const { width, allTenants = false, multiple = false, refreshButton, tenantButton } = props; //get the current tenant from SearchParams called 'tenantFilter' const router = useRouter(); const settings = useSettings(); + const queryClient = useQueryClient(); const tenant = router.query.tenantFilter ? router.query.tenantFilter : settings.currentTenant; + const routerUpdateTimeoutRef = useRef(null); + // Fetch tenant list const tenantList = ApiGetCall({ url: "/api/listTenants", @@ -172,6 +176,15 @@ export const CippTenantSelector = (props) => { if (currentTenant?.value) { const query = { ...router.query }; if (query.tenantFilter !== currentTenant.value) { + // Clear any pending timeout + if (routerUpdateTimeoutRef.current) { + clearTimeout(routerUpdateTimeoutRef.current); + } + + // Cancel all in-flight queries before changing tenant + queryClient.cancelQueries(); + + // Update router only - let the URL watcher handle settings query.tenantFilter = currentTenant.value; router.replace( { @@ -182,41 +195,47 @@ export const CippTenantSelector = (props) => { { 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) 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]); + }, [router.isReady, router.query.tenantFilter, tenantList.isSuccess]); // This effect ensures the tenant filter parameter is included in the URL when missing useEffect(() => { @@ -234,7 +253,7 @@ export const CippTenantSelector = (props) => { { shallow: true } ); } - }, [router.isReady, router.query, settings.currentTenant]); + }, [router.isReady, router.query.tenantFilter, settings.currentTenant]); useEffect(() => { if (tenant && currentTenant?.value && currentTenant?.value !== "AllTenants") { @@ -268,6 +287,15 @@ export const CippTenantSelector = (props) => { } }, [tenant, tenantList.isSuccess, currentTenant]); + // Cleanup on unmount + useEffect(() => { + return () => { + if (routerUpdateTimeoutRef.current) { + clearTimeout(routerUpdateTimeoutRef.current); + } + }; + }, []); + return ( <> { type: "GET", icon: , link: "/identity/administration/users/user/bec?userId=[id]", - confirmText: "Are you sure you want to research this compromised account?", + confirmText: + "Are you sure you want to research if [userPrincipalName] is a compromised account?", multiPost: false, }, { @@ -173,7 +174,8 @@ export const useCippUserActions = () => { dateTimeType: "datetime", }, ], - confirmText: "Are you sure you want to create a Temporary Access Password?", + confirmText: + "Are you sure you want to create a Temporary Access Password for [userPrincipalName]?", multiPost: false, condition: () => canWriteUser, }, @@ -184,7 +186,7 @@ export const useCippUserActions = () => { icon: , url: "/api/ExecResetMFA", data: { ID: "userPrincipalName" }, - confirmText: "Are you sure you want to reset MFA for this user?", + confirmText: "Are you sure you want to reset MFA for [userPrincipalName]?", multiPost: false, condition: () => canWriteUser, }, @@ -195,7 +197,7 @@ export const useCippUserActions = () => { icon: , url: "/api/ExecSendPush", data: { UserEmail: "userPrincipalName" }, - confirmText: "Are you sure you want to send an MFA request?", + confirmText: "Are you sure you want to send an MFA request to [userPrincipalName]?", multiPost: false, }, { @@ -281,6 +283,7 @@ export const useCippUserActions = () => { icon: , url: "/api/EditGroup", customDataformatter: (row, action, formData) => { + // Build the member list from selected users let addMember = []; if (Array.isArray(row)) { row @@ -305,20 +308,26 @@ export const useCippUserActions = () => { }, }); } - return { + + // Handle multiple groups - return an array of requests (one per group) + const selectedGroups = Array.isArray(formData.groupId) + ? formData.groupId + : [formData.groupId]; + + return selectedGroups.map((group) => ({ addMember: addMember, tenantFilter: tenant, - groupId: formData.groupId, - }; + groupId: group, + })); }, fields: [ { type: "autoComplete", name: "groupId", - label: "Select a group to add the user to", - multiple: false, + label: "Select groups to add the user to", + multiple: true, creatable: false, - validators: { required: "Please select a group" }, + validators: { required: "Please select at least one group" }, api: { url: "/api/ListGroups", labelField: (option) => @@ -335,8 +344,8 @@ export const useCippUserActions = () => { }, }, ], - confirmText: "Are you sure you want to add [userPrincipalName] to this group?", - multiPost: true, + confirmText: "Are you sure you want to add [userPrincipalName] to the selected groups?", + multiPost: false, allowResubmit: true, condition: () => canWriteGroup, }, @@ -403,7 +412,7 @@ export const useCippUserActions = () => { icon: , url: "/api/ExecOneDriveProvision", data: { UserPrincipalName: "userPrincipalName" }, - confirmText: "Are you sure you want to pre-provision OneDrive for this user?", + confirmText: "Are you sure you want to pre-provision OneDrive for [userPrincipalName]?", multiPost: false, condition: () => canWriteUser, }, diff --git a/src/components/CippComponents/DesktopDevicesSankey.jsx b/src/components/CippComponents/DesktopDevicesSankey.jsx new file mode 100644 index 000000000000..4ffd02318ab8 --- /dev/null +++ b/src/components/CippComponents/DesktopDevicesSankey.jsx @@ -0,0 +1,50 @@ +import { CippSankey } from "./CippSankey"; + +export const DesktopDevicesSankey = ({ data }) => { + //temporary mock sankey for dash - dont delete until replaced. + return ( + + ); +}; diff --git a/src/components/CippComponents/MobileSankey.jsx b/src/components/CippComponents/MobileSankey.jsx new file mode 100644 index 000000000000..56da01903dc9 --- /dev/null +++ b/src/components/CippComponents/MobileSankey.jsx @@ -0,0 +1,49 @@ +import { CippSankey } from "./CippSankey"; + +export const MobileSankey = ({ data }) => { + return ( + + ); +}; diff --git a/src/components/CippComponents/ScheduledTaskDetails.jsx b/src/components/CippComponents/ScheduledTaskDetails.jsx index dd1255dbf7ec..ba52c316646a 100644 --- a/src/components/CippComponents/ScheduledTaskDetails.jsx +++ b/src/components/CippComponents/ScheduledTaskDetails.jsx @@ -272,6 +272,7 @@ const ScheduledTaskDetails = ({ data, showActions = true }) => { noCard data={result.Results} disablePagination={result.Results.length <= 10} + refreshFunction={() => taskDetailResults.refetch()} /> ) : typeof result.Results === "object" ? ( { name="postExecution.psa" formControl={formControl} /> + diff --git a/src/components/CippFormPages/CippSchedulerForm.jsx b/src/components/CippFormPages/CippSchedulerForm.jsx index f4fa7296bb44..f134461bce8c 100644 --- a/src/components/CippFormPages/CippSchedulerForm.jsx +++ b/src/components/CippFormPages/CippSchedulerForm.jsx @@ -100,6 +100,8 @@ const CippSchedulerForm = (props) => { { value: "0", label: "Once" }, { value: "1d", label: "Every 1 day" }, { value: "7d", label: "Every 7 days" }, + { value: "14d", label: "Every 14 days" }, + { value: "21d", label: "Every 21 days" }, { value: "30d", label: "Every 30 days" }, { value: "365d", label: "Every 365 days" }, ]; @@ -568,6 +570,16 @@ const CippSchedulerForm = (props) => { /> + + + + {/* Divider */} diff --git a/src/components/CippIntegrations/CippApiClientManagement.jsx b/src/components/CippIntegrations/CippApiClientManagement.jsx index 13cb698b8c56..4f42eb3d99a7 100644 --- a/src/components/CippIntegrations/CippApiClientManagement.jsx +++ b/src/components/CippIntegrations/CippApiClientManagement.jsx @@ -253,10 +253,11 @@ const CippApiClientManagement = () => { showDivider={false} isFetching={azureConfig.isFetching} /> - {azureConfig.isSuccess && ( + {azureConfig.isSuccess && apiClients.isSuccess && ( <> {!isEqual( - apiClients.data?.pages?.[0]?.Results?.filter((c) => c.Enabled) + (apiClients.data?.pages?.[0]?.Results || []) + .filter((c) => c.Enabled) .map((c) => c.ClientId) .sort(), (azureConfig.data?.Results?.ClientIDs || []).sort() @@ -264,7 +265,8 @@ const CippApiClientManagement = () => { You have unsaved changes. Click Actions > Save Azure Configuration to update - the allowed API Clients. + the allowed API Clients. If you've just saved your API clients, try refreshing the + configuration first. )} diff --git a/src/components/CippIntegrations/CippIntegrationTenantMapping.jsx b/src/components/CippIntegrations/CippIntegrationTenantMapping.jsx index fe0039c63799..57680d8bae7d 100644 --- a/src/components/CippIntegrations/CippIntegrationTenantMapping.jsx +++ b/src/components/CippIntegrations/CippIntegrationTenantMapping.jsx @@ -10,7 +10,7 @@ import { Typography, } from "@mui/material"; import { Grid } from "@mui/system"; -import { useState } from "react"; +import { useState, useMemo } from "react"; import { useForm } from "react-hook-form"; import { ApiGetCall, ApiPostCall } from "/src/api/ApiCall"; import { useRouter } from "next/router"; @@ -144,6 +144,11 @@ const CippIntegrationSettings = ({ children }) => { const extension = extensions.find((extension) => extension.id === router.query.id); + // Memoize the removeOptions array to ensure it updates when tableData changes + const removedTenantIds = useMemo(() => { + return Array.isArray(tableData) ? tableData.map((item) => item.TenantId) : []; + }, [tableData]); + useEffect(() => { if (mappings.isSuccess) { setTableData(mappings.data.Mappings ?? []); @@ -173,7 +178,7 @@ const CippIntegrationSettings = ({ children }) => { multiple={false} required={false} disableClearable={false} - removeOptions={tableData.map((item) => item.TenantId)} + removeOptions={removedTenantIds} valueField="customerId" /> diff --git a/src/components/CippSettings/CippBackendCard.jsx b/src/components/CippSettings/CippBackendCard.jsx index 4438f0b813a8..2b0890b0f340 100644 --- a/src/components/CippSettings/CippBackendCard.jsx +++ b/src/components/CippSettings/CippBackendCard.jsx @@ -5,7 +5,7 @@ import { CippOffCanvas } from "../CippComponents/CippOffCanvas"; import { useState } from "react"; import { getCippTranslation } from "/src/utils/get-cipp-translation"; -export const CippBackendCard = ({ backendComponents, item }) => { +export const CippBackendCard = ({ backendComponents, item, hosted }) => { const [open, setOpen] = useState(false); const BackendButton = () => { @@ -30,7 +30,7 @@ export const CippBackendCard = ({ backendComponents, item }) => { variant="contained" size="small" onClick={() => setOpen(true)} - disabled={backendComponents.isFetching} + disabled={backendComponents.isFetching || hosted} startIcon={ item.offcanvasIcon ? {item.offcanvasIcon} : "" } diff --git a/src/components/CippSettings/CippBackupRetentionSettings.jsx b/src/components/CippSettings/CippBackupRetentionSettings.jsx new file mode 100644 index 000000000000..bcd74ec4bb5d --- /dev/null +++ b/src/components/CippSettings/CippBackupRetentionSettings.jsx @@ -0,0 +1,101 @@ +import { Button, ButtonGroup, SvgIcon, Typography, TextField, Box } from "@mui/material"; +import CippButtonCard from "/src/components/CippCards/CippButtonCard"; +import { ApiGetCall, ApiPostCall } from "/src/api/ApiCall"; +import { History } from "@mui/icons-material"; +import { useState, useEffect } from "react"; + +const CippBackupRetentionSettings = () => { + const retentionSetting = ApiGetCall({ + url: "/api/ExecBackupRetentionConfig?List=true", + queryKey: "BackupRetentionSettings", + }); + + const retentionChange = ApiPostCall({ + datafromUrl: true, + relatedQueryKeys: "BackupRetentionSettings", + }); + + const [retentionDays, setRetentionDays] = useState(30); + const [error, setError] = useState(""); + + useEffect(() => { + if (retentionSetting?.data?.Results?.RetentionDays) { + setRetentionDays(retentionSetting.data.Results.RetentionDays); + } + }, [retentionSetting.data]); + + const handleRetentionChange = () => { + const days = parseInt(retentionDays); + + if (isNaN(days) || days < 7) { + setError("Retention must be at least 7 days"); + return; + } + + setError(""); + retentionChange.mutate({ + url: "/api/ExecBackupRetentionConfig", + data: { RetentionDays: days }, + queryKey: "BackupRetentionPost", + }); + }; + + const handleInputChange = (e) => { + const value = e.target.value; + setRetentionDays(value); + + const days = parseInt(value); + if (!isNaN(days) && days < 7) { + setError("Retention must be at least 7 days"); + } else if (isNaN(days)) { + setError("Please enter a valid number"); + } else { + setError(""); + } + }; + + const RetentionControls = () => { + return ( + + + + + ); + }; + + return ( + } + > + + Configure how long to keep backup files. Both CIPP system backups and tenant backups will be + automatically deleted after this period. Minimum retention is 7 days, default is 30 days. + Cleanup runs daily at 2:00 AM. + + + ); +}; + +export default CippBackupRetentionSettings; diff --git a/src/components/CippStandards/CippStandardAccordion.jsx b/src/components/CippStandards/CippStandardAccordion.jsx index aec6d0fbda94..afbd54594256 100644 --- a/src/components/CippStandards/CippStandardAccordion.jsx +++ b/src/components/CippStandards/CippStandardAccordion.jsx @@ -30,7 +30,7 @@ import { } from "@mui/icons-material"; import { Grid } from "@mui/system"; import CippFormComponent from "/src/components/CippComponents/CippFormComponent"; -import { useWatch } from "react-hook-form"; +import { useWatch, useFormState } from "react-hook-form"; import _ from "lodash"; import Microsoft from "../../icons/iconly/bulk/microsoft"; import Azure from "../../icons/iconly/bulk/azure"; @@ -108,6 +108,8 @@ const CippStandardAccordion = ({ control: formControl.control, }); + const { errors: formErrors } = useFormState({ control: formControl.control }); + // Watch all trackDrift values for all standards at once const allTrackDriftValues = useWatch({ control: formControl.control, @@ -568,19 +570,19 @@ const CippStandardAccordion = ({ if (templateList && templateList.label) { templateDisplayName = templateList.label; } - + // Check for TemplateList-Tags selection (takes priority) const templateListTags = _.get(watchedValues, `${standardName}.TemplateList-Tags`); if (templateListTags && templateListTags.label) { templateDisplayName = templateListTags.label; } } - + // For multiple standards, check the first added component const selectedTemplateName = standard.multiple ? _.get(watchedValues, `${standardName}.${standard.addedComponent?.[0]?.name}`) : ""; - + // Build accordion title with template name if available const accordionTitle = templateDisplayName ? `${standard.label} - ${templateDisplayName}` @@ -674,11 +676,16 @@ const CippStandardAccordion = ({ const hasAction = actionValue && (!Array.isArray(actionValue) || actionValue.length > 0); + // Check if this standard has any validation errors + const standardErrors = _.get(formErrors, standardName); + const hasValidationErrors = standardErrors && Object.keys(standardErrors).length > 0; + // Allow saving if: // 1. Action is selected if required // 2. All required fields are filled // 3. There are unsaved changes - const canSave = hasAction && requiredFieldsFilled && hasUnsaved; + // 4. No validation errors + const canSave = hasAction && requiredFieldsFilled && hasUnsaved && !hasValidationErrors; return ( diff --git a/src/components/CippStandards/CippStandardsSideBar.jsx b/src/components/CippStandards/CippStandardsSideBar.jsx index 09cc642f61bd..7e2ccdcb6647 100644 --- a/src/components/CippStandards/CippStandardsSideBar.jsx +++ b/src/components/CippStandards/CippStandardsSideBar.jsx @@ -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 || []; @@ -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( @@ -420,7 +380,8 @@ const CippStandardsSideBar = ({ {(watchForm.tenantFilter?.some( (tenant) => tenant.value === "AllTenants" || tenant.type === "Group" - ) || (watchForm.excludedTenants && watchForm.excludedTenants.length > 0)) && ( + ) || + (watchForm.excludedTenants && watchForm.excludedTenants.length > 0)) && ( <> + + + When enabled, all drift alert notifications (email, webhook, and PSA) will be + disabled. + )} {/* Hide schedule options in drift mode */} @@ -539,7 +516,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.", @@ -561,6 +538,7 @@ const CippStandardsSideBar = ({ type: "drift", driftAlertWebhook: "driftAlertWebhook", driftAlertEmail: "driftAlertEmail", + driftAlertDisableEmail: "driftAlertDisableEmail", } : {}), }, diff --git a/src/components/CippTable/CIPPTableToptoolbar.js b/src/components/CippTable/CIPPTableToptoolbar.js index d393c882d714..3004db8ebf66 100644 --- a/src/components/CippTable/CIPPTableToptoolbar.js +++ b/src/components/CippTable/CIPPTableToptoolbar.js @@ -1269,6 +1269,7 @@ export const CIPPTableToptoolbar = ({ api={actionData.action} row={actionData.data} relatedQueryKeys={queryKeys} + {...actionData.action} /> )} diff --git a/src/components/CippTable/CippDiagnosticsFilter.js b/src/components/CippTable/CippDiagnosticsFilter.js new file mode 100644 index 000000000000..e8118a10e090 --- /dev/null +++ b/src/components/CippTable/CippDiagnosticsFilter.js @@ -0,0 +1,305 @@ +import { useState, useEffect } from "react"; +import { useForm, useWatch } from "react-hook-form"; +import { + Box, + Button, + Stack, + Alert, + AlertTitle, + Accordion, + AccordionSummary, + AccordionDetails, + Typography, + IconButton, + Tooltip, + CircularProgress, +} from "@mui/material"; +import { ExpandMore, Search, Save, Delete } from "@mui/icons-material"; +import { CippFormComponent } from "../CippComponents/CippFormComponent"; +import { ApiGetCall, ApiPostCall } from "../../api/ApiCall"; +import { Grid } from "@mui/system"; +import defaultPresets from "../../data/DiagnosticsPresets.json"; + +const CippDiagnosticsFilter = ({ onSubmitFilter }) => { + const [expanded, setExpanded] = useState(true); + const [selectedPreset, setSelectedPreset] = useState(null); + const [presetOptions, setPresetOptions] = useState([]); + + const formControl = useForm({ + mode: "onChange", + defaultValues: { + presetName: "", + queryPreset: null, + query: "", + }, + }); + + const { handleSubmit } = formControl; + const queryValue = useWatch({ control: formControl.control, name: "query" }); + const queryPreset = useWatch({ control: formControl.control, name: "queryPreset" }); + const presetName = useWatch({ control: formControl.control, name: "presetName" }); + + // Load presets + const presetList = ApiGetCall({ + url: "/api/ListDiagnosticsPresets", + queryKey: "ListDiagnosticsPresets", + }); + + useEffect(() => { + // Combine built-in presets with custom presets + const builtInOptions = defaultPresets.map((preset) => ({ + label: preset.name, + value: preset.id, + query: preset.query, + columns: preset.columns || null, + isBuiltin: true, + })); + + const customOptions = + presetList.isSuccess && presetList.data + ? presetList.data.map((preset) => ({ + label: preset.name, + value: preset.GUID, + query: preset.query, + isBuiltin: false, + })) + : []; + + setPresetOptions([...builtInOptions, ...customOptions]); + }, [presetList.isSuccess, presetList.data]); + + // Load preset when selected + useEffect(() => { + if (queryPreset) { + // queryPreset is the full object from autoComplete + // Check if it's an array (multiple) or object (single) + const preset = Array.isArray(queryPreset) ? queryPreset[0] : queryPreset; + + if (preset?.query) { + formControl.setValue("query", preset.query); + formControl.setValue("presetName", preset.label); + setSelectedPreset(preset); + // Clear the preset selection so user can edit freely + formControl.setValue("queryPreset", null); + } + } + }, [queryPreset, formControl]); + + // Clear selectedPreset when query is manually edited (unless preset is custom or has no columns) + useEffect(() => { + if (selectedPreset && queryValue !== selectedPreset.query) { + // Only clear if preset is built-in and has columns defined + if (selectedPreset.isBuiltin && selectedPreset.columns) { + setSelectedPreset(null); + } + } + }, [queryValue, selectedPreset]); + + const savePresetApi = ApiPostCall({ + relatedQueryKeys: ["ListDiagnosticsPresets"], + }); + + const deletePresetApi = ApiPostCall({ + relatedQueryKeys: ["ListDiagnosticsPresets"], + }); + + const handleSavePreset = () => { + if (!presetName || !queryValue) { + return; + } + + // Built-in presets get saved as new custom presets (no GUID = new preset) + // Custom presets can be updated (include GUID) + const presetData = { + name: presetName, + query: queryValue, + GUID: selectedPreset?.isBuiltin ? undefined : selectedPreset?.value || undefined, + }; + + const isUpdate = selectedPreset && !selectedPreset.isBuiltin; + + savePresetApi.mutate({ + url: "/api/ExecDiagnosticsPresets", + data: presetData, + title: isUpdate ? "Update Preset" : "Save Preset", + message: isUpdate + ? `Preset "${presetName}" updated successfully` + : `Preset "${presetName}" saved successfully`, + }); + }; + + const handleDeletePreset = () => { + if (!selectedPreset || selectedPreset.isBuiltin) { + return; + } + + deletePresetApi.mutate({ + url: "/api/ExecDiagnosticsPresets", + data: { + GUID: selectedPreset.value, + action: "delete", + }, + title: "Delete Preset", + message: `Preset "${selectedPreset.label}" deleted successfully`, + }); + + formControl.setValue("queryPreset", null); + formControl.setValue("presetName", ""); + setSelectedPreset(null); + }; + + const onSubmit = (values) => { + if (values.query && values.query.trim()) { + onSubmitFilter({ + ...values, + presetDisplayName: values.presetName || selectedPreset?.label || null, + columns: selectedPreset?.columns || null, + }); + setExpanded(false); + } + }; + + const handleClear = () => { + formControl.reset({ query: "", presetName: "", queryPreset: null }); + onSubmitFilter({ query: "", presetDisplayName: null, columns: null }); + // Only clear selectedPreset if it's a built-in preset + // Keep custom preset reference so user can continue editing and saving + if (selectedPreset?.isBuiltin) { + setSelectedPreset(null); + } + setExpanded(true); + }; + + return ( + setExpanded(!expanded)}> + }> + Query + + + + + Requirements + + • Application Insights must be deployed for your CIPP environment +
• The Function App's managed identity must have Reader{" "} + permissions on the Application Insights resource +
• Queries are executed using Kusto Query Language (KQL) +
+
+ + + + + + + + + + + + + + + + + + {savePresetApi.isPending ? : } + + + + + + + {deletePresetApi.isPending ? ( + + ) : ( + + )} + + + + + + + + + + ago(1h)\n| where severityLevel >= 2\n| project timestamp, message, severityLevel\n| order by timestamp desc`} + helperText="Enter a valid Kusto Query Language (KQL) query to execute against Application Insights" + sx={{ + "& textarea": { + fontFamily: "monospace", + fontSize: "0.875rem", + }, + }} + /> + + + + + + + +
+
+
+ ); +}; + +export default CippDiagnosticsFilter; diff --git a/src/components/CippWizard/CippAlertsStep.jsx b/src/components/CippWizard/CippAlertsStep.jsx index ba4e62c7f9f5..d0d8689b85d2 100644 --- a/src/components/CippWizard/CippAlertsStep.jsx +++ b/src/components/CippWizard/CippAlertsStep.jsx @@ -16,6 +16,8 @@ export const CippAlertsStep = (props) => { { value: "4h", label: "Every 4 hours" }, { value: "1d", label: "Every 1 day" }, { value: "7d", label: "Every 7 days" }, + { value: "14d", label: "Every 14 days" }, + { value: "21d", label: "Every 21 days" }, { value: "30d", label: "Every 30 days" }, { value: "365d", label: "Every 365 days" }, ]; diff --git a/src/components/CippWizard/CippWizardOffboarding.jsx b/src/components/CippWizard/CippWizardOffboarding.jsx index 80b1dd8855e5..44af44afbb27 100644 --- a/src/components/CippWizard/CippWizardOffboarding.jsx +++ b/src/components/CippWizard/CippWizardOffboarding.jsx @@ -383,6 +383,17 @@ export const CippWizardOffboarding = (props) => { formControl={formControl} /> + + + + diff --git a/src/components/actions-menu.js b/src/components/actions-menu.js index b63ed33e74c8..ff11c55d2edd 100644 --- a/src/components/actions-menu.js +++ b/src/components/actions-menu.js @@ -92,6 +92,7 @@ export const ActionsMenu = (props) => { api={actionData.action} row={actionData.data} relatedQueryKeys={queryKeys} + {...actionData.action} /> )} diff --git a/src/contexts/settings-context.js b/src/contexts/settings-context.js index bc2e177d73cc..37bb7a1cfdd6 100644 --- a/src/contexts/settings-context.js +++ b/src/contexts/settings-context.js @@ -132,14 +132,22 @@ export const SettingsProvider = (props) => { const handleUpdate = useCallback((settings) => { setState((prevState) => { + // Filter out null and undefined values to prevent resetting settings + const filteredSettings = Object.entries(settings).reduce((acc, [key, value]) => { + if (value !== null && value !== undefined) { + acc[key] = value; + } + return acc; + }, {}); + storeSettings({ ...prevState, - ...settings, + ...filteredSettings, }); return { ...prevState, - ...settings, + ...filteredSettings, }; }); }, []); diff --git a/src/data/DiagnosticsPresets.json b/src/data/DiagnosticsPresets.json new file mode 100644 index 000000000000..ac354677b226 --- /dev/null +++ b/src/data/DiagnosticsPresets.json @@ -0,0 +1,56 @@ +[ + { + "name": "Completed Tasks Summary (Last 24h)", + "id": "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d", + "query": "customEvents\n| where timestamp between (ago(1d) .. now())\n| where name == \"CIPP.TaskCompleted\"\n| extend TaskName = tostring(customDimensions.TaskName)\n , Command = tostring(customDimensions.Command)\n , Tenant = tostring(customDimensions.Tenant)\n , DurationMs = todouble(customMeasurements.DurationMs)\n| summarize\n Count = count(),\n TotalDurationMs = sum(DurationMs),\n AvgDurationMs = avg(DurationMs),\n MaxDurationMs = max(DurationMs)\n by TaskName, Command, Tenant\n| extend name = \"CIPP.TaskCompleted\"\n| order by TotalDurationMs desc", + "isBuiltin": true, + "columns": [ + "TaskName", + "Command", + "Tenant", + "Count", + "TotalDurationMs", + "AvgDurationMs", + "MaxDurationMs" + ] + }, + { + "name": "Completed Standards Summary (Last 24h)", + "id": "b2c3d4e5-f6a7-4b5c-9d0e-1f2a3b4c5d6e", + "query": "customEvents\n| where timestamp between (ago(1d) .. now())\n| where name == \"CIPP.StandardCompleted\"\n| extend TaskName = tostring(customDimensions.TaskName)\n , Command = tostring(customDimensions.Command)\n , Tenant = tostring(customDimensions.Tenant)\n , DurationMs = todouble(customMeasurements.DurationMs)\n| summarize\n Count = count(),\n TotalDurationMs = sum(DurationMs),\n AvgDurationMs = avg(DurationMs),\n MaxDurationMs = max(DurationMs)\n by TaskName, Command, Tenant\n| extend name = \"CIPP.StandardCompleted\"\n| order by TotalDurationMs desc", + "isBuiltin": true, + "columns": [ + "TaskName", + "Command", + "Tenant", + "Count", + "TotalDurationMs", + "AvgDurationMs", + "MaxDurationMs" + ] + }, + { + "name": "Console Logs (Last 24h)", + "id": "c3d4e5f6-a7b8-4c5d-0e1f-2a3b4c5d6e7f", + "query": "customEvents\n| where timestamp > ago(1d)\n| where name == \"CIPP.ConsoleLog\"\n| extend Message = tostring(customDimensions['Message'])\n , Level = tostring(customDimensions['Level'])\n , InvocationId = tostring(customDimensions['InvocationId'])\n| project timestamp, name, Level, Message, InvocationId\n| order by timestamp desc", + "isBuiltin": true, + "columns": [ + "timestamp", + "Level", + "Message", + "InvocationId" + ] + }, + { + "name": "Console Errors and Warnings (Last 24h)", + "id": "d4e5f6a7-b8c9-4d5e-1f2a-3b4c5d6e7f8a", + "query": "customEvents\n| where timestamp > ago(1d)\n| where name == \"CIPP.ConsoleLog\"\n| where tostring(customDimensions['Level']) in ('Error', 'Warning')\n| extend Message = tostring(customDimensions['Message'])\n , Level = tostring(customDimensions['Level'])\n , InvocationId = tostring(customDimensions['InvocationId'])\n| project timestamp, name, Level, Message, InvocationId\n| order by timestamp desc", + "isBuiltin": true, + "columns": [ + "timestamp", + "Level", + "Message", + "InvocationId" + ] + } +] \ No newline at end of file diff --git a/src/data/Extensions.json b/src/data/Extensions.json index 3009704f99cd..c6d8ced44146 100644 --- a/src/data/Extensions.json +++ b/src/data/Extensions.json @@ -866,6 +866,16 @@ "compareValue": true, "action": "disable" } + }, + { + "type": "switch", + "name": "CFZTNA.WebhookEnabled", + "label": "Use CloudFlare Service Account credentials with webhooks.", + "condition": { + "field": "CFZTNA.Enabled", + "compareType": "is", + "compareValue": true + } } ], "mappingRequired": false diff --git a/src/data/alerts.json b/src/data/alerts.json index d06a880a09bf..497436c7d6a3 100644 --- a/src/data/alerts.json +++ b/src/data/alerts.json @@ -15,7 +15,7 @@ "recommendedRunInterval": "1d" }, { - "name": "AlertSmtpAuthSuccess", + "name": "SmtpAuthSuccess", "label": "Alert on SMTP AUTH usage with success, helps to phase out SMTP AUTH (Entra P1 Required)", "recommendedRunInterval": "1d" }, @@ -76,8 +76,22 @@ }, { "name": "ExpiringLicenses", - "label": "Alert on licenses expiring in 30 days", - "recommendedRunInterval": "7d" + "label": "Alert on licenses expiring in X days", + "recommendedRunInterval": "7d", + "requiresInput": true, + "multipleInput": true, + "inputs": [ + { + "inputType": "number", + "inputLabel": "Days until expiration (default: 30)", + "inputName": "ExpiringLicensesDays" + }, + { + "inputType": "switch", + "inputLabel": "Alert only on unassigned licenses", + "inputName": "ExpiringLicensesUnassignedOnly" + } + ] }, { "name": "NewAppApproval", @@ -225,6 +239,26 @@ "recommendedRunInterval": "7d", "description": "Monitors Global Admin accounts and alerts when they don't have an alternate email address set, which is important for password recovery of key accounts." }, + { + "name": "GlobalAdminAllowList", + "label": "Alert on Global Admins outside approved list", + "recommendedRunInterval": "4h", + "requiresInput": true, + "multipleInput": true, + "inputs": [ + { + "inputType": "textField", + "inputLabel": "Approved Global Admin UPN prefixes (comma separated)", + "inputName": "ApprovedGlobalAdmins" + }, + { + "inputType": "switch", + "inputLabel": "Alert per non-compliant admin? (off = single aggregated alert)", + "inputName": "AlertEachAdmin" + } + ], + "description": "Alerts when Global Administrator accounts are present whose UPN prefix (before @domain) is not in your approved comma-separated allow list. Toggle per-admin alerts to get one alert per user or a single aggregated alert." + }, { "name": "NewRiskyUsers", "label": "Alert on new risky users (P2 License Required)", @@ -251,5 +285,11 @@ "name": "ReportOnlyCA", "label": "Alert on tenants with Conditional Access policies in report-only mode", "recommendedRunInterval": "1d" + }, + { + "name": "QuarantineReleaseRequests", + "label": "Alert on quarantine release requests", + "recommendedRunInterval": "30m", + "description": "Monitors for user requests to release quarantined messages and provides a CIPP-native alternative to the external email forwarding method. This helps MSPs maintain secure configurations while getting timely notifications about quarantine activity. Links to the tenant's quarantine page are provided in alerts." } ] diff --git a/src/data/dashboardv2-demo-data.js b/src/data/dashboardv2-demo-data.js new file mode 100644 index 000000000000..e5e23eee579a --- /dev/null +++ b/src/data/dashboardv2-demo-data.js @@ -0,0 +1,131 @@ +// Demo data structure matching Zero Trust Assessment +export const dashboardDemoData = { + ExecutedAt: "2025-12-16T10:00:00Z", + TenantName: "Demo Tenant", + Domain: "demo.contoso.com", + TestResultSummary: { + IdentityPassed: 85, + IdentityTotal: 100, + DevicesPassed: 25, + DevicesTotal: 36, + DataPassed: 20, + DataTotal: 30, + }, + TenantInfo: { + TenantOverview: { + UserCount: 1250, + GuestCount: 85, + GroupCount: 340, + ApplicationCount: 156, + DeviceCount: 765, + ManagedDeviceCount: 733, + }, + OverviewCaMfaAllUsers: { + description: + "Over the past 30 days, 68.5% of sign-ins were protected by conditional access policies enforcing multifactor authentication.", + nodes: [ + { source: "User sign in", target: "No CA applied", value: 394 }, + { source: "User sign in", target: "CA applied", value: 856 }, + { source: "CA applied", target: "No MFA", value: 146 }, + { source: "CA applied", target: "MFA", value: 710 }, + ], + }, + OverviewCaDevicesAllUsers: { + description: "Over the past 30 days, 71.2% of sign-ins were from compliant devices.", + nodes: [ + { source: "User sign in", target: "Unmanaged", value: 500 }, + { source: "User sign in", target: "Managed", value: 1150 }, + { source: "Managed", target: "Non-compliant", value: 260 }, + { source: "Managed", target: "Compliant", value: 890 }, + ], + }, + OverviewAuthMethodsPrivilegedUsers: { + description: "Authentication methods used by privileged users over the past 30 days.", + nodes: [ + { source: "Users", target: "Single factor", value: 5 }, + { source: "Users", target: "Phishable", value: 28 }, + { source: "Users", target: "Phish resistant", value: 15 }, + { source: "Phishable", target: "Phone", value: 8 }, + { source: "Phishable", target: "Authenticator", value: 20 }, + { source: "Phish resistant", target: "Passkey", value: 12 }, + { source: "Phish resistant", target: "WHfB", value: 3 }, + ], + }, + OverviewAuthMethodsAllUsers: { + description: "Authentication methods used by all users over the past 30 days.", + nodes: [ + { source: "Users", target: "Single factor", value: 120 }, + { source: "Users", target: "Phishable", value: 580 }, + { source: "Users", target: "Phish resistant", value: 550 }, + { source: "Phishable", target: "Phone", value: 180 }, + { source: "Phishable", target: "Authenticator", value: 400 }, + { source: "Phish resistant", target: "Passkey", value: 450 }, + { source: "Phish resistant", target: "WHfB", value: 100 }, + ], + }, + DeviceOverview: { + DesktopDevicesSummary: { + description: "Desktop devices (Windows and macOS) by join type and compliance status.", + nodes: [ + // Level 1: Desktop devices to OS + { source: "Desktop devices", target: "Windows", value: 585 }, + { source: "Desktop devices", target: "macOS", value: 75 }, + // Level 2: Windows to join types + { source: "Windows", target: "Entra joined", value: 285 }, + { source: "Windows", target: "Entra registered", value: 100 }, + { source: "Windows", target: "Entra hybrid joined", value: 200 }, + // Level 3: Windows join types to compliance + { source: "Entra joined", target: "Compliant", value: 171 }, + { source: "Entra joined", target: "Non-compliant", value: 42 }, + { source: "Entra joined", target: "Unmanaged", value: 72 }, + { source: "Entra hybrid joined", target: "Compliant", value: 50 }, + { source: "Entra hybrid joined", target: "Non-compliant", value: 23 }, + { source: "Entra hybrid joined", target: "Unmanaged", value: 127 }, + { source: "Entra registered", target: "Compliant", value: 60 }, + { source: "Entra registered", target: "Non-compliant", value: 40 }, + { source: "Entra registered", target: "Unmanaged", value: 0 }, + // Level 2: macOS directly to compliance + { source: "macOS", target: "Compliant", value: 56 }, + { source: "macOS", target: "Non-compliant", value: 15 }, + { source: "macOS", target: "Unmanaged", value: 4 }, + ], + }, + MobileSummary: { + description: "Mobile devices by compliance status.", + nodes: [ + { source: "Mobile devices", target: "Android", value: 105 }, + { source: "Mobile devices", target: "iOS", value: 75 }, + { source: "Android", target: "Android (Company)", value: 72 }, + { source: "Android", target: "Android (Personal)", value: 33 }, + { source: "iOS", target: "iOS (Company)", value: 58 }, + { source: "iOS", target: "iOS (Personal)", value: 17 }, + { source: "Android (Company)", target: "Compliant", value: 60 }, + { source: "Android (Company)", target: "Non-compliant", value: 12 }, + { source: "Android (Personal)", target: "Compliant", value: 10 }, + { source: "Android (Personal)", target: "Non-compliant", value: 23 }, + { source: "iOS (Company)", target: "Compliant", value: 52 }, + { source: "iOS (Company)", target: "Non-compliant", value: 6 }, + { source: "iOS (Personal)", target: "Compliant", value: 11 }, + { source: "iOS (Personal)", target: "Non-compliant", value: 6 }, + ], + }, + ManagedDevices: { + deviceOperatingSystemSummary: { + androidCount: 105, + iosCount: 75, + macOSCount: 75, + windowsCount: 585, + linuxCount: 15, + }, + }, + DeviceCompliance: { + compliantDeviceCount: 400, + nonCompliantDeviceCount: 150, + }, + DeviceOwnership: { + corporateCount: 600, + personalCount: 100, + }, + }, + }, +}; diff --git a/src/data/standards.json b/src/data/standards.json index 886e82fc0caf..92d605cf2036 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -77,9 +77,7 @@ "impactColour": "info", "addedDate": "2024-03-19", "powershellEquivalent": "New-MailContact", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.DeployContactTemplates", @@ -113,18 +111,12 @@ "impactColour": "info", "addedDate": "2025-05-31", "powershellEquivalent": "New-MailContact", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.AuditLog", "cat": "Global Standards", - "tag": [ - "CIS M365 5.0 (3.1.1)", - "mip_search_auditlog", - "NIST CSF 2.0 (DE.CM-09)" - ], + "tag": ["CIS M365 5.0 (3.1.1)", "mip_search_auditlog", "NIST CSF 2.0 (DE.CM-09)"], "helpText": "Enables the Unified Audit Log for tracking and auditing activities. Also runs Enable-OrganizationCustomization if necessary.", "executiveText": "Activates comprehensive activity logging across Microsoft 365 services to track user actions, system changes, and security events. This provides essential audit trails for compliance requirements, security investigations, and regulatory reporting.", "addedComponent": [], @@ -133,17 +125,12 @@ "impactColour": "info", "addedDate": "2021-11-16", "powershellEquivalent": "Enable-OrganizationCustomization", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.RestrictThirdPartyStorageServices", "cat": "Global Standards", - "tag": [ - "CIS M365 5.0 (1.3.7)" - ], + "tag": ["CIS M365 5.0 (1.3.7)"], "helpText": "Restricts third-party storage services in Microsoft 365 on the web by managing the Microsoft 365 on the web service principal. This disables integrations with services like Dropbox, Google Drive, Box, and other third-party storage providers.", "docsDescription": "Third-party storage can be enabled for users in Microsoft 365, allowing them to store and share documents using services such as Dropbox, alongside OneDrive and team sites. This standard ensures Microsoft 365 on the web third-party storage services are restricted by creating and disabling the Microsoft 365 on the web service principal (appId: c1f33bc0-bdb4-4248-ba9b-096807ddb43e). By using external storage services an organization may increase the risk of data breaches and unauthorized access to confidential information. Additionally, third-party services may not adhere to the same security standards as the organization, making it difficult to maintain data privacy and security. Impact is highly dependent upon current practices - if users do not use other storage providers, then minimal impact is likely. However, if users regularly utilize providers outside of the tenant this will affect their ability to continue to do so.", "executiveText": "Prevents employees from using external cloud storage services like Dropbox, Google Drive, and Box within Microsoft 365, reducing data security risks and ensuring all company data remains within controlled corporate systems. This helps maintain data governance and prevents potential data leaks to unauthorized platforms.", @@ -153,9 +140,7 @@ "impactColour": "warning", "addedDate": "2025-06-06", "powershellEquivalent": "New-MgServicePrincipal and Update-MgServicePrincipal", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.ProfilePhotos", @@ -207,9 +192,7 @@ "remediate": false }, "powershellEquivalent": "Portal only", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.Branding", @@ -272,10 +255,7 @@ { "name": "standards.EnableCustomerLockbox", "cat": "Global Standards", - "tag": [ - "CIS M365 5.0 (1.3.6)", - "CustomerLockBoxEnabled" - ], + "tag": ["CIS M365 5.0 (1.3.6)", "CustomerLockBoxEnabled"], "helpText": "Enables Customer Lockbox that offers an approval process for Microsoft support to access organization data", "docsDescription": "Customer Lockbox ensures that Microsoft can't access your content to do service operations without your explicit approval. Customer Lockbox ensures only authorized requests allow access to your organizations data.", "executiveText": "Requires explicit organizational approval before Microsoft support staff can access company data for service operations. This provides an additional layer of data protection and ensures the organization maintains control over who can access sensitive business information, even during technical support scenarios.", @@ -285,9 +265,7 @@ "impactColour": "info", "addedDate": "2024-01-08", "powershellEquivalent": "Set-OrganizationConfig -CustomerLockBoxEnabled $true", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.EnablePronouns", @@ -315,9 +293,7 @@ "impact": "Low Impact", "impactColour": "info", "addedDate": "2025-06-06", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.AnonReportDisable", @@ -332,9 +308,7 @@ "impactColour": "info", "addedDate": "2021-11-16", "powershellEquivalent": "Update-MgBetaAdminReportSetting -BodyParameter @{displayConcealedNames = $true}", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.DisableGuestDirectory", @@ -356,17 +330,12 @@ "impactColour": "info", "addedDate": "2022-05-04", "powershellEquivalent": "Set-AzureADMSAuthorizationPolicy -GuestUserRoleId '2af84b1e-32c8-42b7-82bc-daa82404023b'", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.DisableBasicAuthSMTP", "cat": "Global Standards", - "tag": [ - "CIS M365 5.0 (6.5.4)", - "NIST CSF 2.0 (PR.IR-01)" - ], + "tag": ["CIS M365 5.0 (6.5.4)", "NIST CSF 2.0 (PR.IR-01)"], "helpText": "Disables SMTP AUTH organization-wide, impacting POP and IMAP clients that rely on SMTP for sending emails. Default for new tenants. For more information, see the [Microsoft documentation](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/authenticated-client-smtp-submission)", "docsDescription": "Disables tenant-wide SMTP basic authentication, including for all explicitly enabled users, impacting POP and IMAP clients that rely on SMTP for sending emails. For more information, see the [Microsoft documentation](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/authenticated-client-smtp-submission).", "executiveText": "Disables outdated email authentication methods that are vulnerable to security attacks, forcing applications and devices to use modern, more secure authentication protocols. This reduces the risk of email-based security breaches and credential theft.", @@ -376,19 +345,12 @@ "impactColour": "warning", "addedDate": "2021-11-16", "powershellEquivalent": "Set-TransportConfig -SmtpClientAuthenticationDisabled $true", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.ActivityBasedTimeout", "cat": "Global Standards", - "tag": [ - "CIS M365 5.0 (1.3.2)", - "spo_idle_session_timeout", - "NIST CSF 2.0 (PR.AA-03)" - ], + "tag": ["CIS M365 5.0 (1.3.2)", "spo_idle_session_timeout", "NIST CSF 2.0 (PR.AA-03)"], "helpText": "Enables and sets Idle session timeout for Microsoft 365 to 1 hour. This policy affects most M365 web apps", "executiveText": "Automatically logs out inactive users from Microsoft 365 applications after a specified time period to prevent unauthorized access to company data on unattended devices. This security measure protects against data breaches when employees leave workstations unlocked.", "addedComponent": [ @@ -427,18 +389,12 @@ "impactColour": "warning", "addedDate": "2022-04-13", "powershellEquivalent": "Portal or Graph API", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.AuthMethodsSettings", "cat": "Entra (AAD) Standards", - "tag": [ - "EIDSCA.AG01", - "EIDSCA.AG02", - "EIDSCA.AG03" - ], + "tag": ["EIDSCA.AG01", "EIDSCA.AG02", "EIDSCA.AG03"], "helpText": "Configures the report suspicious activity settings and system credential preferences in the authentication methods policy.", "docsDescription": "Controls the authentication methods policy settings for reporting suspicious activity and system credential preferences. These settings help enhance the security of authentication in your organization.", "executiveText": "Configures security settings that allow users to report suspicious login attempts and manages how the system handles authentication credentials. This enhances overall security by enabling early detection of potential security threats and optimizing authentication processes.", @@ -508,9 +464,7 @@ "impactColour": "warning", "addedDate": "2025-07-07", "powershellEquivalent": "Update-MgBetaPolicyAuthenticationMethodPolicy", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.AppDeploy", @@ -589,9 +543,7 @@ "impactColour": "info", "addedDate": "2023-04-25", "powershellEquivalent": "Portal or Graph API", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.PWdisplayAppInformationRequiredState", @@ -615,16 +567,12 @@ "impactColour": "info", "addedDate": "2021-11-16", "powershellEquivalent": "Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.allowOTPTokens", "cat": "Entra (AAD) Standards", - "tag": [ - "EIDSCA.AM02" - ], + "tag": ["EIDSCA.AM02"], "helpText": "Allows you to use MS authenticator OTP token generator", "docsDescription": "Allows you to use Microsoft Authenticator OTP token generator. Useful for using the NPS extension as MFA on VPN clients.", "executiveText": "Enables one-time password generation through Microsoft Authenticator app, providing an additional secure authentication method for employees. This is particularly useful for secure VPN access and other systems requiring multi-factor authentication.", @@ -639,9 +587,7 @@ { "name": "standards.PWcompanionAppAllowedState", "cat": "Entra (AAD) Standards", - "tag": [ - "EIDSCA.AM01" - ], + "tag": ["EIDSCA.AM01"], "helpText": "Sets the state of Authenticator Lite, Authenticator lite is a companion app for passwordless authentication.", "docsDescription": "Sets the Authenticator Lite state to enabled. This allows users to use the Authenticator Lite built into the Outlook app instead of the full Authenticator app.", "executiveText": "Enables a simplified authentication experience by allowing users to authenticate directly through Outlook without requiring a separate authenticator app. This improves user convenience while maintaining security standards for passwordless authentication.", @@ -696,9 +642,7 @@ "impactColour": "info", "addedDate": "2022-12-08", "powershellEquivalent": "Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.EnableHardwareOAuth", @@ -718,10 +662,7 @@ { "name": "standards.allowOAuthTokens", "cat": "Entra (AAD) Standards", - "tag": [ - "EIDSCA.AT01", - "EIDSCA.AT02" - ], + "tag": ["EIDSCA.AT01", "EIDSCA.AT02"], "helpText": "Allows you to use any software OAuth token generator", "docsDescription": "Enables OTP Software OAuth tokens for the tenant. This allows users to use OTP codes generated via software, like a password manager to be used as an authentication method.", "executiveText": "Allows employees to use third-party authentication apps and password managers to generate secure login codes, providing flexibility in authentication methods while maintaining security standards. This accommodates diverse user preferences and existing security tools.", @@ -736,11 +677,7 @@ { "name": "standards.FormsPhishingProtection", "cat": "Global Standards", - "tag": [ - "CIS M365 5.0 (1.3.5)", - "Security", - "PhishingProtection" - ], + "tag": ["CIS M365 5.0 (1.3.5)", "Security", "PhishingProtection"], "helpText": "Enables internal phishing protection for Microsoft Forms to help prevent malicious forms from being created and shared within the organization. This feature scans forms created by internal users for potential phishing content and suspicious patterns.", "docsDescription": "Enables internal phishing protection for Microsoft Forms by setting the isInOrgFormsPhishingScanEnabled property to true. This security feature helps protect organizations from internal phishing attacks through Microsoft Forms by automatically scanning forms created by internal users for potential malicious content, suspicious links, and phishing patterns. When enabled, Forms will analyze form content and block or flag potentially dangerous forms before they can be shared within the organization.", "executiveText": "Automatically scans Microsoft Forms created by employees for malicious content and phishing attempts, preventing the creation and distribution of harmful forms within the organization. This protects against both internal threats and compromised accounts that might be used to distribute malicious content.", @@ -750,10 +687,7 @@ "impactColour": "info", "addedDate": "2025-06-06", "powershellEquivalent": "Graph API", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.TAP", @@ -786,17 +720,12 @@ "impactColour": "info", "addedDate": "2022-03-15", "powershellEquivalent": "Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.PasswordExpireDisabled", "cat": "Entra (AAD) Standards", - "tag": [ - "CIS M365 5.0 (1.3.1)", - "PWAgePolicyNew" - ], + "tag": ["CIS M365 5.0 (1.3.1)", "PWAgePolicyNew"], "helpText": "Disables the expiration of passwords for the tenant by setting the password expiration policy to never expire for any user.", "docsDescription": "Sets passwords to never expire for tenant, recommended to use in conjunction with secure password requirements.", "executiveText": "Eliminates mandatory password expiration requirements, allowing employees to keep strong passwords indefinitely rather than forcing frequent changes that often lead to weaker passwords. This modern security approach reduces help desk calls and improves overall password security when combined with multi-factor authentication.", @@ -806,17 +735,12 @@ "impactColour": "info", "addedDate": "2021-11-16", "powershellEquivalent": "Update-MgDomain", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.CustomBannedPasswordList", "cat": "Entra (AAD) Standards", - "tag": [ - "CIS M365 5.0 (5.2.3.2)" - ], + "tag": ["CIS M365 5.0 (5.2.3.2)"], "helpText": "**Requires Entra ID P1.** Updates and enables the Entra ID custom banned password list with the supplied words. Enter words separated by commas or semicolons. Each word must be 4-16 characters long. Maximum 1,000 words allowed.", "docsDescription": "Updates and enables the Entra ID custom banned password list with the supplied words. This supplements the global banned password list maintained by Microsoft. The custom list is limited to 1,000 key base terms of 4-16 characters each. Entra ID will [block variations and common substitutions](https://learn.microsoft.com/en-us/entra/identity/authentication/tutorial-configure-custom-password-protection#configure-custom-banned-passwords) of these words in user passwords. [How are passwords evaluated?](https://learn.microsoft.com/en-us/entra/identity/authentication/concept-password-ban-bad#score-calculation)", "addedComponent": [ @@ -833,9 +757,7 @@ "impactColour": "warning", "addedDate": "2025-06-28", "powershellEquivalent": "Get-MgBetaDirectorySetting, New-MgBetaDirectorySetting, Update-MgBetaDirectorySetting", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.ExternalMFATrusted", @@ -872,10 +794,7 @@ { "name": "standards.DisableTenantCreation", "cat": "Entra (AAD) Standards", - "tag": [ - "CIS M365 5.0 (1.2.3)", - "CISA (MS.AAD.6.1v1)" - ], + "tag": ["CIS M365 5.0 (1.2.3)", "CISA (MS.AAD.6.1v1)"], "helpText": "Restricts creation of M365 tenants to the Global Administrator or Tenant Creator roles.", "docsDescription": "Users by default are allowed to create M365 tenants. This disables that so only admins can create new M365 tenants.", "executiveText": "Prevents regular employees from creating new Microsoft 365 organizations, ensuring all new tenants are properly managed and controlled by IT administrators. This prevents unauthorized shadow IT environments and maintains centralized governance over Microsoft 365 resources.", @@ -885,10 +804,7 @@ "impactColour": "info", "addedDate": "2022-11-29", "powershellEquivalent": "Update-MgPolicyAuthorizationPolicy", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.EnableAppConsentRequests", @@ -919,9 +835,7 @@ "impactColour": "info", "addedDate": "2023-11-27", "powershellEquivalent": "Update-MgPolicyAdminConsentRequestPolicy", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.NudgeMFA", @@ -952,7 +866,11 @@ "type": "number", "name": "standards.NudgeMFA.snoozeDurationInDays", "label": "Number of days to allow users to skip registering Authenticator (0-14, default is 1)", - "defaultValue": 1 + "defaultValue": 1, + "validators": { + "min": { "value": 0, "message": "Minimum value is 0" }, + "max": { "value": 14, "message": "Maximum value is 14" } + } } ], "label": "Sets the state for the request to setup Authenticator", @@ -965,9 +883,7 @@ { "name": "standards.DisableM365GroupUsers", "cat": "Entra (AAD) Standards", - "tag": [ - "CISA (MS.AAD.21.1v1)" - ], + "tag": ["CISA (MS.AAD.21.1v1)"], "helpText": "Restricts M365 group creation to certain admin roles. This disables the ability to create Teams, SharePoint sites, Planner, etc", "docsDescription": "Users by default are allowed to create M365 groups. This restricts M365 group creation to certain admin roles. This disables the ability to create Teams, SharePoint sites, Planner, etc", "executiveText": "Restricts the creation of Microsoft 365 groups, Teams, and SharePoint sites to authorized administrators, preventing uncontrolled proliferation of collaboration spaces. This ensures proper governance, naming conventions, and resource management while maintaining oversight of all collaborative environments.", @@ -998,10 +914,7 @@ "impactColour": "info", "addedDate": "2024-03-20", "powershellEquivalent": "Update-MgPolicyAuthorizationPolicy", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.BitLockerKeysForOwnedDevice", @@ -1039,10 +952,7 @@ { "name": "standards.DisableSecurityGroupUsers", "cat": "Entra (AAD) Standards", - "tag": [ - "CISA (MS.AAD.20.1v1)", - "NIST CSF 2.0 (PR.AA-05)" - ], + "tag": ["CISA (MS.AAD.20.1v1)", "NIST CSF 2.0 (PR.AA-05)"], "helpText": "Completely disables the creation of security groups by users. This also breaks the ability to manage groups themselves, or create Teams", "executiveText": "Restricts the creation of security groups to IT administrators only, preventing employees from creating unauthorized access groups that could bypass security controls. This ensures proper governance of access permissions and maintains centralized control over who can access what resources.", "addedComponent": [], @@ -1108,10 +1018,7 @@ "impactColour": "warning", "addedDate": "2022-10-20", "powershellEquivalent": "Graph API", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.OauthConsent", @@ -1140,17 +1047,12 @@ "impactColour": "warning", "addedDate": "2021-11-16", "powershellEquivalent": "Update-MgPolicyAuthorizationPolicy", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.OauthConsentLowSec", "cat": "Entra (AAD) Standards", - "tag": [ - "IntegratedApps" - ], + "tag": ["IntegratedApps"], "helpText": "Sets the default oauth consent level so users can consent to applications that have low risks.", "docsDescription": "Allows users to consent to applications with low assigned risk.", "executiveText": "Allows employees to approve low-risk applications without administrative intervention, balancing security with productivity. This provides a middle ground between complete restriction and open access, enabling business agility while maintaining protection against high-risk applications.", @@ -1164,11 +1066,7 @@ { "name": "standards.GuestInvite", "cat": "Entra (AAD) Standards", - "tag": [ - "CISA (MS.AAD.18.1v1)", - "EIDSCA.AP04", - "EIDSCA.AP07" - ], + "tag": ["CISA (MS.AAD.18.1v1)", "EIDSCA.AP04", "EIDSCA.AP07"], "helpText": "This setting controls who can invite guests to your directory to collaborate on resources secured by your company, such as SharePoint sites or Azure resources.", "executiveText": "Controls who within the organization can invite external partners and vendors to access company resources, ensuring proper oversight of external access while enabling necessary business collaboration. This helps maintain security while supporting partnership and vendor relationships.", "addedComponent": [ @@ -1209,25 +1107,24 @@ { "name": "standards.StaleEntraDevices", "cat": "Entra (AAD) Standards", - "tag": [ - "Essential 8 (1501)", - "NIST CSF 2.0 (ID.AM-08)", - "NIST CSF 2.0 (PR.PS-03)" - ], - "helpText": "Remediate is currently not available. Cleans up Entra devices that have not connected/signed in for the specified number of days.", + "tag": ["Essential 8 (1501)", "NIST CSF 2.0 (ID.AM-08)", "NIST CSF 2.0 (PR.PS-03)"], + "helpText": "**Remediate is currently not available**. Cleans up Entra devices that have not connected/signed in for the specified number of days.", "docsDescription": "Remediate is currently not available. Cleans up Entra devices that have not connected/signed in for the specified number of days. First disables and later deletes the devices. More info can be found in the [Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity/devices/manage-stale-devices)", "executiveText": "Automatically identifies and removes inactive devices that haven't connected to company systems for a specified period, reducing security risks from abandoned or lost devices. This maintains a clean device inventory and prevents potential unauthorized access through dormant device registrations.", "addedComponent": [ { "type": "number", "name": "standards.StaleEntraDevices.deviceAgeThreshold", - "label": "Days before stale(Do not set below 30)" + "label": "Days before stale(Do not set below 30)", + "validators": { + "min": { "value": 30, "message": "Minimum value is 30" } + } } ], "disabledFeatures": { "report": false, "warn": false, - "remediate": false + "remediate": true }, "label": "Cleanup stale Entra devices", "impact": "High Impact", @@ -1253,9 +1150,7 @@ { "name": "standards.SecurityDefaults", "cat": "Entra (AAD) Standards", - "tag": [ - "CISA (MS.AAD.11.1v1)" - ], + "tag": ["CISA (MS.AAD.11.1v1)"], "helpText": "Enables security defaults for the tenant, for newer tenants this is enabled by default. Do not enable this feature if you use Conditional Access.", "docsDescription": "Enables SD for the tenant, which disables all forms of basic authentication and enforces users to configure MFA. Users are only prompted for MFA when a logon is considered 'suspect' by Microsoft.", "executiveText": "Activates Microsoft's baseline security configuration that requires multi-factor authentication and blocks legacy authentication methods. This provides essential security protection for organizations without complex conditional access policies, significantly improving security posture with minimal configuration.", @@ -1270,11 +1165,7 @@ { "name": "standards.DisableSMS", "cat": "Entra (AAD) Standards", - "tag": [ - "CIS M365 5.0 (2.3.5)", - "EIDSCA.AS04", - "NIST CSF 2.0 (PR.AA-03)" - ], + "tag": ["CIS M365 5.0 (2.3.5)", "EIDSCA.AS04", "NIST CSF 2.0 (PR.AA-03)"], "helpText": "This blocks users from using SMS as an MFA method. If a user only has SMS as a MFA method, they will be unable to log in.", "docsDescription": "Disables SMS as an MFA method for the tenant. If a user only has SMS as a MFA method, they will be unable to sign in.", "executiveText": "Disables SMS text messages as a multi-factor authentication method due to security vulnerabilities like SIM swapping attacks. This forces users to adopt more secure authentication methods like authenticator apps or hardware tokens, significantly improving account security.", @@ -1284,18 +1175,12 @@ "impactColour": "danger", "addedDate": "2023-12-18", "powershellEquivalent": "Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.DisableVoice", "cat": "Entra (AAD) Standards", - "tag": [ - "CIS M365 5.0 (2.3.5)", - "EIDSCA.AV01", - "NIST CSF 2.0 (PR.AA-03)" - ], + "tag": ["CIS M365 5.0 (2.3.5)", "EIDSCA.AV01", "NIST CSF 2.0 (PR.AA-03)"], "helpText": "This blocks users from using Voice call as an MFA method. If a user only has Voice as a MFA method, they will be unable to log in.", "docsDescription": "Disables Voice call as an MFA method for the tenant. If a user only has Voice call as a MFA method, they will be unable to sign in.", "executiveText": "Disables voice call authentication due to security vulnerabilities and social engineering risks. This forces users to adopt more secure authentication methods like authenticator apps, improving overall account security by eliminating phone-based attack vectors.", @@ -1305,17 +1190,12 @@ "impactColour": "danger", "addedDate": "2023-12-18", "powershellEquivalent": "Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.DisableEmail", "cat": "Entra (AAD) Standards", - "tag": [ - "CIS M365 5.0 (2.3.5)", - "NIST CSF 2.0 (PR.AA-03)" - ], + "tag": ["CIS M365 5.0 (2.3.5)", "NIST CSF 2.0 (PR.AA-03)"], "helpText": "This blocks users from using email as an MFA method. This disables the email OTP option for guest users, and instead prompts them to create a Microsoft account.", "executiveText": "Disables email-based authentication codes due to security concerns with email interception and account compromise. This forces users to adopt more secure authentication methods, particularly affecting guest users who must use stronger verification methods.", "addedComponent": [], @@ -1410,9 +1290,7 @@ { "name": "standards.OutBoundSpamAlert", "cat": "Exchange Standards", - "tag": [ - "CIS M365 5.0 (2.1.6)" - ], + "tag": ["CIS M365 5.0 (2.1.6)"], "helpText": "Set the Outbound Spam Alert e-mail address", "docsDescription": "Sets the e-mail address to which outbound spam alerts are sent.", "addedComponent": [ @@ -1427,9 +1305,7 @@ "impactColour": "info", "addedDate": "2023-05-03", "powershellEquivalent": "Set-HostedOutboundSpamFilterPolicy", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.MessageExpiration", @@ -1493,9 +1369,7 @@ "impactColour": "info", "addedDate": "2024-04-26", "powershellEquivalent": "Set-RemoteDomain -Identity 'Default' -TNEFEnabled $false", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.FocusedInbox", @@ -1612,10 +1486,7 @@ { "name": "standards.EnableOnlineArchiving", "cat": "Exchange Standards", - "tag": [ - "Essential 8 (1511)", - "NIST CSF 2.0 (PR.DS-11)" - ], + "tag": ["Essential 8 (1511)", "NIST CSF 2.0 (PR.DS-11)"], "helpText": "Enables the In-Place Online Archive for all UserMailboxes with a valid license.", "executiveText": "Automatically enables online email archiving for all licensed employees, providing additional storage for older emails while maintaining easy access. This helps manage mailbox sizes, improves email performance, and supports compliance with data retention requirements.", "addedComponent": [], @@ -1650,9 +1521,7 @@ { "name": "standards.SpoofWarn", "cat": "Exchange Standards", - "tag": [ - "CIS M365 5.0 (6.2.3)" - ], + "tag": ["CIS M365 5.0 (6.2.3)"], "helpText": "Adds or removes indicators to e-mail messages received from external senders in Outlook. Works on all Outlook clients/OWA", "docsDescription": "Adds or removes indicators to e-mail messages received from external senders in Outlook. You can read more about this feature on [Microsoft's Exchange Team Blog.](https://techcommunity.microsoft.com/t5/exchange-team-blog/native-external-sender-callouts-on-email-in-outlook/ba-p/2250098)", "executiveText": "Displays visual warnings in Outlook when emails come from external senders, helping employees identify potentially suspicious messages and reducing the risk of phishing attacks. This security feature makes it easier for staff to distinguish between internal and external communications.", @@ -1687,18 +1556,12 @@ "impactColour": "info", "addedDate": "2021-11-16", "powershellEquivalent": "Set-ExternalInOutlook \u2013Enabled $true or $false", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.EnableMailTips", "cat": "Exchange Standards", - "tag": [ - "CIS M365 5.0 (6.5.2)", - "exo_mailtipsenabled" - ], + "tag": ["CIS M365 5.0 (6.5.2)", "exo_mailtipsenabled"], "helpText": "Enables all MailTips in Outlook. MailTips are the notifications Outlook and Outlook on the web shows when an email you create, meets some requirements", "executiveText": "Enables helpful notifications in Outlook that warn users about potential email issues, such as sending to large groups, external recipients, or invalid addresses. This reduces email mistakes and improves communication efficiency by providing real-time guidance to employees.", "addedComponent": [ @@ -1715,10 +1578,7 @@ "impactColour": "info", "addedDate": "2024-01-14", "powershellEquivalent": "Set-OrganizationConfig", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.TeamsMeetingsByDefault", @@ -1770,9 +1630,7 @@ { "name": "standards.RotateDKIM", "cat": "Exchange Standards", - "tag": [ - "CIS M365 5.0 (2.1.9)" - ], + "tag": ["CIS M365 5.0 (2.1.9)"], "helpText": "Rotate DKIM keys that are 1024 bit to 2048 bit", "executiveText": "Upgrades email security by replacing older 1024-bit encryption keys with stronger 2048-bit keys for email authentication. This improves the organization's email security posture and helps prevent email spoofing and tampering, maintaining trust with email recipients.", "addedComponent": [], @@ -1781,17 +1639,12 @@ "impactColour": "info", "addedDate": "2023-03-14", "powershellEquivalent": "Rotate-DkimSigningConfig", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.AddDKIM", "cat": "Exchange Standards", - "tag": [ - "CIS M365 5.0 (2.1.9)" - ], + "tag": ["CIS M365 5.0 (2.1.9)"], "helpText": "Enables DKIM for all domains that currently support it", "executiveText": "Enables email authentication technology that digitally signs outgoing emails to verify they actually came from your organization. This prevents email spoofing, improves email deliverability, and protects the company's reputation by ensuring recipients can trust emails from your domains.", "addedComponent": [], @@ -1800,19 +1653,12 @@ "impactColour": "info", "addedDate": "2023-03-14", "powershellEquivalent": "New-DkimSigningConfig and Set-DkimSigningConfig", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.AddDMARCToMOERA", "cat": "Global Standards", - "tag": [ - "CIS M365 5.0 (2.1.10)", - "Security", - "PhishingProtection" - ], + "tag": ["CIS M365 5.0 (2.1.10)", "Security", "PhishingProtection"], "helpText": "Note: requires 'Domain Name Administrator' GDAP role. This should be enabled even if the MOERA (onmicrosoft.com) domains is not used for sending. Enabling this prevents email spoofing. The default value is 'v=DMARC1; p=reject;' recommended because the domain is only used within M365 and reporting is not needed. Omitting pct tag default to 100%", "docsDescription": "Note: requires 'Domain Name Administrator' GDAP role. Adds a DMARC record to MOERA (onmicrosoft.com) domains. This should be enabled even if the MOERA (onmicrosoft.com) domains is not used for sending. Enabling this prevents email spoofing. The default record is 'v=DMARC1; p=reject;' recommended because the domain is only used within M365 and reporting is not needed. Omitting pct tag default to 100%", "executiveText": "Implements advanced email security for Microsoft's default domain names (onmicrosoft.com) to prevent criminals from impersonating your organization. This blocks fraudulent emails that could damage your company's reputation and protects partners and customers from phishing attacks using your domain names.", @@ -1838,10 +1684,7 @@ "impactColour": "info", "addedDate": "2025-06-16", "powershellEquivalent": "Portal only", - "recommendedBy": [ - "CIS", - "Microsoft" - ] + "recommendedBy": ["CIS", "Microsoft"] }, { "name": "standards.EnableMailboxAuditing", @@ -1864,10 +1707,33 @@ "impactColour": "info", "addedDate": "2024-01-08", "powershellEquivalent": "Set-OrganizationConfig -AuditDisabled $false", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] + }, + { + "name": "standards.AutoArchive", + "cat": "Exchange Standards", + "tag": [], + "helpText": "Configures the auto-archiving threshold percentage for the tenant. When a mailbox exceeds this threshold, the oldest items are automatically moved to the archive mailbox. Archive must be enabled manually or via the CIPP standard 'Enable Online Archive for all users'. More information can be found in [Microsoft's documentation.](https://learn.microsoft.com/en-us/exchange/security-and-compliance/messaging-records-management/auto-archiving)", + "docsDescription": "Configures the auto-archiving threshold at the organization level. Auto-archiving automatically moves the oldest items from a user's primary mailbox to their archive mailbox when mailbox usage exceeds the configured threshold percentage. This prevents mail flow disruptions caused by full mailboxes. Valid range is 80-100, where 100 disables auto-archiving for the tenant. More information can be found in [Microsoft's documentation.](https://learn.microsoft.com/en-us/exchange/security-and-compliance/messaging-records-management/auto-archiving)", + "executiveText": "Configures automatic archiving of mailbox items when storage approaches capacity, preventing email delivery failures due to full mailboxes. This proactive storage management ensures business continuity and reduces helpdesk tickets related to mailbox quota issues.", + "addedComponent": [ + { + "type": "number", + "name": "standards.AutoArchive.AutoArchivingThresholdPercentage", + "label": "Auto-Archiving Threshold Percentage (80-100, default 96, 100 disables)", + "defaultValue": 96, + "validators": { + "min": { "value": 80, "message": "Minimum value is 80" }, + "max": { "value": 100, "message": "Maximum value is 100" } + } + } + ], + "label": "Configure Auto-Archiving Threshold", + "impact": "Low Impact", + "impactColour": "info", + "addedDate": "2025-12-11", + "powershellEquivalent": "Set-OrganizationConfig -AutoArchivingThresholdPercentage 80-100", + "recommendedBy": [] }, { "name": "standards.SendReceiveLimitTenant", @@ -1880,13 +1746,21 @@ "type": "number", "name": "standards.SendReceiveLimitTenant.SendLimit", "label": "Send limit in MB (Default is 35)", - "defaultValue": 35 + "defaultValue": 35, + "validators": { + "min": { "value": 1, "message": "Minimum value is 1" }, + "max": { "value": 150, "message": "Maximum value is 150" } + } }, { "type": "number", "name": "standards.SendReceiveLimitTenant.ReceiveLimit", "label": "Receive Limit in MB (Default is 36)", - "defaultValue": 36 + "defaultValue": 36, + "validators": { + "min": { "value": 1, "message": "Minimum value is 1" }, + "max": { "value": 150, "message": "Maximum value is 150" } + } } ], "label": "Set send/receive size limits", @@ -1972,9 +1846,7 @@ { "name": "standards.EXOOutboundSpamLimits", "cat": "Exchange Standards", - "tag": [ - "CIS M365 5.0 (2.1.6)" - ], + "tag": ["CIS M365 5.0 (2.1.6)"], "helpText": "Configures the outbound spam recipient limits (external per hour, internal per hour, per day) and the action to take when a limit is reached. The 'Set Outbound Spam Alert e-mail' standard is recommended to configure together with this one. ", "docsDescription": "Configures the Exchange Online outbound spam recipient limits for external per hour, internal per hour, and per day, along with the action to take (e.g., BlockUser, Alert) when these limits are exceeded. This helps prevent abuse and manage email flow. Microsoft's recommendations can be found [here.](https://learn.microsoft.com/en-us/defender-office-365/recommended-settings-for-eop-and-office365#eop-outbound-spam-policy-settings) The 'Set Outbound Spam Alert e-mail' standard is recommended to configure together with this one.", "executiveText": "Sets limits on how many emails employees can send per hour and per day to prevent spam and protect the organization's email reputation. When limits are exceeded, the system can alert administrators or temporarily block the user, helping detect compromised accounts or prevent abuse.", @@ -1983,19 +1855,31 @@ "type": "number", "name": "standards.EXOOutboundSpamLimits.RecipientLimitExternalPerHour", "label": "External Recipient Limit Per Hour", - "defaultValue": 400 + "defaultValue": 400, + "validators": { + "min": { "value": 0, "message": "Minimum value is 0" }, + "max": { "value": 10000, "message": "Maximum value is 10000" } + } }, { "type": "number", "name": "standards.EXOOutboundSpamLimits.RecipientLimitInternalPerHour", "label": "Internal Recipient Limit Per Hour", - "defaultValue": 800 + "defaultValue": 800, + "validators": { + "min": { "value": 0, "message": "Minimum value is 0" }, + "max": { "value": 10000, "message": "Maximum value is 10000" } + } }, { "type": "number", "name": "standards.EXOOutboundSpamLimits.RecipientLimitPerDay", "label": "Daily Recipient Limit", - "defaultValue": 800 + "defaultValue": 800, + "validators": { + "min": { "value": 0, "message": "Minimum value is 0" }, + "max": { "value": 10000, "message": "Maximum value is 10000" } + } }, { "type": "autoComplete", @@ -2024,18 +1908,12 @@ "impactColour": "info", "addedDate": "2025-05-13", "powershellEquivalent": "Set-HostedOutboundSpamFilterPolicy", - "recommendedBy": [ - "CIPP", - "CIS" - ] + "recommendedBy": ["CIPP", "CIS"] }, { "name": "standards.DisableExternalCalendarSharing", "cat": "Exchange Standards", - "tag": [ - "CIS M365 5.0 (1.3.3)", - "exo_individualsharing" - ], + "tag": ["CIS M365 5.0 (1.3.3)", "exo_individualsharing"], "helpText": "Disables the ability for users to share their calendar with external users. Only for the default policy, so exclusions can be made if needed.", "docsDescription": "Disables external calendar sharing for the entire tenant. This is not a widely used feature, and it's therefore unlikely that this will impact users. Only for the default policy, so exclusions can be made if needed by making a new policy and assigning it to users.", "executiveText": "Prevents employees from sharing their calendars with external parties, protecting sensitive meeting information and internal schedules from unauthorized access. This security measure helps maintain confidentiality of business activities while still allowing internal collaboration.", @@ -2045,9 +1923,7 @@ "impactColour": "info", "addedDate": "2024-01-08", "powershellEquivalent": "Get-SharingPolicy | Set-SharingPolicy -Enabled $False", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.AutoAddProxy", @@ -2072,10 +1948,7 @@ { "name": "standards.DisableAdditionalStorageProviders", "cat": "Exchange Standards", - "tag": [ - "CIS M365 5.0 (6.5.3)", - "exo_storageproviderrestricted" - ], + "tag": ["CIS M365 5.0 (6.5.3)", "exo_storageproviderrestricted"], "helpText": "Disables the ability for users to open files in Outlook on the Web, from other providers such as Box, Dropbox, Facebook, Google Drive, OneDrive Personal, etc.", "docsDescription": "Disables additional storage providers in OWA. This is to prevent users from using personal storage providers like Dropbox, Google Drive, etc. Usually this has little user impact.", "executiveText": "Prevents employees from accessing personal cloud storage services like Dropbox or Google Drive through Outlook on the web, reducing data security risks and ensuring company information stays within approved corporate systems. This helps maintain data governance and prevents accidental data leaks.", @@ -2085,16 +1958,12 @@ "impactColour": "info", "addedDate": "2024-01-17", "powershellEquivalent": "Get-OwaMailboxPolicy | Set-OwaMailboxPolicy -AdditionalStorageProvidersEnabled $False", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.AntiSpamSafeList", "cat": "Defender Standards", - "tag": [ - "CIS M365 5.0 (2.1.13)" - ], + "tag": ["CIS M365 5.0 (2.1.13)"], "helpText": "Sets the anti-spam connection filter policy option 'safe list' in Defender.", "docsDescription": "Sets [Microsoft's built-in 'safe list'](https://learn.microsoft.com/en-us/powershell/module/exchange/set-hostedconnectionfilterpolicy?view=exchange-ps#-enablesafelist) in the anti-spam connection filter policy, rather than setting a custom safe/block list of IPs.", "executiveText": "Enables Microsoft's pre-approved list of trusted email servers to improve email delivery from legitimate sources while maintaining spam protection. This reduces false positives where legitimate emails might be blocked while still protecting against spam and malicious emails.", @@ -2143,13 +2012,21 @@ "type": "number", "name": "standards.ShortenMeetings.DefaultMinutesToReduceShortEventsBy", "label": "Minutes to reduce short calendar events by (Default is 5)", - "defaultValue": 5 + "defaultValue": 5, + "validators": { + "min": { "value": 0, "message": "Minimum value is 0" }, + "max": { "value": 29, "message": "Maximum value is 29" } + } }, { "type": "number", "name": "standards.ShortenMeetings.DefaultMinutesToReduceLongEventsBy", "label": "Minutes to reduce long calendar events by (Default is 10)", - "defaultValue": 10 + "defaultValue": 10, + "validators": { + "min": { "value": 0, "message": "Minimum value is 0" }, + "max": { "value": 29, "message": "Maximum value is 29" } + } } ], "label": "Set shorten meetings state", @@ -2242,9 +2119,7 @@ "impactColour": "warning", "addedDate": "2024-02-05", "powershellEquivalent": "Get-ManagementRoleAssignment | Remove-ManagementRoleAssignment", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.SafeSendersDisable", @@ -2263,9 +2138,7 @@ "impactColour": "warning", "addedDate": "2023-10-26", "powershellEquivalent": "Set-MailboxJunkEmailConfiguration", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.DelegateSentItems", @@ -2301,9 +2174,7 @@ "impactColour": "warning", "addedDate": "2022-05-25", "powershellEquivalent": "Set-Mailbox", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.UserSubmissions", @@ -2346,11 +2217,7 @@ { "name": "standards.DisableSharedMailbox", "cat": "Exchange Standards", - "tag": [ - "CIS M365 5.0 (1.2.2)", - "CISA (MS.AAD.10.1v1)", - "NIST CSF 2.0 (PR.AA-01)" - ], + "tag": ["CIS M365 5.0 (1.2.2)", "CISA (MS.AAD.10.1v1)", "NIST CSF 2.0 (PR.AA-01)"], "helpText": "Blocks login for all accounts that are marked as a shared mailbox. This is Microsoft best practice to prevent direct logons to shared mailboxes.", "docsDescription": "Shared mailboxes can be directly logged into if the password is reset, this presents a security risk as do all shared login credentials. Microsoft's recommendation is to disable the user account for shared mailboxes. It would be a good idea to review the sign-in reports to establish potential impact.", "executiveText": "Prevents direct login to shared mailbox accounts (like info@company.com), ensuring they can only be accessed through authorized users' accounts. This security measure eliminates the risk of shared passwords and unauthorized access while maintaining proper access control and audit trails.", @@ -2360,17 +2227,12 @@ "impactColour": "warning", "addedDate": "2021-11-16", "powershellEquivalent": "Get-Mailbox & Update-MgUser", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.DisableResourceMailbox", "cat": "Exchange Standards", - "tag": [ - "NIST CSF 2.0 (PR.AA-01)" - ], + "tag": ["NIST CSF 2.0 (PR.AA-01)"], "helpText": "Blocks login for all accounts that are marked as a resource mailbox and does not have a license assigned. Accounts that are synced from on-premises AD are excluded, as account state is managed in the on-premises AD.", "docsDescription": "Resource mailboxes can be directly logged into if the password is reset, this presents a security risk as do all shared login credentials. Microsoft's recommendation is to disable the user account for resource mailboxes. Accounts that are synced from on-premises AD are excluded, as account state is managed in the on-premises AD.", "executiveText": "Prevents direct login to resource mailbox accounts (like conference rooms or equipment), ensuring they can only be managed through proper administrative channels. This security measure eliminates potential unauthorized access to resource scheduling systems while maintaining proper booking functionality.", @@ -2380,10 +2242,7 @@ "impactColour": "warning", "addedDate": "2025-06-01", "powershellEquivalent": "Get-Mailbox & Update-MgUser", - "recommendedBy": [ - "Microsoft", - "CIPP" - ] + "recommendedBy": ["Microsoft", "CIPP"] }, { "name": "standards.EXODisableAutoForwarding", @@ -2404,17 +2263,12 @@ "impactColour": "danger", "addedDate": "2024-07-26", "powershellEquivalent": "Set-HostedOutboundSpamFilterPolicy -AutoForwardingMode 'Off'", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.RetentionPolicyTag", "cat": "Exchange Standards", - "tag": [ - "CIS M365 5.0 (6.4.1)" - ], + "tag": ["CIS M365 5.0 (6.4.1)"], "helpText": "Creates a CIPP - Deleted Items retention policy tag that permanently deletes items in the Deleted Items folder after X days.", "docsDescription": "Creates a CIPP - Deleted Items retention policy tag that permanently deletes items in the Deleted Items folder after X days.", "executiveText": "Automatically and permanently removes deleted emails after a specified number of days, helping manage storage costs and ensuring compliance with data retention policies. This prevents accumulation of unnecessary deleted items while maintaining a reasonable recovery window for accidentally deleted emails.", @@ -2567,9 +2421,7 @@ "impactColour": "info", "addedDate": "2024-03-25", "powershellEquivalent": "Set-SafeLinksPolicy or New-SafeLinksPolicy", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.AntiPhishPolicy", @@ -2598,7 +2450,11 @@ "type": "number", "label": "Phishing email threshold. (Default 1)", "name": "standards.AntiPhishPolicy.PhishThresholdLevel", - "defaultValue": 1 + "defaultValue": 1, + "validators": { + "min": { "value": 1, "message": "Minimum value is 1" }, + "max": { "value": 4, "message": "Maximum value is 4" } + } }, { "type": "switch", @@ -2790,9 +2646,7 @@ "impactColour": "info", "addedDate": "2024-03-25", "powershellEquivalent": "Set-AntiPhishPolicy or New-AntiPhishPolicy", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.SafeAttachmentPolicy", @@ -2876,17 +2730,12 @@ "impactColour": "info", "addedDate": "2024-03-25", "powershellEquivalent": "Set-SafeAttachmentPolicy or New-SafeAttachmentPolicy", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.AtpPolicyForO365", "cat": "Defender Standards", - "tag": [ - "CIS M365 5.0 (2.1.5)", - "NIST CSF 2.0 (DE.CM-09)" - ], + "tag": ["CIS M365 5.0 (2.1.5)", "NIST CSF 2.0 (DE.CM-09)"], "helpText": "This creates a Atp policy that enables Defender for Office 365 for SharePoint, OneDrive and Microsoft Teams.", "addedComponent": [ { @@ -2902,9 +2751,7 @@ "impactColour": "info", "addedDate": "2024-03-25", "powershellEquivalent": "Set-AtpPolicyForO365", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.PhishingSimulations", @@ -3054,9 +2901,7 @@ "impactColour": "info", "addedDate": "2024-03-25", "powershellEquivalent": "Set-MalwareFilterPolicy or New-MalwareFilterPolicy", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.PhishSimSpoofIntelligence", @@ -3105,7 +2950,11 @@ "type": "number", "label": "Bulk email threshold (Default 7)", "name": "standards.SpamFilterPolicy.BulkThreshold", - "defaultValue": 7 + "defaultValue": 7, + "validators": { + "min": { "value": 1, "message": "Minimum value is 1" }, + "max": { "value": 9, "message": "Maximum value is 9" } + } }, { "type": "autoComplete", @@ -3496,9 +3345,7 @@ "impactColour": "info", "addedDate": "2023-05-19", "powershellEquivalent": "Graph API", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.intuneBrandingProfile", @@ -3602,7 +3449,12 @@ { "type": "number", "name": "standards.IntuneComplianceSettings.deviceComplianceCheckinThresholdDays", - "label": "Compliance status validity period (days)" + "label": "Compliance status validity period (days)", + "defaultValue": 130, + "validators": { + "min": { "value": 1, "message": "Minimum value is 1" }, + "max": { "value": 120, "message": "Maximum value is 120" } + } } ], "label": "Set Intune Compliance Settings", @@ -3656,9 +3508,7 @@ { "name": "standards.DefaultPlatformRestrictions", "cat": "Intune Standards", - "tag": [ - "CISA (MS.AAD.19.1v1)" - ], + "tag": ["CISA (MS.AAD.19.1v1)"], "helpText": "Sets the default platform restrictions for enrolling devices into Intune. Note: Do not block personally owned if platform is blocked.", "executiveText": "Controls which types of devices (iOS, Android, Windows, macOS) and ownership models (corporate vs. personal) can be enrolled in the company's device management system. This helps maintain security standards while supporting necessary business device types and usage scenarios.", "addedComponent": [ @@ -3730,6 +3580,27 @@ "powershellEquivalent": "Graph API", "recommendedBy": [] }, + { + "name": "standards.MDMEnrollmentDuringRegistration", + "cat": "Intune Standards", + "tag": [], + "helpText": "Controls the \"Allow my organization to manage my device\" prompt when adding a work or school account on Windows. This setting determines whether automatic MDM enrollment occurs during account registration.", + "docsDescription": "Controls whether Windows shows the \"Allow my organization to manage my device\" prompt when users add a work or school account. When set to disabled, this setting prevents automatic MDM enrollment during the account registration flow, separating account registration from device enrollment. This is useful for environments where you want to allow users to add work accounts without triggering MDM enrollment.", + "executiveText": "Controls automatic device management enrollment during work account setup. When disabled, users can add work accounts to their Windows devices without the prompt asking to allow organizational device management, preventing unintended MDM enrollments on personal or BYOD devices.", + "addedComponent": [ + { + "type": "switch", + "name": "standards.MDMEnrollmentDuringRegistration.disableEnrollment", + "label": "Disable MDM enrollment during registration" + } + ], + "label": "Configure MDM enrollment when adding work or school account", + "impact": "Medium Impact", + "impactColour": "warning", + "addedDate": "2025-12-15", + "powershellEquivalent": "Graph API", + "recommendedBy": [] + }, { "name": "standards.EnrollmentWindowsHelloForBusinessConfiguration", "cat": "Intune Standards", @@ -3767,13 +3638,21 @@ "type": "number", "name": "standards.EnrollmentWindowsHelloForBusinessConfiguration.pinMinimumLength", "label": "Minimum PIN length (4-127)", - "default": 4 + "default": 4, + "validators": { + "min": { "value": 4, "message": "Minimum value is 4" }, + "max": { "value": 127, "message": "Maximum value is 127" } + } }, { "type": "number", "name": "standards.EnrollmentWindowsHelloForBusinessConfiguration.pinMaximumLength", "label": "Maximum PIN length (4-127)", - "default": 127 + "default": 127, + "validators": { + "min": { "value": 4, "message": "Minimum value is 4" }, + "max": { "value": 127, "message": "Maximum value is 127" } + } }, { "type": "autoComplete", @@ -3890,9 +3769,7 @@ { "name": "standards.intuneDeviceReg", "cat": "Intune Standards", - "tag": [ - "CISA (MS.AAD.17.1v1)" - ], + "tag": ["CISA (MS.AAD.17.1v1)"], "helpText": "Sets the maximum number of devices that can be registered by a user. A value of 0 disables device registration by users", "executiveText": "Limits how many devices each employee can register for corporate access, preventing excessive device proliferation while accommodating legitimate business needs. This helps maintain security oversight and prevents potential abuse of device registration privileges.", "addedComponent": [ @@ -4012,7 +3889,11 @@ "type": "number", "name": "standards.SPFileRequests.expirationDays", "label": "Link Expiration 1-730 Days (Optional)", - "required": false + "required": false, + "validators": { + "min": { "value": 1, "message": "Minimum value is 1" }, + "max": { "value": 730, "message": "Maximum value is 730" } + } } ], "label": "Set SharePoint and OneDrive File Requests", @@ -4020,9 +3901,7 @@ "impactColour": "warning", "addedDate": "2025-07-30", "powershellEquivalent": "Set-SPOTenant -CoreRequestFilesLinkEnabled $true -OneDriveRequestFilesLinkEnabled $true -CoreRequestFilesLinkExpirationInDays 30 -OneDriveRequestFilesLinkExpirationInDays 30", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.TenantDefaultTimezone", @@ -4047,9 +3926,7 @@ { "name": "standards.SPAzureB2B", "cat": "SharePoint Standards", - "tag": [ - "CIS M365 5.0 (7.2.2)" - ], + "tag": ["CIS M365 5.0 (7.2.2)"], "helpText": "Ensure SharePoint and OneDrive integration with Azure AD B2B is enabled", "executiveText": "Enables secure collaboration with external partners through SharePoint and OneDrive by integrating with Azure B2B guest access. This allows controlled sharing with external organizations while maintaining security oversight and proper access management.", "addedComponent": [], @@ -4058,18 +3935,12 @@ "impactColour": "info", "addedDate": "2024-07-09", "powershellEquivalent": "Set-SPOTenant -EnableAzureADB2BIntegration $true", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.SPDisallowInfectedFiles", "cat": "SharePoint Standards", - "tag": [ - "CIS M365 5.0 (7.3.1)", - "CISA (MS.SPO.3.1v1)", - "NIST CSF 2.0 (DE.CM-09)" - ], + "tag": ["CIS M365 5.0 (7.3.1)", "CISA (MS.SPO.3.1v1)", "NIST CSF 2.0 (DE.CM-09)"], "helpText": "Ensure Office 365 SharePoint infected files are disallowed for download", "executiveText": "Prevents employees from downloading files that have been identified as containing malware or viruses from SharePoint and OneDrive. This security measure protects against malware distribution through file sharing while maintaining access to clean, safe documents.", "addedComponent": [], @@ -4078,10 +3949,7 @@ "impactColour": "info", "addedDate": "2024-07-09", "powershellEquivalent": "Set-SPOTenant -DisallowInfectedFileDownload $true", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.SPDisableLegacyWorkflows", @@ -4109,25 +3977,24 @@ "impactColour": "warning", "addedDate": "2024-07-09", "powershellEquivalent": "Set-SPOTenant -DefaultSharingLinkType Direct", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.SPExternalUserExpiration", "cat": "SharePoint Standards", - "tag": [ - "CIS M365 5.0 (7.2.9)", - "CISA (MS.SPO.1.5v1)" - ], + "tag": ["CIS M365 5.0 (7.2.9)", "CISA (MS.SPO.1.5v1)"], "helpText": "Ensure guest access to a site or OneDrive will expire automatically", "executiveText": "Automatically expires external user access to SharePoint sites and OneDrive after a specified period, reducing security risks from forgotten or unnecessary guest accounts. This ensures external access is regularly reviewed and maintained only when actively needed.", "addedComponent": [ { "type": "number", "name": "standards.SPExternalUserExpiration.Days", - "label": "Days until expiration (Default 60)" + "label": "Days until expiration (Default 60)", + "defaultValue": 60, + "validators": { + "min": { "value": 1, "message": "Minimum value is 1" }, + "max": { "value": 730, "message": "Maximum value is 730" } + } } ], "label": "Set guest access to expire automatically", @@ -4135,24 +4002,24 @@ "impactColour": "warning", "addedDate": "2024-07-09", "powershellEquivalent": "Set-SPOTenant -ExternalUserExpireInDays 30 -ExternalUserExpirationRequired $True", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.SPEmailAttestation", "cat": "SharePoint Standards", - "tag": [ - "CIS M365 5.0 (7.2.10)", - "CISA (MS.SPO.1.6v1)" - ], + "tag": ["CIS M365 5.0 (7.2.10)", "CISA (MS.SPO.1.6v1)"], "helpText": "Ensure re-authentication with verification code is restricted", "executiveText": "Requires external users to periodically re-verify their identity through email verification codes when accessing SharePoint resources, adding an extra security layer for external collaboration. This helps ensure continued legitimacy of external access over time.", "addedComponent": [ { "type": "number", "name": "standards.SPEmailAttestation.Days", - "label": "Require re-authentication every X Days (Default 15)" + "label": "Require re-authentication every X Days (Default 15)", + "defaultValue": 15, + "validators": { + "min": { "value": 1, "message": "Minimum value is 1" }, + "max": { "value": 365, "message": "Maximum value is 365" } + } } ], "label": "Require re-authentication with verification code", @@ -4160,19 +4027,12 @@ "impactColour": "warning", "addedDate": "2024-07-09", "powershellEquivalent": "Set-SPOTenant -EmailAttestationRequired $true -EmailAttestationReAuthDays 15", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.DefaultSharingLink", "cat": "SharePoint Standards", - "tag": [ - "CIS M365 5.0 (7.2.7)", - "CIS M365 5.0 (7.2.11)", - "CISA (MS.SPO.1.4v1)" - ], + "tag": ["CIS M365 5.0 (7.2.7)", "CIS M365 5.0 (7.2.11)", "CISA (MS.SPO.1.4v1)"], "helpText": "Configure the SharePoint default sharing link type and permission. This setting controls both the type of sharing link created by default and the permission level assigned to those links.", "docsDescription": "Sets the default sharing link type (Direct or Internal) and permission (View) in SharePoint and OneDrive. Direct sharing means links only work for specific people, while Internal sharing means links work for anyone in the organization. Setting the view permission as the default ensures that users must deliberately select the edit permission when sharing a link, reducing the risk of unintentionally granting edit privileges.", "executiveText": "Configures SharePoint default sharing links to implement the principle of least privilege for document sharing. This security measure reduces the risk of accidental data modification while maintaining collaboration functionality, requiring users to explicitly select Edit permissions when necessary. The sharing type setting controls whether links are restricted to specific recipients or available to the entire organization. This reduces the risk of accidental data exposure through link sharing.", @@ -4201,10 +4061,7 @@ "impactColour": "info", "addedDate": "2025-06-13", "powershellEquivalent": "Set-SPOTenant -DefaultSharingLinkType [Direct|Internal] -DefaultLinkPermission View", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.DisableAddShortcutsToOneDrive", @@ -4289,19 +4146,12 @@ "impactColour": "warning", "addedDate": "2024-02-05", "powershellEquivalent": "Set-SPOTenant -LegacyAuthProtocolsEnabled $false", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.sharingCapability", "cat": "SharePoint Standards", - "tag": [ - "CIS M365 5.0 (7.2.3)", - "CISA (MS.AAD.14.1v1)", - "CISA (MS.SPO.1.1v1)" - ], + "tag": ["CIS M365 5.0 (7.2.3)", "CISA (MS.AAD.14.1v1)", "CISA (MS.SPO.1.1v1)"], "helpText": "Sets the default sharing level for OneDrive and SharePoint. This is a tenant wide setting and overrules any settings set on the site level", "executiveText": "Defines the organization's default policy for sharing files and folders in SharePoint and OneDrive, balancing collaboration needs with security requirements. This fundamental setting determines whether employees can share with external users, anonymous links, or only internal colleagues.", "addedComponent": [ @@ -4335,19 +4185,12 @@ "impactColour": "danger", "addedDate": "2022-06-15", "powershellEquivalent": "Update-MgBetaAdminSharePointSetting", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.DisableReshare", "cat": "SharePoint Standards", - "tag": [ - "CIS M365 5.0 (7.2.5)", - "CISA (MS.AAD.14.2v1)", - "CISA (MS.SPO.1.2v1)" - ], + "tag": ["CIS M365 5.0 (7.2.5)", "CISA (MS.AAD.14.2v1)", "CISA (MS.SPO.1.2v1)"], "helpText": "Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access", "docsDescription": "Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access. This is a tenant wide setting and overrules any settings set on the site level", "executiveText": "Prevents external users from sharing company documents with additional people, maintaining control over document distribution and preventing unauthorized access expansion. This security measure ensures that external sharing remains within intended boundaries set by internal employees.", @@ -4357,10 +4200,7 @@ "impactColour": "danger", "addedDate": "2022-06-15", "powershellEquivalent": "Update-MgBetaAdminSharePointSetting", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.DisableUserSiteCreate", @@ -4414,11 +4254,7 @@ { "name": "standards.unmanagedSync", "cat": "SharePoint Standards", - "tag": [ - "CIS M365 5.0 (7.2.3)", - "CISA (MS.SPO.2.1v1)", - "NIST CSF 2.0 (PR.AA-05)" - ], + "tag": ["CIS M365 5.0 (7.2.3)", "CISA (MS.SPO.2.1v1)", "NIST CSF 2.0 (PR.AA-05)"], "helpText": "Entra P1 required. Block or limit access to SharePoint and OneDrive content from unmanaged devices (those not hybrid AD joined or compliant in Intune). These controls rely on Microsoft Entra Conditional Access policies and can take up to 24 hours to take effect.", "docsDescription": "Entra P1 required. Block or limit access to SharePoint and OneDrive content from unmanaged devices (those not hybrid AD joined or compliant in Intune). These controls rely on Microsoft Entra Conditional Access policies and can take up to 24 hours to take effect. 0 = Allow Access, 1 = Allow limited, web-only access, 2 = Block access. All information about this can be found in Microsofts documentation [here.](https://learn.microsoft.com/en-us/sharepoint/control-access-from-unmanaged-devices)", "executiveText": "Restricts access to company files from personal or unmanaged devices, ensuring corporate data can only be accessed from properly secured and monitored devices. This critical security control prevents data leaks while allowing controlled access through web browsers when necessary.", @@ -4447,18 +4283,12 @@ "impactColour": "danger", "addedDate": "2025-06-13", "powershellEquivalent": "Set-SPOTenant -ConditionalAccessPolicy AllowFullAccess | AllowLimitedAccess | BlockAccess", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.sharingDomainRestriction", "cat": "SharePoint Standards", - "tag": [ - "CIS M365 5.0 (7.2.6)", - "CISA (MS.AAD.14.3v1)", - "CISA (MS.SPO.1.3v1)" - ], + "tag": ["CIS M365 5.0 (7.2.6)", "CISA (MS.AAD.14.3v1)", "CISA (MS.SPO.1.3v1)"], "helpText": "Restricts sharing to only users with the specified domain. This is useful for organizations that only want to share with their own domain.", "executiveText": "Controls which external domains employees can share files with, enabling secure collaboration with trusted partners while blocking sharing with unauthorized organizations. This targeted approach maintains necessary business relationships while preventing data exposure to unknown entities.", "addedComponent": [ @@ -4597,9 +4427,7 @@ "impactColour": "info", "addedDate": "2024-11-12", "powershellEquivalent": "Set-CsTeamsMeetingPolicy -AllowAnonymousUsersToJoinMeeting $false -AllowAnonymousUsersToStartMeeting $false -AutoAdmittedUsers $AutoAdmittedUsers -AllowPSTNUsersToBypassLobby $false -MeetingChatEnabledType EnabledExceptAnonymous -DesignatedPresenterRoleMode $DesignatedPresenterRoleMode -AllowExternalParticipantGiveRequestControl $false", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.TeamsChatProtection", @@ -4627,9 +4455,7 @@ "impactColour": "info", "addedDate": "2025-10-02", "powershellEquivalent": "Set-CsTeamsMessagingConfiguration -FileTypeCheck 'Enabled' -UrlReputationCheck 'Enabled' -ReportIncorrectSecurityDetections 'Enabled'", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.TeamsExternalChatWithAnyone", @@ -4661,9 +4487,7 @@ "impactColour": "info", "addedDate": "2025-11-03", "powershellEquivalent": "Set-CsTeamsMessagingPolicy -Identity Global -UseB2BInvitesToAddExternalUsers $false/$true", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.TeamsEmailIntegration", @@ -4683,12 +4507,8 @@ "impactColour": "info", "addedDate": "2024-07-30", "powershellEquivalent": "Set-CsTeamsClientConfiguration -AllowEmailIntoChannel $false", - "recommendedBy": [ - "CIS" - ], - "tag": [ - "CIS M365 5.0 (8.1.2)" - ] + "recommendedBy": ["CIS"], + "tag": ["CIS M365 5.0 (8.1.2)"] }, { "name": "standards.TeamsGuestAccess", @@ -4742,16 +4562,12 @@ "impactColour": "info", "addedDate": "2025-06-14", "powershellEquivalent": "Set-CsTeamsMeetingPolicy -CaptchaVerificationForMeetingJoin", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.TeamsExternalFileSharing", "cat": "Teams Standards", - "tag": [ - "CIS M365 5.0 (8.4.1)" - ], + "tag": ["CIS M365 5.0 (8.4.1)"], "helpText": "Ensure external file sharing in Teams is enabled for only approved cloud storage services.", "executiveText": "Controls which external cloud storage services (like Google Drive, Dropbox, Box) employees can access through Teams, ensuring file sharing occurs only through approved and secure platforms. This helps maintain data governance while supporting necessary business integrations.", "addedComponent": [ @@ -4786,9 +4602,7 @@ "impactColour": "info", "addedDate": "2024-07-28", "powershellEquivalent": "Set-CsTeamsClientConfiguration -AllowGoogleDrive $false -AllowShareFile $false -AllowBox $false -AllowDropBox $false -AllowEgnyte $false", - "recommendedBy": [ - "CIS" - ] + "recommendedBy": ["CIS"] }, { "name": "standards.TeamsEnrollUser", @@ -4915,7 +4729,12 @@ "type": "number", "name": "standards.TeamsMeetingRecordingExpiration.ExpirationDays", "label": "Recording Expiration Days (e.g., 365)", - "required": true + "required": true, + "defaultValue": 120, + "validators": { + "min": { "value": -1, "message": "Minimum value is -1" }, + "max": { "value": 99999, "message": "Maximum value is 99999" } + } } ], "label": "Set Teams Meeting Recording Expiration", @@ -5028,7 +4847,11 @@ "type": "number", "name": "standards.AutopilotStatusPage.TimeOutInMinutes", "label": "Timeout in minutes", - "defaultValue": 60 + "defaultValue": 60, + "validators": { + "min": { "value": 1, "message": "Minimum value is 1" }, + "max": { "value": 1440, "message": "Maximum value is 1440" } + } }, { "type": "textField", @@ -5233,10 +5056,7 @@ "queryKey": "ListIntuneTemplates-tag-autcomplete", "url": "/api/ListIntuneTemplates?mode=Tag", "labelField": "label", - "valueField": "value", - "addedField": { - "templates": "templates" - } + "valueField": "value" } }, { @@ -5495,7 +5315,11 @@ "type": "number", "name": "standards.MailboxRecipientLimits.RecipientLimit", "label": "Recipient Limit", - "defaultValue": 500 + "defaultValue": 500, + "validators": { + "min": { "value": 1, "message": "Minimum value is 1" }, + "max": { "value": 1000, "message": "Maximum value is 1000" } + } } ], "label": "Set Mailbox Recipient Limits", @@ -5503,18 +5327,12 @@ "impactColour": "info", "addedDate": "2025-05-28", "powershellEquivalent": "Set-Mailbox -RecipientLimits", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.DisableExchangeOnlinePowerShell", "cat": "Exchange Standards", - "tag": [ - "CIS M365 5.0 (6.1.1)", - "Security", - "NIST CSF 2.0 (PR.AA-05)" - ], + "tag": ["CIS M365 5.0 (6.1.1)", "Security", "NIST CSF 2.0 (PR.AA-05)"], "helpText": "Disables Exchange Online PowerShell access for non-admin users by setting the RemotePowerShellEnabled property to false for each user. This helps prevent attackers from using PowerShell to run malicious commands, access file systems, registry, and distribute ransomware throughout networks. Users with admin roles are automatically excluded.", "docsDescription": "Disables Exchange Online PowerShell access for non-admin users by setting the RemotePowerShellEnabled property to false for each user. This security measure follows a least privileged access approach, preventing potential attackers from using PowerShell to execute malicious commands, access sensitive systems, or distribute malware. Users with management roles containing 'Admin' are automatically excluded to ensure administrators retain PowerShell access to perform necessary management tasks.", "executiveText": "Restricts PowerShell access to Exchange Online for regular employees while maintaining access for administrators, significantly reducing security risks from compromised accounts. This prevents attackers from using PowerShell to execute malicious commands or distribute ransomware while preserving necessary administrative capabilities.", @@ -5523,19 +5341,12 @@ "impactColour": "warning", "addedDate": "2025-06-19", "powershellEquivalent": "Set-User -Identity $user -RemotePowerShellEnabled $false", - "recommendedBy": [ - "CIS", - "CIPP" - ] + "recommendedBy": ["CIS", "CIPP"] }, { "name": "standards.OWAAttachmentRestrictions", "cat": "Exchange Standards", - "tag": [ - "CIS M365 5.0 (6.1.2)", - "Security", - "NIST CSF 2.0 (PR.AA-05)" - ], + "tag": ["CIS M365 5.0 (6.1.2)", "Security", "NIST CSF 2.0 (PR.AA-05)"], "helpText": "Restricts how users on unmanaged devices can interact with email attachments in Outlook on the web and new Outlook for Windows. Prevents downloading attachments or blocks viewing them entirely.", "docsDescription": "This standard configures the OWA mailbox policy to restrict access to email attachments on unmanaged devices. Users can be prevented from downloading attachments (but can view/edit via Office Online) or blocked from seeing attachments entirely. This helps prevent data exfiltration through email attachments on devices not managed by the organization.", "executiveText": "Restricts access to email attachments on personal or unmanaged devices while allowing full functionality on corporate-managed devices. This security measure prevents data theft through email attachments while maintaining productivity for employees using approved company devices.", @@ -5562,25 +5373,7 @@ "impactColour": "warning", "addedDate": "2025-08-22", "powershellEquivalent": "Set-OwaMailboxPolicy -Identity \"OwaMailboxPolicy-Default\" -ConditionalAccessPolicy ReadOnlyPlusAttachmentsBlocked", - "recommendedBy": [ - "Microsoft Zero Trust", - "CIPP" - ] - }, - { - "name": "standards.LegacyEmailReportAddins", - "cat": "Exchange Standards", - "tag": [], - "helpText": "Removes legacy Report Phishing and Report Message Outlook add-ins.", - "executiveText": "The legacy Report Phishing and Report Message Outlook add-ins are security issues with the add-in which makes them unsafe for the organization.", - "label": "Remove legacy Outlook Report add-ins", - "impact": "Low Impact", - "impactColour": "info", - "addedDate": "2025-08-26", - "powershellEquivalent": "None", - "recommendedBy": [ - "Microsoft" - ] + "recommendedBy": ["Microsoft Zero Trust", "CIPP"] }, { "name": "standards.DeployCheckChromeExtension", @@ -5708,16 +5501,12 @@ "impactColour": "info", "addedDate": "2025-09-18", "powershellEquivalent": "New-GraphPostRequest -uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies'", - "recommendedBy": [ - "CIPP" - ] + "recommendedBy": ["CIPP"] }, { "name": "standards.SecureScoreRemediation", "cat": "Global Standards", - "tag": [ - "lowimpact" - ], + "tag": ["lowimpact"], "helpText": "Allows bulk updating of Secure Score control profiles across tenants. Select controls and assign them to different states: Default, Ignored, Third-Party, or Reviewed.", "addedComponent": [ { @@ -5779,4 +5568,4 @@ "addedDate": "2025-11-19", "powershellEquivalent": "New-GraphPostRequest to /beta/security/secureScoreControlProfiles/{id}" } -] \ No newline at end of file +] diff --git a/src/hooks/use-securescore.js b/src/hooks/use-securescore.js index f96c2bd232b7..a51dc18d9138 100644 --- a/src/hooks/use-securescore.js +++ b/src/hooks/use-securescore.js @@ -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}` diff --git a/src/hooks/use-timezones.js b/src/hooks/use-timezones.js new file mode 100644 index 000000000000..1bfa4b275095 --- /dev/null +++ b/src/hooks/use-timezones.js @@ -0,0 +1,52 @@ +import { useState, useEffect } from "react"; +import { getTimeZones } from "@vvo/tzdb"; + +export const useTimezones = () => { + const [timezones, setTimezones] = useState([{ label: "UTC", value: "UTC" }]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + try { + setLoading(true); + const tzData = getTimeZones({ includeUtc: true }); + + if (!Array.isArray(tzData)) { + throw new Error("getTimeZones did not return an array"); + } + + const formattedTimezones = tzData + .filter((tz) => typeof tz?.name === "string" && tz.name.length > 0) + .map((tz) => { + const name = String(tz.name); + const current = tz?.currentTimeFormat ? String(tz.currentTimeFormat) : undefined; + const label = current ? `${name} (${current})` : name; + return { + label, + value: name, + alternativeName: tz?.alternativeName ? String(tz.alternativeName) : undefined, + }; + }) + // de-duplicate by value + .filter((item, idx, arr) => arr.findIndex((t) => t.value === item.value) === idx) + // sort by label for consistent UX + .sort((a, b) => a.label.localeCompare(b.label)); + + // Always ensure a non-empty array; prepend UTC as a safe default + const withFallback = formattedTimezones.length + ? formattedTimezones + : [{ label: "UTC", value: "UTC" }]; + setTimezones(withFallback); + setError(null); + } catch (err) { + console.error("Error loading timezones:", err); + setError(err.message); + // Fallback to UTC (already seeded), keep as-is + setTimezones((prev) => (prev?.length ? prev : [{ label: "UTC", value: "UTC" }])); + } finally { + setLoading(false); + } + }, []); + console.log("Timezones loaded:", timezones); + return { timezones, loading, error }; +}; diff --git a/src/layouts/HeaderedTabbedLayout.jsx b/src/layouts/HeaderedTabbedLayout.jsx index a449a934b1b4..ece1d0659924 100644 --- a/src/layouts/HeaderedTabbedLayout.jsx +++ b/src/layouts/HeaderedTabbedLayout.jsx @@ -81,14 +81,18 @@ export const HeaderedTabbedLayout = (props) => { ) : ( subtitle && ( - {subtitle.map((item, index) => ( - - {item.icon} - - {item.text} - - - ))} + {subtitle.map((item, index) => + item.component ? ( + {item.component} + ) : ( + + {item.icon} + + {item.text} + + + ) + )} ) )} @@ -111,7 +115,7 @@ export const HeaderedTabbedLayout = (props) => { !mdDown && { flexGrow: 1, overflow: "auto", - height: "calc(100vh - 30px)", + height: "calc(100vh - 350px)", } } > diff --git a/src/layouts/config.js b/src/layouts/config.js index ce0edeb5cdd9..8923b99832d1 100644 --- a/src/layouts/config.js +++ b/src/layouts/config.js @@ -173,6 +173,11 @@ export const nativeMenuItems = [ path: "/tenant/administration/partner-relationships", permissions: ["Tenant.Relationship.*"], }, + { + title: "Domains", + path: "/tenant/administration/domains", + permissions: ["Tenant.Administration.*"], + }, ], }, { @@ -190,7 +195,7 @@ export const nativeMenuItems = [ items: [ { title: "Standards Management", - path: "/tenant/standards/list-standards", + path: "/tenant/standards/alignment", permissions: ["Tenant.Standards.*"], }, { @@ -428,7 +433,7 @@ export const nativeMenuItems = [ permissions: ["Endpoint.MEM.*"], }, { - title: "Protection Policies", + title: "App Policies", path: "/endpoint/MEM/list-appprotection-policies", permissions: ["Endpoint.MEM.*"], }, @@ -456,7 +461,7 @@ export const nativeMenuItems = [ }, { title: "Reports", - permissions: ["Endpoint.Device.*", "Endpoint.Autopilot.*"], + permissions: ["Endpoint.Device.*", "Endpoint.Autopilot.*", "Endpoint.MEM.*"], items: [ { title: "Analytics Device Score", @@ -473,6 +478,11 @@ export const nativeMenuItems = [ path: "/endpoint/reports/autopilot-deployment", permissions: ["Endpoint.Autopilot.*"], }, + { + title: "Discovered Apps", + path: "/endpoint/reports/detected-apps", + permissions: ["Endpoint.MEM.*"], + }, ], }, ], @@ -744,6 +754,7 @@ export const nativeMenuItems = [ "Tenant.Application.*", "Tenant.DomainAnalyser.*", "Exchange.Mailbox.*", + "CIPP.Scheduler.*", ], items: [ { @@ -907,6 +918,12 @@ export const nativeMenuItems = [ roles: ["superadmin"], permissions: ["CIPP.SuperAdmin.*"], }, + { + title: "Diagnostics", + path: "/cipp/advanced/diagnostics", + roles: ["superadmin"], + permissions: ["CIPP.SuperAdmin.*"], + }, ], }, ], diff --git a/src/layouts/index.js b/src/layouts/index.js index 8508f200f287..92774c200e4d 100644 --- a/src/layouts/index.js +++ b/src/layouts/index.js @@ -114,14 +114,6 @@ export const Layout = (props) => { const filterItemsByRole = (items) => { return items .map((item) => { - // role - if (item.roles && item.roles.length > 0) { - const hasRole = item.roles.some((requiredRole) => userRoles.includes(requiredRole)); - if (!hasRole) { - return null; - } - } - // Check permission with pattern matching support if (item.permissions && item.permissions.length > 0) { const hasPermission = userPermissions?.some((userPerm) => { @@ -324,7 +316,7 @@ export const Layout = (props) => {
)} {(currentTenant === "AllTenants" || !currentTenant) && !allTenantsSupport ? ( - + diff --git a/src/pages/_app.js b/src/pages/_app.js index a1218d67d46a..b9b16e9d3ecd 100644 --- a/src/pages/_app.js +++ b/src/pages/_app.js @@ -366,14 +366,13 @@ const App = (props) => { {(settings) => { - if (!settings.isInitialized) { - } + // Create theme even while initializing to avoid blank screen const theme = createTheme({ colorPreset: "orange", - direction: settings.direction, + direction: settings.direction || "ltr", paletteMode: settings.currentTheme?.value !== "browser" - ? settings.currentTheme?.value + ? settings.currentTheme?.value || "light" : preferredTheme, contrast: "high", }); diff --git a/src/pages/cipp/advanced/diagnostics.js b/src/pages/cipp/advanced/diagnostics.js new file mode 100644 index 000000000000..c37e5ef5c478 --- /dev/null +++ b/src/pages/cipp/advanced/diagnostics.js @@ -0,0 +1,399 @@ +import { Layout as DashboardLayout } from "/src/layouts/index.js"; +import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; +import CippDiagnosticsFilter from "/src/components/CippTable/CippDiagnosticsFilter"; +import { CippPropertyListCard } from "/src/components/CippCards/CippPropertyListCard"; +import { useState } from "react"; +import { Grid } from "@mui/system"; +import { Box, Typography, Chip, Stack, Divider } from "@mui/material"; +import { + Error as ErrorIcon, + Warning as WarningIcon, + Info as InfoIcon, + BugReport as DebugIcon, +} from "@mui/icons-material"; + +const Page = () => { + const [apiFilter, setApiFilter] = useState({ query: "", presetDisplayName: null, columns: null }); + const queryKey = JSON.stringify(apiFilter); + + const pageTitle = apiFilter.presetDisplayName + ? `Diagnostics - ${apiFilter.presetDisplayName}` + : "Diagnostics - Application Insights Query"; + + // Determine simpleColumns based on preset columns + const simpleColumns = apiFilter.columns || []; + + return ( + + + + + + } + clearOnError={true} + offCanvas={{ + size: "lg", + children: (row) => { + // Detect event type + const eventName = row.name || ""; + const isConsoleLog = eventName === "CIPP.ConsoleLog"; + const isTaskCompleted = eventName === "CIPP.TaskCompleted"; + const isStandardCompleted = eventName === "CIPP.StandardCompleted"; + + // Console Log Renderer + if (isConsoleLog) { + const getSeverityConfig = (level) => { + const levelStr = String(level || "").toLowerCase(); + switch (levelStr) { + case "error": + case "4": + return { icon: , color: "error", label: "Error" }; + case "warning": + case "3": + return { icon: , color: "warning", label: "Warning" }; + case "debug": + case "0": + return { icon: , color: "default", label: "Debug" }; + case "verbose": + case "1": + return { icon: , color: "info", label: "Verbose" }; + default: + return { icon: , color: "info", label: "Information" }; + } + }; + + const message = + row.Message || row.customDimensions?.Message || row.message || "No message"; + const level = row.Level || row.customDimensions?.Level || row.severityLevel; + const timestamp = + row.Timestamp || + row.customDimensions?.Timestamp || + row.timestamp || + new Date().toISOString(); + const severityConfig = getSeverityConfig(level); + + // Try to extract and parse JSON from message + let parsedMessage = null; + let isJson = false; + let preJsonText = ""; + let postJsonText = ""; + + if (typeof message === "string") { + // Try to find JSON object or array in the message + const jsonObjectMatch = message.match(/(\{[\s\S]*\})/); + const jsonArrayMatch = message.match(/(\[[\s\S]*\])/); + const jsonMatch = jsonObjectMatch || jsonArrayMatch; + + if (jsonMatch) { + try { + parsedMessage = JSON.parse(jsonMatch[1]); + isJson = true; + const jsonStart = jsonMatch.index; + const jsonEnd = jsonStart + jsonMatch[1].length; + preJsonText = message.substring(0, jsonStart).trim(); + postJsonText = message.substring(jsonEnd).trim(); + } catch (e) { + // Not valid JSON, treat as regular text + } + } + } + + return ( + + + {/* Header with severity and timestamp */} + + + + + {new Date(timestamp).toLocaleString()} + + + + + + {/* Message */} + + + Message + {isJson && ( + + )} + + + {isJson ? ( + + {preJsonText && ( + + {preJsonText} + + )} + + {JSON.stringify(parsedMessage, null, 2)} + + {postJsonText && ( + + {postJsonText} + + )} + + ) : ( + + {message} + + )} + + + + + ); + } + + // Task/Standard Completed Renderer + if (isTaskCompleted || isStandardCompleted) { + const taskName = row.TaskName || row.customDimensions?.TaskName || "Unknown Task"; + const command = + row.Command || + row.customDimensions?.Command || + row.customDimensions?.Standard || + "N/A"; + const tenant = row.Tenant || row.customDimensions?.Tenant || "N/A"; + const count = row.Count || row.customDimensions?.Count; + const totalDuration = row.TotalDurationMs || row.customDimensions?.TotalDurationMs; + const avgDuration = row.AvgDurationMs || row.customDimensions?.AvgDurationMs; + const maxDuration = row.MaxDurationMs || row.customDimensions?.MaxDurationMs; + const timestamp = row.timestamp || new Date().toISOString(); + const status = row.Status || row.customDimensions?.Status || "Completed"; + + return ( + + + {/* Header */} + + + + + + {new Date(timestamp).toLocaleString()} + + + + + + {/* Summary */} + + + Summary + + + + + {isTaskCompleted ? "Task Name" : "Standard"} + + {taskName} + + + + Command + + + {command} + + + + + Tenant + + {tenant} + + {count && ( + + + Count + + {count} + + )} + {totalDuration && ( + + + Total Duration (ms) + + {totalDuration.toFixed(2)} + + )} + {avgDuration && ( + + + Average Duration (ms) + + {avgDuration.toFixed(2)} + + )} + {maxDuration && ( + + + Max Duration (ms) + + {maxDuration.toFixed(2)} + + )} + + + + + ); + } + + // Default/Generic Renderer for other event types + const renderValue = (value) => { + if (value === null || value === undefined) { + return ( + + {String(value)} + + ); + } + if (typeof value === "boolean") { + return ( + + ); + } + if (typeof value === "object") { + return ( + + {JSON.stringify(value, null, 2)} + + ); + } + return String(value); + }; + + // Build property items for CippPropertyListCard + const propertyItems = []; + + // Add timestamp first + propertyItems.push({ + label: "timestamp", + value: new Date(row.timestamp || new Date().toISOString()).toLocaleString(), + }); + + // Add all other properties + Object.entries(row) + .filter(([key]) => key !== "timestamp" && key !== "customDimensions") + .sort(([a], [b]) => a.localeCompare(b)) + .forEach(([key, value]) => { + propertyItems.push({ + label: key, + value: renderValue(value), + }); + }); + + // Add customDimensions properties + if (row.customDimensions) { + Object.entries(row.customDimensions) + .sort(([a], [b]) => a.localeCompare(b)) + .forEach(([key, value]) => { + propertyItems.push({ + label: `customDimensions.${key}`, + value: renderValue(value), + }); + }); + } + + return ( + + + + + + + + + + + + ); + }, + }} + title={pageTitle} + tenantInTitle={false} + apiDataKey="Results" + apiUrl={apiFilter.query ? "/api/ExecAppInsightsQuery" : "/api/ListEmptyResults"} + apiData={apiFilter} + queryKey={queryKey} + simpleColumns={simpleColumns} + actions={[]} + /> + ); +}; + +Page.getLayout = (page) => {page}; + +export default Page; diff --git a/src/pages/cipp/logs/index.js b/src/pages/cipp/logs/index.js index c17f96f15b89..18f8ad68174c 100644 --- a/src/pages/cipp/logs/index.js +++ b/src/pages/cipp/logs/index.js @@ -22,7 +22,6 @@ import { EyeIcon } from "@heroicons/react/24/outline"; const simpleColumns = [ "DateTime", "Tenant", - "TenantID", "User", "Message", "API", diff --git a/src/pages/cipp/logs/logentry.js b/src/pages/cipp/logs/logentry.js index 8e32cdfe3a2c..2dbb4a23a5a7 100644 --- a/src/pages/cipp/logs/logentry.js +++ b/src/pages/cipp/logs/logentry.js @@ -80,23 +80,9 @@ const Page = () => { : []; return ( - + - {/* Back button */} - - {logRequest.isLoading && } {logRequest.isError && ( diff --git a/src/pages/cipp/scheduler/index.js b/src/pages/cipp/scheduler/index.js index d1948bc650e2..eaf46486abe6 100644 --- a/src/pages/cipp/scheduler/index.js +++ b/src/pages/cipp/scheduler/index.js @@ -77,6 +77,7 @@ const Page = () => { "Command", "Parameters", "PostExecution", + "Reference", "Recurrence", "Results", ]} diff --git a/src/pages/cipp/settings/backend.js b/src/pages/cipp/settings/backend.js index 283c2dccc55e..6cb35a490f1a 100644 --- a/src/pages/cipp/settings/backend.js +++ b/src/pages/cipp/settings/backend.js @@ -106,7 +106,11 @@ const Page = () => { {backendInfo.map((item) => ( - + ))} diff --git a/src/pages/cipp/settings/index.js b/src/pages/cipp/settings/index.js index 11b432e7988d..e6e5447ba12f 100644 --- a/src/pages/cipp/settings/index.js +++ b/src/pages/cipp/settings/index.js @@ -10,6 +10,7 @@ import CippDnsSettings from "/src/components/CippSettings/CippDnsSettings"; import CippCacheSettings from "/src/components/CippSettings/CippCacheSettings"; import CippBackupSettings from "/src/components/CippSettings/CippBackupSettings"; import CippBrandingSettings from "/src/components/CippSettings/CippBrandingSettings"; +import CippBackupRetentionSettings from "/src/components/CippSettings/CippBackupRetentionSettings"; const Page = () => { return ( @@ -29,6 +30,9 @@ const Page = () => { + + + diff --git a/src/pages/cipp/settings/notifications.js b/src/pages/cipp/settings/notifications.js index 408bdf6f5169..295012322952 100644 --- a/src/pages/cipp/settings/notifications.js +++ b/src/pages/cipp/settings/notifications.js @@ -17,6 +17,7 @@ const Page = () => { return ( { - const pageTitle = "Automated onboarding"; + const pageTitle = "Automated Onboarding"; const [testRunning, setTestRunning] = useState(false); const [correlationId, setCorrelationId] = useState(null); const [validateRunning, setValidateRunning] = useState(false); @@ -106,6 +106,7 @@ const Page = () => { useEffect(() => { if (listSubscription.isSuccess && listEventTypes.isSuccess) { formControl.reset({ + enabled: listSubscription?.data?.Results?.enabled ?? false, EventType: listSubscription?.data?.Results?.webhookEvents?.map((eventType) => { var event = listEventTypes?.data?.Results?.find((event) => event === eventType); return { label: event, value: event }; @@ -118,6 +119,7 @@ const Page = () => { return ( { sx={{ mb: 3, mx: 0, p: 0 }} isFetching={listSubscription.isFetching} propertyItems={[ + { + label: "Status", + value: ( + + ), + }, { label: "Webhook URL", value: , @@ -168,6 +179,14 @@ const Page = () => { showDivider={false} /> + + + { + const pageTitle = "Time Settings"; + + const formControl = useForm({ + mode: "onChange", + defaultValues: { + Timezone: { label: "UTC", value: "UTC" }, + BusinessHoursStart: { label: "09:00", value: "09:00" }, + }, + }); + + // Get timezone and backend info + const backendInfo = ApiGetCall({ + url: "/api/ExecBackendURLs", + queryKey: "backendInfo", + }); + + const { timezones, loading: timezonesLoading } = useTimezones(); + const isFlexConsumption = backendInfo.data?.Results?.SKU === "FlexConsumption"; + + // Generate business hours options (00:00 to 23:00 in hourly increments) + const businessHoursOptions = useMemo(() => { + const hours = []; + for (let i = 0; i < 24; i++) { + const hour = i.toString().padStart(2, "0"); + hours.push({ + label: `${hour}:00`, + value: `${hour}:00`, + }); + } + return hours; + }, []); + + useEffect(() => { + if (backendInfo.isSuccess && backendInfo.data) { + const tzStr = backendInfo.data?.Results?.Timezone || "UTC"; + const tzOption = (timezones || []).find( + (o) => o?.value === tzStr || o?.alternativeName === tzStr + ) || { + label: tzStr, + value: tzStr, + }; + + const startStr = backendInfo.data?.Results?.BusinessHoursStart || "09:00"; + const startOption = businessHoursOptions.find((o) => o.value === startStr) || { + label: startStr, + value: startStr, + }; + + formControl.reset({ + Timezone: tzOption, + BusinessHoursStart: startOption, + }); + } + }, [backendInfo.isSuccess, backendInfo.data, timezones, businessHoursOptions]); + + return ( + + + + + Configure the timezone for CIPP operations and scheduling. If you are using a Flex + Consumption App Service Plan, you can also configure business hours to optimize + performance and cost. + + + + {!backendInfo.isSuccess && ( + + Loading backend information... + + )} + + {timezonesLoading && ( + + Loading timezones... + + )} + + {backendInfo.isSuccess && ( + <> + + + + + {isFlexConsumption && ( + <> + + + + Flex Consumption Business Hours + + Business hours are used to optimize Flex Consumption instance scheduling. Set + the start time for your business hours. CIPP will maintain higher instance + availability during a 10-hour window from the start time for better performance. + Outside of this window, instances may scale down to reduce costs. + + + + + + + + )} + + {!isFlexConsumption && ( + + + + App Service Plan: {backendInfo.data?.SKU || "Unknown"} + + Business hours configuration is only available for Flex Consumption App Service + Plans. + + + )} + + )} + + + ); +}; + +Page.getLayout = (page) => ( + + {page} + +); + +export default Page; diff --git a/src/pages/dashboardv2/devices/index.js b/src/pages/dashboardv2/devices/index.js new file mode 100644 index 000000000000..a4c96d300f0d --- /dev/null +++ b/src/pages/dashboardv2/devices/index.js @@ -0,0 +1,36 @@ +import { Container, Typography, Card, CardContent, CardHeader, Box } from "@mui/material"; +import { TabbedLayout } from "/src/layouts/TabbedLayout"; +import { Layout as DashboardLayout } from "/src/layouts/index.js"; +import tabOptions from "../tabOptions"; + +const Page = () => { + return ( + + + + + + + Device Test Results + + + This tab will display detailed device test results and recommendations. + + + Review device compliance policies, enrollment restrictions, and management + configurations to enhance your device security posture. + + + + + + ); +}; + +Page.getLayout = (page) => ( + + {page} + +); + +export default Page; diff --git a/src/pages/dashboardv2/identity/index.js b/src/pages/dashboardv2/identity/index.js new file mode 100644 index 000000000000..78b76b13e131 --- /dev/null +++ b/src/pages/dashboardv2/identity/index.js @@ -0,0 +1,36 @@ +import { Container, Typography, Card, CardContent, CardHeader, Box } from "@mui/material"; +import { TabbedLayout } from "/src/layouts/TabbedLayout"; +import { Layout as DashboardLayout } from "/src/layouts/index.js"; +import tabOptions from "../tabOptions"; + +const Page = () => { + return ( + + + + + + + Identity Test Results + + + This tab will display detailed identity test results and recommendations. + + + Configure your identity policies and authentication methods to improve your zero trust + posture. + + + + + + ); +}; + +Page.getLayout = (page) => ( + + {page} + +); + +export default Page; diff --git a/src/pages/dashboardv2/index.js b/src/pages/dashboardv2/index.js new file mode 100644 index 000000000000..3b29d86dcaac --- /dev/null +++ b/src/pages/dashboardv2/index.js @@ -0,0 +1,1002 @@ +import { + Box, + Card, + CardContent, + CardHeader, + Container, + Typography, + Avatar, + Divider, + Tooltip, +} from "@mui/material"; +import { Grid } from "@mui/system"; +import { + BarChart, + Bar, + PieChart, + Pie, + Cell, + RadialBarChart, + RadialBar, + PolarAngleAxis, + XAxis, + YAxis, + ResponsiveContainer, + Tooltip as RechartsTooltip, + LabelList, +} from "recharts"; +import { TabbedLayout } from "/src/layouts/TabbedLayout"; +import { Layout as DashboardLayout } from "/src/layouts/index.js"; +import tabOptions from "./tabOptions"; +import { dashboardDemoData } from "/src/data/dashboardv2-demo-data"; +import { CaSankey } from "/src/components/CippComponents/CaSankey"; +import { CaDeviceSankey } from "/src/components/CippComponents/CaDeviceSankey"; +import { AuthMethodSankey } from "/src/components/CippComponents/AuthMethodSankey"; +import { DesktopDevicesSankey } from "/src/components/CippComponents/DesktopDevicesSankey"; +import { MobileSankey } from "/src/components/CippComponents/MobileSankey"; +import { + People as UsersIcon, + Person as UserIcon, + PersonOutline as GuestIcon, + Group as GroupIcon, + Apps as AppsIcon, + Devices as DevicesIcon, + PhoneAndroid as ManagedIcon, + Security as SecurityIcon, + Business as BuildingIcon, + CheckCircle as CheckCircleIcon, + Laptop as MonitorIcon, + Work as BriefcaseIcon, +} from "@mui/icons-material"; + +const Page = () => { + const reportData = dashboardDemoData; + + const formatNumber = (num) => { + if (!num && num !== 0) return "0"; + if (num >= 1000) { + return (num / 1000).toFixed(1) + "K"; + } + return num.toLocaleString(); + }; + + const metricDescriptions = { + users: "Total number of users in your tenant", + guests: "External users with guest access", + groups: "Microsoft 365 and security groups", + apps: "Registered applications", + devices: "All devices accessing tenant resources", + managed: "Devices enrolled in Intune", + }; + + return ( + + + {/* Tenant Overview Section - 3 Column Layout */} + + {/* Column 1: Tenant Information */} + + + + + Tenant + + } + sx={{ pb: 1.5 }} + /> + + + + + Name + + + {reportData.TenantName || "Not Available"} + + + + + Tenant ID + + + {reportData.TenantId || "Not Available"} + + + + + Primary Domain + + + {reportData.Domain || "Not Available"} + + + + + + + + {/* Column 2: Tenant Metrics - 2x3 Grid */} + + + + + + + + + + + Users + + + {formatNumber(reportData.TenantInfo.TenantOverview.UserCount)} + + + + + + + + + + + + + + Guests + + + {formatNumber(reportData.TenantInfo.TenantOverview.GuestCount)} + + + + + + + + + + + + + + Groups + + + {formatNumber(reportData.TenantInfo.TenantOverview.GroupCount)} + + + + + + + + + + + + + + Apps + + + {formatNumber(reportData.TenantInfo.TenantOverview.ApplicationCount)} + + + + + + + + + + + + + + Devices + + + {formatNumber(reportData.TenantInfo.TenantOverview.DeviceCount)} + + + + + + + + + + + + + + Managed + + + {formatNumber(reportData.TenantInfo.TenantOverview.ManagedDeviceCount)} + + + + + + + + + {/* Column 3: Assessment Results */} + + + + + Assessment + + } + sx={{ pb: 1.5 }} + /> + + + + + + Identity + + + {reportData.TestResultSummary.IdentityPassed}/ + {reportData.TestResultSummary.IdentityTotal} + + tests + + + + + + Devices + + + {reportData.TestResultSummary.DevicesPassed}/ + {reportData.TestResultSummary.DevicesTotal} + + tests + + + + + + + + + + + + + + + + + + + {/* Identity Section - 2 Column Grid */} + + + {/* Left Column */} + + + {/* Privileged users auth methods */} + + + + Privileged users auth methods + + } + sx={{ pb: 1 }} + /> + + + {reportData.TenantInfo.OverviewAuthMethodsPrivilegedUsers?.nodes && ( + + )} + + + {reportData.TenantInfo.OverviewAuthMethodsPrivilegedUsers?.description || + "No description available"} + + + + + {/* All Users Auth Methods */} + + + + All users auth methods + + } + sx={{ pb: 1 }} + /> + + + {reportData.TenantInfo.OverviewAuthMethodsAllUsers?.nodes && ( + + )} + + + {reportData.TenantInfo.OverviewAuthMethodsAllUsers?.description || + "No description available"} + + + + + + + {/* Right Column */} + + + {/* User Authentication */} + + + + User authentication + + } + sx={{ pb: 1 }} + /> + + + {reportData.TenantInfo.OverviewCaMfaAllUsers?.nodes && ( + + )} + + + {reportData.TenantInfo.OverviewCaMfaAllUsers?.description || + "No description available"} + + + + + {/* Device Sign-ins */} + + + + Device sign-ins + + } + sx={{ pb: 1 }} + /> + + + {reportData.TenantInfo.OverviewCaDevicesAllUsers?.nodes && ( + + )} + + + {reportData.TenantInfo.OverviewCaDevicesAllUsers?.description || + "No description available"} + + + + + + + + + {/* Devices Section */} + + + {/* Device Summary Chart */} + + + + + Device summary + + } + sx={{ pb: 1 }} + /> + + + + + + + + + + + + + + + + + + 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 + )} + % + + + + + + + + {/* Device Compliance */} + + + + + Device compliance + + } + sx={{ pb: 1 }} + /> + + + + + + + + + + + + + + + + Compliant + + + + {Math.round( + (reportData.TenantInfo.DeviceOverview.DeviceCompliance + .compliantDeviceCount / + (reportData.TenantInfo.DeviceOverview.DeviceCompliance + .compliantDeviceCount + + reportData.TenantInfo.DeviceOverview.DeviceCompliance + .nonCompliantDeviceCount)) * + 100 + )} + % + + + + + + + + Non-compliant + + + + {Math.round( + (reportData.TenantInfo.DeviceOverview.DeviceCompliance + .nonCompliantDeviceCount / + (reportData.TenantInfo.DeviceOverview.DeviceCompliance + .compliantDeviceCount + + reportData.TenantInfo.DeviceOverview.DeviceCompliance + .nonCompliantDeviceCount)) * + 100 + )} + % + + + + + + + + {/* Device Ownership */} + + + + + Device ownership + + } + sx={{ pb: 1 }} + /> + + + + + + + + + + + + + + + + Corporate + + + + {Math.round( + (reportData.TenantInfo.DeviceOverview.DeviceOwnership.corporateCount / + (reportData.TenantInfo.DeviceOverview.DeviceOwnership.corporateCount + + reportData.TenantInfo.DeviceOverview.DeviceOwnership.personalCount)) * + 100 + )} + % + + + + + + + + Personal + + + + {Math.round( + (reportData.TenantInfo.DeviceOverview.DeviceOwnership.personalCount / + (reportData.TenantInfo.DeviceOverview.DeviceOwnership.corporateCount + + reportData.TenantInfo.DeviceOverview.DeviceOwnership.personalCount)) * + 100 + )} + % + + + + + + + + {/* Desktop Devices - Full Width */} + + + + + Desktop devices + + } + sx={{ pb: 1 }} + /> + + + {reportData.TenantInfo.DeviceOverview.DesktopDevicesSummary?.nodes && ( + + )} + + + {reportData.TenantInfo.DeviceOverview.DesktopDevicesSummary?.description || + "No description available"} + + + + + + + + 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); + })()} + % + + + + + + + + {/* Mobile Devices - Full Width */} + + + + + Mobile devices + + } + sx={{ pb: 1 }} + /> + + + {reportData.TenantInfo.DeviceOverview.MobileSummary?.nodes && ( + + )} + + + {reportData.TenantInfo.DeviceOverview.MobileSummary?.description || + "No description available"} + + + + + + + + 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; + })()} + + + + + + + + + + + ); +}; + +Page.getLayout = (page) => ( + + {page} + +); + +export default Page; diff --git a/src/pages/dashboardv2/tabOptions.json b/src/pages/dashboardv2/tabOptions.json new file mode 100644 index 000000000000..4c2bb6411b4d --- /dev/null +++ b/src/pages/dashboardv2/tabOptions.json @@ -0,0 +1,14 @@ +[ + { + "label": "Overview", + "path": "/dashboardv2" + }, + { + "label": "Identity", + "path": "/dashboardv2/identity" + }, + { + "label": "Devices", + "path": "/dashboardv2/devices" + } +] diff --git a/src/pages/endpoint/MEM/devices/index.js b/src/pages/endpoint/MEM/devices/index.js index c0ba3b4790da..e2d5c52ef4ef 100644 --- a/src/pages/endpoint/MEM/devices/index.js +++ b/src/pages/endpoint/MEM/devices/index.js @@ -169,6 +169,32 @@ const Page = () => { condition: (row) => row.operatingSystem === "macOS", confirmText: "Are you sure you want to retrieve the FileVault key for [deviceName]?", }, + { + label: "Reset Passcode", + type: "POST", + icon: , + url: "/api/ExecDevicePasscodeAction", + data: { + GUID: "id", + Action: "resetPasscode", + }, + 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.", + }, + { + label: "Remove Passcode", + type: "POST", + icon: , + url: "/api/ExecDevicePasscodeAction", + data: { + GUID: "id", + Action: "removeDevicePasscode", + }, + condition: (row) => row.operatingSystem === "iOS", + confirmText: + "Are you sure you want to remove the passcode from [deviceName]? This will remove the device passcode requirement.", + }, { label: "Windows Defender Full Scan", type: "POST", diff --git a/src/pages/endpoint/MEM/list-appprotection-policies/index.js b/src/pages/endpoint/MEM/list-appprotection-policies/index.js index 2ec6781e2fa4..f86460eab062 100644 --- a/src/pages/endpoint/MEM/list-appprotection-policies/index.js +++ b/src/pages/endpoint/MEM/list-appprotection-policies/index.js @@ -1,13 +1,25 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; -import { Book } from "@mui/icons-material"; -import { TrashIcon } from "@heroicons/react/24/outline"; +import { Book, LaptopChromebook } from "@mui/icons-material"; +import { GlobeAltIcon, TrashIcon, UserIcon, UserGroupIcon } from "@heroicons/react/24/outline"; import { PermissionButton } from "/src/utils/permissions.js"; import { CippPolicyDeployDrawer } from "/src/components/CippComponents/CippPolicyDeployDrawer.jsx"; +import { useSettings } from "/src/hooks/use-settings.js"; + +const assignmentModeOptions = [ + { label: "Replace existing assignments", value: "replace" }, + { label: "Append to existing assignments", value: "append" }, +]; + +const assignmentFilterTypeOptions = [ + { label: "Include - Apply policy to devices matching filter", value: "include" }, + { label: "Exclude - Apply policy to devices NOT matching filter", value: "exclude" }, +]; const Page = () => { const pageTitle = "App Protection & Configuration Policies"; const cardButtonPermissions = ["Endpoint.MEM.ReadWrite"]; + const tenant = useSettings().currentTenant; const actions = [ { @@ -16,19 +28,179 @@ const Page = () => { url: "/api/AddIntuneTemplate", data: { ID: "id", - URLName: "managedAppPolicies", + URLName: "URLName", }, confirmText: "Are you sure you want to create a template based on this policy?", icon: , color: "info", }, + { + label: "Assign to All Users", + type: "POST", + url: "/api/ExecAssignPolicy", + data: { + AssignTo: "allLicensedUsers", + ID: "id", + type: "URLName", + platformType: "!deviceAppManagement", + }, + fields: [ + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all users?', + icon: , + color: "info", + }, + { + label: "Assign to All Devices", + type: "POST", + url: "/api/ExecAssignPolicy", + data: { + AssignTo: "AllDevices", + ID: "id", + type: "URLName", + platformType: "!deviceAppManagement", + }, + fields: [ + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all devices?', + icon: , + color: "info", + }, + { + label: "Assign Globally (All Users / All Devices)", + type: "POST", + url: "/api/ExecAssignPolicy", + data: { + AssignTo: "AllDevicesAndUsers", + ID: "id", + type: "URLName", + platformType: "!deviceAppManagement", + }, + fields: [ + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all users and devices?', + icon: , + color: "info", + }, + { + label: "Assign to Custom Group", + type: "POST", + url: "/api/ExecAssignPolicy", + icon: , + color: "info", + confirmText: 'Select the target groups for "[displayName]".', + fields: [ + { + type: "autoComplete", + name: "groupTargets", + label: "Group(s)", + multiple: true, + creatable: false, + allowResubmit: true, + validators: { required: "Please select at least one group" }, + api: { + url: "/api/ListGraphRequest", + dataKey: "Results", + queryKey: `ListPolicyAssignmentGroups-${tenant}`, + labelField: (group) => + group.id ? `${group.displayName} (${group.id})` : group.displayName, + valueField: "id", + addedField: { + description: "description", + }, + data: { + Endpoint: "groups", + manualPagination: true, + $select: "id,displayName,description", + $orderby: "displayName", + $top: 999, + $count: true, + }, + }, + }, + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups.", + }, + { + type: "autoComplete", + name: "assignmentFilter", + label: "Assignment Filter (Optional)", + multiple: false, + creatable: false, + api: { + url: "/api/ListAssignmentFilters", + queryKey: `ListAssignmentFilters-${tenant}`, + labelField: (filter) => filter.displayName, + valueField: "displayName", + }, + }, + { + type: "radio", + name: "assignmentFilterType", + label: "Assignment Filter Mode", + options: assignmentFilterTypeOptions, + defaultValue: "include", + helperText: "Choose whether to include or exclude devices matching the filter.", + }, + ], + customDataformatter: (row, action, formData) => { + const selectedGroups = Array.isArray(formData?.groupTargets) ? formData.groupTargets : []; + const tenantFilterValue = tenant === "AllTenants" && row?.Tenant ? row.Tenant : tenant; + return { + tenantFilter: tenantFilterValue, + ID: row?.id, + type: row?.URLName, + platformType: "deviceAppManagement", + GroupIds: selectedGroups.map((group) => group.value).filter(Boolean), + GroupNames: selectedGroups.map((group) => group.label).filter(Boolean), + assignmentMode: formData?.assignmentMode || "replace", + AssignmentFilterName: formData?.assignmentFilter?.value || null, + AssignmentFilterType: formData?.assignmentFilter?.value + ? formData?.assignmentFilterType || "include" + : null, + }; + }, + }, { label: "Delete Policy", type: "POST", url: "/api/RemovePolicy", data: { ID: "id", - URLName: "managedAppPolicies", + URLName: "URLName", }, confirmText: "Are you sure you want to delete this policy?", icon: , @@ -42,22 +214,23 @@ const Page = () => { "displayName", "lastModifiedDateTime", "PolicyTypeName", + "PolicySource", ], actions: actions, }; - const simpleColumns = ["displayName", "isAssigned", "lastModifiedDateTime"]; + const simpleColumns = [ + "displayName", + "PolicyTypeName", + "PolicyAssignment", + "PolicyExclude", + "lastModifiedDateTime", + ]; return ( { const pageTitle = "Intune Compliance Policies"; const cardButtonPermissions = ["Endpoint.MEM.ReadWrite"]; + const tenant = useSettings().currentTenant; const actions = [ { @@ -31,7 +43,18 @@ const Page = () => { ID: "id", type: "deviceCompliancePolicies", }, - confirmText: "Are you sure you want to assign this policy to all users?", + fields: [ + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all users?', icon: , color: "info", }, @@ -44,7 +67,18 @@ const Page = () => { ID: "id", type: "deviceCompliancePolicies", }, - confirmText: "Are you sure you want to assign this policy to all devices?", + fields: [ + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all devices?', icon: , color: "info", }, @@ -57,10 +91,105 @@ const Page = () => { ID: "id", type: "deviceCompliancePolicies", }, - confirmText: "Are you sure you want to assign this policy to all users and devices?", + fields: [ + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all users and devices?', icon: , color: "info", }, + { + label: "Assign to Custom Group", + type: "POST", + url: "/api/ExecAssignPolicy", + icon: , + color: "info", + confirmText: 'Select the target groups for "[displayName]".', + fields: [ + { + type: "autoComplete", + name: "groupTargets", + label: "Group(s)", + multiple: true, + creatable: false, + allowResubmit: true, + validators: { required: "Please select at least one group" }, + api: { + url: "/api/ListGraphRequest", + dataKey: "Results", + queryKey: `ListPolicyAssignmentGroups-${tenant}`, + labelField: (group) => + group.id ? `${group.displayName} (${group.id})` : group.displayName, + valueField: "id", + addedField: { + description: "description", + }, + data: { + Endpoint: "groups", + manualPagination: true, + $select: "id,displayName,description", + $orderby: "displayName", + $top: 999, + $count: true, + }, + }, + }, + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups.", + }, + { + type: "autoComplete", + name: "assignmentFilter", + label: "Assignment Filter (Optional)", + multiple: false, + creatable: false, + api: { + url: "/api/ListAssignmentFilters", + queryKey: `ListAssignmentFilters-${tenant}`, + labelField: (filter) => filter.displayName, + valueField: "displayName", + }, + }, + { + type: "radio", + name: "assignmentFilterType", + label: "Assignment Filter Mode", + options: assignmentFilterTypeOptions, + defaultValue: "include", + helperText: "Choose whether to include or exclude devices matching the filter.", + }, + ], + customDataformatter: (row, action, formData) => { + const selectedGroups = Array.isArray(formData?.groupTargets) ? formData.groupTargets : []; + const tenantFilterValue = tenant === "AllTenants" && row?.Tenant ? row.Tenant : tenant; + return { + tenantFilter: tenantFilterValue, + ID: row?.id, + type: "deviceCompliancePolicies", + GroupIds: selectedGroups.map((group) => group.value).filter(Boolean), + GroupNames: selectedGroups.map((group) => group.label).filter(Boolean), + assignmentMode: formData?.assignmentMode || "replace", + AssignmentFilterName: formData?.assignmentFilter?.value || null, + AssignmentFilterType: formData?.assignmentFilter?.value + ? formData?.assignmentFilterType || "include" + : null, + }; + }, + }, { label: "Delete Policy", type: "POST", @@ -85,20 +214,19 @@ const Page = () => { actions: actions, }; - const simpleColumns = ["displayName", "description", "lastModifiedDateTime"]; + const simpleColumns = [ + "displayName", + "PolicyTypeName", + "PolicyAssignment", + "PolicyExclude", + "description", + "lastModifiedDateTime", + ]; return ( { const pageTitle = "Configuration Policies"; const cardButtonPermissions = ["Endpoint.MEM.ReadWrite"]; + const tenant = useSettings().currentTenant; const actions = [ { @@ -31,7 +43,18 @@ const Page = () => { ID: "id", type: "URLName", }, - confirmText: "Are you sure you want to assign this policy to all users?", + fields: [ + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all users?', icon: , color: "info", }, @@ -44,7 +67,18 @@ const Page = () => { ID: "id", type: "URLName", }, - confirmText: "Are you sure you want to assign this policy to all devices?", + fields: [ + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all devices?', icon: , color: "info", }, @@ -57,7 +91,18 @@ const Page = () => { ID: "id", type: "URLName", }, - confirmText: "Are you sure you want to assign this policy to all users and devices?", + fields: [ + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all users and devices?', icon: , color: "info", }, @@ -65,21 +110,85 @@ const Page = () => { label: "Assign to Custom Group", type: "POST", url: "/api/ExecAssignPolicy", - data: { - ID: "id", - type: "URLName", - }, - confirmText: "Enter the name of the group to assign this policy to. Wildcards (*) are allowed.", icon: , color: "info", + confirmText: 'Select the target groups for "[displayName]".', fields: [ { - type: "textField", - name: "AssignTo", - label: "Group Name(s), optionally comma-separated", - placeholder: "IT-*, Sales Team", + type: "autoComplete", + name: "groupTargets", + label: "Group(s)", + multiple: true, + creatable: false, + allowResubmit: true, + validators: { required: "Please select at least one group" }, + api: { + url: "/api/ListGraphRequest", + dataKey: "Results", + queryKey: `ListPolicyAssignmentGroups-${tenant}`, + labelField: (group) => + group.id ? `${group.displayName} (${group.id})` : group.displayName, + valueField: "id", + addedField: { + description: "description", + }, + data: { + Endpoint: "groups", + manualPagination: true, + $select: "id,displayName,description", + $orderby: "displayName", + $top: 999, + $count: true, + }, + }, + }, + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups.", + }, + { + type: "autoComplete", + name: "assignmentFilter", + label: "Assignment Filter (Optional)", + multiple: false, + creatable: false, + api: { + url: "/api/ListAssignmentFilters", + queryKey: `ListAssignmentFilters-${tenant}`, + labelField: (filter) => filter.displayName, + valueField: "displayName", + }, + }, + { + type: "radio", + name: "assignmentFilterType", + label: "Assignment Filter Mode", + options: assignmentFilterTypeOptions, + defaultValue: "include", + helperText: "Choose whether to include or exclude devices matching the filter.", }, ], + customDataformatter: (row, action, formData) => { + const selectedGroups = Array.isArray(formData?.groupTargets) ? formData.groupTargets : []; + const tenantFilterValue = tenant === "AllTenants" && row?.Tenant ? row.Tenant : tenant; + return { + tenantFilter: tenantFilterValue, + ID: row?.id, + type: row?.URLName, + GroupIds: selectedGroups.map((group) => group.value).filter(Boolean), + GroupNames: selectedGroups.map((group) => group.label).filter(Boolean), + assignmentMode: formData?.assignmentMode || "replace", + AssignmentFilterName: formData?.assignmentFilter?.value || null, + AssignmentFilterType: formData?.assignmentFilter?.value + ? formData?.assignmentFilterType || "include" + : null, + }; + }, }, { label: "Delete Policy", diff --git a/src/pages/endpoint/MEM/list-scripts/index.jsx b/src/pages/endpoint/MEM/list-scripts/index.jsx index d55737b0f3a0..8ace41a37f4f 100644 --- a/src/pages/endpoint/MEM/list-scripts/index.jsx +++ b/src/pages/endpoint/MEM/list-scripts/index.jsx @@ -1,6 +1,12 @@ import { Layout as DashboardLayout } from "/src/layouts/index"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage"; -import { TrashIcon, PencilIcon } from "@heroicons/react/24/outline"; +import { + TrashIcon, + PencilIcon, + UserIcon, + UserGroupIcon, + GlobeAltIcon, +} from "@heroicons/react/24/outline"; import { showToast } from "/src/store/toasts"; import { Button, @@ -14,11 +20,16 @@ import { import { CippCodeBlock } from "/src/components/CippComponents/CippCodeBlock"; import { useState, useEffect, useMemo } from "react"; import { useDispatch } from "react-redux"; -import { Close, Save } from "@mui/icons-material"; +import { Close, Save, LaptopChromebook } from "@mui/icons-material"; import { useSettings } from "../../../../hooks/use-settings"; import { Stack } from "@mui/system"; import { useQuery, useQueryClient } from "@tanstack/react-query"; +const assignmentModeOptions = [ + { label: "Replace existing assignments", value: "replace" }, + { label: "Append to existing assignments", value: "append" }, +]; + const Page = () => { const pageTitle = "Scripts"; const [codeOpen, setCodeOpen] = useState(false); @@ -32,10 +43,11 @@ const Page = () => { const dispatch = useDispatch(); const language = useMemo(() => { - return currentScript?.scriptType?.toLowerCase() === ("macos" || "linux") ? "shell" : "powershell"; + return currentScript?.scriptType?.toLowerCase() === ("macos" || "linux") + ? "shell" + : "powershell"; }, [currentScript?.scriptType]); - const tenantFilter = useSettings().currentTenant; const { isLoading: scriptIsLoading, @@ -154,7 +166,154 @@ const Page = () => { ); }; + // Map script type to Graph API endpoint + const getScriptEndpoint = (scriptType) => { + const mapping = { + Windows: "deviceManagementScripts", + MacOS: "deviceShellScripts", + Remediation: "deviceHealthScripts", + Linux: "configurationPolicies", + }; + return mapping[scriptType] || "deviceManagementScripts"; + }; + const actions = [ + { + label: "Assign to All Users", + type: "POST", + url: "/api/ExecAssignPolicy", + icon: , + color: "info", + fields: [ + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds the new ones.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all users?', + customDataformatter: (row, action, formData) => ({ + tenantFilter: tenantFilter, + ID: row?.id, + Type: getScriptEndpoint(row?.scriptType), + AssignTo: "allLicensedUsers", + assignmentMode: formData?.assignmentMode || "replace", + }), + }, + { + label: "Assign to All Devices", + type: "POST", + url: "/api/ExecAssignPolicy", + icon: , + color: "info", + fields: [ + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds the new ones.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all devices?', + customDataformatter: (row, action, formData) => ({ + tenantFilter: tenantFilter, + ID: row?.id, + Type: getScriptEndpoint(row?.scriptType), + AssignTo: "AllDevices", + assignmentMode: formData?.assignmentMode || "replace", + }), + }, + { + label: "Assign Globally (All Users / All Devices)", + type: "POST", + url: "/api/ExecAssignPolicy", + icon: , + color: "info", + fields: [ + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds the new ones.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all users and devices?', + customDataformatter: (row, action, formData) => ({ + tenantFilter: tenantFilter, + ID: row?.id, + Type: getScriptEndpoint(row?.scriptType), + AssignTo: "AllDevicesAndUsers", + assignmentMode: formData?.assignmentMode || "replace", + }), + }, + { + label: "Assign to Custom Group", + type: "POST", + url: "/api/ExecAssignPolicy", + icon: , + color: "info", + confirmText: 'Select the target groups for "[displayName]".', + fields: [ + { + type: "autoComplete", + name: "groupTargets", + label: "Group(s)", + multiple: true, + creatable: false, + allowResubmit: true, + validators: { required: "Please select at least one group" }, + api: { + url: "/api/ListGraphRequest", + dataKey: "Results", + queryKey: `ListScriptAssignmentGroups-${tenantFilter}`, + labelField: (group) => + group.id ? `${group.displayName} (${group.id})` : group.displayName, + valueField: "id", + addedField: { + description: "description", + }, + data: { + Endpoint: "groups", + manualPagination: true, + $select: "id,displayName,description", + $orderby: "displayName", + $top: 999, + $count: true, + }, + }, + }, + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds the new ones.", + }, + ], + customDataformatter: (row, action, formData) => { + const selectedGroups = Array.isArray(formData?.groupTargets) ? formData.groupTargets : []; + return { + tenantFilter: tenantFilter, + ID: row?.id, + Type: getScriptEndpoint(row?.scriptType), + GroupIds: selectedGroups.map((group) => group.value).filter(Boolean), + GroupNames: selectedGroups.map((group) => group.label).filter(Boolean), + assignmentMode: formData?.assignmentMode || "replace", + }; + }, + }, { label: "Edit Script", icon: , @@ -197,6 +356,8 @@ const Page = () => { const simpleColumns = [ "scriptType", "displayName", + "ScriptAssignment", + "ScriptExclude", "description", "runAsAccount", "lastModifiedDateTime", diff --git a/src/pages/endpoint/applications/list/index.js b/src/pages/endpoint/applications/list/index.js index 9822447c7151..6a5073f1ddef 100644 --- a/src/pages/endpoint/applications/list/index.js +++ b/src/pages/endpoint/applications/list/index.js @@ -1,13 +1,33 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; import { CippApiDialog } from "/src/components/CippComponents/CippApiDialog.jsx"; -import { GlobeAltIcon, TrashIcon, UserIcon } from "@heroicons/react/24/outline"; +import { GlobeAltIcon, TrashIcon, UserIcon, UserGroupIcon } from "@heroicons/react/24/outline"; import { LaptopMac, Sync } from "@mui/icons-material"; import { CippApplicationDeployDrawer } from "/src/components/CippComponents/CippApplicationDeployDrawer"; import { Button, Box } from "@mui/material"; import { useSettings } from "/src/hooks/use-settings.js"; import { useDialog } from "/src/hooks/use-dialog.js"; +const assignmentIntentOptions = [ + { label: "Required", value: "Required" }, + { label: "Available", value: "Available" }, + { label: "Available without enrollment", value: "AvailableWithoutEnrollment" }, + { label: "Uninstall", value: "Uninstall" }, +]; + +const assignmentModeOptions = [ + { label: "Replace existing assignments", value: "replace" }, + { label: "Append to existing assignments", value: "append" }, +]; + +const getAppAssignmentSettingsType = (odataType) => { + if (!odataType || typeof odataType !== "string") { + return undefined; + } + + return odataType.replace("#microsoft.graph.", "").replace(/App$/i, ""); +}; + const Page = () => { const pageTitle = "Applications"; const syncDialog = useDialog(); @@ -22,7 +42,28 @@ const Page = () => { AssignTo: "!AllUsers", ID: "id", }, - confirmText: "Are you sure you want to assign this app to all users?", + fields: [ + { + type: "radio", + name: "Intent", + label: "Assignment intent", + options: assignmentIntentOptions, + defaultValue: "Required", + validators: { required: "Select an assignment intent" }, + helperText: + "Available assigns to Company Portal, Required installs automatically, Uninstall removes the app, Available without enrollment exposes it without device enrollment.", + }, + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups/intents.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all users?', icon: , color: "info", }, @@ -34,7 +75,28 @@ const Page = () => { AssignTo: "!AllDevices", ID: "id", }, - confirmText: "Are you sure you want to assign this app to all devices?", + fields: [ + { + type: "radio", + name: "Intent", + label: "Assignment intent", + options: assignmentIntentOptions, + defaultValue: "Required", + validators: { required: "Select an assignment intent" }, + helperText: + "Available assigns to Company Portal, Required installs automatically, Uninstall removes the app, Available without enrollment exposes it without device enrollment.", + }, + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups/intents.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all devices?', icon: , color: "info", }, @@ -43,13 +105,104 @@ const Page = () => { type: "POST", url: "/api/ExecAssignApp", data: { - AssignTo: "!Both", + AssignTo: "!AllDevicesAndUsers", ID: "id", }, - confirmText: "Are you sure you want to assign this app to all users and devices?", + fields: [ + { + type: "radio", + name: "Intent", + label: "Assignment intent", + options: assignmentIntentOptions, + defaultValue: "Required", + validators: { required: "Select an assignment intent" }, + helperText: + "Available assigns to Company Portal, Required installs automatically, Uninstall removes the app, Available without enrollment exposes it without device enrollment.", + }, + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups/intents.", + }, + ], + confirmText: 'Are you sure you want to assign "[displayName]" to all users and devices?', icon: , color: "info", }, + { + label: "Assign to Custom Group", + type: "POST", + url: "/api/ExecAssignApp", + icon: , + color: "info", + confirmText: 'Select the target groups and intent for "[displayName]".', + fields: [ + { + type: "autoComplete", + name: "groupTargets", + label: "Group(s)", + multiple: true, + creatable: false, + allowResubmit: true, + validators: { required: "Please select at least one group" }, + api: { + url: "/api/ListGraphRequest", + dataKey: "Results", + queryKey: `ListAppAssignmentGroups-${tenant}`, + labelField: (group) => + group.id ? `${group.displayName} (${group.id})` : group.displayName, + valueField: "id", + addedField: { + description: "description", + }, + data: { + Endpoint: "groups", + manualPagination: true, + $select: "id,displayName,description", + $orderby: "displayName", + $top: 999, + $count: true, + }, + }, + }, + { + type: "radio", + name: "assignmentIntent", + label: "Assignment intent", + options: assignmentIntentOptions, + defaultValue: "Required", + validators: { required: "Select an assignment intent" }, + helperText: + "Available assigns to Company Portal, Required installs automatically, Uninstall removes the app, Available without enrollment exposes it without device enrollment.", + }, + { + type: "radio", + name: "assignmentMode", + label: "Assignment mode", + options: assignmentModeOptions, + defaultValue: "replace", + helperText: + "Replace will overwrite existing assignments. Append keeps current assignments and adds/overwrites only for the selected groups/intents.", + }, + ], + customDataformatter: (row, action, formData) => { + const selectedGroups = Array.isArray(formData?.groupTargets) ? formData.groupTargets : []; + const tenantFilterValue = tenant === "AllTenants" && row?.Tenant ? row.Tenant : tenant; + return { + tenantFilter: tenantFilterValue, + ID: row?.id, + GroupIds: selectedGroups.map((group) => group.value).filter(Boolean), + GroupNames: selectedGroups.map((group) => group.label).filter(Boolean), + Intent: formData?.assignmentIntent || "Required", + AssignmentMode: formData?.assignmentMode || "replace", + AppType: getAppAssignmentSettingsType(row?.["@odata.type"]), + }; + }, + }, { label: "Delete Application", type: "POST", @@ -57,7 +210,7 @@ const Page = () => { data: { ID: "id", }, - confirmText: "Are you sure you want to delete this application?", + confirmText: 'Are you sure you want to delete "[displayName]"?', icon: , color: "danger", }, @@ -82,9 +235,11 @@ const Page = () => { const simpleColumns = [ "displayName", + "AppAssignment", + "AppExclude", "publishingState", - "installCommandLine", - "uninstallCommandLine", + "lastModifiedDateTime", + "createdDateTime", ]; return ( diff --git a/src/pages/endpoint/reports/detected-apps/index.js b/src/pages/endpoint/reports/detected-apps/index.js new file mode 100644 index 000000000000..308d02cf3d79 --- /dev/null +++ b/src/pages/endpoint/reports/detected-apps/index.js @@ -0,0 +1,59 @@ +import { Layout as DashboardLayout } from "/src/layouts/index.js"; +import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; + +const Page = () => { + const pageTitle = "Discovered Apps"; + + // Columns to be displayed in the table + const simpleColumns = [ + "displayName", + "version", + "deviceCount", + "platform", + "publisher", + "sizeInByte", + ]; + + // Predefined filters + const filterList = [ + { + filterName: "Windows Apps", + value: [{ id: "platform", value: "windows" }], + type: "column", + }, + { + filterName: "macOS Apps", + value: [{ id: "platform", value: "macOS" }], + type: "column", + }, + { + filterName: "iOS Apps", + value: [{ id: "platform", value: "ios" }], + type: "column", + }, + { + filterName: "Android Apps", + value: [{ id: "platform", value: "android" }], + type: "column", + }, + ]; + + return ( + + ); +}; + +Page.getLayout = (page) => {page}; + +export default Page; diff --git a/src/pages/identity/administration/jit-admin/index.js b/src/pages/identity/administration/jit-admin/index.js index c76658a4fa95..46fd36efb4c9 100644 --- a/src/pages/identity/administration/jit-admin/index.js +++ b/src/pages/identity/administration/jit-admin/index.js @@ -5,6 +5,31 @@ import { AdminPanelSettings } from "@mui/icons-material"; import Link from "next/link"; const Page = () => { + const simpleColumns = [ + "userPrincipalName", + "displayName", + "accountEnabled", + "jitAdminEnabled", + "jitAdminStartDate", + "jitAdminExpiration", + "jitAdminReason", + "jitAdminCreatedBy", + "memberOf", + ]; + + const filters = [ + { + filterName: "Active JIT Admins", + value: [{ id: "jitAdminEnabled", value: true }], + type: "column", + }, + { + filterName: "Expired/Disabled", + value: [{ id: "jitAdminEnabled", value: false }], + type: "column", + }, + ]; + return ( { title="JIT Admin Table" apiUrl="/api/ListJITAdmin" apiDataKey="Results" - simpleColumns={[]} + simpleColumns={simpleColumns} + filters={filters} /> ); }; diff --git a/src/pages/identity/reports/mfa-report/index.js b/src/pages/identity/reports/mfa-report/index.js index b29cacdb046f..815e2f5a0d3c 100644 --- a/src/pages/identity/reports/mfa-report/index.js +++ b/src/pages/identity/reports/mfa-report/index.js @@ -15,6 +15,7 @@ const Page = () => { "CoveredByCA", "MFAMethods", "CAPolicies", + "IsAdmin", ]; const filters = [ { @@ -44,6 +45,11 @@ const Page = () => { value: [{ id: "MFARegistration", value: "Yes" }], type: "column", }, + { + filterName: "Admin Users", + value: [{ id: "IsAdmin", value: "Yes" }], + type: "column" + } ]; const actions = [ diff --git a/src/pages/index.js b/src/pages/index.js index 641235dc34f3..576bf8d504ff 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -203,6 +203,7 @@ const Page = () => { ); const [PortalMenuItems, setPortalMenuItems] = useState([]); + const [partnersVisible, setPartnersVisible] = useState(false); const formatStorageSize = (sizeInMB) => { if (sizeInMB >= 1024) { @@ -237,7 +238,7 @@ const Page = () => { } // Filter the portals based on user settings - return Portals.filter(portal => { + return Portals.filter((portal) => { const settingKey = portal.name; return settingKey ? portalLinks[settingKey] === true : true; }); @@ -260,7 +261,12 @@ const Page = () => { })); setPortalMenuItems(menuItems); } - }, [currentTenantInfo.isSuccess, currentTenant, settings.portalLinks, settings.UserSpecificSettings]); + }, [ + currentTenantInfo.isSuccess, + currentTenant, + settings.portalLinks, + settings.UserSpecificSettings, + ]); return ( <> @@ -283,11 +289,12 @@ const Page = () => { tenantId={organization.data?.id} userStats={{ licensedUsers: dashboard.data?.LicUsers || 0, - unlicensedUsers: dashboard.data?.Users && dashboard.data?.LicUsers - ? dashboard.data?.Users - dashboard.data?.LicUsers - : 0, + unlicensedUsers: + dashboard.data?.Users && dashboard.data?.LicUsers + ? dashboard.data?.Users - dashboard.data?.LicUsers + : 0, guests: dashboard.data?.Guests || 0, - globalAdmins: dashboard.data?.Gas || 0 + globalAdmins: dashboard.data?.Gas || 0, }} standardsData={driftApi.data} organizationData={organization.data} @@ -343,7 +350,7 @@ const Page = () => { "Aligned Policies", "Accepted Deviations", "Current Deviations", - "Customer Specific Deviations" + "Customer Specific Deviations", ] : ["Remediation", "Alert", "Report"] } @@ -397,10 +404,20 @@ const Page = () => { copyItems={true} title="Partner Relationships" isFetching={partners.isFetching} - propertyItems={partners.data?.Results.map((partner, idx) => ({ + propertyItems={partners.data?.Results?.slice( + 0, + partnersVisible ? undefined : 3 + ).map((partner, idx) => ({ label: partner.TenantInfo?.displayName, value: partner.TenantInfo?.defaultDomainName, }))} + actionButton={ + partners.data?.Results?.length > 3 && ( + + ) + } /> @@ -444,7 +461,6 @@ const Page = () => { - ); }; diff --git a/src/pages/tenant/administration/alert-configuration/alert.jsx b/src/pages/tenant/administration/alert-configuration/alert.jsx index f34627884b7f..91d671113f6b 100644 --- a/src/pages/tenant/administration/alert-configuration/alert.jsx +++ b/src/pages/tenant/administration/alert-configuration/alert.jsx @@ -31,6 +31,7 @@ import { CippApiResults } from "../../../../components/CippComponents/CippApiRes import { ApiGetCall, ApiPostCall } from "../../../../api/ApiCall"; import { PlusIcon } from "@heroicons/react/24/outline"; import { CippFormCondition } from "../../../../components/CippComponents/CippFormCondition"; +import { CippHead } from "../../../../components/CippComponents/CippHead"; const AlertWizard = () => { const apiRequest = ApiPostCall({ @@ -55,6 +56,8 @@ const AlertWizard = () => { { value: "4h", label: "Every 4 hours" }, { value: "1d", label: "Every 1 day" }, { value: "7d", label: "Every 7 days" }, + { value: "14d", label: "Every 14 days" }, + { value: "21d", label: "Every 21 days" }, { value: "30d", label: "Every 30 days" }, { value: "365d", label: "Every 365 days" }, ]); @@ -190,7 +193,17 @@ const AlertWizard = () => { ? JSON.parse(alert.RawAlert.Parameters) : alert.RawAlert.Parameters; if (params.InputValue) { - resetObject[usedCommand.inputName] = params.InputValue; + if (usedCommand.multipleInput) { + // Load multiple input values from InputValue object + usedCommand.inputs.forEach((input) => { + if (params.InputValue[input.inputName] !== undefined) { + resetObject[input.inputName] = params.InputValue[input.inputName]; + } + }); + } else { + // Backward compatibility: single input value + resetObject[usedCommand.inputName] = params.InputValue; + } } } catch (error) { console.error("Error parsing parameters:", error); @@ -442,9 +455,20 @@ const AlertWizard = () => { const handleScriptSubmit = (values) => { const getInputParams = () => { if (values.command.value.requiresInput) { - return { - InputValue: values[values.command.value.inputName], - }; + if (values.command.value.multipleInput) { + // Collect all input values into InputValue object + const inputValue = {}; + values.command.value.inputs.forEach((input) => { + if (values[input.inputName] !== undefined && values[input.inputName] !== null) { + inputValue[input.inputName] = values[input.inputName]; + } + }); + return { InputValue: inputValue }; + } else { + return { + InputValue: values[values.command.value.inputName], + }; + } } return {}; }; @@ -491,22 +515,10 @@ const AlertWizard = () => { const { isValid } = useFormState({ control: formControl.control }); return ( - + + - - - {existingAlert.isLoading && } {editAlert ? "Edit" : "Add"} Alert @@ -939,14 +951,33 @@ const AlertWizard = () => { /> - {commandValue?.value?.requiresInput && ( - - )} + {commandValue?.value?.requiresInput && + !commandValue.value?.multipleInput && ( + + )} + {commandValue?.value?.multipleInput && + commandValue.value?.inputs?.map((input, index) => ( + 0 ? 2 : 0 }} + > + + + + + ))} { formControl={formControl} multiline={true} rows={3} - placeholder="Add documentation, FAQ links, or instructions for when this alert triggers..." + placeholder="Add documentation, FAQ links, or instructions for when this alert triggers. Variable replacement like %tenantfilter%, %tenantname% and custom variables are supported. You can also use %resultcount% to include the number of results that triggered the alert." /> diff --git a/src/pages/tenant/administration/alert-configuration/index.js b/src/pages/tenant/administration/alert-configuration/index.js index 2b2864d14272..1cb731572572 100644 --- a/src/pages/tenant/administration/alert-configuration/index.js +++ b/src/pages/tenant/administration/alert-configuration/index.js @@ -2,8 +2,7 @@ import { Button } from "@mui/material"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; import { Layout as DashboardLayout } from "/src/layouts/index.js"; // had to add an extra path here because I added an extra folder structure. We should switch to absolute pathing so we dont have to deal with relative. import Link from "next/link"; -import { EyeIcon } from "@heroicons/react/24/outline"; -import { CopyAll, Delete, NotificationAdd } from "@mui/icons-material"; +import { CopyAll, Delete, Edit, NotificationAdd, Visibility } from "@mui/icons-material"; const Page = () => { const pageTitle = "Alerts"; @@ -11,13 +10,13 @@ const Page = () => { { label: "View Task Details", link: "/cipp/scheduler/task?id=[RowKey]", - icon: , + icon: , condition: (row) => row?.EventType === "Scheduled Task", }, { label: "Edit Alert", link: "/tenant/administration/alert-configuration/alert?id=[RowKey]", - icon: , + icon: , color: "success", target: "_self", }, diff --git a/src/pages/tenant/administration/domains/index.js b/src/pages/tenant/administration/domains/index.js new file mode 100644 index 000000000000..4d4548691292 --- /dev/null +++ b/src/pages/tenant/administration/domains/index.js @@ -0,0 +1,123 @@ +import { Layout as DashboardLayout } from "/src/layouts/index.js"; +import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; +import { CheckCircle, Star, Delete } from "@mui/icons-material"; +import { CippAddDomainDrawer } from "/src/components/CippComponents/CippAddDomainDrawer.jsx"; +import { CippDomainVerificationRecords } from "/src/components/CippComponents/CippDomainVerificationRecords.jsx"; +import { CippDomainServiceConfigurationRecords } from "/src/components/CippComponents/CippDomainServiceConfigurationRecords.jsx"; +import { Box, Typography, Divider } from "@mui/material"; +import { Stack } from "@mui/system"; +import { CippPropertyList } from "/src/components/CippComponents/CippPropertyList"; +import { getCippFormatting } from "/src/utils/get-cipp-formatting"; + +const Page = () => { + const pageTitle = "Domains"; + const apiUrl = "/api/ListGraphRequest"; + + // API Data configuration for the request + const apiData = { + Endpoint: "domains", + }; + + const simpleColumns = [ + "id", + "authenticationType", + "isAdminManaged", + "isDefault", + "isInitial", + "isVerified", + ]; + + const offCanvas = { + size: "lg", + children: (row) => { + const offcanvasProperties = [ + { + label: "Supported Services", + value: getCippFormatting(row?.supportedServices, "supportedServices"), + }, + ]; + return ( + + + + Domain Details for {row.id} + + + + + + + + Verification Records + + + + + + Service Configuration Records + + + + + ); + }, + }; + + return ( + + } + actions={[ + { + label: "Verify Domain", + condition: (row) => !row.isVerified, + type: "POST", + icon: , + url: "/api/ExecDomainAction", + data: { domain: "id", Action: "verify" }, + confirmText: + "Are you sure you want to verify this domain? Use one of the records below to complete verification.", + children: ({ row }) => , + size: "lg", + }, + { + label: "Set as Default", + condition: (row) => !row.isDefault && row.isVerified, + type: "POST", + icon: , + url: "/api/ExecDomainAction", + data: { domain: "id", Action: "setDefault" }, + confirmText: "Are you sure you want to set [id] as the default domain?", + multiPost: false, + hideBulk: true, + }, + { + label: "Delete Domain", + condition: (row) => !row.isDefault && !row.isInitial, + type: "POST", + icon: , + url: "/api/ExecDomainAction", + data: { domain: "id", Action: "delete" }, + confirmText: "Are you sure you want to delete [id]? This action cannot be undone.", + color: "error", + multiPost: false, + }, + ]} + /> + ); +}; + +// Adding the layout for the dashboard +Page.getLayout = (page) => {page}; + +export default Page; 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/conditional/deploy-vacation/index.js b/src/pages/tenant/conditional/deploy-vacation/index.js index d254d8bcf92a..bf1dfcafc145 100644 --- a/src/pages/tenant/conditional/deploy-vacation/index.js +++ b/src/pages/tenant/conditional/deploy-vacation/index.js @@ -23,6 +23,29 @@ const Page = () => { }, ]; + const filterList = [ + { + filterName: "Running", + value: [{ id: "TaskState", value: "Running" }], + type: "column", + }, + { + filterName: "Planned", + value: [{ id: "TaskState", value: "Planned" }], + type: "column", + }, + { + filterName: "Failed", + value: [{ id: "TaskState", value: "Failed" }], + type: "column", + }, + { + filterName: "Completed", + value: [{ id: "TaskState", value: "Completed" }], + type: "column", + }, + ]; + return ( { "Tenant", "Name", "Parameters.Member", + "Reference", "TaskState", "ScheduledTime", "ExecutedTime", ]} + filters={filterList} offCanvas={{ extendedInfoFields: [ "Name", "TaskState", "ScheduledTime", "Parameters.Member", + "Reference", "Parameters.PolicyId", "Tenant", "ExecutedTime", diff --git a/src/pages/tenant/conditional/list-policies/index.js b/src/pages/tenant/conditional/list-policies/index.js index 1612ed5d4bda..22eed3c63e59 100644 --- a/src/pages/tenant/conditional/list-policies/index.js +++ b/src/pages/tenant/conditional/list-policies/index.js @@ -12,12 +12,16 @@ import { import { Box } from "@mui/material"; import CippJsonView from "../../../../components/CippFormPages/CippJSONView"; import { CippCADeployDrawer } from "../../../../components/CippComponents/CippCADeployDrawer"; +import { CippApiLogsDrawer } from "../../../../components/CippComponents/CippApiLogsDrawer"; +import { PermissionButton } from "../../../../utils/permissions"; +import { useSettings } from "/src/hooks/use-settings.js"; // Page Component const Page = () => { const pageTitle = "Conditional Access"; const apiUrl = "/api/ListConditionalAccessPolicies"; const cardButtonPermissions = ["Tenant.ConditionalAccess.ReadWrite"]; + const tenant = useSettings().currentTenant; // Actions configuration const actions = [ @@ -159,9 +163,16 @@ const Page = () => { return ( + - + + } title={pageTitle} apiUrl={apiUrl} diff --git a/src/pages/tenant/conditional/list-template/index.js b/src/pages/tenant/conditional/list-template/index.js index 67abb13f162b..a79bfeef2200 100644 --- a/src/pages/tenant/conditional/list-template/index.js +++ b/src/pages/tenant/conditional/list-template/index.js @@ -1,17 +1,21 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; -import { Button } from "@mui/material"; +import { Button, Box } from "@mui/material"; import CippJsonView from "../../../../components/CippFormPages/CippJSONView"; import { Delete, GitHub, Edit, RocketLaunch } from "@mui/icons-material"; import { ApiGetCall } from "/src/api/ApiCall"; import { CippPolicyImportDrawer } from "/src/components/CippComponents/CippPolicyImportDrawer.jsx"; import { CippCADeployDrawer } from "/src/components/CippComponents/CippCADeployDrawer.jsx"; +import { CippApiLogsDrawer } from "../../../../components/CippComponents/CippApiLogsDrawer"; +import { PermissionButton } from "../../../../utils/permissions"; +import { useSettings } from "/src/hooks/use-settings.js"; import { useState } from "react"; const Page = () => { const pageTitle = "Available Conditional Access Templates"; const [deployDrawerOpen, setDeployDrawerOpen] = useState(false); const [selectedTemplateId, setSelectedTemplateId] = useState(null); + const tenant = useSettings().currentTenant; const integrations = ApiGetCall({ url: "/api/ListExtensionsConfig", @@ -108,10 +112,17 @@ const Page = () => { offCanvas={offCanvas} simpleColumns={["displayName", "GUID"]} cardButton={ - <> + - - - - - {comparisonApi.isError && ( - - - Error fetching comparison data - - - There was an error retrieving the comparison data. Please try running the report - again by clicking the "Run Report Once" button above. - - {comparisonApi.error && ( - - - {comparisonApi.error.message || JSON.stringify(comparisonApi.error, null, 2)} - - + {selectedTemplate && ( + + {selectedTemplate?.runManually && ( + + + + } + /> )} - + + + + } + label={`${compliancePercentage}% Compliant`} + variant="outlined" + size="small" + color={ + compliancePercentage === 100 + ? "success" + : compliancePercentage >= 50 + ? "warning" + : "error" + } + /> + + = 80 ? "success" : combinedScore >= 60 ? "warning" : "error" + } + /> + )} + setFilterMenuAnchor(null)} + > + { + setFilter("all"); + setFilterMenuAnchor(null); + }} + > + All ({allCount}) + + { + setFilter("compliant"); + setFilterMenuAnchor(null); + }} + > + Compliant ({compliantCount}) + + { + setFilter("nonCompliant"); + setFilterMenuAnchor(null); + }} + > + Non-Compliant ({nonCompliantCount}) + + { + setFilter("overridden"); + setFilterMenuAnchor(null); + }} + > + Overridden ({overriddenCount}) + + { + setFilter("nonCompliantWithLicense"); + setFilterMenuAnchor(null); + }} + > + Non-Compliant (License available) ({nonCompliantWithLicenseCount}) + + { + setFilter("nonCompliantWithoutLicense"); + setFilterMenuAnchor(null); + }} + > + Non-Compliant (License not available) ({nonCompliantWithoutLicenseCount}) + + + + {comparisonApi.isError && ( + + + Error fetching comparison data + + + There was an error retrieving the comparison data. Please try running the report + again by clicking the "Run Report Once" button above. + + {comparisonApi.error && ( + + + {comparisonApi.error.message || + JSON.stringify(comparisonApi.error, null, 2)} + + + )} + + )} + + {comparisonApi.isSuccess && + (!comparisonApi.data || comparisonApi.data.length === 0) && ( + + + No comparison data is available. This might be because: + + + + • The tenant has not been scanned yet + + + • The template has no standards configured + + + • There was an issue with the comparison + + + + Try running the report by clicking the "Run Report Once" button above. + + + )} - {comparisonApi.isSuccess && - (!comparisonApi.data || comparisonApi.data.length === 0) && ( + {filteredGroupedStandards && Object.keys(filteredGroupedStandards).length === 0 && ( - No comparison data is available. This might be because: + No standards match the selected filter criteria or search query. - - - • The tenant has not been scanned yet - - - • The template has no standards configured - - - • There was an issue with the comparison - - - Try running the report by clicking the "Run Report Once" button above. + Try selecting a different filter or modifying the search query. )} - {filteredGroupedStandards && Object.keys(filteredGroupedStandards).length === 0 && ( - - - No standards match the selected filter criteria or search query. - - - Try selecting a different filter or modifying the search query. - - - )} + {Object.keys(filteredGroupedStandards).map((category) => ( + + + {category} + - {Object.keys(filteredGroupedStandards).map((category) => ( - - - {category} - - - {filteredGroupedStandards[category].map((standard, index) => ( - - - - + {filteredGroupedStandards[category].map((standard, index) => ( + + + - - - {standard.complianceStatus === "Compliant" ? ( - - ) : standard.complianceStatus === "Reporting Disabled" ? ( - - ) : ( - - )} - - - + + - {standard?.standardName} - - - + {standard.complianceStatus === "Compliant" ? ( + + ) : standard.complianceStatus === "Overridden" ? ( + + ) : standard.complianceStatus === "Reporting Disabled" ? ( + + ) : ( + + )} + + + {standard?.standardName} + + + {standard?.templateActions && + standard.templateActions.length > 0 ? ( + <> + {standard.templateActions.map((action, idx) => { + const actionLabel = + typeof action === "object" + ? action?.label || action?.value || "Unknown" + : action; + const actionValue = + typeof action === "object" ? action?.value : action; + const isRemediate = + actionLabel === "Remediate" || + actionLabel === "remediate"; + + return ( + + {actionValue === "Report" && } + {actionValue === "warn" && ( + + )} + {actionValue === "Remediate" && } + + } + /> + ); + })} + {standard?.autoRemediate && ( + } + /> + )} + + ) : ( + + )} + + - - - - {!standard.standardValue ? ( - - This data has not yet been collected. Collect the data by pressing the - report button on the top of the page. - - ) : ( - + + + {!standard.standardValue ? ( + + This data has not yet been collected. Collect the data by pressing + the report button on the top of the page. + + ) : ( - - {standard.standardValue && - typeof standard.standardValue === "object" && - Object.keys(standard.standardValue).length > 0 ? ( - Object.entries(standard.standardValue).map(([key, value]) => ( - - - {key}: - - - {typeof value === "object" && value !== null - ? value?.label || JSON.stringify(value) - : value === true - ? "Enabled" - : value === false - ? "Disabled" - : String(value)} - - - )) - ) : ( - - {standard.standardValue === true ? ( - - This setting is configured correctly - - ) : standard.standardValue === false ? ( - - This setting is not configured correctly - - ) : standard.standardValue !== undefined ? ( - typeof standard.standardValue === "object" ? ( - "No settings configured" + + + {standard.standardValue && + typeof standard.standardValue === "object" && + Object.keys(standard.standardValue).length > 0 ? ( + Object.entries(standard.standardValue).map(([key, value]) => ( + + + {key}: + + + {typeof value === "object" && value !== null + ? value?.label || JSON.stringify(value) + : value === true + ? "Enabled" + : value === false + ? "Disabled" + : String(value)} + + + )) + ) : ( + + {standard.standardValue === true ? ( + + This setting is configured correctly + + ) : standard.standardValue === false ? ( + + This setting is not configured correctly + + ) : standard.standardValue !== undefined ? ( + typeof standard.standardValue === "object" ? ( + "No settings configured" + ) : ( + String(standard.standardValue) + ) ) : ( - String(standard.standardValue) - ) - ) : ( - - This setting is not configured, or data has not been - collected. If you are getting this after data collection, - the tenant might not be licensed for this feature - - )} - - )} + + This setting is not configured, or data has not been + collected. If you are getting this after data + collection, the tenant might not be licensed for this + feature + + )} + + )} + + )} + + + - )} - - - - - - + + - - - + + - - - - - - {currentTenant} - + + + + + + + {currentTenant} + + + + + + + + + + {standard.complianceStatus} + + + {standard.currentTenantValue?.LastRefresh && ( + + + } size="small" - color="primary" + label={`${new Date( + standard.currentTenantValue.LastRefresh + ).toLocaleString()}`} variant="outlined" - sx={{ mt: 1, px: 2 }} /> - + )} - + + + + {/* Existing tenant comparison content */} + {typeof standard.currentTenantValue?.Value === "object" && + standard.currentTenantValue?.Value !== null ? ( - - - {standard.complianceStatus} - - - {standard.currentTenantValue?.LastRefresh && ( - - - - } - size="small" - label={`${new Date( - standard.currentTenantValue.LastRefresh - ).toLocaleString()}`} - variant="outlined" - /> - )} - - - - - - {/* Existing tenant comparison content */} - {typeof standard.currentTenantValue?.Value === "object" && - standard.currentTenantValue?.Value !== null ? ( - - {standard.complianceStatus === "Reporting Disabled" ? ( - - Reporting is disabled for this standard in the template - configuration. - - ) : ( - <> - {standard.complianceStatus === "Compliant" ? ( - - This setting is configured correctly - - ) : standard.currentTenantValue?.Value === false ? ( - - This setting is not configured correctly - - ) : null} - - {/* Only show values if they're not simple true/false that's already covered by the alerts above */} - {!( - standard.complianceStatus === "Compliant" && - (standard.currentTenantValue?.Value === true || - standard.currentTenantValue?.Value === false) - ) && - Object.entries(standard.currentTenantValue) - .filter( - ([key]) => - key !== "LastRefresh" && - // Skip showing the Value field separately if it's just true/false - !( - key === "Value" && - (standard.currentTenantValue?.Value === true || - standard.currentTenantValue?.Value === false) - ) - ) - .map(([key, value]) => { - const actualValue = key === "Value" ? value : value; - - const standardValueForKey = - standard.standardValue && - typeof standard.standardValue === "object" - ? standard.standardValue[key] - : undefined; - - const isDifferent = - standardValueForKey !== undefined && - JSON.stringify(actualValue) !== - JSON.stringify(standardValueForKey); - - // Format the display value - let displayValue; - if (typeof value === "object" && value !== null) { - displayValue = - value?.label || JSON.stringify(value, null, 2); - } else if (value === true) { - displayValue = "Enabled"; - } else if (value === false) { - displayValue = "Disabled"; - } else { - displayValue = String(value); - } - - return ( - - - {key}: - - + Reporting is disabled for this standard in the template + configuration. + + ) : ( + <> + {standard.complianceStatus === "Overridden" ? ( + + This setting is configured by template:{" "} + {standard.overridingTemplateName || + standard.overridingTemplateId} + + ) : standard.complianceStatus === "Compliant" ? ( + + This setting is configured correctly + + ) : standard.currentTenantValue?.Value === false ? ( + + This setting is not configured correctly + + ) : null} + + {/* Only show values if they're not simple true/false that's already covered by the alerts above */} + {!( + standard.complianceStatus === "Compliant" && + (standard.currentTenantValue?.Value === true || + standard.currentTenantValue?.Value === false) + ) && + Object.entries(standard.currentTenantValue) + .filter( + ([key]) => + key !== "LastRefresh" && + // Skip showing the Value field separately if it's just true/false + !( + key === "Value" && + (standard.currentTenantValue?.Value === true || + standard.currentTenantValue?.Value === false) + ) + ) + .map(([key, value]) => { + const actualValue = key === "Value" ? value : value; + + const standardValueForKey = + standard.standardValue && + typeof standard.standardValue === "object" + ? standard.standardValue[key] + : undefined; + + const isDifferent = + standardValueForKey !== undefined && + JSON.stringify(actualValue) !== + JSON.stringify(standardValueForKey); + + // Format the display value + let displayValue; + if (typeof value === "object" && value !== null) { + displayValue = + value?.label || JSON.stringify(value, null, 2); + } else if (value === true) { + displayValue = "Enabled"; + } else if (value === false) { + displayValue = "Disabled"; + } else { + displayValue = String(value); + } + + return ( + - {displayValue} - - - ); - })} - - )} - - ) : ( - - {standard.complianceStatus === "Reporting Disabled" ? ( - - Reporting is disabled for this standard in the template - configuration. - - ) : standard.complianceStatus === "Compliant" ? ( - - This setting is configured correctly - - ) : standard.currentTenantValue?.Value === false || - standard.currentTenantValue === false ? ( - - This setting is not configured correctly - - ) : standard.currentTenantValue !== undefined ? ( - String( - standard.currentTenantValue?.Value !== undefined - ? standard.currentTenantValue?.Value - : standard.currentTenantValue - ) - ) : ( - - This setting is not configured, or data has not been collected. If - you are getting this after data collection, the tenant might not - be licensed for this feature - - )} - - )} - - - + + {key}: + + + {displayValue} + + + ); + })} + + )} + + ) : ( + + {standard.complianceStatus === "Reporting Disabled" ? ( + + Reporting is disabled for this standard in the template + configuration. + + ) : standard.complianceStatus === "Overridden" ? ( + + This setting is configured by template:{" "} + {standard.overridingTemplateName || + standard.overridingTemplateId} + + ) : standard.complianceStatus === "Compliant" ? ( + + This setting is configured correctly + + ) : standard.currentTenantValue?.Value === false || + standard.currentTenantValue === false ? ( + + This setting is not configured correctly + + ) : standard.currentTenantValue !== undefined ? ( + String( + standard.currentTenantValue?.Value !== undefined + ? standard.currentTenantValue?.Value + : standard.currentTenantValue + ) + ) : ( + + This setting is not configured, or data has not been collected. + If you are getting this after data collection, the tenant might + not be licensed for this feature + + )} + + )} + + + - {standard.complianceDetails && ( - - - - + + - - - theme.palette.primary.main, - textDecoration: "underline", - "&:hover": { - textDecoration: "none", + + + + theme.palette.primary.main, + textDecoration: "underline", + "&:hover": { + textDecoration: "none", + }, }, - }, - fontSize: "0.875rem", - lineHeight: 1.43, - "& p": { - my: 0, - }, - flex: 1, - }} - > - ( - - {children} - - ), - // Convert paragraphs to spans to avoid unwanted spacing - p: ({ children }) => {children}, + fontSize: "0.875rem", + lineHeight: 1.43, + "& p": { + my: 0, + }, + flex: 1, }} > - {standard.complianceDetails} - - - - - - )} - - ))} - - ))} + ( + + {children} + + ), + // Convert paragraphs to spans to avoid unwanted spacing + p: ({ children }) => {children}, + }} + > + {standard.complianceDetails} + + + + + + )} + + ))} + + ))} + )} diff --git a/src/pages/tenant/manage/configuration-backup.js b/src/pages/tenant/manage/configuration-backup.js index 8969f31ca4e4..d2f8e5e19ac4 100644 --- a/src/pages/tenant/manage/configuration-backup.js +++ b/src/pages/tenant/manage/configuration-backup.js @@ -1,5 +1,7 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { HeaderedTabbedLayout } from "/src/layouts/HeaderedTabbedLayout"; +import { useState } from "react"; +import { useForm, useWatch } from "react-hook-form"; import { Button, Box, @@ -31,6 +33,7 @@ import { CippBackupScheduleDrawer } from "/src/components/CippComponents/CippBac import { CippRestoreBackupDrawer } from "/src/components/CippComponents/CippRestoreBackupDrawer"; import { CippApiDialog } from "/src/components/CippComponents/CippApiDialog"; import { CippTimeAgo } from "/src/components/CippComponents/CippTimeAgo"; +import { CippFormTenantSelector } from "/src/components/CippComponents/CippFormTenantSelector"; import { useDialog } from "/src/hooks/use-dialog"; import ReactTimeAgo from "react-time-ago"; import tabOptions from "./tabOptions.json"; @@ -42,16 +45,20 @@ const Page = () => { const { templateId } = router.query; const settings = useSettings(); const removeDialog = useDialog(); + const tenantFilterForm = useForm({ defaultValues: { tenantFilter: null } }); + const backupTenantFilter = useWatch({ control: tenantFilterForm.control, name: "tenantFilter" }); + // Prioritize URL query parameter, then fall back to settings + const currentTenant = router.query.tenantFilter || settings.currentTenant; // API call to get backup files const backupList = ApiGetCall({ url: "/api/ExecListBackup", data: { - tenantFilter: settings.currentTenant, + tenantFilter: currentTenant, Type: "Scheduled", NameOnly: true, }, - queryKey: `BackupList-${settings.currentTenant}`, + queryKey: `BackupList-${currentTenant}`, }); // API call to get existing backup configuration/schedule @@ -61,7 +68,7 @@ const Page = () => { showHidden: true, Type: "New-CIPPBackup", }, - queryKey: `BackupTasks-${settings.currentTenant}`, + queryKey: `BackupTasks-${currentTenant}`, }); // Use the actual backup files as the backup data @@ -77,19 +84,28 @@ const Page = () => { return ["Configuration"]; }; - const backupDisplayItems = filteredBackupData.map((backup, index) => ({ + // Filter backup data by selected tenant if in AllTenants view + const tenantFilteredBackupData = + settings.currentTenant === "AllTenants" && + backupTenantFilter && + backupTenantFilter !== "AllTenants" + ? filteredBackupData.filter((backup) => backup.TenantFilter === backupTenantFilter) + : filteredBackupData; + + const backupDisplayItems = tenantFilteredBackupData.map((backup, index) => ({ id: backup.RowKey || index, name: backup.BackupName || "Unnamed Backup", timestamp: backup.Timestamp, - tenantSource: backup.BackupName?.includes("AllTenants") - ? "All Tenants" - : backup.BackupName?.replace("CIPP Backup - ", "") || settings.currentTenant, + tenantSource: backup.TenantFilter || settings.currentTenant, tags: generateBackupTags(backup), })); // Process existing backup configuration, find tenantFilter. by comparing settings.currentTenant with Tenant.value const currentConfig = Array.isArray(existingBackupConfig.data) - ? existingBackupConfig.data.find((tenant) => tenant.Tenant.value === settings.currentTenant) + ? existingBackupConfig.data.find( + (tenant) => + tenant.Tenant.value === settings.currentTenant || tenant.Tenant.value === "AllTenants" + ) : null; const hasExistingConfig = currentConfig && currentConfig.Parameters?.ScheduledBackupValues; @@ -279,13 +295,33 @@ const Page = () => { {/* Backup History */} - - - - - - Backup History - + + + + + + + Backup History + + {settings.currentTenant === "AllTenants" && ( + + + + )} + {settings.currentTenant === "AllTenants" @@ -305,7 +341,7 @@ const Page = () => { ) : ( - + {backupDisplayItems.map((backup) => ( @@ -332,6 +368,14 @@ const Page = () => { + {settings.currentTenant === "AllTenants" && ( + + )} { confirmText: "Are you sure you want to remove this backup schedule? This will stop automatic backups but won't delete existing backup files.", }} - relatedQueryKeys={[`BackupTasks-${settings.currentTenant}`, `BackupList-${settings.currentTenant}`]} + relatedQueryKeys={[ + `BackupTasks-${settings.currentTenant}`, + `BackupList-${settings.currentTenant}`, + ]} onSuccess={() => { // Refresh both queries when a backup schedule is removed setTimeout(() => { diff --git a/src/pages/tenant/manage/drift.js b/src/pages/tenant/manage/drift.js index b962a68dbd65..8287c6c18cb3 100644 --- a/src/pages/tenant/manage/drift.js +++ b/src/pages/tenant/manage/drift.js @@ -34,7 +34,8 @@ const ManageDriftPage = () => { const router = useRouter(); const { templateId } = router.query; const userSettingsDefaults = useSettings(); - const tenantFilter = userSettingsDefaults.currentTenant || ""; + // Prioritize URL query parameter, then fall back to settings + const tenantFilter = router.query.tenantFilter || userSettingsDefaults.currentTenant || ""; const [anchorEl, setAnchorEl] = useState({}); const [bulkActionsAnchorEl, setBulkActionsAnchorEl] = useState(null); const createDialog = useDialog(); @@ -233,6 +234,30 @@ const ManageDriftPage = () => { return null; }; + // Helper function to format policy objects for display + const formatPolicyValue = (value) => { + if (!value) return "N/A"; + + // If it's already a string, return it + if (typeof value === "string") { + // Check if it's a JSON string and try to parse it + try { + const parsed = JSON.parse(value); + return formatPolicyValue(parsed); + } catch { + return value; + } + } + + // If it's an object (policy object from API) + if (typeof value === "object" && value !== null) { + // Pretty-print the object as JSON + return JSON.stringify(value, null, 2); + } + + return String(value); + }; + // Helper function to create deviation items const createDeviationItems = (deviations, statusOverride = null) => { return (deviations || []).map((deviation, index) => { @@ -269,7 +294,7 @@ const ManageDriftPage = () => { { label: "Standard Name", value: prettyName }, { label: "Description", value: description }, { label: "Expected Value", value: deviation.expectedValue || "N/A" }, - { label: "Current Value", value: deviation.receivedValue || "N/A" }, + { label: "Current Value", value: formatPolicyValue(deviation.receivedValue) }, { label: "Status", value: getDeviationStatusText(statusOverride || deviation.Status || deviation.state), diff --git a/src/pages/tenant/manage/driftManagementActions.js b/src/pages/tenant/manage/driftManagementActions.js index cbad12809dbd..5d7cd8e80488 100644 --- a/src/pages/tenant/manage/driftManagementActions.js +++ b/src/pages/tenant/manage/driftManagementActions.js @@ -1,5 +1,5 @@ import React from "react"; -import { Sync, PlayArrow, PictureAsPdf } from "@mui/icons-material"; +import { Edit, Sync, PlayArrow, PictureAsPdf } from "@mui/icons-material"; /** * Creates the standard drift management actions array @@ -9,7 +9,14 @@ import { Sync, PlayArrow, PictureAsPdf } from "@mui/icons-material"; * @param {Function} options.onGenerateReport - Function to call when generate report is triggered (optional) * @returns {Array} Array of action objects */ -export const createDriftManagementActions = ({ templateId, onRefresh, onGenerateReport, currentTenant }) => { +export const createDriftManagementActions = ({ + templateId, + templateType = "classic", + showEditTemplate = false, + onRefresh, + onGenerateReport, + currentTenant, +}) => { const actions = [ { label: "Refresh Data", @@ -31,6 +38,29 @@ export const createDriftManagementActions = ({ templateId, onRefresh, onGenerate // Add template-specific actions if templateId is available if (templateId) { + // Conditionally add Edit Template action + if (showEditTemplate) { + actions.push({ + label: "Edit Template", + icon: , + color: "info", + noConfirm: true, + customFunction: () => { + // Use Next.js router for internal navigation + import("next/router") + .then(({ default: router }) => { + 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/templates/template?id=${templateId}&type=${templateType}`; + }); + }, + }); + } + actions.push( { label: `Run Standard Now (${currentTenant || "Currently Selected Tenant"})`, diff --git a/src/pages/tenant/manage/edit.js b/src/pages/tenant/manage/edit.js index 7d7bfb11fae1..8d66042e30e3 100644 --- a/src/pages/tenant/manage/edit.js +++ b/src/pages/tenant/manage/edit.js @@ -22,7 +22,8 @@ const Page = () => { const router = useRouter(); const { templateId } = router.query; const settings = useSettings(); - const currentTenant = settings.currentTenant; + // Prioritize URL query parameter, then fall back to settings + const currentTenant = router.query.tenantFilter || settings.currentTenant; const formControl = useForm({ mode: "onChange", diff --git a/src/pages/tenant/manage/history.js b/src/pages/tenant/manage/history.js index 38efa859b9c3..d0e37ec3a077 100644 --- a/src/pages/tenant/manage/history.js +++ b/src/pages/tenant/manage/history.js @@ -31,16 +31,18 @@ import { Info as InfoIcon, CheckCircle as SuccessIcon, ExpandMore, + Sync, } from "@mui/icons-material"; import tabOptions from "./tabOptions.json"; import { useSettings } from "../../../hooks/use-settings"; -import { createDriftManagementActions } from "./driftManagementActions"; const Page = () => { const router = useRouter(); const { templateId } = router.query; const [daysToLoad, setDaysToLoad] = useState(5); - const tenant = useSettings().currentTenant; + const userSettings = useSettings(); + // Prioritize URL query parameter, then fall back to settings + const tenant = router.query.tenantFilter || userSettings.currentTenant; const [expandedMessages, setExpandedMessages] = useState(new Set()); // Toggle message expansion @@ -124,14 +126,17 @@ const Page = () => { setDaysToLoad((prev) => prev + 7); }; - // Actions for the ActionsMenu - const actions = createDriftManagementActions({ - templateId, - onRefresh: () => { - logsData.refetch(); + // Actions for the ActionsMenu - just refresh for history page + const actions = [ + { + label: "Refresh Data", + icon: , + noConfirm: true, + customFunction: () => { + logsData.refetch(); + }, }, - currentTenant: tenant, - }); + ]; const title = "View History"; // Sort logs by date (newest first) diff --git a/src/pages/tenant/manage/policies-deployed.js b/src/pages/tenant/manage/policies-deployed.js index fede3bf0ac15..b6cca34dd67f 100644 --- a/src/pages/tenant/manage/policies-deployed.js +++ b/src/pages/tenant/manage/policies-deployed.js @@ -353,6 +353,8 @@ const PoliciesDeployedPage = () => { const actions = createDriftManagementActions({ templateId, + templateType: currentTemplate?.type || "classic", + showEditTemplate: true, onRefresh: () => { standardsApi.refetch(); comparisonApi.refetch(); @@ -403,7 +405,7 @@ const PoliciesDeployedPage = () => { subtitle={subtitle} actions={actions} actionsData={{}} - backUrl="/tenant/standards/list-standards" + backUrl="/tenant/standards" > diff --git a/src/pages/tenant/manage/recover-policies.js b/src/pages/tenant/manage/recover-policies.js index 3c4ed4e64376..98d10ec5208a 100644 --- a/src/pages/tenant/manage/recover-policies.js +++ b/src/pages/tenant/manage/recover-policies.js @@ -28,7 +28,9 @@ const RecoverPoliciesPage = () => { const router = useRouter(); const { templateId } = router.query; const [selectedPolicies, setSelectedPolicies] = useState([]); - const currentTenant = useSettings().currentTenant; + const userSettings = useSettings(); + // Prioritize URL query parameter, then fall back to settings + const currentTenant = router.query.tenantFilter || userSettings.currentTenant; const formControl = useForm({ mode: "onChange" }); diff --git a/src/pages/tenant/manage/user-defaults.js b/src/pages/tenant/manage/user-defaults.js index 17d50157b8c2..85cccf57c0e3 100644 --- a/src/pages/tenant/manage/user-defaults.js +++ b/src/pages/tenant/manage/user-defaults.js @@ -61,7 +61,7 @@ const Page = () => { <> { const pageTitle = "Standard & Drift Alignment"; diff --git a/src/pages/tenant/standards/list-standards/drift-alignment/index.js b/src/pages/tenant/standards/list-standards/drift-alignment/index.js deleted file mode 100644 index ef0aa4f974e3..000000000000 --- a/src/pages/tenant/standards/list-standards/drift-alignment/index.js +++ /dev/null @@ -1,45 +0,0 @@ -import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; -import { Layout as DashboardLayout } from "/src/layouts/index.js"; -import { TabbedLayout } from "/src/layouts/TabbedLayout"; -import { EyeIcon } from "@heroicons/react/24/outline"; -import tabOptions from "../tabOptions.json"; - -const Page = () => { - const pageTitle = "Drift Alignment"; - - const actions = [ - { - label: "View Tenant Report", - link: "/tenant/manage/applied-standards/?tenantFilter=[tenantFilter]&templateId=[standardId]", - icon: , - color: "info", - target: "_self", - }, - ]; - - return ( - } - simpleColumns={[ - "tenantFilter", - "standardName", - "alignmentScore", - "LicenseMissingPercentage", - "combinedAlignmentScore", - ]} - queryKey="listDriftAlignment" - /> - ); -}; - -Page.getLayout = (page) => ( - - {page} - -); - -export default Page; diff --git a/src/pages/tenant/standards/list-standards/tabOptions.json b/src/pages/tenant/standards/list-standards/tabOptions.json deleted file mode 100644 index 1c522e6ca8ca..000000000000 --- a/src/pages/tenant/standards/list-standards/tabOptions.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "label": "Standard & Drift Alignment", - "path": "/tenant/standards/list-standards" - }, - { - "label": "Templates", - "path": "/tenant/standards/list-standards/classic-standards" - } -] diff --git a/src/pages/tenant/standards/tabOptions.json b/src/pages/tenant/standards/tabOptions.json new file mode 100644 index 000000000000..26c6c751dc1a --- /dev/null +++ b/src/pages/tenant/standards/tabOptions.json @@ -0,0 +1,10 @@ +[ + { + "label": "Standard & Drift Alignment", + "path": "/tenant/standards/alignment" + }, + { + "label": "Templates", + "path": "/tenant/standards/templates" + } +] \ No newline at end of file diff --git a/src/pages/tenant/standards/list-standards/classic-standards/index.js b/src/pages/tenant/standards/templates/index.js similarity index 91% rename from src/pages/tenant/standards/list-standards/classic-standards/index.js rename to src/pages/tenant/standards/templates/index.js index a204fddb81b9..6b3c7c483f87 100644 --- a/src/pages/tenant/standards/list-standards/classic-standards/index.js +++ b/src/pages/tenant/standards/templates/index.js @@ -4,13 +4,13 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; // had to add import { TabbedLayout } from "/src/layouts/TabbedLayout"; import Link from "next/link"; import { CopyAll, Delete, PlayArrow, AddBox, Edit, GitHub, ContentCopy } from "@mui/icons-material"; -import { ApiGetCall, ApiPostCall } from "../../../../../api/ApiCall"; +import { ApiGetCall, ApiPostCall } from "../../../../api/ApiCall"; import { Grid } from "@mui/system"; -import { CippApiResults } from "../../../../../components/CippComponents/CippApiResults"; +import { CippApiResults } from "../../../../components/CippComponents/CippApiResults"; import { EyeIcon } from "@heroicons/react/24/outline"; import tabOptions from "../tabOptions.json"; import { useSettings } from "/src/hooks/use-settings.js"; -import { CippPolicyImportDrawer } from "../../../../../components/CippComponents/CippPolicyImportDrawer.jsx"; +import { CippPolicyImportDrawer } from "../../../../components/CippComponents/CippPolicyImportDrawer.jsx"; import { PermissionButton } from "/src/utils/permissions.js"; const Page = () => { @@ -36,14 +36,14 @@ const Page = () => { { label: "Edit Template", //when using a link it must always be the full path /identity/administration/users/[id] for example. - link: "/tenant/standards/template?id=[GUID]&type=[type]", + link: "/tenant/standards/templates/template?id=[GUID]&type=[type]", icon: , color: "success", target: "_self", }, { label: "Clone & Edit Template", - link: "/tenant/standards/template?id=[GUID]&clone=true&type=[type]", + link: "/tenant/standards/templates/template?id=[GUID]&clone=true&type=[type]", icon: , color: "success", target: "_self", @@ -183,12 +183,17 @@ const Page = () => { tenantInTitle={false} cardButton={ <> - - - {/* Drift management actions */} - {driftActions.length > 0 && ( - - )} - + + + + + + + {editMode + ? isDriftMode + ? "Edit Drift Template" + : "Edit Standards Template" + : isDriftMode + ? "Add Drift Template" + : "Add Standards Template"} + + + + + {/* Drift management actions */} + {driftActions.length > 0 && ( + + )} - - - - {/* Left Column for Accordions */} - - { - // Reset unsaved changes flag - setHasUnsavedChanges(false); - // Update reference for future change detection - initialStandardsRef.current = { ...selectedStandards }; - }} - /> - - - - {/* Show accordions based on selectedStandards (which is populated by API when editing) */} - {existingTemplate.isLoading ? ( - - ) : ( - - )} - - - - - {/* Only render the dialog when it's needed */} - {dialogOpen && ( - }> - - - )} - + + + {/* Left Column for Accordions */} + + { + // Reset unsaved changes flag + setHasUnsavedChanges(false); + // Update reference for future change detection + initialStandardsRef.current = { ...selectedStandards }; + }} + /> + + + + {/* Show accordions based on selectedStandards (which is populated by API when editing) */} + {existingTemplate.isLoading ? ( + + ) : ( + + )} + + + + + + + {/* Only render the dialog when it's needed */} + {dialogOpen && ( + }> + + + )} ); }; diff --git a/src/pages/tenant/standards/tenant-alignment/index.js b/src/pages/tenant/standards/tenant-alignment/index.js deleted file mode 100644 index e891f2a0576d..000000000000 --- a/src/pages/tenant/standards/tenant-alignment/index.js +++ /dev/null @@ -1,38 +0,0 @@ -import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; -import { Layout as DashboardLayout } from "/src/layouts/index.js"; -import { EyeIcon } from "@heroicons/react/24/outline"; - -const Page = () => { - const pageTitle = "Tenant Alignment"; - - const actions = [ - { - label: "View Tenant Report", - link: "/tenant/manage/applied-standards/?tenantFilter=[tenantFilter]&templateId=[standardId]", - icon: , - color: "info", - target: "_self", - }, - ]; - - return ( - - ); -}; - -Page.getLayout = (page) => {page}; - -export default Page; diff --git a/src/utils/cippVersion.js b/src/utils/cippVersion.js index 3de297a7c070..ed64050c0759 100644 --- a/src/utils/cippVersion.js +++ b/src/utils/cippVersion.js @@ -29,7 +29,6 @@ export async function getCippVersion() { // Build headers including X-CIPP-Version. Accept extra headers to merge. export async function buildVersionedHeaders(extra = {}) { const version = await getCippVersion(); - console.log("CIPP Version:", version); return { "Content-Type": "application/json", "X-CIPP-Version": version, diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index 53f738eb2111..36eb740bbe15 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -156,6 +156,7 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr "ExecutedTime", "ScheduledTime", "Timestamp", + "timestamp", "DateTime", "LastRun", "LastRefresh", diff --git a/version_latest.txt b/version_latest.txt deleted file mode 100644 index 5e39348ef037..000000000000 --- a/version_latest.txt +++ /dev/null @@ -1 +0,0 @@ -99.99.99 \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 696b69a0fdaa..e1d176d44d7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1050,9 +1050,9 @@ "@types/json-schema" "^7.0.15" "@eslint/eslintrc@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" - integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== + version "3.3.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz#26393a0806501b5e2b6a43aa588a4d8df67880ac" + integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -1060,7 +1060,7 @@ globals "^14.0.0" ignore "^5.2.0" import-fresh "^3.2.1" - js-yaml "^4.1.0" + js-yaml "^4.1.1" minimatch "^3.1.2" strip-json-comments "^3.1.1" @@ -1326,9 +1326,9 @@ "@monaco-editor/loader" "^1.5.0" "@mui/core-downloads-tracker@^7.3.2": - version "7.3.5" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.5.tgz#2c7769498a287eb9456269571b328f807b69f731" - integrity sha512-kOLwlcDPnVz2QMhiBv0OQ8le8hTCqKM9cRXlfVPL91l3RGeOsxrIhNRsUt3Xb8wb+pTVUolW+JXKym93vRKxCw== + version "7.3.6" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.6.tgz#e7e3a4dc161a377be8224aa988410e89571ab40a" + integrity sha512-QaYtTHlr8kDFN5mE1wbvVARRKH7Fdw1ZuOjBJcFdVpfNfRYKF3QLT4rt+WaB6CKJvpqxRsmEo0kpYinhH5GeHg== "@mui/icons-material@7.3.2": version "7.3.2" @@ -1367,19 +1367,19 @@ react-is "^19.1.1" react-transition-group "^4.4.5" -"@mui/private-theming@^7.3.2", "@mui/private-theming@^7.3.5": - version "7.3.5" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-7.3.5.tgz#53f9203d7d82e69e94dd8df0a19fd4744a330a8f" - integrity sha512-cTx584W2qrLonwhZLbEN7P5pAUu0nZblg8cLBlTrZQ4sIiw8Fbvg7GvuphQaSHxPxrCpa7FDwJKtXdbl2TSmrA== +"@mui/private-theming@^7.3.2", "@mui/private-theming@^7.3.6": + version "7.3.6" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-7.3.6.tgz#1ca65a08e8f7f538d9a10ba974f1f4db5231a969" + integrity sha512-Ws9wZpqM+FlnbZXaY/7yvyvWQo1+02Tbx50mVdNmzWEi51C51y56KAbaDCYyulOOBL6BJxuaqG8rNNuj7ivVyw== dependencies: "@babel/runtime" "^7.28.4" - "@mui/utils" "^7.3.5" + "@mui/utils" "^7.3.6" prop-types "^15.8.1" -"@mui/styled-engine@^7.3.2", "@mui/styled-engine@^7.3.5": - version "7.3.5" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-7.3.5.tgz#b087d791d85eea97812f0e23e9b9fdeb37abad77" - integrity sha512-zbsZ0uYYPndFCCPp2+V3RLcAN6+fv4C8pdwRx6OS3BwDkRCN8WBehqks7hWyF3vj1kdQLIWrpdv/5Y0jHRxYXQ== +"@mui/styled-engine@^7.3.2", "@mui/styled-engine@^7.3.6": + version "7.3.6" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-7.3.6.tgz#dde8e6ae32c9b5b400dcd37afd9514a5344f7d91" + integrity sha512-+wiYbtvj+zyUkmDB+ysH6zRjuQIJ+CM56w0fEXV+VDNdvOuSywG+/8kpjddvvlfMLsaWdQe5oTuYGBcodmqGzQ== dependencies: "@babel/runtime" "^7.28.4" "@emotion/cache" "^11.14.0" @@ -1403,55 +1403,55 @@ prop-types "^15.8.1" "@mui/system@^7.3.2": - version "7.3.5" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-7.3.5.tgz#ea077787ba9e9efc00a6df4db55a833de6a530fc" - integrity sha512-yPaf5+gY3v80HNkJcPi6WT+r9ebeM4eJzrREXPxMt7pNTV/1eahyODO4fbH3Qvd8irNxDFYn5RQ3idHW55rA6g== + version "7.3.6" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-7.3.6.tgz#460f82fc6fe1b79b8c04dc97694f6b162ffc3d25" + integrity sha512-8fehAazkHNP1imMrdD2m2hbA9sl7Ur6jfuNweh5o4l9YPty4iaZzRXqYvBCWQNwFaSHmMEj2KPbyXGp7Bt73Rg== dependencies: "@babel/runtime" "^7.28.4" - "@mui/private-theming" "^7.3.5" - "@mui/styled-engine" "^7.3.5" - "@mui/types" "^7.4.8" - "@mui/utils" "^7.3.5" + "@mui/private-theming" "^7.3.6" + "@mui/styled-engine" "^7.3.6" + "@mui/types" "^7.4.9" + "@mui/utils" "^7.3.6" clsx "^2.1.1" csstype "^3.1.3" prop-types "^15.8.1" -"@mui/types@^7.4.6", "@mui/types@^7.4.8": - version "7.4.8" - resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.4.8.tgz#0c1829353cd7d196be9ac0332a30cdd2792f3558" - integrity sha512-ZNXLBjkPV6ftLCmmRCafak3XmSn8YV0tKE/ZOhzKys7TZXUiE0mZxlH8zKDo6j6TTUaDnuij68gIG+0Ucm7Xhw== +"@mui/types@^7.4.6", "@mui/types@^7.4.9": + version "7.4.9" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.4.9.tgz#99accc87920b4c8c4ce33c5076a58f7f81b528fa" + integrity sha512-dNO8Z9T2cujkSIaCnWwprfeKmTWh97cnjkgmpFJ2sbfXLx8SMZijCYHOtP/y5nnUb/Rm2omxbDMmtUoSaUtKaw== dependencies: "@babel/runtime" "^7.28.4" -"@mui/utils@^7.3.2", "@mui/utils@^7.3.5": - version "7.3.5" - resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-7.3.5.tgz#77f3e2b83454bbd47877c73b04cd804f490bf028" - integrity sha512-jisvFsEC3sgjUjcPnR4mYfhzjCDIudttSGSbe1o/IXFNu0kZuR+7vqQI0jg8qtcVZBHWrwTfvAZj9MNMumcq1g== +"@mui/utils@^7.3.2", "@mui/utils@^7.3.5", "@mui/utils@^7.3.6": + version "7.3.6" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-7.3.6.tgz#508fbe864832f99b215d134eb89e1198cdc66b34" + integrity sha512-jn+Ba02O6PiFs7nKva8R2aJJ9kJC+3kQ2R0BbKNY3KQQ36Qng98GnPRFTlbwYTdMD6hLEBKaMLUktyg/rTfd2w== dependencies: "@babel/runtime" "^7.28.4" - "@mui/types" "^7.4.8" + "@mui/types" "^7.4.9" "@types/prop-types" "^15.7.15" clsx "^2.1.1" prop-types "^15.8.1" react-is "^19.2.0" "@mui/x-date-pickers@^8.11.1": - version "8.19.0" - resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-8.19.0.tgz#a4e082fe39811aef93c029024dd2b6cd4b435b2d" - integrity sha512-TQ4FsGUsiGJVs+Ie4q7nHXUmFqZADXL/1hVtZpOKsdr3WQXwpX7C5YmeakZGFR2NZnuv4snFj+WTee3kgyFbyQ== + version "8.22.0" + resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-8.22.0.tgz#60bc176a308078002e93cfbf3ee55cae234280d5" + integrity sha512-iF4H8EJHcltiwfytTe6M1/iCwBUCB97oVh63nAsL4tre2Ew2jm8bKR4Dw2pbgvSUonB3okpZVuweFBQdnECJCg== dependencies: "@babel/runtime" "^7.28.4" "@mui/utils" "^7.3.5" - "@mui/x-internals" "8.19.0" + "@mui/x-internals" "8.22.0" "@types/react-transition-group" "^4.4.12" clsx "^2.1.1" prop-types "^15.8.1" react-transition-group "^4.4.5" -"@mui/x-internals@8.19.0": - version "8.19.0" - resolved "https://registry.yarnpkg.com/@mui/x-internals/-/x-internals-8.19.0.tgz#256c0abb5abe3c25b7125cbbdaebfc3c5dc3a9ef" - integrity sha512-mMmiyJAN5fW27srXJjhXhXJa+w2xGO45rwcjws6OQc9rdXGdJqRXhBwJd+OT7J1xwSdFIIUhjZRTz1KAfCSGBg== +"@mui/x-internals@8.22.0": + version "8.22.0" + resolved "https://registry.yarnpkg.com/@mui/x-internals/-/x-internals-8.22.0.tgz#adf47979b3ef1e7c078965756916f6bd57c3c655" + integrity sha512-PA7jCKRLbS6aYvTSbGr3Id4CPUdTrUejHm31l8Vje7dw138gBBHrHeGsqWJh/S5foorpK8loiRejKrLlTZokyQ== dependencies: "@babel/runtime" "^7.28.4" "@mui/utils" "^7.3.5" @@ -1472,10 +1472,10 @@ "@emnapi/runtime" "^1.4.3" "@tybys/wasm-util" "^0.10.0" -"@next/env@15.5.6": - version "15.5.6" - resolved "https://registry.yarnpkg.com/@next/env/-/env-15.5.6.tgz#7009d88d419a36a4ba9e110c151604444744a74d" - integrity sha512-3qBGRW+sCGzgbpc5TS1a0p7eNxnOarGVQhZxfvTdnV0gFI61lX7QNtQ4V1TSREctXzYn5NetbUsLvyqwLFJM6Q== +"@next/env@15.5.9": + version "15.5.9" + resolved "https://registry.yarnpkg.com/@next/env/-/env-15.5.9.tgz#53c2c34dc17cd87b61f70c6cc211e303123b2ab8" + integrity sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg== "@next/eslint-plugin-next@15.5.2": version "15.5.2" @@ -1484,45 +1484,135 @@ dependencies: fast-glob "3.3.1" -"@next/swc-darwin-arm64@15.5.6": - version "15.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.6.tgz#f80d3fe536f29f3217ca07d031f7b43862234059" - integrity sha512-ES3nRz7N+L5Umz4KoGfZ4XX6gwHplwPhioVRc25+QNsDa7RtUF/z8wJcbuQ2Tffm5RZwuN2A063eapoJ1u4nPg== - -"@next/swc-darwin-x64@15.5.6": - version "15.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.6.tgz#289334478617318a0d8d9f1f6661a15952f4e4ab" - integrity sha512-JIGcytAyk9LQp2/nuVZPAtj8uaJ/zZhsKOASTjxDug0SPU9LAM3wy6nPU735M1OqacR4U20LHVF5v5Wnl9ptTA== - -"@next/swc-linux-arm64-gnu@15.5.6": - version "15.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.6.tgz#efdd993cd9ad88b82c948c8e518e045566dd2f98" - integrity sha512-qvz4SVKQ0P3/Im9zcS2RmfFL/UCQnsJKJwQSkissbngnB/12c6bZTCB0gHTexz1s6d/mD0+egPKXAIRFVS7hQg== - -"@next/swc-linux-arm64-musl@15.5.6": - version "15.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.6.tgz#a45185126faf69eb65a017bd2c015ad7e86f5c84" - integrity sha512-FsbGVw3SJz1hZlvnWD+T6GFgV9/NYDeLTNQB2MXoPN5u9VA9OEDy6fJEfePfsUKAhJufFbZLgp0cPxMuV6SV0w== - -"@next/swc-linux-x64-gnu@15.5.6": - version "15.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.6.tgz#8174dc8e03a1f7df292bead360f83c53f8dd8b73" - integrity sha512-3QnHGFWlnvAgyxFxt2Ny8PTpXtQD7kVEeaFat5oPAHHI192WKYB+VIKZijtHLGdBBvc16tiAkPTDmQNOQ0dyrA== - -"@next/swc-linux-x64-musl@15.5.6": - version "15.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.6.tgz#6d0776d81c5bd6a1780e6c39f32d7ef172900635" - integrity sha512-OsGX148sL+TqMK9YFaPFPoIaJKbFJJxFzkXZljIgA9hjMjdruKht6xDCEv1HLtlLNfkx3c5w2GLKhj7veBQizQ== - -"@next/swc-win32-arm64-msvc@15.5.6": - version "15.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.6.tgz#4b63d511b5c41278a48168fccb89cf00912411af" - integrity sha512-ONOMrqWxdzXDJNh2n60H6gGyKed42Ieu6UTVPZteXpuKbLZTH4G4eBMsr5qWgOBA+s7F+uB4OJbZnrkEDnZ5Fg== - -"@next/swc-win32-x64-msvc@15.5.6": - version "15.5.6" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.6.tgz#9ed5e84bd85009625dd35c444668d13061452a50" - integrity sha512-pxK4VIjFRx1MY92UycLOOw7dTdvccWsNETQ0kDHkBlcFH1GrTLUjSiHU1ohrznnux6TqRHgv5oflhfIWZwVROQ== +"@next/swc-darwin-arm64@15.5.7": + version "15.5.7" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz#f0c9ccfec2cd87cbd4b241ce4c779a7017aed958" + integrity sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw== + +"@next/swc-darwin-x64@15.5.7": + version "15.5.7" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz#18009e9fcffc5c0687cc9db24182ddeac56280d9" + integrity sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg== + +"@next/swc-linux-arm64-gnu@15.5.7": + version "15.5.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz#fe7c7e08264cf522d4e524299f6d3e63d68d579a" + integrity sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA== + +"@next/swc-linux-arm64-musl@15.5.7": + version "15.5.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz#94228fe293475ec34a5a54284e1056876f43a3cf" + integrity sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw== + +"@next/swc-linux-x64-gnu@15.5.7": + version "15.5.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz#078c71201dfe7fcfb8fa6dc92aae6c94bc011cdc" + integrity sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw== + +"@next/swc-linux-x64-musl@15.5.7": + version "15.5.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz#72947f5357f9226292353e0bb775643da3c7a182" + integrity sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA== + +"@next/swc-win32-arm64-msvc@15.5.7": + version "15.5.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz#397b912cd51c6a80e32b9c0507ecd82514353941" + integrity sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ== + +"@next/swc-win32-x64-msvc@15.5.7": + version "15.5.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz#e02b543d9dc6c1631d4ac239cb1177245dfedfe4" + integrity sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw== + +"@nivo/colors@0.99.0": + version "0.99.0" + resolved "https://registry.yarnpkg.com/@nivo/colors/-/colors-0.99.0.tgz#f844a0f0de0597b35829405541e145f6db50c830" + integrity sha512-hyYt4lEFIfXOUmQ6k3HXm3KwhcgoJpocmoGzLUqzk7DzuhQYJo+4d5jIGGU0N/a70+9XbHIdpKNSblHAIASD3w== + dependencies: + "@nivo/core" "0.99.0" + "@nivo/theming" "0.99.0" + "@types/d3-color" "^3.0.0" + "@types/d3-scale" "^4.0.8" + "@types/d3-scale-chromatic" "^3.0.0" + d3-color "^3.1.0" + d3-scale "^4.0.2" + d3-scale-chromatic "^3.0.0" + lodash "^4.17.21" + +"@nivo/core@0.99.0", "@nivo/core@^0.99.0": + version "0.99.0" + resolved "https://registry.yarnpkg.com/@nivo/core/-/core-0.99.0.tgz#91ccf3d2419fcfb5f740dba468f0d6f059933af4" + integrity sha512-olCItqhPG3xHL5ei+vg52aB6o+6S+xR2idpkd9RormTTUniZb8U2rOdcQojOojPY5i9kVeQyLFBpV4YfM7OZ9g== + dependencies: + "@nivo/theming" "0.99.0" + "@nivo/tooltip" "0.99.0" + "@react-spring/web" "9.4.5 || ^9.7.2 || ^10.0" + "@types/d3-shape" "^3.1.6" + d3-color "^3.1.0" + d3-format "^1.4.4" + d3-interpolate "^3.0.1" + d3-scale "^4.0.2" + d3-scale-chromatic "^3.0.0" + d3-shape "^3.2.0" + d3-time-format "^3.0.0" + lodash "^4.17.21" + react-virtualized-auto-sizer "^1.0.26" + use-debounce "^10.0.4" + +"@nivo/legends@0.99.0": + version "0.99.0" + resolved "https://registry.yarnpkg.com/@nivo/legends/-/legends-0.99.0.tgz#4f1fede8c450dad942b851a9a429838e343aea1b" + integrity sha512-P16FjFqNceuTTZphINAh5p0RF0opu3cCKoWppe2aRD9IuVkvRm/wS5K1YwMCxDzKyKh5v0AuTlu9K6o3/hk8hA== + dependencies: + "@nivo/colors" "0.99.0" + "@nivo/core" "0.99.0" + "@nivo/text" "0.99.0" + "@nivo/theming" "0.99.0" + "@types/d3-scale" "^4.0.8" + d3-scale "^4.0.2" + +"@nivo/sankey@^0.99.0": + version "0.99.0" + resolved "https://registry.yarnpkg.com/@nivo/sankey/-/sankey-0.99.0.tgz#58aa360a7bb37cf950b9e4fb2d052eb6bd302bf3" + integrity sha512-u5hySywsachjo9cHdUxCR9qwD6gfRVPEAcpuIUKiA0WClDjdGbl3vkrQcQcFexJUBThqSSbwGCDWR+2INXSbTw== + dependencies: + "@nivo/colors" "0.99.0" + "@nivo/core" "0.99.0" + "@nivo/legends" "0.99.0" + "@nivo/text" "0.99.0" + "@nivo/theming" "0.99.0" + "@nivo/tooltip" "0.99.0" + "@react-spring/web" "9.4.5 || ^9.7.2 || ^10.0" + "@types/d3-sankey" "^0.11.2" + "@types/d3-shape" "^3.1.6" + d3-sankey "^0.12.3" + d3-shape "^3.2.0" + lodash "^4.17.21" + +"@nivo/text@0.99.0": + version "0.99.0" + resolved "https://registry.yarnpkg.com/@nivo/text/-/text-0.99.0.tgz#b52f37d903e731f60027c814658e271676fafdf8" + integrity sha512-ho3oZpAZApsJNjsIL5WJSAdg/wjzTBcwo1KiHBlRGUmD+yUWO8qp7V+mnYRhJchwygtRVALlPgZ/rlcW2Xr/MQ== + dependencies: + "@nivo/core" "0.99.0" + "@nivo/theming" "0.99.0" + "@react-spring/web" "9.4.5 || ^9.7.2 || ^10.0" + +"@nivo/theming@0.99.0": + version "0.99.0" + resolved "https://registry.yarnpkg.com/@nivo/theming/-/theming-0.99.0.tgz#89de03832081153093dcfc2eb2fdaaf3424da963" + integrity sha512-KvXlf0nqBzh/g2hAIV9bzscYvpq1uuO3TnFN3RDXGI72CrbbZFTGzprPju3sy/myVsauv+Bb+V4f5TZ0jkYKRg== + dependencies: + lodash "^4.17.21" + +"@nivo/tooltip@0.99.0": + version "0.99.0" + resolved "https://registry.yarnpkg.com/@nivo/tooltip/-/tooltip-0.99.0.tgz#63a1bc3b428cb2a07a7f763ad8547e39dd4bcf13" + integrity sha512-weoEGR3xAetV4k2P6k96cdamGzKQ5F2Pq+uyDaHr1P3HYArM879Pl+x+TkU0aWjP6wgUZPx/GOBiV1Hb1JxIqg== + dependencies: + "@nivo/core" "0.99.0" + "@nivo/theming" "0.99.0" + "@react-spring/web" "9.4.5 || ^9.7.2 || ^10.0" "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -1698,14 +1788,59 @@ "@react-pdf/primitives" "^4.1.1" "@react-pdf/stylesheet" "^6.1.1" -"@reduxjs/toolkit@2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.9.0.tgz#d4b12b90c27716a6a507193f8c3b2880729b97d5" - integrity sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog== +"@react-spring/animated@~10.0.3": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-10.0.3.tgz#b42f7041a51d38f395e9ba5fb53ca68c34cd324f" + integrity sha512-7MrxADV3vaUADn2V9iYhaIL6iOWRx9nCJjYrsk2AHD2kwPr6fg7Pt0v+deX5RnCDmCKNnD6W5fasiyM8D+wzJQ== + dependencies: + "@react-spring/shared" "~10.0.3" + "@react-spring/types" "~10.0.3" + +"@react-spring/core@~10.0.3": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-10.0.3.tgz#3b4f3991f5902ce46770c2c1ef05c8e53c3a0f73" + integrity sha512-D4DwNO68oohDf/0HG2G0Uragzb9IA1oXblxrd6MZAcBcUQG2EHUWXewjdECMPLNmQvlYVyyBRH6gPxXM5DX7DQ== + dependencies: + "@react-spring/animated" "~10.0.3" + "@react-spring/shared" "~10.0.3" + "@react-spring/types" "~10.0.3" + +"@react-spring/rafz@~10.0.3": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-10.0.3.tgz#9b328c3992b23d6317452998670636d6b783f2c4" + integrity sha512-Ri2/xqt8OnQ2iFKkxKMSF4Nqv0LSWnxXT4jXFzBDsHgeeH/cHxTLupAWUwmV9hAGgmEhBmh5aONtj3J6R/18wg== + +"@react-spring/shared@~10.0.3": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-10.0.3.tgz#654d03c74d3277bae1a565aff981979536be6002" + integrity sha512-geCal66nrkaQzUVhPkGomylo+Jpd5VPK8tPMEDevQEfNSWAQP15swHm+MCRG4wVQrQlTi9lOzKzpRoTL3CA84Q== + dependencies: + "@react-spring/rafz" "~10.0.3" + "@react-spring/types" "~10.0.3" + +"@react-spring/types@~10.0.3": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-10.0.3.tgz#0c2d7a7e783a6f652bcd24cac80ed569bc2ad8d9" + integrity sha512-H5Ixkd2OuSIgHtxuHLTt7aJYfhMXKXT/rK32HPD/kSrOB6q6ooeiWAXkBy7L8F3ZxdkBb9ini9zP9UwnEFzWgQ== + +"@react-spring/web@9.4.5 || ^9.7.2 || ^10.0": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-10.0.3.tgz#ae3a9ea2362b1d70d2ec36a1e2747c6cee2540a9" + integrity sha512-ndU+kWY81rHsT7gTFtCJ6mrVhaJ6grFmgTnENipzmKqot4HGf5smPNK+cZZJqoGeDsj9ZsiWPW4geT/NyD484A== + dependencies: + "@react-spring/animated" "~10.0.3" + "@react-spring/core" "~10.0.3" + "@react-spring/shared" "~10.0.3" + "@react-spring/types" "~10.0.3" + +"@reduxjs/toolkit@1.x.x || 2.x.x", "@reduxjs/toolkit@^2.11.2": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.11.2.tgz#582225acea567329ca6848583e7dd72580d38e82" + integrity sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ== dependencies: "@standard-schema/spec" "^1.0.0" "@standard-schema/utils" "^0.3.0" - immer "^10.0.3" + immer "^11.0.0" redux "^5.0.1" redux-thunk "^3.1.0" reselect "^5.1.0" @@ -1731,9 +1866,9 @@ integrity sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA== "@standard-schema/spec@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c" - integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA== + version "1.1.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== "@standard-schema/utils@^0.3.0": version "0.3.0" @@ -1894,51 +2029,51 @@ dependencies: remove-accents "0.5.0" -"@tanstack/query-core@5.90.10": - version "5.90.10" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.90.10.tgz#2704b038277fb41e631f356e7428951b5354ed13" - integrity sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ== +"@tanstack/query-core@5.90.12": + version "5.90.12" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.90.12.tgz#e1f5f47e72ef7d0fc794325936921c700352515e" + integrity sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg== -"@tanstack/query-devtools@5.90.1": - version "5.90.1" - resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.90.1.tgz#c57a739a5293f4960a4a6b6fb4b7e4a56e6bd932" - integrity sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ== +"@tanstack/query-devtools@5.91.1": + version "5.91.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.91.1.tgz#0b0e3b94861f3a584560af3047a536bf7fea06f1" + integrity sha512-l8bxjk6BMsCaVQH6NzQEE/bEgFy1hAs5qbgXl0xhzezlaQbPk6Mgz9BqEg2vTLPOHD8N4k+w/gdgCbEzecGyNg== -"@tanstack/query-persist-client-core@5.91.9": - version "5.91.9" - resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-5.91.9.tgz#6754283f974296a2abf6dd660a937a835a48f174" - integrity sha512-LliMZl/pkO/6vRf5//fO8nl4UCfM1LQsnT+N0aRYkK7bqoM3QdqHxD65EApmJRypKkqaWmiyulPG3Mi1NYuyIA== +"@tanstack/query-persist-client-core@5.91.11": + version "5.91.11" + resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-5.91.11.tgz#88a298b5559c67d837a1afefde278e02c97acc59" + integrity sha512-NNpRGxQY/nVOdzfs5QbevPjGsUVoEiFwqxxaopLyu6todwtDOCfIOfhXSmpMVXBiCxUn7kqaUB1iwaBKqoAVRQ== dependencies: - "@tanstack/query-core" "5.90.10" + "@tanstack/query-core" "5.90.12" "@tanstack/query-sync-storage-persister@^5.76.0": - version "5.90.12" - resolved "https://registry.yarnpkg.com/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-5.90.12.tgz#e81e87b74ebe7d95d549651df74a7b511b346baa" - integrity sha512-RXLcwHwCYUKkXbhPLuWftS3h7ZlM4WVfSOvIYMmPIJ7Xcle9KwvVsT2IaUAf7Ltow4qCNRZsSkatf+t9LiOj3A== + version "5.90.14" + resolved "https://registry.yarnpkg.com/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-5.90.14.tgz#b68c7f982d7f41af77df009bdf0a064e94448f78" + integrity sha512-gqQy7VFNUcFnD61mr1sqvMLdL3MXrTzMmjuQqRVyItb9FgEKzMSyBfo8cLZPiWWrz29eAVlQs1rEYUNLZRRMqg== dependencies: - "@tanstack/query-core" "5.90.10" - "@tanstack/query-persist-client-core" "5.91.9" + "@tanstack/query-core" "5.90.12" + "@tanstack/query-persist-client-core" "5.91.11" "@tanstack/react-query-devtools@^5.51.11": - version "5.90.2" - resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.90.2.tgz#248d6ae89ace7dc2da816fa95cdc2b4f63c9e4d2" - integrity sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ== + version "5.91.1" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.91.1.tgz#48c7507ba57156fcd2060d1fdbd3dc6fd566bf16" + integrity sha512-tRnJYwEbH0kAOuToy8Ew7bJw1lX3AjkkgSlf/vzb+NpnqmHPdWM+lA2DSdGQSLi1SU0PDRrrCI1vnZnci96CsQ== dependencies: - "@tanstack/query-devtools" "5.90.1" + "@tanstack/query-devtools" "5.91.1" "@tanstack/react-query-persist-client@^5.76.0": - version "5.90.12" - resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.90.12.tgz#77848b413ab07f075e774c45747aabfee37b3c92" - integrity sha512-o51hwImpKgb85FnFljtCXcUzuLXpKONF9N6bhKfifPL3SNSj8neh1a2aHQd7sN9mbeIeNfGMGJuDpSt/Fc3GwQ== + version "5.90.14" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.90.14.tgz#aa99fbf6e34b4eab7854b1cbed1dcadf9817d94d" + integrity sha512-jTGnr/DBlzV/UYqU+b8bZWECBuqh3Q3g7Ih50IktZkvmwTUsidQhhl2JyknNYVZkl5AgMfPAywNKZLTI82wmlA== dependencies: - "@tanstack/query-persist-client-core" "5.91.9" + "@tanstack/query-persist-client-core" "5.91.11" "@tanstack/react-query@^5.51.11": - version "5.90.10" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.90.10.tgz#dc2d4acbe1c06b708ccaeed43a26ad56bc5d1718" - integrity sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw== + version "5.90.12" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.90.12.tgz#49536842eff6487a9e645a453fea2642d8f4f209" + integrity sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg== dependencies: - "@tanstack/query-core" "5.90.10" + "@tanstack/query-core" "5.90.12" "@tanstack/react-table@8.20.6": version "8.20.6" @@ -1976,149 +2111,149 @@ resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.11.2.tgz#00409e743ac4eea9afe5b7708594d5fcebb00212" integrity sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw== -"@tiptap/core@^3.11.0", "@tiptap/core@^3.4.1": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.11.0.tgz#122a1db7852c9cea48221290210e713bb4efd66e" - integrity sha512-kmS7ZVpHm1EMnW1Wmft9H5ZLM7E0G0NGBx+aGEHGDcNxZBXD2ZUa76CuWjIhOGpwsPbELp684ZdpF2JWoNi4Dg== +"@tiptap/core@^3.13.0", "@tiptap/core@^3.4.1": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.13.0.tgz#ae3fe6fe7732f36b6ea8a2198e1fc53a4ad0d0d2" + integrity sha512-iUelgiTMgPVMpY5ZqASUpk8mC8HuR9FWKaDzK27w9oWip9tuB54Z8mePTxNcQaSPb6ErzEaC8x8egrRt7OsdGQ== -"@tiptap/extension-blockquote@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-3.11.0.tgz#15adf170582ce3a1b81364d50aa6c5b830201a10" - integrity sha512-0H8WVW6Vn4GJ7sQ6wfyDgUU+DqM8fp62g8N0fFPiEhoYtpIYUmCqGhpKnqYR0tet6ofFa648XmA6n2VX7sugzw== +"@tiptap/extension-blockquote@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-3.13.0.tgz#33508ad7f0bd4d74d5065f11d6e33c50ef8835a2" + integrity sha512-K1z/PAIIwEmiWbzrP//4cC7iG1TZknDlF1yb42G7qkx2S2X4P0NiqX7sKOej3yqrPjKjGwPujLMSuDnCF87QkQ== -"@tiptap/extension-bold@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.11.0.tgz#4d23a29717d469b59e82157adc61b0f86a53d988" - integrity sha512-V/c3XYO09Le9GlBGq1MK4c97Fffi0GADQTbZ+LFoi65nUrAwutn5wYnXBcEyWQI6RmFWVDJTieamqtc4j9teyw== +"@tiptap/extension-bold@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.13.0.tgz#1fbff35b20da292172fc5a1886576c3410e1e3ca" + integrity sha512-VYiDN9EEwR6ShaDLclG8mphkb/wlIzqfk7hxaKboq1G+NSDj8PcaSI9hldKKtTCLeaSNu6UR5nkdu/YHdzYWTw== -"@tiptap/extension-bubble-menu@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.11.0.tgz#2ce7820c9aecd0f4ce36c2668353aa8194ea55a5" - integrity sha512-P3j9lQ+EZ5Zg/isJzLpCPX7bp7WUBmz8GPs/HPlyMyN2su8LqXntITBZr8IP1JNBlB/wR83k/W0XqdC57mG7cA== +"@tiptap/extension-bubble-menu@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.13.0.tgz#c62dece2e865a2efaaf455061204f48f59f5b24f" + integrity sha512-qZ3j2DBsqP9DjG2UlExQ+tHMRhAnWlCKNreKddKocb/nAFrPdBCtvkqIEu+68zPlbLD4ukpoyjUklRJg+NipFg== dependencies: "@floating-ui/dom" "^1.0.0" -"@tiptap/extension-bullet-list@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.11.0.tgz#6bcd2ed0a4d7cba5ae6deee0329ae8b18df06e09" - integrity sha512-IKdb1C3bHA1sGPiUcntkL+wHebRg71K5+tgaaRnMw0qmtcpcOQb5zhQOSm5bXUsgCk/WgT04dkZPnpn6Gg1PvQ== - -"@tiptap/extension-code-block@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-3.11.0.tgz#60297e4a7ce05b9a896ff46babbc41c2fc324836" - integrity sha512-y01RJVbygDJWYXxZ0SiCYwvUF2X91RANCLSdb8X0qiwVPgNOzsDrrzS/iqoXkiYmM93pJw+ZWelEZxRvxEwsrg== - -"@tiptap/extension-code@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-3.11.0.tgz#c78d6c8dbf2edaa6578587003e0faf5f57dc1fe2" - integrity sha512-5OpR5O4bveHe1KG9CJsto86NgkuerYq3OLY78vzh9uFCLdv7xgXA2aZYJfRMhbZ7hKsR7hHg1etBJUCk+TKsMg== - -"@tiptap/extension-document@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-3.11.0.tgz#fa4ed625730dcfbb5ea35a630f9163d6843adfed" - integrity sha512-N2G3cwL2Dtur/CgD/byJmFx9T5no6fTO/U462VP3rthQYrRA1AB3TCYqtlwJkmyoxRTNd4qIg4imaPl8ej6Heg== - -"@tiptap/extension-dropcursor@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.11.0.tgz#869236f9644f80f316bd643281e1cb2623f80747" - integrity sha512-gW/QMGAyiXGSpO+X/lTeiBQn1Or8T8UVB3y9Cv2Lh6zx0SWU+FA28EH+y6s3fm872reN4dH/9rEvMuJjhU/BEw== - -"@tiptap/extension-floating-menu@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-3.11.0.tgz#521109d9c0d5f6dc5fb6f2fd8181367af8a91be2" - integrity sha512-nEHdWZHEJYX1II1oJQ4aeZ8O/Kss4BRbYFXQFGIvPelCfCYEATpUJh3aq3767ARSq40bOWyu+Dcd4SCW0We6Sw== - -"@tiptap/extension-gapcursor@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.11.0.tgz#b4c9026bde893d19ecc02bc23273dc2a64272589" - integrity sha512-lXGEZiYX7k/pEFr8BgDE91vqjLTwuf+qhHLTgIpfhbt562nShLPIDj9Vzu3xrR4fwUAMiUNiLyaeInb8j3I4kg== - -"@tiptap/extension-hard-break@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.11.0.tgz#a0d7c5564c4fed1c4446c53f924ff2e468e157cb" - integrity sha512-NJEHTj++kFOayQXKSQSi9j9eAG33eSiJqai2pf4U+snW94fmb8cYLUurDmfYRe20O6EzBSX0X3GjVlkOz+5b7A== - -"@tiptap/extension-heading@^3.11.0", "@tiptap/extension-heading@^3.4.1": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-3.11.0.tgz#c76c27a1253ccf56588f6cbdf876e80b50bc72f4" - integrity sha512-4Eo67Yo7vsYLkizcMoGdZAR9aHbC7FFTrqfNEd4Em3ajRi0iNqyWMaI90UCYlitDdRdqFlq/njWrMqBOLUgaWQ== - -"@tiptap/extension-horizontal-rule@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.11.0.tgz#66011ca8084a28815f2781768e677b94d4611031" - integrity sha512-FugFHZG+oiMBV6k42hn9NOA4wRNc2b9UeEIMR+XwEMpWJInV4VwSwDvu8JClgkDo8z7FEnker9e51DZ00CLWqg== +"@tiptap/extension-bullet-list@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.13.0.tgz#277c9380704f618d71b63278da7bba45ed2b7905" + integrity sha512-fFQmmEUoPzRGiQJ/KKutG35ZX21GE+1UCDo8Q6PoWH7Al9lex47nvyeU1BiDYOhcTKgIaJRtEH5lInsOsRJcSA== + +"@tiptap/extension-code-block@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-3.13.0.tgz#ded41a224db15e938c6a871462a0c33c00acd657" + integrity sha512-kIwfQ4iqootsWg9e74iYJK54/YMIj6ahUxEltjZRML5z/h4gTDcQt2eTpnEC8yjDjHeUVOR94zH9auCySyk9CQ== + +"@tiptap/extension-code@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-3.13.0.tgz#7c05fb8477356aebe8b1f00117a32d3abbf24357" + integrity sha512-sF5raBni6iSVpXWvwJCAcOXw5/kZ+djDHx1YSGWhopm4+fsj0xW7GvVO+VTwiFjZGKSw+K5NeAxzcQTJZd3Vhw== + +"@tiptap/extension-document@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-3.13.0.tgz#74757e23bf92bba82226a91580ce738bc68bd3af" + integrity sha512-RjU7hTJwjKXIdY57o/Pc+Yr8swLkrwT7PBQ/m+LCX5oO/V2wYoWCjoBYnK5KSHrWlNy/aLzC33BvLeqZZ9nzlQ== + +"@tiptap/extension-dropcursor@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.13.0.tgz#04f7659c86558ebeb068fd7d5c2474d8bd28b430" + integrity sha512-m7GPT3c/83ni+bbU8c+3dpNa8ug+aQ4phNB1Q52VQG3oTonDJnZS7WCtn3lB/Hi1LqoqMtEHwhepU2eD+JeXqQ== + +"@tiptap/extension-floating-menu@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-3.13.0.tgz#03d03292add49d1b380cdb1ff3890b2956d4e3f5" + integrity sha512-OsezV2cMofZM4c13gvgi93IEYBUzZgnu8BXTYZQiQYekz4bX4uulBmLa1KOA9EN71FzS+SoLkXHU0YzlbLjlxA== + +"@tiptap/extension-gapcursor@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.13.0.tgz#5e4fbd3b066fa10656314bbbff2e329709be5d2c" + integrity sha512-KVxjQKkd964nin+1IdM2Dvej/Jy4JTMcMgq5seusUhJ9T9P8F9s2D5Iefwgkps3OCzub/aF+eAsZe+1P5KSIgA== + +"@tiptap/extension-hard-break@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.13.0.tgz#b1444339c544f27fe8cff8dcbdb99007e0cdc3e1" + integrity sha512-nH1OBaO+/pakhu+P1jF208mPgB70IKlrR/9d46RMYoYbqJTNf4KVLx5lHAOHytIhjcNg+MjyTfJWfkK+dyCCyg== + +"@tiptap/extension-heading@^3.13.0", "@tiptap/extension-heading@^3.4.1": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-3.13.0.tgz#ead7f224de24ac66bb198cabe6b2af9617967583" + integrity sha512-8VKWX8waYPtUWN97J89em9fOtxNteh6pvUEd0htcOAtoxjt2uZjbW5N4lKyWhNKifZBrVhH2Cc2NUPuftCVgxw== + +"@tiptap/extension-horizontal-rule@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.13.0.tgz#c51eb35f3b3bf6308ab6b354a06c0e96c19dbff6" + integrity sha512-ZUFyORtjj22ib8ykbxRhWFQOTZjNKqOsMQjaAGof30cuD2DN5J5pMz7Haj2fFRtLpugWYH+f0Mi+WumQXC3hCw== "@tiptap/extension-image@^3.4.1": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-3.11.0.tgz#83db8ad14011cc8006be65044094aac1a81fee95" - integrity sha512-AFH1kn5Cpe5D89fP4MKN98N8pwmB3s4Rn8jq6rk3QVaAwdrxwxbAqUI1sX7UPFzaq9eNcHgmGLaDL+UTspSW8g== + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-3.13.0.tgz#55edb952e86c2ebed436cd53def8b2e743d71d7e" + integrity sha512-223uzLUkIa1rkK7aQK3AcIXe6LbCtmnpVb7sY5OEp+LpSaSPyXwyrZ4A0EO1o98qXG68/0B2OqMntFtA9c5Fbw== -"@tiptap/extension-italic@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.11.0.tgz#7c2e59c2d3a0b73dcc788eb099e727d131947ee0" - integrity sha512-WP6wL2b//8bLVdeUCWOpYA7nUStvrAMMD0nRn0F9CEW+l7vH6El2PZFhHmJ9uqXo5MnyugBpARiwgxfoAlef5w== +"@tiptap/extension-italic@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.13.0.tgz#c855521360c8079574f7b0855148e4f561ba396a" + integrity sha512-XbVTgmzk1kgUMTirA6AGdLTcKHUvEJoh3R4qMdPtwwygEOe7sBuvKuLtF6AwUtpnOM+Y3tfWUTNEDWv9AcEdww== -"@tiptap/extension-link@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-3.11.0.tgz#38ed5dc9251a95f02c58f069fe2b30a0ecb80dbc" - integrity sha512-RoUkGqowVMKLE76KktNOGhzNMyKtwrSDRqeYCe1ODPuOMZvDGexOE8cIuA4A1ODkgN6ji9qE/9Sf8uhpZdH39Q== +"@tiptap/extension-link@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-3.13.0.tgz#c6b087a39860068b93d1fb8fcbebbd360f0188b4" + integrity sha512-LuFPJ5GoL12GHW4A+USsj60O90pLcwUPdvEUSWewl9USyG6gnLnY/j5ZOXPYH7LiwYW8+lhq7ABwrDF2PKyBbA== dependencies: linkifyjs "^4.3.2" -"@tiptap/extension-list-item@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-3.11.0.tgz#407231043618d6447a7113674ce9493b412890f9" - integrity sha512-KXTTSBH/T/WW8O1YhK/lVmwlSGh2w2VVucUkMLhgk1VPchahAkn2LfgbgKrCRG/F8M8Jlfvz67iJDo6+bbNqew== +"@tiptap/extension-list-item@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-3.13.0.tgz#03f17af7ed2d0643638e07ce96ad0f9c044ff69b" + integrity sha512-63NbcS/XeQP2jcdDEnEAE3rjJICDj8y1SN1h/MsJmSt1LusnEo8WQ2ub86QELO6XnD3M04V03cY6Knf6I5mTkw== -"@tiptap/extension-list-keymap@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.11.0.tgz#fab78623c4c05c368b903dfdfd4859079c19783a" - integrity sha512-vm1zGdEqcbQnrGlVXchk1ibmTsyxyfGcGPVWsc4MG+UAFcNfcpAnvCar71BF4RGGPtpzOWdqGkvJENyh0L5/Hw== +"@tiptap/extension-list-keymap@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.13.0.tgz#75ee2c28f5d944c407309ce987d07ae23c4cd45a" + integrity sha512-P+HtIa1iwosb1feFc8B/9MN5EAwzS+/dZ0UH0CTF2E4wnp5Z9OMxKl1IYjfiCwHzZrU5Let+S/maOvJR/EmV0g== -"@tiptap/extension-list@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list/-/extension-list-3.11.0.tgz#8a450e74c720ab5965533f319e0de717a6ca530e" - integrity sha512-4Ane7VCVZ+GFOQNuy2nMP+SoWH7EemC3geTTqvgHm1H0tbSosxLJAVaZ9dF06F35RJmYCm+jLJUhRVd156eCRQ== +"@tiptap/extension-list@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list/-/extension-list-3.13.0.tgz#6981a395f2fbe46d9ad20deb75cf65aa9e33feba" + integrity sha512-MMFH0jQ4LeCPkJJFyZ77kt6eM/vcKujvTbMzW1xSHCIEA6s4lEcx9QdZMPpfmnOvTzeoVKR4nsu2t2qT9ZXzAw== -"@tiptap/extension-ordered-list@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.11.0.tgz#41270944de1becd370d48b13854effaa69a0eeea" - integrity sha512-kO8GH4w4Xil+qPiHJLAyILdGHF9hCjkhoVtPD8YEfqK6Qx3bZql5FPySCQNs+MU6rLSCCdam8SUPGY/+SCufqA== +"@tiptap/extension-ordered-list@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.13.0.tgz#552d4a57e9116fd7d32e49c5cdc346baf0bbfd74" + integrity sha512-QuDyLzuK/3vCvx9GeKhgvHWrGECBzmJyAx6gli2HY+Iil7XicbfltV4nvhIxgxzpx3LDHLKzJN9pBi+2MzX60g== -"@tiptap/extension-paragraph@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-3.11.0.tgz#60ecdcb24330b39f72b58760bcaf299b20de43da" - integrity sha512-hxgjZOXOqstRTWv+QjWJjK23rD5qzIV9ePlhX3imLeq/MgX0aU9VBDaG5SGKbSjaBNQnpLw6+sABJi3CDP6Z5A== +"@tiptap/extension-paragraph@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-3.13.0.tgz#01881b5954136de5059e7882be5b210eca0dac46" + integrity sha512-9csQde1i0yeZI5oQQ9e1GYNtGL2JcC2d8Fwtw9FsGC8yz2W0h+Fmk+3bc2kobbtO5LGqupSc1fKM8fAg5rSRDg== -"@tiptap/extension-strike@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-3.11.0.tgz#68f746cbdcdd57a01a31f612d8d04a919d91b6cd" - integrity sha512-XVP/WMYLrqLBfUsGPu2H9MrOUZLhGUaxtZ3hSRffDi/lsw53x/coZ9eO0FxOB9R7z2ksHWmticIs+0YnKt9LNQ== +"@tiptap/extension-strike@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-3.13.0.tgz#f753bae727549fb32ee9251036890ed5f39bc443" + integrity sha512-VHhWNqTAMOfrC48m2FcPIZB0nhl6XHQviAV16SBc+EFznKNv9tQUsqQrnuQ2y6ZVfqq5UxvZ3hKF/JlN/Ff7xw== "@tiptap/extension-table@^3.4.1": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-3.11.0.tgz#9725b077721adbe2a1151e53e3a990426aab26f7" - integrity sha512-2yIj3gKkl0nrw20BKHMrGiUvQO9OK3DAu6UWm06Os9+Sdqiq38Or9YBJRpCfHs9SmXnGBbGUuBW1dnNB7/sZUw== - -"@tiptap/extension-text@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.11.0.tgz#cf55c8c0fa3a18fbc93ec53be7c31fe60ed4e9bd" - integrity sha512-ELAYm2BuChzZOqDG9B0k3W6zqM4pwNvXkam28KgHGiT2y7Ni68Rb+NXp16uVR+5zR6hkqnQ/BmJSKzAW59MXpA== - -"@tiptap/extension-underline@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.11.0.tgz#b5c113351fd9a35fb73fc4555ff687e3e31b0b8d" - integrity sha512-D3PsS/84RlQKFjd5eerMIUioC0mNh4yy1RRV/WbXx6ugu+6T+0hT42gNk9Ap8pDsVQZCk0SHfDyBEUFC2KOwKw== - -"@tiptap/extensions@^3.11.0": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.11.0.tgz#d6f6020312cda743738bbc1e918cd10a7f7d84fc" - integrity sha512-g43beA73ZMLezez1st9LEwYrRHZ0FLzlsSlOZKk7sdmtHLmuqWHf4oyb0XAHol1HZIdGv104rYaGNgmQXr1ecQ== - -"@tiptap/pm@^3.11.0", "@tiptap/pm@^3.4.1": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.11.0.tgz#c9d2bef0db08a5a5b2c6cce035fe893a475ee638" - integrity sha512-plCQDLCZIOc92cizB8NNhBRN0szvYR3cx9i5IXo6v9Xsgcun8KHNcJkesc2AyeqdIs0BtOJZaqQ9adHThz8UDw== + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-3.13.0.tgz#83283bc818582e621cefabf173beeb37fe6f30ba" + integrity sha512-LcH9KE4QBUJ6IPwt1Uo5iU7zatFjUUvXbctIu2fKQ9nqJ7nNSFxRhkNyporVFkTWYH7/rb0qMoF1VxSUGefG5w== + +"@tiptap/extension-text@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.13.0.tgz#90d38438eb99135b1221d7f2944a06545f21d39d" + integrity sha512-VcZIna93rixw7hRkHGCxDbL3kvJWi80vIT25a2pXg0WP1e7Pi3nBYvZIL4SQtkbBCji9EHrbZx3p8nNPzfazYw== + +"@tiptap/extension-underline@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.13.0.tgz#7fc969c3b7adc7d7cc7def498f85c4cb30cf3aba" + integrity sha512-VDQi+UYw0tFnfghpthJTFmtJ3yx90kXeDwFvhmT8G+O+si5VmP05xYDBYBmYCix5jqKigJxEASiBL0gYOgMDEg== + +"@tiptap/extensions@^3.13.0": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.13.0.tgz#542ee8a97575ae32090302b7f09522e025715297" + integrity sha512-i7O0ptSibEtTy+2PIPsNKEvhTvMaFJg1W4Oxfnbuxvaigs7cJV9Q0lwDUcc7CPsNw2T1+44wcxg431CzTvdYoA== + +"@tiptap/pm@^3.13.0", "@tiptap/pm@^3.4.1": + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.13.0.tgz#d01e9f08e2be3e6bfa69ed4457a1c2fee87157b3" + integrity sha512-WKR4ucALq+lwx0WJZW17CspeTpXorbIOpvKv5mulZica6QxqfMhn8n1IXCkDws/mCoLRx4Drk5d377tIjFNsvQ== dependencies: prosemirror-changeset "^2.3.0" prosemirror-collab "^1.3.1" @@ -2140,46 +2275,46 @@ prosemirror-view "^1.38.1" "@tiptap/react@^3.4.1": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-3.11.0.tgz#b9dd344101cd64df45cb7a5785f98c7d3a689f72" - integrity sha512-SDGei/2DjwmhzsxIQNr6dkB6NxLgXZjQ6hF36NfDm4937r5NLrWrNk5tCsoDQiKZ0DHEzuJ6yZM5C7I7LZLB6w== + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-3.13.0.tgz#4287e5df1c7e91182f280fccadf0c6dc6239eaff" + integrity sha512-VqpqNZ9qtPr3pWK4NsZYxXgLSEiAnzl6oS7tEGmkkvJbcGSC+F7R13Xc9twv/zT5QCLxaHdEbmxHbuAIkrMgJQ== dependencies: "@types/use-sync-external-store" "^0.0.6" - fast-deep-equal "^3.1.3" + fast-equals "^5.3.3" use-sync-external-store "^1.4.0" optionalDependencies: - "@tiptap/extension-bubble-menu" "^3.11.0" - "@tiptap/extension-floating-menu" "^3.11.0" + "@tiptap/extension-bubble-menu" "^3.13.0" + "@tiptap/extension-floating-menu" "^3.13.0" "@tiptap/starter-kit@^3.4.1": - version "3.11.0" - resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-3.11.0.tgz#dc21fe81747825d847bfe1e7da4e89039d0517b8" - integrity sha512-8kMMYqVSZ2Oqji+mY1o9meTjCRWp4DplFegu7APqDEQRhlb6mBI0wNuazYb7FKJIHJTtf0F6cYglJrxpu9c/fA== - dependencies: - "@tiptap/core" "^3.11.0" - "@tiptap/extension-blockquote" "^3.11.0" - "@tiptap/extension-bold" "^3.11.0" - "@tiptap/extension-bullet-list" "^3.11.0" - "@tiptap/extension-code" "^3.11.0" - "@tiptap/extension-code-block" "^3.11.0" - "@tiptap/extension-document" "^3.11.0" - "@tiptap/extension-dropcursor" "^3.11.0" - "@tiptap/extension-gapcursor" "^3.11.0" - "@tiptap/extension-hard-break" "^3.11.0" - "@tiptap/extension-heading" "^3.11.0" - "@tiptap/extension-horizontal-rule" "^3.11.0" - "@tiptap/extension-italic" "^3.11.0" - "@tiptap/extension-link" "^3.11.0" - "@tiptap/extension-list" "^3.11.0" - "@tiptap/extension-list-item" "^3.11.0" - "@tiptap/extension-list-keymap" "^3.11.0" - "@tiptap/extension-ordered-list" "^3.11.0" - "@tiptap/extension-paragraph" "^3.11.0" - "@tiptap/extension-strike" "^3.11.0" - "@tiptap/extension-text" "^3.11.0" - "@tiptap/extension-underline" "^3.11.0" - "@tiptap/extensions" "^3.11.0" - "@tiptap/pm" "^3.11.0" + version "3.13.0" + resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-3.13.0.tgz#7f803f0e089a7c2cbd016ad79b257c4cbe910208" + integrity sha512-Ojn6sRub04CRuyQ+9wqN62JUOMv+rG1vXhc2s6DCBCpu28lkCMMW+vTe7kXJcEdbot82+5swPbERw9vohswFzg== + dependencies: + "@tiptap/core" "^3.13.0" + "@tiptap/extension-blockquote" "^3.13.0" + "@tiptap/extension-bold" "^3.13.0" + "@tiptap/extension-bullet-list" "^3.13.0" + "@tiptap/extension-code" "^3.13.0" + "@tiptap/extension-code-block" "^3.13.0" + "@tiptap/extension-document" "^3.13.0" + "@tiptap/extension-dropcursor" "^3.13.0" + "@tiptap/extension-gapcursor" "^3.13.0" + "@tiptap/extension-hard-break" "^3.13.0" + "@tiptap/extension-heading" "^3.13.0" + "@tiptap/extension-horizontal-rule" "^3.13.0" + "@tiptap/extension-italic" "^3.13.0" + "@tiptap/extension-link" "^3.13.0" + "@tiptap/extension-list" "^3.13.0" + "@tiptap/extension-list-item" "^3.13.0" + "@tiptap/extension-list-keymap" "^3.13.0" + "@tiptap/extension-ordered-list" "^3.13.0" + "@tiptap/extension-paragraph" "^3.13.0" + "@tiptap/extension-strike" "^3.13.0" + "@tiptap/extension-text" "^3.13.0" + "@tiptap/extension-underline" "^3.13.0" + "@tiptap/extensions" "^3.13.0" + "@tiptap/pm" "^3.13.0" "@trysound/sax@0.2.0": version "0.2.0" @@ -2193,6 +2328,81 @@ dependencies: tslib "^2.4.0" +"@types/d3-array@^3.0.3": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.2.tgz#e02151464d02d4a1b44646d0fcdb93faf88fde8c" + integrity sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw== + +"@types/d3-color@*", "@types/d3-color@^3.0.0": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-ease@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-interpolate@^3.0.1": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.1.tgz#f632b380c3aca1dba8e34aa049bcd6a4af23df8a" + integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg== + +"@types/d3-path@^1": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.11.tgz#45420fee2d93387083b34eae4fe6d996edf482bc" + integrity sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw== + +"@types/d3-sankey@^0.11.2": + version "0.11.2" + resolved "https://registry.yarnpkg.com/@types/d3-sankey/-/d3-sankey-0.11.2.tgz#803214b11dc0a17db5d782fe9055cd92b06a5d75" + integrity sha512-U6SrTWUERSlOhnpSrgvMX64WblX1AxX6nEjI2t3mLK2USpQrnbwYYK+AS9SwiE7wgYmOsSSKoSdr8aoKBH0HgQ== + dependencies: + "@types/d3-shape" "^1" + +"@types/d3-scale-chromatic@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#dc6d4f9a98376f18ea50bad6c39537f1b5463c39" + integrity sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ== + +"@types/d3-scale@^4.0.2", "@types/d3-scale@^4.0.8": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.9.tgz#57a2f707242e6fe1de81ad7bfcccaaf606179afb" + integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw== + dependencies: + "@types/d3-time" "*" + +"@types/d3-shape@^1": + version "1.3.12" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.12.tgz#8f2f9f7a12e631ce6700d6d55b84795ce2c8b259" + integrity sha512-8oMzcd4+poSLGgV0R1Q1rOlx/xdmozS4Xab7np0eamFFUYq71AU9pOCJEFnkXW2aI/oXdVYJzw6pssbSut7Z9Q== + dependencies: + "@types/d3-path" "^1" + +"@types/d3-shape@^3.1.0", "@types/d3-shape@^3.1.6": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.7.tgz#2b7b423dc2dfe69c8c93596e673e37443348c555" + integrity sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time@*", "@types/d3-time@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.4.tgz#8472feecd639691450dd8000eb33edd444e1323f" + integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g== + +"@types/d3-timer@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + "@types/debug@^4.0.0": version "4.1.12" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" @@ -2281,9 +2491,9 @@ integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== "@types/node@*": - version "24.10.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.1.tgz#91e92182c93db8bd6224fca031e2370cef9a8f01" - integrity sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ== + version "25.0.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.2.tgz#411f9dd6cb2bf5ee46aed7199a9f85ca6b068b4e" + integrity sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA== dependencies: undici-types "~7.16.0" @@ -2293,9 +2503,9 @@ integrity sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw== "@types/papaparse@^5.3.9": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-5.5.0.tgz#318d929d134de45a36f6ac50b2406fdcee3a6c83" - integrity sha512-GVs5iMQmUr54BAZYYkByv8zPofFxmyxUpISPb2oh8sayR3+1zbxasrOvoKiHJ/nnoq/uULuPsu1Lze1EkagVFg== + version "5.5.2" + resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-5.5.2.tgz#cb450a1cd183deb43728e593eb1ac2da60f4fa4d" + integrity sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA== dependencies: "@types/node" "*" @@ -2337,9 +2547,9 @@ integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== "@types/react@*": - version "19.2.6" - resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.6.tgz#d27db1ff45012d53980f5589fda925278e1249ca" - integrity sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w== + version "19.2.7" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.7.tgz#84e62c0f23e8e4e5ac2cadcea1ffeacccae7f62f" + integrity sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg== dependencies: csstype "^3.2.2" @@ -2369,101 +2579,99 @@ integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": - version "8.47.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz#c53edeec13a79483f4ca79c298d5231b02e9dc17" - integrity sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA== + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz#a6ce899690542e2affa9543306d2d3935740abb7" + integrity sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.47.0" - "@typescript-eslint/type-utils" "8.47.0" - "@typescript-eslint/utils" "8.47.0" - "@typescript-eslint/visitor-keys" "8.47.0" - graphemer "^1.4.0" + "@typescript-eslint/scope-manager" "8.50.0" + "@typescript-eslint/type-utils" "8.50.0" + "@typescript-eslint/utils" "8.50.0" + "@typescript-eslint/visitor-keys" "8.50.0" ignore "^7.0.0" natural-compare "^1.4.0" ts-api-utils "^2.1.0" "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": - version "8.47.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.47.0.tgz#51b14ab2be2057ec0f57073b9ff3a9c078b0a964" - integrity sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ== - dependencies: - "@typescript-eslint/scope-manager" "8.47.0" - "@typescript-eslint/types" "8.47.0" - "@typescript-eslint/typescript-estree" "8.47.0" - "@typescript-eslint/visitor-keys" "8.47.0" + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.50.0.tgz#c35b28f686dbe08e81b9d6208ebc08912549f4ba" + integrity sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q== + dependencies: + "@typescript-eslint/scope-manager" "8.50.0" + "@typescript-eslint/types" "8.50.0" + "@typescript-eslint/typescript-estree" "8.50.0" + "@typescript-eslint/visitor-keys" "8.50.0" debug "^4.3.4" -"@typescript-eslint/project-service@8.47.0": - version "8.47.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.47.0.tgz#b8afc65e0527568018af911b702dcfbfdca16471" - integrity sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA== +"@typescript-eslint/project-service@8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.50.0.tgz#1422366b7cc11fef8c6d87770884e608093423a4" + integrity sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ== dependencies: - "@typescript-eslint/tsconfig-utils" "^8.47.0" - "@typescript-eslint/types" "^8.47.0" + "@typescript-eslint/tsconfig-utils" "^8.50.0" + "@typescript-eslint/types" "^8.50.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@8.47.0": - version "8.47.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz#d1c36a973a5499fed3a99e2e6a66aec5c9b1e542" - integrity sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg== +"@typescript-eslint/scope-manager@8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz#e0d6c838dc9044bc679724611b138cb34c81bddf" + integrity sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A== dependencies: - "@typescript-eslint/types" "8.47.0" - "@typescript-eslint/visitor-keys" "8.47.0" + "@typescript-eslint/types" "8.50.0" + "@typescript-eslint/visitor-keys" "8.50.0" -"@typescript-eslint/tsconfig-utils@8.47.0", "@typescript-eslint/tsconfig-utils@^8.47.0": - version "8.47.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz#4f178b62813538759e0989dd081c5474fad39b84" - integrity sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g== +"@typescript-eslint/tsconfig-utils@8.50.0", "@typescript-eslint/tsconfig-utils@^8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz#5c17537ad4c8a13bf6d7393035edaf91a1e13191" + integrity sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w== -"@typescript-eslint/type-utils@8.47.0": - version "8.47.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz#b9b0141d99bd5bece3811d7eee68a002597ffa55" - integrity sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A== +"@typescript-eslint/type-utils@8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz#feb6f54f876980a258b14f1cb033f54fc545d37b" + integrity sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw== dependencies: - "@typescript-eslint/types" "8.47.0" - "@typescript-eslint/typescript-estree" "8.47.0" - "@typescript-eslint/utils" "8.47.0" + "@typescript-eslint/types" "8.50.0" + "@typescript-eslint/typescript-estree" "8.50.0" + "@typescript-eslint/utils" "8.50.0" debug "^4.3.4" ts-api-utils "^2.1.0" -"@typescript-eslint/types@8.47.0", "@typescript-eslint/types@^8.47.0": - version "8.47.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.47.0.tgz#c7fc9b6642d03505f447a8392934b9d1850de5af" - integrity sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A== +"@typescript-eslint/types@8.50.0", "@typescript-eslint/types@^8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.50.0.tgz#ad8f1ad88ae0096f548c9cdf60da9b92832db96e" + integrity sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w== -"@typescript-eslint/typescript-estree@8.47.0": - version "8.47.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz#86416dad58db76c4b3bd6a899b1381f9c388489a" - integrity sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg== +"@typescript-eslint/typescript-estree@8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz#2871d36617f81a127db905fa91b16d1a0251411b" + integrity sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ== dependencies: - "@typescript-eslint/project-service" "8.47.0" - "@typescript-eslint/tsconfig-utils" "8.47.0" - "@typescript-eslint/types" "8.47.0" - "@typescript-eslint/visitor-keys" "8.47.0" + "@typescript-eslint/project-service" "8.50.0" + "@typescript-eslint/tsconfig-utils" "8.50.0" + "@typescript-eslint/types" "8.50.0" + "@typescript-eslint/visitor-keys" "8.50.0" debug "^4.3.4" - fast-glob "^3.3.2" - is-glob "^4.0.3" minimatch "^9.0.4" semver "^7.6.0" + tinyglobby "^0.2.15" ts-api-utils "^2.1.0" -"@typescript-eslint/utils@8.47.0": - version "8.47.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.47.0.tgz#d6c30690431dbfdab98fc027202af12e77c91419" - integrity sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ== +"@typescript-eslint/utils@8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.50.0.tgz#107f20a5747eab5db988c5f6ad462b59851cdd1f" + integrity sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg== dependencies: "@eslint-community/eslint-utils" "^4.7.0" - "@typescript-eslint/scope-manager" "8.47.0" - "@typescript-eslint/types" "8.47.0" - "@typescript-eslint/typescript-estree" "8.47.0" + "@typescript-eslint/scope-manager" "8.50.0" + "@typescript-eslint/types" "8.50.0" + "@typescript-eslint/typescript-estree" "8.50.0" -"@typescript-eslint/visitor-keys@8.47.0": - version "8.47.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz#35f36ed60a170dfc9d4d738e78387e217f24c29f" - integrity sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ== +"@typescript-eslint/visitor-keys@8.50.0": + version "8.50.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz#79d1c95474e08f844dbe13370715cfb9b7e21363" + integrity sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q== dependencies: - "@typescript-eslint/types" "8.47.0" + "@typescript-eslint/types" "8.50.0" eslint-visitor-keys "^4.2.1" "@uiw/react-json-view@^2.0.0-alpha.30": @@ -2573,6 +2781,11 @@ resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777" integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== +"@vvo/tzdb@^6.198.0": + version "6.198.0" + resolved "https://registry.yarnpkg.com/@vvo/tzdb/-/tzdb-6.198.0.tgz#bcb33c581aec4f1258ad96c963ac96327ce960e3" + integrity sha512-bNRWBhWYl0edVgyX6AYbhoCM2tk2lXJjGCyO2VDc2xn6Dw8dLd7WGj2DDXkVOkmOIQTNjEAcxrEpIzz5pWVwFg== + "@yr/monotone-cubic-spline@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz#7272d89f8e4f6fb7a1600c28c378cc18d3b577b9" @@ -2834,10 +3047,10 @@ base64-js@^1.1.2, base64-js@^1.3.0: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -baseline-browser-mapping@^2.8.25: - version "2.8.30" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz#5c7420acc2fd20f3db820a40c6521590a671d137" - integrity sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA== +baseline-browser-mapping@^2.9.0: + version "2.9.8" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.8.tgz#04fb5c10ff9c7a1b04ac08cfdfc3b10942a8ac72" + integrity sha512-Y1fOuNDowLfgKOypdc9SPABfoWXuZHBOyCS4cD52IeZBhr4Md6CLLs6atcxVrzRmQ06E7hSlm5bHHApPKR/byA== bidi-js@^1.0.2: version "1.0.3" @@ -2888,15 +3101,15 @@ browserify-zlib@^0.2.0: pako "~1.0.5" browserslist@^4.24.0, browserslist@^4.28.0: - version "4.28.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.0.tgz#9cefece0a386a17a3cd3d22ebf67b9deca1b5929" - integrity sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ== + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== dependencies: - baseline-browser-mapping "^2.8.25" - caniuse-lite "^1.0.30001754" - electron-to-chromium "^1.5.249" + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" node-releases "^2.0.27" - update-browserslist-db "^1.1.4" + update-browserslist-db "^1.2.0" buffer-from@~0.1.1: version "0.1.2" @@ -2939,10 +3152,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001754: - version "1.0.30001756" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz#fe80104631102f88e58cad8aa203a2c3e5ec9ebd" - integrity sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A== +caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001759: + version "1.0.30001760" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz#bdd1960fafedf8d5f04ff16e81460506ff9b798f" + integrity sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw== canvg@^3.0.11: version "3.0.11" @@ -3205,6 +3418,131 @@ csstype@^3.0.2, csstype@^3.1.3, csstype@^3.2.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== +"d3-array@1 - 2", d3-array@2: + version "2.12.1" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81" + integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ== + dependencies: + internmap "^1.0.0" + +"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6: + version "3.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +"d3-color@1 - 3", d3-color@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-ease@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +"d3-format@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +d3-format@^1.4.4: + version "1.4.5" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4" + integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ== + +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-path@1: + version "1.0.9" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" + integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== + +d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-sankey@^0.12.3: + version "0.12.3" + resolved "https://registry.yarnpkg.com/d3-sankey/-/d3-sankey-0.12.3.tgz#b3c268627bd72e5d80336e8de6acbfec9d15d01d" + integrity sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ== + dependencies: + d3-array "1 - 2" + d3-shape "^1.2.0" + +d3-scale-chromatic@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" + integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + +d3-scale@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +d3-shape@^1.2.0: + version "1.3.7" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" + integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== + dependencies: + d3-path "1" + +d3-shape@^3.1.0, d3-shape@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4": + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +d3-time-format@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6" + integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag== + dependencies: + d3-time "1 - 2" + +"d3-time@1 - 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682" + integrity sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ== + dependencies: + d3-array "2" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +d3-timer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" @@ -3256,6 +3594,11 @@ debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4 dependencies: ms "^2.1.3" +decimal.js-light@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + decode-named-character-reference@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz#25c32ae6dd5e21889549d40f676030e9514cc0ed" @@ -3397,9 +3740,9 @@ domhandler@^5.0.2, domhandler@^5.0.3: domelementtype "^2.3.0" dompurify@^3.2.4: - version "3.3.0" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.3.0.tgz#aaaadbb83d87e1c2fbb066452416359e5b62ec97" - integrity sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ== + version "3.3.1" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.3.1.tgz#c7e1ddebfe3301eacd6c0c12a4af284936dbbb86" + integrity sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q== optionalDependencies: "@types/trusted-types" "^2.0.7" @@ -3444,10 +3787,10 @@ duplexer2@^0.1.2: dependencies: readable-stream "^2.0.2" -electron-to-chromium@^1.5.249: - version "1.5.259" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz#d4393167ec14c5a046cebaec3ddf3377944ce965" - integrity sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ== +electron-to-chromium@^1.5.263: + version "1.5.267" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz#5d84f2df8cdb6bfe7e873706bb21bd4bfb574dc7" + integrity sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw== eml-parse-js@^1.2.0-beta.0: version "1.2.0-beta.1" @@ -3499,10 +3842,10 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0: - version "1.24.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.0.tgz#c44732d2beb0acc1ed60df840869e3106e7af328" - integrity sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== +es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0, es-abstract@^1.24.1: + version "1.24.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.1.tgz#f0c131ed5ea1bb2411134a8dd94def09c46c7899" + integrity sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw== dependencies: array-buffer-byte-length "^1.0.2" arraybuffer.prototype.slice "^1.0.4" @@ -3570,25 +3913,25 @@ es-errors@^1.3.0: integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-iterator-helpers@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz#d1dd0f58129054c0ad922e6a9a1e65eef435fe75" - integrity sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w== + version "1.2.2" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz#d979a9f686e2b0b72f88dbead7229924544720bc" + integrity sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w== dependencies: call-bind "^1.0.8" - call-bound "^1.0.3" + call-bound "^1.0.4" define-properties "^1.2.1" - es-abstract "^1.23.6" + es-abstract "^1.24.1" es-errors "^1.3.0" - es-set-tostringtag "^2.0.3" + es-set-tostringtag "^2.1.0" function-bind "^1.1.2" - get-intrinsic "^1.2.6" + get-intrinsic "^1.3.0" globalthis "^1.0.4" gopd "^1.2.0" has-property-descriptors "^1.0.2" has-proto "^1.2.0" has-symbols "^1.1.0" internal-slot "^1.1.0" - iterator.prototype "^1.1.4" + iterator.prototype "^1.1.5" safe-array-concat "^1.1.3" es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: @@ -3598,7 +3941,7 @@ es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: dependencies: es-errors "^1.3.0" -es-set-tostringtag@^2.0.3, es-set-tostringtag@^2.1.0: +es-set-tostringtag@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== @@ -3625,9 +3968,9 @@ es-to-primitive@^1.3.0: is-symbol "^1.0.4" es-toolkit@^1.39.3: - version "1.42.0" - resolved "https://registry.yarnpkg.com/es-toolkit/-/es-toolkit-1.42.0.tgz#c9e87c7e2d4759ca26887814e6bc780cf4747fc5" - integrity sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA== + version "1.43.0" + resolved "https://registry.yarnpkg.com/es-toolkit/-/es-toolkit-1.43.0.tgz#2c278d55ffeb30421e6e73a009738ed37b10ef61" + integrity sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA== escalade@^3.2.0: version "3.2.0" @@ -3871,6 +4214,11 @@ eventemitter3@^2.0.3: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" integrity sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg== +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -3908,6 +4256,11 @@ fast-equals@^4.0.3: resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-4.0.3.tgz#72884cc805ec3c6679b99875f6b7654f39f0e8c7" integrity sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg== +fast-equals@^5.3.3: + version "5.4.0" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.4.0.tgz#b60073b8764f27029598447f05773c7534ba7f1e" + integrity sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw== + fast-glob@3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" @@ -3919,17 +4272,6 @@ fast-glob@3.3.1: merge2 "^1.3.0" micromatch "^4.0.4" -fast-glob@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.8" - fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -4186,11 +4528,6 @@ gopd@^1.0.1, gopd@^1.2.0: resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - gray-matter@4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" @@ -4311,14 +4648,14 @@ hast-util-to-jsx-runtime@^2.0.0: vfile-message "^4.0.0" hast-util-to-parse5@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz#477cd42d278d4f036bc2ea58586130f6f39ee6ed" - integrity sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw== + version "8.0.1" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz#95aa391cc0514b4951418d01c883d1038af42f5d" + integrity sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA== dependencies: "@types/hast" "^3.0.0" comma-separated-tokens "^2.0.0" devlop "^1.0.0" - property-information "^6.0.0" + property-information "^7.0.0" space-separated-tokens "^2.0.0" web-namespaces "^2.0.0" zwitch "^2.0.0" @@ -4456,11 +4793,16 @@ ignore@^7.0.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== -immer@^10.0.3: +immer@^10.1.1: version "10.2.0" resolved "https://registry.yarnpkg.com/immer/-/immer-10.2.0.tgz#88a4ce06a1af64172d254b70f7cb04df51c871b1" integrity sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw== +immer@^11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/immer/-/immer-11.0.1.tgz#2191adadc17afc94553bfb7a4c0ca95e55ec2489" + integrity sha512-naDCyggtcBWANtIrjQEajhhBEuL9b0Zg4zmlWK2CzS6xCWSE39/vvf4LqnMjUAWHBhot4m9MHCM/Z+mfWhUkiA== + import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" @@ -4493,6 +4835,16 @@ internal-slot@^1.1.0: hasown "^2.0.2" side-channel "^1.1.0" +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + +internmap@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" + integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== + iobuffer@^5.3.2: version "5.4.0" resolved "https://registry.yarnpkg.com/iobuffer/-/iobuffer-5.4.0.tgz#f85dff957fd0579257472f0a4cfe5ed3430e63e1" @@ -4792,7 +5144,7 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -iterator.prototype@^1.1.4: +iterator.prototype@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39" integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== @@ -4836,7 +5188,7 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: +js-yaml@^4.1.0, js-yaml@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== @@ -4994,9 +5346,9 @@ locate-path@^6.0.0: p-locate "^5.0.0" lodash-es@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" - integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + version "4.17.22" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.22.tgz#eb7d123ec2470d69b911abe34f85cb694849b346" + integrity sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q== lodash.debounce@^4.0.8: version "4.0.8" @@ -5247,9 +5599,9 @@ mdast-util-phrasing@^4.0.0: unist-util-is "^6.0.0" mdast-util-to-hast@^13.0.0: - version "13.2.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4" - integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== + version "13.2.1" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053" + integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA== dependencies: "@types/hast" "^3.0.0" "@types/mdast" "^4.0.0" @@ -5806,7 +6158,7 @@ micromark@^4.0.0: micromark-util-symbol "^2.0.0" micromark-util-types "^2.0.0" -micromatch@^4.0.4, micromatch@^4.0.8: +micromatch@^4.0.4: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -5896,24 +6248,24 @@ natural-compare@^1.4.0: integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== next@^15.2.2: - version "15.5.6" - resolved "https://registry.yarnpkg.com/next/-/next-15.5.6.tgz#16d9d1e9ba2e8caf82ba15e060a12286cd25db30" - integrity sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ== + version "15.5.9" + resolved "https://registry.yarnpkg.com/next/-/next-15.5.9.tgz#1b80d05865cc27e710fb4dcfc6fd9e726ed12ad4" + integrity sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg== dependencies: - "@next/env" "15.5.6" + "@next/env" "15.5.9" "@swc/helpers" "0.5.15" caniuse-lite "^1.0.30001579" postcss "8.4.31" styled-jsx "5.1.6" optionalDependencies: - "@next/swc-darwin-arm64" "15.5.6" - "@next/swc-darwin-x64" "15.5.6" - "@next/swc-linux-arm64-gnu" "15.5.6" - "@next/swc-linux-arm64-musl" "15.5.6" - "@next/swc-linux-x64-gnu" "15.5.6" - "@next/swc-linux-x64-musl" "15.5.6" - "@next/swc-win32-arm64-msvc" "15.5.6" - "@next/swc-win32-x64-msvc" "15.5.6" + "@next/swc-darwin-arm64" "15.5.7" + "@next/swc-darwin-x64" "15.5.7" + "@next/swc-linux-arm64-gnu" "15.5.7" + "@next/swc-linux-arm64-musl" "15.5.7" + "@next/swc-linux-x64-gnu" "15.5.7" + "@next/swc-linux-x64-musl" "15.5.7" + "@next/swc-win32-arm64-msvc" "15.5.7" + "@next/swc-win32-x64-msvc" "15.5.7" sharp "^0.34.3" no-case@^3.0.4: @@ -6251,11 +6603,6 @@ property-information@^5.0.0: dependencies: xtend "^4.0.0" -property-information@^6.0.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" - integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== - property-information@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d" @@ -6321,7 +6668,7 @@ prosemirror-inputrules@^1.4.0: prosemirror-state "^1.0.0" prosemirror-transform "^1.0.0" -prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.2.2: +prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.2.2, prosemirror-keymap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz#c0f6ab95f75c0b82c97e44eb6aaf29cbfc150472" integrity sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw== @@ -6348,7 +6695,7 @@ prosemirror-menu@^1.2.4: prosemirror-history "^1.0.0" prosemirror-state "^1.0.0" -prosemirror-model@^1.0.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.24.1, prosemirror-model@^1.25.0: +prosemirror-model@^1.0.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.24.1, prosemirror-model@^1.25.0, prosemirror-model@^1.25.4: version "1.25.4" resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.25.4.tgz#8ebfbe29ecbee9e5e2e4048c4fe8e363fcd56e7c" integrity sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA== @@ -6371,7 +6718,7 @@ prosemirror-schema-list@^1.5.0: prosemirror-state "^1.0.0" prosemirror-transform "^1.7.3" -prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3: +prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3, prosemirror-state@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.4.tgz#72b5e926f9e92dcee12b62a05fcc8a2de3bf5b39" integrity sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw== @@ -6381,15 +6728,15 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3: prosemirror-view "^1.27.0" prosemirror-tables@^1.6.4: - version "1.8.1" - resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.8.1.tgz#896a234e3e18240b629b747a871369dae78c8a9a" - integrity sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug== + version "1.8.3" + resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.8.3.tgz#b10b08518b2aa3aecb8cc64303b833bd816d443c" + integrity sha512-wbqCR/RlRPRe41a4LFtmhKElzBEfBTdtAYWNIGHM6X2e24NN/MTNUKyXjjphfAfdQce37Kh/5yf765mLPYDe7Q== dependencies: - prosemirror-keymap "^1.2.2" - prosemirror-model "^1.25.0" - prosemirror-state "^1.4.3" - prosemirror-transform "^1.10.3" - prosemirror-view "^1.39.1" + prosemirror-keymap "^1.2.3" + prosemirror-model "^1.25.4" + prosemirror-state "^1.4.4" + prosemirror-transform "^1.10.5" + prosemirror-view "^1.41.4" prosemirror-trailing-node@^3.0.0: version "3.0.0" @@ -6399,17 +6746,17 @@ prosemirror-trailing-node@^3.0.0: "@remirror/core-constants" "3.0.0" escape-string-regexp "^4.0.0" -prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.10.3, prosemirror-transform@^1.7.3: +prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.10.5, prosemirror-transform@^1.7.3: version "1.10.5" resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.10.5.tgz#4cf9fe5dcbdbfebd62499f24386e7cec9bc9979b" integrity sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw== dependencies: prosemirror-model "^1.21.0" -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.38.1, prosemirror-view@^1.39.1: - version "1.41.3" - resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.41.3.tgz#753a37ebe172a3e313ad2c3d85496f9ed1b2c256" - integrity sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ== +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.38.1, prosemirror-view@^1.41.4: + version "1.41.4" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.41.4.tgz#4e1b3e90accc0eebe3bddb497a40ce54e4de722d" + integrity sha512-WkKgnyjNncri03Gjaz3IFWvCAE94XoiEgvtr0/r2Xw7R8/IjK3sKLSiDoCHWcsXSAinVaKlGRZDvMCsF1kbzjA== dependencies: prosemirror-model "^1.20.0" prosemirror-state "^1.0.0" @@ -6545,9 +6892,9 @@ react-fast-compare@^2.0.1: integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== react-grid-layout@^1.5.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-1.5.2.tgz#d5a6775446ce540c0df3985c41b5d64622fc6f87" - integrity sha512-vT7xmQqszTT+sQw/LfisrEO4le1EPNnSEMVHy6sBZyzS3yGkMywdOd+5iEFFwQwt0NSaGkxuRmYwa1JsP6OJdw== + version "1.5.3" + resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-1.5.3.tgz#802de040616c443b0162d73cecde792cb5beeaa2" + integrity sha512-KaG6IbjD6fYhagUtIvOzhftXG+ViKZjCjADe86X1KHl7C/dsBN2z0mi14nbvZKTkp0RKiil9RPcJBgq3LnoA8g== dependencies: clsx "^2.1.1" fast-equals "^4.0.3" @@ -6557,9 +6904,9 @@ react-grid-layout@^1.5.0: resize-observer-polyfill "^1.5.1" react-hook-form@^7.53.0: - version "7.66.1" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.66.1.tgz#da56644b4ad9bd31254662d3242975681e29558c" - integrity sha512-2KnjpgG2Rhbi+CIiIBQQ9Df6sMGH5ExNyFl4Hw9qO7pIqMBR8Bvu9RQyjl3JM4vehzCh9soiNUM/xYMswb2EiA== + version "7.68.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.68.0.tgz#733c6871fa4ec5e5bfb13e7650a3a912eafe1721" + integrity sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q== react-hot-toast@2.6.0: version "2.6.0" @@ -6595,9 +6942,9 @@ react-is@^17.0.2: integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== react-is@^19.1.1, react-is@^19.2.0: - version "19.2.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.0.tgz#ddc3b4a4e0f3336c3847f18b806506388d7b9973" - integrity sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA== + version "19.2.3" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.3.tgz#eec2feb69c7fb31f77d0b5c08c10ae1c88886b29" + integrity sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA== react-leaflet-markercluster@^5.0.0-rc.0: version "5.0.0-rc.0" @@ -6655,7 +7002,7 @@ react-quill@^2.0.0: lodash "^4.17.4" quill "^1.3.7" -react-redux@9.2.0: +"react-redux@8.x.x || 9.x.x", react-redux@9.2.0: version "9.2.0" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.2.0.tgz#96c3ab23fb9a3af2cb4654be4b51c989e32366f5" integrity sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g== @@ -6714,10 +7061,15 @@ react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" +react-virtualized-auto-sizer@^1.0.26: + version "1.0.26" + resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.26.tgz#e9470ef6a778dc4f1d5fd76305fa2d8b610c357a" + integrity sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A== + react-virtuoso@^4.12.8: - version "4.14.1" - resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.14.1.tgz#78a5e796a3f9ec501499f01962ec6fc7eed77d8d" - integrity sha512-NRUF1ak8lY+Tvc6WN9cce59gU+lilzVtOozP+pm9J7iHshLGGjsiAB4rB2qlBPHjFbcXOQpT+7womNHGDUql8w== + version "4.17.0" + resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.17.0.tgz#e81f2da99792cfd9317e910b243d847ebeb09248" + integrity sha512-od3pi2v13v31uzn5zPXC2u3ouISFCVhjFVFch2VvS2Cx7pWA2F1aJa3XhNTN2F07M3lhfnMnsmGeH+7wZICr7w== react-window@^2.1.0: version "2.2.3" @@ -6761,6 +7113,23 @@ readable-stream@~1.0.17, readable-stream@~1.0.27-1: isarray "0.0.1" string_decoder "~0.10.x" +recharts@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-3.6.0.tgz#403f0606581153601857e46733277d1411633df3" + integrity sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg== + dependencies: + "@reduxjs/toolkit" "1.x.x || 2.x.x" + clsx "^2.1.1" + decimal.js-light "^2.5.1" + es-toolkit "^1.39.3" + eventemitter3 "^5.0.1" + immer "^10.1.1" + react-redux "8.x.x || 9.x.x" + reselect "5.1.1" + tiny-invariant "^1.3.3" + use-sync-external-store "^1.2.2" + victory-vendor "^37.0.2" + redux-devtools-extension@2.13.9: version "2.13.9" resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz#6b764e8028b507adcb75a1cae790f71e6be08ae7" @@ -6919,7 +7288,7 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -reselect@^5.1.0, reselect@^5.1.1: +reselect@5.1.1, reselect@^5.1.0, reselect@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.1.tgz#c766b1eb5d558291e5e550298adb0becc24bb72e" integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w== @@ -7472,7 +7841,7 @@ tiny-inflate@^1.0.0, tiny-inflate@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== -tiny-invariant@^1.0.6: +tiny-invariant@^1.0.6, tiny-invariant@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== @@ -7482,7 +7851,7 @@ tiny-warning@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tinyglobby@^0.2.13: +tinyglobby@^0.2.13, tinyglobby@^0.2.15: version "0.2.15" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== @@ -7780,10 +8149,10 @@ unrs-resolver@^1.6.2: "@unrs/resolver-binding-win32-ia32-msvc" "1.11.1" "@unrs/resolver-binding-win32-x64-msvc" "1.11.1" -update-browserslist-db@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz#7802aa2ae91477f255b86e0e46dbc787a206ad4a" - integrity sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A== +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== dependencies: escalade "^3.2.0" picocolors "^1.1.1" @@ -7795,12 +8164,17 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +use-debounce@^10.0.4: + version "10.0.6" + resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-10.0.6.tgz#e05060a5e561432ec740c653698f3eb162bd28ec" + integrity sha512-C5OtPyhAZgVoteO9heXMTdW7v/IbFI+8bSVKYCJrSmiWWCLsbUxiBSp4t9v0hNBTGY97bT72ydDIDyGSFWfwXg== + use-memo-one@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99" integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ== -use-sync-external-store@^1.4.0, use-sync-external-store@^1.6.0: +use-sync-external-store@^1.2.2, use-sync-external-store@^1.4.0, use-sync-external-store@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== @@ -7869,6 +8243,26 @@ vfile@^6.0.0: "@types/unist" "^3.0.0" vfile-message "^4.0.0" +victory-vendor@^37.0.2: + version "37.3.6" + resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-37.3.6.tgz#401ac4b029a0b3d33e0cba8e8a1d765c487254da" + integrity sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ== + dependencies: + "@types/d3-array" "^3.0.3" + "@types/d3-ease" "^3.0.0" + "@types/d3-interpolate" "^3.0.1" + "@types/d3-scale" "^4.0.2" + "@types/d3-shape" "^3.1.0" + "@types/d3-time" "^3.0.0" + "@types/d3-timer" "^3.0.0" + d3-array "^3.1.6" + d3-ease "^3.0.1" + d3-interpolate "^3.0.1" + d3-scale "^4.0.2" + d3-shape "^3.1.0" + d3-time "^3.0.0" + d3-timer "^3.0.1" + vite-compatible-readable-stream@^3.6.1: version "3.6.1" resolved "https://registry.yarnpkg.com/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz#27267aebbdc9893c0ddf65a421279cbb1e31d8cd"