diff --git a/package.json b/package.json
index 059e1aabea30..58019e25bb41 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
"@tiptap/extension-table": "^3.4.1",
"@tiptap/pm": "^3.4.1",
"@tiptap/react": "^3.4.1",
- "@tiptap/starter-kit": "^3.4.1",
+ "@tiptap/starter-kit": "^3.19.0",
"@uiw/react-json-view": "^2.0.0-alpha.30",
"@vvo/tzdb": "^6.198.0",
"apexcharts": "5.3.5",
@@ -62,7 +62,7 @@
"formik": "2.4.6",
"gray-matter": "4.0.3",
"i18next": "25.5.2",
- "javascript-time-ago": "^2.5.11",
+ "javascript-time-ago": "^2.6.2",
"jspdf": "^4.1.0",
"jspdf-autotable": "^5.0.2",
"leaflet": "^1.9.4",
@@ -70,7 +70,7 @@
"leaflet.markercluster": "^1.5.3",
"lodash.isequal": "4.5.0",
"material-react-table": "^3.0.1",
- "monaco-editor": "^0.53.0",
+ "monaco-editor": "^0.55.1",
"mui-tiptap": "^1.14.0",
"next": "^16.1.2",
"nprogress": "0.2.0",
diff --git a/src/components/CippComponents/CippReusableSettingsDeployDrawer.jsx b/src/components/CippComponents/CippReusableSettingsDeployDrawer.jsx
new file mode 100644
index 000000000000..06365e32d50c
--- /dev/null
+++ b/src/components/CippComponents/CippReusableSettingsDeployDrawer.jsx
@@ -0,0 +1,165 @@
+import { useEffect, useState } from "react";
+import { Button, Stack } from "@mui/material";
+import { RocketLaunch } from "@mui/icons-material";
+import { useForm, useWatch, useFormState } from "react-hook-form";
+import { CippOffCanvas } from "./CippOffCanvas";
+import { ApiGetCall, ApiPostCall } from "../../api/ApiCall";
+import CippFormComponent from "./CippFormComponent";
+import CippJsonView from "../CippFormPages/CippJSONView";
+import { Grid } from "@mui/system";
+import { CippApiResults } from "./CippApiResults";
+import { useSettings } from "../../hooks/use-settings";
+import { CippFormTenantSelector } from "./CippFormTenantSelector";
+import { PermissionButton as PermissionAwareButton } from "../../utils/permissions";
+
+export const CippReusableSettingsDeployDrawer = ({
+ buttonText = "Deploy Reusable Settings",
+ requiredPermissions = [],
+ PermissionButton = PermissionAwareButton,
+}) => {
+ const [drawerVisible, setDrawerVisible] = useState(false);
+ const formControl = useForm({ mode: "onChange" });
+ const { isValid } = useFormState({ control: formControl.control });
+ const tenantFilter = useSettings()?.tenantFilter;
+ const selectedTemplate = useWatch({ control: formControl.control, name: "TemplateList" });
+ const rawJson = useWatch({ control: formControl.control, name: "rawJSON" });
+ const selectedTenants = useWatch({ control: formControl.control, name: "tenantFilter" });
+
+ const templates = ApiGetCall({ url: "/api/ListIntuneReusableSettingTemplates", queryKey: "ListIntuneReusableSettingTemplates" });
+
+ useEffect(() => {
+ if (templates.isSuccess && selectedTemplate?.value) {
+ const match = templates.data?.find((t) => t.GUID === selectedTemplate.value);
+ if (match) {
+ formControl.setValue("rawJSON", match.RawJSON || "");
+ formControl.setValue("TemplateId", match.GUID);
+ }
+ }
+ }, [templates.isSuccess, templates.data, selectedTemplate, formControl]);
+
+ const effectiveTenants = Array.isArray(selectedTenants) && selectedTenants.length > 0
+ ? selectedTenants
+ : tenantFilter
+ ? [tenantFilter]
+ : [];
+
+ const deploy = ApiPostCall({
+ urlFromData: true,
+ relatedQueryKeys: [
+ "ListIntuneReusableSettingTemplates",
+ `ListIntuneReusableSettings-${effectiveTenants.join(",")}`,
+ ],
+ });
+
+ const handleSubmit = async () => {
+ const isFormValid = await formControl.trigger();
+ if (!isFormValid) {
+ return;
+ }
+ const values = formControl.getValues();
+ deploy.mutate({
+ url: "/api/AddIntuneReusableSetting",
+ data: {
+ tenantFilter: effectiveTenants,
+ TemplateId: values?.TemplateList?.value,
+ rawJSON: values?.rawJSON,
+ },
+ });
+ };
+
+ const handleCloseDrawer = () => {
+ setDrawerVisible(false);
+ formControl.reset();
+ deploy.reset();
+ };
+
+ const safeJson = () => {
+ if (!rawJson) return null;
+ try {
+ return JSON.parse(rawJson);
+ } catch (e) {
+ return null;
+ }
+ };
+
+ return (
+ <>
+ setDrawerVisible(true)}
+ startIcon={}
+ >
+ {buttonText}
+
+
+
+
+
+ }
+ >
+
+
+ ({
+ label:
+ t.displayName ||
+ t.DisplayName ||
+ t.templateName ||
+ t.TemplateName ||
+ t.name ||
+ `Template ${t.GUID}`,
+ value: t.GUID,
+ }))
+ : []
+ }
+ validators={{ required: { value: true, message: "Template selection is required" } }}
+ />
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/src/components/CippComponents/CippTemplateFieldRenderer.jsx b/src/components/CippComponents/CippTemplateFieldRenderer.jsx
index 5f385bfdda64..1757e400acda 100644
--- a/src/components/CippComponents/CippTemplateFieldRenderer.jsx
+++ b/src/components/CippComponents/CippTemplateFieldRenderer.jsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useMemo } from "react";
import { Typography, Divider } from "@mui/material";
import { Grid } from "@mui/system";
import CippFormComponent from "./CippFormComponent";
@@ -10,6 +10,15 @@ const CippTemplateFieldRenderer = ({
formControl,
templateType = "conditionalAccess",
}) => {
+ const intuneDefinitionMap = useMemo(() => {
+ const map = new Map();
+ (intuneCollection || []).forEach((def) => {
+ if (def?.id) {
+ map.set(def.id, def);
+ }
+ });
+ return map;
+ }, []);
// Default blacklisted fields with wildcard support
const defaultBlacklistedFields = [
"id",
@@ -253,6 +262,86 @@ const CippTemplateFieldRenderer = ({
return null;
}
+ // Render Intune group setting collections with child-friendly fields instead of raw [object Object]
+ if (
+ templateType === "intune" &&
+ key.toLowerCase() === "groupsettingcollectionvalue" &&
+ Array.isArray(value)
+ ) {
+ return (
+
+
+ {getCippTranslation(key)}
+
+
+
+ {value.map((groupEntry, groupIndex) => (
+
+
+ {`Entry ${groupIndex + 1}`}
+
+
+ {(groupEntry?.children || []).map((child, childIndex) => {
+ const childPath = `${fieldPath}.${groupIndex}.children.${childIndex}`;
+ const intuneDefinition = intuneDefinitionMap.get(child?.settingDefinitionId);
+ const childLabel =
+ intuneDefinition?.displayName || child?.settingDefinitionId || `Child ${
+ childIndex + 1
+ }`;
+
+ if (child?.simpleSettingValue) {
+ return (
+
+
+
+ );
+ }
+
+ if (child?.choiceSettingValue) {
+ const options =
+ intuneDefinition?.options?.map((option) => ({
+ label: option.displayName || option.id,
+ value: option.id,
+ })) || [];
+
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+ Unsupported group entry type — edit in JSON if needed.
+
+
+ );
+ })}
+
+
+ ))}
+
+
+ );
+ }
+
// Check for custom schema handling
const schemaField = schemaFields[key.toLowerCase()];
if (schemaField) {
@@ -299,9 +388,7 @@ const CippTemplateFieldRenderer = ({
// Handle different setting types
if (settingInstance.choiceSettingValue) {
// Find the setting definition in the intune collection
- const intuneObj = intuneCollection.find(
- (item) => item.id === settingInstance.settingDefinitionId
- );
+ const intuneObj = intuneDefinitionMap.get(settingInstance.settingDefinitionId);
const label = intuneObj?.displayName || `Setting ${index + 1}`;
const options =
@@ -327,9 +414,7 @@ const CippTemplateFieldRenderer = ({
if (settingInstance.simpleSettingValue) {
// Find the setting definition in the intune collection
- const intuneObj = intuneCollection.find(
- (item) => item.id === settingInstance.settingDefinitionId
- );
+ const intuneObj = intuneDefinitionMap.get(settingInstance.settingDefinitionId);
const label = intuneObj?.displayName || `Setting ${index + 1}`;
diff --git a/src/components/CippFormPages/CippAddIntuneReusableSettingTemplateForm.jsx b/src/components/CippFormPages/CippAddIntuneReusableSettingTemplateForm.jsx
new file mode 100644
index 000000000000..6fdf41b42d40
--- /dev/null
+++ b/src/components/CippFormPages/CippAddIntuneReusableSettingTemplateForm.jsx
@@ -0,0 +1,54 @@
+import { Grid } from "@mui/system";
+import CippFormComponent from "/src/components/CippComponents/CippFormComponent";
+
+const CippAddIntuneReusableSettingTemplateForm = ({ formControl }) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CippAddIntuneReusableSettingTemplateForm;
diff --git a/src/data/standards.json b/src/data/standards.json
index 9864918bdf39..4e89eadb5656 100644
--- a/src/data/standards.json
+++ b/src/data/standards.json
@@ -5344,6 +5344,46 @@
}
]
},
+ {
+ "name": "standards.ReusableSettingsTemplate",
+ "cat": "Templates",
+ "label": "Reusable Settings Template",
+ "multiple": true,
+ "disabledFeatures": {
+ "report": false,
+ "warn": false,
+ "remediate": false
+ },
+ "impact": "High Impact",
+ "impactColour": "info",
+ "addedDate": "2026-01-02",
+ "helpText": "Deploy and maintain Intune reusable settings templates that can be referenced by multiple policies.",
+ "executiveText": "Creates and keeps reusable Intune settings templates consistent so common firewall and configuration blocks can be reused across many policies.",
+ "addedComponent": [
+ {
+ "type": "autoComplete",
+ "multiple": true,
+ "creatable": false,
+ "required": true,
+ "name": "TemplateList",
+ "label": "Select Reusable Settings Template",
+ "api": {
+ "queryKey": "ListIntuneReusableSettingTemplates",
+ "url": "/api/ListIntuneReusableSettingTemplates",
+ "labelField": "displayName",
+ "valueField": "GUID",
+ "showRefresh": true,
+ "templateView": {
+ "title": "Reusable Settings",
+ "property": "RawJSON",
+ "type": "intune"
+ }
+ }
+ }
+ ],
+ "powershellEquivalent": "",
+ "recommendedBy": []
+ },
{
"name": "standards.TransportRuleTemplate",
"label": "Transport Rule Template",
diff --git a/src/layouts/config.js b/src/layouts/config.js
index 07ca170abc5b..3bfc34eeb508 100644
--- a/src/layouts/config.js
+++ b/src/layouts/config.js
@@ -447,6 +447,16 @@ export const nativeMenuItems = [
path: "/endpoint/MEM/list-templates",
permissions: ["Endpoint.MEM.*"],
},
+ {
+ title: "Reusable Settings",
+ path: "/endpoint/MEM/reusable-settings",
+ permissions: ["Endpoint.MEM.*"],
+ },
+ {
+ title: "Reusable Settings Templates",
+ path: "/endpoint/MEM/reusable-settings-templates",
+ permissions: ["Endpoint.MEM.*"],
+ },
{
title: "Assignment Filters",
path: "/endpoint/MEM/assignment-filters",
diff --git a/src/pages/cipp/settings/features.js b/src/pages/cipp/settings/features.js
index 930636e3205c..15b6fd3a111e 100644
--- a/src/pages/cipp/settings/features.js
+++ b/src/pages/cipp/settings/features.js
@@ -49,7 +49,7 @@ const Page = () => {
actions: actions,
};
- const simpleColumns = ["Name", "Enabled"];
+ const simpleColumns = ["Name", "Enabled", "Description"];
return (
{
+ const userSettingsDefaults = useSettings();
+
+ const formControl = useForm({
+ mode: "onChange",
+ defaultValues: {
+ tenantFilter: userSettingsDefaults.currentTenant,
+ },
+ });
+
+ return (
+
+
+
+
+
+ );
+};
+
+Page.getLayout = (page) => {page};
+
+export default Page;
diff --git a/src/pages/endpoint/MEM/reusable-settings-templates/edit.jsx b/src/pages/endpoint/MEM/reusable-settings-templates/edit.jsx
new file mode 100644
index 000000000000..d1ca5e5f444b
--- /dev/null
+++ b/src/pages/endpoint/MEM/reusable-settings-templates/edit.jsx
@@ -0,0 +1,374 @@
+import { Alert, Box, Button, Stack, Table, TableBody, TableCell, TableHead, TableRow, Typography, Divider } from "@mui/material";
+import { useForm, useFieldArray } from "react-hook-form";
+import { useRouter } from "next/router";
+import { Layout as DashboardLayout } from "/src/layouts/index.js";
+import CippFormPage from "../../../../components/CippFormPages/CippFormPage";
+import CippFormSkeleton from "../../../../components/CippFormPages/CippFormSkeleton";
+import CippFormComponent from "../../../../components/CippComponents/CippFormComponent";
+import { ApiGetCall } from "../../../../api/ApiCall";
+import { useSettings } from "../../../../hooks/use-settings";
+import { useEffect, useMemo } from "react";
+
+// Structured clone helper for older runtimes
+const deepClone = (obj) => JSON.parse(JSON.stringify(obj));
+
+const generateGuid = () => {
+ const wrap = (val) => `{${val}}`;
+ if (typeof crypto !== "undefined" && crypto.randomUUID) return wrap(crypto.randomUUID());
+ const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
+ return wrap(`${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`);
+};
+
+const EditReusableSettingsTemplate = () => {
+ const router = useRouter();
+ const { id: rawId } = router.query;
+ const { currentTenant } = useSettings();
+
+ const normalizedId = useMemo(() => {
+ if (typeof rawId === "string") return rawId;
+ if (Array.isArray(rawId) && rawId.length > 0) return rawId[0];
+ return undefined;
+ }, [rawId]);
+
+ const formControl = useForm({
+ mode: "onChange",
+ defaultValues: {
+ tenantFilter: currentTenant,
+ GUID: normalizedId,
+ },
+ });
+
+ const templateQuery = ApiGetCall({
+ url: "/api/ListIntuneReusableSettingTemplates",
+ data: normalizedId ? { id: normalizedId } : undefined,
+ queryKey: `ReusableSettingTemplate-${normalizedId}`,
+ waiting: !!normalizedId,
+ });
+
+ const templateData = Array.isArray(templateQuery.data)
+ ? templateQuery.data[0]
+ : templateQuery.data;
+
+ const normalizedTemplate = useMemo(() => {
+ if (!templateData) return null;
+ return {
+ ...templateData,
+ // Normalize all known casing variants to the canonical RawJSON property
+ RawJSON: templateData.RawJSON ?? templateData.RAWJson ?? templateData.RAWJSON,
+ };
+ }, [templateData]);
+
+ const parsedRaw = useMemo(() => {
+ if (!normalizedTemplate?.RawJSON) return null;
+ try {
+ return JSON.parse(normalizedTemplate.RawJSON);
+ } catch (e) {
+ return null;
+ }
+ }, [normalizedTemplate]);
+
+ // Strip the group collection out of the parsed RAW for cleaner form state
+ const sanitizedParsedRaw = useMemo(() => {
+ if (!parsedRaw) return null;
+ const clone = deepClone(parsedRaw);
+ if (clone?.settingInstance?.groupSettingCollectionValue) {
+ delete clone.settingInstance.groupSettingCollectionValue;
+ }
+ return clone;
+ }, [parsedRaw]);
+
+ const groupCollection = useMemo(() => {
+ return (
+ parsedRaw?.settingInstance?.groupSettingCollectionValue ||
+ templateData?.settingInstance?.groupSettingCollectionValue ||
+ []
+ );
+ }, [parsedRaw, templateData]);
+
+ const groupChildDefinitions = useMemo(() => {
+ const first = groupCollection?.[0]?.children || [];
+ return {
+ idDef: first.find((c) => c.settingDefinitionId?.toLowerCase().includes("_id"))?.settingDefinitionId,
+ autoresolveDef: first.find((c) => c.settingDefinitionId?.toLowerCase().includes("_autoresolve"))?.settingDefinitionId,
+ keywordDef: first.find((c) => c.settingDefinitionId?.toLowerCase().includes("_keyword"))?.settingDefinitionId,
+ };
+ }, [groupCollection]);
+
+ useEffect(() => {
+ if (groupCollection) {
+ formControl.setValue("groupSettingCollectionValue", groupCollection);
+ if (sanitizedParsedRaw) {
+ formControl.setValue("parsedRAWJson", sanitizedParsedRaw);
+ }
+ }
+ }, [groupCollection, sanitizedParsedRaw, formControl]);
+
+ useEffect(() => {
+ if (normalizedTemplate) {
+ formControl.setValue("displayName", normalizedTemplate.displayName || normalizedTemplate.name);
+ formControl.setValue("description", normalizedTemplate.description || normalizedTemplate.Description);
+ }
+ }, [normalizedTemplate, formControl]);
+
+ /**
+ * Convert RHF form values into the API payload while preserving Graph @odata fields.
+ * - Flattens react-hook-form autocomplete objects to their .value.
+ * - Restores @odata.* keys from the original template to avoid dot-notation loss from RHF.
+ * - Syncs displayName/description into parsed RAW JSON and reinserts the edited groupSettingCollectionValue.
+ * - Builds the final payload expected by /api/AddIntuneReusableSettingTemplate, including tenant fallback.
+ */
+ const customDataFormatter = useMemo(() => {
+ const getOriginalValueByPath = (obj, path) => {
+ if (!obj) return undefined;
+ const keys = path.split(".");
+ let current = obj;
+ for (const key of keys) {
+ if (current && typeof current === "object" && key in current) {
+ current = current[key];
+ } else {
+ return undefined;
+ }
+ }
+ return current;
+ };
+
+ const extractValues = (obj) => {
+ if (obj === null || obj === undefined) return obj;
+
+ if (
+ obj &&
+ typeof obj === "object" &&
+ obj.hasOwnProperty("value") &&
+ obj.hasOwnProperty("label")
+ ) {
+ return obj.value;
+ }
+
+ if (Array.isArray(obj)) {
+ return obj.map((item) => extractValues(item));
+ }
+
+ if (typeof obj === "object") {
+ const result = {};
+ Object.keys(obj).forEach((key) => {
+ const value = extractValues(obj[key]);
+
+ if (key.endsWith("@odata") && value && typeof value === "object") {
+ // Restore @odata.* keys from the original template to avoid RHF dot-notation artifacts
+ Object.keys(value).forEach((odataKey) => {
+ const baseKey = key.replace("@odata", "");
+ const originalKey = `${baseKey}@odata.${odataKey}`;
+ const originalValue = getOriginalValueByPath(normalizedTemplate, originalKey);
+ if (originalValue !== undefined) {
+ result[originalKey] = originalValue;
+ }
+ });
+ } else {
+ result[key] = value;
+ }
+ });
+ return result;
+ }
+
+ return obj;
+ };
+
+ return (values) => {
+ const processedValues = extractValues(values) || {};
+
+ // Sync template/policy name & description into parsed RAW JSON, and merge edited group collection
+ if (processedValues.parsedRAWJson) {
+ if (processedValues.displayName) {
+ processedValues.parsedRAWJson.displayName = processedValues.displayName;
+ }
+ if (processedValues.description) {
+ processedValues.parsedRAWJson.description = processedValues.description;
+ }
+
+ if (processedValues.groupSettingCollectionValue && processedValues.parsedRAWJson.settingInstance) {
+ processedValues.parsedRAWJson.settingInstance.groupSettingCollectionValue =
+ processedValues.groupSettingCollectionValue;
+ }
+ }
+
+ return {
+ GUID: processedValues.GUID || normalizedId,
+ displayName: processedValues.displayName,
+ description: processedValues.description,
+ package: processedValues.package,
+ rawJSON: JSON.stringify(processedValues.parsedRAWJson || processedValues, null, 2),
+ tenantFilter: processedValues.tenantFilter || currentTenant,
+ };
+ };
+ }, [currentTenant, normalizedId, normalizedTemplate]);
+
+ const { fields, append, remove } = useFieldArray({
+ control: formControl.control,
+ name: "groupSettingCollectionValue",
+ });
+
+ const createEmptyEntry = () => {
+ const { idDef, autoresolveDef, keywordDef } = groupChildDefinitions;
+ const children = [];
+
+ if (idDef) {
+ children.push({
+ "@odata.type": "#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance",
+ settingDefinitionId: idDef,
+ simpleSettingValue: {
+ "@odata.type": "#microsoft.graph.deviceManagementConfigurationStringSettingValue",
+ value: generateGuid(),
+ },
+ });
+ }
+
+ if (autoresolveDef) {
+ children.push({
+ "@odata.type": "#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance",
+ settingDefinitionId: autoresolveDef,
+ choiceSettingValue: { value: "", children: [] },
+ });
+ }
+
+ if (keywordDef) {
+ children.push({
+ "@odata.type": "#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance",
+ settingDefinitionId: keywordDef,
+ simpleSettingValue: {
+ "@odata.type": "#microsoft.graph.deviceManagementConfigurationStringSettingValue",
+ value: "",
+ },
+ });
+ }
+
+ return { children };
+ };
+
+ return (
+
+
+ {templateQuery.isLoading ? (
+
+ ) : templateQuery.isError || !normalizedTemplate ? (
+ Error loading reusable settings template.
+ ) : (
+ <>
+
+ Template
+
+
+
+
+
+
+ {fields?.length > 0 && (
+
+
+ Group Setting Collection (Policy)
+
+
+
+
+ ID
+ Autoresolve
+ Keyword
+ Actions
+
+
+
+ {fields.map((field, index) => {
+ const idPath = `groupSettingCollectionValue.${index}.children.0.simpleSettingValue.value`;
+ const autoresolvePath = `groupSettingCollectionValue.${index}.children.1.choiceSettingValue.value`;
+ const keywordPath = `groupSettingCollectionValue.${index}.children.2.simpleSettingValue.value`;
+
+ const autoresolveBase = groupChildDefinitions.autoresolveDef || "autoresolve";
+ const autoresolveTrue = `${autoresolveBase}_true`;
+ const autoresolveFalse = `${autoresolveBase}_false`;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+
+
+
+
+
+ )}
+ >
+ )}
+
+
+ );
+};
+
+EditReusableSettingsTemplate.getLayout = (page) => {page};
+
+export default EditReusableSettingsTemplate;
diff --git a/src/pages/endpoint/MEM/reusable-settings-templates/index.js b/src/pages/endpoint/MEM/reusable-settings-templates/index.js
new file mode 100644
index 000000000000..230f214e3fca
--- /dev/null
+++ b/src/pages/endpoint/MEM/reusable-settings-templates/index.js
@@ -0,0 +1,108 @@
+import { Layout as DashboardLayout } from "/src/layouts/index.js";
+import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx";
+import CippJsonView from "../../../../components/CippFormPages/CippJSONView";
+import { Button } from "@mui/material";
+import Link from "next/link";
+import { AddBox, GitHub, Delete, Edit } from "@mui/icons-material";
+import { ApiGetCall } from "/src/api/ApiCall";
+
+const Page = () => {
+ const pageTitle = "Reusable Settings Templates";
+
+ const integrations = ApiGetCall({
+ url: "/api/ListExtensionsConfig",
+ queryKey: "Integrations",
+ refetchOnMount: false,
+ refetchOnReconnect: false,
+ });
+
+ const actions = [
+ {
+ label: "Edit Template",
+ icon: ,
+ link: "/endpoint/MEM/reusable-settings-templates/edit?id=[GUID]",
+ },
+ {
+ 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/RemoveIntuneReusableSettingTemplate",
+ icon: ,
+ data: {
+ ID: "GUID",
+ },
+ confirmText: "Do you want to delete the template?",
+ multiPost: false,
+ },
+ ];
+
+ const offCanvas = {
+ children: (row) => ,
+ size: "lg",
+ };
+
+ const simpleColumns = ["displayName", "package", "description", "isSynced"];
+
+ return (
+ }>
+ Add Reusable Settings Template
+
+ }
+ apiUrl="/api/ListIntuneReusableSettingTemplates"
+ tenantInTitle={false}
+ actions={actions}
+ offCanvas={offCanvas}
+ simpleColumns={simpleColumns}
+ queryKey="ListIntuneReusableSettingTemplates-table"
+ />
+ );
+};
+
+Page.getLayout = (page) => {page};
+export default Page;
diff --git a/src/pages/endpoint/MEM/reusable-settings/edit.jsx b/src/pages/endpoint/MEM/reusable-settings/edit.jsx
new file mode 100644
index 000000000000..b6dcd1825ffa
--- /dev/null
+++ b/src/pages/endpoint/MEM/reusable-settings/edit.jsx
@@ -0,0 +1,136 @@
+import { useEffect } from "react";
+import { Alert, Box, Stack } from "@mui/material";
+import { Grid } from "@mui/system";
+import { useForm } from "react-hook-form";
+import { useRouter } from "next/router";
+import { Layout as DashboardLayout } from "/src/layouts/index.js";
+import CippFormPage from "/src/components/CippFormPages/CippFormPage";
+import CippFormSkeleton from "/src/components/CippFormPages/CippFormSkeleton";
+import CippFormComponent from "/src/components/CippComponents/CippFormComponent";
+import CippJsonView from "/src/components/CippFormPages/CippJSONView";
+import { ApiGetCall } from "/src/api/ApiCall";
+import { useSettings } from "/src/hooks/use-settings";
+
+const EditReusableSetting = () => {
+ const router = useRouter();
+ const { id, tenant } = router.query;
+ const { currentTenant } = useSettings();
+
+ const effectiveTenant = tenant || currentTenant;
+
+ const formControl = useForm({
+ mode: "onChange",
+ defaultValues: {
+ tenantFilter: effectiveTenant,
+ },
+ });
+
+ const { reset } = formControl;
+
+ const settingQuery = ApiGetCall({
+ url: "/api/ListIntuneReusableSettings",
+ queryKey: ["ListIntuneReusableSettings", effectiveTenant, id],
+ enabled: !!id && !!effectiveTenant,
+ data: { tenantFilter: effectiveTenant, ID: id },
+ });
+
+ const record = Array.isArray(settingQuery.data) ? settingQuery.data[0] : settingQuery.data;
+
+ useEffect(() => {
+ if (record) {
+ reset({
+ tenantFilter: effectiveTenant,
+ ID: record.id,
+ displayName: record.displayName,
+ description: record.description,
+ rawJSON: record.RawJSON,
+ });
+ }
+ }, [record, effectiveTenant, reset]);
+
+ const safeJson = () => {
+ if (!record?.RawJSON) return null;
+ try {
+ return JSON.parse(record.RawJSON);
+ } catch (e) {
+ console.error("Failed to parse RawJSON for reusable setting preview", {
+ error: e,
+ recordId: record?.id,
+ });
+ return null;
+ }
+ };
+
+ const customDataformatter = (values) => ({
+ tenantFilter: values.tenantFilter || effectiveTenant,
+ ID: values.ID, // forward the existing setting id so the API updates the same record
+ TemplateId: values.ID, // keep legacy TemplateId for API compatibility
+ displayName: values.displayName,
+ description: values.description,
+ rawJSON: values.rawJSON,
+ });
+
+ return (
+
+
+ {settingQuery.isLoading ? (
+
+ ) : settingQuery.isError || !record ? (
+ Error loading reusable setting or setting not found.
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+EditReusableSetting.getLayout = (page) => {page};
+
+export default EditReusableSetting;
diff --git a/src/pages/endpoint/MEM/reusable-settings/index.js b/src/pages/endpoint/MEM/reusable-settings/index.js
new file mode 100644
index 000000000000..2ac91d5d9955
--- /dev/null
+++ b/src/pages/endpoint/MEM/reusable-settings/index.js
@@ -0,0 +1,65 @@
+import { Book, DeleteForever } from "@mui/icons-material";
+import { CippReusableSettingsDeployDrawer } from "/src/components/CippComponents/CippReusableSettingsDeployDrawer.jsx";
+import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx";
+import { Layout as DashboardLayout } from "/src/layouts/index.js";
+import { useSettings } from "../../../../hooks/use-settings";
+import CippJsonView from "../../../../components/CippFormPages/CippJSONView";
+
+const Page = () => {
+ const { currentTenant } = useSettings();
+ const pageTitle = "Reusable Settings";
+
+ const actions = [
+ {
+ label: "Edit Reusable Setting",
+ link: `/endpoint/MEM/reusable-settings/edit?id=[id]&tenant=${currentTenant}&tenantFilter=${currentTenant}`,
+ },
+ {
+ label: "Delete Reusable Setting",
+ type: "POST",
+ url: "/api/RemoveIntuneReusableSetting",
+ icon: ,
+ color: "error",
+ data: {
+ ID: "id",
+ DisplayName: "displayName",
+ },
+ confirmText: "Delete this reusable setting from the tenant?",
+ multiPost: false,
+ },
+ {
+ label: "Create Template from Setting",
+ type: "POST",
+ url: "/api/AddIntuneReusableSettingTemplate",
+ icon: ,
+ data: {
+ displayName: "displayName",
+ description: "description",
+ rawJSON: "RawJSON",
+ },
+ confirmText: "Create a reusable settings template from this entry?",
+ multiPost: false,
+ },
+ ];
+
+ const offCanvas = {
+ children: (row) => ,
+ size: "lg",
+ };
+
+ return (
+ }
+ apiUrl="/api/ListIntuneReusableSettings"
+ queryKey={`ListIntuneReusableSettings-${currentTenant}`}
+ actions={actions}
+ offCanvas={offCanvas}
+ simpleColumns={["displayName", "description", "id", "version"]}
+ />
+ );
+};
+
+Page.getLayout = (page) => {page};
+
+export default Page;
diff --git a/src/pages/tenant/administration/applications/app-registrations.js b/src/pages/tenant/administration/applications/app-registrations.js
index 74dcf8b4e072..1adcf038947c 100644
--- a/src/pages/tenant/administration/applications/app-registrations.js
+++ b/src/pages/tenant/administration/applications/app-registrations.js
@@ -47,6 +47,13 @@ const Page = () => {
DisplayName: "displayName",
Type: "application",
},
+ fields: [
+ {
+ type: "switch",
+ name: "Overwrite",
+ label: "Overwrite Existing Template",
+ },
+ ],
confirmText:
"Create a deployment template from '[displayName]'? This will copy all permissions and create a reusable template. If you run this from a customer tenant, the App Registration will first be copied to the partner tenant as a multi-tenant app.",
condition: (row) => canWriteApplication && !row?.applicationTemplateId,
@@ -130,7 +137,7 @@ const Page = () => {
options={
row?.passwordCredentials?.map((cred) => ({
label: `${cred.displayName || "Unnamed"} (Expiration: ${new Date(
- cred.endDateTime
+ cred.endDateTime,
).toLocaleDateString()})`,
value: cred.keyId,
})) || []
diff --git a/src/pages/tenant/administration/applications/enterprise-apps.js b/src/pages/tenant/administration/applications/enterprise-apps.js
index 39aac9021b60..6a408ca37021 100644
--- a/src/pages/tenant/administration/applications/enterprise-apps.js
+++ b/src/pages/tenant/administration/applications/enterprise-apps.js
@@ -49,6 +49,13 @@ const Page = () => {
DisplayName: "displayName",
Type: "servicePrincipal",
},
+ fields: [
+ {
+ type: "switch",
+ name: "Overwrite",
+ label: "Overwrite Existing Template",
+ },
+ ],
confirmText:
"Create a deployment template from '[displayName]'? This will copy all permissions and create a reusable template.",
condition: (row) => canWriteApplication && row?.signInAudience === "AzureADMultipleOrgs",
@@ -78,7 +85,7 @@ const Page = () => {
options={
row?.passwordCredentials?.map((cred) => ({
label: `${cred.displayName || "Unnamed"} (Expiration: ${new Date(
- cred.endDateTime
+ cred.endDateTime,
).toLocaleDateString()})`,
value: cred.keyId,
})) || []
diff --git a/src/pages/tenant/standards/domains-analyser/index.js b/src/pages/tenant/standards/domains-analyser/index.js
index 58ee11a13fb4..aef98643e21c 100644
--- a/src/pages/tenant/standards/domains-analyser/index.js
+++ b/src/pages/tenant/standards/domains-analyser/index.js
@@ -6,7 +6,7 @@ import { ApiGetCall } from "../../../../api/ApiCall";
import { useSettings } from "../../../../hooks/use-settings";
import { CippApiResults } from "../../../../components/CippComponents/CippApiResults";
import { CippDomainCards } from "../../../../components/CippCards/CippDomainCards";
-import { DeleteForever, TravelExplore, Refresh } from "@mui/icons-material";
+import { DeleteForever, TravelExplore, Refresh, Settings } from "@mui/icons-material";
import { DomainAnalyserDialog } from "../../../../components/CippComponents/DomainAnalyserDialog";
import { useDialog } from "../../../../hooks/use-dialog";
@@ -19,9 +19,27 @@ const Page = () => {
waiting: false,
});
const actions = [
+ {
+ label: "Add/Modify DKIM Selectors",
+ type: "POST",
+ icon: ,
+ url: "/api/ExecDnsConfig",
+ data: { Action: "!SetDkimConfig", Domain: "Domain" },
+ confirmText: "Enter the DKIM selectors for [Domain] (comma-separated)",
+ fields: [
+ {
+ type: "textField",
+ name: "Selector",
+ label: "DKIM Selectors",
+ placeholder: "selector1, selector2, selector3",
+ required: true,
+ },
+ ],
+ multiPost: false,
+ },
{
label: "Delete from analyser",
- type: "GET",
+ type: "POST",
icon: ,
url: "/api/ExecDnsConfig",
data: { Action: "!RemoveDomain", Domain: "Domain" },
diff --git a/yarn.lock b/yarn.lock
index 309852f41632..ca973629395e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2134,20 +2134,20 @@
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.11.2.tgz#00409e743ac4eea9afe5b7708594d5fcebb00212"
integrity sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==
-"@tiptap/core@^3.13.0", "@tiptap/core@^3.4.1":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.13.0.tgz#ae3fe6fe7732f36b6ea8a2198e1fc53a4ad0d0d2"
- integrity sha512-iUelgiTMgPVMpY5ZqASUpk8mC8HuR9FWKaDzK27w9oWip9tuB54Z8mePTxNcQaSPb6ErzEaC8x8egrRt7OsdGQ==
+"@tiptap/core@^3.19.0", "@tiptap/core@^3.4.1":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.19.0.tgz#dca483b50e1b8a596f695aecde387a79fe7da717"
+ integrity sha512-bpqELwPW+DG8gWiD8iiFtSl4vIBooG5uVJod92Qxn3rA9nFatyXRr4kNbMJmOZ66ezUvmCjXVe/5/G4i5cyzKA==
-"@tiptap/extension-blockquote@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-3.13.0.tgz#33508ad7f0bd4d74d5065f11d6e33c50ef8835a2"
- integrity sha512-K1z/PAIIwEmiWbzrP//4cC7iG1TZknDlF1yb42G7qkx2S2X4P0NiqX7sKOej3yqrPjKjGwPujLMSuDnCF87QkQ==
+"@tiptap/extension-blockquote@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-3.19.0.tgz#86c52e8e3b6d1e072ae0d9c895723034a1e37096"
+ integrity sha512-y3UfqY9KD5XwWz3ndiiJ089Ij2QKeiXy/g1/tlAN/F1AaWsnkHEHMLxCP1BIqmMpwsX7rZjMLN7G5Lp7c9682A==
-"@tiptap/extension-bold@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.13.0.tgz#1fbff35b20da292172fc5a1886576c3410e1e3ca"
- integrity sha512-VYiDN9EEwR6ShaDLclG8mphkb/wlIzqfk7hxaKboq1G+NSDj8PcaSI9hldKKtTCLeaSNu6UR5nkdu/YHdzYWTw==
+"@tiptap/extension-bold@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.19.0.tgz#ef0ddfd9b242ef9c25e3348aef9bf2dc681cdc19"
+ integrity sha512-UZgb1d0XK4J/JRIZ7jW+s4S6KjuEDT2z1PPM6ugcgofgJkWQvRZelCPbmtSFd3kwsD+zr9UPVgTh9YIuGQ8t+Q==
"@tiptap/extension-bubble-menu@^3.13.0":
version "3.13.0"
@@ -2156,127 +2156,127 @@
dependencies:
"@floating-ui/dom" "^1.0.0"
-"@tiptap/extension-bullet-list@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.13.0.tgz#277c9380704f618d71b63278da7bba45ed2b7905"
- integrity sha512-fFQmmEUoPzRGiQJ/KKutG35ZX21GE+1UCDo8Q6PoWH7Al9lex47nvyeU1BiDYOhcTKgIaJRtEH5lInsOsRJcSA==
+"@tiptap/extension-bullet-list@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.19.0.tgz#acf12e952b6a5873dc20b58530f2f524807bbd6f"
+ integrity sha512-F9uNnqd0xkJbMmRxVI5RuVxwB9JaCH/xtRqOUNQZnRBt7IdAElCY+Dvb4hMCtiNv+enGM/RFGJuFHR9TxmI7rw==
-"@tiptap/extension-code-block@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-3.13.0.tgz#ded41a224db15e938c6a871462a0c33c00acd657"
- integrity sha512-kIwfQ4iqootsWg9e74iYJK54/YMIj6ahUxEltjZRML5z/h4gTDcQt2eTpnEC8yjDjHeUVOR94zH9auCySyk9CQ==
+"@tiptap/extension-code-block@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-3.19.0.tgz#71a7a362b3fa68c1789c8b9ac224ca89eb410630"
+ integrity sha512-b/2qR+tMn8MQb+eaFYgVk4qXnLNkkRYmwELQ8LEtEDQPxa5Vl7J3eu8+4OyoIFhZrNDZvvoEp80kHMCP8sI6rg==
-"@tiptap/extension-code@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-3.13.0.tgz#7c05fb8477356aebe8b1f00117a32d3abbf24357"
- integrity sha512-sF5raBni6iSVpXWvwJCAcOXw5/kZ+djDHx1YSGWhopm4+fsj0xW7GvVO+VTwiFjZGKSw+K5NeAxzcQTJZd3Vhw==
+"@tiptap/extension-code@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-3.19.0.tgz#15d53c139ad64d1debcc08c7ca5afbcc8e531f0b"
+ integrity sha512-2kqqQIXBXj2Or+4qeY3WoE7msK+XaHKL6EKOcKlOP2BW8eYqNTPzNSL+PfBDQ3snA7ljZQkTs/j4GYDj90vR1A==
-"@tiptap/extension-document@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-3.13.0.tgz#74757e23bf92bba82226a91580ce738bc68bd3af"
- integrity sha512-RjU7hTJwjKXIdY57o/Pc+Yr8swLkrwT7PBQ/m+LCX5oO/V2wYoWCjoBYnK5KSHrWlNy/aLzC33BvLeqZZ9nzlQ==
+"@tiptap/extension-document@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-3.19.0.tgz#dfa6889cff748d489e0bc1028918bf4571372ba5"
+ integrity sha512-AOf0kHKSFO0ymjVgYSYDncRXTITdTcrj1tqxVazrmO60KNl1Rc2dAggDvIVTEBy5NvceF0scc7q3sE/5ZtVV7A==
-"@tiptap/extension-dropcursor@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.13.0.tgz#04f7659c86558ebeb068fd7d5c2474d8bd28b430"
- integrity sha512-m7GPT3c/83ni+bbU8c+3dpNa8ug+aQ4phNB1Q52VQG3oTonDJnZS7WCtn3lB/Hi1LqoqMtEHwhepU2eD+JeXqQ==
+"@tiptap/extension-dropcursor@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.19.0.tgz#fbef441944842f23fe0a35154b519103166a4848"
+ integrity sha512-sf3dEZXiLvsGqVK2maUIzXY6qtYYCvBumag7+VPTMGQ0D4hiZ1X/4ukt4+6VXDg5R2WP1CoIt/QvUetUjWNhbQ==
"@tiptap/extension-floating-menu@^3.13.0":
version "3.13.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-3.13.0.tgz#03d03292add49d1b380cdb1ff3890b2956d4e3f5"
integrity sha512-OsezV2cMofZM4c13gvgi93IEYBUzZgnu8BXTYZQiQYekz4bX4uulBmLa1KOA9EN71FzS+SoLkXHU0YzlbLjlxA==
-"@tiptap/extension-gapcursor@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.13.0.tgz#5e4fbd3b066fa10656314bbbff2e329709be5d2c"
- integrity sha512-KVxjQKkd964nin+1IdM2Dvej/Jy4JTMcMgq5seusUhJ9T9P8F9s2D5Iefwgkps3OCzub/aF+eAsZe+1P5KSIgA==
+"@tiptap/extension-gapcursor@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.19.0.tgz#64e5462a4ab2f0bd110738410dcbf3597d76349f"
+ integrity sha512-w7DACS4oSZaDWjz7gropZHPc9oXqC9yERZTcjWxyORuuIh1JFf0TRYspleK+OK28plK/IftojD/yUDn1MTRhvA==
-"@tiptap/extension-hard-break@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.13.0.tgz#b1444339c544f27fe8cff8dcbdb99007e0cdc3e1"
- integrity sha512-nH1OBaO+/pakhu+P1jF208mPgB70IKlrR/9d46RMYoYbqJTNf4KVLx5lHAOHytIhjcNg+MjyTfJWfkK+dyCCyg==
+"@tiptap/extension-hard-break@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.19.0.tgz#7120524cec9ed4b957963693cb4c57cbecbaecf8"
+ integrity sha512-lAmQraYhPS5hafvCl74xDB5+bLuNwBKIEsVoim35I0sDJj5nTrfhaZgMJ91VamMvT+6FF5f1dvBlxBxAWa8jew==
-"@tiptap/extension-heading@^3.13.0", "@tiptap/extension-heading@^3.4.1":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-3.13.0.tgz#ead7f224de24ac66bb198cabe6b2af9617967583"
- integrity sha512-8VKWX8waYPtUWN97J89em9fOtxNteh6pvUEd0htcOAtoxjt2uZjbW5N4lKyWhNKifZBrVhH2Cc2NUPuftCVgxw==
+"@tiptap/extension-heading@^3.19.0", "@tiptap/extension-heading@^3.4.1":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-3.19.0.tgz#d0bc93426c01a2ed36b9124c1a8205ab3945e77a"
+ integrity sha512-uLpLlfyp086WYNOc0ekm1gIZNlEDfmzOhKzB0Hbyi6jDagTS+p9mxUNYeYOn9jPUxpFov43+Wm/4E24oY6B+TQ==
-"@tiptap/extension-horizontal-rule@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.13.0.tgz#c51eb35f3b3bf6308ab6b354a06c0e96c19dbff6"
- integrity sha512-ZUFyORtjj22ib8ykbxRhWFQOTZjNKqOsMQjaAGof30cuD2DN5J5pMz7Haj2fFRtLpugWYH+f0Mi+WumQXC3hCw==
+"@tiptap/extension-horizontal-rule@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.19.0.tgz#0e77078fcd53beca786277ce83d259e2103cc361"
+ integrity sha512-iqUHmgMGhMgYGwG6L/4JdelVQ5Mstb4qHcgTGd/4dkcUOepILvhdxajPle7OEdf9sRgjQO6uoAU5BVZVC26+ng==
"@tiptap/extension-image@^3.4.1":
version "3.13.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-3.13.0.tgz#55edb952e86c2ebed436cd53def8b2e743d71d7e"
integrity sha512-223uzLUkIa1rkK7aQK3AcIXe6LbCtmnpVb7sY5OEp+LpSaSPyXwyrZ4A0EO1o98qXG68/0B2OqMntFtA9c5Fbw==
-"@tiptap/extension-italic@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.13.0.tgz#c855521360c8079574f7b0855148e4f561ba396a"
- integrity sha512-XbVTgmzk1kgUMTirA6AGdLTcKHUvEJoh3R4qMdPtwwygEOe7sBuvKuLtF6AwUtpnOM+Y3tfWUTNEDWv9AcEdww==
+"@tiptap/extension-italic@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.19.0.tgz#af2a9c095ec846e379041f3e17e1dd101a5a4bf8"
+ integrity sha512-6GffxOnS/tWyCbDkirWNZITiXRta9wrCmrfa4rh+v32wfaOL1RRQNyqo9qN6Wjyl1R42Js+yXTzTTzZsOaLMYA==
-"@tiptap/extension-link@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-3.13.0.tgz#c6b087a39860068b93d1fb8fcbebbd360f0188b4"
- integrity sha512-LuFPJ5GoL12GHW4A+USsj60O90pLcwUPdvEUSWewl9USyG6gnLnY/j5ZOXPYH7LiwYW8+lhq7ABwrDF2PKyBbA==
+"@tiptap/extension-link@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-3.19.0.tgz#e8e656735bda6ca1d4b6577821e06274ab0ff6c8"
+ integrity sha512-HEGDJnnCPfr7KWu7Dsq+eRRe/mBCsv6DuI+7fhOCLDJjjKzNgrX2abbo/zG3D/4lCVFaVb+qawgJubgqXR/Smw==
dependencies:
linkifyjs "^4.3.2"
-"@tiptap/extension-list-item@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-3.13.0.tgz#03f17af7ed2d0643638e07ce96ad0f9c044ff69b"
- integrity sha512-63NbcS/XeQP2jcdDEnEAE3rjJICDj8y1SN1h/MsJmSt1LusnEo8WQ2ub86QELO6XnD3M04V03cY6Knf6I5mTkw==
+"@tiptap/extension-list-item@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-3.19.0.tgz#b2218ff6be694b581fd7d817810a33ee1c218311"
+ integrity sha512-VsSKuJz4/Tb6ZmFkXqWpDYkRzmaLTyE6dNSEpNmUpmZ32sMqo58mt11/huADNwfBFB0Ve7siH/VnFNIJYY3xvg==
-"@tiptap/extension-list-keymap@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.13.0.tgz#75ee2c28f5d944c407309ce987d07ae23c4cd45a"
- integrity sha512-P+HtIa1iwosb1feFc8B/9MN5EAwzS+/dZ0UH0CTF2E4wnp5Z9OMxKl1IYjfiCwHzZrU5Let+S/maOvJR/EmV0g==
+"@tiptap/extension-list-keymap@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.19.0.tgz#41b87b154560aad92e779bff5c6e32e125b792ea"
+ integrity sha512-bxgmAgA3RzBGA0GyTwS2CC1c+QjkJJq9hC+S6PSOWELGRiTbwDN3MANksFXLjntkTa0N5fOnL27vBHtMStURqw==
-"@tiptap/extension-list@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-list/-/extension-list-3.13.0.tgz#6981a395f2fbe46d9ad20deb75cf65aa9e33feba"
- integrity sha512-MMFH0jQ4LeCPkJJFyZ77kt6eM/vcKujvTbMzW1xSHCIEA6s4lEcx9QdZMPpfmnOvTzeoVKR4nsu2t2qT9ZXzAw==
+"@tiptap/extension-list@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-list/-/extension-list-3.19.0.tgz#737dcb56ba9838a4431c1afb035bd622fab46d21"
+ integrity sha512-N6nKbFB2VwMsPlCw67RlAtYSK48TAsAUgjnD+vd3ieSlIufdQnLXDFUP6hFKx9mwoUVUgZGz02RA6bkxOdYyTw==
-"@tiptap/extension-ordered-list@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.13.0.tgz#552d4a57e9116fd7d32e49c5cdc346baf0bbfd74"
- integrity sha512-QuDyLzuK/3vCvx9GeKhgvHWrGECBzmJyAx6gli2HY+Iil7XicbfltV4nvhIxgxzpx3LDHLKzJN9pBi+2MzX60g==
+"@tiptap/extension-ordered-list@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.19.0.tgz#f6f8bfe41d3429c505b44764b473b6dfd7bcd2a1"
+ integrity sha512-cxGsINquwHYE1kmhAcLNLHAofmoDEG6jbesR5ybl7tU5JwtKVO7S/xZatll2DU1dsDAXWPWEeeMl4e/9svYjCg==
-"@tiptap/extension-paragraph@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-3.13.0.tgz#01881b5954136de5059e7882be5b210eca0dac46"
- integrity sha512-9csQde1i0yeZI5oQQ9e1GYNtGL2JcC2d8Fwtw9FsGC8yz2W0h+Fmk+3bc2kobbtO5LGqupSc1fKM8fAg5rSRDg==
+"@tiptap/extension-paragraph@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-3.19.0.tgz#91adde189aabf13a2bfbb2d961833d3bc2bc055f"
+ integrity sha512-xWa6gj82l5+AzdYyrSk9P4ynySaDzg/SlR1FarXE5yPXibYzpS95IWaVR0m2Qaz7Rrk+IiYOTGxGRxcHLOelNg==
-"@tiptap/extension-strike@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-3.13.0.tgz#f753bae727549fb32ee9251036890ed5f39bc443"
- integrity sha512-VHhWNqTAMOfrC48m2FcPIZB0nhl6XHQviAV16SBc+EFznKNv9tQUsqQrnuQ2y6ZVfqq5UxvZ3hKF/JlN/Ff7xw==
+"@tiptap/extension-strike@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-3.19.0.tgz#eac7712cc791488f4c1c48baf3aed1a8d95f398c"
+ integrity sha512-xYpabHsv7PccLUBQaP8AYiFCnYbx6P93RHPd0lgNwhdOjYFd931Zy38RyoxPHAgbYVmhf1iyx7lpuLtBnhS5dA==
"@tiptap/extension-table@^3.4.1":
version "3.13.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-3.13.0.tgz#83283bc818582e621cefabf173beeb37fe6f30ba"
integrity sha512-LcH9KE4QBUJ6IPwt1Uo5iU7zatFjUUvXbctIu2fKQ9nqJ7nNSFxRhkNyporVFkTWYH7/rb0qMoF1VxSUGefG5w==
-"@tiptap/extension-text@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.13.0.tgz#90d38438eb99135b1221d7f2944a06545f21d39d"
- integrity sha512-VcZIna93rixw7hRkHGCxDbL3kvJWi80vIT25a2pXg0WP1e7Pi3nBYvZIL4SQtkbBCji9EHrbZx3p8nNPzfazYw==
+"@tiptap/extension-text@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.19.0.tgz#353278c97bd8f5bdc29f06942fbd1e856bdb5b18"
+ integrity sha512-K95+SnbZy0h6hNFtfy23n8t/nOcTFEf69In9TSFVVmwn/Nwlke+IfiESAkqbt1/7sKJeegRXYO7WzFEmFl9Q/g==
-"@tiptap/extension-underline@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.13.0.tgz#7fc969c3b7adc7d7cc7def498f85c4cb30cf3aba"
- integrity sha512-VDQi+UYw0tFnfghpthJTFmtJ3yx90kXeDwFvhmT8G+O+si5VmP05xYDBYBmYCix5jqKigJxEASiBL0gYOgMDEg==
+"@tiptap/extension-underline@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.19.0.tgz#bbc81d085725981d256127ab416f91d0802ec2a4"
+ integrity sha512-800MGEWfG49j10wQzAFiW/ele1HT04MamcL8iyuPNu7ZbjbGN2yknvdrJlRy7hZlzIrVkZMr/1tz62KN33VHIw==
-"@tiptap/extensions@^3.13.0":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.13.0.tgz#542ee8a97575ae32090302b7f09522e025715297"
- integrity sha512-i7O0ptSibEtTy+2PIPsNKEvhTvMaFJg1W4Oxfnbuxvaigs7cJV9Q0lwDUcc7CPsNw2T1+44wcxg431CzTvdYoA==
+"@tiptap/extensions@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.19.0.tgz#5747c0ebf460b9669e8b4362561872448f66abfe"
+ integrity sha512-ZmGUhLbMWaGqnJh2Bry+6V4M6gMpUDYo4D1xNux5Gng/E/eYtc+PMxMZ/6F7tNTAuujLBOQKj6D+4SsSm457jw==
-"@tiptap/pm@^3.13.0", "@tiptap/pm@^3.4.1":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.13.0.tgz#d01e9f08e2be3e6bfa69ed4457a1c2fee87157b3"
- integrity sha512-WKR4ucALq+lwx0WJZW17CspeTpXorbIOpvKv5mulZica6QxqfMhn8n1IXCkDws/mCoLRx4Drk5d377tIjFNsvQ==
+"@tiptap/pm@^3.19.0", "@tiptap/pm@^3.4.1":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.19.0.tgz#5cb499c7b2603ec6550d0c7a70b924f27fdb7692"
+ integrity sha512-789zcnM4a8OWzvbD2DL31d0wbSm9BVeO/R7PLQwLIGysDI3qzrcclyZ8yhqOEVuvPitRRwYLq+mY14jz7kY4cw==
dependencies:
prosemirror-changeset "^2.3.0"
prosemirror-collab "^1.3.1"
@@ -2309,35 +2309,35 @@
"@tiptap/extension-bubble-menu" "^3.13.0"
"@tiptap/extension-floating-menu" "^3.13.0"
-"@tiptap/starter-kit@^3.4.1":
- version "3.13.0"
- resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-3.13.0.tgz#7f803f0e089a7c2cbd016ad79b257c4cbe910208"
- integrity sha512-Ojn6sRub04CRuyQ+9wqN62JUOMv+rG1vXhc2s6DCBCpu28lkCMMW+vTe7kXJcEdbot82+5swPbERw9vohswFzg==
- dependencies:
- "@tiptap/core" "^3.13.0"
- "@tiptap/extension-blockquote" "^3.13.0"
- "@tiptap/extension-bold" "^3.13.0"
- "@tiptap/extension-bullet-list" "^3.13.0"
- "@tiptap/extension-code" "^3.13.0"
- "@tiptap/extension-code-block" "^3.13.0"
- "@tiptap/extension-document" "^3.13.0"
- "@tiptap/extension-dropcursor" "^3.13.0"
- "@tiptap/extension-gapcursor" "^3.13.0"
- "@tiptap/extension-hard-break" "^3.13.0"
- "@tiptap/extension-heading" "^3.13.0"
- "@tiptap/extension-horizontal-rule" "^3.13.0"
- "@tiptap/extension-italic" "^3.13.0"
- "@tiptap/extension-link" "^3.13.0"
- "@tiptap/extension-list" "^3.13.0"
- "@tiptap/extension-list-item" "^3.13.0"
- "@tiptap/extension-list-keymap" "^3.13.0"
- "@tiptap/extension-ordered-list" "^3.13.0"
- "@tiptap/extension-paragraph" "^3.13.0"
- "@tiptap/extension-strike" "^3.13.0"
- "@tiptap/extension-text" "^3.13.0"
- "@tiptap/extension-underline" "^3.13.0"
- "@tiptap/extensions" "^3.13.0"
- "@tiptap/pm" "^3.13.0"
+"@tiptap/starter-kit@^3.19.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-3.19.0.tgz#312440bd18c3cce379ea8eab3fe174b8141dd313"
+ integrity sha512-dTCkHEz+Y8ADxX7h+xvl6caAj+3nII/wMB1rTQchSuNKqJTOrzyUsCWm094+IoZmLT738wANE0fRIgziNHs/ug==
+ dependencies:
+ "@tiptap/core" "^3.19.0"
+ "@tiptap/extension-blockquote" "^3.19.0"
+ "@tiptap/extension-bold" "^3.19.0"
+ "@tiptap/extension-bullet-list" "^3.19.0"
+ "@tiptap/extension-code" "^3.19.0"
+ "@tiptap/extension-code-block" "^3.19.0"
+ "@tiptap/extension-document" "^3.19.0"
+ "@tiptap/extension-dropcursor" "^3.19.0"
+ "@tiptap/extension-gapcursor" "^3.19.0"
+ "@tiptap/extension-hard-break" "^3.19.0"
+ "@tiptap/extension-heading" "^3.19.0"
+ "@tiptap/extension-horizontal-rule" "^3.19.0"
+ "@tiptap/extension-italic" "^3.19.0"
+ "@tiptap/extension-link" "^3.19.0"
+ "@tiptap/extension-list" "^3.19.0"
+ "@tiptap/extension-list-item" "^3.19.0"
+ "@tiptap/extension-list-keymap" "^3.19.0"
+ "@tiptap/extension-ordered-list" "^3.19.0"
+ "@tiptap/extension-paragraph" "^3.19.0"
+ "@tiptap/extension-strike" "^3.19.0"
+ "@tiptap/extension-text" "^3.19.0"
+ "@tiptap/extension-underline" "^3.19.0"
+ "@tiptap/extensions" "^3.19.0"
+ "@tiptap/pm" "^3.19.0"
"@trysound/sax@0.2.0":
version "0.2.0"
@@ -2567,11 +2567,6 @@
dependencies:
csstype "^3.2.2"
-"@types/trusted-types@^1.0.6":
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-1.0.6.tgz#569b8a08121d3203398290d602d84d73c8dcf5da"
- integrity sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==
-
"@types/trusted-types@^2.0.7":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
@@ -3728,6 +3723,13 @@ domhandler@^5.0.2, domhandler@^5.0.3:
dependencies:
domelementtype "^2.3.0"
+dompurify@3.2.7:
+ version "3.2.7"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.2.7.tgz#721d63913db5111dd6dfda8d3a748cfd7982d44a"
+ integrity sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==
+ optionalDependencies:
+ "@types/trusted-types" "^2.0.7"
+
dompurify@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.3.1.tgz#c7e1ddebfe3301eacd6c0c12a4af284936dbbb86"
@@ -5100,12 +5102,12 @@ iterator.prototype@^1.1.5:
has-symbols "^1.1.0"
set-function-name "^2.0.2"
-javascript-time-ago@^2.5.11:
- version "2.5.12"
- resolved "https://registry.yarnpkg.com/javascript-time-ago/-/javascript-time-ago-2.5.12.tgz#b789f5c84b0518b38700722627c404a09299c5f9"
- integrity sha512-s8PPq2HQ3HIbSU0SjhNvTitf5VoXbQWof9q6k3gIX7F2il0ptjD5lONTDccpuKt/2U7RjbCp/TCHPK7eDwO7zQ==
+javascript-time-ago@^2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/javascript-time-ago/-/javascript-time-ago-2.6.2.tgz#b66ada9080440c472e53845d7912e9d2e7088045"
+ integrity sha512-gagMB4fetS1M1ZHaxQ9kX2amORXa5Los5PGh8NZzWSKrytz43KnpJlaPFTXbg/R7iPMN7U/6MoevgxqJ0QQ5lA==
dependencies:
- relative-time-format "^1.1.7"
+ relative-time-format "^1.1.11"
jay-peg@^1.1.1:
version "1.1.1"
@@ -5360,6 +5362,11 @@ markdown-table@^3.0.0:
resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.4.tgz#fe44d6d410ff9d6f2ea1797a3f60aa4d2b631c2a"
integrity sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==
+marked@14.0.0:
+ version "14.0.0"
+ resolved "https://registry.yarnpkg.com/marked/-/marked-14.0.0.tgz#79a1477358a59e0660276f8fec76de2c33f35d83"
+ integrity sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==
+
material-react-table@^3.0.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/material-react-table/-/material-react-table-3.2.1.tgz#56f595755cab3b669b399999fed9eb305fbb6dd7"
@@ -5902,12 +5909,13 @@ minimist@^1.2.0, minimist@^1.2.6, minimist@~1.2.5:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
-monaco-editor@^0.53.0:
- version "0.53.0"
- resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.53.0.tgz#2f485492e0ee822be13b1b45e3092922963737ae"
- integrity sha512-0WNThgC6CMWNXXBxTbaYYcunj08iB5rnx4/G56UOPeL9UVIUGGHA1GR0EWIh9Ebabj7NpCRawQ5b0hfN1jQmYQ==
+monaco-editor@^0.55.1:
+ version "0.55.1"
+ resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.55.1.tgz#e74c6fe5a6bf985b817d2de3eb88d56afc494a1b"
+ integrity sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==
dependencies:
- "@types/trusted-types" "^1.0.6"
+ dompurify "3.2.7"
+ marked "14.0.0"
ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
@@ -6918,7 +6926,7 @@ rehype-raw@^7.0.0:
hast-util-raw "^9.0.0"
vfile "^6.0.0"
-relative-time-format@^1.1.7:
+relative-time-format@^1.1.11:
version "1.1.11"
resolved "https://registry.yarnpkg.com/relative-time-format/-/relative-time-format-1.1.11.tgz#b193d5192434e7c1c6a53e362f811c68a4f18c45"
integrity sha512-TH+oV/w77hjaB9xCzoFYJ/Icmr/12+02IAoCI/YGS2UBTbjCbBjHGEBxGnVy4EJvOR1qadGzyFRI6hGaJJG93Q==