Skip to content

Commit f3e0e35

Browse files
added backups
1 parent 811e710 commit f3e0e35

File tree

5 files changed

+639
-3
lines changed

5 files changed

+639
-3
lines changed

src/_nav.jsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
faUserShield,
2020
faEnvelope,
2121
faToolbox,
22+
faDownload,
2223
} from '@fortawesome/free-solid-svg-icons'
2324

2425
const _nav = [
@@ -184,6 +185,25 @@ const _nav = [
184185
},
185186
],
186187
},
188+
{
189+
component: CNavGroup,
190+
name: 'Configuration Backup',
191+
section: 'Tenant Administration',
192+
to: '/cipp/gdap',
193+
icon: <FontAwesomeIcon icon={faDownload} className="nav-icon" />,
194+
items: [
195+
{
196+
component: CNavItem,
197+
name: 'Backup Wizard',
198+
to: '/tenant/backup/backup-wizard',
199+
},
200+
{
201+
component: CNavItem,
202+
name: 'Restore Wizard',
203+
to: '/tenant/backup/restore-wizard',
204+
},
205+
],
206+
},
187207
{
188208
component: CNavGroup,
189209
name: 'Tools',

src/importsMap.jsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import React from 'react'
3434
"/identity/reports/Signin-report": React.lazy(() => import('./views/identity/reports/SignIns')),
3535
"/identity/reports/azure-ad-connect-report": React.lazy(() => import('./views/identity/reports/AzureADConnectReport')),
3636
"/identity/reports/risk-detections": React.lazy(() => import('./views/identity/reports/RiskDetections')),
37+
"/tenant/backup/backup-wizard": React.lazy(() => import('./views/tenant/backup/CreateBackup')),
38+
"/tenant/backup/restore-wizard": React.lazy(() => import('./views/tenant/backup/RestoreBackup')),
3739
"/tenant/administration/tenants": React.lazy(() => import('./views/tenant/administration/Tenants')),
3840
"/tenant/administration/tenants/edit": React.lazy(() => import('./views/tenant/administration/EditTenant')),
3941
"/tenant/administration/partner-relationships": React.lazy(() => import('./views/tenant/administration/PartnerRelationships')),
@@ -46,14 +48,14 @@ import React from 'react'
4648
"/tenant/administration/enterprise-apps": React.lazy(() => import('./views/tenant/administration/ListEnterpriseApps')),
4749
"/tenant/administration/app-consent-requests": React.lazy(() => import('./views/tenant/administration/ListAppConsentRequests')),
4850
"/tenant/conditional/list-policies": React.lazy(() => import('./views/tenant/conditional/ConditionalAccess')),
51+
"/tenant/administration/authentication-methods": React.lazy(() => import('./views/tenant/administration/AuthMethods')),
4952
"/tenant/conditional/deploy-vacation": React.lazy(() => import('./views/tenant/conditional/DeployVacation')),
5053
"/tenant/conditional/test-policy": React.lazy(() => import('./views/tenant/conditional/TestCAPolicy')),
5154
"/tenant/conditional/list-named-locations": React.lazy(() => import('./views/tenant/conditional/NamedLocations')),
5255
"/tenant/conditional/deploy": React.lazy(() => import('./views/tenant/conditional/DeployCA')),
5356
"/tenant/conditional/deploy-named-location": React.lazy(() => import('./views/tenant/conditional/DeployNamedLocation')),
5457
"/tenant/conditional/list-template": React.lazy(() => import('./views/tenant/conditional/ListCATemplates')),
5558
"/tenant/conditional/add-template": React.lazy(() => import('./views/tenant/conditional/AddCATemplate')),
56-
"/tenant/administration/authentication-methods": React.lazy(() => import('./views/tenant/administration/AuthMethods')),
5759
"/tenant/administration/list-licenses": React.lazy(() => import('./views/tenant/administration/ListLicences')),
5860
"/tenant/administration/application-consent": React.lazy(() => import('./views/tenant/administration/ListOauthApps')),
5961
"/tenant/standards/list-applied-standards": React.lazy(() => import('./views/tenant/standards/ListAppliedStandards')),
@@ -117,8 +119,8 @@ import React from 'react'
117119
"/email/administration/edit-calendar-permissions": React.lazy(() => import('./views/email-exchange/administration/EditCalendarPermissions')),
118120
"/email/administration/view-mobile-devices": React.lazy(() => import('./views/email-exchange/administration/ViewMobileDevices')),
119121
"/email/administration/edit-contact": React.lazy(() => import('./views/email-exchange/administration/EditContact')),
120-
"/email/administration/mailboxes": React.lazy(() => import('./views/email-exchange/administration/MailboxesList')),
121-
"/email/administration/deleted-mailboxes": React.lazy(() => import('./views/email-exchange/administration/DeletedMailboxes')),
122+
"/email/administration/mailboxes": React.lazy(() => import('./views/email-exchange/administration/MailboxesList')),
123+
"/email/administration/deleted-mailboxes": React.lazy(() => import('./views/email-exchange/administration/DeletedMailboxes')),
122124
"/email/administration/mailbox-rules": React.lazy(() => import('./views/email-exchange/administration/MailboxRuleList')),
123125
"/email/administration/Quarantine": React.lazy(() => import('./views/email-exchange/administration/QuarantineList')),
124126
"/email/administration/tenant-allow-block-lists": React.lazy(() => import('./views/email-exchange/administration/ListTenantAllowBlockList')),

src/routes.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,20 @@
228228
"name": "Administration",
229229
"allowedRoles": ["admin", "editor", "readonly"]
230230
},
231+
{
232+
"path": "/tenant/backup/backup-wizard",
233+
"name": "Backup",
234+
"component": "views/tenant/backup/CreateBackup",
235+
236+
"allowedRoles": ["admin", "editor", "readonly"]
237+
},
238+
{
239+
"path": "/tenant/backup/restore-wizard",
240+
"name": "Restore Backup",
241+
"component": "views/tenant/backup/RestoreBackup",
242+
243+
"allowedRoles": ["admin", "editor", "readonly"]
244+
},
231245
{
232246
"path": "/tenant/administration/tenants",
233247
"name": "Tenants",
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import React, { useState } from 'react'
2+
import { CButton, CCallout, CCol, CForm, CRow, CSpinner, CTooltip } from '@coreui/react'
3+
import { useSelector } from 'react-redux'
4+
import { Field, Form } from 'react-final-form'
5+
import { RFFCFormSwitch } from 'src/components/forms'
6+
import {
7+
useGenericGetRequestQuery,
8+
useLazyGenericGetRequestQuery,
9+
useLazyGenericPostRequestQuery,
10+
} from 'src/store/api/app'
11+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
12+
import { faCircleNotch, faEdit, faEye } from '@fortawesome/free-solid-svg-icons'
13+
import { CippPage, CippPageList } from 'src/components/layout'
14+
import 'react-datepicker/dist/react-datepicker.css'
15+
import { ModalService, TenantSelector } from 'src/components/utilities'
16+
import arrayMutators from 'final-form-arrays'
17+
import { useListConditionalAccessPoliciesQuery } from 'src/store/api/tenants'
18+
import CippButtonCard from 'src/components/contentcards/CippButtonCard'
19+
import { CellTip, cellGenericFormatter } from 'src/components/tables/CellGenericFormat'
20+
import { cellBadgeFormatter, cellDateFormatter } from 'src/components/tables'
21+
22+
const CreateBackup = () => {
23+
const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery()
24+
const currentDate = new Date()
25+
const [startDate, setStartDate] = useState(currentDate)
26+
const [endDate, setEndDate] = useState(currentDate)
27+
28+
const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
29+
const [refreshState, setRefreshState] = useState(false)
30+
const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
31+
32+
const onSubmit = (values) => {
33+
const startDate = new Date()
34+
startDate.setHours(0, 0, 0, 0)
35+
//decrease by 45 seconds to ensure the task runs after the current time
36+
const unixTime = Math.floor(startDate.getTime() / 1000) - 45
37+
const shippedValues = {
38+
TenantFilter: tenantDomain,
39+
Name: `CIPP Backup ${tenantDomain}`,
40+
Command: { value: `New-CIPPBackup` },
41+
Parameters: { ...values },
42+
ScheduledTime: unixTime,
43+
Recurrence: '1d',
44+
}
45+
genericPostRequest({ path: '/api/AddScheduledItem?hidden=true', values: shippedValues }).then(
46+
(res) => {
47+
setRefreshState(res.requestId)
48+
},
49+
)
50+
}
51+
const Offcanvas = (row, rowIndex, formatExtraData) => {
52+
const handleDeleteSchedule = (apiurl, message) => {
53+
ModalService.confirm({
54+
title: 'Confirm',
55+
body: <div>{message}</div>,
56+
onConfirm: () =>
57+
ExecuteGetRequest({ path: apiurl }).then((res) => {
58+
setRefreshState(res.requestId)
59+
}),
60+
confirmLabel: 'Continue',
61+
cancelLabel: 'Cancel',
62+
})
63+
}
64+
let jsonResults
65+
try {
66+
jsonResults = JSON.parse(row.Results)
67+
} catch (error) {
68+
jsonResults = row.Results
69+
}
70+
71+
return (
72+
<>
73+
<CTooltip content="Delete task">
74+
<CButton
75+
onClick={() =>
76+
handleDeleteSchedule(
77+
`/api/RemoveScheduledItem?&ID=${row.RowKey}`,
78+
'Do you want to delete this job?',
79+
)
80+
}
81+
size="sm"
82+
variant="ghost"
83+
color="danger"
84+
>
85+
<FontAwesomeIcon icon={'trash'} href="" />
86+
</CButton>
87+
</CTooltip>
88+
</>
89+
)
90+
}
91+
const columns = [
92+
{
93+
name: 'Tenant',
94+
selector: (row) => row['Tenant'],
95+
sortable: true,
96+
cell: (row) => CellTip(row['Tenant']),
97+
exportSelector: 'Tenant',
98+
},
99+
{
100+
name: 'Task State',
101+
selector: (row) => row['TaskState'],
102+
sortable: true,
103+
cell: cellBadgeFormatter(),
104+
exportSelector: 'TaskState',
105+
},
106+
{
107+
name: 'Last executed time',
108+
selector: (row) => row['ExecutedTime'],
109+
sortable: true,
110+
cell: cellDateFormatter({ format: 'relative' }),
111+
exportSelector: 'ExecutedTime',
112+
},
113+
{
114+
name: 'Actions',
115+
cell: Offcanvas,
116+
maxWidth: '100px',
117+
},
118+
]
119+
120+
const {
121+
data: users = [],
122+
isFetching: usersIsFetching,
123+
error: usersError,
124+
} = useGenericGetRequestQuery({
125+
path: '/api/ListGraphRequest',
126+
params: {
127+
TenantFilter: tenantDomain,
128+
Endpoint: 'users',
129+
$select: 'id,displayName,userPrincipalName,accountEnabled',
130+
$count: true,
131+
$top: 999,
132+
$orderby: 'displayName',
133+
},
134+
})
135+
136+
const {
137+
data: caPolicies = [],
138+
isFetching: caIsFetching,
139+
error: caError,
140+
} = useListConditionalAccessPoliciesQuery({ domain: tenantDomain })
141+
142+
return (
143+
<CippPage title={`Add Backup Schedule`} tenantSelector={false}>
144+
<>
145+
<CRow>
146+
<CCol md={4}>
147+
<CippButtonCard
148+
CardButton={
149+
<CButton type="submit" form="addTask">
150+
Create Backup Schedule
151+
{postResults.isFetching && (
152+
<FontAwesomeIcon icon={faCircleNotch} spin className="ms-2" size="1x" />
153+
)}
154+
</CButton>
155+
}
156+
title="Add backup Schedule"
157+
icon={faEdit}
158+
>
159+
<Form
160+
onSubmit={onSubmit}
161+
mutators={{
162+
...arrayMutators,
163+
}}
164+
render={({ handleSubmit, submitting, values }) => {
165+
return (
166+
<CForm id="addTask" onSubmit={handleSubmit}>
167+
<p>
168+
Backups are stored in CIPPs storage and can be restored using the CIPP
169+
Restore Backup Wizard. Backups run daily or on demand by clicking the backup
170+
now button.
171+
</p>
172+
<CRow className="mb-3">
173+
<CCol>
174+
<label>Tenant</label>
175+
<Field name="tenantFilter">{(props) => <TenantSelector />}</Field>
176+
</CCol>
177+
</CRow>
178+
<CRow>
179+
<hr />
180+
</CRow>
181+
<CRow className="mb-3">
182+
<CCol>
183+
<h3 className="underline mb-4">Identity</h3>
184+
<RFFCFormSwitch name="users" label="User List" />
185+
<RFFCFormSwitch name="groups" label="Groups" />
186+
<h3 className="underline mb-4">Conditional Access</h3>
187+
<RFFCFormSwitch name="ca" label="Conditional Access" />
188+
<RFFCFormSwitch name="namedlocations" label="Named Locations" />
189+
<RFFCFormSwitch name="authstrengths" label="Authentication Strengths" />
190+
<h3 className="underline mb-4">Intune</h3>
191+
<RFFCFormSwitch
192+
name="intuneconfig"
193+
label="Intune Configuration Policies"
194+
/>
195+
<RFFCFormSwitch
196+
name="intunecompliance"
197+
label="Intune Compliance Policies"
198+
/>
199+
<RFFCFormSwitch
200+
name="intuneprotection"
201+
label="Intune Protection Policies"
202+
/>
203+
<h3 className="underline mb-4">CIPP</h3>
204+
<RFFCFormSwitch name="CippAlerts" label="Alerts Configuration" />
205+
<RFFCFormSwitch name="CippStandards" label="Standards Configuration" />
206+
</CCol>
207+
</CRow>
208+
{postResults.isSuccess && (
209+
<CCallout color="success">
210+
<li>{postResults.data.Results}</li>
211+
</CCallout>
212+
)}
213+
{getResults.isFetching && (
214+
<CCallout color="info">
215+
<CSpinner>Loading</CSpinner>
216+
</CCallout>
217+
)}
218+
{getResults.isSuccess && (
219+
<CCallout color="info">{getResults.data?.Results}</CCallout>
220+
)}
221+
{getResults.isError && (
222+
<CCallout color="danger">
223+
Could not connect to API: {getResults.error.message}
224+
</CCallout>
225+
)}
226+
</CForm>
227+
)
228+
}}
229+
/>
230+
</CippButtonCard>
231+
</CCol>
232+
233+
<CCol md={8}>
234+
<CippPageList
235+
key={refreshState}
236+
capabilities={{
237+
allTenants: true,
238+
helpContext: 'https://google.com',
239+
}}
240+
title="Backup Tasks"
241+
tenantSelector={false}
242+
datatable={{
243+
tableProps: {
244+
selectableRows: true,
245+
actionsList: [
246+
{
247+
label: 'Delete task',
248+
modal: true,
249+
modalUrl: `/api/RemoveScheduledItem?&ID=!RowKey`,
250+
modalMessage: 'Do you want to delete this job?',
251+
},
252+
],
253+
},
254+
keyField: 'id',
255+
columns,
256+
reportName: `Scheduled-Jobs`,
257+
path: `/api/ListScheduledItems?RefreshGuid=${refreshState}&showHidden=true&Type=New-CIPPBackup`,
258+
}}
259+
/>
260+
</CCol>
261+
</CRow>
262+
</>
263+
</CippPage>
264+
)
265+
}
266+
267+
export default CreateBackup

0 commit comments

Comments
 (0)