Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions src/components/CippComponents/CippReportDBControls.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { useState, useEffect, useMemo, useCallback } from "react";
import { Button, Chip, SvgIcon, Tooltip } from "@mui/material";
import { Stack } from "@mui/system";
import { Sync, CloudDone, Bolt } from "@mui/icons-material";
import { useSettings } from "../../hooks/use-settings";
import { useDialog } from "../../hooks/use-dialog";
import { CippApiDialog } from "./CippApiDialog";
import { CippQueueTracker } from "../CippTable/CippQueueTracker";

/**
* Hook + UI component that encapsulates all CIPP Reporting DB cache/live mode logic.
*
* @param {Object} config
* @param {string} config.apiUrl - Base API URL without query params (e.g. "/api/ListMailboxes")
* @param {string} config.queryKey - Base query key (e.g. "ListMailboxes")
* @param {string} config.cacheName - Cache type name for sync (e.g. "Mailboxes", "IntunePolicies")
* @param {string} config.syncTitle - Title for the sync dialog (e.g. "Sync Mailboxes")
* @param {string} [config.syncConfirmText] - Custom confirm text. Default auto-generated from cacheName + tenant.
* @param {Object} [config.syncData] - Extra data to pass to ExecCIPPDBCache. Merged with { Name: cacheName }.
* @param {boolean} [config.allowToggle=true] - Whether the user can toggle between cached and live. False = always cached.
* @param {boolean} [config.defaultCached=true] - Initial cached state (when toggle is allowed).
* @param {string[]} [config.cacheColumns=["CacheTimestamp"]] - Extra columns to show when in cached mode.
* @param {string} [config.tenantColumn="Tenant"] - Column name for tenant (shown in AllTenants mode).
* @param {Object} [config.apiData] - Additional static API data to merge (e.g. extra params).
*
* @returns {Object}
* - useReportDB {boolean} - Current cache mode
* - setUseReportDB {Function} - Manual override (rarely needed)
* - isAllTenants {boolean} - Whether AllTenants is selected
* - resolvedApiUrl {string} - API URL with ?UseReportDB=true appended when needed
* - resolvedApiData {Object|undefined} - Merged apiData (for pages that use apiData instead of URL params)
* - resolvedQueryKey {string} - Query key including tenant and cache mode
* - cacheColumns {string[]} - Columns to prepend/append when cached (includes Tenant for AllTenants)
* - controls {JSX.Element} - Ready-to-render JSX for the cache toggle, sync button, and queue tracker
* - syncDialog {JSX.Element} - The CippApiDialog element to render alongside CippTablePage
*/
export function useCippReportDB(config) {
const {
apiUrl,
queryKey,
cacheName,
syncTitle,
syncConfirmText,
syncData,
allowToggle = true,
defaultCached = true,
cacheColumns = ["CacheTimestamp"],
tenantColumn = "Tenant",
apiData: extraApiData,
} = config;

const currentTenant = useSettings().currentTenant;
const isAllTenants = currentTenant === "AllTenants";
const dialog = useDialog();
const [syncQueueId, setSyncQueueId] = useState(null);
const [useReportDB, setUseReportDB] = useState(defaultCached);

// Reset to default whenever tenant changes; AllTenants always forces cached
useEffect(() => {
if (isAllTenants) {
setUseReportDB(true);
} else {
setUseReportDB(defaultCached);
}
}, [currentTenant, isAllTenants, defaultCached]);

// Whether the toggle is actually clickable
const canToggle = allowToggle && !isAllTenants;

// Resolved API URL — append UseReportDB param when cached
const resolvedApiUrl = useMemo(() => {
if (!useReportDB) return apiUrl;
const sep = apiUrl.includes("?") ? "&" : "?";
return `${apiUrl}${sep}UseReportDB=true`;
}, [apiUrl, useReportDB]);

// Alternative: for pages that pass apiData prop instead of URL params
const resolvedApiData = useMemo(() => {
if (!useReportDB && !extraApiData) return undefined;
return {
...(extraApiData || {}),
...(useReportDB ? { UseReportDB: true } : {}),
};
}, [useReportDB, extraApiData]);

// Query key that includes tenant + mode for proper cache separation
const resolvedQueryKey = useMemo(() => {
return `${queryKey}-${currentTenant}-${useReportDB}`;
}, [queryKey, currentTenant, useReportDB]);

// Extra columns to show when in cached mode
const extraColumns = useMemo(() => {
const cols = [];
if (useReportDB && isAllTenants) {
cols.push(tenantColumn);
}
if (useReportDB) {
cols.push(...cacheColumns);
}
return cols;
}, [useReportDB, isAllTenants, tenantColumn, cacheColumns]);

const handleSyncSuccess = useCallback((result) => {
if (result?.Metadata?.QueueId) {
setSyncQueueId(result.Metadata.QueueId);
}
}, []);

// Tooltip text
const tooltipText = !allowToggle
? "This page always uses cached data from the CIPP reporting database."
: isAllTenants
? "AllTenants always uses cached data"
: useReportDB
? "Showing cached data — click to switch to live"
: "Showing live data — click to switch to cache";

const confirmText =
syncConfirmText ||
`Run ${cacheName} cache sync for ${currentTenant}? This will update data immediately.`;

// The controls JSX
const controls = (
<Stack direction="row" spacing={1} alignItems="center">
{useReportDB && (
<>
<CippQueueTracker
queueId={syncQueueId}
queryKey={`${queryKey}-${currentTenant}`}
title={syncTitle}
/>
<Button
startIcon={
<SvgIcon fontSize="small">
<Sync />
</SvgIcon>
}
size="xs"
onClick={dialog.handleOpen}
disabled={isAllTenants}
>
Sync
</Button>
</>
)}
<Tooltip title={tooltipText}>
<span>
<Chip
icon={useReportDB ? <CloudDone /> : <Bolt />}
label={useReportDB ? "Cached" : "Live"}
color="primary"
size="small"
onClick={canToggle ? () => setUseReportDB((prev) => !prev) : undefined}
clickable={canToggle}
disabled={!canToggle}
variant="outlined"
/>
</span>
</Tooltip>
</Stack>
);

// The sync dialog JSX — render alongside the table page
const syncDialogElement = (
<CippApiDialog
createDialog={dialog}
title={syncTitle}
fields={[]}
api={{
type: "GET",
url: "/api/ExecCIPPDBCache",
confirmText,
relatedQueryKeys: [`${queryKey}-${currentTenant}-true`],
data: {
Name: cacheName,
Types: "None",
...(syncData || {}),
},
onSuccess: handleSyncSuccess,
}}
/>
);

return {
useReportDB,
setUseReportDB,
isAllTenants,
resolvedApiUrl,
resolvedApiData,
resolvedQueryKey,
cacheColumns: extraColumns,
controls,
syncDialog: syncDialogElement,
};
}
94 changes: 25 additions & 69 deletions src/pages/email/administration/mailbox-rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,31 @@ import { CippTablePage } from "../../../../components/CippComponents/CippTablePa
import { getCippTranslation } from "../../../../utils/get-cipp-translation";
import { getCippFormatting } from "../../../../utils/get-cipp-formatting";
import { CippPropertyListCard } from "../../../../components/CippCards/CippPropertyListCard";
import { Block, PlayArrow, DeleteForever, Sync, Info } from "@mui/icons-material";
import { Button, SvgIcon, IconButton, Tooltip } from "@mui/material";
import { Stack } from "@mui/system";
import { useDialog } from "../../../../hooks/use-dialog";
import { CippApiDialog } from "../../../../components/CippComponents/CippApiDialog";
import { useSettings } from "../../../../hooks/use-settings";
import { CippQueueTracker } from "../../../../components/CippTable/CippQueueTracker";
import { useState } from "react";
import { Block, PlayArrow, DeleteForever } from "@mui/icons-material";
import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls";

