Skip to content

Commit 615ec5c

Browse files
authored
Merge pull request #5 from KelvinTegelaar/main
[pull] main from KelvinTegelaar:main
2 parents 6424422 + c925e7d commit 615ec5c

File tree

56 files changed

+3700
-2300
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+3700
-2300
lines changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cipp",
3-
"version": "8.7.0",
3+
"version": "8.7.1",
44
"author": "CIPP Contributors",
55
"homepage": "https://cipp.app/",
66
"bugs": {
@@ -89,8 +89,6 @@
8989
"react-leaflet": "5.0.0",
9090
"react-leaflet-markercluster": "^5.0.0-rc.0",
9191
"react-markdown": "10.1.0",
92-
"rehype-raw": "^7.0.0",
93-
"remark-gfm": "^3.0.1",
9492
"react-media-hook": "^0.5.0",
9593
"react-papaparse": "^4.4.0",
9694
"react-quill": "^2.0.0",
@@ -103,6 +101,8 @@
103101
"redux-devtools-extension": "2.13.9",
104102
"redux-persist": "^6.0.0",
105103
"redux-thunk": "3.1.0",
104+
"rehype-raw": "^7.0.0",
105+
"remark-gfm": "^3.0.1",
106106
"simplebar": "6.3.2",
107107
"simplebar-react": "3.3.2",
108108
"stylis-plugin-rtl": "2.1.1",
@@ -114,4 +114,4 @@
114114
"eslint": "9.35.0",
115115
"eslint-config-next": "15.5.2"
116116
}
117-
}
117+
}

public/version.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"version": "8.7.0"
3-
}
2+
"version": "8.7.1"
3+
}

