diff --git a/package.json b/package.json index f58b604c130a..c408d29c4b78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cipp", - "version": "8.5.1", + "version": "8.5.2", "author": "CIPP Contributors", "homepage": "https://cipp.app/", "bugs": { diff --git a/public/version.json b/public/version.json index f39cee3866d5..d0e25ec56792 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "8.5.1" -} + "version": "8.5.2" +} \ No newline at end of file diff --git a/src/components/CippComponents/CippAddConnectorDrawer.jsx b/src/components/CippComponents/CippAddConnectorDrawer.jsx new file mode 100644 index 000000000000..e08654239b14 --- /dev/null +++ b/src/components/CippComponents/CippAddConnectorDrawer.jsx @@ -0,0 +1,166 @@ +import { useState, useEffect } from "react"; +import { Button, Divider } from "@mui/material"; +import { Grid } from "@mui/system"; +import { useForm, useFormState, useWatch } from "react-hook-form"; +import { RocketLaunch } from "@mui/icons-material"; +import { CippOffCanvas } from "./CippOffCanvas"; +import CippFormComponent from "./CippFormComponent"; +import { CippFormTenantSelector } from "./CippFormTenantSelector"; +import { CippApiResults } from "./CippApiResults"; +import { ApiPostCall } from "../../api/ApiCall"; + +export const CippAddConnectorDrawer = ({ + buttonText = "Deploy Connector", + requiredPermissions = [], + PermissionButton = Button, +}) => { + const [drawerVisible, setDrawerVisible] = useState(false); + + const formControl = useForm({ + mode: "onChange", + defaultValues: { + selectedTenants: [], + TemplateList: null, + PowerShellCommand: "", + }, + }); + + const { isValid } = useFormState({ control: formControl.control }); + const templateListVal = useWatch({ control: formControl.control, name: "TemplateList" }); + + const addConnector = ApiPostCall({ + urlFromData: true, + }); + + // Update PowerShellCommand when template is selected + useEffect(() => { + if (templateListVal?.value) { + formControl.setValue("PowerShellCommand", JSON.stringify(templateListVal?.value)); + } + }, [templateListVal, formControl]); + + // Reset form fields on successful creation + useEffect(() => { + if (addConnector.isSuccess) { + const currentTenants = formControl.getValues("selectedTenants"); + formControl.reset({ + selectedTenants: currentTenants, + TemplateList: null, + PowerShellCommand: "", + }); + } + }, [addConnector.isSuccess, formControl]); + + const handleSubmit = () => { + formControl.trigger(); + // Check if the form is valid before proceeding + if (!isValid) { + return; + } + + const formData = formControl.getValues(); + + addConnector.mutate({ + url: "/api/AddExConnector", + data: formData, + }); + }; + + const handleCloseDrawer = () => { + setDrawerVisible(false); + formControl.reset({ + selectedTenants: [], + TemplateList: null, + PowerShellCommand: "", + }); + }; + + return ( + <> + setDrawerVisible(true)} + startIcon={} + > + {buttonText} + + + + + + } + > + + {/* Tenant Selector */} + + + + + + + {/* Template List */} + + option, + url: "/api/ListExconnectorTemplates", + }} + placeholder="Select a template or enter PowerShell JSON manually" + /> + + + + + {/* PowerShell Command */} + + + + + + + + + ); +}; diff --git a/src/components/CippComponents/CippAddTransportRuleDrawer.jsx b/src/components/CippComponents/CippAddTransportRuleDrawer.jsx new file mode 100644 index 000000000000..b9ee583100a8 --- /dev/null +++ b/src/components/CippComponents/CippAddTransportRuleDrawer.jsx @@ -0,0 +1,166 @@ +import { useState, useEffect } from "react"; +import { Button, Divider } from "@mui/material"; +import { Grid } from "@mui/system"; +import { useForm, useFormState, useWatch } from "react-hook-form"; +import { RocketLaunch } from "@mui/icons-material"; +import { CippOffCanvas } from "./CippOffCanvas"; +import CippFormComponent from "./CippFormComponent"; +import { CippFormTenantSelector } from "./CippFormTenantSelector"; +import { CippApiResults } from "./CippApiResults"; +import { ApiPostCall } from "../../api/ApiCall"; + +export const CippAddTransportRuleDrawer = ({ + buttonText = "Deploy Template", + requiredPermissions = [], + PermissionButton = Button, +}) => { + const [drawerVisible, setDrawerVisible] = useState(false); + + const formControl = useForm({ + mode: "onChange", + defaultValues: { + selectedTenants: [], + TemplateList: null, + PowerShellCommand: "", + }, + }); + + const { isValid } = useFormState({ control: formControl.control }); + const templateListVal = useWatch({ control: formControl.control, name: "TemplateList" }); + + const addTransportRule = ApiPostCall({ + urlFromData: true, + }); + + // Update PowerShellCommand when template is selected + useEffect(() => { + if (templateListVal?.value) { + formControl.setValue("PowerShellCommand", JSON.stringify(templateListVal?.value)); + } + }, [templateListVal, formControl]); + + // Reset form fields on successful creation + useEffect(() => { + if (addTransportRule.isSuccess) { + const currentTenants = formControl.getValues("selectedTenants"); + formControl.reset({ + selectedTenants: currentTenants, + TemplateList: null, + PowerShellCommand: "", + }); + } + }, [addTransportRule.isSuccess, formControl]); + + const handleSubmit = () => { + formControl.trigger(); + // Check if the form is valid before proceeding + if (!isValid) { + return; + } + + const formData = formControl.getValues(); + + addTransportRule.mutate({ + url: "/api/AddTransportRule", + data: formData, + }); + }; + + const handleCloseDrawer = () => { + setDrawerVisible(false); + formControl.reset({ + selectedTenants: [], + TemplateList: null, + PowerShellCommand: "", + }); + }; + + return ( + <> + setDrawerVisible(true)} + startIcon={} + > + {buttonText} + + + + + + } + > + + {/* Tenant Selector */} + + + + + + + {/* Template List */} + + option, + url: "/api/ListTransportRulesTemplates", + }} + placeholder="Select a template or enter PowerShell JSON manually" + /> + + + + + {/* PowerShell Command */} + + + + + + + + + ); +}; diff --git a/src/components/CippComponents/CippAliasDialog.jsx b/src/components/CippComponents/CippAliasDialog.jsx index 1046a5bb1cac..7f6696054843 100644 --- a/src/components/CippComponents/CippAliasDialog.jsx +++ b/src/components/CippComponents/CippAliasDialog.jsx @@ -1,10 +1,11 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { Typography, Box, Button, TextField, Chip, Stack } from "@mui/material"; import { Add } from "@mui/icons-material"; import { useWatch } from "react-hook-form"; +import { CippFormDomainSelector } from "./CippFormDomainSelector"; const CippAliasDialog = ({ formHook }) => { - const [newAlias, setNewAlias] = useState(""); + const [aliasPrefix, setAliasPrefix] = useState(""); // Initialize the form field if it doesn't exist useEffect(() => { @@ -21,15 +22,43 @@ const CippAliasDialog = ({ formHook }) => { defaultValue: [], }); + const selectedDomain = useWatch({ + control: formHook.control, + name: "AliasDomain", + }); + const isPending = formHook.formState.isSubmitting; + const selectedDomainValue = useMemo(() => { + if (!selectedDomain) return ""; + if (Array.isArray(selectedDomain)) { + return selectedDomain[0]?.value || selectedDomain[0] || ""; + } + if (typeof selectedDomain === "object") { + return selectedDomain?.value || ""; + } + return selectedDomain; + }, [selectedDomain]); + const handleAddAlias = () => { - if (newAlias.trim()) { - const currentAliases = formHook.getValues("AddedAliases") || []; - const newList = [...currentAliases, newAlias.trim()]; - formHook.setValue("AddedAliases", newList, { shouldValidate: true }); - setNewAlias(""); + const prefix = aliasPrefix.trim(); + const domain = selectedDomainValue; + + if (!prefix || !domain) { + return; + } + + const formattedAlias = `${prefix}@${domain}`; + const currentAliases = formHook.getValues("AddedAliases") || []; + + if (currentAliases.some((alias) => alias.toLowerCase() === formattedAlias.toLowerCase())) { + setAliasPrefix(""); + return; } + + const newList = [...currentAliases, formattedAlias]; + formHook.setValue("AddedAliases", newList, { shouldValidate: true }); + setAliasPrefix(""); }; const handleDeleteAlias = (aliasToDelete) => { @@ -49,16 +78,23 @@ const CippAliasDialog = ({ formHook }) => { <> - Add proxy addresses (aliases) for this user. Enter each alias and click Add or press - Enter. + Add proxy addresses (aliases) for this user. Enter a prefix, choose a verified tenant + domain, and click Add or press Enter. - + setNewAlias(e.target.value)} + value={aliasPrefix} + onChange={(e) => setAliasPrefix(e.target.value)} onKeyPress={handleKeyPress} - placeholder="Enter an alias" + placeholder="Enter alias prefix" variant="outlined" disabled={isPending} size="small" @@ -71,10 +107,20 @@ const CippAliasDialog = ({ formHook }) => { }, }} /> + + + - - } + cardButton={} /> ); }; diff --git a/src/pages/email/transport/list-connectors/add.jsx b/src/pages/email/transport/list-connectors/add.jsx deleted file mode 100644 index acd4f0c0a50f..000000000000 --- a/src/pages/email/transport/list-connectors/add.jsx +++ /dev/null @@ -1,89 +0,0 @@ -import React, { useEffect } from "react"; -import { Divider } from "@mui/material"; -import { Grid } from "@mui/system"; -import { useForm, useWatch } from "react-hook-form"; -import { Layout as DashboardLayout } from "/src/layouts/index.js"; -import CippFormPage from "/src/components/CippFormPages/CippFormPage"; -import CippFormComponent from "/src/components/CippComponents/CippFormComponent"; -import { CippFormTenantSelector } from "/src/components/CippComponents/CippFormTenantSelector"; - -const AddPolicy = () => { - const formControl = useForm({ - mode: "onChange", - defaultValues: { - selectedTenants: [], - TemplateList: null, - PowerShellCommand: "", - }, - }); - - const templateListVal = useWatch({ control: formControl.control, name: "TemplateList" }); - - useEffect(() => { - if (templateListVal?.value) { - formControl.setValue("PowerShellCommand", JSON.stringify(templateListVal?.value)); - } - }, [templateListVal, formControl]); - - return ( - - - - - - - - - {/* TemplateList */} - - option, - url: "/api/ListExconnectorTemplates", - }} - placeholder="Select a template or enter PowerShell JSON manually" - /> - - - - - - - - - - ); -}; - -AddPolicy.getLayout = (page) => {page}; - -export default AddPolicy; diff --git a/src/pages/email/transport/list-connectors/index.js b/src/pages/email/transport/list-connectors/index.js index fdb3ca7d92f4..4a3eeebcc84c 100644 --- a/src/pages/email/transport/list-connectors/index.js +++ b/src/pages/email/transport/list-connectors/index.js @@ -1,11 +1,11 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; -import { Button } from "@mui/material"; -import { RocketLaunch, Book, Check, Block, Delete } from "@mui/icons-material"; -import Link from "next/link"; +import { Book, Check, Block, Delete } from "@mui/icons-material"; +import { CippAddConnectorDrawer } from "../../../../components/CippComponents/CippAddConnectorDrawer"; const Page = () => { const pageTitle = "Connector List"; + const cardButtonPermissions = ["Exchange.Connector.ReadWrite"]; const actions = [ { @@ -72,6 +72,19 @@ const Page = () => { "TlsDomain", ]; + const filters = [ + { + filterName: "Inbound Connectors", + value: [{ id: "cippconnectortype", value: "Inbound" }], + type: "column", + }, + { + filterName: "Outbound Connectors", + value: [{ id: "cippconnectortype", value: "Outbound" }], + type: "column", + }, + ]; + const offCanvas = { extendedInfoFields: simpleColumns, actions: actions, @@ -83,18 +96,9 @@ const Page = () => { apiUrl="/api/ListExchangeConnectors" actions={actions} offCanvas={offCanvas} + filters={filters} simpleColumns={simpleColumns} - cardButton={ - <> - - - } + cardButton={} /> ); }; diff --git a/src/pages/email/transport/list-rules/add.jsx b/src/pages/email/transport/list-rules/add.jsx deleted file mode 100644 index e98931c56eda..000000000000 --- a/src/pages/email/transport/list-rules/add.jsx +++ /dev/null @@ -1,89 +0,0 @@ -import React, { useEffect } from "react"; -import { Divider } from "@mui/material"; -import { Grid } from "@mui/system"; -import { useForm, useWatch } from "react-hook-form"; -import { Layout as DashboardLayout } from "/src/layouts/index.js"; -import CippFormPage from "/src/components/CippFormPages/CippFormPage"; -import CippFormComponent from "/src/components/CippComponents/CippFormComponent"; -import { CippFormTenantSelector } from "/src/components/CippComponents/CippFormTenantSelector"; - -const AddPolicy = () => { - const formControl = useForm({ - mode: "onChange", - defaultValues: { - selectedTenants: [], - TemplateList: null, - PowerShellCommand: "", - }, - }); - - const templateListVal = useWatch({ control: formControl.control, name: "TemplateList" }); - - useEffect(() => { - if (templateListVal?.value) { - formControl.setValue("PowerShellCommand", JSON.stringify(templateListVal?.value)); - } - }, [templateListVal, formControl]); - - return ( - - - - - - - - - {/* TemplateList */} - - option, - url: "/api/ListTransportRulesTemplates", - }} - placeholder="Select a template or enter PowerShell JSON manually" - /> - - - - - - - - - - ); -}; - -AddPolicy.getLayout = (page) => {page}; - -export default AddPolicy; diff --git a/src/pages/email/transport/list-rules/index.js b/src/pages/email/transport/list-rules/index.js index db1fcf5a507e..07bca821c0ed 100644 --- a/src/pages/email/transport/list-rules/index.js +++ b/src/pages/email/transport/list-rules/index.js @@ -1,12 +1,15 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; -import { Button } from "@mui/material"; -import { Book, DoDisturb, Done, RocketLaunch } from "@mui/icons-material"; +import { Book, DoDisturb, Done } from "@mui/icons-material"; import { TrashIcon } from "@heroicons/react/24/outline"; +import { CippAddTransportRuleDrawer } from "../../../../components/CippComponents/CippAddTransportRuleDrawer"; +import { Button } from "@mui/material"; +import { RocketLaunch } from "@mui/icons-material"; import Link from "next/link"; const Page = () => { const pageTitle = "Transport Rules"; + const cardButtonPermissions = ["Exchange.TransportRule.ReadWrite"]; const actions = [ { @@ -75,6 +78,19 @@ const Page = () => { "Tenant", ]; + const filters = [ + { + filterName: "Enabled Rules", + value: [{ id: "State", value: "Enabled" }], + type: "column", + }, + { + filterName: "Disabled Rules", + value: [{ id: "State", value: "Disabled" }], + type: "column", + }, + ]; + return ( { actions={actions} offCanvas={offCanvas} simpleColumns={simpleColumns} - filters={[ - { - filterName: "Enabled Rules", - value: [{ id: "State", value: "Enabled" }], - type: "column", - }, - { - filterName: "Disabled Rules", - value: [{ id: "State", value: "Disabled" }], - type: "column", - }, - ]} + filters={filters} cardButton={ <> - + - - } + cardButton={ } /> ); }; diff --git a/src/pages/identity/administration/users/user/exchange.jsx b/src/pages/identity/administration/users/user/exchange.jsx index 51b166385ef2..b81765ed651d 100644 --- a/src/pages/identity/administration/users/user/exchange.jsx +++ b/src/pages/identity/administration/users/user/exchange.jsx @@ -890,7 +890,7 @@ const Page = () => { label: "Remove Permission", type: "POST", icon: , - url: "/api/ExecModifyCalPerms", + url: "/api/ExecModifyContactPerms", data: { userID: graphUserRequest.data?.[0]?.userPrincipalName, tenantFilter: userSettingsDefaults.currentTenant, diff --git a/src/pages/security/defender/deployment/index.js b/src/pages/security/defender/deployment/index.js index 3ddb85e5d4cb..a7eb6dbfa7db 100644 --- a/src/pages/security/defender/deployment/index.js +++ b/src/pages/security/defender/deployment/index.js @@ -90,8 +90,8 @@ const DeployDefenderForm = () => { /> @@ -120,12 +120,6 @@ const DeployDefenderForm = () => { name="Compliance.ConnectIos" formControl={formControl} /> - { /> + + {/* EDR Assignment Section */} + + + EDR Assignment + + + + diff --git a/src/pages/tenant/manage/configuration-backup.js b/src/pages/tenant/manage/configuration-backup.js index 0cf7dd569c72..8969f31ca4e4 100644 --- a/src/pages/tenant/manage/configuration-backup.js +++ b/src/pages/tenant/manage/configuration-backup.js @@ -182,7 +182,6 @@ const Page = () => { { tabOptions={tabOptions} title={title} subtitle={subtitle} - backUrl="/tenant/standards/list-standards" actions={actions} actionsData={{}} isFetching={driftApi.isFetching || standardsApi.isFetching || comparisonApi.isFetching} diff --git a/src/pages/tenant/manage/edit.js b/src/pages/tenant/manage/edit.js index a49d0afe00b8..8ab89052ca89 100644 --- a/src/pages/tenant/manage/edit.js +++ b/src/pages/tenant/manage/edit.js @@ -145,7 +145,6 @@ const Page = () => { { { tabOptions={tabOptions} title={title} subtitle={subtitle} - backUrl="/tenant/standards/list-standards" actions={actions} actionsData={{}} isFetching={recoverApi.isPending}