Skip to content

Commit feb16cd

Browse files
Merge pull request #1851 from johnduprey/dev
Mailbox Restores
2 parents 75bef6e + 2ce6d21 commit feb16cd

File tree

6 files changed

+429
-0
lines changed

6 files changed

+429
-0
lines changed

src/_nav.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,25 @@ const _nav = [
677677
},
678678
],
679679
},
680+
{
681+
component: CNavGroup,
682+
name: 'Tools',
683+
section: 'Tools',
684+
to: '/email/tools',
685+
icon: <FontAwesomeIcon icon={faToolbox} className="nav-icon" />,
686+
items: [
687+
{
688+
component: CNavItem,
689+
name: 'Mailbox Restore Wizard',
690+
to: '/email/tools/mailbox-restore-wizard',
691+
},
692+
{
693+
component: CNavItem,
694+
name: 'Mailbox Restores',
695+
to: '/email/tools/mailbox-restores',
696+
},
697+
],
698+
},
680699
{
681700
component: CNavTitle,
682701
name: 'Settings',

src/components/utilities/CippActionsOffcanvas.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ export default function CippActionsOffcanvas(props) {
8585
title: 'Confirm',
8686
onConfirm: () => genericGetRequest({ path: modalUrl }),
8787
})
88+
} else if (modalType === 'codeblock') {
89+
ModalService.open({
90+
data: modalBody,
91+
componentType: 'codeblock',
92+
title: 'Info',
93+
size: 'lg',
94+
})
8895
} else {
8996
ModalService.confirm({
9097
key: modalContent,

src/components/utilities/SharedModal.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react'
22
import { CButton, CModal, CModalBody, CModalFooter, CModalHeader, CModalTitle } from '@coreui/react'
33
import PropTypes from 'prop-types'
44
import { CippTable } from 'src/components/tables'
5+
import CippCodeBlock from 'src/components/utilities/CippCodeBlock'
56

67
/**
78
*
@@ -18,6 +19,10 @@ function mapBodyComponent({ componentType, data, componentProps }) {
1819
return <div>{Array.isArray(data) && data.map((el, idx) => <div key={idx}>{el}</div>)}</div>
1920
case 'text':
2021
return String(data)
22+
case 'codeblock':
23+
return (
24+
<CippCodeBlock language="text" code={data} showLineNumbers={false} {...componentProps} />
25+
)
2126
default:
2227
return String(data)
2328
}

src/routes.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,10 @@ const ServiceHealth = React.lazy(() => import('src/views/tenant/administration/S
225225
const EnterpriseApplications = React.lazy(() =>
226226
import('src/views/tenant/administration/ListEnterpriseApps'),
227227
)
228+
const MailboxRestoreWizard = React.lazy(() =>
229+
import('src/views/email-exchange/tools/MailboxRestoreWizard'),
230+
)
231+
const MailboxRestores = React.lazy(() => import('src/views/email-exchange/tools/MailboxRestores'))
228232

229233
const routes = [
230234
// { path: '/', exact: true, name: 'Home' },
@@ -544,6 +548,16 @@ const routes = [
544548
name: 'List Spamfilter Templates',
545549
component: SpamFilterTemplate,
546550
},
551+
{
552+
path: '/email/tools/mailbox-restore-wizard',
553+
name: 'Mailbox Restore Wizard',
554+
component: MailboxRestoreWizard,
555+
},
556+
{
557+
path: '/email/tools/mailbox-restores',
558+
name: 'Mailbox Restores',
559+
component: MailboxRestores,
560+
},
547561
{
548562
path: '/email/spamfilter/add-template',
549563
name: 'Add Spamfilter Template',
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import React from 'react'
2+
import { CCallout, CCol, CListGroup, CListGroupItem, CRow, CSpinner } from '@coreui/react'
3+
import { Field, FormSpy } from 'react-final-form'
4+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
5+
import { faExclamationTriangle, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons'
6+
import { useSelector } from 'react-redux'
7+
import { CippWizard } from 'src/components/layout'
8+
import PropTypes from 'prop-types'
9+
import {
10+
RFFCFormCheck,
11+
RFFCFormInput,
12+
RFFCFormSelect,
13+
RFFCFormSwitch,
14+
RFFSelectSearch,
15+
} from 'src/components/forms'
16+
import { TenantSelector } from 'src/components/utilities'
17+
import { useListUsersQuery } from 'src/store/api/users'
18+
import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app'
19+
20+
const Error = ({ name }) => (
21+
<Field
22+
name={name}
23+
subscription={{ touched: true, error: true }}
24+
render={({ meta: { touched, error } }) =>
25+
touched && error ? (
26+
<CCallout color="danger">
27+
<FontAwesomeIcon icon={faExclamationTriangle} color="danger" />
28+
{error}
29+
</CCallout>
30+
) : null
31+
}
32+
/>
33+
)
34+
35+
Error.propTypes = {
36+
name: PropTypes.string.isRequired,
37+
}
38+
39+
const MailboxRestoreWizard = () => {
40+
const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
41+
const {
42+
data: sourceMailboxes = [],
43+
isFetching: sMailboxesIsFetching,
44+
error: sMailboxError,
45+
} = useGenericGetRequestQuery({
46+
path: '/api/ListMailboxes',
47+
params: { TenantFilter: tenantDomain, SoftDeletedMailbox: true },
48+
})
49+
const {
50+
data: targetMailboxes = [],
51+
isFetching: tMailboxesIsFetching,
52+
error: tMailboxError,
53+
} = useGenericGetRequestQuery({
54+
path: '/api/ListMailboxes',
55+
params: { TenantFilter: tenantDomain },
56+
})
57+
const currentSettings = useSelector((state) => state.app)
58+
const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
59+
60+
const handleSubmit = async (values) => {
61+
const shippedValues = {
62+
TenantFilter: tenantDomain,
63+
RequestName: values.RequestName,
64+
SourceMailbox: values.SourceMailbox.value,
65+
TargetMailbox: values.TargetMailbox.value,
66+
BadItemLimit: values.BadItemLimit,
67+
LargeItemLimit: values.LargeItemLimit,
68+
AcceptLargeDataLoss: values.AcceptLargeDataLoss,
69+
}
70+
71+
//alert(JSON.stringify(values, null, 2))
72+
genericPostRequest({ path: '/api/ExecMailboxRestore', values: shippedValues })
73+
}
74+
75+
return (
76+
<CippWizard onSubmit={handleSubmit} wizardTitle="Mailbox Restore Wizard">
77+
<CippWizard.Page
78+
title="Tenant Choice"
79+
description="Choose the tenant to perform a mailbox restore"
80+
>
81+
<center>
82+
<h3 className="text-primary">Step 1</h3>
83+
<h5 className="card-title mb-4">Choose a tenant</h5>
84+
</center>
85+
<hr className="my-4" />
86+
<Field name="tenantFilter">{(props) => <TenantSelector />}</Field>
87+
<hr className="my-4" />
88+
</CippWizard.Page>
89+
<CippWizard.Page
90+
title="Source Mailbox"
91+
description="Select a soft deleted mailbox to restore."
92+
>
93+
<center>
94+
<h3 className="text-primary">Step 2</h3>
95+
<h5>Select a soft deleted mailbox to restore.</h5>
96+
</center>
97+
<hr className="my-4" />
98+
<div className="mb-2">
99+
<RFFSelectSearch
100+
label={'Soft Deleted Mailboxes in ' + tenantDomain}
101+
values={sourceMailboxes?.map((mbx) => ({
102+
value: mbx.ExchangeGuid,
103+
name: `${mbx.displayName} <${mbx.UPN}>`,
104+
}))}
105+
placeholder={!sMailboxesIsFetching ? 'Select mailbox' : 'Loading...'}
106+
name="SourceMailbox"
107+
/>
108+
{sMailboxError && <span>Failed to load source mailboxes</span>}
109+
</div>
110+
<hr className="my-4" />
111+
</CippWizard.Page>
112+
<CippWizard.Page title="Target Mailbox" description="Select a mailbox to restore to.">
113+
<center>
114+
<h3 className="text-primary">Step 2</h3>
115+
<h5>Select a mailbox to restore to.</h5>
116+
</center>
117+
<hr className="my-4" />
118+
<div className="mb-2">
119+
<RFFSelectSearch
120+
label={'Mailboxes in ' + tenantDomain}
121+
values={targetMailboxes?.map((mbx) => ({
122+
value: mbx.ExchangeGuid,
123+
name: `${mbx.displayName} <${mbx.UPN}>`,
124+
}))}
125+
placeholder={!tMailboxesIsFetching ? 'Select mailbox' : 'Loading...'}
126+
name="TargetMailbox"
127+
/>
128+
{sMailboxError && <span>Failed to load source mailboxes</span>}
129+
</div>
130+
<hr className="my-4" />
131+
</CippWizard.Page>
132+
<CippWizard.Page title="Restore Request Options" description="">
133+
<center>
134+
<h3 className="text-primary">Step 3</h3>
135+
<h5>Enter Restore Request Options</h5>
136+
</center>
137+
<hr className="my-4" />
138+
<div className="mb-2">
139+
<CRow>
140+
<CCol md={6}>
141+
<RFFCFormInput type="text" name="RequestName" label="Restore Request Name" />
142+
</CCol>
143+
</CRow>
144+
<CRow>
145+
<CCol md={6}>
146+
<RFFCFormSwitch name="AcceptLargeDataLoss" label="Accept Large Data Loss" />
147+
</CCol>
148+
</CRow>
149+
<CRow>
150+
<CCol md={6}>
151+
<RFFCFormInput name="BadItemLimit" label="Bad Item Limit" />
152+
</CCol>
153+
</CRow>
154+
<CRow>
155+
<CCol md={6}>
156+
<RFFCFormInput name="LargeItemLimit" label="Large Item Limit" />
157+
</CCol>
158+
</CRow>
159+
</div>
160+
<hr className="my-4" />
161+
</CippWizard.Page>
162+
<CippWizard.Page title="Review and Confirm" description="Confirm the settings to apply">
163+
<center>
164+
<h3 className="text-primary">Step 4</h3>
165+
<h5 className="mb-4">Confirm and apply</h5>
166+
<hr className="my-4" />
167+
</center>
168+
<div className="mb-2">
169+
{postResults.isFetching && (
170+
<CCallout color="info">
171+
<CSpinner>Loading</CSpinner>
172+
</CCallout>
173+
)}
174+
{postResults.isSuccess && (
175+
<CCallout color="success">
176+
{postResults.data.Results.map((message, idx) => {
177+
return <li key={idx}>{message}</li>
178+
})}
179+
</CCallout>
180+
)}
181+
{!postResults.isSuccess && (
182+
<FormSpy>
183+
{(props) => (
184+
<>
185+
<CRow>
186+
<CCol md={{ span: 6, offset: 3 }}>
187+
<CListGroup flush>
188+
<CListGroupItem className="d-flex justify-content-between align-items-center">
189+
<h5 className="mb-0">Selected Tenant:</h5>
190+
{tenantDomain}
191+
</CListGroupItem>
192+
<CListGroupItem className="d-flex justify-content-between align-items-center">
193+
<h5 className="mb-0">Source Mailbox:</h5>
194+
{props.values.SourceMailbox.label}
195+
</CListGroupItem>
196+
<CListGroupItem className="d-flex justify-content-between align-items-center">
197+
<h5 className="mb-0">Target Mailbox:</h5>
198+
{props.values.TargetMailbox.label}
199+
</CListGroupItem>
200+
</CListGroup>
201+
</CCol>
202+
</CRow>
203+
</>
204+
)}
205+
</FormSpy>
206+
)}
207+
</div>
208+
<hr className="my-4" />
209+
</CippWizard.Page>
210+
</CippWizard>
211+
)
212+
}
213+
214+
export default MailboxRestoreWizard

0 commit comments

Comments
 (0)