src/components/CippCards/CippPageCard.jsx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,12 @@ const CippPageCard = (props) => {
2525
<Box
2626
sx={{
2727
flexGrow: 1,
28-
py: 4,
28+
pb: 4,
2929
}}
3030
>
3131
<Container maxWidth={cardSize}>
3232
<Stack spacing={2}>
3333
<Stack spacing={2}>
34-
<div>
35-
{!hideBackButton && (
36-
<Button
37-
color="inherit"
38-
onClick={handleBackClick} // Go back to the previous page
39-
startIcon={
40-
<SvgIcon fontSize="small">
41-
<ArrowLeftIcon />
42-
</SvgIcon>
43-
}
44-
>
45-
{backButtonTitle}
46-
</Button>
47-
)}
48-
</div>
4934
{hideTitleText !== true && (
5035
<div>
5136
<Typography variant="h4">{title}</Typography>

src/components/CippComponents/CippApiDialog.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const CippApiDialog = (props) => {
4141
}
4242

4343
const formHook = useForm({
44-
defaultValues: defaultvalues || {},
44+
defaultValues: typeof defaultvalues === "function" ? defaultvalues(row) : defaultvalues || {},
4545
mode: "onChange", // Enable real-time validation
4646
});
4747

src/components/CippComponents/CippApiLogsDrawer.jsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const CippApiLogsDrawer = ({
1010
apiFilter = null,
1111
tenantFilter = null,
1212
standardFilter = null,
13+
scheduledTaskFilter = null,
1314
requiredPermissions = [],
1415
PermissionButton = Button,
1516
title = "API Logs",
@@ -28,7 +29,9 @@ export const CippApiLogsDrawer = ({
2829
// Build the API URL with the filter
2930
const apiUrl = `/api/ListLogs?Filter=true${apiFilter ? `&API=${apiFilter}` : ""}${
3031
tenantFilter ? `&Tenant=${tenantFilter}` : ""
31-
}${standardFilter ? `&StandardTemplateId=${standardFilter}` : ""}`;
32+
}${standardFilter ? `&StandardTemplateId=${standardFilter}` : ""}${
33+
scheduledTaskFilter ? `&ScheduledTaskId=${scheduledTaskFilter}` : ""
34+
}`;
3235

3336
// Define the columns for the logs table
3437
const simpleColumns = [
@@ -74,7 +77,9 @@ export const CippApiLogsDrawer = ({
7477
url: apiUrl,
7578
dataKey: "",
7679
}}
77-
queryKey={`APILogs-${apiFilter || "All"}`}
80+
queryKey={`APILogs-${apiFilter || "All"}-${tenantFilter || "AllTenants"}-${
81+
standardFilter || "NoStandard"
82+
}-${scheduledTaskFilter || "NoTask"}`}
7883
simpleColumns={simpleColumns}
7984
exportEnabled={true}
8085
offCanvas={{

src/components/CippComponents/CippApiResults.jsx

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,27 @@ export const CippApiResults = (props) => {
158158

159159
const allResults = useMemo(() => {
160160
const apiResults = extractAllResults(correctResultObj);
161+
162+
// Also extract error results if there's an error
163+
if (apiObject.isError && apiObject.error) {
164+
const errorResults = extractAllResults(apiObject.error.response.data);
165+
if (errorResults.length > 0) {
166+
// Mark all error results with error severity and merge with success results
167+
return [...apiResults, ...errorResults.map((r) => ({ ...r, severity: "error" }))];
168+
}
169+
170+
// Fallback to getCippError if extraction didn't work
171+
const processedError = getCippError(apiObject.error);
172+
if (typeof processedError === "string") {
173+
return [
174+
...apiResults,
175+
{ text: processedError, copyField: processedError, severity: "error" },
176+
];
177+
}
178+
}
179+
161180
return apiResults;
162-
}, [correctResultObj]);
181+
}, [correctResultObj, apiObject.isError, apiObject.error]);
163182

164183
useEffect(() => {
165184
setErrorVisible(!!apiObject.isError);
@@ -250,31 +269,8 @@ export const CippApiResults = (props) => {
250269
</Alert>
251270
</Collapse>
252271
)}
253-
{/* Error alert */}
254-
<Collapse in={errorVisible} unmountOnExit>
255-
{apiObject.isError && (
256-
<Alert
257-
sx={alertSx}
258-
variant="filled"
259-
severity="error"
260-
action={
261-
<IconButton
262-
aria-label="close"
263-
color="inherit"
264-
size="small"
265-
onClick={() => setErrorVisible(false)}
266-
>
267-
<Close fontSize="inherit" />
268-
</IconButton>
269-
}
270-
>
271-
{getCippError(apiObject.error)}
272-
</Alert>
273-
)}
274-
</Collapse>
275-
276272
{/* Individual result alerts */}
277-
{apiObject.isSuccess && !errorsOnly && hasVisibleResults && (
273+
{hasVisibleResults && (
278274
<>
279275
{finalResults.map((resultObj) => (
280276
<React.Fragment key={resultObj.id}>

src/components/CippComponents/CippAutocomplete.jsx

Lines changed: 91 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
createFilterOptions,
66
TextField,
77
IconButton,
8+
Tooltip,
89
} from "@mui/material";
910
import { useEffect, useState, useMemo, useCallback, useRef } from "react";
1011
import { useSettings } from "../../hooks/use-settings";
@@ -89,6 +90,7 @@ export const CippAutoComplete = (props) => {
8990
const [offCanvasVisible, setOffCanvasVisible] = useState(false);
9091
const [fullObject, setFullObject] = useState(null);
9192
const [internalValue, setInternalValue] = useState(null); // Track selected value internally
93+
const [open, setOpen] = useState(false); // Control popover open state
9294

9395
// Sync internalValue when external value or defaultValue prop changes (e.g., when editing a form)
9496
useEffect(() => {
@@ -295,6 +297,15 @@ export const CippAutoComplete = (props) => {
295297
<Autocomplete
296298
ref={autocompleteRef}
297299
key={stableKey}
300+
open={open}
301+
onOpen={() => setOpen(true)}
302+
onClose={(event, reason) => {
303+
// Keep open if Tab was used in multiple mode
304+
if (reason === "selectOption" && multiple && event?.type === "click") {
305+
return;
306+
}
307+
setOpen(false);
308+
}}
298309
disabled={disabled || actionGetRequest.isFetching || isFetching}
299310
popupIcon={
300311
actionGetRequest.isFetching || isFetching ? (
@@ -425,6 +436,17 @@ export const CippAutoComplete = (props) => {
425436
event.preventDefault();
426437
// Trigger a click on the highlighted option
427438
highlightedOption.click();
439+
440+
// In multiple mode, keep the popover open and refocus
441+
if (multiple) {
442+
setTimeout(() => {
443+
setOpen(true);
444+
const input = autocompleteRef.current?.querySelector("input");
445+
if (input) {
446+
input.focus();
447+
}
448+
}, 50);
449+
}
428450
}
429451
}
430452
}}
@@ -439,79 +461,83 @@ export const CippAutoComplete = (props) => {
439461
{...other}
440462
/>
441463
{api?.url && api?.showRefresh && (
442-
<IconButton
443-
size="small"
444-
onClick={() => {
445-
actionGetRequest.refetch();
446-
}}
447-
>
448-
<Sync />
449-
</IconButton>
464+
<Tooltip title="Refresh">
465+
<IconButton
466+
size="small"
467+
onClick={() => {
468+
actionGetRequest.refetch();
469+
}}
470+
>
471+
<Sync />
472+
</IconButton>
473+
</Tooltip>
450474
)}
451475
{api?.templateView && (
452-
<IconButton
453-
size="small"
454-
onClick={() => {
455-
// Use internalValue if value prop is not available
456-
const currentValue = value || internalValue;
457-
458-
// Get the full object from the selected value
459-
if (multiple) {
460-
// For multiple selection, get all full objects
461-
const fullObjects = currentValue
462-
.map((v) => {
463-
const valueToFind = v?.value || v;
464-
const found = usedOptions.find((opt) => opt.value === valueToFind);
465-
let rawData = found?.rawData;
466-
467-
// If property is specified, extract and parse JSON from that property
468-
if (rawData && api?.templateView?.property) {
469-
try {
470-
const propertyValue = rawData[api.templateView.property];
471-
if (typeof propertyValue === "string") {
472-
rawData = JSON.parse(propertyValue);
473-
} else {
474-
rawData = propertyValue;
476+
<Tooltip title={`View ${api?.templateView.title}` || "View details"}>
477+
<IconButton
478+
size="small"
479+
onClick={() => {
480+
// Use internalValue if value prop is not available
481+
const currentValue = value || internalValue;
482+
483+
// Get the full object from the selected value
484+
if (multiple) {
485+
// For multiple selection, get all full objects
486+
const fullObjects = currentValue
487+
.map((v) => {
488+
const valueToFind = v?.value || v;
489+
const found = usedOptions.find((opt) => opt.value === valueToFind);
490+
let rawData = found?.rawData;
491+
492+
// If property is specified, extract and parse JSON from that property
493+
if (rawData && api?.templateView?.property) {
494+
try {
495+
const propertyValue = rawData[api.templateView.property];
496+
if (typeof propertyValue === "string") {
497+
rawData = JSON.parse(propertyValue);
498+
} else {
499+
rawData = propertyValue;
500+
}
501+
} catch (e) {
502+
console.error("Failed to parse JSON from property:", e);
503+
// Keep original rawData if parsing fails
475504
}
476-
} catch (e) {
477-
console.error("Failed to parse JSON from property:", e);
478-
// Keep original rawData if parsing fails
479505
}
480-
}
481506

482-
return rawData;
483-
})
484-
.filter(Boolean);
485-
setFullObject(fullObjects);
486-
} else {
487-
// For single selection, get the full object
488-
const valueToFind = currentValue?.value || currentValue;
489-
const selectedOption = usedOptions.find((opt) => opt.value === valueToFind);
490-
let rawData = selectedOption?.rawData || null;
491-
492-
// If property is specified, extract and parse JSON from that property
493-
if (rawData && api?.templateView?.property) {
494-
try {
495-
const propertyValue = rawData[api.templateView.property];
496-
if (typeof propertyValue === "string") {
497-
rawData = JSON.parse(propertyValue);
498-
} else {
499-
rawData = propertyValue;
507+
return rawData;
508+
})
509+
.filter(Boolean);
510+
setFullObject(fullObjects);
511+
} else {
512+
// For single selection, get the full object
513+
const valueToFind = currentValue?.value || currentValue;
514+
const selectedOption = usedOptions.find((opt) => opt.value === valueToFind);
515+
let rawData = selectedOption?.rawData || null;
516+
517+
// If property is specified, extract and parse JSON from that property
518+
if (rawData && api?.templateView?.property) {
519+
try {
520+
const propertyValue = rawData[api.templateView.property];
521+
if (typeof propertyValue === "string") {
522+
rawData = JSON.parse(propertyValue);
523+
} else {
524+
rawData = propertyValue;
525+
}
526+
} catch (e) {
527+
console.error("Failed to parse JSON from property:", e);
528+
// Keep original rawData if parsing fails
500529
}
501-
} catch (e) {
502-
console.error("Failed to parse JSON from property:", e);
503-
// Keep original rawData if parsing fails
504530
}
505-
}
506531

507-
setFullObject(rawData);
508-
}
509-
setOffCanvasVisible(true);
510-
}}
511-
title={api?.templateView.title || "View details"}
512-
>
513-
<Visibility />
514-
</IconButton>
532+
setFullObject(rawData);
533+
}
534+
setOffCanvasVisible(true);
535+
}}
536+
title={api?.templateView.title || "View details"}
537+
>
538+
<Visibility />
539+
</IconButton>
540+
</Tooltip>
515541
)}
516542
</Stack>
517543
)}

0 commit comments

Comments
 (0)