Skip to content

Commit

Permalink
added backups
Browse files Browse the repository at this point in the history
  • Loading branch information
KelvinTegelaar committed Jul 4, 2024
1 parent 811e710 commit f3e0e35
Show file tree
Hide file tree
Showing 5 changed files with 639 additions and 3 deletions.
20 changes: 20 additions & 0 deletions src/_nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
faUserShield,
faEnvelope,
faToolbox,
faDownload,
} from '@fortawesome/free-solid-svg-icons'

const _nav = [
Expand Down Expand Up @@ -184,6 +185,25 @@ const _nav = [
},
],
},
{
component: CNavGroup,
name: 'Configuration Backup',
section: 'Tenant Administration',
to: '/cipp/gdap',
icon: <FontAwesomeIcon icon={faDownload} className="nav-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',
Expand Down
8 changes: 5 additions & 3 deletions src/importsMap.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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')),
Expand All @@ -46,14 +48,14 @@ 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')),
"/tenant/conditional/deploy": React.lazy(() => import('./views/tenant/conditional/DeployCA')),
"/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')),
Expand Down Expand Up @@ -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')),
Expand Down
14 changes: 14 additions & 0 deletions src/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
267 changes: 267 additions & 0 deletions src/views/tenant/backup/CreateBackup.jsx
Original file line number Diff line number Diff line change
@@ -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: <div>{message}</div>,
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 (
<>
<CTooltip content="Delete task">
<CButton
onClick={() =>
handleDeleteSchedule(
`/api/RemoveScheduledItem?&ID=${row.RowKey}`,
'Do you want to delete this job?',
)
}
size="sm"
variant="ghost"
color="danger"
>
<FontAwesomeIcon icon={'trash'} href="" />
</CButton>
</CTooltip>
</>
)
}
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 (
<CippPage title={`Add Backup Schedule`} tenantSelector={false}>
<>
<CRow>
<CCol md={4}>
<CippButtonCard
CardButton={
<CButton type="submit" form="addTask">
Create Backup Schedule
{postResults.isFetching && (
<FontAwesomeIcon icon={faCircleNotch} spin className="ms-2" size="1x" />
)}
</CButton>
}
title="Add backup Schedule"
icon={faEdit}
>
<Form
onSubmit={onSubmit}
mutators={{
...arrayMutators,
}}
render={({ handleSubmit, submitting, values }) => {
return (
<CForm id="addTask" onSubmit={handleSubmit}>
<p>
Backups are stored in CIPPs storage and can be restored using the CIPP
Restore Backup Wizard. Backups run daily or on demand by clicking the backup
now button.
</p>
<CRow className="mb-3">
<CCol>
<label>Tenant</label>
<Field name="tenantFilter">{(props) => <TenantSelector />}</Field>
</CCol>
</CRow>
<CRow>
<hr />
</CRow>
<CRow className="mb-3">
<CCol>
<h3 className="underline mb-4">Identity</h3>
<RFFCFormSwitch name="users" label="User List" />
<RFFCFormSwitch name="groups" label="Groups" />
<h3 className="underline mb-4">Conditional Access</h3>
<RFFCFormSwitch name="ca" label="Conditional Access" />
<RFFCFormSwitch name="namedlocations" label="Named Locations" />
<RFFCFormSwitch name="authstrengths" label="Authentication Strengths" />
<h3 className="underline mb-4">Intune</h3>
<RFFCFormSwitch
name="intuneconfig"
label="Intune Configuration Policies"
/>
<RFFCFormSwitch
name="intunecompliance"
label="Intune Compliance Policies"
/>
<RFFCFormSwitch
name="intuneprotection"
label="Intune Protection Policies"
/>
<h3 className="underline mb-4">CIPP</h3>
<RFFCFormSwitch name="CippAlerts" label="Alerts Configuration" />
<RFFCFormSwitch name="CippStandards" label="Standards Configuration" />
</CCol>
</CRow>
{postResults.isSuccess && (
<CCallout color="success">
<li>{postResults.data.Results}</li>
</CCallout>
)}
{getResults.isFetching && (
<CCallout color="info">
<CSpinner>Loading</CSpinner>
</CCallout>
)}
{getResults.isSuccess && (
<CCallout color="info">{getResults.data?.Results}</CCallout>
)}
{getResults.isError && (
<CCallout color="danger">
Could not connect to API: {getResults.error.message}
</CCallout>
)}
</CForm>
)
}}
/>
</CippButtonCard>
</CCol>
<CCol md={8}>
<CippPageList
key={refreshState}
capabilities={{
allTenants: true,
helpContext: 'https://google.com',
}}
title="Backup Tasks"
tenantSelector={false}
datatable={{
tableProps: {
selectableRows: true,
actionsList: [
{
label: 'Delete task',
modal: true,
modalUrl: `/api/RemoveScheduledItem?&ID=!RowKey`,
modalMessage: 'Do you want to delete this job?',
},
],
},
keyField: 'id',
columns,
reportName: `Scheduled-Jobs`,
path: `/api/ListScheduledItems?RefreshGuid=${refreshState}&showHidden=true&Type=New-CIPPBackup`,
}}
/>
</CCol>
</CRow>
</>
</CippPage>
)
}

export default CreateBackup
Loading

0 comments on commit f3e0e35

Please sign in to comment.