const Page = () => {
const pageTitle = "Mailbox Rules";
const currentTenant = useSettings().currentTenant;
const syncDialog = useDialog();
const [syncQueueId, setSyncQueueId] = useState(null);

const isAllTenants = currentTenant === "AllTenants";
const reportDB = useCippReportDB({
apiUrl: "/api/ListMailboxRules",
queryKey: "ListMailboxRules",
cacheName: "Mailboxes",
syncTitle: "Sync Mailbox Rules",
syncData: { Types: "Rules" },
allowToggle: false,
defaultCached: true,
});

const apiData = {
UseReportDB: true,
};

const simpleColumns = isAllTenants
? ["Tenant", "UserPrincipalName", "Name", "Priority", "Enabled", "From", "CacheTimestamp"]
: ["UserPrincipalName", "Name", "Priority", "Enabled", "From", "CacheTimestamp"];
const simpleColumns = [
...reportDB.cacheColumns.filter((c) => c === "Tenant"),
"UserPrincipalName",
"Name",
"Priority",
"Enabled",
"From",
...reportDB.cacheColumns.filter((c) => c !== "Tenant"),
];

const actions = [
{
Expand Down Expand Up @@ -96,64 +97,19 @@ const Page = () => {
},
};

const pageActions = [
<Stack key="actions-stack" direction="row" spacing={1} alignItems="center">
<CippQueueTracker
queueId={syncQueueId}
queryKey={`ListMailboxRules-${currentTenant}`}
title="Mailbox Rules Sync"
/>
<Tooltip title="This report displays cached data from the CIPP reporting database. Click the Sync button to update the cache for the current tenant.">
<IconButton size="small">
<Info fontSize="small" />
</IconButton>
</Tooltip>
<Button
startIcon={
<SvgIcon fontSize="small">
<Sync />
</SvgIcon>
}
size="xs"
onClick={syncDialog.handleOpen}
>
Sync
</Button>
</Stack>,
];

return (
<>
<CippTablePage
title={pageTitle}
apiUrl="/api/ListMailboxRules"
apiData={apiData}
queryKey={`ListMailboxRules-${currentTenant}`}
apiUrl={reportDB.resolvedApiUrl}
apiData={reportDB.resolvedApiData}
queryKey={reportDB.resolvedQueryKey}
simpleColumns={simpleColumns}
offCanvas={offCanvas}
actions={actions}
cardButton={pageActions}
/>
<CippApiDialog
createDialog={syncDialog}
title="Sync Mailbox Rules"
fields={[]}
api={{
type: "GET",
onSuccess: (result) => {
if (result?.Metadata?.QueueId) {
setSyncQueueId(result.Metadata.QueueId);
}
},
url: "/api/ExecCIPPDBCache",
confirmText: `Run mailbox rules cache sync for ${currentTenant}? This will update mailbox rules data immediately.`,
relatedQueryKeys: [`ListMailboxRules-${currentTenant}`],
data: {
Name: "Mailboxes",
Types: "Rules",
},
}}
cardButton={reportDB.controls}
/>
{reportDB.syncDialog}
</>
);
};
Expand Down
Loading