From 34e6d99c8692ac52397c7ad532e110055874b3da Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 3 Nov 2023 13:50:23 -0400 Subject: [PATCH 1/2] Mailbox Restores --- src/_nav.js | 19 ++ .../utilities/CippActionsOffcanvas.js | 7 + src/components/utilities/SharedModal.js | 3 + src/routes.js | 14 ++ .../tools/MailboxRestoreWizard.js | 214 ++++++++++++++++++ .../email-exchange/tools/MailboxRestores.js | 170 ++++++++++++++ 6 files changed, 427 insertions(+) create mode 100644 src/views/email-exchange/tools/MailboxRestoreWizard.js create mode 100644 src/views/email-exchange/tools/MailboxRestores.js diff --git a/src/_nav.js b/src/_nav.js index 852e4334d8cb..272eb82cd2f8 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -677,6 +677,25 @@ const _nav = [ }, ], }, + { + component: CNavGroup, + name: 'Tools', + section: 'Tools', + to: '/email/tools', + icon: , + items: [ + { + component: CNavItem, + name: 'Mailbox Restore Wizard', + to: '/email/tools/mailbox-restore-wizard', + }, + { + component: CNavItem, + name: 'Mailbox Restores', + to: '/email/tools/mailbox-restores', + }, + ], + }, { component: CNavTitle, name: 'Settings', diff --git a/src/components/utilities/CippActionsOffcanvas.js b/src/components/utilities/CippActionsOffcanvas.js index 371276f49375..ce148defa5a4 100644 --- a/src/components/utilities/CippActionsOffcanvas.js +++ b/src/components/utilities/CippActionsOffcanvas.js @@ -85,6 +85,13 @@ export default function CippActionsOffcanvas(props) { title: 'Confirm', onConfirm: () => genericGetRequest({ path: modalUrl }), }) + } else if (modalType === 'codeblock') { + ModalService.open({ + data: modalBody, + componentType: 'codeblock', + title: 'Info', + size: 'lg', + }) } else { ModalService.confirm({ key: modalContent, diff --git a/src/components/utilities/SharedModal.js b/src/components/utilities/SharedModal.js index 4975c0cc17f6..26b473729a91 100644 --- a/src/components/utilities/SharedModal.js +++ b/src/components/utilities/SharedModal.js @@ -2,6 +2,7 @@ import React from 'react' import { CButton, CModal, CModalBody, CModalFooter, CModalHeader, CModalTitle } from '@coreui/react' import PropTypes from 'prop-types' import { CippTable } from 'src/components/tables' +import CippCodeBlock from 'src/components/utilities/CippCodeBlock' /** * @@ -18,6 +19,8 @@ function mapBodyComponent({ componentType, data, componentProps }) { return
{Array.isArray(data) && data.map((el, idx) =>
{el}
)}
case 'text': return String(data) + case 'codeblock': + return default: return String(data) } diff --git a/src/routes.js b/src/routes.js index d305063a4109..5be9247bfae0 100644 --- a/src/routes.js +++ b/src/routes.js @@ -225,6 +225,10 @@ const ServiceHealth = React.lazy(() => import('src/views/tenant/administration/S const EnterpriseApplications = React.lazy(() => import('src/views/tenant/administration/ListEnterpriseApps'), ) +const MailboxRestoreWizard = React.lazy(() => + import('src/views/email-exchange/tools/MailboxRestoreWizard'), +) +const MailboxRestores = React.lazy(() => import('src/views/email-exchange/tools/MailboxRestores')) const routes = [ // { path: '/', exact: true, name: 'Home' }, @@ -544,6 +548,16 @@ const routes = [ name: 'List Spamfilter Templates', component: SpamFilterTemplate, }, + { + path: '/email/tools/mailbox-restore-wizard', + name: 'Mailbox Restore Wizard', + component: MailboxRestoreWizard, + }, + { + path: '/email/tools/mailbox-restores', + name: 'Mailbox Restores', + component: MailboxRestores, + }, { path: '/email/spamfilter/add-template', name: 'Add Spamfilter Template', diff --git a/src/views/email-exchange/tools/MailboxRestoreWizard.js b/src/views/email-exchange/tools/MailboxRestoreWizard.js new file mode 100644 index 000000000000..2e8acddc59f7 --- /dev/null +++ b/src/views/email-exchange/tools/MailboxRestoreWizard.js @@ -0,0 +1,214 @@ +import React from 'react' +import { CCallout, CCol, CListGroup, CListGroupItem, CRow, CSpinner } from '@coreui/react' +import { Field, FormSpy } from 'react-final-form' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faExclamationTriangle, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons' +import { useSelector } from 'react-redux' +import { CippWizard } from 'src/components/layout' +import PropTypes from 'prop-types' +import { + RFFCFormCheck, + RFFCFormInput, + RFFCFormSelect, + RFFCFormSwitch, + RFFSelectSearch, +} from 'src/components/forms' +import { TenantSelector } from 'src/components/utilities' +import { useListUsersQuery } from 'src/store/api/users' +import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' + +const Error = ({ name }) => ( + + touched && error ? ( + + + {error} + + ) : null + } + /> +) + +Error.propTypes = { + name: PropTypes.string.isRequired, +} + +const MailboxRestoreWizard = () => { + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) + const { + data: sourceMailboxes = [], + isFetching: sMailboxesIsFetching, + error: sMailboxError, + } = useGenericGetRequestQuery({ + path: '/api/ListMailboxes', + params: { TenantFilter: tenantDomain, SoftDeletedMailbox: true }, + }) + const { + data: targetMailboxes = [], + isFetching: tMailboxesIsFetching, + error: tMailboxError, + } = useGenericGetRequestQuery({ + path: '/api/ListMailboxes', + params: { TenantFilter: tenantDomain }, + }) + const currentSettings = useSelector((state) => state.app) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + + const handleSubmit = async (values) => { + const shippedValues = { + TenantFilter: tenantDomain, + RequestName: values.RequestName, + SourceMailbox: values.SourceMailbox.value, + TargetMailbox: values.TargetMailbox.value, + BadItemLimit: values.BadItemLimit, + LargeItemLimit: values.LargeItemLimit, + AcceptLargeDataLoss: values.AcceptLargeDataLoss, + } + + //alert(JSON.stringify(values, null, 2)) + genericPostRequest({ path: '/api/ExecMailboxRestore', values: shippedValues }) + } + + return ( + + +
+

Step 1

+
Choose a tenant
+
+
+ {(props) => } +
+
+ +
+

Step 2

+
Select a soft deleted mailbox to restore.
+
+
+
+ ({ + value: mbx.ExchangeGuid, + name: `${mbx.displayName} <${mbx.UPN}>`, + }))} + placeholder={!sMailboxesIsFetching ? 'Select mailbox' : 'Loading...'} + name="SourceMailbox" + /> + {sMailboxError && Failed to load source mailboxes} +
+
+
+ +
+

Step 2

+
Select a mailbox to restore to.
+
+
+
+ ({ + value: mbx.ExchangeGuid, + name: `${mbx.displayName} <${mbx.UPN}>`, + }))} + placeholder={!tMailboxesIsFetching ? 'Select mailbox' : 'Loading...'} + name="TargetMailbox" + /> + {sMailboxError && Failed to load source mailboxes} +
+
+
+ +
+

Step 3

+
Enter Restore Request Options
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+

Step 4

+
Confirm and apply
+
+
+
+ {postResults.isFetching && ( + + Loading + + )} + {postResults.isSuccess && ( + + {postResults.data.Results.map((message, idx) => { + return
  • {message}
  • + })} +
    + )} + {!postResults.isSuccess && ( + + {(props) => ( + <> + + + + +
    Selected Tenant:
    + {tenantDomain} +
    + +
    Source Mailbox:
    + {props.values.SourceMailbox.label} +
    + +
    Target Mailbox:
    + {props.values.TargetMailbox.label} +
    +
    +
    +
    + + )} +
    + )} +
    +
    +
    +
    + ) +} + +export default MailboxRestoreWizard diff --git a/src/views/email-exchange/tools/MailboxRestores.js b/src/views/email-exchange/tools/MailboxRestores.js new file mode 100644 index 000000000000..05459fa1e08d --- /dev/null +++ b/src/views/email-exchange/tools/MailboxRestores.js @@ -0,0 +1,170 @@ +import { CSpinner, CButton } from '@coreui/react' +import { + faEllipsisV, + faTrashAlt, + faExclamationTriangle, + faCheck, +} from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import React, { useState } from 'react' +import { useSelector } from 'react-redux' +import { CippPageList } from 'src/components/layout' +import { + CellDelegatedPrivilege, + cellDateFormatter, + cellNullTextFormatter, +} from 'src/components/tables' +import { CippActionsOffcanvas } from 'src/components/utilities' +import GDAPRoles from 'src/data/GDAPRoles' +import { useLazyGenericGetRequestQuery } from 'src/store/api/app' +import { ModalService } from 'src/components/utilities' +import { constants } from 'buffer' +import Skeleton from 'react-loading-skeleton' +import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' + +const Actions = (row, rowIndex, formatExtraData) => { + const [ocVisible, setOCVisible] = useState(false) + const [getMailboxRestoreStats, mailboxRestoreStats] = useLazyGenericGetRequestQuery() + const tenant = useSelector((state) => state.app.currentTenant) + + function statProperty(mailboxRestoreStats, propertyName) { + return ( + <> + {mailboxRestoreStats.isFetching && } + {!mailboxRestoreStats.isFetching && + mailboxRestoreStats.isSuccess && + (mailboxRestoreStats?.data[0][propertyName]?.toString() ?? ' ')} + + ) + } + + function loadOffCanvasDetails(id) { + setOCVisible(true) + getMailboxRestoreStats({ + path: 'api/ListMailboxRestores', + params: { + TenantFilter: tenant.defaultDomainName, + Identity: id, + Statistics: true, + IncludeReport: true, + }, + }) + } + var extendedInfo = [ + { label: 'Status', value: statProperty(mailboxRestoreStats, 'Status') }, + { label: 'Status Detail', value: statProperty(mailboxRestoreStats, 'StatusDetail') }, + { label: 'Sync Stage', value: statProperty(mailboxRestoreStats, 'SyncStage') }, + { label: 'Data Consistency', value: statProperty(mailboxRestoreStats, 'DataConsistencyScore') }, + { + label: 'Estimated Transfer', + value: statProperty(mailboxRestoreStats, 'EstimatedTransferSize'), + }, + { + label: 'Bytes Transferred', + value: statProperty(mailboxRestoreStats, 'BytesTransferred'), + }, + { + label: 'Percent Complete', + value: statProperty(mailboxRestoreStats, 'PercentComplete'), + }, + { + label: 'Estimated Item Count', + value: statProperty(mailboxRestoreStats, 'EstimatedTransferItemCount'), + }, + { + label: 'Transferred Items', + value: statProperty(mailboxRestoreStats, 'ItemsTransferred'), + }, + ] + + return ( + <> + loadOffCanvasDetails(row.Identity)}> + + + setOCVisible(false)} + /> + + ) +} + +const MailboxRestores = () => { + const tenant = useSelector((state) => state.app.currentTenant) + const columns = [ + { + name: 'Name', + selector: (row) => row['Name'], + sortable: true, + exportSelector: 'Name', + cell: cellGenericFormatter(), + }, + { + name: 'Status', + selector: (row) => row['Status'], + sortable: true, + exportSelector: 'Status', + cell: cellGenericFormatter(), + }, + { + name: 'Target Mailbox', + selector: (row) => row['TargetMailbox'], + sortable: true, + exportSelector: 'TargetMailbox', + cell: cellGenericFormatter(), + }, + { + name: 'Created', + selector: (row) => row['WhenCreated'], + sortable: true, + exportSelector: 'WhenCreated', + cell: cellDateFormatter({ format: 'short' }), + }, + { + name: 'Changed', + selector: (row) => row['WhenChanged'], + sortable: true, + exportSelector: 'WhenChanged', + cell: cellDateFormatter({ format: 'short' }), + }, + { + name: 'Actions', + cell: Actions, + maxWidth: '80px', + }, + ] + return ( +
    + +
    + ) +} + +export default MailboxRestores From fd8d3930d9d2c3f734c9d433efc0a59792e9301e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 3 Nov 2023 13:59:46 -0400 Subject: [PATCH 2/2] Update SharedModal.js --- src/components/utilities/SharedModal.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/utilities/SharedModal.js b/src/components/utilities/SharedModal.js index 26b473729a91..fb8501fc93ca 100644 --- a/src/components/utilities/SharedModal.js +++ b/src/components/utilities/SharedModal.js @@ -20,7 +20,9 @@ function mapBodyComponent({ componentType, data, componentProps }) { case 'text': return String(data) case 'codeblock': - return + return ( + + ) default: return String(data) }