Skip to content

Commit 331e106

Browse files
Merge pull request #3958 from KelvinTegelaar/dev
Dev to release
2 parents c74d16c + 0077702 commit 331e106

File tree

35 files changed

+2054
-600
lines changed

35 files changed

+2054
-600
lines changed

.github/workflows/Close_Stale_Issues_and_PRs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ jobs:
1313
stale-issue-message: 'This issue is stale because it has been open 10 days with no activity. We will close this issue soon. If you want this feature implemented you can contribute it. See: https://docs.cipp.app/dev-documentation/contributing-to-the-code . Please notify the team if you are working on this yourself.'
1414
close-issue-message: 'This issue was closed because it has been stalled for 14 days with no activity.'
1515
stale-issue-label: 'no-activity'
16-
exempt-issue-labels: 'planned'
16+
exempt-issue-labels: 'planned,bug'
1717
days-before-stale: 9
18-
days-before-close: 14
18+
days-before-close: 5

.vscode/extensions.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"recommendations": [
3+
"editorconfig.editorconfig",
4+
"streetsidesoftware.code-spell-checker",
5+
]
6+
}

cspell.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"version": "0.2",
3+
"ignorePaths": [],
4+
"dictionaryDefinitions": [],
5+
"dictionaries": [],
6+
"words": [
7+
"CIPP",
8+
"CIPP-API",
9+
"Entra",
10+
"Intune",
11+
"GDAP",
12+
"OBEE",
13+
"AITM",
14+
"Passwordless",
15+
"Yubikey",
16+
"Sherweb",
17+
"Autotask",
18+
"Datto",
19+
"Syncro",
20+
"ImmyBot",
21+
"Choco",
22+
],
23+
"ignoreWords": [
24+
"CIPPAPI",
25+
"locationcipp",
26+
"TNEF",
27+
"winmail",
28+
"PSTN",
29+
],
30+
"import": []
31+
}

public/version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"version": "7.4.2"
2+
"version": "7.5.0"
33
}
Lines changed: 97 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,102 @@
1+
import React, { useState } from "react";
12
import { Box, Card, Stack, SvgIcon, Typography, Skeleton } from "@mui/material";
23
import Grid from "@mui/material/Grid";
4+
import { CippOffCanvas } from "../CippComponents/CippOffCanvas";
5+
import { CippPropertyListCard } from "./CippPropertyListCard";
36

