Skip to content

Commit

Permalink
Graph Explorer Scheduler
Browse files Browse the repository at this point in the history
Add CippScheduleOffcanvas
  • Loading branch information
JohnDuprey committed May 27, 2024
1 parent 25f3801 commit 5059a03
Show file tree
Hide file tree
Showing 3 changed files with 326 additions and 1 deletion.
273 changes: 273 additions & 0 deletions src/components/utilities/CippScheduleOffcanvas.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import {
CButton,
CCallout,
CCard,
CCardBody,
CCardHeader,
CCol,
CForm,
CRow,
CSpinner,
CTooltip,
} from '@coreui/react'
import { CippOffcanvas, TenantSelector } from '.'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { Field, Form, FormSpy } from 'react-final-form'
import arrayMutators from 'final-form-arrays'
import {
RFFCFormInput,
RFFCFormInputArray,
RFFCFormSwitch,
RFFSelectSearch,
} from 'src/components/forms'
import { useSelector } from 'react-redux'
import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app'
import DatePicker from 'react-datepicker'
import 'react-datepicker/dist/react-datepicker.css'

export default function CippScheduleOffcanvas({
state: visible,
hideFunction,
title,
placement,
...props
}) {
const currentDate = new Date()
const [startDate, setStartDate] = useState(currentDate)
const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName)
const [refreshState, setRefreshState] = useState(false)
const taskName = `Scheduled Task ${currentDate.toLocaleString()}`
const { data: availableCommands = [], isLoading: isLoadingcmd } = useGenericGetRequestQuery({
path: 'api/ListFunctionParameters?Module=CIPPCore',
})

const recurrenceOptions = [
{ value: '0', name: 'Only once' },
{ value: '1', name: 'Every 1 day' },
{ value: '7', name: 'Every 7 days' },
{ value: '30', name: 'Every 30 days' },
{ value: '365', name: 'Every 365 days' },
]

const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery()
const onSubmit = (values) => {
const unixTime = Math.floor(startDate.getTime() / 1000)
const shippedValues = {
TenantFilter: tenantDomain,
Name: values.taskName,
Command: values.command,
Parameters: values.parameters,
ScheduledTime: unixTime,
Recurrence: values.Recurrence,
AdditionalProperties: values.additional,
PostExecution: {
Webhook: values.webhook,
Email: values.email,
PSA: values.psa,
},
}
genericPostRequest({ path: '/api/AddScheduledItem', values: shippedValues }).then((res) => {
setRefreshState(res.requestId)
})
}

return (
<CippOffcanvas
placement={placement}
title={title}
visible={visible}
hideFunction={hideFunction}
>
<CCard>
<CCardHeader></CCardHeader>
<CCardBody>
<Form
onSubmit={onSubmit}
mutators={{
...arrayMutators,
}}
initialValues={{ ...props.initialValues }}
render={({ handleSubmit, submitting, values }) => {
return (
<CForm onSubmit={handleSubmit}>
<CRow className="mb-3">
<CCol>
<label>Tenant</label>
<Field name="tenantFilter">{(props) => <TenantSelector />}</Field>
</CCol>
</CRow>
<CRow>
<CCol>
<RFFCFormInput
type="text"
name="taskName"
label="Task Name"
firstValue={`Task ${currentDate.toLocaleString()}`}
/>
</CCol>
</CRow>
<CRow>
<CCol>
<label>Scheduled Date</label>
<DatePicker
className="form-control mb-3"
selected={startDate}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={15}
dateFormat="Pp"
onChange={(date) => setStartDate(date)}
/>
</CCol>
</CRow>
<CRow className="mb-3">
<CCol>
<RFFSelectSearch
values={recurrenceOptions}
name="Recurrence"
placeholder="Select a recurrence"
label="Recurrence"
/>
</CCol>
</CRow>
<CRow className="mb-3">
<CCol>
<RFFSelectSearch
values={availableCommands.map((cmd) => ({
value: cmd.Function,
name: cmd.Function,
}))}
name="command"
placeholder={
isLoadingcmd ? (
<CSpinner size="sm" />
) : (
'Select a command or report to execute.'
)
}
label="Command to execute"
/>
</CCol>
</CRow>
<FormSpy>
{/* eslint-disable react/prop-types */}
{(props) => {
const selectedCommand = availableCommands.find(
(cmd) => cmd.Function === props.values.command?.value,
)
return (
<CRow className="mb-3">
<CCol>{selectedCommand?.Synopsis}</CCol>
</CRow>
)
}}
</FormSpy>
<CRow>
<FormSpy>
{/* eslint-disable react/prop-types */}
{(props) => {
const selectedCommand = availableCommands.find(
(cmd) => cmd.Function === props.values.command?.value,
)
let paramblock = null
if (selectedCommand) {
//if the command parameter type is boolean we use <RFFCFormCheck /> else <RFFCFormInput />.
const parameters = selectedCommand.Parameters
if (parameters.length > 0) {
paramblock = parameters.map((param, idx) => (
<CRow key={idx} className="mb-3">
<CTooltip
content={
param?.Description !== null
? param.Description
: 'No Description'
}
placement="left"
>
<CCol>
{param.Type === 'System.Boolean' ||
param.Type ===
'System.Management.Automation.SwitchParameter' ? (
<>
<label>{param.Name}</label>
<RFFCFormSwitch
initialValue={false}
name={`parameters.${param.Name}`}
label={`True`}
/>
</>
) : (
<>
{param.Type === 'System.Collections.Hashtable' ? (
<RFFCFormInputArray
name={`parameters.${param.Name}`}
label={`${param.Name}`}
key={idx}
/>
) : (
<RFFCFormInput
type="text"
key={idx}
name={`parameters.${param.Name}`}
label={`${param.Name}`}
/>
)}
</>
)}
</CCol>
</CTooltip>
</CRow>
))
}
}
return paramblock
}}
</FormSpy>
</CRow>
<CRow className="mb-3">
<CCol>
<RFFCFormInputArray name={`additional`} label="Additional Properties" />
</CCol>
</CRow>
<CRow className="mb-3">
<CCol>
<label>Send results to</label>
<RFFCFormSwitch name="webhook" label="Webhook" />
<RFFCFormSwitch name="email" label="E-mail" />
<RFFCFormSwitch name="psa" label="PSA" />
</CCol>
</CRow>
<CRow className="mb-3">
<CCol md={6}>
<CButton type="submit" disabled={submitting}>
Add Schedule
{postResults.isFetching && (
<FontAwesomeIcon icon="circle-notch" spin className="ms-2" size="1x" />
)}
</CButton>
</CCol>
</CRow>
{postResults.isSuccess && (
<CCallout color="success">
<li>{postResults.data.Results}</li>
</CCallout>
)}
</CForm>
)
}}
/>
</CCardBody>
</CCard>
</CippOffcanvas>
)
}

