diff --git a/.claude/worktrees/blissful-golick-d405ab b/.claude/worktrees/blissful-golick-d405ab
new file mode 160000
index 000000000000..0710355e2ada
--- /dev/null
+++ b/.claude/worktrees/blissful-golick-d405ab
@@ -0,0 +1 @@
+Subproject commit 0710355e2adac37fffe4c7eef48d6f2c3a04993d
diff --git a/.github/workflows/Node_Project_Check.yml b/.github/workflows/Node_Project_Check.yml
index 1116a307ceb7..3347edfbe84d 100644
--- a/.github/workflows/Node_Project_Check.yml
+++ b/.github/workflows/Node_Project_Check.yml
@@ -20,7 +20,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v6.3.0
+ uses: actions/setup-node@v6.4.0
with:
node-version: ${{ matrix.node-version }}
- name: Install and Build Test
diff --git a/.github/workflows/cipp_dev_build.yml b/.github/workflows/cipp_dev_build.yml
index f0131a4f3692..394b8bc794f1 100644
--- a/.github/workflows/cipp_dev_build.yml
+++ b/.github/workflows/cipp_dev_build.yml
@@ -26,7 +26,7 @@ jobs:
echo "node_version=$node_sanitized_version" >> $GITHUB_OUTPUT
- name: Set up Node.js
- uses: actions/setup-node@v6.3.0
+ uses: actions/setup-node@v6.4.0
with:
node-version: ${{ steps.get_node_version.outputs.node_version }}
diff --git a/.github/workflows/cipp_frontend_build.yml b/.github/workflows/cipp_frontend_build.yml
index 8dedfa497669..5c8d7230e9d3 100644
--- a/.github/workflows/cipp_frontend_build.yml
+++ b/.github/workflows/cipp_frontend_build.yml
@@ -26,7 +26,7 @@ jobs:
echo "node_version=$node_sanitized_version" >> $GITHUB_OUTPUT
- name: Set up Node.js
- uses: actions/setup-node@v6.3.0
+ uses: actions/setup-node@v6.4.0
with:
node-version: ${{ steps.get_node_version.outputs.node_version }}
diff --git a/next.config.js b/next.config.js
index 97685f34f91e..f2bc28bcd2bb 100644
--- a/next.config.js
+++ b/next.config.js
@@ -16,6 +16,7 @@ const config = {
'mui-tiptap',
'recharts',
'@react-pdf/renderer',
+ 'lodash',
],
webpackMemoryOptimizations: true,
preloadEntriesOnStart: false,
diff --git a/package.json b/package.json
index ee84a95ad7bf..a4402ea681b5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "cipp",
- "version": "10.4.3",
+ "version": "10.4.5",
"author": "CIPP Contributors",
"homepage": "https://cipp.app/",
"bugs": {
@@ -43,37 +43,35 @@
"@reduxjs/toolkit": "^2.11.2",
"@tanstack/query-sync-storage-persister": "^5.90.25",
"@tanstack/react-query": "^5.96.2",
- "@tanstack/react-query-devtools": "^5.51.11",
- "@tanstack/react-query-persist-client": "^5.76.0",
+ "@tanstack/react-query-devtools": "^5.96.2",
+ "@tanstack/react-query-persist-client": "^5.96.2",
"@tanstack/react-table": "^8.19.2",
"@tiptap/core": "^3.4.1",
"@tiptap/extension-heading": "^3.4.1",
- "@tiptap/extension-image": "^3.20.5",
"@tiptap/extension-table": "^3.19.0",
"@tiptap/pm": "^3.22.3",
"@tiptap/react": "^3.20.5",
"@tiptap/starter-kit": "^3.20.5",
- "@uiw/react-json-view": "^2.0.0-alpha.41",
"@vvo/tzdb": "^6.198.0",
"apexcharts": "5.10.4",
"axios": "1.15.0",
"date-fns": "4.1.0",
"diff": "^8.0.3",
+ "dompurify": "^3.4.2",
"eml-parse-js": "^1.2.0-beta.0",
"export-to-csv": "^1.3.0",
"formik": "2.4.9",
"gray-matter": "4.0.3",
- "i18next": "25.8.18",
"javascript-time-ago": "^2.6.2",
"jspdf": "^4.2.0",
"jspdf-autotable": "^5.0.7",
"leaflet": "^1.9.4",
- "leaflet-defaulticon-compatibility": "^0.1.2",
"leaflet.markercluster": "^1.5.3",
+ "lodash": "^4.18.1",
"lodash.isequal": "4.5.0",
"material-react-table": "^3.0.1",
"monaco-editor": "^0.55.1",
- "mui-tiptap": "^1.29.1",
+ "mui-tiptap": "^1.30.0",
"next": "^16.2.2",
"nprogress": "0.2.0",
"numeral": "2.0.6",
@@ -82,15 +80,12 @@
"react": "19.2.5",
"react-apexcharts": "2.1.0",
"react-beautiful-dnd": "13.1.1",
- "react-copy-to-clipboard": "^5.1.0",
"react-dom": "19.2.5",
"react-dropzone": "15.0.0",
"react-error-boundary": "^6.1.1",
- "react-grid-layout": "^2.2.3",
"react-hook-form": "^7.72.0",
"react-hot-toast": "2.6.0",
"react-html-parser": "^2.0.2",
- "react-i18next": "16.6.5",
"react-leaflet": "5.0.0",
"react-leaflet-markercluster": "^5.0.0-rc.0",
"react-markdown": "10.1.0",
@@ -101,28 +96,23 @@
"react-syntax-highlighter": "^16.1.0",
"react-time-ago": "^7.3.3",
"react-virtuoso": "^4.18.5",
- "react-window": "^2.2.7",
- "recharts": "^3.7.0",
+ "recharts": "^3.8.1",
"redux": "5.0.1",
- "redux-devtools-extension": "2.13.9",
"redux-persist": "^6.0.0",
- "redux-thunk": "3.1.0",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.0",
+ "remark-parse": "^11.0.0",
"simplebar": "6.3.3",
"simplebar-react": "3.3.2",
"stylis-plugin-rtl": "2.1.1",
- "typescript": "5.9.3",
+ "unified": "^11.0.5",
"yup": "1.7.1"
},
"devDependencies": {
"@svgr/webpack": "8.1.0",
- "@types/react": "^19.2.14",
- "@types/react-dom": "^19.2.3",
"eslint": "^9.39.4",
"eslint-config-next": "^16.2.3",
"eslint-config-prettier": "^10.1.8",
- "prettier": "^3.8.1",
- "prettier-eslint": "^16.4.2"
+ "prettier": "^3.8.1"
}
}
diff --git a/public/assets/integrations/autotask.png b/public/assets/integrations/autotask.png
new file mode 100644
index 000000000000..cf2940427613
Binary files /dev/null and b/public/assets/integrations/autotask.png differ
diff --git a/public/assets/integrations/connectwise.png b/public/assets/integrations/connectwise.png
new file mode 100644
index 000000000000..ef1dfd1234f0
Binary files /dev/null and b/public/assets/integrations/connectwise.png differ
diff --git a/public/assets/integrations/kaseya.svg b/public/assets/integrations/kaseya.svg
new file mode 100644
index 000000000000..7e34c610d76a
--- /dev/null
+++ b/public/assets/integrations/kaseya.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/manifest.json b/public/manifest.json
index 2cc60cd8b5a7..42f5d73ea6af 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -1,15 +1,26 @@
{
- "short_name": "Carpatin",
- "name": "Carpatin",
+ "short_name": "CIPP",
+ "name": "CIPP - CyberDrian Improved Partner Portal",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
+ },
+ {
+ "src": "android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
}
],
- "start_url": ".",
+ "start_url": "/",
+ "scope": "/",
"display": "standalone",
- "theme_color": "#000000",
+ "theme_color": "#ffffff",
"background_color": "#ffffff"
-}
\ No newline at end of file
+}
diff --git a/public/sw.js b/public/sw.js
new file mode 100644
index 000000000000..a5b7af04ecf4
--- /dev/null
+++ b/public/sw.js
@@ -0,0 +1,8 @@
+// Minimal service worker to satisfy Chrome's installability criteria.
+// This does NOT cache anything or provide offline support — it simply
+// passes all requests through to the network so Chrome treats the site
+// as an installable web app.
+
+self.addEventListener('install', () => self.skipWaiting())
+self.addEventListener('activate', (event) => event.waitUntil(self.clients.claim()))
+self.addEventListener('fetch', () => {})
diff --git a/public/version.json b/public/version.json
index 521d35e0af9b..a09d0fcf2ccd 100644
--- a/public/version.json
+++ b/public/version.json
@@ -1,3 +1,3 @@
{
- "version": "10.4.3"
+ "version": "10.4.5"
}
diff --git a/src/components/CippCards/CippDomainCards.jsx b/src/components/CippCards/CippDomainCards.jsx
index 9268fcd08f96..fede72bd1347 100644
--- a/src/components/CippCards/CippDomainCards.jsx
+++ b/src/components/CippCards/CippDomainCards.jsx
@@ -470,6 +470,13 @@ export const CippDomainCards = ({ domain: propDomain = "", fullwidth = false })
waiting: !!domain,
});
+ const { data: autoDiscoverData, isFetching: autoDiscoverLoading } = ApiGetCall({
+ url: "/api/ListDomainHealth",
+ queryKey: `autodiscover-${domain}`,
+ data: { Domain: domain, Action: "ReadAutoDiscover" },
+ waiting: !!domain,
+ });
+
const { data: httpsData, isFetching: httpsLoading } = ApiGetCall({
url: "/api/ListDomainHealth",
queryKey: `https-${domain}-${subdomains}`,
@@ -684,6 +691,26 @@ export const CippDomainCards = ({ domain: propDomain = "", fullwidth = false })
}
/>
+
+
+
+ AutoDiscover ({autoDiscoverData?.RecordType || "None"}):
+
+
+
+
+ }
+ />
+
{enableHttps && (
Disconnect all current sessions
Remove all MFA methods for the user
Disable all inbox rules for the user
+ Disable OneDrive sharing
- );
+ )
}
diff --git a/src/components/CippCards/CippStandardsDialog.jsx b/src/components/CippCards/CippStandardsDialog.jsx
index 86de00f07d92..0e006ef43615 100644
--- a/src/components/CippCards/CippStandardsDialog.jsx
+++ b/src/components/CippCards/CippStandardsDialog.jsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
-import _ from 'lodash'
+import { get } from 'lodash'
import {
Dialog,
DialogTitle,
@@ -311,7 +311,7 @@ export const CippStandardsDialog = ({ open, onClose, standardsData, currentTenan
{info.addedComponent.map((component, componentIndex) => {
- const value = _.get(templateItem, component.name)
+ const value = get(templateItem, component.name)
let displayValue = 'N/A'
if (value) {
@@ -427,7 +427,7 @@ export const CippStandardsDialog = ({ open, onClose, standardsData, currentTenan
let extractedValue = null
// Try direct access first
- componentValue = _.get(config, component.name)
+ componentValue = get(config, component.name)
// If direct access fails and component name contains dots (nested structure)
if (
@@ -441,7 +441,7 @@ export const CippStandardsDialog = ({ open, onClose, standardsData, currentTenan
if (pathParts[0] === 'standards' && config.standards) {
// Remove 'standards.' prefix and try to find the value in config.standards
const nestedPath = pathParts.slice(1).join('.')
- extractedValue = _.get(config.standards, nestedPath)
+ extractedValue = get(config.standards, nestedPath)
// If still not found, try alternative nested structures
// Some standards have double nesting like: config.standards.StandardName.fieldName
@@ -452,7 +452,7 @@ export const CippStandardsDialog = ({ open, onClose, standardsData, currentTenan
) {
const standardName = pathParts[1]
const fieldPath = pathParts.slice(2).join('.')
- extractedValue = _.get(
+ extractedValue = get(
config.standards,
`${standardName}.${fieldPath}`
)
diff --git a/src/components/CippCards/CippUniversalSearchV2.jsx b/src/components/CippCards/CippUniversalSearchV2.jsx
index 28a53f35ef82..070396f0a56e 100644
--- a/src/components/CippCards/CippUniversalSearchV2.jsx
+++ b/src/components/CippCards/CippUniversalSearchV2.jsx
@@ -348,6 +348,16 @@ export const CippUniversalSearchV2 = React.forwardRef(
router.push(
`/identity/administration/groups/group?groupId=${itemData.id}&tenantFilter=${tenantDomain}`,
);
+ } else if (searchType === "Applications") {
+ if (match.Type === "Apps") {
+ router.push(
+ `/tenant/administration/applications/app-registration?appId=${itemData.appId || itemData.id}&tenantFilter=${tenantDomain}`,
+ );
+ } else {
+ router.push(
+ `/tenant/administration/applications/enterprise-app?spId=${itemData.id}&tenantFilter=${tenantDomain}`,
+ );
+ }
} else if (searchType === "Pages") {
router.push(match.path, undefined, { shallow: true });
}
@@ -389,6 +399,11 @@ export const CippUniversalSearchV2 = React.forwardRef(
icon: "Group",
onClick: () => handleTypeChange("Groups"),
},
+ {
+ label: "Applications",
+ icon: "Apps",
+ onClick: () => handleTypeChange("Applications"),
+ },
{
label: "BitLocker",
icon: "FilePresent",
@@ -730,6 +745,20 @@ const Results = ({
)}
>
)}
+ {searchType === "Applications" && (
+ <>
+ {itemData.appId && (
+
+ {highlightMatch(itemData.appId || "")}
+
+ )}
+ {itemData.publisherName && (
+
+ {highlightMatch(itemData.publisherName || "")}
+
+ )}
+ >
+ )}
a.displayName.localeCompare(b.displayName));
- if (!_.isEqual(selectedApp, newApps)) {
+ if (!isEqual(selectedApp, newApps)) {
setSelectedApp(newApps); // Prevent unnecessary updates
}
diff --git a/src/components/CippComponents/CippAppTemplateDrawer.jsx b/src/components/CippComponents/CippAppTemplateDrawer.jsx
index e9db8701f3d6..4727f66988f2 100644
--- a/src/components/CippComponents/CippAppTemplateDrawer.jsx
+++ b/src/components/CippComponents/CippAppTemplateDrawer.jsx
@@ -892,6 +892,21 @@ export const CippAppTemplateDrawer = ({
/>
+
+
+
+
+
{/* Add App Button */}
{applicationType?.value && (
diff --git a/src/components/CippComponents/CippApplicationDeployDrawer.jsx b/src/components/CippComponents/CippApplicationDeployDrawer.jsx
index 99c2cd52d249..a68ade232835 100644
--- a/src/components/CippComponents/CippApplicationDeployDrawer.jsx
+++ b/src/components/CippComponents/CippApplicationDeployDrawer.jsx
@@ -1,119 +1,119 @@
-import React, { useEffect, useCallback, useState } from "react";
-import { Divider, Button, Alert, CircularProgress } from "@mui/material";
-import { Grid } from "@mui/system";
-import { useForm, useWatch } from "react-hook-form";
-import { Add } from "@mui/icons-material";
-import { CippOffCanvas } from "./CippOffCanvas";
-import CippFormComponent from "./CippFormComponent";
-import { CippFormTenantSelector } from "./CippFormTenantSelector";
-import { CippFormCondition } from "./CippFormCondition";
-import { CippApiResults } from "./CippApiResults";
-import languageList from "../../data/languageList.json";
-import { ApiPostCall } from "../../api/ApiCall";
+import React, { useEffect, useCallback, useState } from 'react'
+import { Divider, Button, Alert, CircularProgress } from '@mui/material'
+import { Grid } from '@mui/system'
+import { useForm, useWatch } from 'react-hook-form'
+import { Add } from '@mui/icons-material'
+import { CippOffCanvas } from './CippOffCanvas'
+import CippFormComponent from './CippFormComponent'
+import { CippFormTenantSelector } from './CippFormTenantSelector'
+import { CippFormCondition } from './CippFormCondition'
+import { CippApiResults } from './CippApiResults'
+import languageList from '../../data/languageList.json'
+import { ApiPostCall } from '../../api/ApiCall'
export const CippApplicationDeployDrawer = ({
- buttonText = "Add Application",
+ buttonText = 'Add Application',
requiredPermissions = [],
PermissionButton = Button,
}) => {
- const [drawerVisible, setDrawerVisible] = useState(false);
+ const [drawerVisible, setDrawerVisible] = useState(false)
const formControl = useForm({
- mode: "onChange",
- });
+ mode: 'onChange',
+ })
const selectedTenants = useWatch({
control: formControl.control,
- name: "selectedTenants",
- });
+ name: 'selectedTenants',
+ })
const applicationType = useWatch({
control: formControl.control,
- name: "appType",
- });
+ name: 'appType',
+ })
const searchQuerySelection = useWatch({
control: formControl.control,
- name: "packageSearch",
- });
+ name: 'packageSearch',
+ })
const updateSearchSelection = useCallback(
(searchQuerySelection) => {
if (searchQuerySelection) {
- formControl.setValue("packagename", searchQuerySelection.value.packagename);
- formControl.setValue("applicationName", searchQuerySelection.value.applicationName);
- formControl.setValue("description", searchQuerySelection.value.description);
+ formControl.setValue('packagename', searchQuerySelection.value.packagename)
+ formControl.setValue('applicationName', searchQuerySelection.value.applicationName)
+ formControl.setValue('description', searchQuerySelection.value.description)
searchQuerySelection.value.customRepo
- ? formControl.setValue("customRepo", searchQuerySelection.value.customRepo)
- : null;
+ ? formControl.setValue('customRepo', searchQuerySelection.value.customRepo)
+ : null
}
},
- [formControl.setValue],
- );
+ [formControl.setValue]
+ )
useEffect(() => {
- updateSearchSelection(searchQuerySelection);
- }, [updateSearchSelection, searchQuerySelection]);
+ updateSearchSelection(searchQuerySelection)
+ }, [updateSearchSelection, searchQuerySelection])
const postUrl = {
- mspApp: "/api/AddMSPApp",
- StoreApp: "/api/AddStoreApp",
- winGetApp: "/api/AddwinGetApp",
- chocolateyApp: "/api/AddChocoApp",
- officeApp: "/api/AddOfficeApp",
- win32ScriptApp: "/api/AddWin32ScriptApp",
- };
+ mspApp: '/api/AddMSPApp',
+ StoreApp: '/api/AddStoreApp',
+ winGetApp: '/api/AddwinGetApp',
+ chocolateyApp: '/api/AddChocoApp',
+ officeApp: '/api/AddOfficeApp',
+ win32ScriptApp: '/api/AddWin32ScriptApp',
+ }
const ChocosearchResults = ApiPostCall({
urlFromData: true,
- });
+ })
const winGetSearchResults = ApiPostCall({
urlFromData: true,
- });
+ })
const deployApplication = ApiPostCall({
urlFromData: true,
- relatedQueryKeys: ["Queued Applications"],
- });
+ relatedQueryKeys: ['Queued Applications'],
+ })
const searchApp = (searchText, type) => {
- if (type === "choco") {
+ if (type === 'choco') {
ChocosearchResults.mutate({
url: `/api/ListAppsRepository`,
data: { search: searchText },
queryKey: `SearchApp-${searchText}-${type}`,
- });
+ })
}
- if (type === "StoreApp") {
+ if (type === 'StoreApp') {
winGetSearchResults.mutate({
url: `/api/ListPotentialApps`,
- data: { searchString: searchText, type: "WinGet" },
+ data: { searchString: searchText, type: 'WinGet' },
queryKey: `SearchApp-${searchText}-${type}`,
- });
+ })
}
- };
+ }
const handleSubmit = () => {
- const formData = formControl.getValues();
- const formattedData = { ...formData };
- formattedData.tenantFilter = "allTenants"; //added to prevent issues with location check. temp fix
+ const formData = formControl.getValues()
+ const formattedData = { ...formData }
+ formattedData.tenantFilter = 'allTenants' //added to prevent issues with location check. temp fix
formattedData.selectedTenants = selectedTenants.map((tenant) => ({
defaultDomainName: tenant.value,
customerId: tenant.addedFields.customerId,
- }));
+ }))
deployApplication.mutate({
url: postUrl[applicationType?.value],
data: formattedData,
- relatedQueryKeys: ["Queued Applications"],
- });
- };
+ relatedQueryKeys: ['Queued Applications'],
+ })
+ }
const handleCloseDrawer = () => {
- setDrawerVisible(false);
- formControl.reset();
- };
+ setDrawerVisible(false)
+ formControl.reset()
+ }
return (
<>
@@ -130,7 +130,7 @@ export const CippApplicationDeployDrawer = ({
onClose={handleCloseDrawer}
size="xl"
footer={
-
+
+
+ Clear
+
+
+
+
+ )}
+
+ {/* ── Tab 1: Guided Filter ── */}
+ {tabValue === 1 && (
+
+
+
+ Search the local container log files directly. Logs are rotated by size and
+ retained on disk. Use “Search All Files” to search across rotated log
+ files.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {(Array.isArray(timeRange) ? timeRange[0]?.value : timeRange) === "custom" && (
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ }>
+ Search Logs
+
+
+ Clear
+
+
+
+
+ )}
+
+
+
+ );
+};
+
+const Page = () => {
+ const [apiFilter, setApiFilter] = useState(null);
+ const queryKey = JSON.stringify(apiFilter);
+
+ return (
+
+
+
+
+
+ }
+ clearOnError={true}
+ offCanvas={{
+ size: "lg",
+ children: (row) => {
+ const levelColor = getLevelColor(row.Level);
+ return (
+
+
+
+
+
+
+ {row.Timestamp}
+
+
+
+
+
+ Message
+
+
+
+ {row.Message}
+
+
+
+ {row.Raw && row.Raw !== row.Message && (
+
+
+ Raw Log Line
+
+
+
+ {row.Raw}
+
+
+
+ )}
+
+
+ );
+ },
+ }}
+ title="Container Logs"
+ tenantInTitle={false}
+ apiDataKey="Results"
+ apiUrl={apiFilter ? "/api/ListContainerLogs" : "/api/ListEmptyResults"}
+ apiData={apiFilter}
+ queryKey={queryKey}
+ simpleColumns={["Timestamp", "Level", "Message"]}
+ actions={[]}
+ />
+ );
+};
+
+Page.getLayout = (page) => {page};
+
+export default Page;
diff --git a/src/pages/cipp/advanced/super-admin/cipp-users.js b/src/pages/cipp/advanced/super-admin/cipp-users.js
new file mode 100644
index 000000000000..8fe35569ef16
--- /dev/null
+++ b/src/pages/cipp/advanced/super-admin/cipp-users.js
@@ -0,0 +1,33 @@
+import { TabbedLayout } from "../../../../layouts/TabbedLayout";
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import tabOptions from "./tabOptions";
+import CippPageCard from "../../../../components/CippCards/CippPageCard";
+import { CippUserManagement } from "../../../../components/CippSettings/CippUserManagement";
+import { CardContent, Stack, Alert } from "@mui/material";
+
+const Page = () => {
+ return (
+
+
+
+
+ Manage users who can access CIPP. Add users by their email address (UPN) and assign
+ them built-in or custom roles. Users not in this list will still be able to log in if
+ "Allow All Tenant Users" is enabled, but they will only receive default
+ (authenticated) permissions. Role resolution also considers Entra group mappings
+ configured on the CIPP Roles page.
+
+
+
+
+
+ );
+};
+
+Page.getLayout = (page) => (
+
+ {page}
+
+);
+
+export default Page;
diff --git a/src/pages/cipp/advanced/super-admin/container.js b/src/pages/cipp/advanced/super-admin/container.js
new file mode 100644
index 000000000000..9fb8a701174b
--- /dev/null
+++ b/src/pages/cipp/advanced/super-admin/container.js
@@ -0,0 +1,21 @@
+import { Container } from "@mui/material";
+import { TabbedLayout } from "../../../../layouts/TabbedLayout";
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import tabOptions from "./tabOptions";
+import { CippContainerManagement } from "../../../../components/CippSettings/CippContainerManagement";
+
+const Page = () => {
+ return (
+
+
+
+ );
+};
+
+Page.getLayout = (page) => (
+
+ {page}
+
+);
+
+export default Page;
diff --git a/src/pages/cipp/advanced/super-admin/sso.js b/src/pages/cipp/advanced/super-admin/sso.js
new file mode 100644
index 000000000000..fc5b112f3f1c
--- /dev/null
+++ b/src/pages/cipp/advanced/super-admin/sso.js
@@ -0,0 +1,26 @@
+import { Container } from "@mui/material";
+import { Grid } from "@mui/system";
+import { TabbedLayout } from "../../../../layouts/TabbedLayout";
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import tabOptions from "./tabOptions";
+import { CippSSOSettings } from "../../../../components/CippSettings/CippSSOSettings";
+
+const Page = () => {
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+Page.getLayout = (page) => (
+
+ {page}
+
+);
+
+export default Page;
diff --git a/src/pages/cipp/advanced/super-admin/tabOptions.json b/src/pages/cipp/advanced/super-admin/tabOptions.json
index 672df76996c6..fbccb6b73c55 100644
--- a/src/pages/cipp/advanced/super-admin/tabOptions.json
+++ b/src/pages/cipp/advanced/super-admin/tabOptions.json
@@ -22,5 +22,17 @@
{
"label": "SAM App Permissions",
"path": "/cipp/advanced/super-admin/sam-app-permissions"
+ },
+ {
+ "label": "CIPP Users",
+ "path": "/cipp/advanced/super-admin/cipp-users"
+ },
+ {
+ "label": "SSO",
+ "path": "/cipp/advanced/super-admin/sso"
+ },
+ {
+ "label": "Container Management",
+ "path": "/cipp/advanced/super-admin/container"
}
]
diff --git a/src/pages/cipp/integrations/index.js b/src/pages/cipp/integrations/index.js
index 60ee764853b4..6d3f24f86ee4 100644
--- a/src/pages/cipp/integrations/index.js
+++ b/src/pages/cipp/integrations/index.js
@@ -68,45 +68,83 @@ const Page = () => {
status = 'Enabled'
}
- return (
-
-
-
+ {extension.comingSoon && (
+
+ Coming Soon
+
+ )}
+
+
-
- {extension?.logo && (
+ {extension?.logo && (
+
+ )}
+
+
+ {extension.description}
+
+
+
+
+
+ {extension.comingSoon ? (
+ <>
- )}
-
- {extension.description}
-
-
-
-
-
+ Coming Soon
+ >
+ ) : (
+ <>
{integrations.isSuccess ? (
{
{integrations.isSuccess ? status : 'Loading'}
-
-
-
-
+ >
+ )}
+
+
+
+ )
+
+ return (
+
+ {extension.comingSoon ? (
+ cardContent
+ ) : (
+
+ {cardContent}
+
+ )}
)
})}
diff --git a/src/pages/cipp/settings/features.js b/src/pages/cipp/settings/features.js
index 15b6fd3a111e..d630e93eb2ba 100644
--- a/src/pages/cipp/settings/features.js
+++ b/src/pages/cipp/settings/features.js
@@ -59,6 +59,7 @@ const Page = () => {
offCanvas={offCanvas}
simpleColumns={simpleColumns}
tenantInTitle={false}
+ dataFilter={(row) => !row.Hidden}
/>
);
};
diff --git a/src/pages/email/administration/hve-accounts/index.js b/src/pages/email/administration/hve-accounts/index.js
new file mode 100644
index 000000000000..265884d44650
--- /dev/null
+++ b/src/pages/email/administration/hve-accounts/index.js
@@ -0,0 +1,204 @@
+import { Layout as DashboardLayout } from '../../../../layouts/index.js'
+import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx'
+import { CippHVEUserDrawer } from '../../../../components/CippComponents/CippHVEUserDrawer.jsx'
+import { useCippReportDB } from '../../../../components/CippComponents/CippReportDBControls'
+import { Stack } from '@mui/system'
+import { TrashIcon } from '@heroicons/react/24/outline'
+import {
+ Edit,
+ AlternateEmail,
+ Receipt,
+ RemoveCircleOutline,
+ Reply,
+} from '@mui/icons-material'
+
+const Page = () => {
+ const pageTitle = 'HVE Accounts'
+
+ const reportDB = useCippReportDB({
+ apiUrl: '/api/ListHVEAccounts',
+ queryKey: 'ListHVEAccounts',
+ cacheName: 'HVEAccounts',
+ syncTitle: 'Sync HVE Accounts',
+ allowToggle: true,
+ defaultCached: true,
+ })
+
+ const actions = [
+ {
+ label: 'Edit Display Name',
+ type: 'POST',
+ url: '/api/ExecHVEUser',
+ icon: ,
+ data: { Identity: 'primarySmtpAddress', Action: 'Edit' },
+ fields: [
+ {
+ type: 'textField',
+ name: 'DisplayName',
+ label: 'Display Name',
+ },
+ ],
+ confirmText: 'Update display name for [primarySmtpAddress]',
+ hideBulk: true,
+ },
+ {
+ label: 'Set Reply-To Address',
+ type: 'POST',
+ url: '/api/ExecHVEUser',
+ icon: ,
+ data: { Identity: 'primarySmtpAddress', Action: 'Edit' },
+ fields: [
+ {
+ type: 'textField',
+ name: 'ReplyTo',
+ label: 'Reply-To Address',
+ placeholder: 'e.g. replies@contoso.com (leave empty to clear)',
+ },
+ ],
+ confirmText: 'Update reply-to address for [primarySmtpAddress]',
+ hideBulk: true,
+ },
+ {
+ label: 'Change Primary SMTP Address',
+ type: 'POST',
+ url: '/api/ExecHVEUser',
+ icon: ,
+ data: { Identity: 'primarySmtpAddress', Action: 'Edit' },
+ fields: [
+ {
+ type: 'textField',
+ name: 'username',
+ label: 'Username (local part)',
+ placeholder: 'e.g. hveaccount01',
+ },
+ {
+ type: 'autoComplete',
+ name: 'domain',
+ label: 'Domain',
+ api: {
+ url: '/api/ListGraphRequest',
+ dataKey: 'Results',
+ queryKey: 'listDomains-hve',
+ labelField: (option) => option.id,
+ valueField: 'id',
+ addedField: {
+ isDefault: 'isDefault',
+ isVerified: 'isVerified',
+ },
+ data: {
+ Endpoint: 'domains',
+ manualPagination: true,
+ $count: true,
+ $top: 99,
+ },
+ dataFilter: (domains) =>
+ domains
+ .filter((d) => d?.addedFields?.isVerified === true)
+ .sort((a, b) => {
+ if (a.addedFields?.isDefault === true) return -1
+ if (b.addedFields?.isDefault === true) return 1
+ return 0
+ }),
+ },
+ },
+ ],
+ confirmText: 'Change primary SMTP address for [primarySmtpAddress]',
+ hideBulk: true,
+ },
+ {
+ label: 'Assign Billing Policy',
+ type: 'POST',
+ url: '/api/ExecHVEUser',
+ icon: ,
+ data: { Identity: 'primarySmtpAddress', Action: 'AssignBillingPolicy' },
+ fields: [
+ {
+ type: 'autoComplete',
+ name: 'BillingPolicyId',
+ label: 'Billing Policy',
+ multiple: false,
+ api: {
+ url: '/api/ListHVEAccounts',
+ queryKey: 'ListHVEBillingPolicies',
+ labelField: (option) =>
+ `${option.Name || option.BillingPolicyName || option.BillingPolicyId} (${option.BillingPolicyId || option.Guid || option.Identity})`,
+ valueField: (option) => option.BillingPolicyId || option.Guid || option.Identity,
+ data: {
+ ListBillingPolicies: true,
+ },
+ },
+ },
+ ],
+ confirmText: 'Assign billing policy to [primarySmtpAddress]. Current policy: [BillingPolicyName]',
+ hideBulk: true,
+ },
+ {
+ label: 'Remove Billing Policy',
+ type: 'POST',
+ url: '/api/ExecHVEUser',
+ icon: ,
+ data: { Identity: 'primarySmtpAddress', Action: 'RemoveBillingPolicy' },
+ confirmText:
+ 'Remove billing policy [BillingPolicyName] from [primarySmtpAddress]?',
+ condition: (row) => row.BillingPolicyName && row.BillingPolicyName !== 'None',
+ hideBulk: true,
+ },
+ {
+ label: 'Delete HVE Account',
+ type: 'POST',
+ icon: ,
+ url: '/api/ExecHVEUser',
+ data: { Identity: 'primarySmtpAddress', Action: 'Remove' },
+ confirmText: 'Are you sure you want to delete HVE account [primarySmtpAddress]?',
+ multiPost: false,
+ },
+ ]
+
+ const offCanvas = {
+ extendedInfoFields: [
+ 'displayName',
+ 'primarySmtpAddress',
+ 'Alias',
+ 'AdditionalEmailAddresses',
+ 'BillingPolicyName',
+ 'BillingPolicyId',
+ 'WhenCreated',
+ 'ExternalDirectoryObjectId',
+ ],
+ actions: actions,
+ }
+
+ const simpleColumns = [
+ ...reportDB.cacheColumns.filter((c) => c === 'Tenant'),
+ 'displayName',
+ 'primarySmtpAddress',
+ 'Alias',
+ 'WhenCreated',
+ 'AdditionalEmailAddresses',
+ ...reportDB.cacheColumns.filter((c) => c !== 'Tenant'),
+ ]
+
+ return (
+ <>
+
+
+ {reportDB.controls}
+
+ }
+ />
+ {reportDB.syncDialog}
+ >
+ )
+}
+
+Page.getLayout = (page) => {page}
+
+export default Page
diff --git a/src/pages/email/administration/mailbox-rules/index.js b/src/pages/email/administration/mailbox-rules/index.js
index 4b9a9ece88cb..98f0d076caff 100644
--- a/src/pages/email/administration/mailbox-rules/index.js
+++ b/src/pages/email/administration/mailbox-rules/index.js
@@ -1,90 +1,97 @@
-import { Layout as DashboardLayout } from "../../../../layouts/index.js";
-import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
-import { getCippTranslation } from "../../../../utils/get-cipp-translation";
-import { getCippFormatting } from "../../../../utils/get-cipp-formatting";
-import { CippPropertyListCard } from "../../../../components/CippCards/CippPropertyListCard";
-import { Block, PlayArrow, DeleteForever } from "@mui/icons-material";
-import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls";
+import { Layout as DashboardLayout } from '../../../../layouts/index.js'
+import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx'
+import { getCippTranslation } from '../../../../utils/get-cipp-translation'
+import { getCippFormatting } from '../../../../utils/get-cipp-formatting'
+import { CippPropertyListCard } from '../../../../components/CippCards/CippPropertyListCard'
+import { Block, PlayArrow, DeleteForever } from '@mui/icons-material'
+import { useCippReportDB } from '../../../../components/CippComponents/CippReportDBControls'
const Page = () => {
- const pageTitle = "Mailbox Rules";
+ const pageTitle = 'Mailbox Rules'
const reportDB = useCippReportDB({
- apiUrl: "/api/ListMailboxRules",
- queryKey: "ListMailboxRules",
- cacheName: "Mailboxes",
- syncTitle: "Sync Mailbox Rules",
- syncData: { Types: "Rules" },
+ apiUrl: '/api/ListMailboxRules',
+ queryKey: 'ListMailboxRules',
+ cacheName: 'Mailboxes',
+ syncTitle: 'Sync Mailbox Rules',
+ syncData: { Types: 'Rules' },
allowToggle: false,
defaultCached: true,
- });
+ })
const simpleColumns = [
- ...reportDB.cacheColumns.filter((c) => c === "Tenant"),
- "UserPrincipalName",
- "Name",
- "Priority",
- "Enabled",
- "From",
- ...reportDB.cacheColumns.filter((c) => c !== "Tenant"),
- ];
+ ...reportDB.cacheColumns.filter((c) => c === 'Tenant'),
+ 'UserPrincipalName',
+ 'Name',
+ 'Priority',
+ 'Enabled',
+ 'From',
+ ...reportDB.cacheColumns.filter((c) => c !== 'Tenant'),
+ ]
const actions = [
{
- label: "Enable Mailbox Rule",
- type: "POST",
+ label: 'Enable Mailbox Rule',
+ type: 'POST',
icon: ,
- url: "/api/ExecSetMailboxRule",
+ url: '/api/ExecSetMailboxRule',
data: {
- ruleId: "Identity",
- userPrincipalName: "OperationGuid",
- ruleName: "Name",
+ ruleId: 'Identity',
+ userPrincipalName: 'OperationGuid',
+ ruleName: 'Name',
Enable: true,
+ tenantFilter: 'Tenant',
},
condition: (row) => !row.Enabled,
- confirmText: "Are you sure you want to enable this mailbox rule?",
+ confirmText: 'Are you sure you want to enable this mailbox rule?',
multiPost: false,
},
{
- label: "Disable Mailbox Rule",
- type: "POST",
+ label: 'Disable Mailbox Rule',
+ type: 'POST',
icon: ,
- url: "/api/ExecSetMailboxRule",
+ url: '/api/ExecSetMailboxRule',
data: {
- ruleId: "Identity",
- userPrincipalName: "OperationGuid",
- ruleName: "Name",
+ ruleId: 'Identity',
+ userPrincipalName: 'OperationGuid',
+ ruleName: 'Name',
Disable: true,
+ tenantFilter: 'Tenant',
},
condition: (row) => row.Enabled,
- confirmText: "Are you sure you want to disable this mailbox rule?",
+ confirmText: 'Are you sure you want to disable this mailbox rule?',
multiPost: false,
},
{
- label: "Remove Mailbox Rule",
- type: "POST",
+ label: 'Remove Mailbox Rule',
+ type: 'POST',
icon: ,
- url: "/api/ExecRemoveMailboxRule",
- data: { ruleId: "Identity", userPrincipalName: "OperationGuid", ruleName: "Name" },
- confirmText: "Are you sure you want to remove this mailbox rule?",
+ url: '/api/ExecRemoveMailboxRule',
+ data: {
+ ruleId: 'Identity',
+ userPrincipalName: 'OperationGuid',
+ ruleName: 'Name',
+ tenantFilter: 'Tenant',
+ },
+ confirmText: 'Are you sure you want to remove this mailbox rule?',
multiPost: false,
},
- ];
+ ]
const offCanvas = {
children: (data) => {
const keys = Object.keys(data).filter(
- (key) => !key.includes("@odata") && !key.includes("@data"),
- );
- const properties = [];
+ (key) => !key.includes('@odata') && !key.includes('@data')
+ )
+ const properties = []
keys.forEach((key) => {
if (data[key] && data[key].length > 0) {
properties.push({
label: getCippTranslation(key),
value: getCippFormatting(data[key], key),
- });
+ })
}
- });
+ })
return (
{
actionItems={actions}
data={data}
/>
- );
+ )
},
- };
+ }
return (
<>
@@ -111,8 +118,8 @@ const Page = () => {
/>
{reportDB.syncDialog}
>
- );
-};
+ )
+}
-Page.getLayout = (page) => {page};
-export default Page;
+Page.getLayout = (page) => {page}
+export default Page
diff --git a/src/pages/email/administration/mailboxes/index.js b/src/pages/email/administration/mailboxes/index.js
index f0187d414983..01bc84afae65 100644
--- a/src/pages/email/administration/mailboxes/index.js
+++ b/src/pages/email/administration/mailboxes/index.js
@@ -1,7 +1,6 @@
import { Layout as DashboardLayout } from '../../../../layouts/index.js'
import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx'
import CippExchangeActions from '../../../../components/CippComponents/CippExchangeActions'
-import { CippHVEUserDrawer } from '../../../../components/CippComponents/CippHVEUserDrawer.jsx'
import { CippSharedMailboxDrawer } from '../../../../components/CippComponents/CippSharedMailboxDrawer.jsx'
import { useCippReportDB } from '../../../../components/CippComponents/CippReportDBControls'
import { Stack } from '@mui/system'
@@ -71,7 +70,6 @@ const Page = () => {
cardButton={
-
{reportDB.controls}
}
diff --git a/src/pages/email/administration/tenant-allow-block-list-templates/index.js b/src/pages/email/administration/tenant-allow-block-list-templates/index.js
index 85de23ce2ec4..4e945b486c4d 100644
--- a/src/pages/email/administration/tenant-allow-block-list-templates/index.js
+++ b/src/pages/email/administration/tenant-allow-block-list-templates/index.js
@@ -1,13 +1,26 @@
+import { useState } from 'react'
import { Layout as DashboardLayout } from '../../../../layouts/index.js'
import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx'
-import { Delete } from '@mui/icons-material'
+import { Delete, Edit } from '@mui/icons-material'
import CippJsonView from '../../../../components/CippFormPages/CippJSONView'
import { CippTenantAllowBlockListTemplateDrawer } from '../../../../components/CippComponents/CippTenantAllowBlockListTemplateDrawer.jsx'
const Page = () => {
const pageTitle = 'Tenant Allow/Block List Templates'
+ const [editDrawerVisible, setEditDrawerVisible] = useState(false)
+ const [editData, setEditData] = useState(null)
const actions = [
+ {
+ label: 'Edit Template',
+ noConfirm: true,
+ customFunction: (row) => {
+ setEditData(row)
+ setEditDrawerVisible(true)
+ },
+ icon: ,
+ color: 'primary',
+ },
{
label: 'Delete Template',
type: 'POST',
@@ -36,18 +49,26 @@ const Page = () => {
]
return (
-
- }
- />
+ <>
+ }
+ />
+ {
+ setEditDrawerVisible(visible)
+ if (!visible) setEditData(null)
+ }}
+ />
+ >
)
}
diff --git a/src/pages/email/tools/message-trace/index.js b/src/pages/email/tools/message-trace/index.js
index 56ccf9bcd20a..d5876859b182 100644
--- a/src/pages/email/tools/message-trace/index.js
+++ b/src/pages/email/tools/message-trace/index.js
@@ -347,6 +347,5 @@ const Page = () => {
);
};
-Page.getLayout = (page) => {page};
-
+Page.getLayout = (page) => {page};
export default Page;
diff --git a/src/pages/endpoint/MEM/assignment-filters/index.js b/src/pages/endpoint/MEM/assignment-filters/index.js
index 462647494c98..bedf0ef1ada4 100644
--- a/src/pages/endpoint/MEM/assignment-filters/index.js
+++ b/src/pages/endpoint/MEM/assignment-filters/index.js
@@ -5,11 +5,19 @@ import Link from "next/link";
import { TrashIcon } from "@heroicons/react/24/outline";
import { Edit, Add, Book } from "@mui/icons-material";
import { Stack } from "@mui/system";
-import { useSettings } from "../../../../hooks/use-settings";
+import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls";
const Page = () => {
const pageTitle = "Assignment Filters";
- const { currentTenant } = useSettings();
+
+ const reportDB = useCippReportDB({
+ apiUrl: "/api/ListAssignmentFilters",
+ queryKey: "assignment-filters",
+ cacheName: "IntuneAssignmentFilters",
+ syncTitle: "Sync Assignment Filters Report",
+ allowToggle: true,
+ defaultCached: false,
+ });
const actions = [
{
@@ -62,28 +70,35 @@ const Page = () => {
actions: actions,
};
+ const simpleColumns = [
+ ...reportDB.cacheColumns,
+ "displayName",
+ "description",
+ "platform",
+ "assignmentFilterManagementType",
+ "rule",
+ ];
+
return (
-
- }>
- Add Assignment Filter
-
-
- }
- apiUrl="/api/ListAssignmentFilters"
- queryKey={`assignment-filters-${currentTenant}`}
- actions={actions}
- offCanvas={offCanvas}
- simpleColumns={[
- "displayName",
- "description",
- "platform",
- "assignmentFilterManagementType",
- "rule",
- ]}
- />
+ <>
+
+ }>
+ Add Assignment Filter
+
+ {reportDB.controls}
+
+ }
+ apiUrl={reportDB.resolvedApiUrl}
+ queryKey={reportDB.resolvedQueryKey}
+ actions={actions}
+ offCanvas={offCanvas}
+ simpleColumns={simpleColumns}
+ />
+ {reportDB.syncDialog}
+ >
);
};
diff --git a/src/pages/endpoint/MEM/compare-policies/index.js b/src/pages/endpoint/MEM/compare-policies/index.js
index da74c739462b..80b660e666a1 100644
--- a/src/pages/endpoint/MEM/compare-policies/index.js
+++ b/src/pages/endpoint/MEM/compare-policies/index.js
@@ -4,7 +4,7 @@ import { ApiPostCall } from "../../../../api/ApiCall";
import { CippFormComponent } from "../../../../components/CippComponents/CippFormComponent";
import { CippFormCondition } from "../../../../components/CippComponents/CippFormCondition";
import { CippFormTenantSelector } from "../../../../components/CippComponents/CippFormTenantSelector";
-import { CippCodeBlock } from "../../../../components/CippComponents/CippCodeBlock";
+import CippJsonView from "../../../../components/CippFormPages/CippJSONView";
import {
Box,
Button,
@@ -19,16 +19,12 @@ import {
TableHead,
TableRow,
Paper,
- Accordion,
- AccordionSummary,
- AccordionDetails,
Alert,
Stack,
Chip,
Skeleton,
} from "@mui/material";
import {
- ExpandMore as ExpandMoreIcon,
CompareArrows as CompareArrowsIcon,
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
@@ -359,14 +355,10 @@ const Page = () => {
return errData?.Results || compareApi.error?.message || "An error occurred";
}, [compareApi.isError, compareApi.error]);
- const sourceAJson = useMemo(
- () => (results?.sourceAData ? JSON.stringify(results.sourceAData, null, 2) : ""),
- [results?.sourceAData],
- );
- const sourceBJson = useMemo(
- () => (results?.sourceBData ? JSON.stringify(results.sourceBData, null, 2) : ""),
- [results?.sourceBData],
- );
+ const comparisonRows = useMemo(() => {
+ if (!Array.isArray(results?.Results)) return [];
+ return results.Results.filter(Boolean);
+ }, [results?.Results]);
return (
@@ -418,14 +410,14 @@ const Page = () => {
>
{results.identical
? "Policies are identical - no differences found."
- : `${results.Results?.length || 0} difference${results.Results?.length === 1 ? "" : "s"} found between policies.`}
+ : `${comparisonRows.length} difference${comparisonRows.length === 1 ? "" : "s"} found between policies.`}
A: {results.sourceALabel} — B:{" "}
{results.sourceBLabel}
- {!results.identical && results.Results?.length > 0 && (
+ {!results.identical && comparisonRows.length > 0 && (
@@ -437,7 +429,7 @@ const Page = () => {
- {results.Results.map((row, index) => (
+ {comparisonRows.map((row, index) => (
({
@@ -457,23 +449,17 @@ const Page = () => {
)}
-
- }>
- Source A Raw JSON — {results.sourceALabel}
-
-
-
-
-
-
-
- }>
- Source B Raw JSON — {results.sourceBLabel}
-
-
-
-
-
+
+
+
)}
diff --git a/src/pages/endpoint/MEM/devices/index.js b/src/pages/endpoint/MEM/devices/index.js
index e8e34e9338d5..087052560120 100644
--- a/src/pages/endpoint/MEM/devices/index.js
+++ b/src/pages/endpoint/MEM/devices/index.js
@@ -4,7 +4,8 @@ import { CippApiDialog } from "../../../../components/CippComponents/CippApiDial
import { useSettings } from "../../../../hooks/use-settings";
import { useDialog } from "../../../../hooks/use-dialog.js";
import { EyeIcon } from "@heroicons/react/24/outline";
-import { Box, Button } from "@mui/material";
+import { Button } from "@mui/material";
+import { Stack } from "@mui/system";
import {
Sync,
RestartAlt,
@@ -412,11 +413,11 @@ const Page = () => {
offCanvas={offCanvas}
simpleColumns={simpleColumns}
cardButton={
-
+
}>
Sync DEP
-
+
}
/>
{
const pageTitle = 'App Protection & Configuration Policies'
const cardButtonPermissions = ['Endpoint.MEM.ReadWrite']
const tenant = useSettings().currentTenant
+ const reportDB = useCippReportDB({
+ apiUrl: '/api/ListAppProtectionPolicies',
+ queryKey: 'ListAppProtectionPolicies',
+ cacheName: 'IntuneAppProtectionPolicies',
+ syncTitle: 'Sync App Protection Policies Report',
+ allowToggle: true,
+ defaultCached: false,
+ })
+
const actions = useCippIntunePolicyActions(tenant, 'URLName', {
templateData: {
ID: 'id',
@@ -31,6 +42,7 @@ const Page = () => {
}
const simpleColumns = [
+ ...reportDB.cacheColumns,
'displayName',
'PolicyTypeName',
'PolicyAssignment',
@@ -39,20 +51,27 @@ const Page = () => {
]
return (
-
- }
- />
+ <>
+
+
+ {reportDB.controls}
+
+ }
+ />
+ {reportDB.syncDialog}
+ >
)
}
diff --git a/src/pages/endpoint/MEM/list-compliance-policies/index.js b/src/pages/endpoint/MEM/list-compliance-policies/index.js
index b3394023c492..32574567a0ff 100644
--- a/src/pages/endpoint/MEM/list-compliance-policies/index.js
+++ b/src/pages/endpoint/MEM/list-compliance-policies/index.js
@@ -4,12 +4,23 @@ import { PermissionButton } from "../../../../utils/permissions.js";
import { CippPolicyDeployDrawer } from "../../../../components/CippComponents/CippPolicyDeployDrawer.jsx";
import { useSettings } from "../../../../hooks/use-settings.js";
import { useCippIntunePolicyActions } from "../../../../components/CippComponents/CippIntunePolicyActions.jsx";
+import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls";
+import { Stack } from "@mui/system";
const Page = () => {
const pageTitle = "Intune Compliance Policies";
const cardButtonPermissions = ["Endpoint.MEM.ReadWrite"];
const tenant = useSettings().currentTenant;
+ const reportDB = useCippReportDB({
+ apiUrl: "/api/ListCompliancePolicies",
+ queryKey: "ListCompliancePolicies",
+ cacheName: "IntuneCompliancePolicies",
+ syncTitle: "Sync Compliance Policies Report",
+ allowToggle: true,
+ defaultCached: false,
+ });
+
const actions = useCippIntunePolicyActions(tenant, "deviceCompliancePolicies", {
templateData: {
ID: "id",
@@ -29,6 +40,7 @@ const Page = () => {
};
const simpleColumns = [
+ ...reportDB.cacheColumns,
"displayName",
"PolicyTypeName",
"PolicyAssignment",
@@ -38,20 +50,27 @@ const Page = () => {
];
return (
-
- }
- />
+ <>
+
+
+ {reportDB.controls}
+
+ }
+ />
+ {reportDB.syncDialog}
+ >
);
};
diff --git a/src/pages/endpoint/MEM/list-policies/index.js b/src/pages/endpoint/MEM/list-policies/index.js
index dd241aa91016..22559f99097c 100644
--- a/src/pages/endpoint/MEM/list-policies/index.js
+++ b/src/pages/endpoint/MEM/list-policies/index.js
@@ -52,6 +52,7 @@ const Page = () => {
'lastModifiedDateTime',
]
+
return (
<>
{
const [codeContentChanged, setCodeContentChanged] = useState(false);
const [warnOpen, setWarnOpen] = useState(false);
const [currentScript, setCurrentScript] = useState(null);
+ const [scriptTenant, setScriptTenant] = useState(null);
+
+ const tenantFilter = useSettings().currentTenant;
+ const reportDB = useCippReportDB({
+ apiUrl: "/api/ListIntuneScript",
+ queryKey: "ListIntuneScript",
+ cacheName: "IntuneScripts",
+ syncTitle: "Sync Intune Scripts Report",
+ allowToggle: true,
+ defaultCached: false,
+ });
const dispatch = useDispatch();
@@ -48,17 +60,16 @@ const Page = () => {
: "powershell";
}, [currentScript?.scriptType]);
- const tenantFilter = useSettings().currentTenant;
const {
isLoading: scriptIsLoading,
isRefetching: scriptIsFetching,
refetch: scriptRefetch,
data,
} = useQuery({
- queryKey: ["script", { scriptId }],
+ queryKey: ["script", { scriptId, scriptTenant }],
queryFn: async () => {
const response = await fetch(
- `/api/EditIntuneScript?TenantFilter=${tenantFilter}&ScriptId=${scriptId}`
+ `/api/EditIntuneScript?TenantFilter=${scriptTenant || tenantFilter}&ScriptId=${scriptId}`
);
return response.json();
},
@@ -79,6 +90,7 @@ const Page = () => {
const handleScriptEdit = async (row, action) => {
setScriptId(row.id);
+ setScriptTenant(row?.Tenant || tenantFilter);
setCodeOpen(!codeOpen);
};
@@ -94,6 +106,7 @@ const Page = () => {
setCodeOpen(!codeOpen);
setCodeContentChanged(false);
setScriptId(null);
+ setScriptTenant(null);
setCodeContent("");
}
};
@@ -114,7 +127,7 @@ const Page = () => {
scriptType,
} = currentScript;
const patchData = {
- TenantFilter: tenantFilter,
+ TenantFilter: scriptTenant || tenantFilter,
ScriptId: id,
ScriptType: scriptType,
IntuneScript: JSON.stringify({
@@ -197,7 +210,7 @@ const Page = () => {
],
confirmText: 'Are you sure you want to assign "[displayName]" to all users?',
customDataformatter: (row, action, formData) => ({
- tenantFilter: tenantFilter,
+ tenantFilter: tenantFilter === "AllTenants" && row?.Tenant ? row.Tenant : tenantFilter,
ID: row?.id,
Type: getScriptEndpoint(row?.scriptType),
AssignTo: "allLicensedUsers",
@@ -223,7 +236,7 @@ const Page = () => {
],
confirmText: 'Are you sure you want to assign "[displayName]" to all devices?',
customDataformatter: (row, action, formData) => ({
- tenantFilter: tenantFilter,
+ tenantFilter: tenantFilter === "AllTenants" && row?.Tenant ? row.Tenant : tenantFilter,
ID: row?.id,
Type: getScriptEndpoint(row?.scriptType),
AssignTo: "AllDevices",
@@ -249,7 +262,7 @@ const Page = () => {
],
confirmText: 'Are you sure you want to assign "[displayName]" to all users and devices?',
customDataformatter: (row, action, formData) => ({
- tenantFilter: tenantFilter,
+ tenantFilter: tenantFilter === "AllTenants" && row?.Tenant ? row.Tenant : tenantFilter,
ID: row?.id,
Type: getScriptEndpoint(row?.scriptType),
AssignTo: "AllDevicesAndUsers",
@@ -305,7 +318,7 @@ const Page = () => {
customDataformatter: (row, action, formData) => {
const selectedGroups = Array.isArray(formData?.groupTargets) ? formData.groupTargets : [];
return {
- tenantFilter: tenantFilter,
+ tenantFilter: tenantFilter === "AllTenants" && row?.Tenant ? row.Tenant : tenantFilter,
ID: row?.id,
Type: getScriptEndpoint(row?.scriptType),
GroupIds: selectedGroups.map((group) => group.value).filter(Boolean),
@@ -354,6 +367,7 @@ const Page = () => {
};
const simpleColumns = [
+ ...reportDB.cacheColumns,
"scriptType",
"displayName",
"ScriptAssignment",
@@ -367,10 +381,12 @@ const Page = () => {
<>
+ {reportDB.syncDialog}
>
);
};
-Page.getLayout = (page) => {page};
+Page.getLayout = (page) => {page};
export default Page;
diff --git a/src/pages/endpoint/MEM/list-templates/index.js b/src/pages/endpoint/MEM/list-templates/index.js
index fcfc8aa81e5a..c1c81670d8b7 100644
--- a/src/pages/endpoint/MEM/list-templates/index.js
+++ b/src/pages/endpoint/MEM/list-templates/index.js
@@ -1,161 +1,251 @@
-import { Layout as DashboardLayout } from "../../../../layouts/index.js";
-import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
-import { PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
-import { Edit, GitHub, LocalOffer, LocalOfferOutlined, CopyAll } from "@mui/icons-material";
-import CippJsonView from "../../../../components/CippFormPages/CippJSONView";
-import { ApiGetCall } from "../../../../api/ApiCall";
-import { CippPolicyImportDrawer } from "../../../../components/CippComponents/CippPolicyImportDrawer.jsx";
-import { PermissionButton } from "../../../../utils/permissions.js";
+import { Layout as DashboardLayout } from '../../../../layouts/index.js'
+import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx'
+import { PencilIcon, TrashIcon } from '@heroicons/react/24/outline'
+import { Edit, GitHub, LocalOffer, LocalOfferOutlined, CopyAll } from '@mui/icons-material'
+import CippJsonView from '../../../../components/CippFormPages/CippJSONView'
+import { ApiGetCall } from '../../../../api/ApiCall'
+import { CippPolicyImportDrawer } from '../../../../components/CippComponents/CippPolicyImportDrawer.jsx'
+import { PermissionButton } from '../../../../utils/permissions.js'
+import {
+ Box,
+ Chip,
+ Link,
+ Stack,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+ Tooltip,
+ Typography,
+} from '@mui/material'
+import NextLink from 'next/link'
const Page = () => {
- const pageTitle = "Available Endpoint Manager Templates";
- const cardButtonPermissions = ["Endpoint.MEM.ReadWrite"];
+ const pageTitle = 'Available Endpoint Manager Templates'
+ const cardButtonPermissions = ['Endpoint.MEM.ReadWrite']
const integrations = ApiGetCall({
- url: "/api/ListExtensionsConfig",
- queryKey: "Integrations",
+ url: '/api/ListExtensionsConfig',
+ queryKey: 'Integrations',
refetchOnMount: false,
refetchOnReconnect: false,
- });
+ })
const actions = [
{
- label: "Edit Template",
+ label: 'Edit Template',
link: `/endpoint/MEM/list-templates/edit?id=[GUID]`,
icon: ,
- color: "info",
+ color: 'info',
condition: (row) => row.isSynced === false,
},
{
- label: "Edit Template Name and Description",
- type: "POST",
- url: "/api/ExecEditTemplate",
+ label: 'Edit Template Name and Description',
+ type: 'POST',
+ url: '/api/ExecEditTemplate',
fields: [
{
- type: "textField",
- name: "displayName",
- label: "Display Name",
+ type: 'textField',
+ name: 'displayName',
+ label: 'Display Name',
},
{
- type: "textField",
- name: "description",
- label: "Description",
+ type: 'textField',
+ name: 'description',
+ label: 'Description',
},
],
- data: { GUID: "GUID", Type: "!IntuneTemplate" },
+ data: { GUID: 'GUID', Type: '!IntuneTemplate' },
defaultvalues: (row) => ({
displayName: row.displayName,
description: row.description,
}),
confirmText:
- "Enter the new name and description for the template. Warning: This will disconnect the template from a template library if applied.",
+ 'Enter the new name and description for the template. Warning: This will disconnect the template from a template library if applied.',
multiPost: false,
icon: ,
- color: "info",
+ color: 'info',
},
{
- label: "Clone Template",
- type: "POST",
- url: "/api/ExecCloneTemplate",
- data: { GUID: "GUID", Type: "!IntuneTemplate" },
+ label: 'Clone Template',
+ type: 'POST',
+ url: '/api/ExecCloneTemplate',
+ data: { GUID: 'GUID', Type: '!IntuneTemplate' },
confirmText:
- "Are you sure you want to clone [displayName]? Cloned template are no longer synced with a template library.",
+ 'Are you sure you want to clone [displayName]? Cloned template are no longer synced with a template library.',
multiPost: false,
icon: ,
- color: "info",
+ color: 'info',
},
{
- label: "Add to package",
- type: "POST",
- url: "/api/ExecSetPackageTag",
- data: { GUID: "GUID" },
+ label: 'Add to package',
+ type: 'POST',
+ url: '/api/ExecSetPackageTag',
+ data: { GUID: 'GUID' },
fields: [
{
- type: "textField",
- name: "Package",
- label: "Package Name",
+ type: 'textField',
+ name: 'Package',
+ label: 'Package Name',
required: true,
validators: {
- required: { value: true, message: "Package name is required" },
+ required: { value: true, message: 'Package name is required' },
},
},
],
- confirmText: "Enter the package name to assign to the selected template(s).",
+ confirmText: 'Enter the package name to assign to the selected template(s).',
multiPost: true,
icon: ,
- color: "info",
+ color: 'info',
},
{
- label: "Remove from package",
- type: "POST",
- url: "/api/ExecSetPackageTag",
- data: { GUID: "GUID", Remove: true },
- confirmText: "Are you sure you want to remove the selected template(s) from their package?",
+ label: 'Remove from package',
+ type: 'POST',
+ url: '/api/ExecSetPackageTag',
+ data: { GUID: 'GUID', Remove: true },
+ confirmText: 'Are you sure you want to remove the selected template(s) from their package?',
multiPost: true,
icon: ,
- color: "warning",
+ color: 'warning',
},
{
- label: "Save to GitHub",
- type: "POST",
- url: "/api/ExecCommunityRepo",
+ label: 'Save to GitHub',
+ type: 'POST',
+ url: '/api/ExecCommunityRepo',
icon: ,
data: {
- Action: "UploadTemplate",
- GUID: "GUID",
+ Action: 'UploadTemplate',
+ GUID: 'GUID',
},
fields: [
{
- label: "Repository",
- name: "FullName",
- type: "select",
+ label: 'Repository',
+ name: 'FullName',
+ type: 'select',
api: {
- url: "/api/ListCommunityRepos",
+ url: '/api/ListCommunityRepos',
data: {
WriteAccess: true,
},
- queryKey: "CommunityRepos-Write",
- dataKey: "Results",
- valueField: "FullName",
- labelField: "FullName",
+ queryKey: 'CommunityRepos-Write',
+ dataKey: 'Results',
+ valueField: 'FullName',
+ labelField: 'FullName',
},
multiple: false,
creatable: false,
required: true,
validators: {
- required: { value: true, message: "This field is required" },
+ required: { value: true, message: 'This field is required' },
},
},
{
- label: "Commit Message",
- placeholder: "Enter a commit message for adding this file to GitHub",
- name: "Message",
- type: "textField",
+ label: 'Commit Message',
+ placeholder: 'Enter a commit message for adding this file to GitHub',
+ name: 'Message',
+ type: 'textField',
multiline: true,
required: true,
rows: 4,
},
],
- confirmText: "Are you sure you want to save this template to the selected repository?",
+ confirmText: 'Are you sure you want to save this template to the selected repository?',
condition: () => integrations.isSuccess && integrations?.data?.GitHub?.Enabled,
},
{
- label: "Delete Template",
- type: "POST",
- url: "/api/RemoveIntuneTemplate",
- data: { ID: "GUID" },
- confirmText: "Do you want to delete the template?",
+ label: 'Delete Template',
+ type: 'POST',
+ url: '/api/RemoveIntuneTemplate',
+ data: { ID: 'GUID' },
+ confirmText: 'Do you want to delete the template?',
multiPost: false,
icon: ,
- color: "danger",
+ color: 'danger',
},
- ];
+ ]
const offCanvas = {
- children: (row) => ,
- size: "lg",
- };
+ children: (row) => (
+
+ {Array.isArray(row.usage) && row.usage.length > 0 && (
+
+
+ Used in Standards Templates
+
+
+
+
+ Template Name
+ Included In
+
+
+
+ {row.usage.map((u, i) => (
+
+
+
+ {u.templateName ?? u.templateId}
+
+
+
+ {u.matchType === 'package' ? (
+
+ }
+ />
+
+ ) : (
+
+
+
+ )}
+
+
+ ))}
+
+
+
+ )}
+
+
+ ),
+ size: 'lg',
+ }
- const simpleColumns = ["displayName", "isSynced", "package", "description", "Type"];
+ const simpleColumns = ['displayName', 'isSynced', 'package', 'description', 'Type', 'usage']
+
+ const filterList = [
+ {
+ filterName: 'Synced Templates',
+ value: [{ id: 'isSynced', value: 'Yes' }],
+ type: 'column',
+ },
+ {
+ filterName: 'Custom Templates',
+ value: [{ id: 'isSynced', value: 'No' }],
+ type: 'column',
+ },
+ ]
return (
<>
@@ -166,6 +256,7 @@ const Page = () => {
actions={actions}
offCanvas={offCanvas}
simpleColumns={simpleColumns}
+ filters={filterList}
queryKey="ListIntuneTemplates-table"
cardButton={
{
}
/>
>
- );
-};
+ )
+}
-Page.getLayout = (page) => {page};
-export default Page;
+Page.getLayout = (page) => {page}
+export default Page
diff --git a/src/pages/endpoint/MEM/reusable-settings/index.js b/src/pages/endpoint/MEM/reusable-settings/index.js
index c301082ea1f0..75219f0d4136 100644
--- a/src/pages/endpoint/MEM/reusable-settings/index.js
+++ b/src/pages/endpoint/MEM/reusable-settings/index.js
@@ -4,15 +4,29 @@ import { CippTablePage } from "../../../../components/CippComponents/CippTablePa
import { Layout as DashboardLayout } from "../../../../layouts/index.js";
import { useSettings } from "../../../../hooks/use-settings";
import CippJsonView from "../../../../components/CippFormPages/CippJSONView";
+import { Stack } from "@mui/system";
+import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls";
const Page = () => {
const { currentTenant } = useSettings();
const pageTitle = "Reusable Settings";
+ const reportDB = useCippReportDB({
+ apiUrl: "/api/ListIntuneReusableSettings",
+ queryKey: "ListIntuneReusableSettings",
+ cacheName: "IntuneReusableSettings",
+ syncTitle: "Sync Reusable Settings Report",
+ allowToggle: true,
+ defaultCached: false,
+ });
+ const isAllTenants = reportDB.isAllTenants;
+
const actions = [
{
label: "Edit Reusable Setting",
- link: `/endpoint/MEM/reusable-settings/edit?id=[id]&tenant=${currentTenant}&tenantFilter=${currentTenant}`,
+ link: isAllTenants
+ ? "/endpoint/MEM/reusable-settings/edit?id=[id]&tenant=[Tenant]&tenantFilter=[Tenant]"
+ : `/endpoint/MEM/reusable-settings/edit?id=[id]&tenant=${currentTenant}&tenantFilter=${currentTenant}`,
},
{
label: "Delete Reusable Setting",
@@ -47,18 +61,32 @@ const Page = () => {
size: "lg",
};
+ const simpleColumns = [
+ ...reportDB.cacheColumns,
+ "displayName",
+ "description",
+ "id",
+ "version",
+ ];
+
return (
-
- }
- apiUrl="/api/ListIntuneReusableSettings"
- queryKey={`ListIntuneReusableSettings-${currentTenant}`}
- actions={actions}
- offCanvas={offCanvas}
- simpleColumns={["displayName", "description", "id", "version"]}
- />
+ <>
+
+
+ {reportDB.controls}
+
+ }
+ apiUrl={reportDB.resolvedApiUrl}
+ queryKey={reportDB.resolvedQueryKey}
+ actions={actions}
+ offCanvas={offCanvas}
+ simpleColumns={simpleColumns}
+ />
+ {reportDB.syncDialog}
+ >
);
};
diff --git a/src/pages/endpoint/applications/list/index.js b/src/pages/endpoint/applications/list/index.js
index 41671638cfce..fec79a4cf7f9 100644
--- a/src/pages/endpoint/applications/list/index.js
+++ b/src/pages/endpoint/applications/list/index.js
@@ -4,9 +4,11 @@ import { CippApiDialog } from "../../../../components/CippComponents/CippApiDial
import { GlobeAltIcon, TrashIcon, UserIcon, UserGroupIcon } from "@heroicons/react/24/outline";
import { LaptopMac, Sync, BookmarkAdd } from "@mui/icons-material";
import { CippApplicationDeployDrawer } from "../../../../components/CippComponents/CippApplicationDeployDrawer";
-import { Button, Box } from "@mui/material";
+import { Button } from "@mui/material";
+import { Stack } from "@mui/system";
import { useSettings } from "../../../../hooks/use-settings.js";
import { useDialog } from "../../../../hooks/use-dialog.js";
+import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls";
const assignmentIntentOptions = [
{ label: "Required", value: "Required" },
@@ -44,9 +46,18 @@ const mapOdataToAppType = (odataType) => {
const Page = () => {
const pageTitle = "Applications";
- const syncDialog = useDialog();
+ const vppSyncDialog = useDialog();
const tenant = useSettings().currentTenant;
+ const reportDB = useCippReportDB({
+ apiUrl: "/api/ListApps",
+ queryKey: "ListApps",
+ cacheName: "IntuneApplications",
+ syncTitle: "Sync Intune Applications Report",
+ allowToggle: true,
+ defaultCached: false,
+ });
+
const getAssignmentFilterFields = () => [
{
type: "autoComplete",
@@ -291,6 +302,7 @@ const Page = () => {
};
const simpleColumns = [
+ ...reportDB.cacheColumns,
"displayName",
"AppAssignment",
"AppExclude",
@@ -303,22 +315,24 @@ const Page = () => {
<>
+
- }>
+ }>
Sync VPP
-
+ {reportDB.controls}
+
}
/>
{
confirmText: `Are you sure you want to sync Apple Volume Purchase Program (VPP) tokens? This will sync all VPP tokens for ${tenant}.`,
}}
/>
+ {reportDB.syncDialog}
>
);
};
-Page.getLayout = (page) => {page};
+Page.getLayout = (page) => {page};
export default Page;
diff --git a/src/pages/endpoint/applications/templates/index.js b/src/pages/endpoint/applications/templates/index.js
index 6c4a0eb53285..b4a2a2bd1e28 100644
--- a/src/pages/endpoint/applications/templates/index.js
+++ b/src/pages/endpoint/applications/templates/index.js
@@ -1,122 +1,127 @@
-import { useState } from "react";
-import { Layout as DashboardLayout } from "../../../../layouts/index.js";
-import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
-import { TrashIcon } from "@heroicons/react/24/outline";
-import { Edit, RocketLaunch } from "@mui/icons-material";
-import { CippAppTemplateDrawer } from "../../../../components/CippComponents/CippAppTemplateDrawer";
-import CippJsonView from "../../../../components/CippFormPages/CippJSONView";
-import { Box } from "@mui/material";
-import { ApiGetCall } from "../../../../api/ApiCall";
-import { GitHub } from "@mui/icons-material";
+import { useState } from 'react'
+import { Layout as DashboardLayout } from '../../../../layouts/index.js'
+import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx'
+import { TrashIcon } from '@heroicons/react/24/outline'
+import { Edit, RocketLaunch } from '@mui/icons-material'
+import { CippAppTemplateDrawer } from '../../../../components/CippComponents/CippAppTemplateDrawer'
+import CippJsonView from '../../../../components/CippFormPages/CippJSONView'
+import { Box } from '@mui/material'
+import { ApiGetCall } from '../../../../api/ApiCall'
+import { GitHub } from '@mui/icons-material'
const Page = () => {
- const pageTitle = "Application Templates";
- const [editDrawerOpen, setEditDrawerOpen] = useState(false);
- const [editTemplate, setEditTemplate] = useState(null);
+ const pageTitle = 'Application Templates'
+ const [editDrawerOpen, setEditDrawerOpen] = useState(false)
+ const [editTemplate, setEditTemplate] = useState(null)
const integrations = ApiGetCall({
- url: "/api/ListExtensionsConfig",
- queryKey: "Integrations",
+ url: '/api/ListExtensionsConfig',
+ queryKey: 'Integrations',
refetchOnMount: false,
refetchOnReconnect: false,
- });
+ })
const actions = [
{
- label: "Edit Template",
+ label: 'Edit Template',
icon: ,
- color: "info",
+ color: 'info',
noConfirm: true,
customFunction: (row) => {
- setEditTemplate({ ...row });
- setEditDrawerOpen(true);
+ setEditTemplate({ ...row })
+ setEditDrawerOpen(true)
},
},
{
- label: "Save to GitHub",
- type: "POST",
- url: "/api/ExecCommunityRepo",
+ label: 'Save to GitHub',
+ type: 'POST',
+ url: '/api/ExecCommunityRepo',
icon: ,
data: {
- Action: "UploadTemplate",
- GUID: "GUID",
+ Action: 'UploadTemplate',
+ GUID: 'GUID',
},
fields: [
{
- label: "Repository",
- name: "FullName",
- type: "select",
+ label: 'Repository',
+ name: 'FullName',
+ type: 'select',
api: {
- url: "/api/ListCommunityRepos",
+ url: '/api/ListCommunityRepos',
data: {
WriteAccess: true,
},
- queryKey: "CommunityRepos-Write",
- dataKey: "Results",
- valueField: "FullName",
- labelField: "FullName",
+ queryKey: 'CommunityRepos-Write',
+ dataKey: 'Results',
+ valueField: 'FullName',
+ labelField: 'FullName',
},
multiple: false,
creatable: false,
required: true,
validators: {
- required: { value: true, message: "This field is required" },
+ required: { value: true, message: 'This field is required' },
},
},
{
- label: "Commit Message",
- placeholder: "Enter a commit message for adding this file to GitHub",
- name: "Message",
- type: "textField",
+ label: 'Commit Message',
+ placeholder: 'Enter a commit message for adding this file to GitHub',
+ name: 'Message',
+ type: 'textField',
multiline: true,
required: true,
rows: 4,
},
],
- confirmText: "Are you sure you want to save this template to the selected repository?",
+ confirmText: 'Are you sure you want to save this template to the selected repository?',
condition: () => integrations.isSuccess && integrations?.data?.GitHub?.Enabled,
},
{
- label: "Deploy Template",
- type: "POST",
- url: "/api/ExecDeployAppTemplate",
+ label: 'Deploy Template',
+ type: 'POST',
+ url: '/api/ExecDeployAppTemplate',
icon: ,
- color: "info",
+ color: 'info',
fields: [
{
- type: "autoComplete",
- name: "selectedTenants",
- label: "Select Tenants",
+ type: 'autoComplete',
+ name: 'selectedTenants',
+ label: 'Select Tenants',
multiple: true,
creatable: false,
api: {
- url: "/api/ListTenants?AllTenantSelector=true",
- queryKey: "ListTenants-AppTemplateDeploy",
+ url: '/api/ListTenants?AllTenantSelector=true',
+ queryKey: 'ListTenants-AppTemplateDeploy',
labelField: (tenant) => `${tenant.displayName} (${tenant.defaultDomainName})`,
- valueField: "defaultDomainName",
+ valueField: 'defaultDomainName',
addedField: {
- customerId: "customerId",
- defaultDomainName: "defaultDomainName",
+ customerId: 'customerId',
+ defaultDomainName: 'defaultDomainName',
},
},
- validators: { required: "Please select at least one tenant" },
+ validators: { required: 'Please select at least one tenant' },
},
{
- type: "radio",
- name: "AssignTo",
- label: "Override Assignment (optional)",
+ type: 'radio',
+ name: 'AssignTo',
+ label: 'Override Assignment (optional)',
options: [
- { label: "Keep template assignment", value: "" },
- { label: "Do not assign", value: "On" },
- { label: "Assign to all users", value: "allLicensedUsers" },
- { label: "Assign to all devices", value: "AllDevices" },
- { label: "Assign to all users and devices", value: "AllDevicesAndUsers" },
- { label: "Assign to Custom Group", value: "customGroup" },
+ { label: 'Keep template assignment', value: '' },
+ { label: 'Do not assign', value: 'On' },
+ { label: 'Assign to all users', value: 'allLicensedUsers' },
+ { label: 'Assign to all devices', value: 'AllDevices' },
+ { label: 'Assign to all users and devices', value: 'AllDevicesAndUsers' },
+ { label: 'Assign to Custom Group', value: 'customGroup' },
],
},
{
- type: "textField",
- name: "customGroup",
- label: "Custom Group Names (comma separated, wildcards allowed)",
+ type: 'textField',
+ name: 'customGroup',
+ label: 'Custom Group Names (comma separated, wildcards allowed)',
+ },
+ {
+ type: 'textField',
+ name: 'excludeGroup',
+ label: 'Exclude Group Names (comma separated, wildcards allowed)',
},
],
customDataformatter: (row, action, formData) => ({
@@ -125,26 +130,27 @@ const Page = () => {
defaultDomainName: t.value,
customerId: t.addedFields?.customerId,
})),
- AssignTo: formData?.AssignTo || "",
- customGroup: formData?.customGroup || "",
+ AssignTo: formData?.AssignTo || '',
+ customGroup: formData?.customGroup || '',
+ excludeGroup: formData?.excludeGroup || '',
}),
confirmText: 'Deploy "[displayName]" ([appCount] apps) to the selected tenants?',
},
{
- label: "Delete Template",
- type: "POST",
- url: "/api/RemoveAppTemplate",
- data: { ID: "GUID" },
+ label: 'Delete Template',
+ type: 'POST',
+ url: '/api/RemoveAppTemplate',
+ data: { ID: 'GUID' },
confirmText: 'Delete the template "[displayName]"?',
icon: ,
- color: "danger",
+ color: 'danger',
},
- ];
+ ]
const offCanvas = {
children: (row) => ,
- size: "lg",
- };
+ size: 'lg',
+ }
return (
<>
@@ -154,10 +160,10 @@ const Page = () => {
apiUrl="/api/ListAppTemplates"
actions={actions}
offCanvas={offCanvas}
- simpleColumns={["displayName", "description", "appCount", "appTypes", "appNames"]}
+ simpleColumns={['displayName', 'description', 'appCount', 'appTypes', 'appNames']}
queryKey="ListAppTemplates"
cardButton={
-
+
}
@@ -166,13 +172,13 @@ const Page = () => {
editData={editTemplate}
open={editDrawerOpen}
onClose={() => {
- setEditDrawerOpen(false);
- setEditTemplate(null);
+ setEditDrawerOpen(false)
+ setEditTemplate(null)
}}
/>
>
- );
-};
+ )
+}
-Page.getLayout = (page) => {page};
-export default Page;
+Page.getLayout = (page) => {page}
+export default Page
diff --git a/src/pages/identity/administration/jit-admin-templates/add.jsx b/src/pages/identity/administration/jit-admin-templates/add.jsx
index 986e7ea378d1..0a57a8b33882 100644
--- a/src/pages/identity/administration/jit-admin-templates/add.jsx
+++ b/src/pages/identity/administration/jit-admin-templates/add.jsx
@@ -9,6 +9,7 @@ import { CippFormDomainSelector } from "../../../../components/CippComponents/Ci
import { CippFormUserSelector } from "../../../../components/CippComponents/CippFormUserSelector";
import { CippFormGroupSelector } from "../../../../components/CippComponents/CippFormGroupSelector";
import gdaproles from "../../../../data/GDAPRoles.json";
+import countryList from "../../../../data/countryList.json";
import { useSettings } from "../../../../hooks/use-settings";
import { useEffect } from "react";
@@ -352,6 +353,19 @@ const Page = () => {
/>
)}
+
+ ({
+ label: Name,
+ value: Code,
+ }))}
+ formControl={formControl}
+ />
+
{
/>
)}
+
+ ({
+ label: Name,
+ value: Code,
+ }))}
+ formControl={formControl}
+ />
+
{
const watcher = useWatch({ control: formControl.control });
const useTAP = useWatch({ control: formControl.control, name: "UseTAP" });
+ const startDate = useWatch({ control: formControl.control, name: "startDate" });
+ const endDate = useWatch({ control: formControl.control, name: "endDate" });
+ const tapLifetimeInMinutes = useWatch({
+ control: formControl.control,
+ name: "tapLifetimeInMinutes",
+ });
const tapPolicy = ApiGetCall({
url: selectedTenant
@@ -46,6 +53,22 @@ const Page = () => {
const useRoles = useWatch({ control: formControl.control, name: "useRoles" });
const useGroups = useWatch({ control: formControl.control, name: "useGroups" });
+ useEffect(() => {
+ if (!useTAP || !startDate || !endDate) {
+ formControl.setValue("tapLifetimeInMinutes", null);
+ return;
+ }
+
+ const requestedMinutes = Math.max(1, Math.round((endDate - startDate) / 60));
+ const tapPolicyConfig = tapPolicy.data?.Results?.[0];
+ const policyMax = tapPolicyConfig?.maximumLifetimeInMinutes ?? 1440;
+ const policyMin = Math.min(tapPolicyConfig?.minimumLifetimeInMinutes ?? 1, policyMax);
+ formControl.setValue(
+ "tapLifetimeInMinutes",
+ Math.min(Math.max(requestedMinutes, policyMin), policyMax)
+ );
+ }, [useTAP, startDate, endDate, tapPolicy.data, formControl]);
+
// Clear fields when switches are toggled off
useEffect(() => {
if (!useRoles) {
@@ -205,6 +228,9 @@ const Page = () => {
if (template.defaultExistingUser) {
formControl.setValue("existingUser", template.defaultExistingUser, { shouldDirty: true });
}
+ if (template.defaultUsageLocation) {
+ formControl.setValue("usageLocation", template.defaultUsageLocation, { shouldDirty: true });
+ }
// Dates
if (template.defaultDuration) {
@@ -343,6 +369,19 @@ const Page = () => {
validators={{ required: "Domain is required" }}
/>
+
+ ({
+ label: Name,
+ value: Code,
+ }))}
+ formControl={formControl}
+ />
+
@@ -484,6 +523,11 @@ const Page = () => {
/>
+
{
TAP is not enabled in this tenant. TAP generation will fail.
)}
+ {useTAP && tapLifetimeInMinutes && (
+
+ TAP will be valid for {tapLifetimeInMinutes} minutes.
+
+ )}
{
// Get unique tenant domains from users
const tenantDomains =
[...new Set(users?.map((user) => user.Tenant || user.tenantFilter).filter(Boolean))] || []
+ const firstTenantDomain = tenantDomains[0]
+ const hasManagerSelected = selectedProperties.includes('manager')
+ const hasSponsorSelected = selectedProperties.includes('sponsor')
+ const hasRelationshipSelected = hasManagerSelected || hasSponsorSelected
+
+ useEffect(() => {
+ if (!hasRelationshipSelected || !firstTenantDomain) {
+ return
+ }
+
+ const currentTenantFilter = formControl.getValues('tenantFilter')
+ if (currentTenantFilter?.value !== firstTenantDomain) {
+ formControl.setValue('tenantFilter', { value: firstTenantDomain })
+ }
+ }, [firstTenantDomain, formControl, hasRelationshipSelected])
// Fetch custom data mappings for all tenants
const customDataMappings = ApiGetCall({
@@ -248,6 +275,21 @@ const PropertySelectionStep = (props) => {
)
}
+ if (property?.type === 'userSelector') {
+ return (
+
+ )
+ }
+
// Default to text input for string types with consistent styling
return (
{
Properties to update
+ {hasRelationshipSelected && tenantDomains.length > 1 && (
+
+ The user picker is scoped to {firstTenantDomain}. Cross-tenant manager or sponsor
+ assignment is not supported, so the selected user must exist in each target tenant.
+
+ )}
{selectedProperties.map(renderPropertyInput)}
@@ -455,7 +503,14 @@ const ConfirmationStep = (props) => {
}
selectedProperties.forEach((propName) => {
- if (propertyValues[propName] !== undefined && propertyValues[propName] !== '') {
+ const propertyValue = propertyValues[propName]
+
+ if (propertyValue !== undefined && propertyValue !== '' && propertyValue !== null) {
+ if (propName === 'manager' || propName === 'sponsor') {
+ if (propertyValue?.value) userData[propName] = propertyValue.value
+ return
+ }
+
// Handle dot notation properties (e.g., "extension_abc123.customField")
if (propName.includes('.')) {
const parts = propName.split('.')
@@ -470,10 +525,10 @@ const ConfirmationStep = (props) => {
}
// Set the final property value
- current[parts[parts.length - 1]] = propertyValues[propName]
+ current[parts[parts.length - 1]] = propertyValue
} else {
// Handle regular properties
- userData[propName] = propertyValues[propName]
+ userData[propName] = propertyValue
}
}
})
@@ -522,8 +577,13 @@ const ConfirmationStep = (props) => {
{selectedProperties.map((propName) => {
const property = allProperties.find((p) => p.property === propName)
const value = propertyValues[propName]
- const displayValue =
- property?.type === 'boolean' ? (value ? 'Yes' : 'No') : value || 'Not set'
+ let displayValue = value || 'Not set'
+
+ if (propName === 'manager' || propName === 'sponsor') {
+ displayValue = value?.label || value?.value || 'Not set'
+ } else if (property?.type === 'boolean') {
+ displayValue = value ? 'Yes' : 'No'
+ }
return (
diff --git a/src/pages/identity/administration/users/user/exchange.jsx b/src/pages/identity/administration/users/user/exchange.jsx
index 1d01f699c5a7..1336a740612d 100644
--- a/src/pages/identity/administration/users/user/exchange.jsx
+++ b/src/pages/identity/administration/users/user/exchange.jsx
@@ -947,13 +947,15 @@ const Page = () => {
icon: ,
url: "/api/ExecSetMailboxRule",
customDataformatter: (row, action, formData) => {
- return {
- ruleId: row?.Identity,
+ const rows = Array.isArray(row) ? row : [row];
+ const result = rows.map((r) => ({
+ ruleId: r?.Identity,
userPrincipalName: graphUserRequest.data?.[0]?.userPrincipalName,
- ruleName: row?.Name,
+ ruleName: r?.Name,
Enable: true,
tenantFilter: userSettingsDefaults.currentTenant,
- };
+ }));
+ return Array.isArray(row) ? result : result[0];
},
condition: (row) => row && !row.Enabled,
confirmText: "Are you sure you want to enable this mailbox rule?",
@@ -965,13 +967,15 @@ const Page = () => {
icon: ,
url: "/api/ExecSetMailboxRule",
customDataformatter: (row, action, formData) => {
- return {
- ruleId: row?.Identity,
+ const rows = Array.isArray(row) ? row : [row];
+ const result = rows.map((r) => ({
+ ruleId: r?.Identity,
userPrincipalName: graphUserRequest.data?.[0]?.userPrincipalName,
- ruleName: row?.Name,
+ ruleName: r?.Name,
Disable: true,
tenantFilter: userSettingsDefaults.currentTenant,
- };
+ }));
+ return Array.isArray(row) ? result : result[0];
},
condition: (row) => row && row.Enabled,
confirmText: "Are you sure you want to disable this mailbox rule?",
@@ -983,12 +987,14 @@ const Page = () => {
icon: ,
url: "/api/ExecRemoveMailboxRule",
customDataformatter: (row, action, formData) => {
- return {
- ruleId: row?.Identity,
- ruleName: row?.Name,
+ const rows = Array.isArray(row) ? row : [row];
+ const result = rows.map((r) => ({
+ ruleId: r?.Identity,
+ ruleName: r?.Name,
userPrincipalName: graphUserRequest.data?.[0]?.userPrincipalName,
tenantFilter: userSettingsDefaults.currentTenant,
- };
+ }));
+ return Array.isArray(row) ? result : result[0];
},
confirmText: "Are you sure you want to remove this mailbox rule?",
multiPost: false,
diff --git a/src/pages/security/compliance/dlp-templates/index.js b/src/pages/security/compliance/dlp-templates/index.js
new file mode 100644
index 000000000000..99e949f700d1
--- /dev/null
+++ b/src/pages/security/compliance/dlp-templates/index.js
@@ -0,0 +1,108 @@
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
+import { TrashIcon } from "@heroicons/react/24/outline";
+import { GitHub } from "@mui/icons-material";
+import { ApiGetCall } from "../../../../api/ApiCall";
+import { CippPolicyImportDrawer } from "../../../../components/CippComponents/CippPolicyImportDrawer.jsx";
+import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx";
+import { PermissionButton } from "../../../../utils/permissions.js";
+
+const Page = () => {
+ const pageTitle = "DLP Compliance Policy Templates";
+ const cardButtonPermissions = ["Security.DlpCompliancePolicy.ReadWrite"];
+
+ const integrations = ApiGetCall({
+ url: "/api/ListExtensionsConfig",
+ queryKey: "Integrations",
+ refetchOnMount: false,
+ refetchOnReconnect: false,
+ });
+
+ const actions = [
+ {
+ label: "Save to GitHub",
+ type: "POST",
+ url: "/api/ExecCommunityRepo",
+ icon: ,
+ data: {
+ Action: "UploadTemplate",
+ GUID: "GUID",
+ },
+ fields: [
+ {
+ label: "Repository",
+ name: "FullName",
+ type: "select",
+ api: {
+ url: "/api/ListCommunityRepos",
+ data: { WriteAccess: true },
+ queryKey: "CommunityRepos-Write",
+ dataKey: "Results",
+ valueField: "FullName",
+ labelField: "FullName",
+ },
+ multiple: false,
+ creatable: false,
+ required: true,
+ validators: { required: { value: true, message: "This field is required" } },
+ },
+ {
+ label: "Commit Message",
+ placeholder: "Enter a commit message for adding this file to GitHub",
+ name: "Message",
+ type: "textField",
+ multiline: true,
+ required: true,
+ rows: 4,
+ },
+ ],
+ confirmText: "Are you sure you want to save this template to the selected repository?",
+ condition: () => integrations.isSuccess && integrations?.data?.GitHub?.Enabled,
+ },
+ {
+ label: "Delete Template",
+ type: "POST",
+ url: "/api/RemoveDlpCompliancePolicyTemplate",
+ data: { ID: "GUID" },
+ confirmText: "Do you want to delete the template?",
+ icon: ,
+ color: "danger",
+ },
+ ];
+
+ const offCanvas = {
+ extendedInfoFields: ["name", "comments", "Mode", "Workload", "Enabled", "GUID"],
+ actions: actions,
+ };
+
+ const simpleColumns = ["name", "comments", "Mode", "Workload", "Enabled", "GUID"];
+
+ return (
+
+
+
+ >
+ }
+ />
+ );
+};
+
+Page.getLayout = (page) => {page};
+export default Page;
diff --git a/src/pages/security/compliance/dlp/index.js b/src/pages/security/compliance/dlp/index.js
new file mode 100644
index 000000000000..750cfe6ade53
--- /dev/null
+++ b/src/pages/security/compliance/dlp/index.js
@@ -0,0 +1,112 @@
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
+import { Book, Block, Check } from "@mui/icons-material";
+import { TrashIcon } from "@heroicons/react/24/outline";
+import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx";
+import { PermissionButton } from "../../../../utils/permissions.js";
+
+const Page = () => {
+ const pageTitle = "DLP Compliance Policies";
+ const apiUrl = "/api/ListDlpCompliancePolicy";
+ const cardButtonPermissions = ["Security.DlpCompliancePolicy.ReadWrite"];
+
+ const actions = [
+ {
+ label: "Create template based on policy",
+ type: "POST",
+ icon: ,
+ url: "/api/AddDlpCompliancePolicyTemplate",
+ dataFunction: (data) => {
+ return { ...data };
+ },
+ confirmText: "Are you sure you want to create a template based on this DLP policy?",
+ },
+ {
+ label: "Enable Policy",
+ type: "POST",
+ icon: ,
+ url: "/api/EditDlpCompliancePolicy",
+ data: {
+ State: "!enable",
+ Identity: "Name",
+ },
+ confirmText: "Are you sure you want to enable this DLP policy?",
+ condition: (row) => row.Enabled === false,
+ },
+ {
+ label: "Disable Policy",
+ type: "POST",
+ icon: ,
+ url: "/api/EditDlpCompliancePolicy",
+ data: {
+ State: "!disable",
+ Identity: "Name",
+ },
+ confirmText: "Are you sure you want to disable this DLP policy?",
+ condition: (row) => row.Enabled === true,
+ },
+ {
+ label: "Delete Policy",
+ type: "POST",
+ icon: ,
+ url: "/api/RemoveDlpCompliancePolicy",
+ data: {
+ Identity: "Name",
+ },
+ confirmText: "Are you sure you want to delete this DLP policy?",
+ color: "danger",
+ },
+ ];
+
+ const offCanvas = {
+ extendedInfoFields: [
+ "Name",
+ "Comment",
+ "Mode",
+ "Enabled",
+ "Workload",
+ "ExchangeLocation",
+ "SharePointLocation",
+ "OneDriveLocation",
+ "TeamsLocation",
+ "EndpointDlpLocation",
+ "RuleCount",
+ "CreatedBy",
+ "WhenCreatedUTC",
+ "WhenChangedUTC",
+ ],
+ actions: actions,
+ };
+
+ const simpleColumns = [
+ "Name",
+ "Mode",
+ "Enabled",
+ "Workload",
+ "RuleCount",
+ "CreatedBy",
+ "WhenCreatedUTC",
+ "WhenChangedUTC",
+ ];
+
+ return (
+
+ }
+ />
+ );
+};
+
+Page.getLayout = (page) => {page};
+export default Page;
diff --git a/src/pages/security/compliance/labels-templates/index.js b/src/pages/security/compliance/labels-templates/index.js
new file mode 100644
index 000000000000..78477f7735b5
--- /dev/null
+++ b/src/pages/security/compliance/labels-templates/index.js
@@ -0,0 +1,115 @@
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
+import { TrashIcon } from "@heroicons/react/24/outline";
+import { GitHub } from "@mui/icons-material";
+import { ApiGetCall } from "../../../../api/ApiCall";
+import { CippPolicyImportDrawer } from "../../../../components/CippComponents/CippPolicyImportDrawer.jsx";
+import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx";
+import { PermissionButton } from "../../../../utils/permissions.js";
+
+const Page = () => {
+ const pageTitle = "Sensitivity Label Templates";
+ const cardButtonPermissions = ["Security.SensitivityLabel.ReadWrite"];
+
+ const integrations = ApiGetCall({
+ url: "/api/ListExtensionsConfig",
+ queryKey: "Integrations",
+ refetchOnMount: false,
+ refetchOnReconnect: false,
+ });
+
+ const actions = [
+ {
+ label: "Save to GitHub",
+ type: "POST",
+ url: "/api/ExecCommunityRepo",
+ icon: ,
+ data: {
+ Action: "UploadTemplate",
+ GUID: "GUID",
+ },
+ fields: [
+ {
+ label: "Repository",
+ name: "FullName",
+ type: "select",
+ api: {
+ url: "/api/ListCommunityRepos",
+ data: { WriteAccess: true },
+ queryKey: "CommunityRepos-Write",
+ dataKey: "Results",
+ valueField: "FullName",
+ labelField: "FullName",
+ },
+ multiple: false,
+ creatable: false,
+ required: true,
+ validators: { required: { value: true, message: "This field is required" } },
+ },
+ {
+ label: "Commit Message",
+ placeholder: "Enter a commit message for adding this file to GitHub",
+ name: "Message",
+ type: "textField",
+ multiline: true,
+ required: true,
+ rows: 4,
+ },
+ ],
+ confirmText: "Are you sure you want to save this template to the selected repository?",
+ condition: () => integrations.isSuccess && integrations?.data?.GitHub?.Enabled,
+ },
+ {
+ label: "Delete Template",
+ type: "POST",
+ url: "/api/RemoveSensitivityLabelTemplate",
+ data: { ID: "GUID" },
+ confirmText: "Do you want to delete the template?",
+ icon: ,
+ color: "danger",
+ },
+ ];
+
+ const offCanvas = {
+ extendedInfoFields: [
+ "name",
+ "DisplayName",
+ "comments",
+ "ContentType",
+ "EncryptionEnabled",
+ "GUID",
+ ],
+ actions: actions,
+ };
+
+ const simpleColumns = ["name", "DisplayName", "comments", "ContentType", "EncryptionEnabled", "GUID"];
+
+ return (
+
+
+
+ >
+ }
+ />
+ );
+};
+
+Page.getLayout = (page) => {page};
+export default Page;
diff --git a/src/pages/security/compliance/labels/index.js b/src/pages/security/compliance/labels/index.js
new file mode 100644
index 000000000000..e35ff5942b0f
--- /dev/null
+++ b/src/pages/security/compliance/labels/index.js
@@ -0,0 +1,91 @@
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
+import { Book } from "@mui/icons-material";
+import { TrashIcon } from "@heroicons/react/24/outline";
+import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx";
+import { PermissionButton } from "../../../../utils/permissions.js";
+
+const Page = () => {
+ const pageTitle = "Sensitivity Labels";
+ const apiUrl = "/api/ListSensitivityLabel";
+ const cardButtonPermissions = ["Security.SensitivityLabel.ReadWrite"];
+
+ const actions = [
+ {
+ label: "Create template based on label",
+ type: "POST",
+ icon: ,
+ url: "/api/AddSensitivityLabelTemplate",
+ dataFunction: (data) => {
+ return { ...data };
+ },
+ confirmText: "Are you sure you want to create a template based on this sensitivity label?",
+ },
+ {
+ label: "Delete Label",
+ type: "POST",
+ icon: ,
+ url: "/api/RemoveSensitivityLabel",
+ data: {
+ Identity: "Guid",
+ },
+ confirmText:
+ "Are you sure you want to delete this sensitivity label? Labels currently published to users will be removed from policies.",
+ color: "danger",
+ },
+ ];
+
+ const offCanvas = {
+ extendedInfoFields: [
+ "DisplayName",
+ "Name",
+ "Comment",
+ "Tooltip",
+ "ParentId",
+ "ContentType",
+ "EncryptionEnabled",
+ "EncryptionProtectionType",
+ "ContentMarkingHeaderEnabled",
+ "ContentMarkingFooterEnabled",
+ "ContentMarkingWatermarkEnabled",
+ "SiteAndGroupProtectionEnabled",
+ "Priority",
+ "Disabled",
+ "PublishedInPolicies",
+ ],
+ actions: actions,
+ };
+
+ const simpleColumns = [
+ "DisplayName",
+ "Name",
+ "ContentType",
+ "EncryptionEnabled",
+ "ContentMarkingHeaderEnabled",
+ "ContentMarkingWatermarkEnabled",
+ "SiteAndGroupProtectionEnabled",
+ "Priority",
+ "Disabled",
+ ];
+
+ return (
+
+ }
+ />
+ );
+};
+
+Page.getLayout = (page) => {page};
+export default Page;
diff --git a/src/pages/security/compliance/retention-templates/index.js b/src/pages/security/compliance/retention-templates/index.js
new file mode 100644
index 000000000000..291c989c35ec
--- /dev/null
+++ b/src/pages/security/compliance/retention-templates/index.js
@@ -0,0 +1,108 @@
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
+import { TrashIcon } from "@heroicons/react/24/outline";
+import { GitHub } from "@mui/icons-material";
+import { ApiGetCall } from "../../../../api/ApiCall";
+import { CippPolicyImportDrawer } from "../../../../components/CippComponents/CippPolicyImportDrawer.jsx";
+import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx";
+import { PermissionButton } from "../../../../utils/permissions.js";
+
+const Page = () => {
+ const pageTitle = "Retention Policy Templates";
+ const cardButtonPermissions = ["Security.RetentionCompliancePolicy.ReadWrite"];
+
+ const integrations = ApiGetCall({
+ url: "/api/ListExtensionsConfig",
+ queryKey: "Integrations",
+ refetchOnMount: false,
+ refetchOnReconnect: false,
+ });
+
+ const actions = [
+ {
+ label: "Save to GitHub",
+ type: "POST",
+ url: "/api/ExecCommunityRepo",
+ icon: ,
+ data: {
+ Action: "UploadTemplate",
+ GUID: "GUID",
+ },
+ fields: [
+ {
+ label: "Repository",
+ name: "FullName",
+ type: "select",
+ api: {
+ url: "/api/ListCommunityRepos",
+ data: { WriteAccess: true },
+ queryKey: "CommunityRepos-Write",
+ dataKey: "Results",
+ valueField: "FullName",
+ labelField: "FullName",
+ },
+ multiple: false,
+ creatable: false,
+ required: true,
+ validators: { required: { value: true, message: "This field is required" } },
+ },
+ {
+ label: "Commit Message",
+ placeholder: "Enter a commit message for adding this file to GitHub",
+ name: "Message",
+ type: "textField",
+ multiline: true,
+ required: true,
+ rows: 4,
+ },
+ ],
+ confirmText: "Are you sure you want to save this template to the selected repository?",
+ condition: () => integrations.isSuccess && integrations?.data?.GitHub?.Enabled,
+ },
+ {
+ label: "Delete Template",
+ type: "POST",
+ url: "/api/RemoveRetentionCompliancePolicyTemplate",
+ data: { ID: "GUID" },
+ confirmText: "Do you want to delete the template?",
+ icon: ,
+ color: "danger",
+ },
+ ];
+
+ const offCanvas = {
+ extendedInfoFields: ["name", "comments", "Enabled", "RestrictiveRetention", "GUID"],
+ actions: actions,
+ };
+
+ const simpleColumns = ["name", "comments", "Enabled", "RestrictiveRetention", "GUID"];
+
+ return (
+
+
+
+ >
+ }
+ />
+ );
+};
+
+Page.getLayout = (page) => {page};
+export default Page;
diff --git a/src/pages/security/compliance/retention/index.js b/src/pages/security/compliance/retention/index.js
new file mode 100644
index 000000000000..962301013f29
--- /dev/null
+++ b/src/pages/security/compliance/retention/index.js
@@ -0,0 +1,111 @@
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
+import { Book, Block, Check } from "@mui/icons-material";
+import { TrashIcon } from "@heroicons/react/24/outline";
+import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx";
+import { PermissionButton } from "../../../../utils/permissions.js";
+
+const Page = () => {
+ const pageTitle = "Purview Retention Policies";
+ const apiUrl = "/api/ListRetentionCompliancePolicy";
+ const cardButtonPermissions = ["Security.RetentionCompliancePolicy.ReadWrite"];
+
+ const actions = [
+ {
+ label: "Create template based on policy",
+ type: "POST",
+ icon: ,
+ url: "/api/AddRetentionCompliancePolicyTemplate",
+ data: { Identity: "Name" },
+ confirmText: "Are you sure you want to create a template based on this retention policy?",
+ },
+ {
+ label: "Enable Policy",
+ type: "POST",
+ icon: ,
+ url: "/api/EditRetentionCompliancePolicy",
+ data: {
+ State: "!enable",
+ Identity: "Name",
+ },
+ confirmText: "Are you sure you want to enable this retention policy?",
+ condition: (row) => row.Enabled === false,
+ },
+ {
+ label: "Disable Policy",
+ type: "POST",
+ icon: ,
+ url: "/api/EditRetentionCompliancePolicy",
+ data: {
+ State: "!disable",
+ Identity: "Name",
+ },
+ confirmText: "Are you sure you want to disable this retention policy?",
+ condition: (row) => row.Enabled === true,
+ },
+ {
+ label: "Delete Policy",
+ type: "POST",
+ icon: ,
+ url: "/api/RemoveRetentionCompliancePolicy",
+ data: {
+ Identity: "Name",
+ },
+ confirmText: "Are you sure you want to delete this retention policy?",
+ color: "danger",
+ },
+ ];
+
+ const offCanvas = {
+ extendedInfoFields: [
+ "Name",
+ "Comment",
+ "Enabled",
+ "Workload",
+ "RestrictiveRetention",
+ "ExchangeLocation",
+ "SharePointLocation",
+ "OneDriveLocation",
+ "ModernGroupLocation",
+ "TeamsChannelLocation",
+ "TeamsChatLocation",
+ "RuleCount",
+ "CreatedBy",
+ "WhenCreatedUTC",
+ "WhenChangedUTC",
+ ],
+ actions: actions,
+ };
+
+ const simpleColumns = [
+ "Name",
+ "Enabled",
+ "Workload",
+ "RuleCount",
+ "RestrictiveRetention",
+ "CreatedBy",
+ "WhenCreatedUTC",
+ "WhenChangedUTC",
+ ];
+
+ return (
+
+ }
+ />
+ );
+};
+
+Page.getLayout = (page) => {page};
+export default Page;
diff --git a/src/pages/security/compliance/sit-templates/index.js b/src/pages/security/compliance/sit-templates/index.js
new file mode 100644
index 000000000000..3b6bf4b87121
--- /dev/null
+++ b/src/pages/security/compliance/sit-templates/index.js
@@ -0,0 +1,108 @@
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
+import { TrashIcon } from "@heroicons/react/24/outline";
+import { GitHub } from "@mui/icons-material";
+import { ApiGetCall } from "../../../../api/ApiCall";
+import { CippPolicyImportDrawer } from "../../../../components/CippComponents/CippPolicyImportDrawer.jsx";
+import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx";
+import { PermissionButton } from "../../../../utils/permissions.js";
+
+const Page = () => {
+ const pageTitle = "Sensitive Information Type Templates";
+ const cardButtonPermissions = ["Security.SensitiveInfoType.ReadWrite"];
+
+ const integrations = ApiGetCall({
+ url: "/api/ListExtensionsConfig",
+ queryKey: "Integrations",
+ refetchOnMount: false,
+ refetchOnReconnect: false,
+ });
+
+ const actions = [
+ {
+ label: "Save to GitHub",
+ type: "POST",
+ url: "/api/ExecCommunityRepo",
+ icon: ,
+ data: {
+ Action: "UploadTemplate",
+ GUID: "GUID",
+ },
+ fields: [
+ {
+ label: "Repository",
+ name: "FullName",
+ type: "select",
+ api: {
+ url: "/api/ListCommunityRepos",
+ data: { WriteAccess: true },
+ queryKey: "CommunityRepos-Write",
+ dataKey: "Results",
+ valueField: "FullName",
+ labelField: "FullName",
+ },
+ multiple: false,
+ creatable: false,
+ required: true,
+ validators: { required: { value: true, message: "This field is required" } },
+ },
+ {
+ label: "Commit Message",
+ placeholder: "Enter a commit message for adding this file to GitHub",
+ name: "Message",
+ type: "textField",
+ multiline: true,
+ required: true,
+ rows: 4,
+ },
+ ],
+ confirmText: "Are you sure you want to save this template to the selected repository?",
+ condition: () => integrations.isSuccess && integrations?.data?.GitHub?.Enabled,
+ },
+ {
+ label: "Delete Template",
+ type: "POST",
+ url: "/api/RemoveSensitiveInfoTypeTemplate",
+ data: { ID: "GUID" },
+ confirmText: "Do you want to delete the template?",
+ icon: ,
+ color: "danger",
+ },
+ ];
+
+ const offCanvas = {
+ extendedInfoFields: ["name", "comments", "Pattern", "Confidence", "Locale", "GUID"],
+ actions: actions,
+ };
+
+ const simpleColumns = ["name", "comments", "Pattern", "Confidence", "Locale", "GUID"];
+
+ return (
+
+
+
+ >
+ }
+ />
+ );
+};
+
+Page.getLayout = (page) => {page};
+export default Page;
diff --git a/src/pages/security/compliance/sit/index.js b/src/pages/security/compliance/sit/index.js
new file mode 100644
index 000000000000..3101f0502218
--- /dev/null
+++ b/src/pages/security/compliance/sit/index.js
@@ -0,0 +1,70 @@
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
+import { TrashIcon } from "@heroicons/react/24/outline";
+import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx";
+import { PermissionButton } from "../../../../utils/permissions.js";
+
+const Page = () => {
+ const pageTitle = "Sensitive Information Types";
+ const apiUrl = "/api/ListSensitiveInfoType";
+ const cardButtonPermissions = ["Security.SensitiveInfoType.ReadWrite"];
+
+ const actions = [
+ {
+ label: "Delete SIT",
+ type: "POST",
+ icon: ,
+ url: "/api/RemoveSensitiveInfoType",
+ data: {
+ Identity: "Name",
+ },
+ confirmText:
+ "Are you sure you want to delete this Sensitive Information Type? Built-in Microsoft types cannot be deleted.",
+ color: "danger",
+ },
+ ];
+
+ const offCanvas = {
+ extendedInfoFields: [
+ "Name",
+ "Description",
+ "Publisher",
+ "Recommended",
+ "RulePackId",
+ "RulePackVersion",
+ "State",
+ "Type",
+ ],
+ actions: actions,
+ };
+
+ const simpleColumns = [
+ "Name",
+ "Publisher",
+ "Description",
+ "Recommended",
+ "RulePackVersion",
+ "State",
+ ];
+
+ return (
+
+ }
+ />
+ );
+};
+
+Page.getLayout = (page) => {page};
+export default Page;
diff --git a/src/pages/teams-share/onedrive/index.js b/src/pages/teams-share/onedrive/index.js
index b5ef1f7a2792..7c06e88f1441 100644
--- a/src/pages/teams-share/onedrive/index.js
+++ b/src/pages/teams-share/onedrive/index.js
@@ -8,10 +8,12 @@ const Page = () => {
const reportDB = useCippReportDB({
apiUrl: '/api/ListSites?type=OneDriveUsageAccount',
queryKey: 'ListSites-OneDriveUsageAccount',
- cacheName: 'OneDriveUsage',
- syncTitle: 'Sync OneDrive Usage',
+ cacheName: 'Sites',
+ syncTitle: 'Sync OneDrive Report',
+ syncData: { Types: 'OneDriveUsageAccount' },
allowToggle: true,
defaultCached: false,
+ allowAllTenantSync: true,
})
const actions = [
@@ -115,6 +117,6 @@ const Page = () => {
)
}
-Page.getLayout = (page) => {page}
+Page.getLayout = (page) => {page}
export default Page
diff --git a/src/pages/teams-share/sharepoint/index.js b/src/pages/teams-share/sharepoint/index.js
index ac08cec280cd..cf1353f52b7f 100644
--- a/src/pages/teams-share/sharepoint/index.js
+++ b/src/pages/teams-share/sharepoint/index.js
@@ -22,10 +22,12 @@ const Page = () => {
const reportDB = useCippReportDB({
apiUrl: '/api/ListSites?type=SharePointSiteUsage',
queryKey: 'ListSites-SharePointSiteUsage',
- cacheName: 'SharePointSiteUsage',
- syncTitle: 'Sync SharePoint Site Usage',
+ cacheName: 'Sites',
+ syncTitle: 'Sync SharePoint Sites Report',
+ syncData: { Types: 'SharePointSiteUsage' },
allowToggle: true,
- defaultCached: false,
+ defaultCached: true,
+ allowAllTenantSync: true,
})
const actions = [
@@ -270,6 +272,6 @@ const Page = () => {
)
}
-Page.getLayout = (page) => {page}
+Page.getLayout = (page) => {page}
export default Page
diff --git a/src/pages/teams-share/teams/business-voice/index.js b/src/pages/teams-share/teams/business-voice/index.js
index a3aa56764153..3c9354706147 100644
--- a/src/pages/teams-share/teams/business-voice/index.js
+++ b/src/pages/teams-share/teams/business-voice/index.js
@@ -1,10 +1,20 @@
import { Layout as DashboardLayout } from "../../../../layouts/index.js";
import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
+import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls";
import { PersonAdd, PersonRemove, LocationOn } from "@mui/icons-material";
const Page = () => {
const pageTitle = "Teams Business Voice";
+ const reportDB = useCippReportDB({
+ apiUrl: "/api/ListTeamsVoice",
+ queryKey: "ListTeamsVoice",
+ cacheName: "TeamsVoice",
+ syncTitle: "Sync Teams Business Voice Report",
+ allowToggle: true,
+ defaultCached: false,
+ });
+
const actions = [
// the modal dropdowns that were added below may not exist yet, and will need to be tested.
{
@@ -81,34 +91,40 @@ const Page = () => {
};
return (
-
+ <>
+
+ {reportDB.syncDialog}
+ >
);
};
-Page.getLayout = (page) => {page};
+Page.getLayout = (page) => {page};
export default Page;
diff --git a/src/pages/teams-share/teams/list-team/index.js b/src/pages/teams-share/teams/list-team/index.js
index bf48cccb08a3..99b51994bafe 100644
--- a/src/pages/teams-share/teams/list-team/index.js
+++ b/src/pages/teams-share/teams/list-team/index.js
@@ -1,13 +1,24 @@
import { Layout as DashboardLayout } from "../../../../layouts/index.js";
import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
import { Button } from "@mui/material";
+import { Stack } from "@mui/system";
import { Delete, GroupAdd } from "@mui/icons-material";
import Link from "next/link";
import { Edit } from "@mui/icons-material";
+import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls";
const Page = () => {
const pageTitle = "Teams";
+ const reportDB = useCippReportDB({
+ apiUrl: "/api/ListTeams?type=list",
+ queryKey: "ListTeams-list",
+ cacheName: "Teams",
+ syncTitle: "Sync Teams Report",
+ allowToggle: true,
+ defaultCached: false,
+ });
+
const actions = [
{
label: "Edit Group",
@@ -32,22 +43,34 @@ const Page = () => {
];
return (
-
- }>
- Add Team
-
- >
- }
- />
+ <>
+
+ }>
+ Add Team
+
+ {reportDB.controls}
+
+ }
+ />
+ {reportDB.syncDialog}
+ >
);
};
-Page.getLayout = (page) => {page};
+Page.getLayout = (page) => {page};
export default Page;
diff --git a/src/pages/teams-share/teams/teams-activity/index.js b/src/pages/teams-share/teams/teams-activity/index.js
index 2f2797a57cbb..f5fb2bb53754 100644
--- a/src/pages/teams-share/teams/teams-activity/index.js
+++ b/src/pages/teams-share/teams/teams-activity/index.js
@@ -1,18 +1,40 @@
import { Layout as DashboardLayout } from "../../../../layouts/index.js";
import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
+import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls";
const Page = () => {
const pageTitle = "Teams Activity List";
+ const reportDB = useCippReportDB({
+ apiUrl: "/api/ListTeamsActivity?type=TeamsUserActivityUser",
+ queryKey: "ListTeamsActivity-TeamsUserActivityUser",
+ cacheName: "TeamsActivity",
+ syncTitle: "Sync Teams Activity Report",
+ allowToggle: true,
+ defaultCached: false,
+ });
+
return (
-
+ <>
+
+ {reportDB.syncDialog}
+ >
);
};
-Page.getLayout = (page) => {page};
+Page.getLayout = (page) => {page};
export default Page;
diff --git a/src/pages/tenant/conditional/list-policies/edit.jsx b/src/pages/tenant/conditional/list-policies/edit.jsx
new file mode 100644
index 000000000000..156cd39f12b2
--- /dev/null
+++ b/src/pages/tenant/conditional/list-policies/edit.jsx
@@ -0,0 +1,75 @@
+import React, { useEffect, useState } from "react";
+import { Alert, Box } from "@mui/material";
+import { useForm } from "react-hook-form";
+import { useRouter } from "next/router";
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import CippFormPage from "../../../../components/CippFormPages/CippFormPage";
+import CippFormSkeleton from "../../../../components/CippFormPages/CippFormSkeleton";
+import { ApiGetCall } from "../../../../api/ApiCall";
+import CippCAPolicyBuilder, {
+ extractCAPolicyJSON,
+} from "../../../../components/CippComponents/CippCAPolicyBuilder";
+import { useSettings } from "../../../../hooks/use-settings.js";
+
+const EditCAPolicy = () => {
+ const router = useRouter();
+ const { id: policyId } = router.query;
+ const tenantFilter = useSettings()?.currentTenant;
+ const [policyData, setPolicyData] = useState(null);
+
+ const formControl = useForm({ mode: "onChange" });
+
+ // Fetch the current policies for this tenant
+ const policiesQuery = ApiGetCall({
+ url: `/api/ListConditionalAccessPolicies?tenantFilter=${tenantFilter}`,
+ queryKey: `CAPolicies-${tenantFilter}`,
+ enabled: !!policyId && !!tenantFilter,
+ });
+
+ useEffect(() => {
+ if (policiesQuery.isSuccess && policiesQuery.data?.Results) {
+ const match = policiesQuery.data.Results.find((p) => p.id === policyId);
+ if (match?.rawjson) {
+ const parsed = JSON.parse(match.rawjson);
+ setPolicyData(parsed);
+ }
+ }
+ }, [policiesQuery.isSuccess, policiesQuery.data, policyId]);
+
+ const dataFormatter = (values) => {
+ const cleaned = extractCAPolicyJSON(values);
+ return {
+ tenantFilter,
+ PolicyId: policyId,
+ PolicyBody: cleaned,
+ };
+ };
+
+ return (
+
+
+ {policiesQuery.isLoading ? (
+
+ ) : policiesQuery.isError ? (
+ Error loading policies.
+ ) : !policyData ? (
+ Policy not found for ID: {policyId}
+ ) : (
+
+ )}
+
+
+ );
+};
+
+EditCAPolicy.getLayout = (page) => {page};
+
+export default EditCAPolicy;
diff --git a/src/pages/tenant/conditional/list-policies/index.js b/src/pages/tenant/conditional/list-policies/index.js
index d7f48eac6694..1e85c99b3ebc 100644
--- a/src/pages/tenant/conditional/list-policies/index.js
+++ b/src/pages/tenant/conditional/list-policies/index.js
@@ -25,6 +25,13 @@ const Page = () => {
// Actions configuration
const actions = [
+ {
+ label: "Edit Policy",
+ link: "/tenant/conditional/list-policies/edit?id=[id]",
+ icon: ,
+ color: "info",
+ hideBulk: true,
+ },
{
label: "Create template based on policy",
type: "POST",
diff --git a/src/pages/tenant/conditional/list-template/create.jsx b/src/pages/tenant/conditional/list-template/create.jsx
new file mode 100644
index 000000000000..1843c369c323
--- /dev/null
+++ b/src/pages/tenant/conditional/list-template/create.jsx
@@ -0,0 +1,39 @@
+import React from "react";
+import { Box } from "@mui/material";
+import { useForm } from "react-hook-form";
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import CippFormPage from "../../../../components/CippFormPages/CippFormPage";
+import CippCAPolicyBuilder, { extractCAPolicyJSON } from "../../../../components/CippComponents/CippCAPolicyBuilder";
+
+const CreateCATemplate = () => {
+ const formControl = useForm({
+ mode: "onChange",
+ defaultValues: {
+ state: { label: "Report-only", value: "enabledForReportingButNotEnforced" },
+ },
+ });
+
+ const customDataFormatter = (values) => {
+ return extractCAPolicyJSON(values);
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+CreateCATemplate.getLayout = (page) => {page};
+
+export default CreateCATemplate;
diff --git a/src/pages/tenant/conditional/list-template/edit.jsx b/src/pages/tenant/conditional/list-template/edit.jsx
index 9521ddae99ed..6d82be777062 100644
--- a/src/pages/tenant/conditional/list-template/edit.jsx
+++ b/src/pages/tenant/conditional/list-template/edit.jsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
-import { Alert, Box, Typography } from "@mui/material";
+import { Alert, Box, Typography, ToggleButtonGroup, ToggleButton } from "@mui/material";
import { useForm } from "react-hook-form";
import { useRouter } from "next/router";
import { Layout as DashboardLayout } from "../../../../layouts/index.js";
@@ -7,15 +7,29 @@ import CippFormPage from "../../../../components/CippFormPages/CippFormPage";
import CippFormSkeleton from "../../../../components/CippFormPages/CippFormSkeleton";
import { ApiGetCall } from "../../../../api/ApiCall";
import CippTemplateFieldRenderer from "../../../../components/CippComponents/CippTemplateFieldRenderer";
+import CippCAPolicyBuilder, {
+ extractCAPolicyJSON,
+} from "../../../../components/CippComponents/CippCAPolicyBuilder";
const EditCATemplate = () => {
const router = useRouter();
const { GUID } = router.query;
const [templateData, setTemplateData] = useState(null);
const [originalData, setOriginalData] = useState(null);
+ const [editorMode, setEditorMode] = useState("builder");
const formControl = useForm({ mode: "onChange" });
+ // When switching to builder mode, reset the form to clear any empty []
+ // values that CippTemplateFieldRenderer may have injected
+ const handleEditorModeChange = (e, val) => {
+ if (!val) return;
+ if (val === "builder" && editorMode !== "builder") {
+ formControl.reset({});
+ }
+ setEditorMode(val);
+ };
+
// Fetch the template data
const templateQuery = ApiGetCall({
url: `/api/ListCATemplates?GUID=${GUID}`,
@@ -110,6 +124,14 @@ const EditCATemplate = () => {
};
};
+ // Build a data formatter that works for both editor modes
+ const builderDataFormatter = (values) => {
+ const cleaned = extractCAPolicyJSON(values);
+ return { GUID, ...cleaned };
+ };
+
+ const activeFormatter = editorMode === "builder" ? builderDataFormatter : customDataFormatter;
+
return (
{
queryKey={[`CATemplate-${GUID}`, "CATemplates"]}
backButtonTitle="Conditional Access Templates"
postUrl="/api/ExecEditTemplate?type=CATemplate"
- customDataformatter={customDataFormatter}
+ customDataformatter={activeFormatter}
formPageType="Edit"
+ titleButton={
+
+ Policy Builder
+ Field Editor
+
+ }
>
{templateQuery.isLoading ? (
) : templateQuery.isError || !templateData ? (
Error loading template or template not found.
+ ) : editorMode === "builder" ? (
+
) : (
{
const pageTitle = "Available Conditional Access Templates";
@@ -42,6 +43,37 @@ const Page = () => {
icon: ,
color: "info",
},
+ {
+ label: "Add to package",
+ type: "POST",
+ url: "/api/ExecSetPackageTag",
+ data: { GUID: "GUID" },
+ fields: [
+ {
+ type: "textField",
+ name: "Package",
+ label: "Package Name",
+ required: true,
+ validators: {
+ required: { value: true, message: "Package name is required" },
+ },
+ },
+ ],
+ confirmText: "Enter the package name to assign to the selected template(s).",
+ multiPost: true,
+ icon: ,
+ color: "info",
+ },
+ {
+ label: "Remove from package",
+ type: "POST",
+ url: "/api/ExecSetPackageTag",
+ data: { GUID: "GUID", Remove: true },
+ confirmText: "Are you sure you want to remove the selected template(s) from their package?",
+ multiPost: true,
+ icon: ,
+ color: "warning",
+ },
{
label: "Save to GitHub",
type: "POST",
@@ -110,10 +142,16 @@ const Page = () => {
queryKey="ListCATemplates-table"
actions={actions}
offCanvas={offCanvas}
- simpleColumns={["displayName", "GUID"]}
+ simpleColumns={["displayName", "package", "GUID"]}
cardButton={
-
+ }
+ >
+ Create Template
+
{
templateDetails.refetch()
},
currentTenant,
+ templateTenants: Array.isArray(selectedTemplate?.tenantFilter)
+ ? selectedTemplate.tenantFilter
+ : [],
+ excludedTenants: Array.isArray(selectedTemplate?.excludedTenants)
+ ? selectedTemplate.excludedTenants
+ : [],
}),
]
diff --git a/src/pages/tenant/manage/configuration-backup.js b/src/pages/tenant/manage/configuration-backup.js
index 15154dc5e258..2d839fa6466d 100644
--- a/src/pages/tenant/manage/configuration-backup.js
+++ b/src/pages/tenant/manage/configuration-backup.js
@@ -89,8 +89,8 @@ const Page = () => {
queryKey: `BackupTasks-${currentTenant}`,
});
- // Use the actual backup files as the backup data
- const filteredBackupData = Array.isArray(backupList.data) ? backupList.data : [];
+ // Use the actual backup files as the backup data — filter out any null entries
+ const filteredBackupData = Array.isArray(backupList.data) ? backupList.data.filter(Boolean) : [];
// Generate backup tags from actual API response items - use raw items directly
const generateBackupTags = (backup) => {
// Use the Items array directly from the API response without any translation
@@ -173,11 +173,12 @@ const Page = () => {
};
// Filter backup data by selected tenant if in AllTenants view
+ const selectedTenantValue = backupTenantFilter?.value ?? backupTenantFilter;
const tenantFilteredBackupData =
settings.currentTenant === "AllTenants" &&
- backupTenantFilter &&
- backupTenantFilter !== "AllTenants"
- ? filteredBackupData.filter((backup) => backup.TenantFilter === backupTenantFilter)
+ selectedTenantValue &&
+ selectedTenantValue !== "AllTenants"
+ ? filteredBackupData.filter((backup) => backup.TenantFilter === selectedTenantValue)
: filteredBackupData;
const backupDisplayItems = tenantFilteredBackupData.map((backup, index) => ({
@@ -188,7 +189,7 @@ const Page = () => {
tags: generateBackupTags(backup),
}));
- // Process existing backup configuration, find tenantFilter. by comparing settings.currentTenant with Tenant.value
+ // Process existing backup configuration
const currentConfig = Array.isArray(existingBackupConfig.data)
? existingBackupConfig.data.find(
(tenant) =>
@@ -383,8 +384,9 @@ const Page = () => {
No Backup Configuration
No backup schedule is currently configured for{" "}
- {settings.currentTenant === "AllTenants" ? "any tenant" : settings.currentTenant}.
- Click "Add Backup Schedule" to create an automated backup configuration.
+ {settings.currentTenant === "AllTenants" ? "AllTenants" : settings.currentTenant}.
+ Click "Add Backup Schedule" to create an automated backup configuration that will apply to all tenants.
+ A tenant specific backup can exist alongside a global backup, and will run according to its own schedule.
)}
diff --git a/src/pages/tenant/manage/drift.js b/src/pages/tenant/manage/drift.js
index 8c383590ed04..df2a3869dc38 100644
--- a/src/pages/tenant/manage/drift.js
+++ b/src/pages/tenant/manage/drift.js
@@ -95,6 +95,12 @@ const ManageDriftPage = () => {
queryKey: 'ListIntuneTemplates',
})
+ // API call to get all CA templates for displayName lookup
+ const caTemplatesApi = ApiGetCall({
+ url: '/api/ListCATemplates',
+ queryKey: 'ListCATemplates',
+ })
+
// API call for standards comparison (when templateId is available)
const comparisonApi = ApiGetCall({
url: '/api/ListStandardsCompare',
@@ -232,6 +238,14 @@ const ManageDriftPage = () => {
displayName = template.TemplateList.label
}
}
+ // If not found in standardSettings, look up in all CA templates (for tag templates)
+ if (!displayName && Array.isArray(caTemplatesApi.data)) {
+ const template = caTemplatesApi.data.find((t) => t.GUID === guid)
+ if (template?.displayName) {
+ displayName = template.displayName
+ }
+ }
+
// If template not found, return null to filter it out later
if (!displayName) {
return null
@@ -1362,6 +1376,7 @@ const ManageDriftPage = () => {
)
// Actions for the ActionsMenu
+ const currentDriftTemplate = standardsApi.data?.find((t) => t.GUID === templateId)
const actions = createDriftManagementActions({
templateId,
onRefresh: () => {
@@ -1375,6 +1390,12 @@ const ManageDriftPage = () => {
setTriggerReport(true)
},
currentTenant: tenantFilter,
+ templateTenants: Array.isArray(currentDriftTemplate?.tenantFilter)
+ ? currentDriftTemplate.tenantFilter
+ : [],
+ excludedTenants: Array.isArray(currentDriftTemplate?.excludedTenants)
+ ? currentDriftTemplate.excludedTenants
+ : [],
})
// Effect to trigger the ExecutiveReportButton when needed
diff --git a/src/pages/tenant/manage/driftManagementActions.js b/src/pages/tenant/manage/driftManagementActions.js
index 5d7cd8e80488..7b3f59c7bca0 100644
--- a/src/pages/tenant/manage/driftManagementActions.js
+++ b/src/pages/tenant/manage/driftManagementActions.js
@@ -1,5 +1,6 @@
-import React from "react";
-import { Edit, Sync, PlayArrow, PictureAsPdf } from "@mui/icons-material";
+import React from 'react'
+import { Edit, Sync, PlayArrow, PictureAsPdf } from '@mui/icons-material'
+import { CippFormTemplateTenantSelector } from '../../../components/CippComponents/CippFormTemplateTenantSelector.jsx'
/**
* Creates the standard drift management actions array
@@ -11,29 +12,31 @@ import { Edit, Sync, PlayArrow, PictureAsPdf } from "@mui/icons-material";
*/
export const createDriftManagementActions = ({
templateId,
- templateType = "classic",
+ templateType = 'classic',
showEditTemplate = false,
onRefresh,
onGenerateReport,
currentTenant,
+ templateTenants = [],
+ excludedTenants = [],
}) => {
const actions = [
{
- label: "Refresh Data",
+ label: 'Refresh Data',
icon: ,
noConfirm: true,
customFunction: onRefresh,
},
- ];
+ ]
// Add Generate Report action if handler is provided
if (onGenerateReport) {
actions.push({
- label: "Generate Report",
+ label: 'Generate Report',
icon: ,
noConfirm: true,
customFunction: onGenerateReport,
- });
+ })
}
// Add template-specific actions if templateId is available
@@ -41,57 +44,55 @@ export const createDriftManagementActions = ({
// Conditionally add Edit Template action
if (showEditTemplate) {
actions.push({
- label: "Edit Template",
+ label: 'Edit Template',
icon: ,
- color: "info",
+ color: 'info',
noConfirm: true,
customFunction: () => {
// Use Next.js router for internal navigation
- import("next/router")
+ 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}`;
- });
+ window.location.href = `/tenant/standards/templates/template?id=${templateId}&type=${templateType}`
+ })
},
- });
+ })
}
- actions.push(
- {
- label: `Run Standard Now (${currentTenant || "Currently Selected Tenant"})`,
- type: "GET",
- url: "/api/ExecStandardsRun",
- icon: ,
- data: {
- TemplateId: templateId,
- },
- confirmText: "Are you sure you want to force a run of this standard?",
- multiPost: false,
+ actions.push({
+ label: 'Run Standard Now',
+ type: 'GET',
+ url: '/api/ExecStandardsRun',
+ icon: ,
+ data: {
+ TemplateId: templateId,
},
- {
- label: "Run Standard Now (All Tenants in Template)",
- type: "GET",
- url: "/api/ExecStandardsRun",
- icon: ,
- data: {
- TemplateId: templateId,
- tenantFilter: "allTenants",
- },
- confirmText: "Are you sure you want to force a run of this standard?",
- multiPost: false,
- }
- );
+ customDataformatter: (_row, _action, formData) => ({
+ TemplateId: templateId,
+ tenantFilter: formData.tenantFilter?.value ?? formData.tenantFilter,
+ }),
+ children: ({ formHook }) => (
+
+ ),
+ confirmText: 'Are you sure you want to force a run of this standard?',
+ allowResubmit: true,
+ multiPost: false,
+ })
}
- return actions;
-};
+ return actions
+}
/**
* Default export for backward compatibility
*/
-export default createDriftManagementActions;
+export default createDriftManagementActions
diff --git a/src/pages/tenant/manage/policies-deployed.js b/src/pages/tenant/manage/policies-deployed.js
index d38f68d03698..616f5a64491f 100644
--- a/src/pages/tenant/manage/policies-deployed.js
+++ b/src/pages/tenant/manage/policies-deployed.js
@@ -1,6 +1,6 @@
-import { Layout as DashboardLayout } from "../../../layouts/index.js";
-import { useRouter } from "next/router";
-import { Policy, Security, AdminPanelSettings, Devices, ExpandMore } from "@mui/icons-material";
+import { Layout as DashboardLayout } from '../../../layouts/index.js'
+import { useRouter } from 'next/router'
+import { Policy, Security, AdminPanelSettings, Devices, ExpandMore } from '@mui/icons-material'
import {
Box,
Stack,
@@ -9,34 +9,34 @@ import {
AccordionSummary,
AccordionDetails,
Chip,
-} from "@mui/material";
-import { HeaderedTabbedLayout } from "../../../layouts/HeaderedTabbedLayout";
-import tabOptions from "./tabOptions.json";
-import { CippDataTable } from "../../../components/CippTable/CippDataTable";
-import { CippHead } from "../../../components/CippComponents/CippHead";
-import { ApiGetCall } from "../../../api/ApiCall";
-import standardsData from "../../../data/standards.json";
-import { createDriftManagementActions } from "./driftManagementActions";
-import { useSettings } from "../../../hooks/use-settings";
-import { CippAutoComplete } from "../../../components/CippComponents/CippAutocomplete";
-import { useEffect } from "react";
+} from '@mui/material'
+import { HeaderedTabbedLayout } from '../../../layouts/HeaderedTabbedLayout'
+import tabOptions from './tabOptions.json'
+import { CippDataTable } from '../../../components/CippTable/CippDataTable'
+import { CippHead } from '../../../components/CippComponents/CippHead'
+import { ApiGetCall } from '../../../api/ApiCall'
+import standardsData from '../../../data/standards.json'
+import { createDriftManagementActions } from './driftManagementActions'
+import { useSettings } from '../../../hooks/use-settings'
+import { CippAutoComplete } from '../../../components/CippComponents/CippAutocomplete'
+import { useEffect } from 'react'
const PoliciesDeployedPage = () => {
- const userSettingsDefaults = useSettings();
- const router = useRouter();
- const { templateId } = router.query;
- const tenantFilter = router.query.tenantFilter || userSettingsDefaults.tenantFilter;
- const currentTenant = userSettingsDefaults.currentTenant;
+ const userSettingsDefaults = useSettings()
+ const router = useRouter()
+ const { templateId } = router.query
+ const tenantFilter = router.query.tenantFilter || userSettingsDefaults.tenantFilter
+ const currentTenant = userSettingsDefaults.currentTenant
// API call to get standards template data
const standardsApi = ApiGetCall({
- url: "/api/listStandardTemplates",
- queryKey: "ListStandardsTemplates-Drift",
- });
+ url: '/api/listStandardTemplates',
+ queryKey: 'ListStandardsTemplates-Drift',
+ })
// API call to get standards comparison data
const comparisonApi = ApiGetCall({
- url: "/api/ListStandardsCompare",
+ url: '/api/ListStandardsCompare',
data: {
TemplateId: templateId,
TenantFilter: tenantFilter,
@@ -44,66 +44,70 @@ const PoliciesDeployedPage = () => {
},
queryKey: `StandardsCompare-${templateId}-${tenantFilter}`,
enabled: !!templateId && !!tenantFilter,
- });
+ })
// API call to get drift data for deviation statuses
const driftApi = ApiGetCall({
- url: "/api/listTenantDrift",
+ url: '/api/listTenantDrift',
data: {
tenantFilter: tenantFilter,
standardsId: templateId,
},
queryKey: `TenantDrift-${templateId}-${tenantFilter}`,
enabled: !!templateId && !!tenantFilter,
- });
+ })
// API call to get all Intune templates for displayName lookup
const intuneTemplatesApi = ApiGetCall({
- url: "/api/ListIntuneTemplates",
- queryKey: "ListIntuneTemplates",
- });
+ url: '/api/ListIntuneTemplates',
+ queryKey: 'ListIntuneTemplates',
+ })
+
+ // API call to get all CA templates for displayName lookup
+ const caTemplatesApi = ApiGetCall({
+ url: '/api/ListCATemplates',
+ queryKey: 'ListCATemplates',
+ })
// Find the current template from standards data
- const currentTemplate = (standardsApi.data || []).find(
- (template) => template.GUID === templateId
- );
- const templateStandards = currentTemplate?.standards || {};
- const comparisonData = comparisonApi.data?.[0] || {};
+ const currentTemplate = (standardsApi.data || []).find((template) => template.GUID === templateId)
+ const templateStandards = currentTemplate?.standards || {}
+ const comparisonData = comparisonApi.data?.[0] || {}
// Helper function to get status from comparison data with deviation status
const getStatus = (standardKey, templateValue = null, templateType = null) => {
- const comparisonKey = `standards.${standardKey}`;
- const comparisonItem = comparisonData[comparisonKey];
- const value = comparisonItem?.Value;
+ const comparisonKey = `standards.${standardKey}`
+ const comparisonItem = comparisonData[comparisonKey]
+ const value = comparisonItem?.Value
// If value is true, it's deployed and compliant
if (value === true) {
- return "Deployed";
+ return 'Deployed'
}
// Check if ExpectedValue and CurrentValue match (like drift.js does)
if (comparisonItem?.ExpectedValue && comparisonItem?.CurrentValue) {
try {
- const expectedStr = JSON.stringify(comparisonItem.ExpectedValue);
- const currentStr = JSON.stringify(comparisonItem.CurrentValue);
+ const expectedStr = JSON.stringify(comparisonItem.ExpectedValue)
+ const currentStr = JSON.stringify(comparisonItem.CurrentValue)
if (expectedStr === currentStr) {
- return "Deployed";
+ return 'Deployed'
}
} catch (e) {
- console.error("Error comparing values:", e);
+ console.error('Error comparing values:', e)
}
}
// If value is explicitly false, it means not deployed (not a deviation)
if (value === false) {
- return "Not Deployed";
+ return 'Not Deployed'
}
// If value is null/undefined, check drift data for deviation status
- const driftData = Array.isArray(driftApi.data) ? driftApi.data : [];
+ const driftData = Array.isArray(driftApi.data) ? driftApi.data : []
// For templates, we need to match against the full template path
- let searchKeys = [standardKey, `standards.${standardKey}`];
+ let searchKeys = [standardKey, `standards.${standardKey}`]
// Add template-specific search keys
if (templateValue && templateType) {
@@ -111,7 +115,7 @@ const PoliciesDeployedPage = () => {
`standards.${templateType}.${templateValue}`,
`${templateType}.${templateValue}`,
templateValue
- );
+ )
}
const deviation = driftData.find((item) =>
@@ -122,26 +126,26 @@ const PoliciesDeployedPage = () => {
item.standardName?.includes(key) ||
item.policyName?.includes(key)
)
- );
+ )
if (deviation && deviation.Status) {
- return `Deviation - ${deviation.Status}`;
+ return `Deviation - ${deviation.Status}`
}
// Only return "Deviation - New" if we have comparison data but value is null
if (comparisonItem) {
- return "Deviation - New";
+ return 'Deviation - New'
}
- return "Not Configured";
- };
+ return 'Not Configured'
+ }
// Helper function to get display name from drift data
const getDisplayNameFromDrift = (standardKey, templateValue = null, templateType = null) => {
- const driftData = Array.isArray(driftApi.data) ? driftApi.data : [];
+ const driftData = Array.isArray(driftApi.data) ? driftApi.data : []
// For templates, we need to match against the full template path
- let searchKeys = [standardKey, `standards.${standardKey}`];
+ let searchKeys = [standardKey, `standards.${standardKey}`]
// Add template-specific search keys
if (templateValue && templateType) {
@@ -149,7 +153,7 @@ const PoliciesDeployedPage = () => {
`standards.${templateType}.${templateValue}`,
`${templateType}.${templateValue}`,
templateValue
- );
+ )
}
const deviation = driftData.find((item) =>
@@ -160,277 +164,285 @@ const PoliciesDeployedPage = () => {
item.standardName?.includes(key) ||
item.policyName?.includes(key)
)
- );
+ )
// If found in drift data, return the display name
if (deviation?.standardDisplayName) {
- return deviation.standardDisplayName;
+ return deviation.standardDisplayName
}
// If not found in drift data and this is an Intune template, look it up in the Intune templates API
- if (templateType === "IntuneTemplate" && templateValue && intuneTemplatesApi.data) {
- const template = intuneTemplatesApi.data.find((t) => t.GUID === templateValue);
+ if (templateType === 'IntuneTemplate' && templateValue && intuneTemplatesApi.data) {
+ const template = intuneTemplatesApi.data.find((t) => t.GUID === templateValue)
if (template?.Displayname) {
- return template.Displayname;
+ return template.Displayname
+ }
+ }
+
+ // If not found in drift data and this is a CA template, look it up in the CA templates API
+ if (templateType === 'ConditionalAccessTemplate' && templateValue && caTemplatesApi.data) {
+ const template = caTemplatesApi.data.find((t) => t.GUID === templateValue)
+ if (template?.displayName) {
+ return template.displayName
}
}
- return null;
- };
+ return null
+ }
// Helper function to get last refresh date
const getLastRefresh = (standardKey) => {
- const comparisonKey = `standards.${standardKey}`;
- const lastRefresh = comparisonData[comparisonKey]?.LastRefresh;
- return lastRefresh ? new Date(lastRefresh).toLocaleDateString() : "N/A";
- };
+ const comparisonKey = `standards.${standardKey}`
+ const lastRefresh = comparisonData[comparisonKey]?.LastRefresh
+ return lastRefresh ? new Date(lastRefresh).toLocaleDateString() : 'N/A'
+ }
// Helper function to get standard name from standards.json
const getStandardName = (standardKey) => {
- const standardName = `standards.${standardKey}`;
- const standard = standardsData.find((s) => s.name === standardName);
- return standard?.label || standardKey.replace(/([A-Z])/g, " $1").trim();
- };
+ const standardName = `standards.${standardKey}`
+ const standard = standardsData.find((s) => s.name === standardName)
+ return standard?.label || standardKey.replace(/([A-Z])/g, ' $1').trim()
+ }
// Helper function to get template label from standards API data
const getTemplateLabel = (templateValue, templateType) => {
- if (!templateValue || !currentTemplate) return "Unknown Template";
+ if (!templateValue || !currentTemplate) return 'Unknown Template'
// Search through all templates in the current template data
- const allTemplates = currentTemplate.standards || {};
+ const allTemplates = currentTemplate.standards || {}
// Look for the template in the specific type array
if (allTemplates[templateType] && Array.isArray(allTemplates[templateType])) {
const template = allTemplates[templateType].find(
(t) => t.TemplateList?.value === templateValue
- );
+ )
if (template?.TemplateList?.label) {
- return template.TemplateList.label;
+ return template.TemplateList.label
}
}
// If not found in the specific type, search through all template types
for (const [key, templates] of Object.entries(allTemplates)) {
if (Array.isArray(templates)) {
- const template = templates.find((t) => t.TemplateList?.value === templateValue);
+ const template = templates.find((t) => t.TemplateList?.value === templateValue)
if (template?.TemplateList?.label) {
- return template.TemplateList.label;
+ return template.TemplateList.label
}
}
}
- return "Unknown Template";
- };
+ return 'Unknown Template'
+ }
// Process Security Standards (everything NOT IntuneTemplates or ConditionalAccessTemplates)
const deployedStandards = Object.entries(templateStandards)
- .filter(([key]) => key !== "IntuneTemplate" && key !== "ConditionalAccessTemplate")
+ .filter(([key]) => key !== 'IntuneTemplate' && key !== 'ConditionalAccessTemplate')
.map(([key, value], index) => ({
id: index + 1,
name: getStandardName(key),
- category: "Security Standard",
+ category: 'Security Standard',
status: getStatus(key),
lastModified: getLastRefresh(key),
standardKey: key,
- }));
+ }))
// Process Intune Templates
- const intunePolices = [];
- (templateStandards.IntuneTemplate || []).forEach((template, index) => {
- console.log("Processing IntuneTemplate in policies-deployed:", template);
+ const intunePolices = []
+ ;(templateStandards.IntuneTemplate || []).forEach((template, index) => {
+ console.log('Processing IntuneTemplate in policies-deployed:', template)
// Check if this template has TemplateList-Tags (try both property formats)
- const templateListTags = template["TemplateList-Tags"] || template.TemplateListTags;
+ const templateListTags = template['TemplateList-Tags'] || template.TemplateListTags
// Check if this template has TemplateList-Tags and expand them
if (templateListTags?.value && templateListTags?.addedFields?.templates) {
console.log(
- "Found TemplateList-Tags for IntuneTemplate in policies-deployed:",
+ 'Found TemplateList-Tags for IntuneTemplate in policies-deployed:',
templateListTags
- );
- console.log("Templates to expand:", templateListTags.addedFields.templates);
+ )
+ console.log('Templates to expand:', templateListTags.addedFields.templates)
// Expand TemplateList-Tags into multiple template items
templateListTags.addedFields.templates.forEach((expandedTemplate, expandedIndex) => {
- console.log("Expanding IntuneTemplate in policies-deployed:", expandedTemplate);
- const standardKey = `IntuneTemplate.${expandedTemplate.GUID}`;
+ console.log('Expanding IntuneTemplate in policies-deployed:', expandedTemplate)
+ const standardKey = `IntuneTemplate.${expandedTemplate.GUID}`
const driftDisplayName = getDisplayNameFromDrift(
standardKey,
expandedTemplate.GUID,
- "IntuneTemplate"
- );
- const packageTagName = templateListTags.value;
+ 'IntuneTemplate'
+ )
+ const packageTagName = templateListTags.value
const templateName =
- expandedTemplate.displayName || expandedTemplate.name || "Unknown Template";
+ expandedTemplate.displayName || expandedTemplate.name || 'Unknown Template'
intunePolices.push({
id: intunePolices.length + 1,
name: `${driftDisplayName || templateName} (via ${packageTagName})`,
- category: "Intune Template",
- platform: "Multi-Platform",
- status: getStatus(standardKey, expandedTemplate.GUID, "IntuneTemplate"),
+ category: 'Intune Template',
+ platform: 'Multi-Platform',
+ status: getStatus(standardKey, expandedTemplate.GUID, 'IntuneTemplate'),
lastModified: getLastRefresh(standardKey),
- assignedGroups: template.AssignTo || "N/A",
+ assignedGroups: template.AssignTo || 'N/A',
templateValue: expandedTemplate.GUID,
- });
- });
+ })
+ })
} else {
// Regular TemplateList processing
- const templateGuid = template.TemplateList?.value;
- const standardKey = `IntuneTemplate.${templateGuid}`;
- const driftDisplayName = getDisplayNameFromDrift(standardKey, templateGuid, "IntuneTemplate");
+ const templateGuid = template.TemplateList?.value
+ const standardKey = `IntuneTemplate.${templateGuid}`
+ const driftDisplayName = getDisplayNameFromDrift(standardKey, templateGuid, 'IntuneTemplate')
// Try multiple fallbacks for the name
- let templateName = driftDisplayName;
+ let templateName = driftDisplayName
if (!templateName) {
- const templateLabel = getTemplateLabel(templateGuid, "IntuneTemplate");
- if (templateLabel !== "Unknown Template") {
- templateName = `Intune - ${templateLabel}`;
+ const templateLabel = getTemplateLabel(templateGuid, 'IntuneTemplate')
+ if (templateLabel !== 'Unknown Template') {
+ templateName = `Intune - ${templateLabel}`
}
}
// If still no name, try looking up directly in intuneTemplatesApi by GUID
if (!templateName && templateGuid && intuneTemplatesApi.data) {
- const intuneTemplate = intuneTemplatesApi.data.find((t) => t.GUID === templateGuid);
+ const intuneTemplate = intuneTemplatesApi.data.find((t) => t.GUID === templateGuid)
if (intuneTemplate?.Displayname) {
- templateName = intuneTemplate.Displayname;
+ templateName = intuneTemplate.Displayname
}
}
// Final fallback
if (!templateName) {
- templateName = `Intune - ${templateGuid || "Unknown Template"}`;
+ templateName = `Intune - ${templateGuid || 'Unknown Template'}`
}
intunePolices.push({
id: intunePolices.length + 1,
name: templateName,
- category: "Intune Template",
- platform: "Multi-Platform",
- status: getStatus(standardKey, templateGuid, "IntuneTemplate"),
+ category: 'Intune Template',
+ platform: 'Multi-Platform',
+ status: getStatus(standardKey, templateGuid, 'IntuneTemplate'),
lastModified: getLastRefresh(standardKey),
- assignedGroups: template.AssignTo || "N/A",
+ assignedGroups: template.AssignTo || 'N/A',
templateValue: templateGuid,
- });
+ })
}
- });
+ })
// Add any templates from comparison data that weren't in template standards (e.g., from tags)
// Check for IntuneTemplate entries in comparison data
Object.keys(comparisonData).forEach((key) => {
- if (key.startsWith("standards.IntuneTemplate.")) {
- const guid = key.replace("standards.IntuneTemplate.", "");
+ if (key.startsWith('standards.IntuneTemplate.')) {
+ const guid = key.replace('standards.IntuneTemplate.', '')
// Check if this GUID is already in our list
- const alreadyExists = intunePolices.some((p) => p.templateValue === guid);
+ const alreadyExists = intunePolices.some((p) => p.templateValue === guid)
if (!alreadyExists && comparisonData[key]?.Value === true) {
- const standardKey = `IntuneTemplate.${guid}`;
- const driftDisplayName = getDisplayNameFromDrift(standardKey, guid, "IntuneTemplate");
+ const standardKey = `IntuneTemplate.${guid}`
+ const driftDisplayName = getDisplayNameFromDrift(standardKey, guid, 'IntuneTemplate')
intunePolices.push({
id: intunePolices.length + 1,
name: driftDisplayName || `Intune - ${guid}`,
- category: "Intune Template",
- platform: "Multi-Platform",
- status: getStatus(standardKey, guid, "IntuneTemplate"),
+ category: 'Intune Template',
+ platform: 'Multi-Platform',
+ status: getStatus(standardKey, guid, 'IntuneTemplate'),
lastModified: getLastRefresh(standardKey),
- assignedGroups: "N/A",
+ assignedGroups: 'N/A',
templateValue: guid,
- });
+ })
}
}
- });
+ })
// Process Conditional Access Templates
- const conditionalAccessPolicies = [];
- (templateStandards.ConditionalAccessTemplate || []).forEach((template, index) => {
- console.log("Processing ConditionalAccessTemplate in policies-deployed:", template);
+ const conditionalAccessPolicies = []
+ ;(templateStandards.ConditionalAccessTemplate || []).forEach((template, index) => {
+ console.log('Processing ConditionalAccessTemplate in policies-deployed:', template)
// Check if this template has TemplateList-Tags (try both property formats)
- const templateListTags = template["TemplateList-Tags"] || template.TemplateListTags;
+ const templateListTags = template['TemplateList-Tags'] || template.TemplateListTags
// Check if this template has TemplateList-Tags and expand them
if (templateListTags?.value && templateListTags?.addedFields?.templates) {
console.log(
- "Found TemplateList-Tags for ConditionalAccessTemplate in policies-deployed:",
+ 'Found TemplateList-Tags for ConditionalAccessTemplate in policies-deployed:',
templateListTags
- );
- console.log("Templates to expand:", templateListTags.addedFields.templates);
+ )
+ console.log('Templates to expand:', templateListTags.addedFields.templates)
// Expand TemplateList-Tags into multiple template items
templateListTags.addedFields.templates.forEach((expandedTemplate, expandedIndex) => {
- console.log("Expanding ConditionalAccessTemplate in policies-deployed:", expandedTemplate);
- const standardKey = `ConditionalAccessTemplate.${expandedTemplate.GUID}`;
+ console.log('Expanding ConditionalAccessTemplate in policies-deployed:', expandedTemplate)
+ const standardKey = `ConditionalAccessTemplate.${expandedTemplate.GUID}`
const driftDisplayName = getDisplayNameFromDrift(
standardKey,
expandedTemplate.GUID,
- "ConditionalAccessTemplate"
- );
- const packageTagName = templateListTags.value;
+ 'ConditionalAccessTemplate'
+ )
+ const packageTagName = templateListTags.value
const templateName =
- expandedTemplate.displayName || expandedTemplate.name || "Unknown Template";
+ expandedTemplate.displayName || expandedTemplate.name || 'Unknown Template'
conditionalAccessPolicies.push({
id: conditionalAccessPolicies.length + 1,
name: `${driftDisplayName || templateName} (via ${packageTagName})`,
- state: template.state || "Unknown",
- conditions: "Conditional Access Policy",
- controls: "Access Control",
+ state: template.state || 'Unknown',
+ conditions: 'Conditional Access Policy',
+ controls: 'Access Control',
lastModified: getLastRefresh(standardKey),
- status: getStatus(standardKey, expandedTemplate.GUID, "ConditionalAccessTemplate"),
+ status: getStatus(standardKey, expandedTemplate.GUID, 'ConditionalAccessTemplate'),
templateValue: expandedTemplate.GUID,
- });
- });
+ })
+ })
} else {
// Regular TemplateList processing
- const standardKey = `ConditionalAccessTemplate.${template.TemplateList?.value}`;
+ const standardKey = `ConditionalAccessTemplate.${template.TemplateList?.value}`
const driftDisplayName = getDisplayNameFromDrift(
standardKey,
template.TemplateList?.value,
- "ConditionalAccessTemplate"
- );
+ 'ConditionalAccessTemplate'
+ )
const templateLabel = getTemplateLabel(
template.TemplateList?.value,
- "ConditionalAccessTemplate"
- );
+ 'ConditionalAccessTemplate'
+ )
conditionalAccessPolicies.push({
id: conditionalAccessPolicies.length + 1,
name: driftDisplayName || `Conditional Access - ${templateLabel}`,
- state: template.state || "Unknown",
- conditions: "Conditional Access Policy",
- controls: "Access Control",
+ state: template.state || 'Unknown',
+ conditions: 'Conditional Access Policy',
+ controls: 'Access Control',
lastModified: getLastRefresh(standardKey),
- status: getStatus(standardKey, template.TemplateList?.value, "ConditionalAccessTemplate"),
+ status: getStatus(standardKey, template.TemplateList?.value, 'ConditionalAccessTemplate'),
templateValue: template.TemplateList?.value,
- });
+ })
}
- });
+ })
// Add any CA templates from comparison data that weren't in template standards
Object.keys(comparisonData).forEach((key) => {
- if (key.startsWith("standards.ConditionalAccessTemplate.")) {
- const guid = key.replace("standards.ConditionalAccessTemplate.", "");
+ if (key.startsWith('standards.ConditionalAccessTemplate.')) {
+ const guid = key.replace('standards.ConditionalAccessTemplate.', '')
// Check if this GUID is already in our list
- const alreadyExists = conditionalAccessPolicies.some((p) => p.templateValue === guid);
+ const alreadyExists = conditionalAccessPolicies.some((p) => p.templateValue === guid)
if (!alreadyExists && comparisonData[key]?.Value === true) {
- const standardKey = `ConditionalAccessTemplate.${guid}`;
+ const standardKey = `ConditionalAccessTemplate.${guid}`
const driftDisplayName = getDisplayNameFromDrift(
standardKey,
guid,
- "ConditionalAccessTemplate"
- );
+ 'ConditionalAccessTemplate'
+ )
conditionalAccessPolicies.push({
id: conditionalAccessPolicies.length + 1,
name: driftDisplayName || `Conditional Access - ${guid}`,
- state: "Unknown",
- conditions: "Conditional Access Policy",
- controls: "Access Control",
+ state: 'Unknown',
+ conditions: 'Conditional Access Policy',
+ controls: 'Access Control',
lastModified: getLastRefresh(standardKey),
- status: getStatus(standardKey, guid, "ConditionalAccessTemplate"),
+ status: getStatus(standardKey, guid, 'ConditionalAccessTemplate'),
templateValue: guid,
- });
+ })
}
}
- });
+ })
// Simple filter for all templates (no type filtering)
const templateOptions = standardsApi.data
@@ -442,35 +454,41 @@ const PoliciesDeployedPage = () => {
`Template ${template.GUID}`,
value: template.GUID,
}))
- : [];
+ : []
// Find currently selected template
const selectedTemplateOption =
templateId && templateOptions.length
? templateOptions.find((option) => option.value === templateId) || null
- : null;
+ : null
// Effect to refetch APIs when templateId changes (needed for shallow routing)
useEffect(() => {
if (templateId) {
- comparisonApi.refetch();
- driftApi.refetch();
+ comparisonApi.refetch()
+ driftApi.refetch()
}
- }, [templateId]);
+ }, [templateId])
const actions = createDriftManagementActions({
templateId,
- templateType: currentTemplate?.type || "classic",
+ templateType: currentTemplate?.type || 'classic',
showEditTemplate: true,
onRefresh: () => {
- standardsApi.refetch();
- comparisonApi.refetch();
- driftApi.refetch();
+ standardsApi.refetch()
+ comparisonApi.refetch()
+ driftApi.refetch()
},
currentTenant,
- });
- const title = "View Deployed Policies";
- const subtitle = [];
+ templateTenants: Array.isArray(currentTemplate?.tenantFilter)
+ ? currentTemplate.tenantFilter
+ : [],
+ excludedTenants: Array.isArray(currentTemplate?.excludedTenants)
+ ? currentTemplate.excludedTenants
+ : [],
+ })
+ const title = 'View Deployed Policies'
+ const subtitle = []
return (
{
{/* Filters Section */}
-
+
{
defaultValue={selectedTemplateOption}
value={selectedTemplateOption}
onChange={(selectedTemplate) => {
- const query = { ...router.query };
+ const query = { ...router.query }
if (selectedTemplate && selectedTemplate.value) {
- query.templateId = selectedTemplate.value;
+ query.templateId = selectedTemplate.value
} else {
- delete query.templateId;
+ delete query.templateId
}
router.replace(
{
@@ -507,7 +525,7 @@ const PoliciesDeployedPage = () => {
},
undefined,
{ shallow: true }
- );
+ )
}}
sx={{ width: 300 }}
placeholder="Select template..."
@@ -528,7 +546,7 @@ const PoliciesDeployedPage = () => {
{
title="Intune Templates"
data={intunePolices}
simpleColumns={[
- "name",
- "category",
- "platform",
- "status",
- "lastModified",
- "assignedGroups",
+ 'name',
+ 'category',
+ 'platform',
+ 'status',
+ 'lastModified',
+ 'assignedGroups',
]}
noCard={true}
isFetching={
@@ -580,12 +598,12 @@ const PoliciesDeployedPage = () => {
title="Conditional Access Templates"
data={conditionalAccessPolicies}
simpleColumns={[
- "name",
- "state",
- "status",
- "conditions",
- "controls",
- "lastModified",
+ 'name',
+ 'state',
+ 'status',
+ 'conditions',
+ 'controls',
+ 'lastModified',
]}
noCard={true}
isFetching={
@@ -597,9 +615,9 @@ const PoliciesDeployedPage = () => {
- );
-};
+ )
+}
-PoliciesDeployedPage.getLayout = (page) => {page};
+PoliciesDeployedPage.getLayout = (page) => {page}
-export default PoliciesDeployedPage;
+export default PoliciesDeployedPage
diff --git a/src/pages/tenant/reports/list-licenses/index.js b/src/pages/tenant/reports/list-licenses/index.js
index 35d61ea16158..4d877df75f8f 100644
--- a/src/pages/tenant/reports/list-licenses/index.js
+++ b/src/pages/tenant/reports/list-licenses/index.js
@@ -1,24 +1,92 @@
-import { Layout as DashboardLayout } from "../../../../layouts/index.js";
-import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
+import { Layout as DashboardLayout } from '../../../../layouts/index.js'
+import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx'
+import { AssignmentInd } from '@mui/icons-material'
+import CippFormComponent from '../../../../components/CippComponents/CippFormComponent'
const Page = () => {
- const pageTitle = "Licences Report";
- const apiUrl = "/api/ListLicenses";
+ const pageTitle = 'Licences Report'
+ const apiUrl = '/api/ListLicenses'
const simpleColumns = [
- "Tenant",
- "License",
- "CountUsed",
- "CountAvailable",
- "TotalLicenses",
- "AssignedUsers",
- "AssignedGroups",
- "TermInfo", // TODO TermInfo is not showing as a clickable json object in the table, like CApolicies does in the mfa report. IDK how to fix it. -Bobby
- ];
+ 'Tenant',
+ 'License',
+ 'CountUsed',
+ 'CountAvailable',
+ 'TotalLicenses',
+ 'AssignedUsers',
+ 'AssignedGroups',
+ 'TermInfo', // TODO TermInfo is not showing as a clickable json object in the table, like CApolicies does in the mfa report. IDK how to fix it. -Bobby
+ ]
- return ;
-};
+ const actions = [
+ {
+ label: 'Assign License to User',
+ type: 'POST',
+ url: '/api/ExecBulkLicense',
+ icon: ,
+ confirmText: 'Are you sure you want to assign [License] to the selected user?',
+ multiPost: false,
+ children: ({ formHook, row }) => (
+ `${option.displayName} (${option.userPrincipalName})`,
+ valueField: 'id',
+ queryKey: `Users-${row?.Tenant}`,
+ data: {
+ Endpoint: 'users',
+ $select: 'id,displayName,userPrincipalName',
+ $count: true,
+ $orderby: 'displayName',
+ $top: 999,
+ },
+ }}
+ />
+ ),
+ customDataformatter: (row, action, formData) => ({
+ tenantFilter: row.Tenant,
+ LicenseOperation: 'Add',
+ Licenses: [{ label: row.License, value: row.skuId }],
+ userIds: [formData.userIds?.value],
+ }),
+ },
+ ]
-Page.getLayout = (page) => {page};
+ const offCanvas = {
+ extendedInfoFields: [
+ 'Tenant',
+ 'License',
+ 'CountUsed',
+ 'CountAvailable',
+ 'TotalLicenses',
+ 'AssignedUsers',
+ 'AssignedGroups',
+ 'TermInfo',
+ ],
+ actions: actions,
+ }
-export default Page;
+ return (
+
+ )
+}
+
+Page.getLayout = (page) => {page}
+
+export default Page
diff --git a/src/pages/tenant/standards/alignment/index.js b/src/pages/tenant/standards/alignment/index.js
index 45238be2960a..60c05489bc24 100644
--- a/src/pages/tenant/standards/alignment/index.js
+++ b/src/pages/tenant/standards/alignment/index.js
@@ -1,16 +1,248 @@
import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx'
import { Layout as DashboardLayout } from '../../../../layouts/index.js'
import { TabbedLayout } from '../../../../layouts/TabbedLayout'
+import { ApiGetCallWithPagination } from '../../../../api/ApiCall'
+import { useSettings } from '../../../../hooks/use-settings'
import { Delete, Edit } from '@mui/icons-material'
-import { EyeIcon, ListBulletIcon, ChartBarIcon } from '@heroicons/react/24/outline'
+import { EyeIcon, ListBulletIcon, ChartBarIcon, Squares2X2Icon } from '@heroicons/react/24/outline'
import tabOptions from '../tabOptions.json'
-import { useState } from 'react'
-import { Box, Chip, Divider, Stack, Tooltip, Typography } from '@mui/material'
+import { useEffect, useMemo, useState } from 'react'
+import ReactMarkdown from 'react-markdown'
+import remarkGfm from 'remark-gfm'
+import {
+ Box,
+ Chip,
+ Divider,
+ Stack,
+ ToggleButton,
+ ToggleButtonGroup,
+ Tooltip,
+ Typography,
+} from '@mui/material'
import standardsData from '../../../../data/standards.json'
+const complianceColors = {
+ compliant: 'success',
+ 'non-compliant': 'error',
+ 'accepted deviation': 'info',
+ 'customer specific': 'info',
+ 'license missing': 'warning',
+ 'reporting disabled': 'default',
+}
+
+const compliancePriority = {
+ compliant: 10,
+ 'reporting disabled': 20,
+ 'customer specific': 30,
+ 'accepted deviation': 40,
+ 'license missing': 50,
+ 'non-compliant': 60,
+}
+
+const getComplianceStatus = (status) => String(status ?? 'Unknown').trim() || 'Unknown'
+
+const getComplianceColor = (status) =>
+ complianceColors[getComplianceStatus(status).toLowerCase()] ?? 'default'
+
+const getCompliancePriority = (status) =>
+ compliancePriority[getComplianceStatus(status).toLowerCase()] ?? 0
+
+const isAlignedComplianceStatus = (status) =>
+ ['compliant', 'accepted deviation', 'customer specific'].includes(
+ getComplianceStatus(status).toLowerCase()
+ )
+
+const getPageRows = (page) => {
+ if (Array.isArray(page)) return page
+ if (Array.isArray(page?.Results)) return page.Results
+ if (Array.isArray(page?.Data)) return page.Data
+ if (Array.isArray(page?.data)) return page.data
+ if (Array.isArray(page?.value)) return page.value
+ return []
+}
+
+const getStandardInfo = (standardId) => {
+ const baseName = standardId?.split('.').slice(0, -1).join('.')
+ return (
+ standardsData.find((s) => s.name === standardId) ??
+ standardsData.find((s) => s.name === baseName)
+ )
+}
+
const Page = () => {
const pageTitle = 'Standard & Drift Alignment'
- const [granular, setGranular] = useState(false)
+ const tenant = useSettings().currentTenant
+ const [viewMode, setViewMode] = useState('summary')
+ const [byStandardTenantFilter, setByStandardTenantFilter] = useState('all')
+ const isSummary = viewMode === 'summary'
+ const isGranular = viewMode === 'granular'
+ const isByStandard = viewMode === 'byStandard'
+
+ const {
+ data: byStandardApiData,
+ fetchNextPage: fetchNextByStandardPage,
+ hasNextPage: byStandardHasNextPage,
+ isFetching: byStandardIsFetching,
+ isSuccess: byStandardIsSuccess,
+ } = ApiGetCallWithPagination({
+ url: '/api/ListTenantAlignment',
+ data: { tenantFilter: tenant, granular: 'true' },
+ queryKey: `listTenantAlignment-byStandard-source-${tenant}`,
+ waiting: isByStandard,
+ })
+
+ useEffect(() => {
+ if (isByStandard && byStandardIsSuccess && byStandardHasNextPage && !byStandardIsFetching) {
+ fetchNextByStandardPage()
+ }
+ }, [
+ byStandardApiData?.pages?.length,
+ byStandardHasNextPage,
+ byStandardIsFetching,
+ byStandardIsSuccess,
+ fetchNextByStandardPage,
+ isByStandard,
+ ])
+
+ const byStandardSourceData = useMemo(
+ () => byStandardApiData?.pages?.flatMap((page) => getPageRows(page)) ?? [],
+ [byStandardApiData]
+ )
+
+ const byStandardData = useMemo(() => {
+ const groupedStandards = new Map()
+
+ byStandardSourceData.forEach((row) => {
+ const standardKey = row.standardId || row.standardName
+ if (!standardKey) return
+
+ const standardInfo = getStandardInfo(row.standardId)
+ const hasExactMatch = standardsData.find((s) => s.name === row.standardId)
+ const standardName = hasExactMatch
+ ? (standardInfo?.label ?? row.standardName ?? standardKey)
+ : (row.standardName ?? standardInfo?.label ?? standardKey)
+
+ if (!groupedStandards.has(standardKey)) {
+ groupedStandards.set(standardKey, {
+ standardId: standardKey,
+ standardName,
+ category: standardInfo?.cat ?? 'Uncategorized',
+ standardTypes: new Set(),
+ tenants: new Map(),
+ })
+ }
+
+ const standard = groupedStandards.get(standardKey)
+ const standardType = row.standardType ?? row.templateType
+ if (standardType) standard.standardTypes.add(standardType)
+
+ const tenantKey = row.tenantFilter ?? row.tenantName ?? row.Tenant ?? 'Unknown'
+ const status = getComplianceStatus(row.complianceStatus)
+ const tenant = standard.tenants.get(tenantKey) ?? {
+ tenantFilter: tenantKey,
+ complianceStatus: status,
+ rows: [],
+ }
+
+ tenant.rows.push(row)
+ if (getCompliancePriority(status) > getCompliancePriority(tenant.complianceStatus)) {
+ tenant.complianceStatus = status
+ }
+ standard.tenants.set(tenantKey, tenant)
+ })
+
+ return Array.from(groupedStandards.values())
+ .map((standard) => {
+ const tenants = Array.from(standard.tenants.values())
+ .map((tenant) => {
+ const templateNames = [
+ ...new Set(tenant.rows.map((row) => row.templateName).filter(Boolean)),
+ ]
+ const latestDataCollection = tenant.rows
+ .map((row) => row.latestDataCollection)
+ .filter(Boolean)
+ .sort((a, b) => new Date(b) - new Date(a))[0]
+
+ return {
+ tenantFilter: tenant.tenantFilter,
+ complianceStatus: tenant.complianceStatus,
+ templateName: templateNames.join(', ') || 'N/A',
+ latestDataCollection,
+ rowCount: tenant.rows.length,
+ rows: tenant.rows,
+ }
+ })
+ .sort((a, b) => a.tenantFilter.localeCompare(b.tenantFilter))
+
+ const counts = tenants.reduce(
+ (acc, tenant) => {
+ switch (getComplianceStatus(tenant.complianceStatus).toLowerCase()) {
+ case 'compliant':
+ acc.compliantCount += 1
+ break
+ case 'non-compliant':
+ acc.nonCompliantCount += 1
+ break
+ case 'accepted deviation':
+ acc.acceptedDeviationCount += 1
+ break
+ case 'customer specific':
+ acc.customerSpecificCount += 1
+ break
+ case 'license missing':
+ acc.licenseMissingCount += 1
+ break
+ case 'reporting disabled':
+ acc.reportingDisabledCount += 1
+ break
+ default:
+ acc.otherCount += 1
+ }
+ return acc
+ },
+ {
+ compliantCount: 0,
+ nonCompliantCount: 0,
+ acceptedDeviationCount: 0,
+ customerSpecificCount: 0,
+ licenseMissingCount: 0,
+ reportingDisabledCount: 0,
+ otherCount: 0,
+ }
+ )
+
+ const totalTenants = tenants.length
+ const alignedCount =
+ counts.compliantCount + counts.acceptedDeviationCount + counts.customerSpecificCount
+ const compliancePercentage = totalTenants
+ ? Math.round((alignedCount / totalTenants) * 100)
+ : 0
+ const licenseMissingPercentage = totalTenants
+ ? Math.round((counts.licenseMissingCount / totalTenants) * 100)
+ : 0
+
+ return {
+ standardId: standard.standardId,
+ standardName: standard.standardName,
+ category: standard.category,
+ standardType: Array.from(standard.standardTypes).sort().join(', ') || 'N/A',
+ totalTenants,
+ alignedCount,
+ compliancePercentage,
+ alignmentScore: compliancePercentage,
+ LicenseMissingPercentage: licenseMissingPercentage,
+ complianceScore: `${compliancePercentage}%`,
+ summaryStatus: compliancePercentage === 100 ? 'Fully Compliant' : 'Needs Attention',
+ hasNonCompliant: counts.nonCompliantCount > 0 ? 'Yes' : 'No',
+ hasLicenseMissing: counts.licenseMissingCount > 0 ? 'Yes' : 'No',
+ hasAcceptedDeviation: counts.acceptedDeviationCount > 0 ? 'Yes' : 'No',
+ isFullyCompliant: compliancePercentage === 100 ? 'Yes' : 'No',
+ tenants,
+ ...counts,
+ }
+ })
+ .sort((a, b) => a.standardName.localeCompare(b.standardName))
+ }, [byStandardSourceData])
const summaryFilterList = [
{
@@ -53,6 +285,29 @@ const Page = () => {
},
]
+ const byStandardFilterList = [
+ {
+ filterName: 'Fully Compliant',
+ value: [{ id: 'isFullyCompliant', value: 'Yes' }],
+ type: 'column',
+ },
+ {
+ filterName: 'Has Non-Compliant',
+ value: [{ id: 'hasNonCompliant', value: 'Yes' }],
+ type: 'column',
+ },
+ {
+ filterName: 'License Missing',
+ value: [{ id: 'hasLicenseMissing', value: 'Yes' }],
+ type: 'column',
+ },
+ {
+ filterName: 'Accepted Deviation',
+ value: [{ id: 'hasAcceptedDeviation', value: 'Yes' }],
+ type: 'column',
+ },
+ ]
+
const summaryActions = [
{
label: 'View Tenant Report',
@@ -178,16 +433,7 @@ const Page = () => {
standardsData.find((s) => s.name === baseName)?.label ??
row.standardName
- const complianceColors = {
- compliant: 'success',
- 'non-compliant': 'error',
- 'accepted deviation': 'info',
- 'customer specific': 'info',
- 'license missing': 'warning',
- 'reporting disabled': 'default',
- }
- const statusColor =
- complianceColors[String(row.complianceStatus ?? '').toLowerCase()] ?? 'default'
+ const statusColor = getComplianceColor(row.complianceStatus)
const properties = [
{ label: 'Standard', value: prettyName },
@@ -434,39 +680,245 @@ const Page = () => {
},
}
+ const byStandardOffCanvas = {
+ size: 'md',
+ title: 'Standard Tenant Summary',
+ contentPadding: 0,
+ children: (row) => {
+ const standardInfo = getStandardInfo(row.standardId)
+ const properties = [
+ { label: 'Standard', value: row.standardName },
+ { label: 'Category', value: row.category },
+ { label: 'Type', value: row.standardType },
+ { label: 'Tenants', value: row.totalTenants },
+ { label: 'Compliance', value: `${row.alignmentScore}%` },
+ { label: 'Licenses Missing', value: `${row.LicenseMissingPercentage}%` },
+ ]
+ const tenants = row.tenants ?? []
+ const compliantTenants = tenants.filter((tenant) =>
+ isAlignedComplianceStatus(tenant.complianceStatus)
+ )
+ const nonCompliantTenants = tenants.filter(
+ (tenant) => !isAlignedComplianceStatus(tenant.complianceStatus)
+ )
+ const filteredTenants =
+ byStandardTenantFilter === 'compliant'
+ ? compliantTenants
+ : byStandardTenantFilter === 'nonCompliant'
+ ? nonCompliantTenants
+ : tenants
+
+ return (
+
+ }
+ sx={{ borderBottom: '1px solid', borderColor: 'divider' }}
+ >
+ {properties.map(({ label, value }) => (
+
+
+ {label}
+
+
+ {value ?? 'N/A'}
+
+
+ ))}
+
+
+ {standardInfo?.helpText && (
+
+
+ Description
+
+
+ {standardInfo.helpText}
+
+
+ )}
+
+
+
+
+ Tenant Compliance
+
+ {
+ if (newFilter !== null) setByStandardTenantFilter(newFilter)
+ }}
+ sx={{
+ alignSelf: { xs: 'flex-start', sm: 'center' },
+ '& .MuiToggleButton-root': { py: 0.25, px: 1, fontSize: '0.75rem' },
+ }}
+ >
+ All ({tenants.length})
+ Compliant ({compliantTenants.length})
+
+ Noncompliant ({nonCompliantTenants.length})
+
+
+
+ {filteredTenants.length === 0 && (
+
+ No tenants match this filter.
+
+ )}
+ {filteredTenants.map((tenant) => (
+
+
+
+
+ {tenant.tenantFilter}
+
+
+ Template: {tenant.templateName}
+
+
+
+
+
+ Last Applied:{' '}
+ {tenant.latestDataCollection
+ ? new Date(tenant.latestDataCollection).toLocaleString()
+ : 'N/A'}
+ {tenant.rowCount > 1 ? ` (${tenant.rowCount} template matches)` : ''}
+
+
+ ))}
+
+
+ )
+ },
+ }
+
const modeToggle = (
-
-
- ) : (
-
- )
- }
- label={granular ? 'Per Standard' : 'Summary'}
- onClick={() => setGranular((v) => !v)}
- color="primary"
- variant="filled"
- size="small"
- clickable
- />
-
+ {
+ if (newViewMode !== null) setViewMode(newViewMode)
+ }}
+ sx={{ '& .MuiToggleButton-root': { py: 0.25, px: 1, fontSize: '0.75rem' } }}
+ >
+
+
+
+
+ Summary
+
+
+
+
+
+
+
+ Per Standard
+
+
+
+
+
+
+
+ By Standard
+
+
+
+
)
return (
{
'standardType',
'latestDataCollection',
]
- : [
- 'tenantFilter',
- 'standardName',
- 'standardType',
- 'alignmentScore',
- 'LicenseMissingPercentage',
- 'combinedAlignmentScore',
- 'currentDeviationsCount',
- ]
+ : isByStandard
+ ? [
+ 'standardName',
+ 'category',
+ 'standardType',
+ 'totalTenants',
+ 'tenants',
+ 'compliancePercentage',
+ 'LicenseMissingPercentage',
+ 'alignedCount',
+ 'compliantCount',
+ 'nonCompliantCount',
+ 'licenseMissingCount',
+ 'acceptedDeviationCount',
+ ]
+ : [
+ 'tenantFilter',
+ 'standardName',
+ 'standardType',
+ 'alignmentScore',
+ 'LicenseMissingPercentage',
+ 'combinedAlignmentScore',
+ 'pendingDeviationsCount',
+ 'deniedDeviationsCount',
+ ]
+ }
+ queryKey={
+ isGranular
+ ? 'listTenantAlignment-granular'
+ : isByStandard
+ ? 'listTenantAlignment-byStandard'
+ : 'listTenantAlignment'
}
- queryKey={granular ? 'listTenantAlignment-granular' : 'listTenantAlignment'}
- offCanvas={granular ? granularOffCanvas : undefined}
+ offCanvas={isGranular ? granularOffCanvas : isByStandard ? byStandardOffCanvas : undefined}
+ offCanvasOnRowClick={isByStandard}
cardButton={modeToggle}
/>
)
diff --git a/src/pages/tenant/standards/bpa-report/view.js b/src/pages/tenant/standards/bpa-report/view.js
index f85fb633a3a3..6abd0203b330 100644
--- a/src/pages/tenant/standards/bpa-report/view.js
+++ b/src/pages/tenant/standards/bpa-report/view.js
@@ -10,7 +10,7 @@ import { useEffect, useState } from "react";
import CippButtonCard from "../../../../components/CippCards/CippButtonCard";
import { CippDataTable } from "../../../../components/CippTable/CippDataTable";
import { CippImageCard } from "../../../../components/CippCards/CippImageCard";
-import _ from "lodash";
+import { get } from "lodash";
const Page = () => {
const router = useRouter();
const { id } = router.query;
@@ -52,8 +52,8 @@ const Page = () => {
const tenantData = bpaData?.data?.Data?.find((data) => data.GUID === tenantId);
const cards = frontendFields.map((field) => {
//instead of this, use lodash to get the data for blockData
- const blockData = _.get(tenantData, field.value)
- ? _.get(tenantData, field.value)
+ const blockData = get(tenantData, field.value)
+ ? get(tenantData, field.value)
: undefined;
return {
name: field.name,
diff --git a/src/pages/tenant/standards/templates/index.js b/src/pages/tenant/standards/templates/index.js
index b23752c50d9b..e6b82787f7db 100644
--- a/src/pages/tenant/standards/templates/index.js
+++ b/src/pages/tenant/standards/templates/index.js
@@ -1,152 +1,151 @@
-import { Alert, Button } from "@mui/material";
-import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
-import { Layout as DashboardLayout } from "../../../../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 { TabbedLayout } from "../../../../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 { Grid } from "@mui/system";
-import { CippApiResults } from "../../../../components/CippComponents/CippApiResults";
-import { EyeIcon } from "@heroicons/react/24/outline";
-import tabOptions from "../tabOptions.json";
-import { useSettings } from "../../../../hooks/use-settings.js";
-import { CippPolicyImportDrawer } from "../../../../components/CippComponents/CippPolicyImportDrawer.jsx";
-import { PermissionButton } from "../../../../utils/permissions.js";
+import { Alert, Button } from '@mui/material'
+import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx'
+import { Layout as DashboardLayout } from '../../../../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 { TabbedLayout } from '../../../../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 { Grid } from '@mui/system'
+import { CippApiResults } from '../../../../components/CippComponents/CippApiResults'
+import { EyeIcon } from '@heroicons/react/24/outline'
+import tabOptions from '../tabOptions.json'
+import { CippPolicyImportDrawer } from '../../../../components/CippComponents/CippPolicyImportDrawer.jsx'
+import { PermissionButton } from '../../../../utils/permissions.js'
+import { CippFormTemplateTenantSelector } from '../../../../components/CippComponents/CippFormTemplateTenantSelector.jsx'
const Page = () => {
- const oldStandards = ApiGetCall({ url: "/api/ListStandards", queryKey: "ListStandards-legacy" });
+ const oldStandards = ApiGetCall({ url: '/api/ListStandards', queryKey: 'ListStandards-legacy' })
const integrations = ApiGetCall({
- url: "/api/ListExtensionsConfig",
- queryKey: "Integrations",
+ url: '/api/ListExtensionsConfig',
+ queryKey: 'Integrations',
refetchOnMount: false,
refetchOnReconnect: false,
- });
+ })
- const currentTenant = useSettings().currentTenant;
- const pageTitle = "Templates";
- const cardButtonPermissions = ["Tenant.Standards.ReadWrite"];
+ const pageTitle = 'Templates'
+ const cardButtonPermissions = ['Tenant.Standards.ReadWrite']
const actions = [
{
- label: "View Tenant Report",
- link: "/tenant/manage/applied-standards/?templateId=[GUID]",
+ label: 'View Tenant Report',
+ link: '/tenant/manage/applied-standards/?templateId=[GUID]',
icon: ,
- color: "info",
- target: "_self",
+ color: 'info',
+ target: '_self',
},
{
- label: "Edit Template",
+ label: 'Edit Template',
//when using a link it must always be the full path /identity/administration/users/[id] for example.
- link: "/tenant/standards/templates/template?id=[GUID]&type=[type]",
+ link: '/tenant/standards/templates/template?id=[GUID]&type=[type]',
icon: ,
- color: "success",
- target: "_self",
+ color: 'success',
+ target: '_self',
},
{
- label: "Clone & Edit Template",
- link: "/tenant/standards/templates/template?id=[GUID]&clone=true&type=[type]",
+ label: 'Clone & Edit Template',
+ link: '/tenant/standards/templates/template?id=[GUID]&clone=true&type=[type]',
icon: ,
- color: "success",
- target: "_self",
+ color: 'success',
+ target: '_self',
},
{
- label: "Create Drift Clone",
- type: "POST",
- url: "/api/ExecDriftClone",
+ label: 'Create Drift Clone',
+ type: 'POST',
+ url: '/api/ExecDriftClone',
icon: ,
- color: "warning",
+ color: 'warning',
data: {
- id: "GUID",
+ id: 'GUID',
},
confirmText:
- "Are you sure you want to create a drift clone of [templateName]? This will create a new drift template based on this template.",
+ 'Are you sure you want to create a drift clone of [templateName]? This will create a new drift template based on this template.',
multiPost: false,
},
{
- label: `Run Template Now (${currentTenant || "Currently Selected Tenant"})`,
- type: "GET",
- url: "/api/ExecStandardsRun",
+ label: 'Run Template Now',
+ type: 'GET',
+ url: '/api/ExecStandardsRun',
icon: ,
data: {
- TemplateId: "GUID",
+ TemplateId: 'GUID',
},
- confirmText: "Are you sure you want to force a run of this template?",
+ allowResubmit: true,
+ customDataformatter: (row, action, formData) => ({
+ TemplateId: row.GUID,
+ tenantFilter: formData.tenantFilter?.value ?? formData.tenantFilter,
+ }),
+ children: ({ formHook, row }) => (
+
+ ),
+ confirmText: 'Are you sure you want to force a run of this template?',
multiPost: false,
},
{
- label: "Run Template Now (All Tenants in Template)",
- type: "GET",
- url: "/api/ExecStandardsRun",
- icon: ,
- data: {
- TemplateId: "GUID",
- tenantFilter: "allTenants",
- },
- confirmText: "Are you sure you want to force a run of this template?",
- multiPost: false,
- },
- {
- label: "Save to GitHub",
- type: "POST",
- url: "/api/ExecCommunityRepo",
+ label: 'Save to GitHub',
+ type: 'POST',
+ url: '/api/ExecCommunityRepo',
icon: ,
data: {
- Action: "UploadTemplate",
- GUID: "GUID",
+ Action: 'UploadTemplate',
+ GUID: 'GUID',
},
fields: [
{
- label: "Repository",
- name: "FullName",
- type: "select",
+ label: 'Repository',
+ name: 'FullName',
+ type: 'select',
api: {
- url: "/api/ListCommunityRepos",
+ url: '/api/ListCommunityRepos',
data: {
WriteAccess: true,
},
- queryKey: "CommunityRepos-Write",
- dataKey: "Results",
- valueField: "FullName",
- labelField: "FullName",
+ queryKey: 'CommunityRepos-Write',
+ dataKey: 'Results',
+ valueField: 'FullName',
+ labelField: 'FullName',
},
multiple: false,
creatable: false,
required: true,
validators: {
- required: { value: true, message: "This field is required" },
+ required: { value: true, message: 'This field is required' },
},
},
{
- label: "Commit Message",
- placeholder: "Enter a commit message for adding this file to GitHub",
- name: "Message",
- type: "textField",
+ label: 'Commit Message',
+ placeholder: 'Enter a commit message for adding this file to GitHub',
+ name: 'Message',
+ type: 'textField',
multiline: true,
required: true,
rows: 4,
},
],
- confirmText: "Are you sure you want to save this template to the selected repository?",
+ confirmText: 'Are you sure you want to save this template to the selected repository?',
condition: () => integrations.isSuccess && integrations?.data?.GitHub?.Enabled,
},
{
- label: "Delete Template",
- type: "POST",
- url: "/api/RemoveStandardTemplate",
+ label: 'Delete Template',
+ type: 'POST',
+ url: '/api/RemoveStandardTemplate',
icon: ,
data: {
- ID: "GUID",
+ ID: 'GUID',
},
- confirmText: "Are you sure you want to delete [templateName]?",
+ confirmText: 'Are you sure you want to delete [templateName]?',
multiPost: false,
},
- ];
- const conversionApi = ApiPostCall({ relatedQueryKeys: "listStandardTemplates" });
+ ]
+ const conversionApi = ApiPostCall({ relatedQueryKeys: 'listStandardTemplates' })
const handleConversion = () => {
conversionApi.mutate({
- url: "/api/execStandardConvert",
+ url: '/api/execStandardConvert',
data: {},
- });
- };
+ })
+ }
const tableFilter = (
{oldStandards.isSuccess && oldStandards.data.length !== 0 && (
@@ -154,7 +153,7 @@ const Page = () => {
You have legacy standards available. Press the button to convert these standards to
@@ -163,7 +162,7 @@ const Page = () => {
they are correct and re-enable the schedule.
- handleConversion()} variant={"contained"}>
+ handleConversion()} variant={'contained'}>
Convert Legacy Standards
@@ -175,7 +174,7 @@ const Page = () => {
)}
- );
+ )
return (
{
actions={actions}
tableFilter={tableFilter}
simpleColumns={[
- "templateName",
- "type",
- "tenantFilter",
- "excludedTenants",
- "updatedAt",
- "updatedBy",
- "runManually",
- "standards",
+ 'templateName',
+ 'type',
+ 'tenantFilter',
+ 'excludedTenants',
+ 'updatedAt',
+ 'updatedBy',
+ 'runManually',
+ 'standards',
]}
queryKey="listStandardTemplates"
/>
- );
-};
+ )
+}
Page.getLayout = (page) => (
{page}
-);
+)
-export default Page;
+export default Page
diff --git a/src/pages/tenant/standards/templates/template.jsx b/src/pages/tenant/standards/templates/template.jsx
index 19cf27c788f2..7e442863b3f1 100644
--- a/src/pages/tenant/standards/templates/template.jsx
+++ b/src/pages/tenant/standards/templates/template.jsx
@@ -1,205 +1,205 @@
-import { Box, Button, Container, Stack, Typography, SvgIcon, Skeleton } from "@mui/material";
-import { Grid } from "@mui/system";
-import { Layout as DashboardLayout } from "../../../../layouts/index.js";
-import { useForm, useWatch } from "react-hook-form";
-import { useRouter } from "next/router";
-import { Add, SaveRounded } from "@mui/icons-material";
-import { useEffect, useState, useCallback, useMemo, useRef, lazy, Suspense } from "react";
-import standards from "../../../../data/standards";
-import CippStandardAccordion from "../../../../components/CippStandards/CippStandardAccordion";
+import { Box, Button, Container, Stack, Typography, SvgIcon, Skeleton } from '@mui/material'
+import { Grid } from '@mui/system'
+import { Layout as DashboardLayout } from '../../../../layouts/index.js'
+import { useForm, useWatch } from 'react-hook-form'
+import { useRouter } from 'next/router'
+import { Add, SaveRounded } from '@mui/icons-material'
+import { useEffect, useState, useCallback, useMemo, useRef, lazy, Suspense } from 'react'
+import standards from '../../../../data/standards'
+import CippStandardAccordion from '../../../../components/CippStandards/CippStandardAccordion'
// Lazy load the dialog to improve initial page load performance
const CippStandardDialog = lazy(
- () => import("../../../../components/CippStandards/CippStandardDialog"),
-);
-import CippStandardsSideBar from "../../../../components/CippStandards/CippStandardsSideBar";
-import { ArrowLeftIcon } from "@mui/x-date-pickers";
-import { useDialog } from "../../../../hooks/use-dialog";
-import { ApiGetCall } from "../../../../api/ApiCall";
-import _ from "lodash";
-import { createDriftManagementActions } from "../../manage/driftManagementActions";
-import { ActionsMenu } from "../../../../components/actions-menu";
-import { useSettings } from "../../../../hooks/use-settings";
-import { CippHead } from "../../../../components/CippComponents/CippHead";
+ () => import('../../../../components/CippStandards/CippStandardDialog')
+)
+import CippStandardsSideBar from '../../../../components/CippStandards/CippStandardsSideBar'
+import { ArrowLeftIcon } from '@mui/x-date-pickers'
+import { useDialog } from '../../../../hooks/use-dialog'
+import { ApiGetCall } from '../../../../api/ApiCall'
+import { get } from 'lodash'
+import { createDriftManagementActions } from '../../manage/driftManagementActions'
+import { ActionsMenu } from '../../../../components/actions-menu'
+import { useSettings } from '../../../../hooks/use-settings'
+import { CippHead } from '../../../../components/CippComponents/CippHead'
const Page = () => {
- const router = useRouter();
- const [editMode, setEditMode] = useState(false);
- const formControl = useForm({ mode: "onBlur" });
- const { formState } = formControl;
- const [dialogOpen, setDialogOpen] = useState(false);
- const [expanded, setExpanded] = useState(null);
- const [searchQuery, setSearchQuery] = useState("");
- const [selectedStandards, setSelectedStandards] = useState({});
- const [updatedAt, setUpdatedAt] = useState(false);
- const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
- const [currentStep, setCurrentStep] = useState(0);
- const [hasDriftConflict, setHasDriftConflict] = useState(false);
- const initialStandardsRef = useRef({});
-
- const currentTenant = useSettings().currentTenant;
+ const router = useRouter()
+ const [editMode, setEditMode] = useState(false)
+ const formControl = useForm({ mode: 'onBlur' })
+ const { formState } = formControl
+ const [dialogOpen, setDialogOpen] = useState(false)
+ const [expanded, setExpanded] = useState(null)
+ const [searchQuery, setSearchQuery] = useState('')
+ const [selectedStandards, setSelectedStandards] = useState({})
+ const [updatedAt, setUpdatedAt] = useState(false)
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
+ const [currentStep, setCurrentStep] = useState(0)
+ const [hasDriftConflict, setHasDriftConflict] = useState(false)
+ const initialStandardsRef = useRef({})
+
+ const currentTenant = useSettings().currentTenant
// Check if this is drift mode
- const isDriftMode = router.query.type === "drift";
+ const isDriftMode = router.query.type === 'drift'
// Set drift mode flag in form when in drift mode
useEffect(() => {
if (isDriftMode) {
- formControl.setValue("isDriftTemplate", true);
+ formControl.setValue('isDriftTemplate', true)
}
- }, [isDriftMode, formControl]);
+ }, [isDriftMode, formControl])
// Watch form values to check valid configuration
- const watchForm = useWatch({ control: formControl.control });
+ const watchForm = useWatch({ control: formControl.control })
const existingTemplate = ApiGetCall({
url: `/api/listStandardTemplates`,
data: { id: router.query.id },
queryKey: `listStandardTemplates-${router.query.id}`,
waiting: editMode,
- });
+ })
// Check if the template configuration is valid and update currentStep
useEffect(() => {
const stepsStatus = {
- step1: !!_.get(watchForm, "templateName"),
- step2: _.get(watchForm, "tenantFilter", []).length > 0,
+ step1: !!get(watchForm, 'templateName'),
+ step2: get(watchForm, 'tenantFilter', []).length > 0,
step3: Object.keys(selectedStandards).length > 0,
step4:
- _.get(watchForm, "standards") &&
+ get(watchForm, 'standards') &&
Object.keys(selectedStandards).length > 0 &&
Object.keys(selectedStandards).every((standardName) => {
- const standardValues = _.get(watchForm, standardName, {});
+ const standardValues = get(watchForm, standardName, {})
// Always require an action value which should be an array with at least one element
- const actionValue = _.get(standardValues, "action");
- return actionValue && (!Array.isArray(actionValue) || actionValue.length > 0);
+ const actionValue = get(standardValues, 'action')
+ return actionValue && (!Array.isArray(actionValue) || actionValue.length > 0)
}),
- };
+ }
- const completedSteps = Object.values(stepsStatus).filter(Boolean).length;
- setCurrentStep(completedSteps);
- }, [selectedStandards, watchForm, isDriftMode]);
+ const completedSteps = Object.values(stepsStatus).filter(Boolean).length
+ setCurrentStep(completedSteps)
+ }, [selectedStandards, watchForm, isDriftMode])
// Handle route change events
const handleRouteChange = useCallback(
(url) => {
if (hasUnsavedChanges) {
const confirmLeave = window.confirm(
- "You have unsaved changes. Are you sure you want to leave this page?",
- );
+ 'You have unsaved changes. Are you sure you want to leave this page?'
+ )
if (!confirmLeave) {
- router.events.emit("routeChangeError");
- throw "Route change was aborted";
+ router.events.emit('routeChangeError')
+ throw 'Route change was aborted'
}
}
},
- [hasUnsavedChanges, router],
- );
+ [hasUnsavedChanges, router]
+ )
// Handle browser back/forward navigation or tab close
useEffect(() => {
const handleBeforeUnload = (e) => {
if (hasUnsavedChanges) {
- e.preventDefault();
- e.returnValue = "You have unsaved changes. Are you sure you want to leave this page?";
- return e.returnValue;
+ e.preventDefault()
+ e.returnValue = 'You have unsaved changes. Are you sure you want to leave this page?'
+ return e.returnValue
}
- };
+ }
// Add event listeners
- window.addEventListener("beforeunload", handleBeforeUnload);
- router.events.on("routeChangeStart", handleRouteChange);
+ window.addEventListener('beforeunload', handleBeforeUnload)
+ router.events.on('routeChangeStart', handleRouteChange)
// Remove event listeners on cleanup
return () => {
- window.removeEventListener("beforeunload", handleBeforeUnload);
- router.events.off("routeChangeStart", handleRouteChange);
- };
- }, [hasUnsavedChanges, handleRouteChange, router.events]);
+ window.removeEventListener('beforeunload', handleBeforeUnload)
+ router.events.off('routeChangeStart', handleRouteChange)
+ }
+ }, [hasUnsavedChanges, handleRouteChange, router.events])
// Track form changes
useEffect(() => {
// Compare the current form values with the initial values to check for real changes
- const currentValues = formControl.getValues();
- const initialValues = initialStandardsRef.current;
+ const currentValues = formControl.getValues()
+ const initialValues = initialStandardsRef.current
if (
formState.isDirty ||
JSON.stringify(selectedStandards) !== JSON.stringify(initialStandardsRef.current)
) {
- setHasUnsavedChanges(true);
+ setHasUnsavedChanges(true)
} else {
- setHasUnsavedChanges(false);
+ setHasUnsavedChanges(false)
}
- }, [formState.isDirty, selectedStandards, formControl]);
+ }, [formState.isDirty, selectedStandards, formControl])
useEffect(() => {
if (router.query.id) {
- setEditMode(true);
+ setEditMode(true)
}
if (existingTemplate.isSuccess) {
//formControl.reset(existingTemplate.data?.[0]);
- const apiData = existingTemplate.data?.[0];
+ const apiData = existingTemplate.data?.[0]
Object.keys(apiData.standards).forEach((key) => {
if (Array.isArray(apiData.standards[key])) {
apiData.standards[key] = apiData.standards[key].filter(
- (value) => value !== null && value !== undefined,
- );
+ (value) => value !== null && value !== undefined
+ )
}
- });
+ })
- formControl.reset(apiData);
+ formControl.reset(apiData)
if (router.query.clone) {
- formControl.setValue("templateName", `${apiData.templateName} (Clone)`);
- formControl.setValue("GUID", "");
+ formControl.setValue('templateName', `${apiData.templateName} (Clone)`)
+ formControl.setValue('GUID', '')
}
//set the updated at date and user
setUpdatedAt({
date: apiData?.updatedAt,
user: apiData?.updatedBy,
- });
+ })
// Transform standards from the API to match the format for selectedStandards
- const standardsFromApi = apiData?.standards;
- const transformedStandards = {};
+ const standardsFromApi = apiData?.standards
+ const transformedStandards = {}
Object.keys(standardsFromApi).forEach((key) => {
if (Array.isArray(standardsFromApi[key])) {
standardsFromApi[key].forEach((_, index) => {
- transformedStandards[`standards.${key}[${index}]`] = true;
- });
+ transformedStandards[`standards.${key}[${index}]`] = true
+ })
} else {
- transformedStandards[`standards.${key}`] = true;
+ transformedStandards[`standards.${key}`] = true
}
- });
+ })
- setSelectedStandards(transformedStandards);
+ setSelectedStandards(transformedStandards)
// Store initial state for change detection
- initialStandardsRef.current = { ...transformedStandards };
- setHasUnsavedChanges(false);
+ initialStandardsRef.current = { ...transformedStandards }
+ setHasUnsavedChanges(false)
}
- }, [existingTemplate.isSuccess, router]);
+ }, [existingTemplate.isSuccess, router])
// Memoize categories to avoid unnecessary recalculations
const categories = useMemo(() => {
return standards.reduce((acc, standard) => {
- const { cat } = standard;
+ const { cat } = standard
if (!acc[cat]) {
- acc[cat] = [];
+ acc[cat] = []
}
- acc[cat].push(standard);
- return acc;
- }, {});
- }, []);
+ acc[cat].push(standard)
+ return acc
+ }, {})
+ }, [])
const handleOpenDialog = useCallback(() => {
- setDialogOpen(true);
- }, []);
+ setDialogOpen(true)
+ }, [])
const handleCloseDialog = useCallback(() => {
- setDialogOpen(false);
- setSearchQuery("");
- }, []);
+ setDialogOpen(false)
+ setSearchQuery('')
+ }, [])
const filterStandards = (standardsList) =>
standardsList.filter(
@@ -207,149 +207,161 @@ const Page = () => {
standard.label.toLowerCase().includes(searchQuery.toLowerCase()) ||
standard.helpText.toLowerCase().includes(searchQuery.toLowerCase()) ||
(standard.tag &&
- standard.tag.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase()))),
- );
+ standard.tag.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase()))) ||
+ (standard.appliesToTest &&
+ standard.appliesToTest.some((testId) =>
+ testId.toLowerCase().includes(searchQuery.toLowerCase())
+ ))
+ )
const handleToggleStandard = (standardName) => {
setSelectedStandards((prev) => ({
...prev,
[standardName]: !prev[standardName],
- }));
- };
+ }))
+ }
const handleAddMultipleStandard = (standardName) => {
//if the standardname contains an array qualifier,e.g standardName[0], strip that away.
- const arrayPattern = /(.*)\[(\d+)\]$/;
- const match = standardName.match(arrayPattern);
+ const arrayPattern = /(.*)\[(\d+)\]$/
+ const match = standardName.match(arrayPattern)
if (match) {
- standardName = match[1];
+ standardName = match[1]
}
setSelectedStandards((prev) => {
- const existingInstances = Object.keys(prev).filter((name) => name.startsWith(standardName));
- const newIndex = existingInstances.length;
+ const existingInstances = Object.keys(prev).filter((name) => name.startsWith(standardName))
+ const newIndex = existingInstances.length
return {
...prev,
[`${standardName}[${newIndex}]`]: true,
- };
- });
- };
+ }
+ })
+ }
const handleRemoveStandard = (standardName) => {
- const arrayPattern = /(.*)\[(\d+)\]$/;
- const match = standardName.match(arrayPattern);
+ const arrayPattern = /(.*)\[(\d+)\]$/
+ const match = standardName.match(arrayPattern)
if (match) {
- const baseName = match[1];
- const removedIndex = parseInt(match[2]);
+ const baseName = match[1]
+ const removedIndex = parseInt(match[2])
// Remove the item from the form array
- const currentArray = formControl.getValues(baseName) || [];
- const updatedArray = currentArray.filter((_, i) => i !== removedIndex);
- formControl.setValue(baseName, updatedArray);
+ const currentArray = formControl.getValues(baseName) || []
+ const updatedArray = currentArray.filter((_, i) => i !== removedIndex)
+ formControl.setValue(baseName, updatedArray)
// Re-index selectedStandards to keep indices contiguous
- const escapedBaseName = baseName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- const reindexPattern = new RegExp(`^${escapedBaseName}\\[(\\d+)\\]$`);
+ const escapedBaseName = baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ const reindexPattern = new RegExp(`^${escapedBaseName}\\[(\\d+)\\]$`)
setSelectedStandards((prev) => {
- const newSelected = {};
+ const newSelected = {}
Object.keys(prev).forEach((key) => {
- const keyMatch = key.match(reindexPattern);
+ const keyMatch = key.match(reindexPattern)
if (keyMatch) {
- const idx = parseInt(keyMatch[1]);
+ const idx = parseInt(keyMatch[1])
if (idx < removedIndex) {
- newSelected[key] = prev[key];
+ newSelected[key] = prev[key]
} else if (idx > removedIndex) {
// Shift higher indices down by 1
- newSelected[`${baseName}[${idx - 1}]`] = prev[key];
+ newSelected[`${baseName}[${idx - 1}]`] = prev[key]
}
// Skip the removed index
} else {
- newSelected[key] = prev[key];
+ newSelected[key] = prev[key]
}
- });
- return newSelected;
- });
+ })
+ return newSelected
+ })
} else {
setSelectedStandards((prev) => {
- const newSelected = { ...prev };
- delete newSelected[standardName];
- return newSelected;
- });
- formControl.unregister(standardName);
+ const newSelected = { ...prev }
+ delete newSelected[standardName]
+ return newSelected
+ })
+ formControl.unregister(standardName)
}
- };
+ }
const handleAccordionToggle = (standardName) => {
- setExpanded((prev) => (prev === standardName ? null : standardName));
- };
+ setExpanded((prev) => (prev === standardName ? null : standardName))
+ }
- const createDialog = useDialog();
+ const createDialog = useDialog()
// Save action that will open the create dialog
const handleSave = () => {
- createDialog.handleOpen();
+ createDialog.handleOpen()
// Will be set to false after successful save in the dialog component
- };
+ }
// Determine if save button should be disabled based on configuration
const isSaveDisabled = isDriftMode
- ? !_.get(watchForm, "tenantFilter") ||
- !_.get(watchForm, "tenantFilter").length ||
+ ? !get(watchForm, 'tenantFilter') ||
+ !get(watchForm, 'tenantFilter').length ||
currentStep < 4 ||
hasDriftConflict // For drift mode, require all steps and no drift conflicts
- : !_.get(watchForm, "tenantFilter") ||
- !_.get(watchForm, "tenantFilter").length ||
- currentStep < 4;
+ : !get(watchForm, 'tenantFilter') ||
+ !get(watchForm, 'tenantFilter').length ||
+ currentStep < 4
// Create drift management actions (excluding refresh)
const driftActions = useMemo(() => {
- if (!editMode || !router.query.id) return [];
+ if (!editMode || !router.query.id) return []
const allActions = createDriftManagementActions({
templateId: router.query.id,
- onRefresh: () => {}, // Empty function since we're filtering out refresh
+ onRefresh: () => {},
currentTenant: currentTenant,
- });
+ templateTenants: Array.isArray(watchForm?.tenantFilter) ? watchForm.tenantFilter : [],
+ excludedTenants: Array.isArray(watchForm?.excludedTenants) ? watchForm.excludedTenants : [],
+ })
// Filter out the refresh action
- return allActions.filter((action) => action.label !== "Refresh Data");
- }, [editMode, router.query.id, currentTenant]);
+ return allActions.filter((action) => action.label !== 'Refresh Data')
+ }, [
+ editMode,
+ router.query.id,
+ currentTenant,
+ watchForm?.tenantFilter,
+ watchForm?.excludedTenants,
+ ])
- const actions = [];
+ const actions = []
const steps = [
- "Set a name for the Template",
- "Assigned Template to Tenants",
- "Added Standards to Template",
- "Configured all Standards",
- ];
+ 'Set a name for the Template',
+ 'Assigned Template to Tenants',
+ 'Added Standards to Template',
+ 'Configured all Standards',
+ ]
const handleSafeNavigation = (url) => {
if (hasUnsavedChanges) {
const confirmLeave = window.confirm(
- "You have unsaved changes. Are you sure you want to leave this page?",
- );
+ 'You have unsaved changes. Are you sure you want to leave this page?'
+ )
if (confirmLeave) {
- router.push(url);
+ router.push(url)
}
} else {
- router.push(url);
+ router.push(url)
}
- };
+ }
return (
-
+
@@ -364,11 +376,11 @@ const Page = () => {
{editMode
? isDriftMode
- ? "Edit Drift Template"
- : "Edit Standards Template"
+ ? 'Edit Drift Template'
+ : 'Edit Standards Template'
: isDriftMode
- ? "Add Drift Template"
- : "Add Standards Template"}
+ ? 'Add Drift Template'
+ : 'Add Standards Template'}
{
-
-
+
+
{/* Left Column for Accordions */}
-
+
{
onDriftConflictChange={setHasDriftConflict}
onSaveSuccess={() => {
// Reset unsaved changes flag
- setHasUnsavedChanges(false);
+ setHasUnsavedChanges(false)
// Update reference for future change detection
- initialStandardsRef.current = { ...selectedStandards };
+ initialStandardsRef.current = { ...selectedStandards }
}}
/>
-
+
{/* Show accordions based on selectedStandards (which is populated by API when editing) */}
{existingTemplate.isLoading ? (
@@ -462,9 +474,9 @@ const Page = () => {
)}
- );
-};
+ )
+}
-Page.getLayout = (page) => {page};
+Page.getLayout = (page) => {page}
-export default Page;
+export default Page
diff --git a/src/pages/tenant/tools/geoiplookup/index.js b/src/pages/tenant/tools/geoiplookup/index.js
index 162e93a3b5a1..8f6daa37ad3e 100644
--- a/src/pages/tenant/tools/geoiplookup/index.js
+++ b/src/pages/tenant/tools/geoiplookup/index.js
@@ -102,9 +102,9 @@ const Page = () => {
name="ipAddress"
type="textField"
validators={{
- validate: (value) => getCippValidator(value, "ip"),
+ validate: (value) => getCippValidator(value, "ipAny"),
}}
- placeholder="Enter IP Address"
+ placeholder="Enter IP Address (IPv4 or IPv6)"
required
/>
diff --git a/src/pages/tools/custom-tests/add.jsx b/src/pages/tools/custom-tests/add.jsx
index b101c5869550..e4da31a623c5 100644
--- a/src/pages/tools/custom-tests/add.jsx
+++ b/src/pages/tools/custom-tests/add.jsx
@@ -37,6 +37,7 @@ import { renderCustomScriptMarkdownTemplate } from '../../../utils/customScriptT
import { useSettings } from '../../../hooks/use-settings'
import CippFormPage from '../../../components/CippFormPages/CippFormPage'
import CippFormComponent from '../../../components/CippComponents/CippFormComponent'
+import { CippFormCondition } from '../../../components/CippComponents/CippFormCondition'
import { CippApiResults } from '../../../components/CippComponents/CippApiResults'
import { CippCodeBlock } from '../../../components/CippComponents/CippCodeBlock'
import { markdownStyles } from '../../../components/CippTestDetail/CippTestDetailOffCanvas'
@@ -117,6 +118,7 @@ const Page = () => {
ScriptContent: '',
Enabled: false,
AlertOnFailure: false,
+ AlertStatuses: [{ value: 'Failed', label: 'Failed' }],
ReturnType: 'JSON',
ResultMode: { value: 'Auto', label: 'Auto' },
MarkdownTemplate: '',
@@ -146,6 +148,12 @@ const Page = () => {
ScriptContent: script.ScriptContent || '',
Enabled: script.Enabled || false,
AlertOnFailure: script.AlertOnFailure || false,
+ AlertStatuses: script.AlertStatuses
+ ? (typeof script.AlertStatuses === 'string'
+ ? JSON.parse(script.AlertStatuses)
+ : script.AlertStatuses
+ ).map((s) => ({ value: s, label: s }))
+ : [{ value: 'Failed', label: 'Failed' }],
ReturnType: script.ReturnType || 'JSON',
ResultMode: toSelectOption(script.ResultMode, 'Auto'),
MarkdownTemplate: script.MarkdownTemplate || '',
@@ -253,6 +261,9 @@ const Page = () => {
ScriptContent: data.ScriptContent,
Enabled: data.Enabled,
AlertOnFailure: data.AlertOnFailure,
+ AlertStatuses: data.AlertOnFailure
+ ? (data.AlertStatuses?.map(s => s.value) || ['Failed'])
+ : [],
ReturnType: data.ReturnType,
ResultMode: data.ResultMode?.value ?? data.ResultMode,
MarkdownTemplate: data.MarkdownTemplate,
@@ -311,6 +322,7 @@ const Page = () => {
{ value: 'Auto', label: 'Auto' },
{ value: 'AlwaysPass', label: 'Always Pass' },
{ value: 'AlwaysInfo', label: 'Always Info' },
+ { value: 'AlwaysInvestigate', label: 'Always Investigate' },
]
const scriptNameField = {
@@ -400,6 +412,20 @@ const Page = () => {
'When enabled, a failed test triggers an alert routed to your configured notification channels (email, webhook, or PSA).',
}
+ const alertStatusesField = {
+ name: 'AlertStatuses',
+ label: 'Alert on Status',
+ type: 'autoComplete',
+ multiple: true,
+ options: [
+ { label: 'Failed', value: 'Failed' },
+ { label: 'Passed', value: 'Passed' },
+ { label: 'Info', value: 'Info' },
+ { label: 'Investigate', value: 'Investigate' },
+ ],
+ helperText: 'Choose which test result statuses trigger an alert.',
+ }
+
const returnTypeField = {
name: 'ReturnType',
label: 'Result Display Type',
@@ -739,7 +765,8 @@ All UPNs: {{join(Result[*].UserPrincipalName, ", ")}}`,
Return a hashtable with CIPPStatus (Passed/
- Failed/Info), CIPPResults, and optional{' '}
+ Failed/Info/Investigate),{' '}
+ CIPPResults, and optional{' '}
CIPPResultMarkdown to control status and rendering directly (Auto
result mode only)
@@ -1291,6 +1318,20 @@ $md = $summaryTable + "\n\n---\n\n" + $policyTable
disabled={isScriptLoading}
/>
+
+
+
+
+
diff --git a/src/utils/get-cipp-column-size.js b/src/utils/get-cipp-column-size.js
index a62c579b8d48..d24c5d549b8c 100644
--- a/src/utils/get-cipp-column-size.js
+++ b/src/utils/get-cipp-column-size.js
@@ -15,6 +15,8 @@ export const getCippColumnSize = (accessorKey, header) => {
switch (accessorKey) {
case 'alignmentScore':
case 'combinedAlignmentScore':
+ case 'compliancePercentage':
+ case 'complianceScore':
case 'LicenseMissingPercentage':
case 'ScorePercentage':
return { size: 250, minSize: 250 }
diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js
index 62473638e095..5580ab4b5de0 100644
--- a/src/utils/get-cipp-formatting.js
+++ b/src/utils/get-cipp-formatting.js
@@ -11,6 +11,7 @@ import {
BarChart,
} from '@mui/icons-material'
import { Chip, Link, SvgIcon, Tooltip } from '@mui/material'
+import NextLink from 'next/link'
import { alpha } from '@mui/material/styles'
import { Box } from '@mui/system'
import { CippCopyToClipBoard } from '../components/CippComponents/CippCopyToClipboard'
@@ -259,7 +260,11 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr
return isText ? data : data
}
- if (cellName === 'alignmentScore' || cellName === 'combinedAlignmentScore') {
+ if (
+ cellName === 'alignmentScore' ||
+ cellName === 'combinedAlignmentScore' ||
+ cellName === 'compliancePercentage'
+ ) {
// Handle alignment score, return a percentage with a label
return isText ? (
`${data}%`
@@ -269,7 +274,8 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr
}
if (cellName === 'currentDeviationsCount') {
- if (data === undefined || data === null) return isText ? 'N/A' :
+ if (data === undefined || data === null)
+ return isText ? 'N/A' :
const count = Number(data)
const color = count > 0 ? 'warning' : 'success'
const label = count > 0 ? `${count} Deviation${count !== 1 ? 's' : ''}` : 'None'
@@ -994,8 +1000,7 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr
// into human-readable form (e.g. "1 hour 23 minutes 30 seconds") across all CIPP tables.
// The try/catch below handles same-suffixed fields that are not actually ISO 8601.
// Add explicit entries below for fields that don't follow the *Duration naming convention.
- const durationArray = [
- ]
+ const durationArray = []
if (durationArray.includes(cellName) || cellName.endsWith('Duration')) {
isoDuration.setLocales(
{
@@ -1016,6 +1021,17 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr
}
}
+ // Internal CIPP navigation links
+ if ((cellName === 'cippLink') && typeof data === 'string') {
+ return isText ? (
+ data
+ ) : (
+
+ View
+
+ )
+ }
+
//if string starts with http, return a link
if (typeof data === 'string' && data.toLowerCase().startsWith('http')) {
return isText ? (
diff --git a/src/utils/get-cipp-validator.js b/src/utils/get-cipp-validator.js
index f5541e0dc25c..b6cff111b8a3 100644
--- a/src/utils/get-cipp-validator.js
+++ b/src/utils/get-cipp-validator.js
@@ -19,6 +19,12 @@ export const getCippValidator = (value, type) => {
return typeof value === "string" || "This is not a valid string";
case "ip":
return /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(value) || "This is not a valid IP address";
+ case "ipAny":
+ return (
+ /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(value) ||
+ /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))$/.test(value) ||
+ "This is not a valid IPv4 or IPv6 address"
+ );
case "ipv4cidr":
return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}\/([0-9]|[12][0-9]|3[0-2])$/.test(value) || "This is not a valid IPv4 CIDR";
case "ipv6":
diff --git a/src/utils/intune-bind-helpers.js b/src/utils/intune-bind-helpers.js
new file mode 100644
index 000000000000..6ff61ff0d5bf
--- /dev/null
+++ b/src/utils/intune-bind-helpers.js
@@ -0,0 +1,14 @@
+// Parsers for Intune Admin Template @odata.bind refs (e.g. `groupPolicyDefinitions('GUID')`).
+// Shared so the hook and CippJSONView renderer can't drift.
+
+export const definitionBindPattern = /groupPolicyDefinitions\('([0-9a-f-]{36})'\)/i
+export const presentationBindPattern = /presentations\('([0-9a-f-]{36})'\)/i
+
+export const extractBindGuid = (value, pattern) => {
+ if (typeof value !== 'string') {
+ return null
+ }
+
+ const match = value.match(pattern)
+ return match?.[1]?.toLowerCase() || null
+}
diff --git a/yarn.lock b/yarn.lock
index 717cf1e70089..acfda103a76e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1016,14 +1016,14 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6"
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
-"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1":
+"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595"
integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==
dependencies:
eslint-visitor-keys "^3.4.3"
-"@eslint-community/regexpp@^4.12.1", "@eslint-community/regexpp@^4.12.2", "@eslint-community/regexpp@^4.6.1":
+"@eslint-community/regexpp@^4.12.1", "@eslint-community/regexpp@^4.12.2":
version "4.12.2"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
@@ -1051,21 +1051,6 @@
dependencies:
"@types/json-schema" "^7.0.15"
-"@eslint/eslintrc@^2.1.4":
- version "2.1.4"
- resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad"
- integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
- dependencies:
- ajv "^6.12.4"
- debug "^4.3.2"
- espree "^9.6.0"
- globals "^13.19.0"
- ignore "^5.2.0"
- import-fresh "^3.2.1"
- js-yaml "^4.1.0"
- minimatch "^3.1.2"
- strip-json-comments "^3.1.1"
-
"@eslint/eslintrc@^3.3.5":
version "3.3.5"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.5.tgz#c131793cfc1a7b96f24a83e0a8bbd4b881558c60"
@@ -1081,11 +1066,6 @@
minimatch "^3.1.5"
strip-json-comments "^3.1.1"
-"@eslint/js@8.57.1":
- version "8.57.1"
- resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2"
- integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
-
"@eslint/js@9.39.4":
version "9.39.4"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.4.tgz#a3f83bfc6fd9bf33a853dfacd0b49b398eb596c1"
@@ -1142,25 +1122,11 @@
"@humanfs/core" "^0.19.1"
"@humanwhocodes/retry" "^0.4.0"
-"@humanwhocodes/config-array@^0.13.0":
- version "0.13.0"
- resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748"
- integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==
- dependencies:
- "@humanwhocodes/object-schema" "^2.0.3"
- debug "^4.3.1"
- minimatch "^3.0.5"
-
"@humanwhocodes/module-importer@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
-"@humanwhocodes/object-schema@^2.0.3":
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
- integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
-
"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2":
version "0.4.3"
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba"
@@ -1313,13 +1279,6 @@
resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz#a81ffb00e69267cd0a1d626eaedb8a8430b2b2f8"
integrity sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==
-"@jest/schemas@^29.6.3":
- version "29.6.3"
- resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03"
- integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==
- dependencies:
- "@sinclair/typebox" "^0.27.8"
-
"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5":
version "0.3.13"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
@@ -1722,7 +1681,7 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
-"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
+"@nodelib/fs.walk@^1.2.3":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
@@ -1950,11 +1909,6 @@
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==
-"@sinclair/typebox@^0.27.8":
- version "0.27.10"
- resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.10.tgz#beefe675f1853f73676aecc915b2bd2ac98c4fc6"
- integrity sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==
-
"@sinonjs/text-encoding@^0.7.2":
version "0.7.3"
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz#282046f03e886e352b2d5f5da5eb755e01457f3f"
@@ -2107,10 +2061,10 @@
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.96.2.tgz#766dab253476afd0b27959b66abb606d8d2dd9f5"
integrity sha512-hzI6cTVh4KNRk8UtoIBS7Lv9g6BnJPXvBKsvYH1aGWvv0347jT3BnSvztOE+kD76XGvZnRC/t6qdW1CaIfwCeA==
-"@tanstack/query-devtools@5.93.0":
- version "5.93.0"
- resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.93.0.tgz#517f61d4e2cfb9af671e34ad5e7e871052bca814"
- integrity sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg==
+"@tanstack/query-devtools@5.96.2":
+ version "5.96.2"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.96.2.tgz#6301662b95d4a7a8b9b53e53d3a9091ae45e4d25"
+ integrity sha512-vBTB1Qhbm3nHSbEUtQwks/EdcAtFfEapr1WyBW4w2ExYKuXVi3jIxUIHf5MlSltiHuL7zNyUuanqT/7sI2sb6g==
"@tanstack/query-persist-client-core@5.92.4":
version "5.92.4"
@@ -2119,6 +2073,13 @@
dependencies:
"@tanstack/query-core" "5.91.2"
+"@tanstack/query-persist-client-core@5.96.2":
+ version "5.96.2"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-persist-client-core/-/query-persist-client-core-5.96.2.tgz#65ea5a2104a85a2f39ef1a007f6f0ad63fbf1c49"
+ integrity sha512-BYsP8folbvxzZsNnWJxSenEAdepGNfv809150U78D84yt/THi33EwfUCcdKWFbma5XKwlaFQGWMJKeWnVJ6GVA==
+ dependencies:
+ "@tanstack/query-core" "5.96.2"
+
"@tanstack/query-sync-storage-persister@^5.90.25":
version "5.90.27"
resolved "https://registry.yarnpkg.com/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-5.90.27.tgz#249c055565e31e0587c2b1900b0d7e0012982dd3"
@@ -2127,19 +2088,19 @@
"@tanstack/query-core" "5.91.2"
"@tanstack/query-persist-client-core" "5.92.4"
-"@tanstack/react-query-devtools@^5.51.11":
- version "5.91.3"
- resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.91.3.tgz#0f65340fa3f7e7d5575de928ad70cfa6b5f74ff1"
- integrity sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA==
+"@tanstack/react-query-devtools@^5.96.2":
+ version "5.96.2"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.96.2.tgz#b44a19f1ebdb45a3e59fcf4e4361ff37500f382c"
+ integrity sha512-nTFKLGuTOFvmFRvcyZ3ArWC/DnMNPoBh6h/2yD6rsf7TCTJCQt+oUWOp2uKPTIuEPtF/vN9Kw5tl5mD1Kbposw==
dependencies:
- "@tanstack/query-devtools" "5.93.0"
+ "@tanstack/query-devtools" "5.96.2"
-"@tanstack/react-query-persist-client@^5.76.0":
- version "5.90.27"
- resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.90.27.tgz#6bf177ea728eec30df50d87f4151dfac8aeaf4f0"
- integrity sha512-rKiCZ2C0kzmyDoLfrPHz2UdEDKHo/oXkKVRbhgtHya/bWH6jWDFX5cSFc1SLB33FDrgR8uOG1MwVohBrI4+F8A==
+"@tanstack/react-query-persist-client@^5.96.2":
+ version "5.96.2"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query-persist-client/-/react-query-persist-client-5.96.2.tgz#b47d62fc990a9fd38ddcf4a080d1300ae887e5a0"
+ integrity sha512-smQ38oVPlnvkG+G7R60IAD9X6azJLRjHEd7twml9XBLYM31ncPDP0tUKy/Gv/4ItVmKTtjZ5VabXpVZxnaWSww==
dependencies:
- "@tanstack/query-persist-client-core" "5.92.4"
+ "@tanstack/query-persist-client-core" "5.96.2"
"@tanstack/react-query@^5.96.2":
version "5.96.2"
@@ -2256,11 +2217,6 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.20.5.tgz#c21b2c7405f4aad7b507e36cc3394aba51ea2253"
integrity sha512-4UtpUHg8cRzxWjJUGtni5VnXYbhsO7ygf1H1pr4Rv63XMBg9lfYDeSwByIuVy9biEFP7eGEFnezzb5Zlh1btmQ==
-"@tiptap/extension-image@^3.20.5":
- version "3.20.5"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-3.20.5.tgz#90a80ce694dcda452a296d38f457bfbe72bf940d"
- integrity sha512-qxKupWKhX75Xc9GJ9Uel+KIFL9x6tb8W3RvQM1UolyJX/H7wyBO7sXp9XmKRkHZsDXRgLVbnkYBe+X83o16AIA==
-
"@tiptap/extension-italic@^3.20.5":
version "3.20.5"
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.20.5.tgz#c53436f05968b16eda6b8e0efbaebaf3f4587e3b"
@@ -2590,11 +2546,6 @@
resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.3.tgz#85f1d1d17569b28b8db45e16e996407a56b0ab04"
integrity sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==
-"@types/react-dom@^19.2.3":
- version "19.2.3"
- resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c"
- integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==
-
"@types/react-redux@^7.1.20":
version "7.1.34"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.34.tgz#83613e1957c481521e6776beeac4fd506d11bd0e"
@@ -2610,7 +2561,7 @@
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044"
integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==
-"@types/react@*", "@types/react@^19.2.14":
+"@types/react@*":
version "19.2.14"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.14.tgz#39604929b5e3957e3a6fa0001dafb17c7af70bad"
integrity sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==
@@ -2662,17 +2613,6 @@
"@typescript-eslint/visitor-keys" "8.57.1"
debug "^4.4.3"
-"@typescript-eslint/parser@^6.21.0":
- version "6.21.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b"
- integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==
- dependencies:
- "@typescript-eslint/scope-manager" "6.21.0"
- "@typescript-eslint/types" "6.21.0"
- "@typescript-eslint/typescript-estree" "6.21.0"
- "@typescript-eslint/visitor-keys" "6.21.0"
- debug "^4.3.4"
-
"@typescript-eslint/project-service@8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.57.1.tgz#16af9fe16eedbd7085e4fdc29baa73715c0c55c5"
@@ -2682,14 +2622,6 @@
"@typescript-eslint/types" "^8.57.1"
debug "^4.4.3"
-"@typescript-eslint/scope-manager@6.21.0":
- version "6.21.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1"
- integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==
- dependencies:
- "@typescript-eslint/types" "6.21.0"
- "@typescript-eslint/visitor-keys" "6.21.0"
-
"@typescript-eslint/scope-manager@8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz#4524d7e7b420cb501807499684d435ae129aaf35"
@@ -2714,30 +2646,11 @@
debug "^4.4.3"
ts-api-utils "^2.4.0"
-"@typescript-eslint/types@6.21.0":
- version "6.21.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d"
- integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==
-
"@typescript-eslint/types@8.57.1", "@typescript-eslint/types@^8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.57.1.tgz#54b27a8a25a7b45b4f978c3f8e00c4c78f11142c"
integrity sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==
-"@typescript-eslint/typescript-estree@6.21.0":
- version "6.21.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46"
- integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==
- dependencies:
- "@typescript-eslint/types" "6.21.0"
- "@typescript-eslint/visitor-keys" "6.21.0"
- debug "^4.3.4"
- globby "^11.1.0"
- is-glob "^4.0.3"
- minimatch "9.0.3"
- semver "^7.5.4"
- ts-api-utils "^1.0.1"
-
"@typescript-eslint/typescript-estree@8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz#a9fd28d4a0ec896aa9a9a7e0cead62ea24f99e76"
@@ -2763,14 +2676,6 @@
"@typescript-eslint/types" "8.57.1"
"@typescript-eslint/typescript-estree" "8.57.1"
-"@typescript-eslint/visitor-keys@6.21.0":
- version "6.21.0"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47"
- integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==
- dependencies:
- "@typescript-eslint/types" "6.21.0"
- eslint-visitor-keys "^3.4.1"
-
"@typescript-eslint/visitor-keys@8.57.1":
version "8.57.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz#3af4f88118924d3be983d4b8ae84803f11fe4563"
@@ -2779,12 +2684,7 @@
"@typescript-eslint/types" "8.57.1"
eslint-visitor-keys "^5.0.0"
-"@uiw/react-json-view@^2.0.0-alpha.41":
- version "2.0.0-alpha.41"
- resolved "https://registry.yarnpkg.com/@uiw/react-json-view/-/react-json-view-2.0.0-alpha.41.tgz#54425c948175df5fd2155fa22a12cfb023f98773"
- integrity sha512-botRpQ5AgymYEsqXSdT2/1LefAJEYfMntvdnx1SqhTQCTW9HygeFZXx9inkYqUmiQZ3+0QlZnodjBvwnUfZhVA==
-
-"@ungap/structured-clone@^1.0.0", "@ungap/structured-clone@^1.2.0":
+"@ungap/structured-clone@^1.0.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
@@ -2901,12 +2801,12 @@ acorn-jsx@^5.3.2:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
-acorn@^8.15.0, acorn@^8.9.0:
+acorn@^8.15.0:
version "8.16.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
-ajv@^6.12.4, ajv@^6.14.0:
+ajv@^6.14.0:
version "6.14.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a"
integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==
@@ -2916,21 +2816,6 @@ ajv@^6.12.4, ajv@^6.14.0:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
-ansi-regex@^2.0.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
- integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==
-
-ansi-regex@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
- integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
-
-ansi-styles@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
- integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==
-
ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
@@ -2938,11 +2823,6 @@ ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
-ansi-styles@^5.0.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
- integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
-
apexcharts@5.10.4:
version "5.10.4"
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-5.10.4.tgz#79c9a05ab40b069f33873a1859de6cb0882ccf0e"
@@ -2987,11 +2867,6 @@ array-includes@^3.1.6, array-includes@^3.1.8, array-includes@^3.1.9:
is-string "^1.1.1"
math-intrinsics "^1.1.0"
-array-union@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
- integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
-
array.prototype.findlast@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904"
@@ -3195,13 +3070,6 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
-brace-expansion@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7"
- integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==
- dependencies:
- balanced-match "^1.0.0"
-
brace-expansion@^5.0.2:
version "5.0.4"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.4.tgz#614daaecd0a688f660bbbc909a8748c3d80d4336"
@@ -3306,17 +3174,6 @@ ccount@^2.0.0:
resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5"
integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
-chalk@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
- integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==
- dependencies:
- ansi-styles "^2.2.1"
- escape-string-regexp "^1.0.2"
- has-ansi "^2.0.0"
- strip-ansi "^3.0.0"
- supports-color "^2.0.0"
-
chalk@^4.0.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
@@ -3397,11 +3254,6 @@ commander@^7.2.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
-common-tags@^1.8.2:
- version "1.8.2"
- resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6"
- integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==
-
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -3417,13 +3269,6 @@ convert-source-map@^2.0.0:
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
-copy-to-clipboard@^3.3.3:
- version "3.3.3"
- resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0"
- integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==
- dependencies:
- toggle-selection "^1.0.6"
-
core-js-compat@^3.48.0:
version "3.49.0"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.49.0.tgz#06145447d92f4aaf258a0c44f24b47afaeaffef6"
@@ -3467,7 +3312,7 @@ crelt@^1.0.0:
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==
-cross-spawn@^7.0.2, cross-spawn@^7.0.6:
+cross-spawn@^7.0.6:
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
@@ -3713,7 +3558,7 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
-debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3:
+debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.4.0, debug@^4.4.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
@@ -3809,18 +3654,6 @@ diff@^8.0.3:
resolved "https://registry.yarnpkg.com/diff/-/diff-8.0.4.tgz#4f5baf3188b9b2431117b962eb20ba330fadf696"
integrity sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==
-dir-glob@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
- integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
- dependencies:
- path-type "^4.0.0"
-
-dlv@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
- integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
-
doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
@@ -3828,13 +3661,6 @@ doctrine@^2.1.0:
dependencies:
esutils "^2.0.2"
-doctrine@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
- integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
- dependencies:
- esutils "^2.0.2"
-
dom-helpers@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
@@ -3898,6 +3724,13 @@ dompurify@^3.3.1:
optionalDependencies:
"@types/trusted-types" "^2.0.7"
+dompurify@^3.4.2:
+ version "3.4.2"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.4.2.tgz#f0ff81be682c485505097ba8195a058d8f575218"
+ integrity sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==
+ optionalDependencies:
+ "@types/trusted-types" "^2.0.7"
+
domutils@^1.5.1:
version "1.7.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
@@ -4130,11 +3963,6 @@ escalade@^3.2.0:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
-escape-string-regexp@^1.0.2:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
- integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
-
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
@@ -4275,14 +4103,6 @@ eslint-plugin-react@^7.37.0:
string.prototype.matchall "^4.0.12"
string.prototype.repeat "^1.0.0"
-eslint-scope@^7.1.1, eslint-scope@^7.2.2:
- version "7.2.2"
- resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
- integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
- dependencies:
- esrecurse "^4.3.0"
- estraverse "^5.2.0"
-
eslint-scope@^8.4.0:
version "8.4.0"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82"
@@ -4291,7 +4111,7 @@ eslint-scope@^8.4.0:
esrecurse "^4.3.0"
estraverse "^5.2.0"
-eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
+eslint-visitor-keys@^3.4.3:
version "3.4.3"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
@@ -4306,50 +4126,6 @@ eslint-visitor-keys@^5.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be"
integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==
-eslint@^8.57.1:
- version "8.57.1"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9"
- integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
- dependencies:
- "@eslint-community/eslint-utils" "^4.2.0"
- "@eslint-community/regexpp" "^4.6.1"
- "@eslint/eslintrc" "^2.1.4"
- "@eslint/js" "8.57.1"
- "@humanwhocodes/config-array" "^0.13.0"
- "@humanwhocodes/module-importer" "^1.0.1"
- "@nodelib/fs.walk" "^1.2.8"
- "@ungap/structured-clone" "^1.2.0"
- ajv "^6.12.4"
- chalk "^4.0.0"
- cross-spawn "^7.0.2"
- debug "^4.3.2"
- doctrine "^3.0.0"
- escape-string-regexp "^4.0.0"
- eslint-scope "^7.2.2"
- eslint-visitor-keys "^3.4.3"
- espree "^9.6.1"
- esquery "^1.4.2"
- esutils "^2.0.2"
- fast-deep-equal "^3.1.3"
- file-entry-cache "^6.0.1"
- find-up "^5.0.0"
- glob-parent "^6.0.2"
- globals "^13.19.0"
- graphemer "^1.4.0"
- ignore "^5.2.0"
- imurmurhash "^0.1.4"
- is-glob "^4.0.0"
- is-path-inside "^3.0.3"
- js-yaml "^4.1.0"
- json-stable-stringify-without-jsonify "^1.0.1"
- levn "^0.4.1"
- lodash.merge "^4.6.2"
- minimatch "^3.1.2"
- natural-compare "^1.4.0"
- optionator "^0.9.3"
- strip-ansi "^6.0.1"
- text-table "^0.2.0"
-
eslint@^9.39.4:
version "9.39.4"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.4.tgz#855da1b2e2ad66dc5991195f35e262bcec8117b5"
@@ -4399,21 +4175,12 @@ espree@^10.0.1, espree@^10.4.0:
acorn-jsx "^5.3.2"
eslint-visitor-keys "^4.2.1"
-espree@^9.3.1, espree@^9.6.0, espree@^9.6.1:
- version "9.6.1"
- resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
- integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
- dependencies:
- acorn "^8.9.0"
- acorn-jsx "^5.3.2"
- eslint-visitor-keys "^3.4.1"
-
esprima@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
-esquery@^1.4.0, esquery@^1.4.2, esquery@^1.5.0:
+esquery@^1.5.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d"
integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==
@@ -4484,11 +4251,6 @@ fast-diff@1.1.2:
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==
-fast-equals@^4.0.3:
- version "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"
@@ -4505,17 +4267,6 @@ fast-glob@3.3.1:
merge2 "^1.3.0"
micromatch "^4.0.4"
-fast-glob@^3.2.9:
- 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"
@@ -4559,13 +4310,6 @@ fflate@^0.8.1:
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea"
integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==
-file-entry-cache@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
- integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
- dependencies:
- flat-cache "^3.0.4"
-
file-entry-cache@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f"
@@ -4600,15 +4344,6 @@ find-up@^5.0.0:
locate-path "^6.0.0"
path-exists "^4.0.0"
-flat-cache@^3.0.4:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee"
- integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==
- dependencies:
- flatted "^3.2.9"
- keyv "^4.5.3"
- rimraf "^3.0.2"
-
flat-cache@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c"
@@ -4679,11 +4414,6 @@ formik@2.4.9:
tiny-warning "^1.0.2"
tslib "^2.0.0"
-fs.realpath@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
- integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
-
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
@@ -4770,30 +4500,11 @@ glob-parent@^6.0.2:
dependencies:
is-glob "^4.0.3"
-glob@^7.1.3:
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
- integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
- dependencies:
- fs.realpath "^1.0.0"
- inflight "^1.0.4"
- inherits "2"
- minimatch "^3.1.1"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
-
globals@16.4.0:
version "16.4.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-16.4.0.tgz#574bc7e72993d40cf27cf6c241f324ee77808e51"
integrity sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==
-globals@^13.19.0:
- version "13.24.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171"
- integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==
- dependencies:
- type-fest "^0.20.2"
-
globals@^14.0.0:
version "14.0.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"
@@ -4807,18 +4518,6 @@ globalthis@^1.0.4:
define-properties "^1.2.1"
gopd "^1.0.1"
-globby@^11.1.0:
- version "11.1.0"
- resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
- integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
- dependencies:
- array-union "^2.1.0"
- dir-glob "^3.0.1"
- fast-glob "^3.2.9"
- ignore "^5.2.0"
- merge2 "^1.4.1"
- slash "^3.0.0"
-
goober@^2.1.16:
version "2.1.18"
resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.18.tgz#b72d669bd24d552d441638eee26dfd5716ea6442"
@@ -4829,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"
@@ -4844,13 +4538,6 @@ gray-matter@4.0.3:
section-matter "^1.0.0"
strip-bom-string "^1.0.0"
-has-ansi@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
- integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==
- dependencies:
- ansi-regex "^2.0.0"
-
has-bigints@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe"
@@ -5032,13 +4719,6 @@ hsl-to-rgb-for-reals@^1.1.0:
resolved "https://registry.yarnpkg.com/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz#e1eb23f6b78016e3722431df68197e6dcdc016d9"
integrity sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==
-html-parse-stringify@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
- integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
- dependencies:
- void-elements "3.1.0"
-
html-tokenize@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-tokenize/-/html-tokenize-2.0.1.tgz#c3b2ea6e2837d4f8c06693393e9d2a12c960be5f"
@@ -5085,13 +4765,6 @@ hyphen@^1.6.4:
resolved "https://registry.yarnpkg.com/hyphen/-/hyphen-1.14.1.tgz#c9fbd5e1af750f00d5034aa37f6ec41f95ffed93"
integrity sha512-kvL8xYl5QMTh+LwohVN72ciOxC0OEV79IPdJSTwEXok9y9QHebXGdFgrED4sWfiax/ODx++CAMk3hMy4XPJPOw==
-i18next@25.8.18:
- version "25.8.18"
- resolved "https://registry.yarnpkg.com/i18next/-/i18next-25.8.18.tgz#51863b65bc42e3525271f2680ebbf7d150ff53cc"
- integrity sha512-lzY5X83BiL5AP77+9DydbrqkQHFN9hUzWGjqjLpPcp5ZOzuu1aSoKaU3xbBLSjWx9dAzW431y+d+aogxOZaKRA==
- dependencies:
- "@babel/runtime" "^7.28.6"
-
ignore@^5.2.0:
version "5.3.2"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
@@ -5125,20 +4798,7 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
-indent-string@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
- integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
-
-inflight@^1.0.4:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
- integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
- dependencies:
- once "^1.3.0"
- wrappy "1"
-
-inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -5342,11 +5002,6 @@ is-number@^7.0.0:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
-is-path-inside@^3.0.3:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
- integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
-
is-plain-obj@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
@@ -5560,7 +5215,7 @@ jspdf@^4.2.0:
object.assign "^4.1.4"
object.values "^1.1.6"
-keyv@^4.5.3, keyv@^4.5.4:
+keyv@^4.5.4:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
@@ -5584,11 +5239,6 @@ language-tags@^1.0.9:
dependencies:
language-subtag-registry "^0.3.20"
-leaflet-defaulticon-compatibility@^0.1.2:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/leaflet-defaulticon-compatibility/-/leaflet-defaulticon-compatibility-0.1.2.tgz#f5e1a5841aeab9d1682d17887348855a741b3c2a"
- integrity sha512-IrKagWxkTwzxUkFIumy/Zmo3ksjuAu3zEadtOuJcKzuXaD76Gwvg2Z1mLyx7y52ykOzM8rAH5ChBs4DnfdGa6Q==
-
leaflet.markercluster@^1.5.3:
version "1.5.3"
resolved "https://registry.yarnpkg.com/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz#9cdb52a4eab92671832e1ef9899669e80efc4056"
@@ -5664,18 +5314,10 @@ lodash@^4.17.21, lodash@^4.17.4:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a"
integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==
-loglevel-colored-level-prefix@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz#6a40218fdc7ae15fc76c3d0f3e676c465388603e"
- integrity sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==
- dependencies:
- chalk "^1.1.3"
- loglevel "^1.4.1"
-
-loglevel@^1.4.1:
- version "1.9.2"
- resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.2.tgz#c2e028d6c757720107df4e64508530db6621ba08"
- integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==
+lodash@^4.18.1:
+ version "4.18.1"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c"
+ integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==
longest-streak@^3.0.0:
version "3.1.0"
@@ -5958,7 +5600,7 @@ memoize-one@^6.0.0:
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
-merge2@^1.3.0, merge2@^1.4.1:
+merge2@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
@@ -6236,7 +5878,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==
@@ -6256,13 +5898,6 @@ mime-types@^2.1.12:
dependencies:
mime-db "1.52.0"
-minimatch@9.0.3:
- version "9.0.3"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825"
- integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==
- dependencies:
- brace-expansion "^2.0.1"
-
minimatch@^10.2.2:
version "10.2.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde"
@@ -6270,7 +5905,7 @@ minimatch@^10.2.2:
dependencies:
brace-expansion "^5.0.2"
-minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2, minimatch@^3.1.5:
+minimatch@^3.1.2, minimatch@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e"
integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==
@@ -6295,10 +5930,10 @@ ms@^2.1.1, ms@^2.1.3:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-mui-tiptap@^1.29.1:
- version "1.29.1"
- resolved "https://registry.yarnpkg.com/mui-tiptap/-/mui-tiptap-1.29.1.tgz#eaf54adebc4af14f55b55cb21ef91347b5074343"
- integrity sha512-FyOILZSirwYxXs+WJTVs/3QmycCTDJQ5rkBaZkkgtzskbF8fgCIzKUtuTk7bKRE17aWi+VRoGDAu/VVO4Xp6IA==
+mui-tiptap@^1.30.0:
+ version "1.30.0"
+ resolved "https://registry.yarnpkg.com/mui-tiptap/-/mui-tiptap-1.30.0.tgz#91257ebb32b12241fe27b24ded42804a3abe2c51"
+ integrity sha512-BVgv9JstoNsk1SudQuIGV58N7GHlWSdItc8Yxa2BXZ6GFjJ1q1QLFoD6mALRrsKEhxpRfkRHrGaOLlZ5KkO2cQ==
dependencies:
clsx "^2.1.1"
encodeurl "^2.0.0"
@@ -6476,13 +6111,6 @@ object.values@^1.1.6, object.values@^1.2.1:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
-once@^1.3.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
- integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
- dependencies:
- wrappy "1"
-
optionator@^0.9.3:
version "0.9.4"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
@@ -6595,11 +6223,6 @@ path-exists@^4.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
-path-is-absolute@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
- integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
-
path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
@@ -6659,38 +6282,11 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
-prettier-eslint@^16.4.2:
- version "16.4.2"
- resolved "https://registry.yarnpkg.com/prettier-eslint/-/prettier-eslint-16.4.2.tgz#d84bff76e0ce4a6ffccacacb2474f7635ca8ac35"
- integrity sha512-vtJAQEkaN8fW5QKl08t7A5KCjlZuDUNeIlr9hgolMS5s3+uzbfRHDwaRnzrdqnY2YpHDmeDS/8zY0MKQHXJtaA==
- dependencies:
- "@typescript-eslint/parser" "^6.21.0"
- common-tags "^1.8.2"
- dlv "^1.1.3"
- eslint "^8.57.1"
- indent-string "^4.0.0"
- lodash.merge "^4.6.2"
- loglevel-colored-level-prefix "^1.0.0"
- prettier "^3.5.3"
- pretty-format "^29.7.0"
- require-relative "^0.8.7"
- tslib "^2.8.1"
- vue-eslint-parser "^9.4.3"
-
-prettier@^3.5.3, prettier@^3.8.1:
+prettier@^3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173"
integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==
-pretty-format@^29.7.0:
- version "29.7.0"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
- integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==
- dependencies:
- "@jest/schemas" "^29.6.3"
- ansi-styles "^5.0.0"
- react-is "^18.0.0"
-
prismjs@^1.30.0:
version "1.30.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9"
@@ -6701,7 +6297,7 @@ process-nextick-args@~2.0.0:
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
-prop-types@15.8.1, prop-types@15.x, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@15.8.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -6959,14 +6555,6 @@ react-colorful@^5.6.1:
resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b"
integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==
-react-copy-to-clipboard@^5.1.0:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.1.tgz#76adb8be03616e99692fcf3f762365ed3fb5ff16"
- integrity sha512-s+HrzLyJBxrpGTYXF15dTgMjAJpEPZT/Yp6NytAtZMRngejxt6Pt5WrfFxLAcsqUDU6sY1Jz6tyHwIicE1U2Xg==
- dependencies:
- copy-to-clipboard "^3.3.3"
- prop-types "^15.8.1"
-
react-dom@19.2.5:
version "19.2.5"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.5.tgz#b8768b10837d0b8e9ca5b9e2d58dff3d880ea25e"
@@ -6974,14 +6562,6 @@ react-dom@19.2.5:
dependencies:
scheduler "^0.27.0"
-react-draggable@^4.4.6, react-draggable@^4.5.0:
- version "4.5.0"
- resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.5.0.tgz#0b274ccb6965fcf97ed38fcf7e3cc223bc48cdf5"
- integrity sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==
- dependencies:
- clsx "^2.1.1"
- prop-types "^15.8.1"
-
react-dropzone@15.0.0:
version "15.0.0"
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-15.0.0.tgz#bd03c7c2b14fe4ea9db1a9c74502b85339f2e505"
@@ -7001,18 +6581,6 @@ react-fast-compare@^2.0.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
-react-grid-layout@^2.2.3:
- version "2.2.3"
- resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-2.2.3.tgz#6daf24b8c48448af617238520dd233a9375e2f16"
- integrity sha512-OAEJHBxmfuxQfVtZwRzmsokijGlBgzYIJ7MUlLk/VSa43SaGzu15w5D0P2RDrfX5EvP9POMbL6bFrai/huDzbQ==
- dependencies:
- clsx "^2.1.1"
- fast-equals "^4.0.3"
- prop-types "^15.8.1"
- react-draggable "^4.4.6"
- react-resizable "^3.1.3"
- resize-observer-polyfill "^1.5.1"
-
react-hook-form@^7.72.0:
version "7.72.0"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.72.0.tgz#995a655b894249fd8798f36383e43f55ed66ae25"
@@ -7033,15 +6601,6 @@ react-html-parser@^2.0.2:
dependencies:
htmlparser2 "^3.9.0"
-react-i18next@16.6.5:
- version "16.6.5"
- resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-16.6.5.tgz#6fd2b0b82ed6988b87e51487d53c28954994d361"
- integrity sha512-bfdJhmyjQCXtU9CLcGMn3a1V5/jTeUX/x29cOhlS1Lolm/epRtm24gnYsltxArsc29ow3klSJEijjfYXc5kxjg==
- dependencies:
- "@babel/runtime" "^7.29.2"
- html-parse-stringify "^3.0.1"
- use-sync-external-store "^1.6.0"
-
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -7052,11 +6611,6 @@ react-is@^17.0.2:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-react-is@^18.0.0:
- version "18.3.1"
- resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
- integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
-
react-is@^19.2.3:
version "19.2.4"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.4.tgz#a080758243c572ccd4a63386537654298c99d135"
@@ -7143,14 +6697,6 @@ react-redux@^7.2.0:
prop-types "^15.7.2"
react-is "^17.0.2"
-react-resizable@^3.1.3:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-3.1.3.tgz#b8c3f8aeffb7b0b2c2306bfc7a742462e58125fb"
- integrity sha512-liJBNayhX7qA4tBJiBD321FDhJxgGTJ07uzH5zSORXoE8h7PyEZ8mLqmosST7ppf6C4zUsbd2gzDMmBCfFp9Lw==
- dependencies:
- prop-types "15.x"
- react-draggable "^4.5.0"
-
react-syntax-highlighter@^16.1.0:
version "16.1.1"
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-16.1.1.tgz#928459855d375f5cfc8e646071e20d541cebcb52"
@@ -7192,11 +6738,6 @@ react-virtuoso@^4.18.5:
resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.18.5.tgz#450108e585c7a1124b995c7ea3cf367ed4857631"
integrity sha512-QDyNjyNEuurZG67SOmzYyxEkQYSyGmAMixOI6M15L/Q4CF39EgG+88y6DgZRo0q7rmy0HPx3Fj90I8/tPdnRCQ==
-react-window@^2.2.7:
- version "2.2.7"
- resolved "https://registry.yarnpkg.com/react-window/-/react-window-2.2.7.tgz#7f3d31695d4323701b7e80dfc9bbbe1d4a0c160f"
- integrity sha512-SH5nvfUQwGHYyriDUAOt7wfPsfG9Qxd6OdzQxl5oQ4dsSsUicqQvjV7dR+NqZ4coY0fUn3w1jnC5PwzIUWEg5w==
-
react@19.2.5:
version "19.2.5"
resolved "https://registry.yarnpkg.com/react/-/react-19.2.5.tgz#c888ab8b8ef33e2597fae8bdb2d77edbdb42858b"
@@ -7234,10 +6775,10 @@ readable-stream@~1.0.17, readable-stream@~1.0.27-1:
isarray "0.0.1"
string_decoder "~0.10.x"
-recharts@^3.7.0:
- version "3.8.0"
- resolved "https://registry.yarnpkg.com/recharts/-/recharts-3.8.0.tgz#461025818cbb858e7ff2e5820b67c6143e9b418d"
- integrity sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ==
+recharts@^3.8.1:
+ version "3.8.1"
+ resolved "https://registry.yarnpkg.com/recharts/-/recharts-3.8.1.tgz#1784b14784dab9a27eb426c475e6a9187f14cf01"
+ integrity sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==
dependencies:
"@reduxjs/toolkit" "^1.9.0 || 2.x.x"
clsx "^2.1.1"
@@ -7251,17 +6792,12 @@ recharts@^3.7.0:
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"
- integrity sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==
-
redux-persist@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"
integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==
-redux-thunk@3.1.0, redux-thunk@^3.1.0:
+redux-thunk@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3"
integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==
@@ -7421,21 +6957,11 @@ 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==
-require-relative@^0.8.7:
- version "0.8.7"
- resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de"
- integrity sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==
-
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==
-resize-observer-polyfill@^1.5.1:
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
- integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
-
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -7482,13 +7008,6 @@ rgbcolor@^1.0.1:
resolved "https://registry.yarnpkg.com/rgbcolor/-/rgbcolor-1.0.1.tgz#d6505ecdb304a6595da26fa4b43307306775945d"
integrity sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==
-rimraf@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
- integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
- dependencies:
- glob "^7.1.3"
-
rope-sequence@^1.3.0:
version "1.3.4"
resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.4.tgz#df85711aaecd32f1e756f76e43a415171235d425"
@@ -7567,7 +7086,7 @@ semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-semver@^7.3.6, semver@^7.5.4, semver@^7.7.1, semver@^7.7.3:
+semver@^7.7.1, semver@^7.7.3:
version "7.7.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a"
integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==
@@ -7718,11 +7237,6 @@ simplebar@6.3.3:
dependencies:
simplebar-core "^1.3.2"
-slash@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
- integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
-
snake-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c"
@@ -7869,20 +7383,6 @@ stringify-entities@^4.0.0:
character-entities-html4 "^2.0.0"
character-entities-legacy "^3.0.0"
-strip-ansi@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
- integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==
- dependencies:
- ansi-regex "^2.0.0"
-
-strip-ansi@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
strip-bom-string@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92"
@@ -7931,11 +7431,6 @@ stylis@4.2.0:
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
-supports-color@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
- integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==
-
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
@@ -7983,11 +7478,6 @@ text-segmentation@^1.0.3:
dependencies:
utrie "^1.0.2"
-text-table@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
- integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
-
through2@~0.4.1:
version "0.4.2"
resolved "https://registry.yarnpkg.com/through2/-/through2-0.4.2.tgz#dbf5866031151ec8352bb6c4db64a2292a840b9b"
@@ -8036,11 +7526,6 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
-toggle-selection@^1.0.6:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
- integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==
-
toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
@@ -8056,11 +7541,6 @@ trough@^2.0.0:
resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f"
integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==
-ts-api-utils@^1.0.1:
- version "1.4.3"
- resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064"
- integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==
-
ts-api-utils@^2.4.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1"
@@ -8076,7 +7556,7 @@ tsconfig-paths@^3.15.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
-tslib@^2.0.0, tslib@^2.0.3, tslib@^2.4.0, tslib@^2.7.0, tslib@^2.8.0, tslib@^2.8.1:
+tslib@^2.0.0, tslib@^2.0.3, tslib@^2.4.0, tslib@^2.7.0, tslib@^2.8.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
@@ -8088,11 +7568,6 @@ type-check@^0.4.0, type-check@~0.4.0:
dependencies:
prelude-ls "^1.2.1"
-type-fest@^0.20.2:
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
- integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
-
type-fest@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
@@ -8153,11 +7628,6 @@ typescript-eslint@^8.46.0:
"@typescript-eslint/typescript-estree" "8.57.1"
"@typescript-eslint/utils" "8.57.1"
-typescript@5.9.3:
- version "5.9.3"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
- integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
-
uc.micro@^2.0.0, uc.micro@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee"
@@ -8217,7 +7687,7 @@ unicode-trie@^2.0.0:
pako "^0.2.5"
tiny-inflate "^1.0.0"
-unified@^11.0.0:
+unified@^11.0.0, unified@^11.0.5:
version "11.0.5"
resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1"
integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==
@@ -8390,24 +7860,6 @@ vite-compatible-readable-stream@^3.6.1:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
-void-elements@3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
- integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
-
-vue-eslint-parser@^9.4.3:
- version "9.4.3"
- resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz#9b04b22c71401f1e8bca9be7c3e3416a4bde76a8"
- integrity sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==
- dependencies:
- debug "^4.3.4"
- eslint-scope "^7.1.1"
- eslint-visitor-keys "^3.3.0"
- espree "^9.3.1"
- esquery "^1.4.0"
- lodash "^4.17.21"
- semver "^7.3.6"
-
w3c-keyname@^2.2.0:
version "2.2.8"
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
@@ -8483,11 +7935,6 @@ word-wrap@^1.2.5:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
-wrappy@1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
- integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
-
xtend@~2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b"