diff --git a/src/_nav.jsx b/src/_nav.jsx
index e6567546bf11..631d6eaf1169 100644
--- a/src/_nav.jsx
+++ b/src/_nav.jsx
@@ -19,6 +19,7 @@ import {
faUserShield,
faEnvelope,
faToolbox,
+ faDownload,
} from '@fortawesome/free-solid-svg-icons'
const _nav = [
@@ -184,6 +185,25 @@ const _nav = [
},
],
},
+ {
+ component: CNavGroup,
+ name: 'Configuration Backup',
+ section: 'Tenant Administration',
+ to: '/cipp/gdap',
+ icon: ,
+ items: [
+ {
+ component: CNavItem,
+ name: 'Backup Wizard',
+ to: '/tenant/backup/backup-wizard',
+ },
+ {
+ component: CNavItem,
+ name: 'Restore Wizard',
+ to: '/tenant/backup/restore-wizard',
+ },
+ ],
+ },
{
component: CNavGroup,
name: 'Tools',
diff --git a/src/importsMap.jsx b/src/importsMap.jsx
index 63ae50439b0d..9666488abfc9 100644
--- a/src/importsMap.jsx
+++ b/src/importsMap.jsx
@@ -34,6 +34,8 @@ import React from 'react'
"/identity/reports/Signin-report": React.lazy(() => import('./views/identity/reports/SignIns')),
"/identity/reports/azure-ad-connect-report": React.lazy(() => import('./views/identity/reports/AzureADConnectReport')),
"/identity/reports/risk-detections": React.lazy(() => import('./views/identity/reports/RiskDetections')),
+ "/tenant/backup/backup-wizard": React.lazy(() => import('./views/tenant/backup/CreateBackup')),
+ "/tenant/backup/restore-wizard": React.lazy(() => import('./views/tenant/backup/RestoreBackup')),
"/tenant/administration/tenants": React.lazy(() => import('./views/tenant/administration/Tenants')),
"/tenant/administration/tenants/edit": React.lazy(() => import('./views/tenant/administration/EditTenant')),
"/tenant/administration/partner-relationships": React.lazy(() => import('./views/tenant/administration/PartnerRelationships')),
@@ -46,6 +48,7 @@ import React from 'react'
"/tenant/administration/enterprise-apps": React.lazy(() => import('./views/tenant/administration/ListEnterpriseApps')),
"/tenant/administration/app-consent-requests": React.lazy(() => import('./views/tenant/administration/ListAppConsentRequests')),
"/tenant/conditional/list-policies": React.lazy(() => import('./views/tenant/conditional/ConditionalAccess')),
+ "/tenant/administration/authentication-methods": React.lazy(() => import('./views/tenant/administration/AuthMethods')),
"/tenant/conditional/deploy-vacation": React.lazy(() => import('./views/tenant/conditional/DeployVacation')),
"/tenant/conditional/test-policy": React.lazy(() => import('./views/tenant/conditional/TestCAPolicy')),
"/tenant/conditional/list-named-locations": React.lazy(() => import('./views/tenant/conditional/NamedLocations')),
@@ -53,7 +56,6 @@ import React from 'react'
"/tenant/conditional/deploy-named-location": React.lazy(() => import('./views/tenant/conditional/DeployNamedLocation')),
"/tenant/conditional/list-template": React.lazy(() => import('./views/tenant/conditional/ListCATemplates')),
"/tenant/conditional/add-template": React.lazy(() => import('./views/tenant/conditional/AddCATemplate')),
- "/tenant/administration/authentication-methods": React.lazy(() => import('./views/tenant/administration/AuthMethods')),
"/tenant/administration/list-licenses": React.lazy(() => import('./views/tenant/administration/ListLicences')),
"/tenant/administration/application-consent": React.lazy(() => import('./views/tenant/administration/ListOauthApps')),
"/tenant/standards/list-applied-standards": React.lazy(() => import('./views/tenant/standards/ListAppliedStandards')),
@@ -117,8 +119,8 @@ import React from 'react'
"/email/administration/edit-calendar-permissions": React.lazy(() => import('./views/email-exchange/administration/EditCalendarPermissions')),
"/email/administration/view-mobile-devices": React.lazy(() => import('./views/email-exchange/administration/ViewMobileDevices')),
"/email/administration/edit-contact": React.lazy(() => import('./views/email-exchange/administration/EditContact')),
- "/email/administration/mailboxes": React.lazy(() => import('./views/email-exchange/administration/MailboxesList')),
- "/email/administration/deleted-mailboxes": React.lazy(() => import('./views/email-exchange/administration/DeletedMailboxes')),
+ "/email/administration/mailboxes": React.lazy(() => import('./views/email-exchange/administration/MailboxesList')),
+ "/email/administration/deleted-mailboxes": React.lazy(() => import('./views/email-exchange/administration/DeletedMailboxes')),
"/email/administration/mailbox-rules": React.lazy(() => import('./views/email-exchange/administration/MailboxRuleList')),
"/email/administration/Quarantine": React.lazy(() => import('./views/email-exchange/administration/QuarantineList')),
"/email/administration/tenant-allow-block-lists": React.lazy(() => import('./views/email-exchange/administration/ListTenantAllowBlockList')),
diff --git a/src/routes.json b/src/routes.json
index 669c385e6d3d..96584165d77c 100644
--- a/src/routes.json
+++ b/src/routes.json
@@ -228,6 +228,20 @@
"name": "Administration",
"allowedRoles": ["admin", "editor", "readonly"]
},
+ {
+ "path": "/tenant/backup/backup-wizard",
+ "name": "Backup",
+ "component": "views/tenant/backup/CreateBackup",
+
+ "allowedRoles": ["admin", "editor", "readonly"]
+ },
+ {
+ "path": "/tenant/backup/restore-wizard",
+ "name": "Restore Backup",
+ "component": "views/tenant/backup/RestoreBackup",
+
+ "allowedRoles": ["admin", "editor", "readonly"]
+ },
{
"path": "/tenant/administration/tenants",
"name": "Tenants",
diff --git a/src/views/tenant/backup/CreateBackup.jsx b/src/views/tenant/backup/CreateBackup.jsx
new file mode 100644
index 000000000000..b694fc225efd
--- /dev/null
+++ b/src/views/tenant/backup/CreateBackup.jsx
@@ -0,0 +1,267 @@
+import React, { useState } from 'react'
+import { CButton, CCallout, CCol, CForm, CRow, CSpinner, CTooltip } from '@coreui/react'
+import { useSelector } from 'react-redux'
+import { Field, Form } from 'react-final-form'
+import { RFFCFormSwitch } from 'src/components/forms'
+import {
+ useGenericGetRequestQuery,
+ useLazyGenericGetRequestQuery,
+ useLazyGenericPostRequestQuery,
+} from 'src/store/api/app'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faCircleNotch, faEdit, faEye } from '@fortawesome/free-solid-svg-icons'
+import { CippPage, CippPageList } from 'src/components/layout'
+import 'react-datepicker/dist/react-datepicker.css'
+import { ModalService, TenantSelector } from 'src/components/utilities'
+import arrayMutators from 'final-form-arrays'
+import { useListConditionalAccessPoliciesQuery } from 'src/store/api/tenants'
+import CippButtonCard from 'src/components/contentcards/CippButtonCard'
+import { CellTip, cellGenericFormatter } from 'src/components/tables/CellGenericFormat'
+import { cellBadgeFormatter, cellDateFormatter } from 'src/components/tables'
+
+const CreateBackup = () => {
+ const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery()
+ const currentDate = new Date()
+ const [startDate, setStartDate] = useState(currentDate)
+ const [endDate, setEndDate] = useState(currentDate)
+
+ const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
+ const [refreshState, setRefreshState] = useState(false)
+ const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
+
+ const onSubmit = (values) => {
+ const startDate = new Date()
+ startDate.setHours(0, 0, 0, 0)
+ //decrease by 45 seconds to ensure the task runs after the current time
+ const unixTime = Math.floor(startDate.getTime() / 1000) - 45
+ const shippedValues = {
+ TenantFilter: tenantDomain,
+ Name: `CIPP Backup ${tenantDomain}`,
+ Command: { value: `New-CIPPBackup` },
+ Parameters: { ...values },
+ ScheduledTime: unixTime,
+ Recurrence: '1d',
+ }
+ genericPostRequest({ path: '/api/AddScheduledItem?hidden=true', values: shippedValues }).then(
+ (res) => {
+ setRefreshState(res.requestId)
+ },
+ )
+ }
+ const Offcanvas = (row, rowIndex, formatExtraData) => {
+ const handleDeleteSchedule = (apiurl, message) => {
+ ModalService.confirm({
+ title: 'Confirm',
+ body:
{message}
,
+ onConfirm: () =>
+ ExecuteGetRequest({ path: apiurl }).then((res) => {
+ setRefreshState(res.requestId)
+ }),
+ confirmLabel: 'Continue',
+ cancelLabel: 'Cancel',
+ })
+ }
+ let jsonResults
+ try {
+ jsonResults = JSON.parse(row.Results)
+ } catch (error) {
+ jsonResults = row.Results
+ }
+
+ return (
+ <>
+
+
+ handleDeleteSchedule(
+ `/api/RemoveScheduledItem?&ID=${row.RowKey}`,
+ 'Do you want to delete this job?',
+ )
+ }
+ size="sm"
+ variant="ghost"
+ color="danger"
+ >
+
+
+
+ >
+ )
+ }
+ const columns = [
+ {
+ name: 'Tenant',
+ selector: (row) => row['Tenant'],
+ sortable: true,
+ cell: (row) => CellTip(row['Tenant']),
+ exportSelector: 'Tenant',
+ },
+ {
+ name: 'Task State',
+ selector: (row) => row['TaskState'],
+ sortable: true,
+ cell: cellBadgeFormatter(),
+ exportSelector: 'TaskState',
+ },
+ {
+ name: 'Last executed time',
+ selector: (row) => row['ExecutedTime'],
+ sortable: true,
+ cell: cellDateFormatter({ format: 'relative' }),
+ exportSelector: 'ExecutedTime',
+ },
+ {
+ name: 'Actions',
+ cell: Offcanvas,
+ maxWidth: '100px',
+ },
+ ]
+
+ const {
+ data: users = [],
+ isFetching: usersIsFetching,
+ error: usersError,
+ } = useGenericGetRequestQuery({
+ path: '/api/ListGraphRequest',
+ params: {
+ TenantFilter: tenantDomain,
+ Endpoint: 'users',
+ $select: 'id,displayName,userPrincipalName,accountEnabled',
+ $count: true,
+ $top: 999,
+ $orderby: 'displayName',
+ },
+ })
+
+ const {
+ data: caPolicies = [],
+ isFetching: caIsFetching,
+ error: caError,
+ } = useListConditionalAccessPoliciesQuery({ domain: tenantDomain })
+
+ return (
+
+ <>
+
+
+
+ Create Backup Schedule
+ {postResults.isFetching && (
+
+ )}
+
+ }
+ title="Add backup Schedule"
+ icon={faEdit}
+ >
+
+
+
+
+
+
+
+ >
+
+ )
+}
+
+export default CreateBackup
diff --git a/src/views/tenant/backup/RestoreBackup.jsx b/src/views/tenant/backup/RestoreBackup.jsx
new file mode 100644
index 000000000000..161ddc2da6a3
--- /dev/null
+++ b/src/views/tenant/backup/RestoreBackup.jsx
@@ -0,0 +1,333 @@
+import React, { useState } from 'react'
+import { CCallout, CCol, CListGroup, CListGroupItem, CRow, CSpinner, CTooltip } 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 { CippCallout, CippWizard } from 'src/components/layout'
+import PropTypes from 'prop-types'
+import { Condition, RFFCFormSwitch, RFFSelectSearch } from 'src/components/forms'
+import { TenantSelector } from 'src/components/utilities'
+import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app'
+import 'react-datepicker/dist/react-datepicker.css'
+
+const Error = ({ name }) => (
+
+ touched && error ? (
+
+
+ {error}
+
+ ) : null
+ }
+ />
+)
+
+Error.propTypes = {
+ name: PropTypes.string.isRequired,
+}
+
+const OffboardingWizard = () => {
+ const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
+ const {
+ data: currentBackups = [],
+ isFetching: currentBackupsIsFetching,
+ error: currentBackupsError,
+ } = useGenericGetRequestQuery({
+ path: `/api/ExecListBackup?TenantFilter=${tenantDomain}&Type=Scheduled`,
+ })
+
+ const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
+
+ const handleSubmit = async (values) => {
+ const shippedValues = {
+ TenantFilter: tenantDomain,
+ OOO: values.OOO ? values.OOO : '',
+ forward: values.forward ? values.forward.value : '',
+ OnedriveAccess: values.OnedriveAccess ? values.OnedriveAccess : '',
+ AccessNoAutomap: values.AccessNoAutomap ? values.AccessNoAutomap : '',
+ AccessAutomap: values.AccessAutomap ? values.AccessAutomap : '',
+ ConvertToShared: values.ConvertToShared,
+ HideFromGAL: values.HideFromGAL,
+ DisableSignIn: values.DisableSignIn,
+ RemoveGroups: values.RemoveGroups,
+ RemoveLicenses: values.RemoveLicenses,
+ ResetPass: values.ResetPass,
+ RevokeSessions: values.RevokeSessions,
+ user: values.User,
+ deleteuser: values.DeleteUser,
+ removeRules: values.RemoveRules,
+ removeMobile: values.RemoveMobile,
+ keepCopy: values.keepCopy,
+ removePermissions: values.removePermissions,
+ PostExecution: values.Scheduled?.enabled
+ ? { webhook: values.webhook, psa: values.psa, email: values.email }
+ : '',
+ }
+
+ //alert(JSON.stringify(values, null, 2))
+ genericPostRequest({ path: '/api/ExecOffboardUser', values: shippedValues })
+ }
+
+ return (
+
+
+
+ Step 1
+ Choose a tenant
+
+
+ {(props) => }
+
+
+
+
+ Step 2
+ Select the backup to restore
+
+
+
+ ({
+ value: backup.RowKey,
+ name: `${backup.BackupDate}`,
+ }))}
+ placeholder={!currentBackupsIsFetching ? 'Select a backup' : 'Loading...'}
+ name="User"
+ />
+ {currentBackupsError && Failed to load list of Current Backups}
+
+
+
+
+
+ Step 3
+ Choose restore options
+
+
+
+
+
+ Identity
+
+
+ Conditional Access
+
+
+
+
+
+ Intune
+
+
+
+ CIPP
+
+
+
+
+
+
+
+
+
+
+
+
+ Warning
+
+ Overwriting existing entries will remove the current settings and replace them with
+ the backup settings. If you have selected to restore users, all properties will be
+ overwritten with the backup settings.
+
+
+
+ To prevent and skip already existing entries, deselect the setting from the list
+ above, or disable overwrite.
+
+
+
+
+
+
+
+
+ Step 4
+ Confirm and apply
+
+
+
+ {postResults.isFetching && (
+
+ Loading
+
+ )}
+ {postResults.isSuccess && (
+
+ {postResults.data.Results.map((message, idx) => {
+ return {message}
+ })}
+
+ )}
+ {!postResults.isSuccess && (
+
+ {/* eslint-disable react/prop-types */}
+ {(props) => (
+ <>
+
+
+
+
+ Selected Tenant:
+ {tenantDomain}
+
+
+
+
+
+
+
+
+
+ Revoke Sessions
+
+
+
+ Remove all mobile devices
+
+
+
+ Remove all mailbox rules
+
+
+
+ Remove all mailbox permissions
+
+
+
+ Remove Licenses
+
+
+
+ Convert to Shared
+
+
+
+ Disable Sign-in
+
+
+
+ Reset Password
+
+
+
+ Remove from all groups
+
+
+
+ Hide from Global Address List
+
+
+
+ Set Out of Office
+
+
+
+ Give another user access to the mailbox with automap
+
+
+
+ Give another user access to the mailbox without automap
+
+
+
+ Give another user access to OneDrive
+
+
+
+ Forward all e-mail to another user
+
+
+
+
+
+ >
+ )}
+
+ )}
+
+
+
+
+ )
+}
+
+export default OffboardingWizard