Skip to content

Commit a674097

Browse files
Merge pull request #4431 from KelvinTegelaar/dev
Dev to release
2 parents aaa4eed + 471c082 commit a674097

File tree

89 files changed

+8881
-2794
lines changed

Some content is hidden

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

89 files changed

+8881
-2794
lines changed

.gitignore

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,5 @@ yarn-error.log*
2929
debug.log
3030
app.log
3131

32-
# Cursor IDE
33-
.cursor/rules
34-
32+
# AI rules
33+
.*/rules

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.1.1"
3-
}
2+
"version": "8.2.0"
3+
}

src/components/CippCards/CippChartCard.jsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export const CippChartCard = ({
9292
chartType = "donut",
9393
title,
9494
actions,
95+
onClick,
9596
}) => {
9697
const [range, setRange] = useState("Last 7 days");
9798
const [barSeries, setBarSeries] = useState([]);
@@ -109,7 +110,18 @@ export const CippChartCard = ({
109110
}, [chartType, chartSeries.length, labels]);
110111

111112
return (
112-
<Card style={{ width: "100%", height: "100%" }}>
113+
<Card
114+
style={{ width: "100%", height: "100%" }}
115+
onClick={onClick}
116+
sx={{
117+
cursor: onClick ? "pointer" : "default",
118+
transition: "all 0.2s ease-in-out",
119+
"&:hover": onClick ? {
120+
boxShadow: (theme) => theme.shadows[8],
121+
transform: "translateY(-2px)",
122+
} : {},
123+
}}
124+
>
113125
<CardHeader
114126
action={
115127
actions ? (
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
import React, { useState } from "react";
2+
import _ from "lodash";
3+
import {
4+
Dialog,
5+
DialogTitle,
6+
DialogContent,
7+
DialogActions,
8+
Button,
9+
Typography,
10+
Box,
11+
Chip,
12+
Stack,
13+
Divider,
14+
Card,
15+
CardContent,
16+
Grid,
17+
IconButton,
18+
Tooltip,
19+
Accordion,
20+
AccordionSummary,
21+
AccordionDetails,
22+
} from "@mui/material";
23+
import {
24+
Close as CloseIcon,
25+
Info as InfoIcon,
26+
Warning as WarningIcon,
27+
CheckCircle as CheckCircleIcon,
28+
Assignment as AssignmentIcon,
29+
Notifications as NotificationsIcon,
30+
Construction as ConstructionIcon,
31+
Public as PublicIcon,
32+
Cloud as CloudIcon,
33+
Email as EmailIcon,
34+
Security as SecurityIcon,
35+
PhoneAndroid as PhoneAndroidIcon,
36+
ExpandMore as ExpandMoreIcon,
37+
} from "@mui/icons-material";
38+
import { SvgIcon } from "@mui/material";
39+
import standards from "../../data/standards.json";
40+
41+
const getCategoryIcon = (category) => {
42+
switch (category) {
43+
case "Global Standards":
44+
return <PublicIcon fontSize="small" />;
45+
case "Entra (AAD) Standards":
46+
return <CloudIcon fontSize="small" />;
47+
case "Exchange Standards":
48+
return <EmailIcon fontSize="small" />;
49+
case "Defender Standards":
50+
return <SecurityIcon fontSize="small" />;
51+
case "Intune Standards":
52+
return <PhoneAndroidIcon fontSize="small" />;
53+
default:
54+
return <PublicIcon fontSize="small" />;
55+
}
56+
};
57+
58+
const getActionIcon = (action) => {
59+
switch (action?.toLowerCase()) {
60+
case "report":
61+
return <AssignmentIcon fontSize="small" />;
62+
case "alert":
63+
case "warn":
64+
return <NotificationsIcon fontSize="small" />;
65+
case "remediate":
66+
return <ConstructionIcon fontSize="small" />;
67+
default:
68+
return <InfoIcon fontSize="small" />;
69+
}
70+
};
71+
72+
const getImpactColor = (impact) => {
73+
switch (impact?.toLowerCase()) {
74+
case "low impact":
75+
return "info";
76+
case "medium impact":
77+
return "warning";
78+
case "high impact":
79+
return "error";
80+
default:
81+
return "default";
82+
}
83+
};
84+
85+
export const CippStandardsDialog = ({ open, onClose, standardsData, currentTenant }) => {
86+
const [expanded, setExpanded] = useState(false);
87+
if (!standardsData) return null;
88+
89+
// Get applicable templates for the current tenant
90+
const applicableTemplates = standardsData.filter((template) => {
91+
const tenantFilterArr = Array.isArray(template?.tenantFilter) ? template.tenantFilter : [];
92+
const excludedTenantsArr = Array.isArray(template?.excludedTenants)
93+
? template.excludedTenants
94+
: [];
95+
96+
const tenantInFilter =
97+
tenantFilterArr.length > 0 && tenantFilterArr.some((tf) => tf.value === currentTenant);
98+
99+
const allTenantsTemplate =
100+
tenantFilterArr.some((tf) => tf.value === "AllTenants") &&
101+
(excludedTenantsArr.length === 0 ||
102+
!excludedTenantsArr.some((et) => et.value === currentTenant));
103+
104+
return tenantInFilter || allTenantsTemplate;
105+
});
106+
107+
// Combine standards from all applicable templates
108+
const combinedStandards = {};
109+
for (const template of applicableTemplates) {
110+
for (const [standardKey, standardValue] of Object.entries(template.standards)) {
111+
combinedStandards[standardKey] = standardValue;
112+
}
113+
}
114+
115+
// Group standards by category
116+
const standardsByCategory = {};
117+
Object.entries(combinedStandards).forEach(([standardKey, standardConfig]) => {
118+
const standardInfo = standards.find((s) => s.name === `standards.${standardKey}`);
119+
if (standardInfo) {
120+
const category = standardInfo.cat;
121+
if (!standardsByCategory[category]) {
122+
standardsByCategory[category] = [];
123+
}
124+
standardsByCategory[category].push({
125+
key: standardKey,
126+
config: standardConfig,
127+
info: standardInfo,
128+
});
129+
}
130+
});
131+
132+
const handleAccordionChange = (panel) => (event, isExpanded) => {
133+
setExpanded(isExpanded ? panel : false);
134+
};
135+
136+
return (
137+
<Dialog
138+
open={open}
139+
onClose={onClose}
140+
maxWidth="lg"
141+
fullWidth
142+
PaperProps={{
143+
sx: {
144+
maxHeight: "90vh",
145+
},
146+
}}
147+
>
148+
<DialogTitle
149+
sx={{ m: 0, p: 2, display: "flex", justifyContent: "space-between", alignItems: "center" }}
150+
>
151+
<Typography variant="h6">Standards Configuration</Typography>
152+
<IconButton
153+
aria-label="close"
154+
onClick={onClose}
155+
sx={{
156+
color: (theme) => theme.palette.grey[500],
157+
}}
158+
>
159+
<CloseIcon />
160+
</IconButton>
161+
</DialogTitle>
162+
<DialogContent dividers>
163+
<Stack spacing={2}>
164+
<Box>
165+
<Typography variant="body2" color="text.secondary" gutterBottom>
166+
Showing standards configuration for tenant: <strong>{currentTenant}</strong>
167+
</Typography>
168+
<Typography variant="body2" color="text.secondary">
169+
Total templates applied: <strong>{applicableTemplates.length}</strong> | Total
170+
standards: <strong>{Object.keys(combinedStandards).length}</strong>
171+
</Typography>
172+
</Box>
173+
174+
{Object.entries(standardsByCategory).map(([category, categoryStandards], idx) => (
175+
<Accordion
176+
key={category}
177+
expanded={expanded === category}
178+
onChange={handleAccordionChange(category)}
179+
sx={{
180+
mb: 1,
181+
borderRadius: 2,
182+
boxShadow: "none",
183+
border: (theme) => `1px solid ${theme.palette.divider}`,
184+
"&:before": { display: "none" },
185+
}}
186+
>
187+
<AccordionSummary
188+
expandIcon={<ExpandMoreIcon />}
189+
aria-controls={`${category}-content`}
190+
id={`${category}-header`}
191+
sx={{
192+
minHeight: 48,
193+
"& .MuiAccordionSummary-content": { alignItems: "center", m: 0 },
194+
}}
195+
>
196+
<Stack direction="row" alignItems="center" spacing={1}>
197+
<SvgIcon color="primary">{getCategoryIcon(category)}</SvgIcon>
198+
<Typography variant="subtitle1" fontWeight={600}>
199+
{category}
200+
</Typography>
201+
<Chip
202+
label={`${categoryStandards.length} standards`}
203+
size="small"
204+
variant="outlined"
205+
/>
206+
</Stack>
207+
</AccordionSummary>
208+
<AccordionDetails sx={{ pt: 1, pb: 2 }}>
209+
<Grid container spacing={1}>
210+
{categoryStandards.map(({ key, config, info }) => (
211+
<Grid item xs={12} md={6} key={key}>
212+
<Card variant="outlined" sx={{ height: "100%", mb: 1, p: 0 }}>
213+
<CardContent sx={{ p: 1.5, "&:last-child": { pb: 1.5 } }}>
214+
<Stack spacing={1}>
215+
<Box>
216+
<Typography variant="subtitle2" fontWeight="bold">
217+
{info.label}
218+
</Typography>
219+
<Typography variant="caption" color="text.secondary">
220+
{info.helpText}
221+
</Typography>
222+
</Box>
223+
<Stack direction="row" spacing={0.5} flexWrap="wrap">
224+
<Chip
225+
label={info.impact}
226+
size="small"
227+
color={getImpactColor(info.impact)}
228+
variant="outlined"
229+
/>
230+
{info.tag && info.tag.length > 0 && (
231+
<Chip
232+
label={`${info.tag.length} tags`}
233+
size="small"
234+
variant="outlined"
235+
/>
236+
)}
237+
</Stack>
238+
<Box>
239+
<Typography variant="caption" fontWeight="bold" gutterBottom>
240+
Actions:
241+
</Typography>
242+
<Stack direction="row" spacing={0.5} flexWrap="wrap">
243+
{config.action && Array.isArray(config.action) ? (
244+
config.action.map((action, index) => (
245+
<Chip
246+
key={index}
247+
icon={getActionIcon(action.value)}
248+
label={action.value}
249+
size="small"
250+
variant="outlined"
251+
color={
252+
action.value?.toLowerCase() === "remediate"
253+
? "error"
254+
: action.value?.toLowerCase() === "alert" ||
255+
action.value?.toLowerCase() === "warn"
256+
? "warning"
257+
: "info"
258+
}
259+
/>
260+
))
261+
) : (
262+
<Typography variant="caption" color="text.secondary">
263+
No actions configured
264+
</Typography>
265+
)}
266+
</Stack>
267+
</Box>
268+
269+
{info.addedComponent && info.addedComponent.length > 0 && (
270+
<Box>
271+
<Typography variant="caption" fontWeight="bold" gutterBottom>
272+
Fields:
273+
</Typography>
274+
<Stack spacing={0.5}>
275+
{info.addedComponent.map((component, index) => {
276+
const componentValue = _.get(config, component.name);
277+
const displayValue =
278+
componentValue?.label || componentValue || "N/A";
279+
return (
280+
<Box
281+
key={index}
282+
sx={{ display: "flex", flexDirection: "column", gap: 0.5 }}
283+
>
284+
<Typography variant="caption" color="text.secondary">
285+
{component.label || component.name}:
286+
</Typography>
287+
<Chip
288+
label={displayValue}
289+
size="small"
290+
variant="outlined"
291+
/>
292+
</Box>
293+
);
294+
})}
295+
</Stack>
296+
</Box>
297+
)}
298+
</Stack>
299+
</CardContent>
300+
</Card>
301+
</Grid>
302+
))}
303+
</Grid>
304+
</AccordionDetails>
305+
</Accordion>
306+
))}
307+
308+
{Object.keys(standardsByCategory).length === 0 && (
309+
<Box textAlign="center" py={4}>
310+
<Typography variant="h6" color="text.secondary">
311+
No standards configured for this tenant
312+
</Typography>
313+
<Typography variant="body2" color="text.secondary">
314+
Standards templates may not be applied to this tenant or no standards are currently
315+
active.
316+
</Typography>
317+
</Box>
318+
)}
319+
</Stack>
320+
</DialogContent>
321+
<DialogActions>
322+
<Button onClick={onClose}>Close</Button>
323+
</DialogActions>
324+
</Dialog>
325+
);
326+
};

0 commit comments

Comments
 (0)