4-
export const CippInfoBar = ({ data, isFetching }) => (
5-
<Card>
6-
<Grid container>
7-
{data.map((item) => (
8-
<Grid
9-
xs={12}
10-
sm={6}
11-
md={3}
12-
key={item.name}
13-
sx={{
14-
borderBottom: (theme) => ({
15-
xs: `1px solid ${theme.palette.divider}`,
16-
md: "none",
17-
}),
18-
borderRight: (theme) => ({
19-
md: `1px solid ${theme.palette.divider}`,
20-
}),
21-
"&:nth-of-type(3)": {
22-
borderBottom: (theme) => ({
23-
xs: `1px solid ${theme.palette.divider}`,
24-
sm: "none",
25-
}),
26-
},
27-
"&:nth-of-type(4)": {
28-
borderBottom: "none",
29-
borderRight: "none",
30-
},
31-
}}
32-
>
33-
<Stack alignItems="center" direction="row" spacing={2} sx={{ p: 2 }}>
34-
{item?.icon && (
35-
<SvgIcon color={item.color ? item.color : "primary"} fontSize="small">
36-
{item.icon}
37-
</SvgIcon>
38-
)}
39-
<Box
40-
sx={() => {
41-
if (!item?.icon) {
42-
return { pl: 2 };
43-
}
7+
export const CippInfoBar = ({ data, isFetching }) => {
8+
const [visibleIndex, setVisibleIndex] = useState(null);
9+
10+
return (
11+
<Card>
12+
<Grid container>
13+
{data.map((item, index) => (
14+
<>
15+
<Grid
16+
xs={12}
17+
sm={6}
18+
md={3}
19+
key={item.name}
20+
onClick={item.offcanvas ? () => setVisibleIndex(index) : undefined}
21+
sx={{
22+
cursor: item.offcanvas ? "pointer" : "default",
23+
borderBottom: (theme) => ({
24+
xs: `1px solid ${theme.palette.divider}`,
25+
md: "none",
26+
}),
27+
borderRight: (theme) => ({
28+
md: `1px solid ${theme.palette.divider}`,
29+
}),
30+
"&:nth-of-type(3)": {
31+
borderBottom: (theme) => ({
32+
xs: `1px solid ${theme.palette.divider}`,
33+
sm: "none",
34+
}),
35+
},
36+
"&:nth-of-type(4)": {
37+
borderBottom: "none",
38+
borderRight: "none",
39+
},
4440
}}
4541
>
46-
<Typography color="text.secondary" variant="overline">
47-
{item.name}
48-
</Typography>
49-
<Typography variant="h6">
50-
{isFetching ? <Skeleton width={"100%"} /> : item.data}
51-
</Typography>
52-
</Box>
53-
</Stack>
54-
</Grid>
55-
))}
56-
</Grid>
57-
</Card>
58-
);
42+
<Stack alignItems="center" direction="row" spacing={2} sx={{ p: 2 }}>
43+
{item?.icon && (
44+
<SvgIcon color={item.color ? item.color : "primary"} fontSize="small">
45+
{item.icon}
46+
</SvgIcon>
47+
)}
48+
<Box
49+
sx={() => {
50+
if (!item?.icon) {
51+
return { pl: 2 };
52+
}
53+
}}
54+
>
55+
<Typography color="text.secondary" variant="overline">
56+
{item.name}
57+
</Typography>
58+
<Typography variant="h6">
59+
{isFetching ? <Skeleton width={"100%"} /> : item.data}
60+
</Typography>
61+
</Box>
62+
</Stack>
63+
</Grid>
64+
{item.offcanvas && (
65+
<>
66+
{console.log("item.offcanvas", item.offcanvas)}
67+
<CippOffCanvas
68+
title={item?.offcanvas?.title || "Details"}
69+
size="md"
70+
visible={visibleIndex === index}
71+
onClose={() => setVisibleIndex(null)}
72+
>
73+
<Box
74+
sx={{
75+
overflowY: "auto",
76+
maxHeight: "100%",
77+
display: "flex",
78+
flexDirection: "column",
79+
}}
80+
>
81+
<Grid container spacing={1}>
82+
<Grid item xs={12}>
83+
{item?.offcanvas?.propertyItems?.length > 0 && (
84+
<CippPropertyListCard
85+
isFetching={isFetching}
86+
align="vertical"
87+
title={item?.offcanvas?.title}
88+
propertyItems={item.offcanvas.propertyItems ?? []}
89+
/>
90+
)}
91+
</Grid>
92+
</Grid>
93+
</Box>
94+
</CippOffCanvas>
95+
</>
96+
)}
97+
</>
98+
))}
99+
</Grid>
100+
</Card>
101+
);
102+
};
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React, { useState } from "react";
2+
import {
3+
Dialog,
4+
DialogContent,
5+
DialogTitle,
6+
Button,
7+
DialogActions,
8+
Alert,
9+
CircularProgress,
10+
} from "@mui/material";
11+
import { CheckCircle, Error, Sync } from "@mui/icons-material";
12+
import { useForm, FormProvider } from "react-hook-form";
13+
import { CippFormTenantSelector } from "./CippFormTenantSelector";
14+
import { ApiPostCall } from "/src/api/ApiCall";
15+
import { CippApiResults } from "./CippApiResults";
16+
17+
export const BPASyncDialog = ({ createDialog }) => {
18+
const methods = useForm({
19+
defaultValues: {
20+
tenantFilter: {
21+
value: "AllTenants",
22+
label: "*All Tenants",
23+
},
24+
},
25+
});
26+
27+
// Use methods for form handling and control
28+
const { handleSubmit, control } = methods;
29+
30+
const [tenantId, setTenantId] = useState("");
31+
const [isSyncing, setIsSyncing] = useState(false);
32+
33+
// Use ApiGetCall instead of useApiCall
34+
const bpaSyncResults = ApiPostCall({
35+
urlfromdata: true,
36+
});
37+
38+
const handleForm = (values) => {
39+
setTenantId(values.tenantFilter || "");
40+
setIsSyncing(true);
41+
42+
bpaSyncResults.mutate({
43+
url: "/api/ExecBPA",
44+
queryKey: `bpa-sync-${tenantId}`,
45+
data: tenantId ? { TenantFilter: tenantId } : {},
46+
});
47+
};
48+
49+
// Reset syncing state when dialog is closed
50+
const handleClose = () => {
51+
setIsSyncing(false);
52+
createDialog.handleClose();
53+
};
54+
55+
return (
56+
<Dialog open={createDialog.open} onClose={handleClose} maxWidth="sm" fullWidth>
57+
<FormProvider {...methods}>
58+
<form onSubmit={handleSubmit(handleForm)}>
59+
<DialogTitle>Force BPA Sync</DialogTitle>
60+
<DialogContent>
61+
<div className="mb-3">
62+
<p>
63+
This will force a Best Practice Analyzer (BPA) sync. Select a tenant (or all
64+
tenants) below.
65+
</p>
66+
<CippFormTenantSelector
67+
formControl={control}
68+
name="tenantFilter"
69+
required={false}
70+
disableClearable={false}
71+
allTenants={true}
72+
type="single"
73+
/>
74+
</div>
75+
<CippApiResults apiObject={bpaSyncResults} alertSx={{ mt: 2 }} />
76+
</DialogContent>
77+
<DialogActions>
78+
<Button onClick={handleClose}>Cancel</Button>
79+
<Button
80+
type="submit"
81+
variant="contained"
82+
disabled={isSyncing && bpaSyncResults.isLoading}
83+
startIcon={<Sync />}
84+
>
85+
Sync BPA
86+
</Button>
87+
</DialogActions>
88+
</form>
89+
</FormProvider>
90+
</Dialog>
91+
);
92+
};

src/components/CippComponents/CippApiDialog.jsx

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Grid } from
33
import { Stack } from "@mui/system";
44
import { CippApiResults } from "./CippApiResults";
55
import { ApiGetCall, ApiPostCall } from "../../api/ApiCall";
6-
import { useEffect, useState } from "react";
6+
import React, { useEffect, useState } from "react";
77
import { useForm } from "react-hook-form";
88
import { useSettings } from "../../hooks/use-settings";
99
import CippFormComponent from "./CippFormComponent";
@@ -284,6 +284,10 @@ export const CippApiDialog = (props) => {
284284

285285
const [linkClicked, setLinkClicked] = useState(false);
286286

287+
useEffect(() => {
288+
setLinkClicked(false);
289+
}, [api.link]);
290+
287291
useEffect(() => {
288292
if (api.link && !linkClicked && row && Object.keys(row).length > 0) {
289293
const timeoutId = setTimeout(() => {
@@ -319,18 +323,40 @@ export const CippApiDialog = (props) => {
319323
};
320324

321325
var confirmText;
322-
if (typeof api?.confirmText === "string" && !Array.isArray(row)) {
323-
confirmText = api.confirmText.replace(/\[([^\]]+)\]/g, (_, key) => {
324-
return getNestedValue(row, key) || `[${key}]`;
325-
});
326-
} else if (Array.isArray(row) && row.length > 1) {
327-
confirmText = api.confirmText.replace(/\[([^\]]+)\]/g, "the selected rows");
328-
} else if (Array.isArray(row) && row.length === 1) {
329-
confirmText = api.confirmText.replace(/\[([^\]]+)\]/g, (_, key) => {
330-
return getNestedValue(row[0], key) || `[${key}]`;
331-
});
326+
if (typeof api?.confirmText === "string") {
327+
if (!Array.isArray(row)) {
328+
confirmText = api.confirmText.replace(/\[([^\]]+)\]/g, (_, key) => {
329+
return getNestedValue(row, key) || `[${key}]`;
330+
});
331+
} else if (row.length > 1) {
332+
confirmText = api.confirmText.replace(/\[([^\]]+)\]/g, "the selected rows");
333+
} else if (row.length === 1) {
334+
confirmText = api.confirmText.replace(/\[([^\]]+)\]/g, (_, key) => {
335+
return getNestedValue(row[0], key) || `[${key}]`;
336+
});
337+
}
332338
} else {
333-
confirmText = api.confirmText;
339+
// Handle JSX/Component confirmText
340+
const replaceTextInElement = (element) => {
341+
if (!element) return element;
342+
if (typeof element === "string") {
343+
if (Array.isArray(row) && row.length > 1) {
344+
return element.replace(/\[([^\]]+)\]/g, "the selected rows");
345+
} else if (Array.isArray(row) && row.length === 1) {
346+
return element.replace(
347+
/\[([^\]]+)\]/g,
348+
(_, key) => getNestedValue(row[0], key) || `[${key}]`
349+
);
350+
}
351+
return element.replace(/\[([^\]]+)\]/g, (_, key) => getNestedValue(row, key) || `[${key}]`);
352+
}
353+
if (React.isValidElement(element)) {
354+
const newChildren = React.Children.map(element.props.children, replaceTextInElement);
355+
return React.cloneElement(element, {}, newChildren);
356+
}
357+
return element;
358+
};
359+
confirmText = replaceTextInElement(api?.confirmText);
334360
}
335361

336362
return (

src/components/CippComponents/CippApiResults.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Close, Download, RouterOutlined } from "@mui/icons-material";
1+
import { Close, Download } from "@mui/icons-material";
22
import {
33
Alert,
44
CircularProgress,

0 commit comments

Comments
 (0)