CippScheduleOffcanvas.propTypes = {
groups: PropTypes.array,
placement: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
state: PropTypes.bool,
hideFunction: PropTypes.func.isRequired,
}
1 change: 0 additions & 1 deletion src/views/cipp/Scheduler.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ const Scheduler = () => {
if (typeof row?.Parameters[key] === 'object') {
var nestedParamList = []
Object.keys(row?.Parameters[key]).forEach((nestedKey) => {
console.log(nestedKey)
nestedParamList.push({
Key: nestedKey,
Value: row?.Parameters[key][nestedKey],
Expand Down
53 changes: 53 additions & 0 deletions src/views/tenant/administration/GraphExplorer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat'
import PropTypes from 'prop-types'
import { CippCodeOffCanvas, ModalService } from 'src/components/utilities'
import { debounce } from 'lodash-es'
import CippScheduleOffcanvas from 'src/components/utilities/CippScheduleOffcanvas'

const GraphExplorer = () => {
const tenant = useSelector((state) => state.app.currentTenant)
Expand All @@ -57,6 +58,8 @@ const GraphExplorer = () => {
error: presetsError,
} = useGenericGetRequestQuery({ path: '/api/ListGraphExplorerPresets', params: { random2 } })
const QueryColumns = { set: false, data: [] }
const [scheduleVisible, setScheduleVisible] = useState(false)
const [scheduleValues, setScheduleValues] = useState({})

const debounceEndpointChange = useMemo(() => {
function endpointChange(value) {
Expand Down Expand Up @@ -148,6 +151,36 @@ const GraphExplorer = () => {
})
}

function handleSchedule(values) {
var graphParameters = []
const paramNames = ['$filter', '$format', '$search', '$select', '$top']
paramNames.map((param) => {
if (values[param]) {
if (Array.isArray(values[param])) {
graphParameters.push({ Key: param, Value: values[param].map((p) => p.value).join(',') })
} else {
graphParameters.push({ Key: param, Value: values[param] })
}
}
})

const reportName = values.name ?? 'Graph Explorer'
const shippedValues = {
taskName: reportName + ' - ' + tenant.displayName,
command: { label: 'Get-GraphRequestList', value: 'Get-GraphRequestList' },
parameters: {
Parameters: graphParameters,
NoPagination: values.NoPagination,
ReverseTenantLookup: values.ReverseTenantLookup,
ReverseTenantLookupProperty: values.ReverseTenantLookupProperty,
Endpoint: values.endpoint,
SkipCache: true,
},
}
setScheduleValues(shippedValues)
setScheduleVisible(true)
}

const presets = [
{
name: 'All users with email addresses',
Expand Down Expand Up @@ -617,6 +650,19 @@ const GraphExplorer = () => {
<FontAwesomeIcon className="me-2" icon={faSearch} />
Query
</CButton>
<FormSpy>
{(props) => {
return (
<CButton
onClick={() => handleSchedule(props.values)}
className="ms-2"
>
<FontAwesomeIcon className="me-2" icon="calendar-alt" />
Schedule Report
</CButton>
)
}}
</FormSpy>
</CCol>
</CRow>
</CForm>
Expand All @@ -628,6 +674,13 @@ const GraphExplorer = () => {
</CCollapse>
</CCol>
</CRow>
<CippScheduleOffcanvas
title="Schedule Report"
state={scheduleVisible}
placement="end"
hideFunction={() => setScheduleVisible(false)}
initialValues={scheduleValues}
/>
<hr />
<CippPage title="Report Results" tenantSelector={false}>
{!searchNow && <span>Execute a search to get started.</span>}
Expand Down

0 comments on commit 5059a03

Please sign in to comment.