diff --git a/src/components/contentcards/CippButtonCard.jsx b/src/components/contentcards/CippButtonCard.jsx index 8e280ab067d3..a34acbd66142 100644 --- a/src/components/contentcards/CippButtonCard.jsx +++ b/src/components/contentcards/CippButtonCard.jsx @@ -22,7 +22,7 @@ export default function CippButtonCard({ {isFetching && } {children} - {CardButton} + {CardButton && {CardButton}} ) } @@ -30,7 +30,7 @@ export default function CippButtonCard({ CippButtonCard.propTypes = { title: PropTypes.string.isRequired, titleType: PropTypes.string, - CardButton: PropTypes.element.isRequired, + CardButton: PropTypes.element, children: PropTypes.element.isRequired, isFetching: PropTypes.bool, className: PropTypes.string, diff --git a/src/components/tables/CellTable.jsx b/src/components/tables/CellTable.jsx index cfcbd5403065..2186a406fe6b 100644 --- a/src/components/tables/CellTable.jsx +++ b/src/components/tables/CellTable.jsx @@ -23,6 +23,15 @@ export default function cellTable( if (columnProp === undefined || columnProp === null) { columnProp = [] } else { + var objectLength = 1 + var lengthText = 'Item' + if (columnProp instanceof Array) { + objectLength = columnProp.length + if (objectLength > 1) { + lengthText = 'Items' + } + } + if (!Array.isArray(columnProp) && typeof columnProp === 'object') { columnProp = Object.keys(columnProp).map((key) => { return { @@ -93,7 +102,7 @@ export default function cellTable( size="sm" onClick={() => handleTable({ columnProp })} > - {columnProp.length} Items + {objectLength} {lengthText} ) } diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index 04ede5a4c204..698ce52d176a 100644 --- a/src/components/tables/CippTable.jsx +++ b/src/components/tables/CippTable.jsx @@ -63,7 +63,7 @@ const FilterComponent = ({ filterText, onFilter, onClear, filterlist, onFilterPr {filterlist && filterlist.map((item, idx) => { return ( - onFilterPreset(item.filter)}> + onFilterPreset(item.filter)}> {item.filterName} ) @@ -722,7 +722,7 @@ export default function CippTable({ {dataKeys() && dataKeys().map((item, idx) => { return ( - addColumn(item)}> + addColumn(item)}> {updatedColumns.find( (o) => o.exportSelector === item && o?.omit !== true, ) && }{' '} @@ -820,7 +820,7 @@ export default function CippTable({ {actionsList.map((item, idx) => { return ( - executeselectedAction(item)}> + executeselectedAction(item)}> {item.label} ) @@ -885,6 +885,7 @@ export default function CippTable({ updatedColumns, addColumn, setGraphFilter, + isFetching, ]) const tablePageSize = useSelector((state) => state.app.tablePageSize) const [codeCopied, setCodeCopied] = useState(false) @@ -950,8 +951,8 @@ export default function CippTable({ const results = message.data?.Results const displayResults = Array.isArray(results) ? results.join(', ') : results return ( - <> -
  • + +
  • {displayResults} onCodeCopied()}>
  • - + ) })} {loopRunning && ( @@ -1008,11 +1009,12 @@ export default function CippTable({ progressPending={isFetching} progressComponent={} paginationRowsPerPageOptions={[25, 50, 100, 200, 500]} + keyField={keyField} {...rest} /> {selectedRows.length >= 1 && Selected {selectedRows.length} items} setCodeOffcanvasVisible(false)} diff --git a/src/components/utilities/CippActionsOffcanvas.jsx b/src/components/utilities/CippActionsOffcanvas.jsx index e543a511ffb1..7287af9722da 100644 --- a/src/components/utilities/CippActionsOffcanvas.jsx +++ b/src/components/utilities/CippActionsOffcanvas.jsx @@ -38,11 +38,11 @@ import { faGlobe } from '@fortawesome/free-solid-svg-icons' import { cellGenericFormatter } from '../tables/CellGenericFormat' import ReactSelect from 'react-select' -const CippOffcanvasCard = ({ action, key }) => { +const CippOffcanvasCard = ({ action }) => { const [offcanvasVisible, setOffcanvasVisible] = useState(false) return ( <> - + Report Name: {action.label} @@ -95,7 +95,6 @@ const CippOffcanvasCard = ({ action, key }) => { } CippOffcanvasCard.propTypes = { action: PropTypes.object, - key: PropTypes.object, } export default function CippActionsOffcanvas(props) { diff --git a/src/components/utilities/CippAppPermissionBuilder.jsx b/src/components/utilities/CippAppPermissionBuilder.jsx index 6f52e44f30ec..c986b67145f2 100644 --- a/src/components/utilities/CippAppPermissionBuilder.jsx +++ b/src/components/utilities/CippAppPermissionBuilder.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react' +import React, { useEffect, useRef, useState, useCallback } from 'react' import { CButton, CCallout, @@ -13,24 +13,35 @@ import { } from '@coreui/react' import { Field, Form, FormSpy } from 'react-final-form' import { RFFCFormRadioList, RFFSelectSearch } from 'src/components/forms' -import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' +import { useGenericGetRequestQuery, useLazyGenericGetRequestQuery } from 'src/store/api/app' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { TenantSelectorMultiple, ModalService, CippOffcanvas } from 'src/components/utilities' +import { + TenantSelectorMultiple, + ModalService, + CippOffcanvas, + CippCodeBlock, +} from 'src/components/utilities' import PropTypes from 'prop-types' import { OnChange } from 'react-final-form-listeners' import { useListTenantsQuery } from 'src/store/api/tenants' -import { OffcanvasListSection } from 'src/components/utilities/CippListOffcanvas' import CippButtonCard from 'src/components/contentcards/CippButtonCard' import { CippTable } from '../tables' import { Row } from 'react-bootstrap' import { cellGenericFormatter } from '../tables/CellGenericFormat' import Skeleton from 'react-loading-skeleton' -import { uniqueId } from 'lodash-es' +import CippDropzone from './CippDropzone' +import { Editor } from '@monaco-editor/react' +import { useSelector } from 'react-redux' +import { CippCallout } from '../layout' const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitting }) => { const [selectedApp, setSelectedApp] = useState([]) const [permissionsImported, setPermissionsImported] = useState(false) const [newPermissions, setNewPermissions] = useState({}) + const [importedManifest, setImportedManifest] = useState(null) + const [manifestVisible, setManifestVisible] = useState(false) + const currentTheme = useSelector((state) => state.app.currentTheme) + const [calloutMessage, setCalloutMessage] = useState(null) const { data: servicePrincipals = [], @@ -42,6 +53,8 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt path: 'api/ExecServicePrincipals', }) + const [createServicePrincipal, createResult] = useLazyGenericGetRequestQuery() + const removeServicePrincipal = (appId) => { var servicePrincipal = selectedApp.find((sp) => sp?.appId === appId) var newServicePrincipals = selectedApp.filter((sp) => sp?.appId !== appId) @@ -65,6 +78,8 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt onConfirm: () => { setSelectedApp([]) setPermissionsImported(false) + setManifestVisible(false) + setCalloutMessage('Permissions reset to default.') }, }) } @@ -78,6 +93,15 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt } } + const onCreateServicePrincipal = (appId) => { + createServicePrincipal({ + path: 'api/ExecServicePrincipals?Action=Create&AppId=' + appId, + }).then(() => { + refetchSpList() + setCalloutMessage(createResult?.data?.Results) + }) + } + const addPermissionRow = (servicePrincipal, permissionType, permission) => { var updatedPermissions = JSON.parse(JSON.stringify(newPermissions)) @@ -91,7 +115,7 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt var newPermission = [] if (currentPermission) { currentPermission.map((perm) => { - if (perm.id.lower() !== permission.value.lower()) { + if (perm.id !== permission.value) { newPermission.push(perm) } }) @@ -150,6 +174,8 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt requiredResourceAccess: [], } + var additionalPermissions = [] + selectedApp.map((sp) => { var appRoles = newPermissions?.Permissions[sp.appId]?.applicationPermissions var delegatedPermissions = newPermissions?.Permissions[sp.appId]?.delegatedPermissions @@ -157,24 +183,40 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt resourceAppId: sp.appId, resourceAccess: [], } - appRoles.map((role) => { - requiredResourceAccess.resourceAccess.push({ - id: role.id, - type: 'Role', - }) - }) - delegatedPermissions.map((perm) => { - // permission not a guid skip - if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(perm.id)) { + var additionalRequiredResourceAccess = { + resourceAppId: sp.appId, + resourceAccess: [], + } + if (appRoles) { + appRoles.map((role) => { requiredResourceAccess.resourceAccess.push({ - id: perm.id, - type: 'Scope', + id: role.id, + type: 'Role', }) - } - }) + }) + } + if (delegatedPermissions) { + delegatedPermissions.map((perm) => { + // permission not a guid skip + if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(perm.id)) { + requiredResourceAccess.resourceAccess.push({ + id: perm.id, + type: 'Scope', + }) + } else { + additionalRequiredResourceAccess.resourceAccess.push({ + id: perm.id, + type: 'Scope', + }) + } + }) + } if (requiredResourceAccess.resourceAccess.length > 0) { manifest.requiredResourceAccess.push(requiredResourceAccess) } + if (additionalRequiredResourceAccess.resourceAccess.length > 0) { + additionalPermissions.push(additionalRequiredResourceAccess) + } }) var fileName = `${appDisplayName.replace(' ', '-')}.json` @@ -186,12 +228,94 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt var url = URL.createObjectURL(blob) var a = document.createElement('a') a.href = url - a.download = `${fileName}.json` + a.download = `${fileName}` a.click() + URL.revokeObjectURL(url) + + if (additionalPermissions.length > 0) { + ModalService.confirm({ + title: 'Additional Permissions', + body: 'Some permissions are not supported in the manifest. Would you like to download them?', + confirmLabel: 'Download', + onConfirm: () => { + var additionalBlob = new Blob([JSON.stringify(additionalPermissions, null, 2)], { + type: 'application/json', + }) + var additionalUrl = URL.createObjectURL(additionalBlob) + var additionalA = document.createElement('a') + additionalA.href = additionalUrl + additionalA.download = 'AdditionalPermissions.json' + additionalA.click() + URL.revokeObjectURL(additionalUrl) + }, + }) + } } } - const importManifest = () => {} + const importManifest = () => { + var updatedPermissions = { Permissions: {} } + var manifest = importedManifest + var requiredResourceAccess = manifest.requiredResourceAccess + var selectedServicePrincipals = [] + + requiredResourceAccess.map((resourceAccess) => { + var sp = servicePrincipals?.Results?.find((sp) => sp.appId === resourceAccess.resourceAppId) + if (sp) { + var appRoles = [] + var delegatedPermissions = [] + selectedServicePrincipals.push(sp) + resourceAccess.resourceAccess.map((access) => { + if (access.type === 'Role') { + var role = sp.appRoles.find((role) => role.id === access.id) + if (role) { + appRoles.push({ + id: role.id, + value: role.value, + }) + } + } else if (access.type === 'Scope') { + var scope = sp.publishedPermissionScopes.find((scope) => scope.id === access.id) + if (scope) { + delegatedPermissions.push({ + id: scope.id, + value: scope.value, + }) + } + } + }) + updatedPermissions.Permissions[sp.appId] = { + applicationPermissions: appRoles, + delegatedPermissions: delegatedPermissions, + } + } + }) + setNewPermissions(updatedPermissions) + setSelectedApp(selectedServicePrincipals) + setImportedManifest(null) + setPermissionsImported(true) + setManifestVisible(false) + setCalloutMessage('Manifest imported successfully.') + } + + const onManifestImport = useCallback((acceptedFiles) => { + acceptedFiles.forEach((file) => { + const reader = new FileReader() + reader.onabort = () => console.log('file reading was aborted') + reader.onerror = () => console.log('file reading has failed') + reader.onload = () => { + console.log(reader.result) + try { + var manifest = JSON.parse(reader.result) + setImportedManifest(manifest) + console.log(importedManifest) + } catch { + console.log('invalid manifest') + } + } + reader.readAsText(file) + }) + }, []) useEffect(() => { try { @@ -213,7 +337,6 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt }, }, }) - setPermissionsImported(true) } else if (spSuccess && initialAppIds.length > 0 && permissionsImported == false) { var newApps = [] initialAppIds?.map((appId) => { @@ -241,7 +364,6 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt ]) const ApiPermissionRow = ({ servicePrincipal = null }) => { - const [offcanvasVisible, setOffcanvasVisible] = useState(false) return ( <> {spSuccess && servicePrincipal !== null && ( @@ -336,6 +458,7 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt
    refetchSpList()} allowCreate={true} onCreateOption={(newSp) => { - console.log(newSp) + onCreateServicePrincipal(newSp) }} placeholder="(Advanced) Select a Service Principal" /> @@ -626,7 +750,7 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt { - importManifest() + setManifestVisible(true) }} className={`circular-button`} title={'+'} @@ -636,7 +760,146 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt + { + setManifestVisible(false) + }} + addedClass="offcanvas-large" + placement="end" + > + + +

    + Import a JSON application manifest to set permissions. This will + overwrite any existing permissions. +

    +
    +
    + + + + + + {importedManifest && ( + <> + + + importManifest()}> + Import + + + + + +

    Preview

    + +
    +
    + + )} +
    + {calloutMessage && ( + + + + + {calloutMessage} + + + + )} + {newPermissions?.MissingPermissions && + newPermissions?.Type === 'Table' && + Object.keys(newPermissions?.MissingPermissions).length > 0 && ( + + + + + + + New Permissions Available + {Object.keys(newPermissions?.MissingPermissions).map((perm) => { + // translate appid to display name + var sp = servicePrincipals?.Results?.find( + (sp) => sp.appId === perm, + ) + return ( +
    + {sp?.displayName}:{' '} + {Object.keys(newPermissions?.MissingPermissions[perm]).map( + (type) => { + return ( + <> + {newPermissions?.MissingPermissions[perm][type] + .length > 0 && ( + + {type == 'applicationPermissions' + ? 'Application' + : 'Delegated'}{' '} + -{' '} + {newPermissions?.MissingPermissions[perm][type] + .map((p) => { + return p.value + }) + .join(', ')} + + )} + + ) + }, + )} +
    + ) + })} +
    + + + { + var updatedPermissions = JSON.parse( + JSON.stringify(newPermissions), + ) + Object.keys(newPermissions?.MissingPermissions).map( + (perm) => { + Object.keys( + newPermissions?.MissingPermissions[perm], + ).map((type) => { + newPermissions?.MissingPermissions[perm][type].map( + (p) => { + updatedPermissions.Permissions[perm][type].push(p) + }, + ) + }) + }, + ) + updatedPermissions.MissingPermissions = {} + setNewPermissions(updatedPermissions) + }} + className={`circular-button float-end`} + > + + + + +
    +
    +
    +
    + )} <> {selectedApp?.length > 0 && @@ -648,7 +911,10 @@ const CippAppPermissionBuilder = ({ onSubmit, currentPermissions = {}, isSubmitt {sp.displayName} - + diff --git a/src/components/utilities/CippCodeOffcanvas.jsx b/src/components/utilities/CippCodeOffcanvas.jsx index a9b5cf41a055..10920c368722 100644 --- a/src/components/utilities/CippCodeOffcanvas.jsx +++ b/src/components/utilities/CippCodeOffcanvas.jsx @@ -44,7 +44,7 @@ function CippCodeOffCanvas({ addedClass="offcanvas-large" placement="end" visible={visible} - id={row} + id={crypto.randomUUID()} hideFunction={hideFunction} > { +const CippDropzone = ({ + title, + onDrop, + dropMessage, + accept, + maxFiles = 1, + returnCard = true, + ...props +}) => { const { getRootProps, getInputProps, isFocused, isDragAccept, isDragReject } = useDropzone({ onDrop, accept: accept, maxFiles: maxFiles, }) return ( - -
    - - - {dropMessage} - -
    -
    + <> + {returnCard ? ( + +
    + + + {dropMessage} + +
    +
    + ) : ( +
    + + + {dropMessage} + +
    + )} + ) } @@ -73,6 +92,7 @@ CippDropzone.propTypes = { dropMessage: PropTypes.string, accept: PropTypes.object, maxFiles: PropTypes.number, + returnCard: PropTypes.bool, } export default CippDropzone diff --git a/src/data/alerts.json b/src/data/alerts.json index 835216740c4d..13f18265caf4 100644 --- a/src/data/alerts.json +++ b/src/data/alerts.json @@ -99,5 +99,11 @@ "name": "DeviceCompliance", "label": "Alert on device compliance issues", "recommendedRunInterval": "4h" + }, + { + "name": "HuntressRogueApps", + "label": "Alert on Huntress Rogue Apps detected", + "recommendedRunInterval": "4h", + "description": "Huntress has provided a repository of known rogue apps that are commonly used in BEC, data exfiltration and other Microsoft 365 attacks. This alert will notify you if any of these apps are detected in the selected tenant(s). For more information, see https://huntresslabs.github.io/rogueapps/." } ] diff --git a/src/views/cipp/app-settings/SettingsNotifications.jsx b/src/views/cipp/app-settings/SettingsNotifications.jsx index 781d2ac72e6a..f38bb2f1fd1f 100644 --- a/src/views/cipp/app-settings/SettingsNotifications.jsx +++ b/src/views/cipp/app-settings/SettingsNotifications.jsx @@ -64,145 +64,147 @@ export function SettingsNotifications() { } isFetching={notificationListResult.isFetching} > - {notificationListResult.isUninitialized && listNotification()} - {notificationListResult.isFetching || - (generateAlertResult.isFetching && ( - - ))} - {!notificationListResult.isFetching && notificationListResult.error && ( - Error loading data - )} - {notificationListResult.isSuccess && ( -
    true} - initialValues={{ - ...notificationListResult.data, - logsToInclude: notificationListResult.data?.logsToInclude?.map((m) => ({ - label: m, - value: m, - })), - Severity: notificationListResult.data?.Severity?.map((s) => ({ - label: s, - value: s, - })), - }} - onSubmit={onSubmit} - render={({ handleSubmit, submitting, values }) => { - return ( - - {notificationConfigResult.isFetching && ( - - Loading - - )} - {notificationConfigResult.isSuccess && !notificationConfigResult.isFetching && ( - - {notificationConfigResult.data?.Results} - - )} - {notificationConfigResult.isError && !notificationConfigResult.isFetching && ( - - Could not connect to API: {notificationConfigResult.error.message} - - )} - - - - - - - - - - - - - - - - - - - + <> + {notificationListResult.isUninitialized && listNotification()} + {notificationListResult.isFetching || + (generateAlertResult.isFetching && ( + + ))} + {!notificationListResult.isFetching && notificationListResult.error && ( + Error loading data + )} + {notificationListResult.isSuccess && ( + true} + initialValues={{ + ...notificationListResult.data, + logsToInclude: notificationListResult.data?.logsToInclude?.map((m) => ({ + label: m, + value: m, + })), + Severity: notificationListResult.data?.Severity?.map((s) => ({ + label: s, + value: s, + })), + }} + onSubmit={onSubmit} + render={({ handleSubmit, submitting, values }) => { + return ( + + {notificationConfigResult.isFetching && ( + + Loading + + )} + {notificationConfigResult.isSuccess && !notificationConfigResult.isFetching && ( + + {notificationConfigResult.data?.Results} + + )} + {notificationConfigResult.isError && !notificationConfigResult.isFetching && ( + + Could not connect to API: {notificationConfigResult.error.message} + + )} - + + + + + + + + + + + + + + + + + + + + + - - - ) - }} - /> - )} - - Use the button below to save the changes, or generate a test alert. The test alert will be - processed in a batch with other alerts - + + ) + }} + /> + )} + + Use the button below to save the changes, or generate a test alert. The test alert will + be processed in a batch with other alerts + + ) diff --git a/src/views/cipp/app-settings/SettingsPartner.jsx b/src/views/cipp/app-settings/SettingsPartner.jsx index b569b9c33c24..823844ac56b5 100644 --- a/src/views/cipp/app-settings/SettingsPartner.jsx +++ b/src/views/cipp/app-settings/SettingsPartner.jsx @@ -24,6 +24,7 @@ import React, { useEffect } from 'react' import { CippCallout } from 'src/components/layout/index.js' import { CippCodeBlock } from 'src/components/utilities' import { CellDate } from 'src/components/tables' +import Skeleton from 'react-loading-skeleton' /** * Sets the notification settings. @@ -148,17 +149,21 @@ export function SettingsPartner() { render={({ handleSubmit }) => ( <> - ({ - name: event, - value: event, - }))} - multi={true} - refreshFunction={() => webhookEvents.refetch()} - helpText="Select the events you want to receive notifications for." - /> + {webhookEvents.isSuccess ? ( + ({ + name: event, + value: event, + }))} + multi={true} + refreshFunction={() => webhookEvents.refetch()} + helpText="Select the events you want to receive notifications for." + /> + ) : ( + + )} { genericPostRequest({ path: 'api/ExecSAMAppPermissions?Action=Update', values: values, + }).then(() => { + refetchSam() }) } - const { data: samAppPermissions = [], isFetching: samAppPermissionsFetching } = - useGenericGetRequestQuery({ - path: 'api/ExecSAMAppPermissions', - }) + const { + data: samAppPermissions = [], + isFetching: samAppPermissionsFetching, + refetch: refetchSam, + } = useGenericGetRequestQuery({ + path: 'api/ExecSAMAppPermissions', + }) return ( diff --git a/src/views/cipp/app-settings/components/SettingsSAMRoles.jsx b/src/views/cipp/app-settings/components/SettingsSAMRoles.jsx index ebc8310b6abd..fdce15fb39cd 100644 --- a/src/views/cipp/app-settings/components/SettingsSAMRoles.jsx +++ b/src/views/cipp/app-settings/components/SettingsSAMRoles.jsx @@ -14,11 +14,10 @@ import { Field, Form, FormSpy } from 'react-final-form' import { RFFCFormRadioList, RFFSelectSearch } from 'src/components/forms' import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { TenantSelectorMultiple, ModalService, CippOffcanvas } from 'src/components/utilities' +import { TenantSelectorMultiple, ModalService } from 'src/components/utilities' import PropTypes from 'prop-types' import { OnChange } from 'react-final-form-listeners' import { useListTenantsQuery } from 'src/store/api/tenants' -import { OffcanvasListSection } from 'src/components/utilities/CippListOffcanvas' import CippButtonCard from 'src/components/contentcards/CippButtonCard' import GDAPRoles from 'src/data/GDAPRoles' diff --git a/src/views/email-exchange/connectors/ConnectorList.jsx b/src/views/email-exchange/connectors/ConnectorList.jsx index c18d5eeb0004..1f3829abcb1d 100644 --- a/src/views/email-exchange/connectors/ConnectorList.jsx +++ b/src/views/email-exchange/connectors/ConnectorList.jsx @@ -57,7 +57,7 @@ const Offcanvas = (row, rowIndex, formatExtraData) => { modal: true, icon: , modalUrl: `/api/RemoveExConnector?TenantFilter=${tenant.defaultDomainName}&GUID=${row.Guid}&Type=${row.cippconnectortype}`, - modalMessage: 'Are you sure you want to disable this rule?', + modalMessage: 'Are you sure you want to delete this rule?', }, ]} placement="end" diff --git a/src/views/identity/administration/DeployJITAdmin.jsx b/src/views/identity/administration/DeployJITAdmin.jsx index 81d23666fa61..146ab7d6fef1 100644 --- a/src/views/identity/administration/DeployJITAdmin.jsx +++ b/src/views/identity/administration/DeployJITAdmin.jsx @@ -47,10 +47,13 @@ const DeployJITAdmin = () => { const onSubmit = (values) => { const startTime = Math.floor(startDate.getTime() / 1000) const endTime = Math.floor(endDate.getTime() / 1000) + const shippedValues = { TenantFilter: tenantDomain, - UserId: values.UserId?.value, - UserPrincipalName: `${values.username}@${values.domain}`, + UserId: values.UserId?.value.id, + UserPrincipalName: values.username + ? `${values.username}@${values.domain}` + : values.UserId?.value.userPrincipalName, FirstName: values.FirstName, LastName: values.LastName, useraction: values.useraction, @@ -168,7 +171,7 @@ const DeployJITAdmin = () => { ({ - value: user.id, + value: { userPrincipalName: user.userPrincipalName, id: user.id }, name: `${user.displayName} <${user.userPrincipalName}>`, }))} placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} diff --git a/src/views/teams-share/teams/BusinessVoice.jsx b/src/views/teams-share/teams/BusinessVoice.jsx index 6c132806d8bf..5f2c0d32598a 100644 --- a/src/views/teams-share/teams/BusinessVoice.jsx +++ b/src/views/teams-share/teams/BusinessVoice.jsx @@ -1,68 +1,208 @@ -import React from 'react' +import React, { useState } from 'react' +import { CButton } from '@coreui/react' import { useSelector } from 'react-redux' import { CellBoolean } from 'src/components/tables' import { CippPageList } from 'src/components/layout' - -const Formatter = (cell) => CellBoolean({ cell }) -const columns = [ - { - name: 'Assigned to User', - selector: (row) => row['AssignedTo'], - sortable: true, - exportSelector: 'AssignedTo', - }, - { - name: 'Phone Number', - selector: (row) => row['TelephoneNumber'], - sortable: true, - exportSelector: 'TelephoneNumber', - }, - { - name: 'Number Type', - selector: (row) => row['NumberType'], - sortable: true, - exportSelector: 'NumberType', - }, - { - name: 'Country', - selector: (row) => row['IsoCountryCode'], - sortable: true, - exportSelector: 'IsCountryCode', - }, - { - name: 'Location', - selector: (row) => row['PlaceName'], - sortable: true, - exportSelector: 'PlaceName', - }, - { - name: 'Activation State', - selector: (row) => row['ActivationState'], - formatter: Formatter, - exportSelector: 'ActivationState', - sortable: true, - }, - { - name: 'Operator Connect', - selector: (row) => row['IsOperatorConnect'], - formatter: Formatter, - sortable: true, - exportSelector: 'IsOperatorConnect', - }, - { - name: 'Purchased on', - selector: (row) => row['AcquisitionDate'], - sortable: true, - exportSelector: 'AcquisitionDate', - }, -] +import { TitleButton } from 'src/components/buttons' +import { CippActionsOffcanvas } from 'src/components/utilities' +import { faEllipsisV } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' const BusinessVoice = () => { const tenant = useSelector((state) => state.app.currentTenant) + const Offcanvas = (row, rowIndex, formatExtraData) => { + const tenant = useSelector((state) => state.app.currentTenant) + const [ocVisible, setOCVisible] = useState(false) + return ( + <> + setOCVisible(true)}> + + + setOCVisible(false)} + /> + + ) + } + const Formatter = (cell) => CellBoolean({ cell }) + const usageFormatter = (cell) => { + if (cell.includes('UserAssignment')) { + return 'User' + } + if (cell.includes('FirstPartyAppAssignment')) { + return 'Voice App' + } + if (cell.includes('ConferenceAssignment')) { + return 'Conference' + } + return cell[0] + } + const columns = [ + { + name: 'Assigned to User', + selector: (row) => row['AssignedTo'], + sortable: true, + exportSelector: 'AssignedTo', + }, + { + name: 'Phone Number', + selector: (row) => row['TelephoneNumber'], + sortable: true, + exportSelector: 'TelephoneNumber', + }, + { + name: 'Assignment Status', + selector: (row) => row['AssignmentStatus'], + sortable: true, + exportSelector: 'AssignmentStatus', + }, + { + name: 'Number Type', + selector: (row) => row['NumberType'], + sortable: true, + exportSelector: 'NumberType', + }, + { + name: 'Licensed Usage', + selector: (row) => usageFormatter(row['AcquiredCapabilities']), + sortable: true, + exportSelector: 'AcquiredCapabilities', + }, + { + name: 'Country', + selector: (row) => row['IsoCountryCode'], + sortable: true, + exportSelector: 'IsCountryCode', + }, + { + name: 'Location', + selector: (row) => row['PlaceName'], + sortable: true, + exportSelector: 'PlaceName', + }, + { + name: 'Activation State', + selector: (row) => row['ActivationState'], + formatter: Formatter, + exportSelector: 'ActivationState', + sortable: true, + }, + { + name: 'Operator Connect', + selector: (row) => row['IsOperatorConnect'], + formatter: Formatter, + sortable: true, + exportSelector: 'IsOperatorConnect', + }, + { + name: 'Purchased on', + selector: (row) => row['AcquisitionDate'], + sortable: true, + exportSelector: 'AcquisitionDate', + }, + { + name: 'Actions', + cell: Offcanvas, + }, + ] + const titleButtons = ( +
    +
    + +
    +
    + ) return ( { const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) @@ -112,6 +113,18 @@ const AlertWizard = () => { } } + const getScriptDescription = () => { + const values = currentFormState?.values + if (values) { + const command = values.command?.value + if (command?.description) { + return HtmlParser(command.description) + } else { + return null + } + } + } + const setAuditForm = (e) => { const preset = presetValues.find((p) => p.value === e.value) setAuditFormState(preset.template) @@ -368,6 +381,16 @@ const AlertWizard = () => { render={({ handleSubmit, submitting, values }) => { return ( + {getScriptDescription() && ( + + + + + {getScriptDescription()} + + + + )} { /> +