diff --git a/package.json b/package.json index 0560615cafee..b6cd7f8010a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cipp", - "version": "10.4.1", + "version": "10.4.2", "author": "CIPP Contributors", "homepage": "https://cipp.app/", "bugs": { diff --git a/public/version.json b/public/version.json index 4d2e87f1cdee..fdddd5f6239a 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "10.4.1" + "version": "10.4.2" } diff --git a/src/components/CippComponents/CippReportDBControls.jsx b/src/components/CippComponents/CippReportDBControls.jsx new file mode 100644 index 000000000000..75d9c70e32c8 --- /dev/null +++ b/src/components/CippComponents/CippReportDBControls.jsx @@ -0,0 +1,193 @@ +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]) + + // Keep mode flag in the URL only; CippTablePage merges apiData into query params. + const resolvedApiData = useMemo(() => { + if (!extraApiData) return undefined + return { + ...extraApiData, + } + }, [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 = ( + + {useReportDB && ( + <> + + + + )} + + + : } + label={useReportDB ? 'Cached' : 'Live'} + color="primary" + size="small" + onClick={canToggle ? () => setUseReportDB((prev) => !prev) : undefined} + clickable={canToggle} + disabled={!canToggle} + variant="outlined" + /> + + + + ) + + // The sync dialog JSX — render alongside the table page + const syncDialogElement = ( + + ) + + return { + useReportDB, + setUseReportDB, + isAllTenants, + resolvedApiUrl, + resolvedApiData, + resolvedQueryKey, + cacheColumns: extraColumns, + controls, + syncDialog: syncDialogElement, + } +} diff --git a/src/components/CippFormPages/CippAddEditUser.jsx b/src/components/CippFormPages/CippAddEditUser.jsx index ac2b9a0262c8..d33a9705dfdb 100644 --- a/src/components/CippFormPages/CippAddEditUser.jsx +++ b/src/components/CippFormPages/CippAddEditUser.jsx @@ -851,6 +851,17 @@ const CippAddEditUser = (props) => { }))} creatable={false} formControl={formControl} + customAction={{ + icon: , + tooltip: 'Refresh groups', + onClick: () => { + tenantGroups.refetch() + if (formType === 'edit') { + userGroups.refetch() + } + }, + position: 'outside', + }} /> )} diff --git a/src/data/CIPPDBCacheTypes.json b/src/data/CIPPDBCacheTypes.json index 8ff123ff4aff..8742001441cd 100644 --- a/src/data/CIPPDBCacheTypes.json +++ b/src/data/CIPPDBCacheTypes.json @@ -254,11 +254,26 @@ "friendlyName": "Mailbox Usage", "description": "Exchange Online mailbox usage statistics" }, + { + "type": "OneDriveSiteListing", + "friendlyName": "OneDrive Site Listing", + "description": "OneDrive personal site listing details used for usage reporting" + }, { "type": "OneDriveUsage", "friendlyName": "OneDrive Usage", "description": "OneDrive usage statistics" }, + { + "type": "SharePointSiteListing", + "friendlyName": "SharePoint Site Listing", + "description": "SharePoint site listing details used for usage reporting" + }, + { + "type": "SharePointSiteUsage", + "friendlyName": "SharePoint Site Usage", + "description": "SharePoint site usage statistics" + }, { "type": "ConditionalAccessPolicies", "friendlyName": "Conditional Access Policies", diff --git a/src/data/cipp-roles.json b/src/data/cipp-roles.json index f95e32fa18c6..ac3c389f65a2 100644 --- a/src/data/cipp-roles.json +++ b/src/data/cipp-roles.json @@ -1,10 +1,19 @@ { "readonly": { - "include": ["*.Read"], - "exclude": ["CIPP.SuperAdmin.*"] + "include": [ + "*.Read" + ], + "exclude": [ + "CIPP.SuperAdmin.*", + "CIPP.Admin.*", + "CIPP.AppSettings.*" + ] }, "editor": { - "include": ["*.Read", "*.ReadWrite"], + "include": [ + "*.Read", + "*.ReadWrite" + ], "exclude": [ "CIPP.SuperAdmin.*", "CIPP.Admin.*", @@ -13,11 +22,17 @@ ] }, "admin": { - "include": ["*"], - "exclude": ["CIPP.SuperAdmin.*"] + "include": [ + "*" + ], + "exclude": [ + "CIPP.SuperAdmin.*" + ] }, "superadmin": { - "include": ["*"], + "include": [ + "*" + ], "exclude": [] } -} +} \ No newline at end of file diff --git a/src/data/standards.json b/src/data/standards.json index 07bff3d77f06..44a0c52d15e8 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -6304,5 +6304,73 @@ "EXCHANGE_S_ENTERPRISE_GOV", "EXCHANGE_LITE" ] + }, + { + "name": "standards.SPDisableCustomScripts", + "cat": "SharePoint Standards", + "tag": [], + "helpText": "Prevents users from running custom scripts on SharePoint and OneDrive sites. Custom scripts can modify site behaviors and bypass governance controls.", + "docsDescription": "Disables the ability to add and run custom scripts on SharePoint and OneDrive sites at the tenant level. When custom scripts are allowed, governance cannot be enforced, and the capabilities of inserted code cannot be scoped or blocked. Microsoft recommends using the SharePoint Framework instead of custom scripts.", + "executiveText": "Blocks custom scripts from being added to SharePoint and OneDrive sites, enforcing governance controls and preventing unscoped code execution. This aligns with Microsoft's Baseline Security Mode recommendation to permanently remove the ability to add new custom scripts, directing organizations to use the SharePoint Framework instead.", + "addedComponent": [], + "label": "Disable custom scripts on SharePoint sites", + "impact": "High Impact", + "impactColour": "danger", + "addedDate": "2026-04-28", + "powershellEquivalent": "Set-SPOTenant -CustomScriptsRestrictMode $true", + "recommendedBy": ["CIPP"], + "requiredCapabilities": [ + "SHAREPOINTWAC", + "SHAREPOINTSTANDARD", + "SHAREPOINTENTERPRISE", + "SHAREPOINTENTERPRISE_EDU", + "ONEDRIVE_BASIC", + "ONEDRIVE_ENTERPRISE" + ] + }, + { + "name": "standards.SPDisableStoreAccess", + "cat": "SharePoint Standards", + "tag": [], + "helpText": "Disables end users from installing applications from the Microsoft Store into SharePoint sites.", + "docsDescription": "Removes the ability for end users to install applications directly from the Microsoft Store into SharePoint. This prevents uncontrolled app installations that can increase governance costs and go against organizational policies.", + "executiveText": "Prevents end users from installing applications from the Microsoft Store into SharePoint sites, ensuring that only approved applications are available. This reduces governance overhead and aligns with Microsoft's Baseline Security Mode recommendations.", + "addedComponent": [], + "label": "Disable SharePoint Store access", + "impact": "Low Impact", + "impactColour": "info", + "addedDate": "2026-04-28", + "powershellEquivalent": "Set-SPOTenant -DisableSharePointStoreAccess $true", + "recommendedBy": ["CIPP"], + "requiredCapabilities": [ + "SHAREPOINTWAC", + "SHAREPOINTSTANDARD", + "SHAREPOINTENTERPRISE", + "SHAREPOINTENTERPRISE_EDU", + "ONEDRIVE_BASIC", + "ONEDRIVE_ENTERPRISE" + ] + }, + { + "name": "standards.DisableEWS", + "cat": "Exchange Standards", + "tag": [], + "helpText": "Disables Exchange Web Services (EWS) organization-wide. This reduces the attack surface by blocking legacy API access to mailbox data. Warning: This may break Office web add-ins on builds older than 16.0.19127.", + "docsDescription": "Disables Exchange Web Services (EWS) at the organization level to reduce attack surface. EWS provides cross-platform API access to sensitive Exchange Online data such as emails, meetings, and contacts. If compromised, attackers can access confidential data, send phishing emails, or spoof identities. Disabling EWS also reduces legacy app usage and minimizes exploitable endpoints. Note that this may break first-party features including web add-ins for Word, Excel, PowerPoint, and Outlook on builds older than 16.0.19127.", + "executiveText": "Disables Exchange Web Services (EWS) across the organization to reduce attack surface and prevent legacy API access to sensitive mailbox data. This aligns with Microsoft's Baseline Security Mode recommendation to minimize exploitable endpoints while requiring updates to applications that depend on EWS.", + "addedComponent": [], + "label": "Disable Exchange Web Services", + "impact": "High Impact", + "impactColour": "danger", + "addedDate": "2026-04-28", + "powershellEquivalent": "Set-OrganizationConfig -EwsEnabled $false", + "recommendedBy": ["CIPP"], + "requiredCapabilities": [ + "EXCHANGE_S_STANDARD", + "EXCHANGE_S_ENTERPRISE", + "EXCHANGE_S_STANDARD_GOV", + "EXCHANGE_S_ENTERPRISE_GOV", + "EXCHANGE_LITE" + ] } ] diff --git a/src/hooks/use-user-bookmarks.js b/src/hooks/use-user-bookmarks.js index 0e6b1512bb35..7427ea5c06f0 100644 --- a/src/hooks/use-user-bookmarks.js +++ b/src/hooks/use-user-bookmarks.js @@ -4,18 +4,40 @@ import { ApiGetCall, ApiPostCall } from "../api/ApiCall"; const SETTINGS_STORAGE_KEY = "app.settings"; +const sanitizeBookmark = (bookmark) => { + if (!bookmark || typeof bookmark !== "object") { + return null; + } + + if (typeof bookmark.path !== "string") { + return null; + } + + const path = bookmark.path.trim(); + if (!path) { + return null; + } + + const label = + typeof bookmark.label === "string" && bookmark.label.trim() + ? bookmark.label.trim() + : path; + + return { + ...bookmark, + path, + label, + }; +}; + const normalizeBookmarks = (value) => { if (Array.isArray(value)) { - return value; + return value.map(sanitizeBookmark).filter(Boolean); } - if ( - value && - typeof value === "object" && - typeof value.path === "string" && - typeof value.label === "string" - ) { - return [value]; + const singleBookmark = sanitizeBookmark(value); + if (singleBookmark) { + return [singleBookmark]; } return []; @@ -103,7 +125,7 @@ export const useUserBookmarks = () => { const persistBookmarks = useCallback( (nextBookmarks, callbacks = {}) => { - const safeBookmarks = Array.isArray(nextBookmarks) ? nextBookmarks : []; + const safeBookmarks = normalizeBookmarks(nextBookmarks); queryClient.setQueryData(["userSettings"], (previous) => ({ ...(previous || {}), diff --git a/src/pages/cipp/logs/index.js b/src/pages/cipp/logs/index.js index 1cbd2fa8e02d..1896cf3e2349 100644 --- a/src/pages/cipp/logs/index.js +++ b/src/pages/cipp/logs/index.js @@ -1,6 +1,6 @@ -import { useState } from "react"; -import { Layout as DashboardLayout } from "../../../layouts/index.js"; -import { CippTablePage } from "../../../components/CippComponents/CippTablePage.jsx"; +import { useState } from 'react' +import { Layout as DashboardLayout } from '../../../layouts/index.js' +import { CippTablePage } from '../../../components/CippComponents/CippTablePage.jsx' import { Button, Accordion, @@ -11,73 +11,73 @@ import { Stack, Alert, Box, -} from "@mui/material"; -import { Grid } from "@mui/system"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import { useForm } from "react-hook-form"; -import CippFormComponent from "../../../components/CippComponents/CippFormComponent"; -import { FunnelIcon, XMarkIcon } from "@heroicons/react/24/outline"; -import { EyeIcon } from "@heroicons/react/24/outline"; -import { useSettings } from "../../../hooks/use-settings.js"; +} from '@mui/material' +import { Grid } from '@mui/system' +import ExpandMoreIcon from '@mui/icons-material/ExpandMore' +import { useForm } from 'react-hook-form' +import CippFormComponent from '../../../components/CippComponents/CippFormComponent' +import { FunnelIcon, XMarkIcon } from '@heroicons/react/24/outline' +import { EyeIcon } from '@heroicons/react/24/outline' +import { useSettings } from '../../../hooks/use-settings.js' const simpleColumns = [ - "DateTime", - "Tenant", - "User", - "Message", - "API", - "Severity", - "AppId", - "IP", - "LogData", -]; + 'DateTime', + 'Tenant', + 'User', + 'Message', + 'API', + 'Severity', + 'AppId', + 'IP', + 'LogData', +] const offcanvas = { - extendedInfoFields: ["DateTime", "API", "Severity", "Message", "User", "AppId", "IP", "LogData"], -}; + extendedInfoFields: ['DateTime', 'API', 'Severity', 'Message', 'User', 'AppId', 'IP', 'LogData'], +} -const apiUrl = "/api/Listlogs"; -const pageTitle = "Logbook Results"; +const apiUrl = '/api/Listlogs' +const pageTitle = 'Logbook Results' const actions = [ { - label: "View Log Entry", - link: "/cipp/logs/logentry?logentry=[RowKey]&dateFilter=[DateFilter]", + label: 'View Log Entry', + link: '/cipp/logs/logentry?logentry=[RowKey]&dateFilter=[DateFilter]', icon: , - color: "primary", + color: 'primary', }, -]; +] const Page = () => { const formControl = useForm({ defaultValues: { startDate: null, endDate: null, - username: "", + username: '', severity: [], }, - }); + }) - const [expanded, setExpanded] = useState(false); // State for Accordion - const [filterEnabled, setFilterEnabled] = useState(false); // State for filter toggle - const [startDate, setStartDate] = useState(null); // State for start date filter - const [endDate, setEndDate] = useState(null); // State for end date filter - const [username, setUsername] = useState(null); // State for username filter - const [severity, setSeverity] = useState(null); // State for severity filter - const settings = useSettings(); // Hook to access settings - const currentTenant = settings?.currentTenant; + const [expanded, setExpanded] = useState(false) // State for Accordion + const [filterEnabled, setFilterEnabled] = useState(false) // State for filter toggle + const [startDate, setStartDate] = useState(null) // State for start date filter + const [endDate, setEndDate] = useState(null) // State for end date filter + const [username, setUsername] = useState(null) // State for username filter + const [severity, setSeverity] = useState(null) // State for severity filter + const settings = useSettings() // Hook to access settings + const currentTenant = settings?.currentTenant // Watch date fields to show warning for large date ranges - const watchStartDate = formControl.watch("startDate"); - const watchEndDate = formControl.watch("endDate"); + const watchStartDate = formControl.watch('startDate') + const watchEndDate = formControl.watch('endDate') // Component to display warning for large date ranges const DateRangeWarning = () => { - if (!watchStartDate || !watchEndDate) return null; + if (!watchStartDate || !watchEndDate) return null - const startDateMs = new Date(watchStartDate * 1000); - const endDateMs = new Date(watchEndDate * 1000); - const daysDifference = (endDateMs - startDateMs) / (1000 * 60 * 60 * 24); + const startDateMs = new Date(watchStartDate * 1000) + const endDateMs = new Date(watchEndDate * 1000) + const daysDifference = (endDateMs - startDateMs) / (1000 * 60 * 60 * 24) if (daysDifference > 10) { return ( @@ -88,11 +88,11 @@ const Page = () => { narrowing your date range if you encounter issues. - ); + ) } - return null; - }; + return null + } const onSubmit = (data) => { // Check if any filter is applied @@ -100,51 +100,51 @@ const Page = () => { data.startDate !== null || data.endDate !== null || data.username !== null || - data.severity?.length > 0; - setFilterEnabled(hasFilter); + data.severity?.length > 0 + setFilterEnabled(hasFilter) // Format start date if available setStartDate( data.startDate - ? new Date(data.startDate * 1000).toISOString().split("T")[0].replace(/-/g, "") - : null, - ); + ? new Date(data.startDate * 1000).toISOString().split('T')[0].replace(/-/g, '') + : null + ) // Format end date if available setEndDate( data.endDate - ? new Date(data.endDate * 1000).toISOString().split("T")[0].replace(/-/g, "") - : null, - ); + ? new Date(data.endDate * 1000).toISOString().split('T')[0].replace(/-/g, '') + : null + ) // Set username filter if available - setUsername(data.username || null); + setUsername(data.username || null) // Set severity filter if available (convert array to comma-separated string) setSeverity( data.severity && data.severity.length > 0 - ? data.severity.map((item) => item.value).join(",") - : null, - ); + ? data.severity.map((item) => item.value).join(',') + : null + ) // Close the accordion after applying filters - setExpanded(false); - }; + setExpanded(false) + } const clearFilters = () => { formControl.reset({ startDate: null, endDate: null, - username: "", + username: '', severity: [], - }); - setFilterEnabled(false); - setStartDate(null); - setEndDate(null); - setUsername(null); - setSeverity(null); - setExpanded(false); // Close the accordion when clearing filters - }; + }) + setFilterEnabled(false) + setStartDate(null) + setEndDate(null) + setUsername(null) + setSeverity(null) + setExpanded(false) // Close the accordion when clearing filters + } return ( { Logbook Filters {filterEnabled ? ( - + ( {startDate || endDate ? ( <> {startDate ? new Date( - startDate.replace(/(\d{4})(\d{2})(\d{2})/, "$1-$2-$3") + "T00:00:00", + startDate.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3') + 'T00:00:00' ).toLocaleDateString() : new Date().toLocaleDateString()} - {startDate && endDate ? " - " : ""} + {startDate && endDate ? ' - ' : ''} {endDate ? new Date( - endDate.replace(/(\d{4})(\d{2})(\d{2})/, "$1-$2-$3") + "T00:00:00", + endDate.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3') + 'T00:00:00' ).toLocaleDateString() - : ""} + : ''} ) : null} - {username && (startDate || endDate) && " | "} + {username && (startDate || endDate) && ' | '} {username && <>User: {username}} - {severity && (username || startDate || endDate) && " | "} - {severity && <>Severity: {severity.replace(/,/g, ", ")}}) + {severity && (username || startDate || endDate) && ' | '} + {severity && <>Severity: {severity.replace(/,/g, ', ')}}) ) : ( - + (Today: {new Date().toLocaleDateString()}) )} @@ -196,7 +196,7 @@ const Page = () => { Use the filters below to narrow down your logbook results. You can filter by date range, username, and severity levels. By default, the logbook shows the - current day based on UTC time. Your local time is{" "} + current day based on UTC time. Your local time is{' '} {new Date().getTimezoneOffset() / -60} hours offset from UTC. @@ -220,18 +220,18 @@ const Page = () => { formControl={formControl} validators={{ validate: (value) => { - const startDate = formControl.getValues("startDate"); + const startDate = formControl.getValues('startDate') if (value && !startDate) { - return "Start date must be set when using an end date"; + return 'Start date must be set when using an end date' } if ( startDate && value && new Date(value * 1000) < new Date(startDate * 1000) ) { - return "End date must be after start date"; + return 'End date must be after start date' } - return true; + return true }, }} /> @@ -263,12 +263,12 @@ const Page = () => { formControl={formControl} multiple={true} options={[ - { value: "Info", label: "Info" }, - { value: "Warn", label: "Warning" }, - { value: "Error", label: "Error" }, - { value: "Critical", label: "Critical" }, - { value: "Alert", label: "Alert" }, - { value: "Debug", label: "Debug" }, + { value: 'Info', label: 'Info' }, + { value: 'Warn', label: 'Warning' }, + { value: 'Error', label: 'Error' }, + { value: 'Critical', label: 'Critical' }, + { value: 'Alert', label: 'Alert' }, + { value: 'Debug', label: 'Debug' }, ]} placeholder="Select severity levels" /> @@ -312,7 +312,7 @@ const Page = () => { apiUrl={apiUrl} simpleColumns={simpleColumns} queryKey={`Listlogs-${startDate}-${endDate}-${username}-${severity}-${filterEnabled}-${currentTenant}`} - tenantInTitle={true} + tenantInTitle={false} apiData={{ StartDate: startDate, // Pass start date filter from state EndDate: endDate, // Pass end date filter from state @@ -324,9 +324,9 @@ const Page = () => { actions={actions} offCanvas={offcanvas} /> - ); -}; + ) +} -Page.getLayout = (page) => {page}; +Page.getLayout = (page) => {page} -export default Page; +export default Page diff --git a/src/pages/cipp/settings/password-config/index.js b/src/pages/cipp/settings/password-config/index.js index 540dd2853b2f..06d2c452a60a 100644 --- a/src/pages/cipp/settings/password-config/index.js +++ b/src/pages/cipp/settings/password-config/index.js @@ -41,32 +41,32 @@ function normalizeConfigForBackend(config) { return { passwordType: String(config.passwordType || PASSWORD_TYPES.CLASSIC), charCount: String(parseInt(config.charCount, 10) || DEFAULT_VALUES.CHAR_COUNT), - includeUppercase: String(Boolean(config.includeUppercase)), - includeLowercase: String(Boolean(config.includeLowercase)), - includeDigits: String(Boolean(config.includeDigits)), - includeSpecialChars: String(Boolean(config.includeSpecialChars)), + includeUppercase: Boolean(config.includeUppercase), + includeLowercase: Boolean(config.includeLowercase), + includeDigits: Boolean(config.includeDigits), + includeSpecialChars: Boolean(config.includeSpecialChars), specialCharSet: String(config.specialCharSet || DEFAULT_VALUES.SPECIAL_CHAR_SET), wordCount: String(parseInt(config.wordCount, 10) || DEFAULT_VALUES.WORD_COUNT), separator: config.separator !== undefined && config.separator !== null ? String(config.separator) : DEFAULT_VALUES.SEPARATOR, - capitalizeWords: String(Boolean(config.capitalizeWords)), - appendNumber: String(Boolean(config.appendNumber)), - appendSpecialChar: String(Boolean(config.appendSpecialChar)), + capitalizeWords: Boolean(config.capitalizeWords), + appendNumber: Boolean(config.appendNumber), + appendSpecialChar: Boolean(config.appendSpecialChar), }; } const DEFAULT_CONFIG = { - passwordType: PASSWORD_TYPES.CLASSIC, + passwordType: PASSWORD_TYPES.CLASSIC, charCount: String(DEFAULT_VALUES.CHAR_COUNT), - includeUppercase: true, - includeLowercase: true, + includeUppercase: true, + includeLowercase: true, includeDigits: true, - includeSpecialChars: true, + includeSpecialChars: true, specialCharSet: DEFAULT_VALUES.SPECIAL_CHAR_SET, - wordCount: String(DEFAULT_VALUES.WORD_COUNT), + wordCount: String(DEFAULT_VALUES.WORD_COUNT), separator: DEFAULT_VALUES.SEPARATOR, - capitalizeWords: false, - appendNumber: false, + capitalizeWords: false, + appendNumber: false, appendSpecialChar: false, }; @@ -89,7 +89,7 @@ const Page = () => { if (typeof v === 'number') return v === 1; return def; }; - + setConfig({ passwordType: r.passwordType || DEFAULT_CONFIG.passwordType, charCount: String(parseInt(r.charCount, 10) || DEFAULT_CONFIG.charCount), @@ -115,11 +115,11 @@ const Page = () => { const handleSave = () => { const normalizedConfig = normalizeConfigForBackend(config); - + passwordSave.mutate( - { - url: "/api/ExecPasswordConfig", - data: normalizedConfig, + { + url: "/api/ExecPasswordConfig", + data: normalizedConfig, queryKey: "PasswordSettingsPost", } ); diff --git a/src/pages/email/administration/mailbox-rules/index.js b/src/pages/email/administration/mailbox-rules/index.js index eb414aae760e..4b9a9ece88cb 100644 --- a/src/pages/email/administration/mailbox-rules/index.js +++ b/src/pages/email/administration/mailbox-rules/index.js @@ -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 = [ { @@ -96,64 +97,19 @@ const Page = () => { }, }; - const pageActions = [ - - - - - - - - - , - ]; - return ( <> - { - 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} ); }; diff --git a/src/pages/email/administration/mailboxes/index.js b/src/pages/email/administration/mailboxes/index.js index bb3d72bb40fe..f0187d414983 100644 --- a/src/pages/email/administration/mailboxes/index.js +++ b/src/pages/email/administration/mailboxes/index.js @@ -3,27 +3,20 @@ import { CippTablePage } from '../../../../components/CippComponents/CippTablePa import CippExchangeActions from '../../../../components/CippComponents/CippExchangeActions' import { CippHVEUserDrawer } from '../../../../components/CippComponents/CippHVEUserDrawer.jsx' import { CippSharedMailboxDrawer } from '../../../../components/CippComponents/CippSharedMailboxDrawer.jsx' -import { Sync, CloudDone, Bolt } from '@mui/icons-material' -import { Button, SvgIcon, Tooltip, Chip } from '@mui/material' -import { useSettings } from '../../../../hooks/use-settings' +import { useCippReportDB } from '../../../../components/CippComponents/CippReportDBControls' import { Stack } from '@mui/system' -import { useDialog } from '../../../../hooks/use-dialog' -import { CippApiDialog } from '../../../../components/CippComponents/CippApiDialog' -import { useState, useEffect } from 'react' -import { CippQueueTracker } from '../../../../components/CippTable/CippQueueTracker' const Page = () => { const pageTitle = 'Mailboxes' - const currentTenant = useSettings().currentTenant - const syncDialog = useDialog() - const [syncQueueId, setSyncQueueId] = useState(null) - const isAllTenants = currentTenant === 'AllTenants' - const [useReportDB, setUseReportDB] = useState(true) - - useEffect(() => { - setUseReportDB(true) - }, [currentTenant]) + const reportDB = useCippReportDB({ + apiUrl: '/api/ListMailboxes', + queryKey: 'ListMailboxes', + cacheName: 'Mailboxes', + syncTitle: 'Sync Mailboxes', + allowToggle: true, + defaultCached: true, + }) // Define off-canvas details const offCanvas = { @@ -55,31 +48,22 @@ const Page = () => { ] // Simplified columns for the table - const simpleColumns = isAllTenants - ? [ - 'Tenant', // Tenant - 'displayName', // Display Name - 'recipientTypeDetails', // Recipient Type Details - 'UPN', // User Principal Name - 'primarySmtpAddress', // Primary Email Address - 'AdditionalEmailAddresses', // Additional Email Addresses - 'CacheTimestamp', // Cache Timestamp - ] - : [ - 'displayName', // Display Name - 'recipientTypeDetails', // Recipient Type Details - 'UPN', // User Principal Name - 'primarySmtpAddress', // Primary Email Address - 'AdditionalEmailAddresses', // Additional Email Addresses - 'CacheTimestamp', // Cache Timestamp - ] + const simpleColumns = [ + ...reportDB.cacheColumns.filter((c) => c === 'Tenant'), + 'displayName', + 'recipientTypeDetails', + 'UPN', + 'primarySmtpAddress', + 'AdditionalEmailAddresses', + ...reportDB.cacheColumns.filter((c) => c !== 'Tenant'), + ] return ( <> { - {useReportDB && ( - <> - - - - )} - - - : } - label={useReportDB ? 'Cached' : 'Live'} - color="primary" - size="small" - onClick={isAllTenants ? undefined : () => setUseReportDB((prev) => !prev)} - clickable={!isAllTenants} - disabled={isAllTenants} - variant="outlined" - /> - - + {reportDB.controls} } /> - { - if (response?.Metadata?.QueueId) { - setSyncQueueId(response.Metadata.QueueId) - } - }, - }} - /> + {reportDB.syncDialog} ) } diff --git a/src/pages/email/reports/calendar-permissions/index.js b/src/pages/email/reports/calendar-permissions/index.js index 6ca2faac6d0d..eb4620f1cf4f 100644 --- a/src/pages/email/reports/calendar-permissions/index.js +++ b/src/pages/email/reports/calendar-permissions/index.js @@ -1,54 +1,44 @@ import { Layout as DashboardLayout } from '../../../../layouts/index.js' import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx' import { useState } from 'react' -import { Button, Alert, SvgIcon, Tooltip, Chip } from '@mui/material' -import { useSettings } from '../../../../hooks/use-settings' +import { Tooltip, Chip } from '@mui/material' import { Stack } from '@mui/system' -import { Sync, CloudDone, Person, CalendarMonth } from '@mui/icons-material' -import { useDialog } from '../../../../hooks/use-dialog' -import { CippApiDialog } from '../../../../components/CippComponents/CippApiDialog' -import { CippQueueTracker } from '../../../../components/CippTable/CippQueueTracker' +import { Person, CalendarMonth } from '@mui/icons-material' +import { useCippReportDB } from '../../../../components/CippComponents/CippReportDBControls' const Page = () => { const [byUser, setByUser] = useState(true) - const currentTenant = useSettings().currentTenant - const syncDialog = useDialog() - const [syncQueueId, setSyncQueueId] = useState(null) - const isAllTenants = currentTenant === 'AllTenants' + const reportDB = useCippReportDB({ + apiUrl: '/api/ListCalendarPermissions', + queryKey: 'calendar-permissions', + cacheName: 'Mailboxes', + syncTitle: 'Sync Calendar Permissions Cache', + syncData: { Types: 'CalendarPermissions' }, + allowToggle: false, + defaultCached: true, + cacheColumns: ['MailboxCacheTimestamp', 'PermissionCacheTimestamp'], + }) const columns = byUser ? [ - ...(isAllTenants ? ['Tenant'] : []), + ...reportDB.cacheColumns.filter((c) => c === 'Tenant'), 'User', 'UserMailboxType', 'Permissions', - 'MailboxCacheTimestamp', - 'PermissionCacheTimestamp', + ...reportDB.cacheColumns.filter((c) => c !== 'Tenant'), ] : [ - ...(isAllTenants ? ['Tenant'] : []), + ...reportDB.cacheColumns.filter((c) => c === 'Tenant'), 'CalendarUPN', 'CalendarDisplayName', 'CalendarType', 'Permissions', - 'MailboxCacheTimestamp', - 'PermissionCacheTimestamp', + ...reportDB.cacheColumns.filter((c) => c !== 'Tenant'), ] - // Compute apiData based on byUser directly (no useState needed) - const apiData = { - UseReportDB: true, - ByUser: byUser, - } - - const pageActions = [ - - + const pageActions = ( + { variant="outlined" /> - - - - } - label="Cached" - color="primary" - size="small" - disabled - variant="outlined" - /> - - - , - ] + {reportDB.controls} + + ) return ( <> - {currentTenant && currentTenant !== '' ? ( - - ) : ( - Please select a tenant to view calendar permissions. - )} - { - if (result?.Metadata?.QueueId) { - setSyncQueueId(result.Metadata.QueueId) - } - }, - }} + + {reportDB.syncDialog} ) } diff --git a/src/pages/email/reports/mailbox-forwarding/index.js b/src/pages/email/reports/mailbox-forwarding/index.js index 008637df1a97..a24c324f983f 100644 --- a/src/pages/email/reports/mailbox-forwarding/index.js +++ b/src/pages/email/reports/mailbox-forwarding/index.js @@ -1,36 +1,28 @@ import { Layout as DashboardLayout } from "../../../../layouts/index.js"; import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; -import { useState } from "react"; -import { Button, Alert, SvgIcon, IconButton, Tooltip } from "@mui/material"; -import { useSettings } from "../../../../hooks/use-settings"; -import { Stack } from "@mui/system"; -import { Sync, Info } from "@mui/icons-material"; -import { useDialog } from "../../../../hooks/use-dialog"; -import { CippApiDialog } from "../../../../components/CippComponents/CippApiDialog"; -import { CippQueueTracker } from "../../../../components/CippTable/CippQueueTracker"; +import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls"; const Page = () => { - const currentTenant = useSettings().currentTenant; - const syncDialog = useDialog(); - const [syncQueueId, setSyncQueueId] = useState(null); - - const isAllTenants = currentTenant === "AllTenants"; + const reportDB = useCippReportDB({ + apiUrl: "/api/ListMailboxForwarding", + queryKey: "mailbox-forwarding", + cacheName: "Mailboxes", + syncTitle: "Sync Mailbox Cache", + allowToggle: false, + defaultCached: true, + }); const columns = [ - ...(isAllTenants ? ["Tenant"] : []), + ...reportDB.cacheColumns.filter((c) => c === "Tenant"), "UPN", "DisplayName", "RecipientTypeDetails", "ForwardingType", "ForwardTo", "DeliverToMailboxAndForward", - "CacheTimestamp", + ...reportDB.cacheColumns.filter((c) => c !== "Tenant"), ]; - const apiData = { - UseReportDB: true, - }; - const filters = [ { filterName: "External Forwarding", @@ -44,69 +36,19 @@ const Page = () => { }, ]; - const pageActions = [ - - - - - - - - - , - ]; - return ( <> - {currentTenant && currentTenant !== "" ? ( - - ) : ( - Please select a tenant to view mailbox forwarding settings. - )} - { - if (result?.Metadata?.QueueId) { - setSyncQueueId(result.Metadata.QueueId); - } - }, - }} + + {reportDB.syncDialog} ); }; diff --git a/src/pages/email/reports/mailbox-permissions/index.js b/src/pages/email/reports/mailbox-permissions/index.js index cdeff1997a46..da771b6e90f0 100644 --- a/src/pages/email/reports/mailbox-permissions/index.js +++ b/src/pages/email/reports/mailbox-permissions/index.js @@ -1,54 +1,44 @@ import { Layout as DashboardLayout } from '../../../../layouts/index.js' import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx' import { useState } from 'react' -import { Button, Alert, SvgIcon, Tooltip, Chip } from '@mui/material' -import { useSettings } from '../../../../hooks/use-settings' +import { Tooltip, Chip } from '@mui/material' import { Stack } from '@mui/system' -import { Sync, CloudDone, Person, Inbox } from '@mui/icons-material' -import { useDialog } from '../../../../hooks/use-dialog' -import { CippApiDialog } from '../../../../components/CippComponents/CippApiDialog' -import { CippQueueTracker } from '../../../../components/CippTable/CippQueueTracker' +import { Person, Inbox } from '@mui/icons-material' +import { useCippReportDB } from '../../../../components/CippComponents/CippReportDBControls' const Page = () => { const [byUser, setByUser] = useState(true) - const currentTenant = useSettings().currentTenant - const syncDialog = useDialog() - const [syncQueueId, setSyncQueueId] = useState(null) - const isAllTenants = currentTenant === 'AllTenants' + const reportDB = useCippReportDB({ + apiUrl: '/api/ListMailboxPermissions', + queryKey: 'mailbox-permissions', + cacheName: 'Mailboxes', + syncTitle: 'Sync Mailbox Permissions Cache', + syncData: { Types: 'Permissions' }, + allowToggle: false, + defaultCached: true, + cacheColumns: ['MailboxCacheTimestamp', 'PermissionCacheTimestamp'], + }) const columns = byUser ? [ - ...(isAllTenants ? ['Tenant'] : []), + ...reportDB.cacheColumns.filter((c) => c === 'Tenant'), 'User', 'UserMailboxType', 'Permissions', - 'MailboxCacheTimestamp', - 'PermissionCacheTimestamp', + ...reportDB.cacheColumns.filter((c) => c !== 'Tenant'), ] : [ - ...(isAllTenants ? ['Tenant'] : []), + ...reportDB.cacheColumns.filter((c) => c === 'Tenant'), 'MailboxUPN', 'MailboxDisplayName', 'MailboxType', 'Permissions', - 'MailboxCacheTimestamp', - 'PermissionCacheTimestamp', + ...reportDB.cacheColumns.filter((c) => c !== 'Tenant'), ] - // Compute apiData based on byUser directly (no useState needed) - const apiData = { - UseReportDB: true, - ByUser: byUser, - } - - const pageActions = [ - - + const pageActions = ( + { variant="outlined" /> - - - - } - label="Cached" - color="primary" - size="small" - disabled - variant="outlined" - /> - - - , - ] + {reportDB.controls} + + ) return ( <> - {currentTenant && currentTenant !== '' ? ( - - ) : ( - Please select a tenant to view mailbox permissions. - )} - { - if (result?.Metadata?.QueueId) { - setSyncQueueId(result.Metadata.QueueId) - } - }, - }} + + {reportDB.syncDialog} ) } diff --git a/src/pages/endpoint/MEM/list-policies/index.js b/src/pages/endpoint/MEM/list-policies/index.js index 1a78f45906cb..8cc3cd2cb653 100644 --- a/src/pages/endpoint/MEM/list-policies/index.js +++ b/src/pages/endpoint/MEM/list-policies/index.js @@ -4,27 +4,22 @@ 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 { Sync, Info, CloudDone, Bolt } from '@mui/icons-material' -import { Button, SvgIcon, IconButton, Tooltip, Chip } from '@mui/material' +import { useCippReportDB } from '../../../../components/CippComponents/CippReportDBControls' import { Stack } from '@mui/system' -import { useDialog } from '../../../../hooks/use-dialog' -import { CippApiDialog } from '../../../../components/CippComponents/CippApiDialog' -import { CippQueueTracker } from '../../../../components/CippTable/CippQueueTracker' -import { useState, useEffect } from 'react' const Page = () => { const pageTitle = 'Configuration Policies' const cardButtonPermissions = ['Endpoint.MEM.ReadWrite'] const tenant = useSettings().currentTenant - const isAllTenants = tenant === 'AllTenants' - const syncDialog = useDialog() - const [syncQueueId, setSyncQueueId] = useState(null) - const [useReportDB, setUseReportDB] = useState(isAllTenants) - // Reset toggle whenever the tenant changes - useEffect(() => { - setUseReportDB(tenant === 'AllTenants') - }, [tenant]) + const reportDB = useCippReportDB({ + apiUrl: '/api/ListIntunePolicy', + queryKey: 'ListIntunePolicy', + cacheName: 'IntunePolicies', + syncTitle: 'Sync Intune Policy Report', + allowToggle: true, + defaultCached: false, + }) const actions = useCippIntunePolicyActions(tenant, 'URLName', { templateData: { @@ -45,7 +40,7 @@ const Page = () => { } const simpleColumns = [ - ...(useReportDB ? ['Tenant', 'CacheTimestamp'] : []), + ...reportDB.cacheColumns, 'displayName', 'PolicyTypeName', 'PolicyAssignment', @@ -54,62 +49,15 @@ const Page = () => { 'lastModifiedDateTime', ] - const pageActions = [ - - {useReportDB && ( - <> - - - - )} - - - : } - label={useReportDB ? 'Cached' : 'Live'} - color="primary" - size="small" - onClick={isAllTenants ? undefined : () => setUseReportDB((prev) => !prev)} - clickable={!isAllTenants} - disabled={isAllTenants} - variant="outlined" - /> - - - , - ] - return ( <> { requiredPermissions={cardButtonPermissions} PermissionButton={PermissionButton} /> - {pageActions} + {reportDB.controls} } /> - { - if (result?.Metadata?.QueueId) { - setSyncQueueId(result?.Metadata?.QueueId) - } - }, - }} - /> + {reportDB.syncDialog} ) } diff --git a/src/pages/identity/reports/inactive-users-report/index.js b/src/pages/identity/reports/inactive-users-report/index.js index 168231bf6c26..8e8e7a0edc50 100644 --- a/src/pages/identity/reports/inactive-users-report/index.js +++ b/src/pages/identity/reports/inactive-users-report/index.js @@ -1,26 +1,21 @@ import { Layout as DashboardLayout } from "../../../../layouts/index.js"; import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; import { EyeIcon, TrashIcon } from "@heroicons/react/24/outline"; -import { Edit, Block, Sync, Info } from "@mui/icons-material"; -import { - Button, - SvgIcon, - IconButton, - Tooltip, - Alert, -} from "@mui/material"; -import { Stack } from "@mui/system"; -import { useSettings } from "../../../../hooks/use-settings"; -import { useDialog } from "../../../../hooks/use-dialog"; -import { CippApiDialog } from "../../../../components/CippComponents/CippApiDialog"; +import { Edit, Block } from "@mui/icons-material"; +import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls"; const Page = () => { const pageTitle = "Inactive users (6 months)"; - const apiUrl = "/api/ListInactiveAccounts"; - const currentTenant = useSettings().currentTenant; - const syncDialog = useDialog(); - const isAllTenants = currentTenant === "AllTenants"; + const reportDB = useCippReportDB({ + apiUrl: "/api/ListInactiveAccounts", + queryKey: "inactive-users", + cacheName: "Users", + syncTitle: "Sync User Cache", + allowToggle: false, + defaultCached: true, + cacheColumns: ["lastRefreshedDateTime"], + }); const actions = [ { @@ -74,6 +69,7 @@ const Page = () => { }; const simpleColumns = [ + ...reportDB.cacheColumns.filter((c) => c === "Tenant"), "tenantDisplayName", "userPrincipalName", "displayName", @@ -81,60 +77,21 @@ const Page = () => { "lastNonInteractiveSignInDateTime", "numberOfAssignedLicenses", "daysSinceLastSignIn", - "lastRefreshedDateTime", - ]; - - const pageActions = [ - - - - - - - - , + ...reportDB.cacheColumns.filter((c) => c !== "Tenant"), ]; return ( <> - {currentTenant && currentTenant !== "" ? ( - - ) : ( - Please select a tenant to view inactive users. - )} - + {reportDB.syncDialog} ); }; diff --git a/src/pages/identity/reports/mfa-report/index.js b/src/pages/identity/reports/mfa-report/index.js index d6ee22e99081..668030f9f923 100644 --- a/src/pages/identity/reports/mfa-report/index.js +++ b/src/pages/identity/reports/mfa-report/index.js @@ -1,58 +1,39 @@ import { Layout as DashboardLayout } from '../../../../layouts/index.js' import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx' -import { LockPerson, Sync, CloudDone } from '@mui/icons-material' -import { Button, Alert, SvgIcon, Tooltip, Chip } from '@mui/material' -import { useSettings } from '../../../../hooks/use-settings' -import { Stack } from '@mui/system' -import { useDialog } from '../../../../hooks/use-dialog' -import { CippApiDialog } from '../../../../components/CippComponents/CippApiDialog' +import { LockPerson } from '@mui/icons-material' +import { useCippReportDB } from '../../../../components/CippComponents/CippReportDBControls' import { useRouter } from 'next/router' -import { useMemo, useState } from 'react' -import { CippQueueTracker } from '../../../../components/CippTable/CippQueueTracker' +import { useMemo } from 'react' const Page = () => { const pageTitle = 'MFA Report' - const apiUrl = '/api/ListMFAUsers' - const currentTenant = useSettings().currentTenant - const syncDialog = useDialog() const router = useRouter() - const [syncQueueId, setSyncQueueId] = useState(null) - const isAllTenants = currentTenant === 'AllTenants' + const reportDB = useCippReportDB({ + apiUrl: '/api/ListMFAUsers', + queryKey: 'ListMFAUsers', + cacheName: 'MFAState', + syncTitle: 'Sync MFA Report', + allowToggle: false, + defaultCached: true, + }) + + const simpleColumns = [ + ...reportDB.cacheColumns.filter((c) => c === 'Tenant'), + 'UPN', + 'AccountEnabled', + 'isLicensed', + 'MFARegistration', + 'PerUser', + 'CoveredBySD', + 'CoveredByCA', + 'MFAMethods', + 'CAPolicies', + 'IsAdmin', + 'UserType', + ...reportDB.cacheColumns.filter((c) => c !== 'Tenant'), + ] - const apiData = { - UseReportDB: true, - } - const simpleColumns = isAllTenants - ? [ - 'Tenant', - 'UPN', - 'AccountEnabled', - 'isLicensed', - 'MFARegistration', - 'PerUser', - 'CoveredBySD', - 'CoveredByCA', - 'MFAMethods', - 'CAPolicies', - 'IsAdmin', - 'UserType', - 'CacheTimestamp', - ] - : [ - 'UPN', - 'AccountEnabled', - 'isLicensed', - 'MFARegistration', - 'PerUser', - 'CoveredBySD', - 'CoveredByCA', - 'MFAMethods', - 'CAPolicies', - 'IsAdmin', - 'UserType', - 'CacheTimestamp', - ] const filters = [ { filterName: 'Enabled, licensed users', @@ -88,7 +69,6 @@ const Page = () => { }, ] - // Parse filters from URL query parameters const urlFilters = useMemo(() => { if (router.query.filters) { try { @@ -127,71 +107,20 @@ const Page = () => { }, ] - const pageActions = [ - - - - - - } - label="Cached" - color="primary" - size="small" - disabled - variant="outlined" - /> - - - , - ] - return ( <> - { - if (result?.Metadata?.QueueId) { - setSyncQueueId(result?.Metadata?.QueueId) - } - }, - }} - /> + {reportDB.syncDialog} ) } diff --git a/src/pages/security/reports/mde-onboarding/index.js b/src/pages/security/reports/mde-onboarding/index.js index 015653b411c1..839d85f6906a 100644 --- a/src/pages/security/reports/mde-onboarding/index.js +++ b/src/pages/security/reports/mde-onboarding/index.js @@ -12,16 +12,15 @@ import { CircularProgress, Button, SvgIcon, - IconButton, - Tooltip, } from "@mui/material"; -import { Sync, Info, OpenInNew } from "@mui/icons-material"; +import { Sync, OpenInNew } from "@mui/icons-material"; import { ApiGetCall } from "../../../../api/ApiCall"; import { CippHead } from "../../../../components/CippComponents/CippHead"; import { useDialog } from "../../../../hooks/use-dialog"; import { CippApiDialog } from "../../../../components/CippComponents/CippApiDialog"; import { CippQueueTracker } from "../../../../components/CippTable/CippQueueTracker"; import { useState } from "react"; +import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls"; const statusColors = { enabled: "success", @@ -78,11 +77,6 @@ const SingleTenantView = ({ tenant }) => { queryKey={`MDEOnboarding-${tenant}`} title="MDE Onboarding Sync" /> - - - - - - , - ]; - return ( <> - { - if (result?.Metadata?.QueueId) { - setSyncQueueId(result.Metadata.QueueId); - } - }, - }} + cardButton={reportDB.controls} /> + {reportDB.syncDialog} ); }; Page.getLayout = (page) => {page}; -export default Page; \ No newline at end of file +export default Page; diff --git a/src/pages/teams-share/onedrive/index.js b/src/pages/teams-share/onedrive/index.js index 8d279cffaf73..b5ef1f7a2792 100644 --- a/src/pages/teams-share/onedrive/index.js +++ b/src/pages/teams-share/onedrive/index.js @@ -1,43 +1,52 @@ -import { Layout as DashboardLayout } from "../../../layouts/index.js"; -import { CippTablePage } from "../../../components/CippComponents/CippTablePage.jsx"; -import { PersonAdd, PersonRemove } from "@mui/icons-material"; +import { Layout as DashboardLayout } from '../../../layouts/index.js' +import { CippTablePage } from '../../../components/CippComponents/CippTablePage.jsx' +import { PersonAdd, PersonRemove } from '@mui/icons-material' +import { useCippReportDB } from '../../../components/CippComponents/CippReportDBControls' const Page = () => { - const pageTitle = "OneDrive"; + const pageTitle = 'OneDrive' + const reportDB = useCippReportDB({ + apiUrl: '/api/ListSites?type=OneDriveUsageAccount', + queryKey: 'ListSites-OneDriveUsageAccount', + cacheName: 'OneDriveUsage', + syncTitle: 'Sync OneDrive Usage', + allowToggle: true, + defaultCached: false, + }) const actions = [ { - label: "Add permissions to OneDrive", + label: 'Add permissions to OneDrive', icon: , - type: "POST", - url: "/api/ExecSharePointPerms", + type: 'POST', + url: '/api/ExecSharePointPerms', data: { - UPN: "ownerPrincipalName", - URL: "webUrl", + UPN: 'ownerPrincipalName', + URL: 'webUrl', RemovePermission: false, }, confirmText: "Select the User to add to this user's OneDrive permissions", fields: [ { - type: "autoComplete", - name: "onedriveAccessUser", - label: "Select User", + type: 'autoComplete', + name: 'onedriveAccessUser', + label: 'Select User', multiple: false, creatable: false, api: { - url: "/api/ListGraphRequest", + url: '/api/ListGraphRequest', data: { - Endpoint: "users", - $select: "id,displayName,userPrincipalName", + Endpoint: 'users', + $select: 'id,displayName,userPrincipalName', $top: 999, $count: true, }, - queryKey: "ListUsersAutoComplete", - dataKey: "Results", + queryKey: 'ListUsersAutoComplete', + dataKey: 'Results', labelField: (user) => `${user.displayName} (${user.userPrincipalName})`, - valueField: "userPrincipalName", + valueField: 'userPrincipalName', addedField: { - id: "id", + id: 'id', }, showRefresh: true, }, @@ -45,57 +54,67 @@ const Page = () => { ], }, { - label: "Remove permissions from OneDrive", + label: 'Remove permissions from OneDrive', icon: , - type: "POST", - url: "/api/ExecSharePointPerms", + type: 'POST', + url: '/api/ExecSharePointPerms', data: { - UPN: "ownerPrincipalName", - URL: "webUrl", + UPN: 'ownerPrincipalName', + URL: 'webUrl', RemovePermission: true, }, confirmText: "Select the User to remove from this user's OneDrive permissions", fields: [ { - type: "autoComplete", - name: "onedriveAccessUser", - label: "Select User", + type: 'autoComplete', + name: 'onedriveAccessUser', + label: 'Select User', multiple: false, creatable: false, api: { - url: "/api/listUsers", + url: '/api/listUsers', labelField: (onedriveAccessUser) => `${onedriveAccessUser.displayName} (${onedriveAccessUser.userPrincipalName})`, - valueField: "userPrincipalName", + valueField: 'userPrincipalName', addedField: { - displayName: "displayName", + displayName: 'displayName', }, }, }, ], }, - ]; + ] + + const simpleColumns = [ + ...reportDB.cacheColumns.filter((c) => c === 'Tenant'), + 'displayName', + 'createdDateTime', + 'ownerPrincipalName', + 'lastActivityDate', + 'fileCount', + 'storageUsedInGigabytes', + 'storageAllocatedInGigabytes', + 'reportRefreshDate', + 'webUrl', + ...reportDB.cacheColumns.filter((c) => c !== 'Tenant'), + ] return ( - - ); -}; + <> + + {reportDB.syncDialog} + + ) +} -Page.getLayout = (page) => {page}; +Page.getLayout = (page) => {page} -export default Page; +export default Page diff --git a/src/pages/teams-share/sharepoint/index.js b/src/pages/teams-share/sharepoint/index.js index 21cefc406ca4..ac08cec280cd 100644 --- a/src/pages/teams-share/sharepoint/index.js +++ b/src/pages/teams-share/sharepoint/index.js @@ -1,6 +1,6 @@ -import { Layout as DashboardLayout } from "../../../layouts/index.js"; -import { CippTablePage } from "../../../components/CippComponents/CippTablePage.jsx"; -import { Button } from "@mui/material"; +import { Layout as DashboardLayout } from '../../../layouts/index.js' +import { CippTablePage } from '../../../components/CippComponents/CippTablePage.jsx' +import { Button } from '@mui/material' import { Add, AddToPhotos, @@ -9,49 +9,59 @@ import { AdminPanelSettings, NoAccounts, Delete, -} from "@mui/icons-material"; -import Link from "next/link"; -import { CippDataTable } from "../../../components/CippTable/CippDataTable"; -import { useSettings } from "../../../hooks/use-settings"; +} from '@mui/icons-material' +import Link from 'next/link' +import { Stack } from '@mui/system' +import { CippDataTable } from '../../../components/CippTable/CippDataTable' +import { useSettings } from '../../../hooks/use-settings' +import { useCippReportDB } from '../../../components/CippComponents/CippReportDBControls' const Page = () => { - const pageTitle = "SharePoint Sites"; - const tenantFilter = useSettings().currentTenant; + const pageTitle = 'SharePoint Sites' + const tenantFilter = useSettings().currentTenant + const reportDB = useCippReportDB({ + apiUrl: '/api/ListSites?type=SharePointSiteUsage', + queryKey: 'ListSites-SharePointSiteUsage', + cacheName: 'SharePointSiteUsage', + syncTitle: 'Sync SharePoint Site Usage', + allowToggle: true, + defaultCached: false, + }) const actions = [ { - label: "Add Member", - type: "POST", + label: 'Add Member', + type: 'POST', icon: , - url: "/api/ExecSetSharePointMember", + url: '/api/ExecSetSharePointMember', data: { - groupId: "ownerPrincipalName", + groupId: 'ownerPrincipalName', add: true, - URL: "webUrl", - SharePointType: "rootWebTemplate", + URL: 'webUrl', + SharePointType: 'rootWebTemplate', }, - confirmText: "Select the User to add as a member.", + confirmText: 'Select the User to add as a member.', fields: [ { - type: "autoComplete", - name: "user", - label: "Select User", + type: 'autoComplete', + name: 'user', + label: 'Select User', multiple: false, creatable: false, api: { - url: "/api/ListGraphRequest", + url: '/api/ListGraphRequest', data: { - Endpoint: "users", - $select: "id,displayName,userPrincipalName", + Endpoint: 'users', + $select: 'id,displayName,userPrincipalName', $top: 999, $count: true, }, - queryKey: "ListUsersAutoComplete", - dataKey: "Results", + queryKey: 'ListUsersAutoComplete', + dataKey: 'Results', labelField: (user) => `${user.displayName} (${user.userPrincipalName})`, - valueField: "userPrincipalName", + valueField: 'userPrincipalName', addedField: { - id: "id", + id: 'id', }, showRefresh: true, }, @@ -60,38 +70,38 @@ const Page = () => { multiPost: false, }, { - label: "Remove Member", - type: "POST", + label: 'Remove Member', + type: 'POST', icon: , - url: "/api/ExecSetSharePointMember", + url: '/api/ExecSetSharePointMember', data: { - groupId: "ownerPrincipalName", + groupId: 'ownerPrincipalName', add: false, - URL: "URL", - SharePointType: "rootWebTemplate", + URL: 'URL', + SharePointType: 'rootWebTemplate', }, - confirmText: "Select the User to remove as a member.", + confirmText: 'Select the User to remove as a member.', fields: [ { - type: "autoComplete", - name: "user", - label: "Select User", + type: 'autoComplete', + name: 'user', + label: 'Select User', multiple: false, creatable: false, api: { - url: "/api/ListGraphRequest", + url: '/api/ListGraphRequest', data: { - Endpoint: "users", - $select: "id,displayName,userPrincipalName", + Endpoint: 'users', + $select: 'id,displayName,userPrincipalName', $top: 999, $count: true, }, - queryKey: "ListUsersAutoComplete", - dataKey: "Results", + queryKey: 'ListUsersAutoComplete', + dataKey: 'Results', labelField: (user) => `${user.displayName} (${user.userPrincipalName})`, - valueField: "userPrincipalName", + valueField: 'userPrincipalName', addedField: { - id: "id", + id: 'id', }, showRefresh: true, }, @@ -100,37 +110,37 @@ const Page = () => { multiPost: false, }, { - label: "Add Site Admin", - type: "POST", + label: 'Add Site Admin', + type: 'POST', icon: , - url: "/api/ExecSharePointPerms", + url: '/api/ExecSharePointPerms', data: { - UPN: "ownerPrincipalName", + UPN: 'ownerPrincipalName', RemovePermission: false, - URL: "webUrl", + URL: 'webUrl', }, - confirmText: "Select the User to add to the Site Admins permissions", + confirmText: 'Select the User to add to the Site Admins permissions', fields: [ { - type: "autoComplete", - name: "user", - label: "Select User", + type: 'autoComplete', + name: 'user', + label: 'Select User', multiple: false, creatable: false, api: { - url: "/api/ListGraphRequest", + url: '/api/ListGraphRequest', data: { - Endpoint: "users", - $select: "id,displayName,userPrincipalName", + Endpoint: 'users', + $select: 'id,displayName,userPrincipalName', $top: 999, $count: true, }, - queryKey: "ListUsersAutoComplete", - dataKey: "Results", + queryKey: 'ListUsersAutoComplete', + dataKey: 'Results', labelField: (user) => `${user.displayName} (${user.userPrincipalName})`, - valueField: "userPrincipalName", + valueField: 'userPrincipalName', addedField: { - id: "id", + id: 'id', }, showRefresh: true, }, @@ -139,37 +149,37 @@ const Page = () => { multiPost: false, }, { - label: "Remove Site Admin", - type: "POST", + label: 'Remove Site Admin', + type: 'POST', icon: , - url: "/api/ExecSharePointPerms", + url: '/api/ExecSharePointPerms', data: { - UPN: "ownerPrincipalName", + UPN: 'ownerPrincipalName', RemovePermission: true, - URL: "webUrl", + URL: 'webUrl', }, - confirmText: "Select the User to remove from the Site Admins permissions", + confirmText: 'Select the User to remove from the Site Admins permissions', fields: [ { - type: "autoComplete", - name: "user", - label: "Select User", + type: 'autoComplete', + name: 'user', + label: 'Select User', multiple: false, creatable: false, api: { - url: "/api/ListGraphRequest", + url: '/api/ListGraphRequest', data: { - Endpoint: "users", - $select: "id,displayName,userPrincipalName", + Endpoint: 'users', + $select: 'id,displayName,userPrincipalName', $top: 999, $count: true, }, - queryKey: "ListUsersAutoComplete", - dataKey: "Results", + queryKey: 'ListUsersAutoComplete', + dataKey: 'Results', labelField: (user) => `${user.displayName} (${user.userPrincipalName})`, - valueField: "userPrincipalName", + valueField: 'userPrincipalName', addedField: { - id: "id", + id: 'id', }, showRefresh: true, }, @@ -178,75 +188,88 @@ const Page = () => { multiPost: false, }, { - label: "Delete Site", - type: "POST", + label: 'Delete Site', + type: 'POST', icon: , - url: "/api/DeleteSharepointSite", + url: '/api/DeleteSharepointSite', data: { - SiteId: "siteId", + SiteId: 'siteId', }, - confirmText: "Are you sure you want to delete this SharePoint site? This action cannot be undone.", - color: "error", + confirmText: + 'Are you sure you want to delete this SharePoint site? This action cannot be undone.', + color: 'error', multiPost: false, }, - ]; + ] const offCanvas = { - extendedInfoFields: ["displayName", "description", "webUrl"], + extendedInfoFields: ['displayName', 'description', 'webUrl'], actions: actions, children: (row) => ( ), - size: "lg", // Make the offcanvas extra large - }; + size: 'lg', // Make the offcanvas extra large + } + + const simpleColumns = [ + ...reportDB.cacheColumns.filter((c) => c === 'Tenant'), + 'displayName', + 'createdDateTime', + 'ownerPrincipalName', + 'lastActivityDate', + 'fileCount', + 'storageUsedInGigabytes', + 'storageAllocatedInGigabytes', + 'reportRefreshDate', + 'webUrl', + ...reportDB.cacheColumns.filter((c) => c !== 'Tenant'), + ] + + const pageActions = ( + + + + {reportDB.controls} + + ) return ( - - - - - } - /> - ); -}; + <> + + {reportDB.syncDialog} + + ) +} -Page.getLayout = (page) => {page}; +Page.getLayout = (page) => {page} -export default Page; +export default Page diff --git a/src/pages/tenant/administration/alert-configuration/alert.jsx b/src/pages/tenant/administration/alert-configuration/alert.jsx index dff059067894..d9af06f2b549 100644 --- a/src/pages/tenant/administration/alert-configuration/alert.jsx +++ b/src/pages/tenant/administration/alert-configuration/alert.jsx @@ -166,12 +166,14 @@ const AlertWizard = () => { } else if (alert.RawAlert.TenantGroup) { try { const tenantGroupObject = JSON.parse(alert.RawAlert.TenantGroup) - tenantFilterForForm = { - value: tenantGroupObject.value, - label: tenantGroupObject.label, - type: 'Group', - addedFields: tenantGroupObject, - } + tenantFilterForForm = [ + { + value: tenantGroupObject.value, + label: tenantGroupObject.label, + type: 'Group', + addedFields: tenantGroupObject, + }, + ] } catch (error) { console.error('Error parsing tenant group:', error) tenantFilterForForm = [ diff --git a/src/pages/tenant/gdap-management/index.js b/src/pages/tenant/gdap-management/index.js index 25e1a1308489..f1bde321dd71 100644 --- a/src/pages/tenant/gdap-management/index.js +++ b/src/pages/tenant/gdap-management/index.js @@ -1,99 +1,103 @@ -import { TabbedLayout } from "../../../layouts/TabbedLayout"; -import { Layout as DashboardLayout } from "../../../layouts/index.js"; -import tabOptions from "./tabOptions"; -import { Container } from "@mui/system"; -import { Grid } from "@mui/system"; -import { CippInfoBar } from "../../../components/CippCards/CippInfoBar"; -import { ApiPostCall, ApiGetCallWithPagination } from "../../../api/ApiCall"; +import { TabbedLayout } from '../../../layouts/TabbedLayout' +import { Layout as DashboardLayout } from '../../../layouts/index.js' +import tabOptions from './tabOptions' +import { Container } from '@mui/system' +import { Grid } from '@mui/system' +import { CippInfoBar } from '../../../components/CippCards/CippInfoBar' +import { ApiPostCall, ApiGetCallWithPagination } from '../../../api/ApiCall' import { Add, AdminPanelSettings, HourglassBottom, Layers, SupervisorAccount, -} from "@mui/icons-material"; -import CippPermissionCheck from "../../../components/CippSettings/CippPermissionCheck"; -import { Button } from "@mui/material"; -import { useEffect, useState } from "react"; -import CippButtonCard from "../../../components/CippCards/CippButtonCard"; -import { WizardSteps } from "../../../components/CippWizard/wizard-steps"; -import Link from "next/link"; -import { CippHead } from "../../../components/CippComponents/CippHead"; -import { usePermissions } from "../../../hooks/use-permissions"; +} from '@mui/icons-material' +import CippPermissionCheck from '../../../components/CippSettings/CippPermissionCheck' +import { Button } from '@mui/material' +import { useEffect, useState } from 'react' +import CippButtonCard from '../../../components/CippCards/CippButtonCard' +import { WizardSteps } from '../../../components/CippWizard/wizard-steps' +import Link from 'next/link' +import { CippHead } from '../../../components/CippComponents/CippHead' +import { usePermissions } from '../../../hooks/use-permissions' const Page = () => { - const [createDefaults, setCreateDefaults] = useState(false); - const [activeStep, setActiveStep] = useState(0); - const { checkRoles } = usePermissions(); - const canViewGdapChecks = checkRoles(["CIPP.AppSettings.Read"]); + const [createDefaults, setCreateDefaults] = useState(false) + const [activeStep, setActiveStep] = useState(0) + const { checkPermissions } = usePermissions() + const canViewGdapChecks = checkPermissions(['CIPP.AppSettings.Read']) const relationships = ApiGetCallWithPagination({ - url: "/api/ListGDAPRelationships", - queryKey: "ListGDAPRelationships", - }); + url: '/api/ListGDAPRelationships', + queryKey: 'ListGDAPRelationships', + waiting: true, + }) const mappedRoles = ApiGetCallWithPagination({ - url: "/api/ListGDAPRoles", - queryKey: "ListGDAPRoles", - }); + url: '/api/ListGDAPRoles', + queryKey: 'ListGDAPRoles', + waiting: true, + }) const roleTemplates = ApiGetCallWithPagination({ - url: "/api/ExecGDAPRoleTemplate", - queryKey: "ListGDAPRoleTemplates", - }); + url: '/api/ExecGDAPRoleTemplate', + queryKey: 'ListGDAPRoleTemplates', + waiting: true, + }) const pendingInvites = ApiGetCallWithPagination({ - url: "/api/ListGDAPInvite", - queryKey: "ListGDAPInvite", - }); + url: '/api/ListGDAPInvite', + queryKey: 'ListGDAPInvite', + waiting: true, + }) const createCippDefaults = ApiPostCall({ urlFromData: true, - relatedQueryKeys: ["ListGDAPRoleTemplates", "ListGDAPRoles"], - }); + relatedQueryKeys: ['ListGDAPRoleTemplates', 'ListGDAPRoles'], + }) useEffect(() => { if (roleTemplates.isSuccess) { - var promptCreateDefaults = true; + var promptCreateDefaults = true // check templates for CIPP Defaults - const firstPageResults = roleTemplates?.data?.pages?.[0]?.Results; + const firstPageResults = roleTemplates?.data?.pages?.[0]?.Results if ( firstPageResults && Array.isArray(firstPageResults) && firstPageResults.length > 0 && - firstPageResults.find((t) => t?.TemplateId === "CIPP Defaults") + firstPageResults.find((t) => t?.TemplateId === 'CIPP Defaults') ) { - promptCreateDefaults = false; + promptCreateDefaults = false } - setCreateDefaults(promptCreateDefaults); + setCreateDefaults(promptCreateDefaults) } - }, [roleTemplates]); + }, [roleTemplates]) useEffect(() => { if (mappedRoles.isSuccess && roleTemplates.isSuccess && pendingInvites.isSuccess) { - const mappedRolesFirstPage = mappedRoles?.data?.pages?.[0]; + const mappedRolesFirstPage = mappedRoles?.data?.pages?.[0] if ( mappedRolesFirstPage && Array.isArray(mappedRolesFirstPage) && mappedRolesFirstPage.length > 0 ) { - setActiveStep(1); + setActiveStep(1) - const roleTemplatesFirstPage = roleTemplates?.data?.pages?.[0]?.Results; + const roleTemplatesFirstPage = roleTemplates?.data?.pages?.[0]?.Results if ( roleTemplatesFirstPage && Array.isArray(roleTemplatesFirstPage) && roleTemplatesFirstPage.length > 0 ) { - setActiveStep(2); + setActiveStep(2) - const pendingInvitesFirstPage = pendingInvites?.data?.pages?.[0]; + const pendingInvitesFirstPage = pendingInvites?.data?.pages?.[0] if ( pendingInvitesFirstPage && Array.isArray(pendingInvitesFirstPage) && pendingInvitesFirstPage.length > 0 ) { - setActiveStep(4); + setActiveStep(4) } } } @@ -104,7 +108,7 @@ const Page = () => { roleTemplates.isSuccess, roleTemplates.isFetching, pendingInvites.isSuccess, - ]); + ]) return ( { relationships.data?.pages ?.map((page) => page?.Results?.length || 0) .reduce((a, b) => (a || 0) + (b || 0), 0) ?? 0, - name: "GDAP Relationships", - color: "secondary", + name: 'GDAP Relationships', + color: 'secondary', }, { icon: , @@ -140,8 +144,8 @@ const Page = () => { mappedRoles.data?.pages ?.map((page) => page?.length || 0) .reduce((a, b) => (a || 0) + (b || 0), 0) ?? 0, - name: "Mapped Admin Roles", - color: "green", + name: 'Mapped Admin Roles', + color: 'green', }, { icon: , @@ -149,7 +153,7 @@ const Page = () => { roleTemplates.data?.pages ?.map((page) => page?.Results?.length || 0) .reduce((a, b) => (a || 0) + (b || 0), 0) ?? 0, - name: "Role Templates", + name: 'Role Templates', }, { icon: , @@ -157,7 +161,7 @@ const Page = () => { pendingInvites.data?.pages ?.map((page) => page?.length || 0) .reduce((a, b) => (a || 0) + (b || 0), 0) ?? 0, - name: "Pending Invites", + name: 'Pending Invites', }, ]} /> @@ -177,27 +181,27 @@ const Page = () => { { )} - ); -}; + ) +} Page.getLayout = (page) => ( {page} -); +) -export default Page; +export default Page diff --git a/src/pages/tenant/reports/application-consent/index.js b/src/pages/tenant/reports/application-consent/index.js index 743b4226b763..08ac8b35d47c 100644 --- a/src/pages/tenant/reports/application-consent/index.js +++ b/src/pages/tenant/reports/application-consent/index.js @@ -1,107 +1,39 @@ import { Layout as DashboardLayout } from "../../../../layouts/index.js"; import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; -import { Sync, CloudDone, Bolt } from "@mui/icons-material"; -import { Button, SvgIcon, Tooltip, Chip } from "@mui/material"; -import { useSettings } from "../../../../hooks/use-settings"; -import { Stack } from "@mui/system"; -import { useDialog } from "../../../../hooks/use-dialog"; -import { CippApiDialog } from "../../../../components/CippComponents/CippApiDialog"; -import { useState, useEffect } from "react"; -import { CippQueueTracker } from "../../../../components/CippTable/CippQueueTracker"; +import { useCippReportDB } from "../../../../components/CippComponents/CippReportDBControls"; const pageTitle = "Consented Applications"; const Page = () => { - const currentTenant = useSettings().currentTenant; - const syncDialog = useDialog(); - const [syncQueueId, setSyncQueueId] = useState(null); - - const isAllTenants = currentTenant === "AllTenants"; - const [useReportDB, setUseReportDB] = useState(true); - - useEffect(() => { - setUseReportDB(true); - }, [currentTenant]); - - const simpleColumns = isAllTenants - ? ["Tenant", "Name", "ApplicationID", "ObjectID", "Scope", "StartTime", "CacheTimestamp"] - : ["Name", "ApplicationID", "ObjectID", "Scope", "StartTime", "CacheTimestamp"]; + const reportDB = useCippReportDB({ + apiUrl: "/api/ListOAuthApps", + queryKey: "ListOAuthApps", + cacheName: "OAuth2PermissionGrants", + syncTitle: "Sync Consented Applications", + allowToggle: true, + defaultCached: true, + }); + + const simpleColumns = [ + ...reportDB.cacheColumns.filter((c) => c === "Tenant"), + "Name", + "ApplicationID", + "ObjectID", + "Scope", + "StartTime", + ...reportDB.cacheColumns.filter((c) => c !== "Tenant"), + ]; return ( <> - {useReportDB && ( - <> - - - - )} - - - : } - label={useReportDB ? "Cached" : "Live"} - color="primary" - size="small" - onClick={isAllTenants ? undefined : () => setUseReportDB((prev) => !prev)} - clickable={!isAllTenants} - disabled={isAllTenants} - variant="outlined" - /> - - - - } - /> - { - if (response?.Metadata?.QueueId) { - setSyncQueueId(response.Metadata.QueueId); - } - }, - }} + cardButton={reportDB.controls} /> + {reportDB.syncDialog} ); }; diff --git a/src/pages/tools/custom-tests/add.jsx b/src/pages/tools/custom-tests/add.jsx index 6951fe57c507..df7fe5f1cd50 100644 --- a/src/pages/tools/custom-tests/add.jsx +++ b/src/pages/tools/custom-tests/add.jsx @@ -300,6 +300,7 @@ const Page = () => { label: 'Category', type: 'autoComplete', required: true, + multiple: false, placeholder: 'Select or enter a category', options: categoryOptions, creatable: true,