Skip to content

Commit 46e5d95

Browse files
authored
Merge pull request #2418 from JohnDuprey/dev
Replace maintenance page with Durable Functions
2 parents c44694c + 0cafcaa commit 46e5d95

File tree

2 files changed

+156
-142
lines changed

2 files changed

+156
-142
lines changed

src/components/contentcards/CippChartCard.jsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React from 'react'
22
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
3-
import { CCard, CCardBody, CCardFooter, CCardHeader, CCardTitle } from '@coreui/react'
3+
import { CButton, CCard, CCardBody, CCardFooter, CCardHeader, CCardTitle } from '@coreui/react'
44
import Skeleton from 'react-loading-skeleton'
55
import { CChart } from '@coreui/react-chartjs'
66
import { getStyle } from '@coreui/utils'
7+
import PropTypes from 'prop-types'
78

89
export default function CippChartCard({
910
title,
@@ -13,12 +14,22 @@ export default function CippChartCard({
1314
ChartType = 'pie',
1415
LegendLocation = 'bottom',
1516
isFetching,
17+
refreshFunction,
1618
}) {
1719
return (
1820
<CCard className="h-100 mb-3">
1921
<CCardHeader>
2022
<CCardTitle>
2123
{titleType === 'big' ? <h3 className="underline mb-3">{title}</h3> : title}
24+
{refreshFunction && (
25+
<CButton
26+
className="position-absolute top-0 end-0 mt-2 me-2"
27+
variant="ghost"
28+
onClick={refreshFunction}
29+
>
30+
<FontAwesomeIcon icon="sync" />
31+
</CButton>
32+
)}
2233
</CCardTitle>
2334
</CCardHeader>
2435
<CCardBody>
@@ -30,6 +41,7 @@ export default function CippChartCard({
3041
labels: ChartLabels,
3142
datasets: [
3243
{
44+
label: title,
3345
backgroundColor: [
3446
getStyle('--cyberdrain-warning'),
3547
getStyle('--cyberdrain-info'),
@@ -59,3 +71,13 @@ export default function CippChartCard({
5971
</CCard>
6072
)
6173
}
74+
CippChartCard.propTypes = {
75+
title: PropTypes.string.isRequired,
76+
titleType: PropTypes.oneOf(['normal', 'big']),
77+
ChartData: PropTypes.array.isRequired,
78+
ChartLabels: PropTypes.array.isRequired,
79+
ChartType: PropTypes.oneOf(['pie', 'bar', 'line']),
80+
LegendLocation: PropTypes.oneOf(['top', 'bottom', 'left', 'right']),
81+
isFetching: PropTypes.bool,
82+
refreshFunction: PropTypes.func,
83+
}
Lines changed: 133 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,161 +1,153 @@
11
import React, { useState } from 'react'
2-
import { useLazyGenericGetRequestQuery } from 'src/store/api/app.js'
3-
import {
4-
CButton,
5-
CCard,
6-
CCardBody,
7-
CCardHeader,
8-
CCardTitle,
9-
CCol,
10-
CForm,
11-
CRow,
12-
} from '@coreui/react'
13-
import { Form } from 'react-final-form'
14-
import Skeleton from 'react-loading-skeleton'
15-
import { RFFCFormSelect } from 'src/components/forms/index.js'
2+
import { useGenericGetRequestQuery, useLazyGenericGetRequestQuery } from 'src/store/api/app.js'
3+
import { CButton, CCallout, CCol, CRow, CSpinner } from '@coreui/react'
4+
import CippChartCard from 'src/components/contentcards/CippChartCard'
5+
import { CippDatatable, CippTable, cellDateFormatter } from 'src/components/tables'
6+
import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat'
7+
import { CippCallout, CippContentCard } from 'src/components/layout'
168
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
17-
import { faLink, faScroll } from '@fortawesome/free-solid-svg-icons'
18-
import { CippCodeBlock } from 'src/components/utilities/index.js'
19-
import { Buffer } from 'buffer'
9+
import CippButtonCard from 'src/components/contentcards/CippButtonCard'
10+
import { ModalService } from 'src/components/utilities'
2011

2112
/**
2213
* Performs maintenance operations on settings.
2314
*
2415
* @returns {JSX.Element} The JSX element representing the settings maintenance component.
2516
*/
2617
export function SettingsMaintenance() {
27-
const [selectedScript, setSelectedScript] = useState()
28-
const [listBackend, listBackendResult] = useLazyGenericGetRequestQuery()
29-
const [listScript, listScriptResult] = useLazyGenericGetRequestQuery()
30-
const [listScriptLink, listScriptLinkResult] = useLazyGenericGetRequestQuery()
18+
const orchestrators = useGenericGetRequestQuery({
19+
path: '/api/ExecDurableFunctions',
20+
params: { Action: 'ListOrchestrators' },
21+
})
22+
const durableStats = useGenericGetRequestQuery({
23+
path: '/api/ExecDurableFunctions',
24+
params: { Action: 'ListStats' },
25+
})
3126

32-
const handleSubmit = async (values) => {
33-
listScript({ path: 'api/ExecMaintenanceScripts', params: values })
34-
setSelectedScript(values.ScriptFile)
35-
}
27+
const [resetDurables, resetDurableStatus] = useLazyGenericGetRequestQuery()
3628

37-
const handleGetLink = () => {
38-
listScriptLink({
39-
path: 'api/ExecMaintenanceScripts',
40-
params: { ScriptFile: selectedScript, MakeLink: 'True' },
29+
const handleResetDurables = () => {
30+
ModalService.confirm({
31+
title: 'Confirm',
32+
body: <div>Are you sure you want to reset all Durable Orchestrators?</div>,
33+
onConfirm: () =>
34+
resetDurables({
35+
path: '/api/ExecDurableFunctions',
36+
params: { Action: 'ResetDurables' },
37+
}).then(() => {
38+
orchestrators.refetch()
39+
durableStats.refetch()
40+
}),
41+
confirmLabel: 'Reset',
42+
cancelLabel: 'Cancel',
4143
})
4244
}
45+
46+
const ResetButton = (
47+
<CButton onClick={handleResetDurables} color="danger">
48+
<FontAwesomeIcon icon="trash" /> Reset Durables
49+
</CButton>
50+
)
51+
4352
return (
44-
<>
45-
{listBackendResult.isUninitialized && listBackend({ path: 'api/ExecMaintenanceScripts' })}
46-
<CRow>
47-
<CCol>
48-
<CCard className="options-card">
49-
<CCardHeader>
50-
<CCardTitle className="d-flex justify-content-between">Maintenance</CCardTitle>
51-
</CCardHeader>
52-
<CCardBody>
53-
<Form
54-
initialValues={{}}
55-
onSubmit={handleSubmit}
56-
render={({ handleSubmit, submitting, values }) => {
57-
return (
58-
<CForm onSubmit={handleSubmit}>
59-
{listBackendResult.isFetching && (
60-
<>
61-
<CRow>
62-
<CCol>
63-
<Skeleton count={5} />
64-
</CCol>
65-
</CRow>
66-
</>
67-
)}
68-
{!listBackendResult.isFetching && listBackendResult.isSuccess && (
69-
<>
70-
<CRow>
71-
<CCol>
72-
<RFFCFormSelect
73-
name="ScriptFile"
74-
label="Script File"
75-
placeholder="-- Select a script --"
76-
values={listBackendResult.data.ScriptFiles}
77-
/>
78-
</CCol>
79-
</CRow>
80-
<CRow className="mb-3">
81-
<CCol>
82-
<CButton type="submit" disabled={submitting}>
83-
<FontAwesomeIcon icon={faScroll} className="me-2" />
84-
Load Script
85-
</CButton>
86-
</CCol>
87-
</CRow>
88-
</>
89-
)}
90-
</CForm>
91-
)
92-
}}
93-
/>
94-
</CCardBody>
95-
</CCard>
53+
<div className="mh-100">
54+
<CRow className="mb-3">
55+
<CCol sm={12} md={5} className="mh-25">
56+
<CippChartCard
57+
title="Durable Functions"
58+
titleType="big"
59+
ChartType="bar"
60+
ChartLabels={durableStats.data?.Queues?.map((queue) => {
61+
return queue?.Name
62+
})}
63+
ChartData={durableStats.data?.Queues?.map((queue) => {
64+
return queue?.ApproximateMessageCount
65+
})}
66+
isFetching={durableStats.isLoading}
67+
refreshFunction={() => durableStats.refetch()}
68+
/>
69+
</CCol>
70+
<CCol sm={12} md={3} className="mh-25">
71+
<CippChartCard
72+
title="Status"
73+
titleType="big"
74+
ChartType="pie"
75+
ChartLabels={durableStats.data?.Orchestrators?.map((status) => {
76+
return status.Name
77+
})}
78+
ChartData={durableStats?.data?.Orchestrators?.map((status) => {
79+
return status.Count
80+
})}
81+
isFetching={durableStats.isLoading}
82+
refreshFunction={() => durableStats.refetch()}
83+
/>
84+
</CCol>
85+
<CCol sm={12} md={4}>
86+
<CippButtonCard title="Troubleshooting" titleType="big" CardButton={ResetButton}>
87+
<small>
88+
<p>Use these actions when troubleshooting performance issues with the backend.</p>
89+
<p>
90+
<b>NOTE: Resetting durables will terminate any running processes.</b>
91+
</p>
92+
</small>
93+
94+
{resetDurableStatus.isFetching && <CSpinner className="ms-2" />}
95+
{!resetDurableStatus.isFetching && resetDurableStatus.isSuccess && (
96+
<CippCallout color="info" dismissible>
97+
{resetDurableStatus?.data?.Message}
98+
</CippCallout>
99+
)}
100+
</CippButtonCard>
96101
</CCol>
97102
</CRow>
98-
<CRow>
99-
<CCol>
100-
{listScriptResult.isFetching && (
101-
<CCard className="h-100">
102-
<CCardBody>
103-
<Skeleton count={10} />
104-
</CCardBody>
105-
</CCard>
106-
)}
107-
{!listScriptResult.isFetching && listScriptResult.isSuccess && (
108-
<CCard className="h-100">
109-
<CCardHeader>
110-
<CCardTitle>Script Details</CCardTitle>
111-
</CCardHeader>
112-
<CCardBody>
113-
<p>
114-
<CButton type="submit" onClick={handleGetLink}>
115-
<FontAwesomeIcon icon={faLink} className="me-2" />
116-
Create Link
117-
</CButton>
118-
</p>
119-
{listScriptLinkResult.isSuccess && (
120-
<p>
121-
{listScriptLinkResult.data.Link !== undefined && (
122-
<>
123-
<p>
124-
Copy this text into a PowerShell terminal, we recommend Azure Cloud Shell.
125-
Azure modules and the az command line utilties are required for these
126-
scripts to work. The link is valid for 5 minutes.
127-
</p>
128-
<CippCodeBlock
129-
language="text"
130-
showLineNumbers={false}
131-
wrapLongLines={true}
132-
code={
133-
'irm ' +
134-
window.location.origin +
135-
listScriptLinkResult.data.Link +
136-
' | iex'
137-
}
138-
/>
139-
</>
140-
)}
141-
</p>
142-
)}
143-
{listScriptResult.data.ScriptContent !== undefined && (
144-
<p>
145-
<h5>Maintenance Script Contents</h5>
146-
<CippCodeBlock
147-
language="powershell"
148-
showLineNumbers={true}
149-
wrapLongLines={false}
150-
code={Buffer.from(listScriptResult.data.ScriptContent, 'base64').toString()}
151-
/>
152-
</p>
153-
)}
154-
</CCardBody>
155-
</CCard>
156-
)}
103+
<CRow className="mb-3">
104+
<CCol className="mh-75">
105+
<CippContentCard title="Orchestrators" titleType="big">
106+
<CippTable
107+
data={orchestrators?.data?.Orchestrators}
108+
columns={[
109+
{
110+
name: 'Created',
111+
selector: (row) => row['CreatedTime'],
112+
sortable: true,
113+
cell: cellDateFormatter({ format: 'short' }),
114+
},
115+
{
116+
name: 'Completed',
117+
selector: (row) => row?.CompletedTime,
118+
sortable: true,
119+
cell: cellDateFormatter({ format: 'short' }),
120+
},
121+
{
122+
name: 'Name',
123+
selector: (row) => row['Name'],
124+
sortable: true,
125+
cell: cellGenericFormatter(),
126+
},
127+
{
128+
name: 'Status',
129+
selector: (row) => row['RuntimeStatus'],
130+
sortable: true,
131+
cell: cellGenericFormatter(),
132+
},
133+
{
134+
name: 'Input',
135+
selector: (row) => row['Input'],
136+
cell: cellGenericFormatter(),
137+
},
138+
]}
139+
filterlist={[
140+
{ filterName: 'Running', filter: 'Complex: RuntimeStatus eq Running' },
141+
{ filterName: 'Pending', filter: 'Complex: RuntimeStatus eq Pending' },
142+
{ filterName: 'Completed', filter: 'Complex: RuntimeStatus eq Completed' },
143+
{ filterName: 'Failed', filter: 'Complex: RuntimeStatus eq Failed' },
144+
]}
145+
isFetching={orchestrators.isFetching}
146+
refreshFunction={() => orchestrators.refetch()}
147+
/>
148+
</CippContentCard>
157149
</CCol>
158150
</CRow>
159-
</>
151+
</div>
160152
)
161153
}

0 commit comments

Comments
 (0)