Skip to content

Commit 2df3c74

Browse files
authored
Merge pull request #2314 from JohnDuprey/dev
Partner Center webhooks
2 parents 6ce20e0 + 5d0fc9e commit 2df3c74

File tree

3 files changed

+288
-9
lines changed

3 files changed

+288
-9
lines changed

src/components/layout/AppFooter.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ const AppFooter = () => {
3030
<CLink className="me-2" href="https://rewst.io/" target="_blank">
3131
<CImage src={rewst} alt="Rewst" />
3232
</CLink>
33-
<CLink className="me-2" href="https://www.augmentt.com" target="_blank">
33+
<CLink
34+
className="me-2"
35+
href="https://www.augmentt.com/?utm_source=cipp&utm_medium=referral&utm_campaign=2024"
36+
target="_blank"
37+
>
3438
<CImage src={augmentt} alt="Augmentt" />
3539
</CLink>
3640
<CLink className="me-2" href="https://ninjaone.com" target="_blank">

src/views/cipp/app-settings/CIPPSettings.jsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { SettingsLicenses } from 'src/views/cipp/app-settings/SettingsLicenses.j
1111
import { SettingsExtensions } from 'src/views/cipp/app-settings/SettingsExtensions.jsx'
1212
import { SettingsMaintenance } from 'src/views/cipp/app-settings/SettingsMaintenance.jsx'
1313
import { SettingsExtensionMappings } from 'src/views/cipp/app-settings/SettingsExtensionMappings.jsx'
14+
import { SettingsPartner } from 'src/views/cipp/app-settings/SettingsPartner.jsx'
1415

1516
/**
1617
* This function returns the settings page content for CIPP.
@@ -35,15 +36,18 @@ export default function CIPPSettings() {
3536
Notifications
3637
</CNavItem>
3738
<CNavItem active={active === 5} onClick={() => setActive(5)} href="#">
38-
Licenses
39+
Partner Webhooks
3940
</CNavItem>
4041
<CNavItem active={active === 6} onClick={() => setActive(6)} href="#">
41-
Maintenance
42+
Licenses
4243
</CNavItem>
4344
<CNavItem active={active === 7} onClick={() => setActive(7)} href="#">
44-
Extensions
45+
Maintenance
4546
</CNavItem>
4647
<CNavItem active={active === 8} onClick={() => setActive(8)} href="#">
48+
Extensions
49+
</CNavItem>
50+
<CNavItem active={active === 9} onClick={() => setActive(9)} href="#">
4751
Extension Mappings
4852
</CNavItem>
4953
</CNav>
@@ -68,22 +72,26 @@ export default function CIPPSettings() {
6872
<SettingsNotifications />
6973
</CTabPane>
7074
<CTabPane visible={active === 5} className="mt-3">
71-
<CippLazy visible={active === 5}>
72-
<SettingsLicenses />
73-
</CippLazy>
75+
<CippLazy visible={active === 5}></CippLazy>
76+
<SettingsPartner />
7477
</CTabPane>
7578
<CTabPane visible={active === 6} className="mt-3">
7679
<CippLazy visible={active === 6}>
77-
<SettingsMaintenance />
80+
<SettingsLicenses />
7881
</CippLazy>
7982
</CTabPane>
8083
<CTabPane visible={active === 7} className="mt-3">
8184
<CippLazy visible={active === 7}>
82-
<SettingsExtensions />
85+
<SettingsMaintenance />
8386
</CippLazy>
8487
</CTabPane>
8588
<CTabPane visible={active === 8} className="mt-3">
8689
<CippLazy visible={active === 8}>
90+
<SettingsExtensions />
91+
</CippLazy>
92+
</CTabPane>
93+
<CTabPane visible={active === 9} className="mt-3">
94+
<CippLazy visible={active === 9}>
8795
<SettingsExtensionMappings />
8896
</CippLazy>
8997
</CTabPane>
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import {
2+
useGenericGetRequestQuery,
3+
useLazyGenericGetRequestQuery,
4+
useLazyGenericPostRequestQuery,
5+
} from 'src/store/api/app.js'
6+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
7+
import {
8+
CBadge,
9+
CButton,
10+
CCallout,
11+
CCard,
12+
CCardBody,
13+
CCardHeader,
14+
CCardTitle,
15+
CCol,
16+
CForm,
17+
CLink,
18+
CRow,
19+
CSpinner,
20+
} from '@coreui/react'
21+
import { Form } from 'react-final-form'
22+
import { RFFSelectSearch } from 'src/components/forms/index.js'
23+
import React, { useEffect } from 'react'
24+
import { CippCallout } from 'src/components/layout/index.js'
25+
import { CippCodeBlock } from 'src/components/utilities'
26+
import { CellDate } from 'src/components/tables'
27+
28+
/**
29+
* Sets the notification settings.
30+
* @returns {JSX.Element} The notification settings component.
31+
*/
32+
export function SettingsPartner() {
33+
const webhookConfig = useGenericGetRequestQuery({
34+
path: '/api/ExecPartnerWebhook',
35+
params: { Action: 'ListSubscription' },
36+
})
37+
const webhookEvents = useGenericGetRequestQuery({
38+
path: '/api/ExecPartnerWebhook',
39+
params: { Action: 'ListEventTypes' },
40+
})
41+
const [submitWebhook, webhookCreateResult] = useLazyGenericPostRequestQuery()
42+
const [sendTest, sendTestResult] = useLazyGenericGetRequestQuery()
43+
const [checkTest, checkTestResult] = useLazyGenericGetRequestQuery()
44+
45+
const onSubmit = (values) => {
46+
const shippedValues = {
47+
EventType: values?.EventType?.map((event) => event.value),
48+
}
49+
submitWebhook({
50+
path: '/api/ExecPartnerWebhook?Action=CreateSubscription',
51+
values: shippedValues,
52+
}).then((res) => {
53+
webhookConfig.refetch()
54+
})
55+
}
56+
57+
useEffect(() => {
58+
if (
59+
sendTestResult.isSuccess &&
60+
sendTestResult?.data?.Results?.correlationId &&
61+
!checkTestResult?.data?.Results?.results
62+
) {
63+
setTimeout(
64+
checkTest({
65+
path: '/api/ExecPartnerWebhook',
66+
params: {
67+
Action: 'ValidateTest',
68+
CorrelationId: sendTestResult?.data?.Results?.correlationId,
69+
},
70+
}),
71+
1000,
72+
)
73+
}
74+
}, [sendTestResult, checkTest, checkTestResult])
75+
76+
return (
77+
<CCard className="h-100">
78+
<CCardHeader></CCardHeader>
79+
<CCardBody>
80+
<>
81+
<CButton
82+
size="sm"
83+
onClick={() => webhookConfig.refetch()}
84+
className="mb-2"
85+
disabled={webhookConfig.isFetching}
86+
>
87+
{webhookConfig.isFetching ? (
88+
<>
89+
<CSpinner className="me-2" size="sm" /> Loading...
90+
</>
91+
) : (
92+
<>
93+
<FontAwesomeIcon icon="sync" className="me-2" />
94+
Refresh
95+
</>
96+
)}
97+
</CButton>
98+
99+
{!webhookConfig.isFetching && webhookConfig.error && (
100+
<CippCallout color="danger">Error loading data</CippCallout>
101+
)}
102+
{webhookConfig.isSuccess && (
103+
<>
104+
<h3 className="underline mb-5"> Webhook Configuration</h3>
105+
<CRow>
106+
<CCol sm={12} md={6} lg={8} className="mb-3">
107+
Subscribe to Microsoft Partner center webhooks to enable automatic tenant
108+
onboarding and alerting. Updating the settings will replace any existing webhook
109+
subscription with one pointing to CIPP. Refer to the{' '}
110+
<CLink
111+
href="https://learn.microsoft.com/en-us/partner-center/developer/partner-center-webhooks"
112+
target="_blank"
113+
>
114+
Microsoft Partner Center documentation
115+
</CLink>{' '}
116+
for more information on the webhook types.
117+
</CCol>
118+
</CRow>
119+
<CRow>
120+
<CCol sm={12} md={6} className="mb-3">
121+
<p className="fw-lighter">Webhook URL</p>
122+
<CippCodeBlock
123+
code={webhookConfig?.data?.Results?.webhookUrl ?? 'No webhook URL found'}
124+
language="plain"
125+
showLineNumbers={false}
126+
/>
127+
</CCol>
128+
<CCol sm={12} md={6} className="mb-3">
129+
<p className="fw-lighter">Last Updated</p>
130+
<CellDate
131+
cell={webhookConfig?.data?.Results?.lastModifiedTimestamp}
132+
format="short"
133+
/>
134+
</CCol>
135+
<CCol sm={12} md={12} className="mb-3">
136+
<p className="fw-lighter">Subscribed Events</p>
137+
<Form
138+
onSubmit={onSubmit}
139+
initialValues={{
140+
EventType: webhookConfig?.data?.Results?.webhookEvents.map((event) => ({
141+
label: event,
142+
value: event,
143+
})),
144+
}}
145+
render={({ handleSubmit }) => (
146+
<>
147+
<CForm onSubmit={handleSubmit}>
148+
<RFFSelectSearch
149+
name="EventType"
150+
label="Event Types"
151+
values={webhookEvents.data?.Results?.map((event) => ({
152+
name: event,
153+
value: event,
154+
}))}
155+
multi={true}
156+
refreshFunction={() => webhookEvents.refetch()}
157+
helpText="Select the events you want to receive notifications for."
158+
/>
159+
<CButton
160+
type="submit"
161+
color="primary"
162+
className="my-3"
163+
disabled={webhookCreateResult.isFetching}
164+
>
165+
{webhookCreateResult.isFetching ? (
166+
<>
167+
<CSpinner size="sm" className="me-2" />
168+
Saving...
169+
</>
170+
) : (
171+
'Save'
172+
)}
173+
</CButton>
174+
</CForm>
175+
</>
176+
)}
177+
/>
178+
{webhookCreateResult.isSuccess && (
179+
<CippCallout color="info" dismissible>
180+
{webhookCreateResult?.data?.Results}
181+
</CippCallout>
182+
)}
183+
</CCol>
184+
</CRow>
185+
<h3 className="underline mb-5">Webhook Test</h3>
186+
<CRow>
187+
<CCol sm={12} md={12} className="mb-3">
188+
<CButton
189+
className="me-3"
190+
onClick={() =>
191+
sendTest({
192+
path: '/api/ExecPartnerWebhook',
193+
params: { Action: 'SendTest' },
194+
})
195+
}
196+
>
197+
{sendTestResult.isFetching ? (
198+
<>
199+
<CSpinner size="sm" className="me-2" />
200+
Running Test...
201+
</>
202+
) : (
203+
'Start Test'
204+
)}
205+
</CButton>
206+
{checkTestResult.isFetching && !checkTestResult?.data?.Results?.result && (
207+
<>
208+
<CSpinner size="sm" className="me-2" /> Waiting for results
209+
</>
210+
)}
211+
</CCol>
212+
</CRow>
213+
<CRow>
214+
{checkTestResult.isSuccess && (
215+
<>
216+
<CCol sm={12} md={4} className="mb-3">
217+
<p className="fw-lighter">Status</p>
218+
{checkTestResult?.data?.Results.status}
219+
</CCol>
220+
{Array.isArray(checkTestResult?.data?.Results?.results) && (
221+
<>
222+
<CCol sm={12} md={4} className="mb-3">
223+
<p className="fw-lighter">Status Code</p>
224+
<FontAwesomeIcon
225+
icon={
226+
checkTestResult?.data?.Results?.results[0].responseCode == 200
227+
? 'check-circle'
228+
: 'times-circle'
229+
}
230+
color={
231+
checkTestResult?.data?.Results?.results[0].responseCode == 200
232+
? 'green'
233+
: 'red'
234+
}
235+
className="me-2"
236+
/>
237+
{checkTestResult?.data?.Results?.results[0].responseCode}
238+
</CCol>
239+
{checkTestResult?.data?.Results?.results[0].responseMessage !== '' && (
240+
<CCol sm={12} md={4} className="mb-3">
241+
<p className="fw-lighter">Response Message</p>
242+
{checkTestResult.data.Results.results[0].responseMessage}
243+
</CCol>
244+
)}
245+
<CCol sm={12} md={4} className="mb-3">
246+
<p className="fw-lighter">Date/Time</p>
247+
<CellDate
248+
cell={Date(
249+
Date.parse(
250+
checkTestResult?.data?.Results?.results[0].dateTimeUtc + 'Z',
251+
),
252+
).toLocaleString()}
253+
format="short"
254+
/>
255+
</CCol>
256+
</>
257+
)}
258+
</>
259+
)}
260+
</CRow>
261+
</>
262+
)}
263+
</>
264+
</CCardBody>
265+
</CCard>
266+
)
267+
}

0 commit comments

Comments
 (0)