diff --git a/public/version_latest.txt b/public/version_latest.txt index 3bff059174b8..804440660c71 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -5.1.1 \ No newline at end of file +5.2.1 \ No newline at end of file diff --git a/src/_nav.jsx b/src/_nav.jsx index a17d7e73a344..d701c055f36d 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -50,6 +50,11 @@ const _nav = [ name: 'Groups', to: '/identity/administration/groups', }, + { + component: CNavItem, + name: 'Devices', + to: '/identity/administration/devices', + }, { component: CNavItem, name: 'Deploy Group Template', @@ -737,6 +742,11 @@ const _nav = [ name: 'Logbook', to: '/cipp/logs', }, + { + component: CNavItem, + name: 'Statistics', + to: '/cipp/statistics', + }, { component: CNavItem, name: 'SAM Setup Wizard', diff --git a/src/components/forms/RFFComponents.jsx b/src/components/forms/RFFComponents.jsx index 2d315826b4f2..834c9688d2cc 100644 --- a/src/components/forms/RFFComponents.jsx +++ b/src/components/forms/RFFComponents.jsx @@ -11,6 +11,7 @@ import { CTooltip, } from '@coreui/react' import Select from 'react-select' +import Creatable, { useCreatable } from 'react-select/creatable' import { Field } from 'react-final-form' import { FieldArray } from 'react-final-form-arrays' import React, { useState, useMemo, useRef } from 'react' @@ -393,6 +394,7 @@ export const RFFSelectSearch = ({ disabled = false, retainInput = true, isLoading = false, + allowCreate = false, refreshFunction, props, }) => { @@ -433,7 +435,7 @@ export const RFFSelectSearch = ({ )} - {onChange && ( + {!allowCreate && onChange && ( )} + {allowCreate && onChange && ( + + )} + {allowCreate && !onChange && ( + + )} {meta.error && meta.touched && {meta.error}} ) diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index 32f89ad71795..74dbf52ecd53 100644 --- a/src/components/tables/CippTable.jsx +++ b/src/components/tables/CippTable.jsx @@ -389,14 +389,43 @@ export default function CippTable({ } const newModalBody = {} for (let [objName, objValue] of Object.entries(modalBody)) { - if (objValue.toString().startsWith('!')) { + if (typeof objValue === 'object' && objValue !== null) { + newModalBody[objName] = {} + for (let [nestedObjName, nestedObjValue] of Object.entries(objValue)) { + if (typeof nestedObjValue === 'string' && nestedObjValue.startsWith('!')) { + newModalBody[objName][nestedObjName] = row[nestedObjValue.replace('!', '')] + } else { + newModalBody[objName][nestedObjName] = nestedObjValue + } + } + } else if (typeof objValue === 'string' && objValue.startsWith('!')) { newModalBody[objName] = row[objValue.replace('!', '')] + } else { + newModalBody[objName] = objValue } } const NewModalUrl = `${modalUrl.split('?')[0]}?${urlParams.toString()}` + const selectedValue = inputRef.current.value + let additionalFields = {} + if (inputRef.current.nodeName === 'SELECT') { + const selectedItem = dropDownInfo.data.find( + (item) => item[modalDropdown.valueField] === selectedValue, + ) + if (selectedItem && modalDropdown.addedField) { + Object.keys(modalDropdown.addedField).forEach((key) => { + additionalFields[key] = selectedItem[modalDropdown.addedField[key]] + }) + } + } + const results = await genericPostRequest({ path: NewModalUrl, - values: { ...modalBody, ...newModalBody, ...{ input: inputRef.current.value } }, + values: { + ...modalBody, + ...newModalBody, + ...additionalFields, + ...{ input: inputRef.current.value }, + }, }) resultsarr.push(results) setMassResults(resultsarr) @@ -466,6 +495,7 @@ export default function CippTable({ } const executeselectedAction = (item) => { + console.log(item) setModalContent({ item, }) @@ -556,6 +586,9 @@ export default function CippTable({ let output = {} for (let k in obj) { let val = obj[k] + if (val === null) { + val = '' + } const newKey = prefix ? prefix + '.' + k : k if (typeof val === 'object') { if (Array.isArray(val)) { diff --git a/src/components/utilities/CippCodeOffcanvas.jsx b/src/components/utilities/CippCodeOffcanvas.jsx index 589c27ce3cb8..a9b5cf41a055 100644 --- a/src/components/utilities/CippCodeOffcanvas.jsx +++ b/src/components/utilities/CippCodeOffcanvas.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useState, useEffect } from 'react' import { CButton, CCallout, CCol, CRow, CSpinner } from '@coreui/react' import { CippOffcanvas } from 'src/components/utilities' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' @@ -6,6 +6,8 @@ import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 's import { Editor } from '@monaco-editor/react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import CopyToClipboard from 'react-copy-to-clipboard' function CippCodeOffCanvas({ row, @@ -14,11 +16,13 @@ function CippCodeOffCanvas({ type, title = 'Template JSON', hideButton = false, + path = `/api/ExecEditTemplate?type=${type}`, }) { const [SaveTemplate, templateDetails] = useLazyGenericPostRequestQuery() const currentTheme = useSelector((state) => state.app.currentTheme) const [templateData, setFormData] = useState(row) const [invalidJSON, setInvalid] = useState(false) + const [copied, setCopied] = useState(false) function handleEditorChange(value, event) { try { @@ -29,6 +33,10 @@ function CippCodeOffCanvas({ } } + useEffect(() => { + setCopied(false) + }, [setCopied, templateData]) + return ( <> {!hideButton && ( - - SaveTemplate({ - path: `/api/ExecEditTemplate?type=${type}`, - method: 'POST', - values: templateData, - }) - } - > - Save changes {templateDetails.isFetching && } - + <> + + SaveTemplate({ + path: path, + method: 'POST', + values: templateData, + }) + } + className="me-2" + > + {templateDetails.isFetching ? ( + + ) : ( + + )} + Save changes + + setCopied(true)}> + + Copy + to Clipboard + + + )} @@ -83,6 +105,7 @@ CippCodeOffCanvas.propTypes = { type: PropTypes.string, title: PropTypes.string, hideButton: PropTypes.bool, + path: PropTypes.string, } export default CippCodeOffCanvas diff --git a/src/components/utilities/SharedModal.jsx b/src/components/utilities/SharedModal.jsx index fb8501fc93ca..9167b7a2073b 100644 --- a/src/components/utilities/SharedModal.jsx +++ b/src/components/utilities/SharedModal.jsx @@ -29,7 +29,7 @@ function mapBodyComponent({ componentType, data, componentProps }) { } const sharedProps = { - componentType: PropTypes.oneOf(['table', 'list', 'text', 'confirm']), + componentType: PropTypes.oneOf(['table', 'list', 'text', 'confirm', 'codeblock']), componentProps: PropTypes.object, body: PropTypes.oneOfType([PropTypes.node, PropTypes.element]), data: PropTypes.any, diff --git a/src/data/standards.json b/src/data/standards.json index 8c49f0deb337..01394297cd34 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -2,6 +2,7 @@ { "name": "standards.MailContacts", "cat": "Global Standards", + "tag": ["lowimpact"], "helpText": "Defines the email address to receive general updates and information related to M365 subscriptions. Leave a contact field blank if you do not want to update the contact information.", "disabledFeatures": { "report": false, @@ -37,6 +38,7 @@ { "name": "standards.AuditLog", "cat": "Global Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Enables the Unified Audit Log for tracking and auditing activities. Also runs Enable-OrganizationCustomization if necessary.", "addedComponent": [], "label": "Enable the Unified Audit Log", @@ -46,6 +48,7 @@ { "name": "standards.PhishProtection", "cat": "Global Standards", + "tag": ["lowimpact"], "helpText": "Adds branding to the logon page that only appears if the url is not login.microsoftonline.com. This potentially prevents AITM attacks via EvilNginx. This will also automatically generate alerts if a clone of your login page has been found when set to Remediate.", "addedComponent": [], "label": "Enable Phishing Protection system via branding CSS", @@ -60,6 +63,7 @@ { "name": "standards.EnableCustomerLockbox", "cat": "Global Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Enables Customer Lockbox that offers an approval process for Microsoft support to access organization data", "addedComponent": [], "label": "Enable Customer Lockbox", @@ -69,6 +73,7 @@ { "name": "standards.AnonReportDisable", "cat": "Global Standards", + "tag": ["lowimpact"], "helpText": "Shows usernames instead of pseudo anonymised names in reports. This standard is required for reporting to work correctly.", "addedComponent": [], "label": "Enable Usernames instead of pseudo anonymised names in reports", @@ -78,6 +83,7 @@ { "name": "standards.DisableGuestDirectory", "cat": "Global Standards", + "tag": ["lowimpact"], "helpText": "Disables Guest access to enumerate directory objects. This prevents guest users from seeing other users or guests in the directory.", "addedComponent": [], "label": "Restrict guest user access to directory objects", @@ -87,6 +93,7 @@ { "name": "standards.DisableBasicAuthSMTP", "cat": "Global Standards", + "tag": ["mediumimpact"], "helpText": "Disables SMTP AUTH for the organization and all users. This is the default for new tenants. ", "addedComponent": [], "label": "Disable SMTP Basic Authentication", @@ -96,6 +103,7 @@ { "name": "standards.ActivityBasedTimeout", "cat": "Global Standards", + "tag": ["mediumimpact", "CIS"], "helpText": "Enables and sets Idle session timeout for Microsoft 365 to 1 hour. This policy affects most M365 web apps", "addedComponent": [], "label": "Enable 1 hour Activity based Timeout", @@ -105,6 +113,7 @@ { "name": "standards.laps", "cat": "Entra (AAD) Standards", + "tag": ["lowimpact"], "helpText": "Enables the tenant to use LAPS. You must still create a policy for LAPS to be active on all devices. Use the template standards to deploy this by default.", "addedComponent": [], "label": "Enable LAPS on the tenant", @@ -112,8 +121,9 @@ "impactColour": "info" }, { - "cat": "Entra (AAD) Standards", "name": "standards.PWdisplayAppInformationRequiredState", + "cat": "Entra (AAD) Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Enables the MS authenticator app to display information about the app that is requesting authentication. This displays the application name.", "addedComponent": [], "label": "Enable Passwordless with Location information and Number Matching", @@ -121,8 +131,9 @@ "impactColour": "info" }, { - "cat": "Entra (AAD) Standards", "name": "standards.allowOTPTokens", + "cat": "Entra (AAD) Standards", + "tag": ["lowimpact"], "helpText": "Allows you to use MS authenticator OTP token generator", "addedComponent": [], "label": "Enable OTP via Authenticator", @@ -130,8 +141,9 @@ "impactColour": "info" }, { - "cat": "Entra (AAD) Standards", "name": "standards.PWcompanionAppAllowedState", + "cat": "Entra (AAD) Standards", + "tag": ["lowimpact"], "helpText": "Sets the state of Authenticator Lite, Authenticator lite is a companion app for passwordless authentication.", "addedComponent": [ { @@ -155,8 +167,9 @@ "impactColour": "info" }, { - "cat": "Entra (AAD) Standards", "name": "standards.EnableFIDO2", + "cat": "Entra (AAD) Standards", + "tag": ["lowimpact"], "helpText": "Enables the FIDO2 authenticationMethod for the tenant", "addedComponent": [], "label": "Enable FIDO2 capabilities", @@ -164,8 +177,9 @@ "impactColour": "info" }, { - "cat": "Entra (AAD) Standards", "name": "standards.EnableHardwareOAuth", + "cat": "Entra (AAD) Standards", + "tag": ["lowimpact"], "helpText": "Enables the HardwareOath authenticationMethod for the tenant. This allows you to use hardware tokens for generating 6 digit MFA codes.", "addedComponent": [], "label": "Enable Hardware OAuth tokens", @@ -173,8 +187,9 @@ "impactColour": "info" }, { - "cat": "Entra (AAD) Standards", "name": "standards.allowOAuthTokens", + "cat": "Entra (AAD) Standards", + "tag": ["lowimpact"], "helpText": "Allows you to use any software OAuth token generator", "addedComponent": [], "label": "Enable OTP Software OAuth tokens", @@ -182,8 +197,9 @@ "impactColour": "info" }, { - "cat": "Entra (AAD) Standards", "name": "standards.TAP", + "cat": "Entra (AAD) Standards", + "tag": ["lowimpact"], "helpText": "Enables TAP and sets the default TAP lifetime to 1 hour. This configuration also allows you to select is a TAP is single use or multi-logon.", "addedComponent": [ { @@ -207,8 +223,9 @@ "impactColour": "info" }, { - "cat": "Entra (AAD) Standards", "name": "standards.PasswordExpireDisabled", + "cat": "Entra (AAD) Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Disables the expiration of passwords for the tenant by setting the password expiration policy to never expire for any user.", "addedComponent": [], "label": "Do not expire passwords", @@ -216,8 +233,9 @@ "impactColour": "info" }, { - "cat": "Entra (AAD) Standards", "name": "standards.DisableTenantCreation", + "cat": "Entra (AAD) Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Restricts creation of M365 tenants to the Global Administrator or Tenant Creator roles. ", "addedComponent": [], "label": "Disable M365 Tenant creation by users", @@ -225,8 +243,9 @@ "impactColour": "info" }, { - "cat": "Entra (AAD) Standards", "name": "standards.EnableAppConsentRequests", + "cat": "Entra (AAD) Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Enables App consent admin requests for the tenant via the GA role. Does not overwrite existing reviewer settings", "addedComponent": [ { @@ -240,26 +259,40 @@ "impactColour": "info" }, { + "name": "standards.NudgeMFA", "cat": "Entra (AAD) Standards", - "name": "standards.NudgeMFA.enable", - "helpText": "Enables registration campaign for the tenant", - "addedComponent": [], - "label": "Request to setup Authenticator if not setup yet", - "impact": "Low Impact", - "impactColour": "info" - }, - { - "cat": "Entra (AAD) Standards", - "name": "standards.NudgeMFA.disable", - "helpText": "Disables registration campaign for the tenant", - "addedComponent": [], - "label": "Disables the request to setup Authenticator if setup", + "tag": ["lowimpact"], + "helpText": "Sets the state of the registration campaign for the tenant", + "addedComponent": [ + { + "type": "Select", + "label": "Select value", + "name": "standards.NudgeMFA.state", + "values": [ + { + "label": "Enabled", + "value": "enabled" + }, + { + "label": "Disabled", + "value": "disabled" + } + ] + }, + { + "type": "number", + "name": "standards.NudgeMFA.snoozeDurationInDays", + "label": "Number of days to allow users to skip registering Authenticator (0-14, default is 1)" + } + ], + "label": "Sets the state for the request to setup Authenticator", "impact": "Low Impact", "impactColour": "info" }, { - "cat": "Entra (AAD) Standards", "name": "standards.DisableM365GroupUsers", + "cat": "Entra (AAD) Standards", + "tag": ["lowimpact"], "helpText": "Restricts M365 group creation to certain admin roles. This disables the ability to create Teams, Sharepoint sites, Planner, etc", "addedComponent": [], "label": "Disable M365 Group creation by users", @@ -267,8 +300,9 @@ "impactColour": "info" }, { - "cat": "Entra (AAD) Standards", "name": "standards.DisableSecurityGroupUsers", + "cat": "Entra (AAD) Standards", + "tag": ["mediumimpact"], "helpText": "Completely disables the creation of security groups by users. This also breaks the ability to manage groups themselves, or create Teams", "addedComponent": [], "label": "Disable Security Group creation by users", @@ -276,17 +310,19 @@ "impactColour": "warning" }, { - "cat": "Entra (AAD) Standards", "name": "standards.LegacyMFACleanup", - "helpText": "Removes legacy Per-User MFA if the tenant has Security Defaults or an All Users Conditional Access rule enabled.", + "cat": "Entra (AAD) Standards", + "tag": ["mediumimpact"], + "helpText": "This standard currently does not function and can be safely disabled", "addedComponent": [], "label": "Remove Legacy MFA if SD or CA is active", "impact": "Medium Impact", "impactColour": "warning" }, { - "cat": "Entra (AAD) Standards", "name": "standards.DisableSelfServiceLicenses", + "cat": "Entra (AAD) Standards", + "tag": ["mediumimpact"], "helpText": "This standard currently does not function and can be safely disabled", "addedComponent": [], "label": "Disable Self Service Licensing", @@ -294,8 +330,9 @@ "impactColour": "warning" }, { - "cat": "Entra (AAD) Standards", "name": "standards.DisableGuests", + "cat": "Entra (AAD) Standards", + "tag": ["mediumimpact"], "helpText": "Blocks login for guest users that have not logged in for 90 days", "addedComponent": [], "label": "Disable Guest accounts that have not logged on for 90 days", @@ -303,8 +340,9 @@ "impactColour": "warning" }, { - "cat": "Entra (AAD) Standards", "name": "standards.OauthConsent", + "cat": "Entra (AAD) Standards", + "tag": ["mediumimpact", "CIS"], "helpText": "Disables users from being able to consent to applications, except for those specified in the field below", "addedComponent": [ { @@ -318,21 +356,18 @@ "impactColour": "warning" }, { - "cat": "Entra (AAD) Standards", "name": "standards.OauthConsentLowSec", + "cat": "Entra (AAD) Standards", + "tag": ["mediumimpact"], "helpText": "Sets the default oauth consent level so users can consent to applications that have low risks.", "label": "Allow users to consent to applications with low security risk (Prevent OAuth phishing. Lower impact, less secure)", "impact": "Medium Impact", "impactColour": "warning" }, { - "cat": "Entra (AAD) Standards", "name": "standards.UndoOauth", - "disabledFeatures": { - "report": true, - "warn": true, - "remediate": false - }, + "cat": "Entra (AAD) Standards", + "tag": ["highimpact"], "helpText": "Disables App consent and set to Allow user consent for apps", "addedComponent": [], "label": "Undo App Consent Standard", @@ -340,8 +375,9 @@ "impactColour": "danger" }, { - "cat": "Entra (AAD) Standards", "name": "standards.SecurityDefaults", + "cat": "Entra (AAD) Standards", + "tag": ["highimpact"], "helpText": "Enables security defaults for the tenant, for newer tenants this is enabled by default. Do not enable this feature if you use Conditional Access.", "addedComponent": [], "label": "Enable Security Defaults", @@ -349,8 +385,9 @@ "impactColour": "danger" }, { - "cat": "Entra (AAD) Standards", "name": "standards.DisableSMS", + "cat": "Entra (AAD) Standards", + "tag": ["highimpact"], "helpText": "This blocks users from using SMS as an MFA method. If a user only has SMS as a MFA method, they will be unable to login.", "addedComponent": [], "label": "Disables SMS as an MFA method", @@ -358,8 +395,9 @@ "impactColour": "danger" }, { - "cat": "Entra (AAD) Standards", "name": "standards.DisableVoice", + "cat": "Entra (AAD) Standards", + "tag": ["highimpact"], "helpText": "This blocks users from using Voice call as an MFA method. If a user only has Voice as a MFA method, they will be unable to login.", "addedComponent": [], "label": "Disables Voice call as an MFA method", @@ -367,8 +405,9 @@ "impactColour": "danger" }, { - "cat": "Entra (AAD) Standards", "name": "standards.DisableEmail", + "cat": "Entra (AAD) Standards", + "tag": ["highimpact"], "helpText": "This blocks users from using email as an MFA method. This disables the email OTP option for guest users, and instead promts them to create a Microsoft account.", "addedComponent": [], "label": "Disables Email as an MFA method", @@ -376,8 +415,9 @@ "impactColour": "danger" }, { - "cat": "Entra (AAD) Standards", "name": "standards.Disablex509Certificate", + "cat": "Entra (AAD) Standards", + "tag": ["highimpact"], "helpText": "This blocks users from using Certificates as an MFA method.", "addedComponent": [], "label": "Disables Certificates as an MFA method", @@ -387,6 +427,7 @@ { "name": "standards.OutBoundSpamAlert", "cat": "Exchange Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Set the Outbound Spam Alert e-mail address", "addedComponent": [ { @@ -399,9 +440,20 @@ "impact": "Low Impact", "impactColour": "info" }, + { + "name": "standards.MessageExpiration", + "cat": "Exchange Standards", + "tag": ["lowimpact"], + "helpText": "Sets the transport message configuration to timeout a message at 12 hours.", + "addedComponent": [], + "label": "Lower Transport Message Expiration to 12 hours", + "impact": "Low Impact", + "impactColour": "info" + }, { "name": "standards.AutoExpandArchive", "cat": "Exchange Standards", + "tag": ["lowimpact"], "helpText": "Enables auto-expanding archives for the tenant", "addedComponent": [], "label": "Enable Auto-expanding archives", @@ -411,6 +463,7 @@ { "name": "standards.EnableOnlineArchiving", "cat": "Exchange Standards", + "tag": ["lowimpact"], "helpText": "Enables the In-Place Online Archive for all UserMailboxes with a valid license.", "addedComponent": [], "label": "Enable Online Archive for all users", @@ -420,6 +473,7 @@ { "name": "standards.SpoofWarn", "cat": "Exchange Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Adds or removes indicators to e-mail messages received from external senders in Outlook. Works on all Outlook clients/OWA", "addedComponent": [ { @@ -445,6 +499,7 @@ { "name": "standards.EnableMailTips", "cat": "Exchange Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Enables all MailTips in Outlook. MailTips are the notifications Outlook and Outlook on the web shows when an email you create, meets some requirements", "addedComponent": [ { @@ -461,6 +516,7 @@ { "name": "standards.DisableViva", "cat": "Exchange Standards", + "tag": ["lowimpact"], "helpText": "Disables the daily viva reports for all users.", "addedComponent": [], "label": "Disable daily Insight/Viva reports", @@ -470,6 +526,7 @@ { "name": "standards.RotateDKIM", "cat": "Exchange Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Rotate DKIM keys that are 1024 bit to 2048 bit", "addedComponent": [], "label": "Rotate DKIM keys that are 1024 bit to 2048 bit", @@ -479,6 +536,7 @@ { "name": "standards.AddDKIM", "cat": "Exchange Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Enables DKIM for all domains that currently support it", "addedComponent": [], "label": "Enables DKIM for all domains that currently support it", @@ -488,7 +546,8 @@ { "name": "standards.EnableMailboxAuditing", "cat": "Exchange Standards", - "helpText": "Enables Mailbox auditing for all mailboxes and on tenant level. By default Microsoft does not enable mailbox auditing for Resource Mailboxes, Public Folder Mailboxes and DiscoverySearch Mailboxes. Unified Audit Log needs to be enabled for this standard to function.", + "tag": ["lowimpact", "CIS"], + "helpText": "Enables Mailbox auditing for all mailboxes and on tenant level. Disables audit bypass on all mailboxes. Unified Audit Log needs to be enabled for this standard to function.", "addedComponent": [], "label": "Enable Mailbox auditing", "impact": "Low Impact", @@ -497,6 +556,7 @@ { "name": "standards.SendReceiveLimitTenant", "cat": "Exchange Standards", + "tag": ["lowimpact"], "helpText": "Sets the Send and Receive limits for new users. Valid values are 1MB to 150MB", "addedComponent": [ { @@ -517,6 +577,7 @@ { "name": "standards.calDefault", "cat": "Exchange Standards", + "tag": ["lowimpact"], "helpText": "Sets the default sharing level for the default calendar, for all users", "disabledFeatures": { "report": true, @@ -567,6 +628,7 @@ { "name": "standards.DisableExternalCalendarSharing", "cat": "Exchange Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Disables the ability for users to share their calendar with external users. Only for the default policy, so exclusions can be made if needed.", "addedComponent": [], "label": "Disable external calendar sharing", @@ -576,6 +638,7 @@ { "name": "standards.DisableAdditionalStorageProviders", "cat": "Exchange Standards", + "tag": ["lowimpact", "CIS"], "helpText": "Disables the ability for users to open files in Outlook on the Web, from other providers such as Box, Dropbox, Facebook, Google Drive, OneDrive Personal, etc.", "addedComponent": [], "label": "Disable additional storage providers in OWA", @@ -585,6 +648,7 @@ { "name": "standards.DisableOutlookAddins", "cat": "Exchange Standards", + "tag": ["mediumimpact", "CIS"], "helpText": "Disables the ability for users to install add-ins in Outlook. This is to prevent users from installing malicious add-ins.", "addedComponent": [], "label": "Disable users from installing add-ins in Outlook", @@ -594,13 +658,14 @@ { "name": "standards.SafeSendersDisable", "cat": "Exchange Standards", + "tag": ["mediumimpact"], + "helpText": "Loops through all users and removes the Safe Senders list. This is to prevent SPF bypass attacks, as the Safe Senders list is not checked by SPF.", + "addedComponent": [], "disabledFeatures": { "report": true, "warn": true, "remediate": false }, - "helpText": "Loops through all users and removes the Safe Senders list. This is to prevent SPF bypass attacks, as the Safe Senders list is not checked by SPF.", - "addedComponent": [], "label": "Remove Safe Senders to prevent SPF bypass", "impact": "Medium Impact", "impactColour": "warning" @@ -608,6 +673,7 @@ { "name": "standards.DelegateSentItems", "cat": "Exchange Standards", + "tag": ["mediumimpact"], "helpText": "Sets emails sent as and on behalf of shared mailboxes to also be stored in the shared mailbox sent items folder", "addedComponent": [], "label": "Set mailbox Sent Items delegation (Sent items for shared mailboxes)", @@ -617,6 +683,7 @@ { "name": "standards.SendFromAlias", "cat": "Exchange Standards", + "tag": ["mediumimpact"], "helpText": "Enables the ability for users to send from their alias addresses.", "addedComponent": [], "label": "Allow users to send from their alias addresses", @@ -624,26 +691,35 @@ "impactColour": "warning" }, { - "name": "standards.UserSubmissions.enable", - "cat": "Exchange Standards", - "helpText": "Enables the spam submission button in Outlook", - "addedComponent": [], - "label": "Enable the built-in Report button in Outlook", - "impact": "Medium Impact", - "impactColour": "warning" - }, - { - "name": "standards.UserSubmissions.disable", + "name": "standards.UserSubmissions", "cat": "Exchange Standards", - "helpText": "Disables the spam submission button in Outlook", - "addedComponent": [], - "label": "Disable the built-in Report button in Outlook", + "tag": ["mediumimpact"], + "helpText": "Set the state of the spam submission button in Outlook", + "addedComponent": [ + { + "type": "Select", + "label": "Select value", + "name": "standards.UserSubmissions.state", + "values": [ + { + "label": "Enabled", + "value": "enable" + }, + { + "label": "Disabled", + "value": "disable" + } + ] + } + ], + "label": "Set the state of the built-in Report button in Outlook", "impact": "Medium Impact", "impactColour": "warning" }, { "name": "standards.DisableSharedMailbox", "cat": "Exchange Standards", + "tag": ["mediumimpact", "CIS"], "helpText": "Blocks login for all accounts that are marked as a shared mailbox. This is Microsoft best practice to prevent direct logons to shared mailboxes.", "addedComponent": [], "label": "Disable Shared Mailbox AAD accounts", @@ -653,10 +729,11 @@ { "name": "standards.intuneDeviceRetirementDays", "cat": "Intune Standards", + "tag": ["lowimpact"], "helpText": "A value between 0 and 270 is supported. A value of 0 disables retirement, retired devices are removed from Intune after the specified number of days.", "addedComponent": [ { - "type": "input", + "type": "number", "name": "standards.intuneDeviceRetirementDays.days", "label": "Maximum days (0 equals disabled)" } @@ -668,10 +745,11 @@ { "name": "standards.intuneDeviceReg", "cat": "Intune Standards", + "tag": ["mediumimpact"], "helpText": "sets the maximum number of devices that can be registered by a user. A value of 0 disables device registration by users", "addedComponent": [ { - "type": "input", + "type": "number", "name": "standards.intuneDeviceReg.max", "label": "Maximum devices (Enter 2147483647 for unlimited.)" } @@ -683,6 +761,7 @@ { "name": "standards.intuneRequireMFA", "cat": "Intune Standards", + "tag": ["mediumimpact"], "helpText": "Requires MFA for all users to register devices with Intune. This is useful when not using Conditional Access.", "label": "Require Multifactor Authentication to register or join devices with Microsoft Entra", "impact": "Medium Impact", @@ -691,6 +770,7 @@ { "name": "standards.DeletedUserRentention", "cat": "SharePoint Standards", + "tag": ["lowimpact"], "helpText": "Sets the retention period for deleted users OneDrive to 1 year/365 days", "addedComponent": [], "label": "Retain a deleted user OneDrive for 1 year", @@ -700,6 +780,7 @@ { "name": "standards.DisableAddShortcutsToOneDrive", "cat": "SharePoint Standards", + "tag": ["mediumimpact"], "helpText": "When the feature is disabled the option Add shortcut to OneDrive will be removed. Any folders that have already been added will remain on the user's computer.", "disabledFeatures": { "report": true, @@ -714,6 +795,7 @@ { "name": "standards.DisableSharePointLegacyAuth", "cat": "SharePoint Standards", + "tag": ["mediumimpact", "CIS"], "helpText": "Disables the ability to authenticate with SharePoint using legacy authentication methods. Any applications that use legacy authentication will need to be updated to use modern authentication.", "addedComponent": [], "label": "Disable legacy basic authentication for SharePoint", @@ -723,6 +805,7 @@ { "name": "standards.sharingCapability", "cat": "SharePoint Standards", + "tag": ["highimpact", "CIS"], "helpText": "Sets the default sharing level for OneDrive and Sharepoint. This is a tenant wide setting and overrules any settings set on the site level", "addedComponent": [ { @@ -756,6 +839,7 @@ { "name": "standards.DisableReshare", "cat": "SharePoint Standards", + "tag": ["highimpact", "CIS"], "helpText": "Disables the ability for external users to share files they don't own. Sharing links can only be made for People with existing access", "addedComponent": [], "label": "Disable Resharing by External Users", @@ -765,6 +849,7 @@ { "name": "standards.DisableUserSiteCreate", "cat": "SharePoint Standards", + "tag": ["highimpact"], "helpText": "Disables users from creating new SharePoint sites", "addedComponent": [], "label": "Disable site creation by standard users", @@ -774,7 +859,8 @@ { "name": "standards.ExcludedfileExt", "cat": "SharePoint Standards", - "helpText": "Sets the file extensions that are excluded from syncing with OneDrive. These files will be blocked from upload.", + "tag": ["highimpact"], + "helpText": "Sets the file extensions that are excluded from syncing with OneDrive. These files will be blocked from upload. '*.' is automatically added to the extension and can be omitted.", "addedComponent": [ { "type": "input", @@ -789,6 +875,7 @@ { "name": "standards.disableMacSync", "cat": "SharePoint Standards", + "tag": ["highimpact"], "helpText": "Disables the ability for Mac devices to sync with OneDrive.", "addedComponent": [], "label": "Do not allow Mac devices to sync using OneDrive", @@ -798,6 +885,7 @@ { "name": "standards.unmanagedSync", "cat": "SharePoint Standards", + "tag": ["highimpact"], "helpText": "This standard will only allow devices that are AD joined, or AAD joined to sync with OneDrive", "addedComponent": [], "label": "Only allow users to sync OneDrive from AAD joined devices", diff --git a/src/routes.js b/src/routes.js index 2f41ef51dff5..4f119b52d8c8 100644 --- a/src/routes.js +++ b/src/routes.js @@ -3,6 +3,7 @@ import React from 'react' const Home = React.lazy(() => import('src/views/home/Home')) const Logs = React.lazy(() => import('src/views/cipp/Logs')) const Scheduler = React.lazy(() => import('src/views/cipp/Scheduler')) +const Statistics = React.lazy(() => import('src/views/cipp/Statistics')) const Users = React.lazy(() => import('src/views/identity/administration/Users')) const DeletedItems = React.lazy(() => import('src/views/identity/administration/Deleted')) const ViewBEC = React.lazy(() => import('src/views/identity/administration/ViewBEC')) @@ -30,6 +31,8 @@ const EditGroup = React.lazy(() => import('src/views/identity/administration/Edi const ViewGroup = React.lazy(() => import('src/views/identity/administration/ViewGroup')) const Roles = React.lazy(() => import('src/views/identity/administration/Roles')) const Devices = React.lazy(() => import('src/views/endpoint/intune/Devices')) +const allDevices = React.lazy(() => import('src/views/identity/administration/Devices')) + const PageLogOut = React.lazy(() => import('src/views/pages/LogoutRedirect/PageLogOut')) const Page404 = React.lazy(() => import('src/views/pages/page404/Page404')) @@ -242,7 +245,7 @@ const routes = [ { path: '/home', name: 'Home', component: Home }, { path: '/cipp/logs', name: 'Logs', component: Logs }, { path: '/cipp/scheduler', name: 'Scheduler', component: Scheduler }, - + { path: '/cipp/statistics', name: 'Statistics', component: Statistics }, { path: '/cipp/404', name: 'Error', component: Page404 }, { path: '/cipp/403', name: 'Error', component: Page403 }, { path: '/cipp/500', name: 'Error', component: Page500 }, @@ -258,6 +261,8 @@ const routes = [ { path: '/identity/administration/ViewBec', name: 'View BEC', component: ViewBEC }, { path: '/identity/administration', name: 'Administration' }, { path: '/identity/administration/users', name: 'Users', component: Users }, + { path: '/identity/administration/devices', name: 'Devices', component: allDevices }, + { path: '/identity/administration/groups/add', name: 'Add Group', component: AddGroup }, { path: '/identity/administration/group-templates', diff --git a/src/views/cipp/Statistics.jsx b/src/views/cipp/Statistics.jsx new file mode 100644 index 000000000000..6fba968ca726 --- /dev/null +++ b/src/views/cipp/Statistics.jsx @@ -0,0 +1,153 @@ +import React, { useState } from 'react' +import { CippPage } from 'src/components/layout' +import { + CButton, + CCard, + CCardBody, + CCardHeader, + CCardTitle, + CCol, + CCollapse, + CFormInput, + CFormLabel, + CFormSelect, + CRow, + CSpinner, +} from '@coreui/react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faChevronRight, faChevronDown } from '@fortawesome/free-solid-svg-icons' +import { CippTable } from 'src/components/tables' +import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' +import { useSelector } from 'react-redux' +import { useGenericGetRequestQuery } from 'src/store/api/app' +import { RFFCFormSelect } from 'src/components/forms' + +const columns = [ + { + name: 'Name', + selector: (row) => row['Name'], + sortable: true, + cell: cellGenericFormatter(), + exportSelector: 'Name', + minWidth: '145px', + maxWidth: '145px', + }, + { + name: 'Executions', + selector: (row) => row['ExecutionCount'], + sortable: true, + exportSelector: 'ExecutionCount', + }, + { + name: 'Total (seconds)', + selector: (row) => row['TotalSeconds'], + sortable: true, + exportSelector: 'TotalSeconds', + }, + { + name: 'Max (seconds)', + selector: (row) => row['MaxSeconds'], + sortable: true, + exportSelector: 'MaxSeconds', + }, + { + name: 'Avg (seconds)', + selector: (row) => row['AvgSeconds'], + sortable: true, + exportSelector: 'AvgSeconds', + cell: (row) => Math.round(row['AvgSeconds'] * 100) / 100, + }, +] + +const Statistics = () => { + const [visibleA, setVisibleA] = useState(false) + const [type, setType] = useState('Functions') + const [interval, setInterval] = useState('Days') + const [time, setTime] = useState(1) + const tenant = useSelector((state) => state.app.currentTenant) + const { data, isFetching, error, isSuccess } = useGenericGetRequestQuery({ + path: '/api/ListFunctionStats', + params: { + FunctionType: 'Queue', + Interval: interval, + Time: time, + TenantFilter: tenant?.defaultDomainName, + }, + }) + + return ( + <> + + + + + + Options + setVisibleA(!visibleA)} + > + + + + + + + + + + + + Report + setType(e.target.value)}> + + + + + +
+ Interval + setInterval(e.target.value)}> + + + + +
+
+ Time + setTime(e.target.value)} + defaultValue={1} + /> +
+
+
+
+
+
+
+
+
+ + + + Statistics + + + {isFetching && } + {isSuccess && ( + + )} + + + + + ) +} + +export default Statistics diff --git a/src/views/email-exchange/connectors/ConnectorList.jsx b/src/views/email-exchange/connectors/ConnectorList.jsx index e4e887adbd71..990e1d555199 100644 --- a/src/views/email-exchange/connectors/ConnectorList.jsx +++ b/src/views/email-exchange/connectors/ConnectorList.jsx @@ -18,7 +18,7 @@ const Offcanvas = (row, rowIndex, formatExtraData) => { { omit: tenantColumnSet, }, { - selector: (row) => row['UPN'], + selector: (row) => row['userPrincipalName'], name: 'User Prinicipal Name', sortable: true, - cell: (row) => CellTip(row['UPN']), - exportSelector: 'UPN', + cell: (row) => CellTip(row['userPrincipalName']), + exportSelector: 'userPrincipalName', minWidth: '200px', }, { @@ -51,41 +51,49 @@ const MailboxStatsList = () => { exportSelector: 'displayName', }, { - selector: (row) => row['MailboxType'], + selector: (row) => row[' recipientType'], name: 'Mailbox Type', sortable: true, - exportSelector: 'MailboxType', + cell: (row) => CellTip(row['recipientType']), + exportSelector: 'recipientType', }, { - selector: (row) => row['LastActive'], + selector: (row) => row['lastActivityDate'], name: 'Last Active', sortable: true, - exportSelector: 'LastActive', + exportSelector: 'lastActivityDate', }, { - selector: (row) => row['UsedGB'], - name: 'Used Space(GB)', + selector: (row) => (row['storageUsedInBytes'] / 1024 ** 3).toFixed(2), + name: 'Used Space (GB)', sortable: true, - exportSelector: 'UsedGB', + exportSelector: 'storageUsedInBytes', }, { - selector: (row) => row['QuotaGB'], + selector: (row) => (row['prohibitSendReceiveQuotaInBytes'] / 1024 ** 3).toFixed(2), name: 'Quota (GB)', sortable: true, exportSelector: 'QuotaGB', }, { - selector: (row) => row['ItemCount'], + selector: (row) => + Math.round((row.storageUsedInBytes / row.prohibitSendReceiveQuotaInBytes) * 100 * 10) / 10, + name: 'Quota Used(%)', + sortable: true, + exportSelector: 'QuotaUsed', + }, + { + selector: (row) => row['itemCount'], name: 'Item Count (Total)', sortable: true, - exportSelector: 'ItemCount', + exportSelector: 'itemCount', }, { - selector: (row) => row['HasArchive'], + selector: (row) => row['hasArchive'], name: 'Archiving Enabled', sortable: true, cell: cellBooleanFormatter({ colourless: true }), - exportSelector: 'HasArchive', + exportSelector: 'hasArchive', }, ] useEffect(() => { @@ -103,9 +111,13 @@ const MailboxStatsList = () => { datatable={{ keyField: 'id', reportName: `${tenant?.defaultDomainName}-MailboxStatistics-List`, - path: '/api/ListMailboxStatistics', + path: '/api/ListGraphRequest', + params: { + TenantFilter: tenant?.defaultDomainName, + Endpoint: "reports/getMailboxUsageDetail(period='D7')", + $format: 'application/json', + }, columns, - params: { TenantFilter: tenant?.defaultDomainName }, tableProps: { conditionalRowStyles: conditionalRowStyles, }, diff --git a/src/views/email-exchange/spamfilter/Spamfilter.jsx b/src/views/email-exchange/spamfilter/Spamfilter.jsx index 75757fe835c6..0ae3082db8a3 100644 --- a/src/views/email-exchange/spamfilter/Spamfilter.jsx +++ b/src/views/email-exchange/spamfilter/Spamfilter.jsx @@ -18,7 +18,7 @@ const Offcanvas = (row, rowIndex, formatExtraData) => { { { { MustChangePass: values.MustChangePass, tenantID: tenantDomain, addedAttributes: values.addedAttributes, + setManager: values.setManager, ...values.license, } //window.alert(JSON.stringify(shippedValues)) @@ -361,6 +362,18 @@ const AddUser = () => { + + ({ + value: user.id, + name: user.displayName, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="setManager" + /> + {usersError && Failed to load list of users} + { { + const [tenantColumnSet, setTenantColumn] = useState(true) + const tenant = useSelector((state) => state.app.currentTenant) + const Offcanvas = (row, rowIndex, formatExtraData) => { + const tenant = useSelector((state) => state.app.currentTenant) + const [ocVisible, setOCVisible] = useState(false) + const editLink = `/identity/administration/groups/edit?groupId=${row.id}&tenantDomain=${tenant.defaultDomainName}` + return ( + <> + setOCVisible(true)}> + + + setOCVisible(false)} + /> + + ) + } + const columns = [ + { + name: 'Tenant', + selector: (row) => row['Tenant'], + sortable: true, + cell: (row) => CellTip(row['Tenant']), + exportSelector: 'Tenant', + omit: tenantColumnSet, + }, + { + name: 'Retrieval Status', + selector: (row) => row['CippStatus'], + sortable: true, + cell: (row) => CellTip(row['CippStatus']), + exportSelector: 'CippStatus', + omit: tenantColumnSet, + }, + { + selector: (row) => row['displayName'], + name: 'Display Name', + sortable: true, + cell: (row) => CellTip(row['displayName']), + exportSelector: 'displayName', + }, + { + selector: (row) => row['deviceOwnership'], + name: 'Device Ownership', + sortable: true, + cell: (row) => CellTip(row['deviceOwnership']), + exportSelector: 'recipientType', + }, + { + selector: (row) => row['enrollmentType'], + name: 'Enrollment Type', + sortable: true, + exportSelector: 'enrollmentType', + }, + { + selector: (row) => row['manufacturer'], + name: 'Manufacturer', + sortable: true, + exportSelector: 'manufacturer', + }, + { + selector: (row) => row['model'], + name: 'Model', + sortable: true, + exportSelector: 'model', + }, + { + selector: (row) => row['operatingSystem'], + name: 'OS', + sortable: true, + exportSelector: 'operatingSystem', + }, + { + selector: (row) => row['operatingSystemVersion'], + name: 'Version', + sortable: true, + exportSelector: 'operatingSystemVersion', + }, + { + selector: (row) => row['profileType'], + name: 'Profile Type', + sortable: true, + exportSelector: 'profileType', + }, + { + name: 'Actions', + cell: Offcanvas, + maxWidth: '20px', + }, + ] + useEffect(() => { + if (tenant.defaultDomainName === 'AllTenants') { + setTenantColumn(false) + } + if (tenant.defaultDomainName !== 'AllTenants') { + setTenantColumn(true) + } + }, [tenant.defaultDomainName, tenantColumnSet]) + return ( + + ) +} + +export default DevicesList diff --git a/src/views/identity/administration/EditUser.jsx b/src/views/identity/administration/EditUser.jsx index 4acf76e7a5e4..cf2efdc67d66 100644 --- a/src/views/identity/administration/EditUser.jsx +++ b/src/views/identity/administration/EditUser.jsx @@ -106,6 +106,7 @@ const EditUser = () => { tenantID: tenantDomain, mustchangepass: values.RequirePasswordChange, addedAttributes: values.addedAttributes, + setManager: values.setManager, ...(values.licenses ? values.license : ''), } // window.alert(JSON.stringify(shippedValues)) @@ -425,6 +426,19 @@ const EditUser = () => { + + ({ + value: user.id, + name: user.displayName, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="setManager" + /> + {usersError && Failed to load list of users} + { }, modalUrl: `/api/ExecOneDriveShortCut`, modalDropdown: { - url: `/api/listSites?TenantFilter=${tenant.defaultDomainName}&type=SharePointSiteUsage`, + url: `/api/listSites?TenantFilter=${tenant.defaultDomainName}&type=SharePointSiteUsage&URLOnly=true`, labelField: 'URL', valueField: 'URL', }, @@ -551,6 +551,58 @@ const Users = (row) => { valueField: 'URL', }, }, + { + label: 'Add to group', + color: 'info', + modal: true, + modalType: 'POST', + modalBody: { + username: '!userPrincipalName', + userid: '!id', + TenantId: tenant.defaultDomainName, + Addmember: { + value: '!userPrincipalName', + }, + }, + modalUrl: `/api/EditGroup`, + modalMessage: 'Select the group to add', + modalDropdown: { + url: `/api/listGroups?TenantFilter=${tenant.defaultDomainName}`, + labelField: 'displayName', + valueField: 'id', + addedField: { + groupId: 'id', + groupType: 'calculatedGroupType', + groupName: 'displayName', + }, + }, + }, + { + label: 'Remove from group', + color: 'info', + modal: true, + modalType: 'POST', + modalBody: { + username: '!userPrincipalName', + userid: '!id', + TenantId: tenant.defaultDomainName, + RemoveMember: { + value: '!userPrincipalName', + }, + }, + modalUrl: `/api/EditGroup`, + modalMessage: 'Select the group to remove', + modalDropdown: { + url: `/api/listGroups?TenantFilter=${tenant.defaultDomainName}`, + labelField: 'displayName', + valueField: 'id', + addedField: { + groupId: 'id', + groupType: 'calculatedGroupType', + groupName: 'displayName', + }, + }, + }, { label: 'Set Out of Office', color: 'info', diff --git a/src/views/identity/reports/MFAReport.jsx b/src/views/identity/reports/MFAReport.jsx index 5dbae89bb4ad..70ece62c2b4e 100644 --- a/src/views/identity/reports/MFAReport.jsx +++ b/src/views/identity/reports/MFAReport.jsx @@ -25,12 +25,6 @@ const columns = [ cell: cellBooleanFormatter({ colourless: true }), exportSelector: 'isLicensed', }, - { - selector: (row) => row['PerUser'], - name: 'Per user MFA Status', - sortable: true, - exportSelector: 'PerUser', - }, { selector: (row) => row['MFARegistration'], name: 'Registered for Conditional MFA', @@ -38,6 +32,13 @@ const columns = [ cell: cellBooleanFormatter(), exportSelector: 'MFARegistration', }, + { + selector: (row) => row['CoveredBySD'], + name: 'Enforced via Security Defaults', + sortable: true, + cell: cellBooleanFormatter({ colourless: true }), + exportSelector: 'CoveredBySD', + }, { selector: (row) => row['CoveredByCA'], name: 'Enforced via Conditional Access', @@ -46,11 +47,10 @@ const columns = [ exportSelector: 'CoveredByCA', }, { - selector: (row) => row['CoveredBySD'], - name: 'Enforced via Security Defaults', + selector: (row) => row['PerUser'], + name: 'Per user MFA Status', sortable: true, - cell: cellBooleanFormatter({ colourless: true }), - exportSelector: 'CoveredBySD', + exportSelector: 'PerUser', }, ] @@ -134,7 +134,17 @@ const MFAList = () => { datatable={{ filterlist: [ { filterName: 'Enabled users', filter: '"accountEnabled":true' }, + { filterName: 'Non-guest users', filter: 'Complex: UPN notlike #EXT#' }, { filterName: 'Licensed users', filter: 'Complex: IsLicensed eq true' }, + { + filterName: 'Enabled, licensed non-guest users missing MFA', + filter: + 'Complex: UPN notlike #EXT#; IsLicensed eq true; accountEnabled eq true; MFARegistration eq false', + }, + { + filterName: 'No MFA methods registered', + filter: 'Complex: MFARegistration eq false', + }, ], columns: tenant.defaultDomainName === 'AllTenants' ? Altcolumns : columns, path: '/api/ListMFAUsers', diff --git a/src/views/teams-share/onedrive/OneDriveList.jsx b/src/views/teams-share/onedrive/OneDriveList.jsx index b4114ab96c06..cacd7d39d0fc 100644 --- a/src/views/teams-share/onedrive/OneDriveList.jsx +++ b/src/views/teams-share/onedrive/OneDriveList.jsx @@ -19,7 +19,7 @@ const OneDriveList = () => { { sortable: true, exportSelector: 'Allocated', }, + { + selector: (row) => Math.round((row.UsedGB / row.Allocated) * 100 * 10) / 10, + name: 'Quota Used(%)', + sortable: true, + exportSelector: 'QuotaUsed', + }, { name: 'URL', selector: (row) => row['url'], diff --git a/src/views/teams-share/sharepoint/SharepointList.jsx b/src/views/teams-share/sharepoint/SharepointList.jsx index 46e793ba25d9..e4ba2d74db6f 100644 --- a/src/views/teams-share/sharepoint/SharepointList.jsx +++ b/src/views/teams-share/sharepoint/SharepointList.jsx @@ -20,7 +20,7 @@ const SharepointList = () => { { const tenant = useSelector((state) => state.app.currentTenant) @@ -39,6 +40,8 @@ const GraphExplorer = () => { const [alertVisible, setAlertVisible] = useState() const [random, setRandom] = useState('') const [random2, setRandom2] = useState('') + const [random3, setRandom3] = useState('') + const [ocVisible, setOCVisible] = useState(false) const [searchNow, setSearchNow] = useState(false) const [visibleA, setVisibleA] = useState(true) const handleSubmit = async (values) => { @@ -48,6 +51,7 @@ const GraphExplorer = () => { } const [execGraphRequest, graphrequest] = useLazyGenericGetRequestQuery() const [execPostRequest, postResults] = useLazyGenericPostRequestQuery() + const [execPropRequest, availableProperties] = useLazyGenericGetRequestQuery() const { data: customPresets = [], isFetching: presetsIsFetching, @@ -55,6 +59,22 @@ const GraphExplorer = () => { } = useGenericGetRequestQuery({ path: '/api/ListGraphExplorerPresets', params: { random2 } }) const QueryColumns = { set: false, data: [] } + function endpointChange(value) { + execPropRequest({ + path: '/api/ListGraphRequest', + params: { + Endpoint: value, + ListProperties: true, + TenantFilter: tenant.defaultDomainName, + IgnoreErrors: true, + random: (Math.random() + 1).toString(36).substring(7), + }, + }) + } + const debounceEndpointChange = useMemo(() => { + return debounce(endpointChange, 1000) + }, [endpointChange]) + if (graphrequest.isSuccess) { if (graphrequest.data?.Results?.length > 0) { //set columns @@ -82,7 +102,7 @@ const GraphExplorer = () => { const handleManagePreset = ({ values, action, message }) => { var params = { action: action, - values: values, + preset: values, } ModalService.confirm({ title: 'Confirm', @@ -216,10 +236,15 @@ const GraphExplorer = () => { useEffect(() => { if (params?.endpoint) { + var select = '' + if (params?.$select) { + select = params.$select.map((p) => p.value).join(',') + } execGraphRequest({ path: 'api/ListGraphRequest', params: { ...params, + $select: select, random: random, }, }) @@ -236,15 +261,36 @@ const GraphExplorer = () => { {({ form }) => ( {(value) => { + if (field == 'endpoint') { + debounceEndpointChange(value) + } if (value?.value) { let preset = presets.filter(function (obj) { return obj.id === value.value }) if (preset[0]?.id !== '') { - if (preset[0]?.params[set]) { - onChange(preset[0]?.params[set]) + if (set == 'endpoint') { + debounceEndpointChange(preset[0]?.params[set]) + } + if (set == '$select') { + if (preset[0]?.params[set]) { + var properties = preset[0].params[set].split(',') + var selectedProps = properties.map((prop) => { + return { + label: prop, + value: prop, + } + }) + onChange(selectedProps) + } else { + onChange('') + } } else { - onChange(preset[0][set]) + if (preset[0]?.params[set]) { + onChange(preset[0]?.params[set]) + } else { + onChange(preset[0][set]) + } } } } @@ -259,6 +305,19 @@ const GraphExplorer = () => { field: PropTypes.node, set: PropTypes.string, } + + function getPresetProps(values) { + var newvals = Object.assign({}, values) + console.log(newvals) + if (newvals?.$select !== undefined && Array.isArray(newvals?.$select)) { + newvals.$select = newvals?.$select.map((p) => p.value).join(',') + } + delete newvals['reportTemplate'] + delete newvals['tenantFilter'] + delete newvals['IsShared'] + return newvals + } + console.log(graphrequest.data) return ( @@ -300,6 +359,7 @@ const GraphExplorer = () => { name="reportTemplate" label="Select a report preset" placeholder="Select a report" + retainInput={false} multi={false} values={presets.map((preset) => { return { @@ -316,7 +376,11 @@ const GraphExplorer = () => { - + + + + + {(props) => { @@ -326,6 +390,14 @@ const GraphExplorer = () => { return ( <>
+ + setOCVisible(true)} + className="me-2" + > + + + {!preset[0]?.isBuiltin && preset[0]?.id && preset[0]?.IsMyPreset && ( @@ -373,6 +445,7 @@ const GraphExplorer = () => { values: props.values, }) } + className="me-2" > @@ -394,6 +467,18 @@ const GraphExplorer = () => { {postResults.data?.Results} )} + { + setOCVisible(false) + setRandom2((Math.random() + 1).toString(36).substring(7)) + }} + /> ) }} @@ -418,6 +503,7 @@ const GraphExplorer = () => { placeholder="Enter the Graph Endpoint you'd like to run the custom report for." /> + { placeholder="Enter the filter string for the Graph query" /> - +
+ { + return { + name: prop, + value: prop, + } + }) + : [] + } + allowCreate={true} + refreshFunction={() => + setRandom3((Math.random() + 1).toString(36).substring(7)) + } + isLoading={availableProperties.isFetching} + /> +
{
{!searchNow && Execute a search to get started.} + {graphrequest.isFetching && ( +
+ Loading Data +
+ )} {graphrequest.isSuccess && QueryColumns.set && searchNow && ( diff --git a/src/views/tenant/administration/ListAlertsQueue.jsx b/src/views/tenant/administration/ListAlertsQueue.jsx index b0800dfaa7bf..23513f50a28d 100644 --- a/src/views/tenant/administration/ListAlertsQueue.jsx +++ b/src/views/tenant/administration/ListAlertsQueue.jsx @@ -93,7 +93,7 @@ const ListClassicAlerts = () => { ({ label: key, value: diff --git a/src/views/tenant/standards/ListAppliedStandards.jsx b/src/views/tenant/standards/ListAppliedStandards.jsx index 1d0979924189..61c50f1a0d99 100644 --- a/src/views/tenant/standards/ListAppliedStandards.jsx +++ b/src/views/tenant/standards/ListAppliedStandards.jsx @@ -207,10 +207,16 @@ const ApplyNewStandard = () => { (tenant) => tenant.displayName === 'AllTenants', ) - function getLabel(item) { + function getLabel(item, type) { + if (!item || !item.name) { + return '' + } const keys = item.name.split('.') let value = keys.reduce((prev, curr) => prev && prev[curr], allTenantsStandard) - return value ? `* Enabled via All Tenants` : '' + if (!value || !value[type]) { + return '' + } + return `* Enabled via All Tenants` } const groupedStandards = allStandardsList.reduce((acc, obj) => { @@ -437,7 +443,7 @@ const ApplyNewStandard = () => { name={`${obj.name}.report`} disabled={obj.disabledFeatures?.report} helpText="Report stores the data in the database to use in custom BPA reports." - sublabel={getLabel(obj)} + sublabel={getLabel(obj, 'report')} /> @@ -446,7 +452,7 @@ const ApplyNewStandard = () => { name={`${obj.name}.alert`} disabled={obj.disabledFeatures?.warn} helpText="Alert Generates an alert in the log, if remediate is enabled the log entry will also say if the remediation was successful." - sublabel={getLabel(obj)} + sublabel={getLabel(obj, 'alert')} /> @@ -455,7 +461,7 @@ const ApplyNewStandard = () => { name={`${obj.name}.remediate`} disabled={obj.disabledFeatures?.remediate} helpText={'Remediate executes the fix for standard.'} - sublabel={getLabel(obj)} + sublabel={getLabel(obj, 'remediate')} /> diff --git a/version_latest.txt b/version_latest.txt index 3bff059174b8..804440660c71 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.1.1 \ No newline at end of file +5.2.1 \ No newline at end of file