diff --git a/public/discord-mark-blue.svg b/public/discord-mark-blue.svg
new file mode 100644
index 000000000000..4cadbc7f7ed3
--- /dev/null
+++ b/public/discord-mark-blue.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/CippComponents/CippFormCondition.jsx b/src/components/CippComponents/CippFormCondition.jsx
index 1763091fbe27..7d3dfd029b68 100644
--- a/src/components/CippComponents/CippFormCondition.jsx
+++ b/src/components/CippComponents/CippFormCondition.jsx
@@ -51,7 +51,7 @@ export const CippFormCondition = (props) => {
if (watcher.includes(compareValue)) {
return children;
}
- } else if (typeof watcher === "object" && compareValue in watcher) {
+ } else if (typeof watcher === "object" && watcher !== null && compareValue in watcher) {
// Check if object contains the key
return children;
}
diff --git a/src/components/CippComponents/CippSpeedDial.jsx b/src/components/CippComponents/CippSpeedDial.jsx
new file mode 100644
index 000000000000..4313ace7a7ef
--- /dev/null
+++ b/src/components/CippComponents/CippSpeedDial.jsx
@@ -0,0 +1,222 @@
+import React, { useState, useEffect } from "react";
+import {
+ SpeedDial,
+ SpeedDialAction,
+ SpeedDialIcon,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ Snackbar,
+ Alert,
+ CircularProgress,
+} from "@mui/material";
+import { Close as CloseIcon } from "@mui/icons-material";
+import { useForm } from "react-hook-form";
+import { CippFormComponent } from "../../components/CippComponents/CippFormComponent";
+
+const CippSpeedDial = ({
+ actions = [],
+ position = { bottom: 16, right: 16 },
+ icon,
+ openIcon = ,
+}) => {
+ const [openDialogs, setOpenDialogs] = useState({});
+ const [loading, setLoading] = useState(false);
+ const [showSnackbar, setShowSnackbar] = useState(false);
+ const [speedDialOpen, setSpeedDialOpen] = useState(false);
+ const [isHovering, setIsHovering] = useState(false);
+ const [snackbarMessage, setSnackbarMessage] = useState("");
+
+ const formControls = actions.reduce((acc, action) => {
+ if (action.form) {
+ acc[action.id] = useForm({
+ mode: "onChange",
+ defaultValues: action.form.defaultValues || {},
+ });
+ }
+ return acc;
+ }, {});
+
+ const handleSpeedDialClose = () => {
+ if (!isHovering) {
+ setTimeout(() => {
+ setSpeedDialOpen(false);
+ }, 200);
+ }
+ };
+
+ const handleMouseEnter = () => {
+ setIsHovering(true);
+ setSpeedDialOpen(true);
+ };
+
+ const handleMouseLeave = () => {
+ setIsHovering(false);
+ handleSpeedDialClose();
+ };
+
+ const handleDialogOpen = (actionId) => {
+ setOpenDialogs((prev) => ({ ...prev, [actionId]: true }));
+ };
+
+ const handleDialogClose = (actionId) => {
+ setOpenDialogs((prev) => ({ ...prev, [actionId]: false }));
+ };
+
+ const handleSubmit = async (actionId, data) => {
+ if (!actions.find((a) => a.id === actionId)?.onSubmit) return;
+
+ setLoading(true);
+ try {
+ const action = actions.find((a) => a.id === actionId);
+ const result = await action.onSubmit(data);
+
+ if (result.success) {
+ formControls[actionId]?.reset();
+ handleDialogClose(actionId);
+ }
+ setSnackbarMessage(result.message);
+ setShowSnackbar(true);
+ } catch (error) {
+ console.error(`Error submitting ${actionId}:`, error);
+ setSnackbarMessage("An error occurred while submitting");
+ setShowSnackbar(true);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ const handleClickOutside = (event) => {
+ if (speedDialOpen) {
+ const speedDial = document.querySelector('[aria-label="Navigation SpeedDial"]');
+ if (speedDial && !speedDial.contains(event.target)) {
+ setSpeedDialOpen(false);
+ }
+ }
+ };
+
+ document.addEventListener("click", handleClickOutside);
+ return () => {
+ document.removeEventListener("click", handleClickOutside);
+ };
+ }, [speedDialOpen]);
+
+ return (
+ <>
+ }
+ open={speedDialOpen}
+ onClose={handleSpeedDialClose}
+ onOpen={() => setSpeedDialOpen(true)}
+ onMouseEnter={handleMouseEnter}
+ onMouseLeave={handleMouseLeave}
+ >
+ {actions.map((action) => (
+ {
+ if (action.form) {
+ handleDialogOpen(action.id);
+ } else if (action.onClick) {
+ action.onClick();
+ }
+ setSpeedDialOpen(false);
+ }}
+ tooltipOpen
+ sx={{
+ "&.MuiSpeedDialAction-fab": {
+ backgroundColor: "background.paper",
+ "&:hover": {
+ backgroundColor: "action.hover",
+ },
+ },
+ "& .MuiSpeedDialAction-staticTooltipLabel": {
+ cursor: "pointer",
+ whiteSpace: "nowrap",
+ marginRight: "10px",
+ padding: "6px 10px",
+ "&:hover": {
+ backgroundColor: "action.hover",
+ },
+ },
+ }}
+ />
+ ))}
+
+
+ {actions
+ .filter((action) => action.form)
+ .map((action) => (
+
+ ))}
+
+ setShowSnackbar(false)}
+ anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
+ >
+ setShowSnackbar(false)} severity="success" sx={{ width: "100%" }}>
+ {snackbarMessage}
+
+
+ >
+ );
+};
+
+export default CippSpeedDial;
diff --git a/src/components/CippComponents/CippUserActions.jsx b/src/components/CippComponents/CippUserActions.jsx
index f88d909281e8..cf30780eb5e8 100644
--- a/src/components/CippComponents/CippUserActions.jsx
+++ b/src/components/CippComponents/CippUserActions.jsx
@@ -292,7 +292,7 @@ export const CippUserActions = () => {
},
confirmText: "Are you sure you want to clear the Immutable ID for this user?",
multiPost: false,
- condition: (row) => row.onPremisesSyncEnabled,
+ condition: (row) => !row.onPremisesSyncEnabled && row?.onPremisesImmutableId,
},
{
label: "Revoke all user sessions",
diff --git a/src/pages/_app.js b/src/pages/_app.js
index 702eb328927c..e520d50c5822 100644
--- a/src/pages/_app.js
+++ b/src/pages/_app.js
@@ -20,6 +20,14 @@ import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
import TimeAgo from "javascript-time-ago";
import en from "javascript-time-ago/locale/en.json";
+import CippSpeedDial from "../components/CippComponents/CippSpeedDial";
+import {
+ Help as HelpIcon,
+ BugReport as BugReportIcon,
+ Feedback as FeedbackIcon,
+} from "@mui/icons-material";
+import { SvgIcon } from "@mui/material";
+import discordIcon from "../../public/discord-mark-blue.svg";
import React from "react";
TimeAgo.addDefaultLocale(en);
@@ -36,6 +44,33 @@ const App = (props) => {
const getLayout = Component.getLayout ?? ((page) => page);
const preferredTheme = useMediaPredicate("(prefers-color-scheme: dark)") ? "dark" : "light";
+ const speedDialActions = [
+ {
+ id: "bug-report",
+ icon: ,
+ name: "Report Bug",
+ href: "https://github.com/KelvinTegelaar/CIPP/issues/new?template=bug.yml",
+ onClick: () => window.open("https://github.com/KelvinTegelaar/CIPP/issues/new?template=bug.yml", "_blank")
+ },
+ {
+ id: "feature-request",
+ icon: ,
+ name: "Request Feature",
+ href: "https://github.com/KelvinTegelaar/CIPP/issues/new?template=feature.yml",
+ onClick: () => window.open("https://github.com/KelvinTegelaar/CIPP/issues/new?template=feature.yml", "_blank")
+ },
+ {
+ id: "discord",
+ icon: (
+
+
+ ),
+ name: "Join the Discord!",
+ href: "https://discord.gg/cyberdrain",
+ onClick: () => window.open("https://discord.gg/cyberdrain", "_blank")
+ },
+ ];
+
return (
@@ -69,6 +104,11 @@ const App = (props) => {
{getLayout()}
+ }
+ position={{ bottom: 16, right: 16 }}
+ />
{settings?.showDevtools && (
diff --git a/src/pages/email/transport/list-connectors/add.jsx b/src/pages/email/transport/list-connectors/add.jsx
index 5410d79c653d..8d173c49908b 100644
--- a/src/pages/email/transport/list-connectors/add.jsx
+++ b/src/pages/email/transport/list-connectors/add.jsx
@@ -27,9 +27,9 @@ const AddPolicy = () => {
return (
@@ -55,7 +55,7 @@ const AddPolicy = () => {
formControl={formControl}
multiple={false}
api={{
- queryKey: `TemplateListTransport`,
+ queryKey: `TemplateListConnectors`,
labelField: "name",
valueField: (option) => option,
url: "/api/ListExconnectorTemplates",
diff --git a/src/pages/email/transport/list-connectors/index.js b/src/pages/email/transport/list-connectors/index.js
index 477b191fdff5..6f2f9fe11539 100644
--- a/src/pages/email/transport/list-connectors/index.js
+++ b/src/pages/email/transport/list-connectors/index.js
@@ -22,8 +22,9 @@ const Page = () => {
type: "POST",
url: "/api/EditExConnector",
icon: ,
+ condition: (row) => !row.Enabled,
data: {
- State: "Enable",
+ State: "!Enable",
GUID: "Guid",
Type: "cippconnectortype",
},
@@ -35,8 +36,9 @@ const Page = () => {
type: "POST",
url: "/api/EditExConnector",
icon: ,
+ condition: (row) => row.Enabled,
data: {
- State: "Disable",
+ State: "!Disable",
GUID: "Guid",
Type: "cippconnectortype",
},
@@ -82,11 +84,6 @@ const Page = () => {
actions={actions}
offCanvas={offCanvas}
simpleColumns={simpleColumns}
- titleButton={{
- label: "Deploy Connector",
- href: "/email/connectors/deploy-connector",
- startIcon: , // Added icon
- }}
cardButton={
<>
);